From b4ad3a3c4b68f9c8736f444aeb3364f833247fdc Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 21 Feb 2023 22:18:17 +0100 Subject: [PATCH 0001/1309] Add support for ARM dot product instructions The sdot instruction computes (and accumulates) a signed dot product, which is quite handy for Stockfish's NNUE code. The instruction is optional for Armv8.2 and Armv8.3, and mandatory for Armv8.4 and above. The commit adds a new 'arm-dotprod' architecture with enabled dot product support. It also enables dot product support for the existing 'apple-silicon' architecture, which is at least Armv8.5. The following local speed test was performed on an Apple M1 with ARCH=apple-silicon. I had to remove CPU pinning from the benchmark script. However, the results were still consistent: Checking both binaries against themselves reported a speedup of +0.0000 and +0.0005, respectively. ``` Result of 100 runs ================== base (...ish.037ef3e1) = 1917997 +/- 7152 test (...fish.dotprod) = 2159682 +/- 9066 diff = +241684 +/- 2923 speedup = +0.1260 P(speedup > 0) = 1.0000 CPU: 10 x arm Hyperthreading: off ``` Fixes #4193 closes https://github.com/official-stockfish/Stockfish/pull/4400 No functional change --- src/Makefile | 65 +++++++++++++++++++----------- src/nnue/layers/affine_transform.h | 24 +++++++++++ src/nnue/layers/simd.h | 13 ++++++ 3 files changed, 78 insertions(+), 24 deletions(-) diff --git a/src/Makefile b/src/Makefile index 775c72c36e9..3d6432fd96b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -69,32 +69,33 @@ VPATH = syzygy:nnue:nnue/features ### Section 2. High-level Configuration ### ========================================================================== # -# flag --- Comp switch --- Description +# flag --- Comp switch --- Description # ---------------------------------------------------------------------------- # -# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode +# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode # sanitize = none/ ... (-fsanitize ) -# --- ( undefined ) --- enable undefined behavior checks -# --- ( thread ) --- enable threading error checks -# --- ( address ) --- enable memory access checks -# --- ...etc... --- see compiler documentation for supported sanitizers -# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations -# arch = (name) --- (-arch) --- Target architecture -# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system -# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction -# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction -# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction -# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions -# mmx = yes/no --- -mmmx --- Use Intel MMX instructions -# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 -# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 -# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 -# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 -# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX -# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 -# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 -# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 -# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture +# --- ( undefined ) --- enable undefined behavior checks +# --- ( thread ) --- enable threading error checks +# --- ( address ) --- enable memory access checks +# --- ...etc... --- see compiler documentation for supported sanitizers +# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations +# arch = (name) --- (-arch) --- Target architecture +# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system +# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction +# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction +# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction +# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions +# mmx = yes/no --- -mmmx --- Use Intel MMX instructions +# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 +# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 +# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 +# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 +# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX +# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 +# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 +# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 +# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture +# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions # # Note that Makefile is space sensitive, so when adding new architectures # or modifying existing flags, you have to make sure there are no extra spaces @@ -116,7 +117,7 @@ ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ - armv7 armv7-neon armv8 apple-silicon general-64 general-32 riscv64)) + armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64)) SUPPORTED_ARCH=true else SUPPORTED_ARCH=false @@ -140,6 +141,7 @@ avx512 = no vnni256 = no vnni512 = no neon = no +dotprod = no arm_version = 0 STRIP = strip @@ -308,11 +310,21 @@ ifeq ($(ARCH),armv8) arm_version = 8 endif +ifeq ($(ARCH),armv8-dotprod) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes + dotprod = yes + arm_version = 8 +endif + ifeq ($(ARCH),apple-silicon) arch = arm64 prefetch = yes popcnt = yes neon = yes + dotprod = yes arm_version = 8 endif @@ -675,6 +687,10 @@ ifeq ($(neon),yes) endif endif +ifeq ($(dotprod),yes) + CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD +endif + ### 3.7 pext ifeq ($(pext),yes) CXXFLAGS += -DUSE_PEXT @@ -776,6 +792,7 @@ help: @echo "armv7 > ARMv7 32-bit" @echo "armv7-neon > ARMv7 32-bit with popcnt and neon" @echo "armv8 > ARMv8 64-bit with popcnt and neon" + @echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" @echo "e2k > Elbrus 2000" @echo "apple-silicon > Apple silicon ARM64" @echo "general-64 > unspecified 64-bit" diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 363b4916e37..63b58af33c3 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -72,6 +72,10 @@ namespace Stockfish::Eval::NNUE::Layers { const __m64 Zeros = _mm_setzero_si64(); const auto inputVector = reinterpret_cast(input); +# elif defined(USE_NEON_DOTPROD) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + # elif defined(USE_NEON) constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const auto inputVector = reinterpret_cast(input); @@ -123,6 +127,14 @@ namespace Stockfish::Eval::NNUE::Layers { sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); output[i] = _mm_cvtsi64_si32(sum); +# elif defined(USE_NEON_DOTPROD) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { + sum = vdotq_s32(sum, inputVector[j], row[j]); + } + output[i] = vaddvq_s32(sum); + # elif defined(USE_NEON) int32x4_t sum = {biases[i]}; const auto row = reinterpret_cast(&weights[offset]); @@ -187,6 +199,9 @@ namespace Stockfish::Eval::NNUE::Layers { #elif defined (USE_SSSE3) static constexpr IndexType InputSimdWidth = 16; static constexpr IndexType MaxNumOutputRegs = 8; +#elif defined (USE_NEON_DOTPROD) + static constexpr IndexType InputSimdWidth = 16; + static constexpr IndexType MaxNumOutputRegs = 8; #elif defined (USE_NEON) static constexpr IndexType InputSimdWidth = 8; static constexpr IndexType MaxNumOutputRegs = 8; @@ -292,6 +307,15 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 #define vec_hadd Simd::m128_hadd #define vec_haddx4 Simd::m128_haddx4 +#elif defined (USE_NEON_DOTPROD) + using acc_vec_t = int32x4_t; + using bias_vec_t = int32x4_t; + using weight_vec_t = int8x16_t; + using in_vec_t = int8x16_t; + #define vec_zero {0} + #define vec_add_dpbusd_32x2 Simd::dotprod_m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::neon_m128_hadd + #define vec_haddx4 Simd::neon_m128_haddx4 #elif defined (USE_NEON) using acc_vec_t = int32x4_t; using bias_vec_t = int32x4_t; diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 381e7a68f8e..22c51980ecc 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -346,6 +346,19 @@ namespace Stockfish::Simd { #endif +#if defined (USE_NEON_DOTPROD) + + [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( + int32x4_t& acc, + int8x16_t a0, int8x16_t b0, + int8x16_t a1, int8x16_t b1) { + + acc = vdotq_s32(acc, a0, b0); + acc = vdotq_s32(acc, a1, b1); + } + +#endif + #if defined (USE_NEON) [[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { From 77dfcbedce2861b2c6c5056d49e7a8731fea4256 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 19 Feb 2023 11:25:10 +0100 Subject: [PATCH 0002/1309] Remove unused macros closes https://github.com/official-stockfish/Stockfish/pull/4397 No functional change --- src/nnue/layers/affine_transform.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 63b58af33c3..313b1568393 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -480,18 +480,14 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_add_dpbusd_32x4 Simd::m256_add_dpbusd_epi32x4 #define vec_hadd Simd::m256_hadd - #define vec_haddx4 Simd::m256_haddx4 #elif defined (USE_SSSE3) using vec_t = __m128i; #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_add_dpbusd_32x4 Simd::m128_add_dpbusd_epi32x4 #define vec_hadd Simd::m128_hadd - #define vec_haddx4 Simd::m128_haddx4 #endif #if defined (USE_SSSE3) @@ -542,9 +538,7 @@ namespace Stockfish::Eval::NNUE::Layers { # undef vec_set_32 # undef vec_add_dpbusd_32 # undef vec_add_dpbusd_32x2 -# undef vec_add_dpbusd_32x4 # undef vec_hadd -# undef vec_haddx4 #else // Use old implementation for the other architectures. affine_transform_non_ssse3< From 08385527dd470ece814ac85013802995a0e7f6ca Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 20 Feb 2023 20:02:55 +0100 Subject: [PATCH 0003/1309] Introduce a function to compute NNUE accumulator This patch introduces `hint_common_parent_position()` to signal that potentially several child nodes will require an NNUE eval. By populating explicitly the accumulator, these subsequent evaluations can be performed more efficiently. This was based on the observation that calculating the evaluation in an excluded move position yielded a significant Elo gain, even though the evaluation itself was already available (work by pb00067). Sopel wrote the code to perform just the accumulator update. This PR is based on cleaned up code that passed STC: https://tests.stockfishchess.org/tests/view/63f62f9be74a12625bcd4aa0 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 110368 W: 29607 L: 29167 D: 51594 Ptnml(0-2): 41, 10551, 33572, 10967, 53 and in an the earlier (equivalent) version passed STC: https://tests.stockfishchess.org/tests/view/63f3c3fee74a12625bcce2a6 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 47552 W: 12786 L: 12467 D: 22299 Ptnml(0-2): 120, 5107, 12997, 5438, 114 passed LTC: https://tests.stockfishchess.org/tests/view/63f45cc2e74a12625bccfa63 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 110368 W: 29607 L: 29167 D: 51594 Ptnml(0-2): 41, 10551, 33572, 10967, 53 closes https://github.com/official-stockfish/Stockfish/pull/4402 Bench: 3726250 --- src/evaluate.h | 1 + src/nnue/evaluate_nnue.cpp | 5 + src/nnue/evaluate_nnue.h | 1 + src/nnue/nnue_feature_transformer.h | 421 +++++++++++++++++----------- src/search.cpp | 8 +- 5 files changed, 264 insertions(+), 172 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index cdea2ab23f3..46f202594e6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -45,6 +45,7 @@ namespace Eval { std::string trace(Position& pos); Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); + void hint_common_parent_position(const Position& pos); void init(); void verify(); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index f132de71385..f33aa3b889b 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -136,6 +136,11 @@ namespace Stockfish::Eval::NNUE { return (bool)stream; } + void hint_common_parent_position(const Position& pos) { + if (Eval::useNNUE) + featureTransformer->hint_common_access(pos); + } + // Evaluation function. Perform differential calculation. Value evaluate(const Position& pos, bool adjusted, int* complexity) { diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 9499f7d99ab..15638caeeaf 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -31,6 +31,7 @@ namespace Stockfish::Eval::NNUE { constexpr std::uint32_t HashValue = FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); + // Deleter for automating release of memory area template struct AlignedDeleter { diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 62f1615d5fe..13f1604fe13 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -25,6 +25,7 @@ #include "nnue_architecture.h" #include // std::memset() +#include // std::pair namespace Stockfish::Eval::NNUE { @@ -332,27 +333,16 @@ namespace Stockfish::Eval::NNUE { #endif return psqt; + } // end of function transform() - } // end of function transform() - - + void hint_common_access(const Position& pos) const { + hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); + } private: template - void update_accumulator(const Position& pos) const { - - // 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. - - #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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif - + [[nodiscard]] std::pair 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(), *next = nullptr; @@ -367,218 +357,313 @@ namespace Stockfish::Eval::NNUE { next = st; st = st->previous; } + return { st, next }; + } - if (st->accumulator.computed[Perspective]) - { - if (next == nullptr) - return; + // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. + // All states must be sequential, that is states_to_update[i] must either be reachable + // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. + // computed_st must be reachable by repeatadly applying ->previous on states_to_update[0], if not nullptr. + template + void update_accumulator_incremetal(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { + static_assert(N > 0); + assert(states_to_update[N-1] == nullptr); - // Update incrementally in two steps. First, we update the "next" - // accumulator. Then, we update the current accumulator (pos.state()). + #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[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; + #endif - // Gather all features to be updated. - const Square ksq = pos.square(Perspective); - FeatureSet::IndexList removed[2], added[2]; - FeatureSet::append_changed_indices( - ksq, next->dirtyPiece, removed[0], added[0]); - for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) - FeatureSet::append_changed_indices( - ksq, st2->dirtyPiece, removed[1], added[1]); + if (states_to_update[0] == nullptr) + return; - // Mark the accumulators as computed. - next->accumulator.computed[Perspective] = true; - pos.state()->accumulator.computed[Perspective] = true; + // Update incrementally going back through states_to_update. - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. - StateInfo *states_to_update[3] = - { next, next == pos.state() ? nullptr : pos.state(), nullptr }; - #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - // Load accumulator - auto accTile = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTile[k]); + // Gather all features to be updated. + const Square ksq = pos.square(Perspective); - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - accTile = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTile[k], acc[k]); - } - } + // 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. + FeatureSet::IndexList removed[N-1], added[N-1]; - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - // Load accumulator - auto accTilePsqt = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqt[k]); + { + int i = N-2; // last potential state to update. Skip last element because it must be nullptr. + while (states_to_update[i] == nullptr) + --i; - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - // Store accumulator - accTilePsqt = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - } + StateInfo *st2 = states_to_update[i]; - #else - for (IndexType i = 0; states_to_update[i]; ++i) + for (; i >= 0; --i) { - std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], - st->accumulator.accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); + states_to_update[i]->accumulator.computed[Perspective] = true; - for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; + StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + + for (; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices( + ksq, st2->dirtyPiece, removed[i], added[i]); + } + } + + StateInfo* st = computed_st; - st = states_to_update[i]; + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. +#ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTile = reinterpret_cast( + &st->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTile[k]); + for (IndexType i = 0; states_to_update[i]; ++i) + { // Difference calculation for the deactivated features for (const auto index : removed[i]) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); } // Difference calculation for the activated features for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } - } - #endif - } - else - { - // Refresh the accumulator - auto& accumulator = pos.state()->accumulator; - accumulator.computed[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); - - #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast( - &biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - for (const auto index : active) { const IndexType offset = HalfDimensions * index + j * TileHeight; auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_add_16(acc[k], column[k]); } - auto accTile = reinterpret_cast( - &accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) + // Store accumulator + accTile = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) vec_store(&accTile[k], acc[k]); } + } - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + // Load accumulator + auto accTilePsqt = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_load_psqt(&accTilePsqt[k]); - for (const auto index : active) + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) { const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + // Store accumulator + accTilePsqt = reinterpret_cast( + &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } + } - #else - std::memcpy(accumulator.accumulation[Perspective], biases, +#else + for (IndexType i = 0; states_to_update[i]; ++i) + { + std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], + st->accumulator.accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; + states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; - for (const auto index : active) + st = states_to_update[i]; + + // Difference calculation for the deactivated features + for (const auto index : removed[i]) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; + st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } + } +#endif + + #if defined(USE_MMX) + _mm_empty(); + #endif + } + + template + void update_accumulator_refresh(const Position& pos) const { + #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[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; #endif + + // Refresh the accumulator + // Could be extracted to a separate function because it's done in 2 places, + // but it's unclear if compilers would correctly handle register allocation. + auto& accumulator = pos.state()->accumulator; + accumulator.computed[Perspective] = true; + FeatureSet::IndexList active; + FeatureSet::append_active_indices(pos, active); + +#ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto biasesTile = reinterpret_cast( + &biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = reinterpret_cast( + &accumulator.accumulation[Perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); + + for (const auto index : active) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); } +#else + std::memcpy(accumulator.accumulation[Perspective], biases, + HalfDimensions * sizeof(BiasType)); + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] = 0; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } +#endif + #if defined(USE_MMX) _mm_empty(); #endif } + template + void hint_common_access_for_perspective(const Position& pos) const { + + // Works like update_accumulator, but performs less work. + // Updates ONLY the accumulator for pos. + + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + // Fast early exit. + if (pos.state()->accumulator.computed[Perspective]) + return; + + auto [oldest_st, _] = try_find_computed_accumulator(pos); + + if (oldest_st->accumulator.computed[Perspective]) + { + // Only update current position accumulator to minimize work. + StateInfo* states_to_update[2] = { pos.state(), nullptr }; + update_accumulator_incremetal(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } + } + + template + void update_accumulator(const Position& pos) const { + + auto [oldest_st, next] = try_find_computed_accumulator(pos); + + if (oldest_st->accumulator.computed[Perspective]) + { + if (next == nullptr) + return; + + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Currently we update 2 accumulators. + // 1. for the current position + // 2. the next accumulator after the computed one + // The heuristic may change in the future. + StateInfo *states_to_update[3] = + { next, next == pos.state() ? nullptr : pos.state(), nullptr }; + + update_accumulator_incremetal(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } + } + alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; diff --git a/src/search.cpp b/src/search.cpp index 6ca2cfa5adb..5cb9750c323 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -730,10 +730,10 @@ namespace { goto moves_loop; } else if (excludedMove) { - // excludeMove implies that we had a ttHit on the containing non-excluded search with ss->staticEval filled from TT - // However static evals from the TT aren't good enough (-13 elo), presumably due to changing optimism context - // Recalculate value with current optimism (without updating thread avgComplexity) - ss->staticEval = eval = evaluate(pos, &complexity); + // 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); + eval = ss->staticEval; + complexity = abs(ss->staticEval - pos.psq_eg_stm()); } else if (ss->ttHit) { From 69639d764bde566e524b8c2566119bf677cb2622 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 21 Feb 2023 11:17:59 -0500 Subject: [PATCH 0004/1309] Reintroduce nnue pawn scaling with lower lazy thresholds Params found with the nevergrad TBPSA optimizer via nevergrad4sf modified to: * use SPRT LLR with fishtest STC elo gainer bounds [0, 2] as the objective function * increase the game batch size after each new optimal point is found The params were the optimal point after TBPSA iteration 7 and 160 nevergrad evaluations with: * initial batch size of 96 games per evaluation * batch size increase of 64 games after each iteration * a budget of 512 evaluations * TC: fixed 1.5 million nodes per move, no time limit nevergrad4sf enables optimizing stockfish params with TBPSA: https://github.com/vondele/nevergrad4sf Using pentanomial game results with smaller game batch sizes was inspired by: Use of SPRT LLR calculated from pentanomial game results as the objective function was an experiment at maximizing the information from game batches to reduce the computational cost for TBPSA to converge on good parameters. For the exact code used to find the params: https://github.com/linrock/tuning-fork Passed STC: https://tests.stockfishchess.org/tests/view/63f4ef5ee74a12625bcd114a LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 66552 W: 17736 L: 17390 D: 31426 Ptnml(0-2): 164, 7229, 18166, 7531, 186 Passed LTC: https://tests.stockfishchess.org/tests/view/63f56028e74a12625bcd2550 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 71264 W: 19150 L: 18787 D: 33327 Ptnml(0-2): 23, 6728, 21771, 7083, 27 closes https://github.com/official-stockfish/Stockfish/pull/4401 bench 3687580 --- src/evaluate.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 080d412b7d9..cf6f23eac55 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -193,8 +193,8 @@ using namespace Trace; namespace { // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(3631); - constexpr Value LazyThreshold2 = Value(2084); + constexpr Value LazyThreshold1 = Value(3622); + constexpr Value LazyThreshold2 = Value(1962); constexpr Value SpaceThreshold = Value(11551); // KingAttackWeights[PieceType] contains king attack weights by piece type @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { else { int nnueComplexity; - int scale = 1076 + 96 * pos.non_pawn_material() / 5120; + int scale = 1001 + 5 * pos.count() + 61 * pos.non_pawn_material() / 4096; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; From 29b5ad5deaf323f43019443b322090caec13f847 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Thu, 23 Feb 2023 19:01:51 +0100 Subject: [PATCH 0005/1309] Fix typo in method name closes https://github.com/official-stockfish/Stockfish/pull/4404 No functional change --- src/nnue/nnue_feature_transformer.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 13f1604fe13..b0d5743e669 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -365,7 +365,7 @@ namespace Stockfish::Eval::NNUE { // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. // computed_st must be reachable by repeatadly applying ->previous on states_to_update[0], if not nullptr. template - void update_accumulator_incremetal(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { + void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { static_assert(N > 0); assert(states_to_update[N-1] == nullptr); @@ -630,7 +630,7 @@ namespace Stockfish::Eval::NNUE { { // Only update current position accumulator to minimize work. StateInfo* states_to_update[2] = { pos.state(), nullptr }; - update_accumulator_incremetal(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else { @@ -656,7 +656,7 @@ namespace Stockfish::Eval::NNUE { StateInfo *states_to_update[3] = { next, next == pos.state() ? nullptr : pos.state(), nullptr }; - update_accumulator_incremetal(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else { From 472e726bff0d0e496dc8359cc071726a76317a72 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 24 Feb 2023 18:25:24 +0300 Subject: [PATCH 0006/1309] Search tuning at very long time control This patch is a result of tuning session of approximately 100k games at 120+1.2. Biggest changes are in extensions, stat bonus and depth reduction for nodes without a tt move. Failed STC: https://tests.stockfishchess.org/tests/view/63f72c72e74a12625bcd7938 LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 13872 W: 3535 L: 3769 D: 6568 Ptnml(0-2): 56, 1621, 3800, 1419, 40 Close to neutral at LTC: https://tests.stockfishchess.org/tests/view/63f738f5e74a12625bcd7b8a Elo: 0.80 +-1.2 (95%) LOS: 90.0% Total: 60000 W: 16213 L: 16074 D: 27713 Ptnml(0-2): 24, 5718, 18379, 5853, 26 nElo: 1.82 +-2.8 (95%) PairsRatio: 1.02 Passed 180+1.8 VLTC: https://tests.stockfishchess.org/tests/view/63f868f3e74a12625bcdb33e LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 15864 W: 4449 L: 4202 D: 7213 Ptnml(0-2): 1, 1301, 5083, 1544, 3 Passed 60+0.6 8 threads SMP VLTC: https://tests.stockfishchess.org/tests/view/63f8a5d6e74a12625bcdbdb3 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 6288 W: 1821 L: 1604 D: 2863 Ptnml(0-2): 0, 402, 2123, 619, 0 closes https://github.com/official-stockfish/Stockfish/pull/4406 bench 4705194 --- src/search.cpp | 102 ++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5cb9750c323..a41ea4fd269 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,7 +63,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(158 * (d - improving)); + return Value(154 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -71,7 +71,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1460 - int(delta) * 1024 / int(rootDelta)) / 1024 + (!i && r > 937); + return (r + 1449 - int(delta) * 1032 / int(rootDelta)) / 1024 + (!i && r > 941); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -81,7 +81,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(350 * d - 400, 1650); + return std::min(340 * d - 470, 1855); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -161,7 +161,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.26 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((19.47 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -354,12 +354,12 @@ void Thread::search() { if (rootDepth >= 4) { Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 15400; + delta = Value(10) + int(prev) * prev / 16502; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 116 * prev / (std::abs(prev) + 170); + int opt = 120 * prev / (std::abs(prev) + 161); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; } @@ -462,16 +462,16 @@ void Thread::search() { && !Threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (71 + 12 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 656.7; + double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 619.6; fallingEval = std::clamp(fallingEval, 0.5, 1.5); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.37 : 0.65; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.15 * timeReduction); - double bestMoveInstability = 1 + 1.7 * totBestMoveChanges / Threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); + double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::min(1.0 + (complexity - 261) / 1738.7, 1.5); + double complexPosition = std::min(1.03 + (complexity - 241) / 1552.0, 1.45); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; @@ -491,7 +491,7 @@ void Thread::search() { Threads.stop = true; } else if ( !mainThread->ponder - && Time.elapsed() > totalTime * 0.53) + && Time.elapsed() > totalTime * 0.50) Threads.increaseDepth = false; else Threads.increaseDepth = true; @@ -760,7 +760,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1940, 1940); + int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1920, 1920); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -770,13 +770,13 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 172; + : 156; improving = improvement > 0; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 394 - 255 * depth * depth) + if (eval < alpha - 426 - 252 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -786,19 +786,19 @@ namespace { // Step 8. Futility pruning: child node (~40 Elo). // The depth condition is important for mate finding. if ( !ss->ttPv - && depth < 8 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 304 >= beta + && depth < 9 + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 280 >= beta && eval >= beta - && eval < 28580) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 25128) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 18200 + && (ss-1)->statScore < 18755 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - improvement / 14 + 235 + complexity / 24 + && ss->staticEval >= beta - 19 * depth - improvement / 13 + 253 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -806,7 +806,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 165, 6) + depth / 3 + 4 - (complexity > 800); + Depth R = std::min(int(eval - beta) / 168, 6) + depth / 3 + 4 - (complexity > 825); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -842,7 +842,7 @@ namespace { } } - probCutBeta = beta + 180 - 54 * improving; + probCutBeta = beta + 186 - 54 * improving; // Step 10. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value @@ -904,14 +904,14 @@ namespace { return qsearch(pos, ss, alpha, beta); if ( cutNode - && depth >= 9 + && depth >= 7 && !ttMove) depth -= 2; moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 402; + probCutBeta = beta + 391; if ( ss->inCheck && !PvNode && depth >= 2 @@ -1006,14 +1006,14 @@ namespace { // Futility pruning for captures (~2 Elo) if ( !givesCheck && !PvNode - && lmrDepth < 7 + && lmrDepth < 6 && !ss->inCheck - && ss->staticEval + 185 + 203 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 6 < alpha) + && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-220) * depth)) + if (!pos.see_ge(move, Value(-206) * depth)) continue; } else @@ -1024,24 +1024,24 @@ namespace { // Continuation history based pruning (~2 Elo) if ( lmrDepth < 5 - && history < -4180 * (depth - 1)) + && history < -4405 * (depth - 1)) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7208; + lmrDepth += history / 7278; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck && lmrDepth < 13 - && ss->staticEval + 103 + 136 * lmrDepth <= alpha) + && ss->staticEval + 103 + 138 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-25 * lmrDepth * lmrDepth - 16 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1056,7 +1056,7 @@ namespace { // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 21) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ @@ -1064,7 +1064,7 @@ namespace { && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (3 + (ss->ttPv && !PvNode)) * depth; + Value singularBeta = ttValue - (2 + (ss->ttPv && !PvNode)) * depth; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1083,7 +1083,7 @@ namespace { && ss->doubleExtensions <= 10) { extension = 2; - depth += depth < 12; + depth += depth < 13; } } @@ -1106,15 +1106,15 @@ namespace { // Check extensions (~1 Elo) else if ( givesCheck - && depth > 9 - && abs(ss->staticEval) > 78) + && depth > 10 + && abs(ss->staticEval) > 88) extension = 1; // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5600) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5705) extension = 1; } @@ -1155,7 +1155,7 @@ namespace { // Decrease reduction for PvNodes based on depth if (PvNode) - r -= 1 + 11 / (3 + depth); + r -= 1 + 12 / (3 + depth); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) @@ -1172,17 +1172,17 @@ namespace { // Decrease reduction if move is a killer and we have a good history if (move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 3600) + && (*contHist[0])[movedPiece][to_sq(move)] >= 3722) r--; ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4467; + - 4182; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (12800 + 4410 * (depth > 7 && depth < 19)); + r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1206,8 +1206,8 @@ namespace { { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (alpha + 66 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 582 && ss->doubleExtensions <= 5; + const bool doDeeperSearch = value > (alpha + 58 + 12 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1318,8 +1318,8 @@ namespace { // Reduce other moves if we have found at least one score improvement if ( depth > 1 && depth < 6 - && beta < VALUE_KNOWN_WIN - && alpha > -VALUE_KNOWN_WIN) + && beta < 10534 + && alpha > -10534) depth -= 1; assert(depth > 0); @@ -1374,7 +1374,7 @@ namespace { else if (!priorCapture) { // Extra bonuses for PV/Cut nodes or bad fail lows - int bonus = (depth > 4) + (PvNode || cutNode) + (bestValue < alpha - 88 * depth); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } @@ -1502,7 +1502,7 @@ namespace { if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 158; + futilityBase = bestValue + 168; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1575,7 +1575,7 @@ namespace { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-108))) + if (!pos.see_ge(move, Value(-110))) continue; } @@ -1708,7 +1708,7 @@ namespace { if (!pos.capture(bestMove)) { - int bonus2 = bestValue > beta + 146 ? bonus1 // larger bonus + int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 98dafda6c8d432924821d085fd958c89bc8834c3 Mon Sep 17 00:00:00 2001 From: Alfredo Menezes Date: Mon, 27 Feb 2023 00:39:26 -0300 Subject: [PATCH 0007/1309] Simplify condition in step 15 Remove 'ttValue <= alpha' check for negative extension in singular search. Also apply some small code style changes. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 127888 W: 33766 L: 33651 D: 60471 Ptnml(0-2): 303, 14082, 35089, 14137, 333 https://tests.stockfishchess.org/tests/view/63f79528e74a12625bcd8c05 LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 89048 W: 23924 L: 23782 D: 41342 Ptnml(0-2): 27, 8635, 27065, 8763, 34 https://tests.stockfishchess.org/tests/view/63f82177e74a12625bcda6f4 LTC (retest): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 196360 W: 52514 L: 52475 D: 91371 Ptnml(0-2): 103, 19066, 59780, 19151, 80 https://tests.stockfishchess.org/tests/view/63f934bfe74a12625bcdd929 closes https://github.com/official-stockfish/Stockfish/pull/4407 Bench: 5310866 --- src/search.cpp | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a41ea4fd269..6ccc70cc673 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -334,7 +334,7 @@ void Thread::search() { pvLast = 0; if (!Threads.increaseDepth) - searchAgainCounter++; + searchAgainCounter++; // MultiPV loop. We perform a full root search for each PV line for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) @@ -432,9 +432,10 @@ void Thread::search() { if (!Threads.stop) completedDepth = rootDepth; - if (rootMoves[0].pv[0] != lastBestMove) { - lastBestMove = rootMoves[0].pv[0]; - lastBestMoveDepth = rootDepth; + if (rootMoves[0].pv[0] != lastBestMove) + { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; } // Have we found a "mate in x"? @@ -729,7 +730,8 @@ namespace { complexity = 0; goto moves_loop; } - else if (excludedMove) { + 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); eval = ss->staticEval; @@ -755,6 +757,7 @@ namespace { // Save static evaluation into transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } + thisThread->complexityAverage.update(complexity); // Use static evaluation difference to improve quiet move ordering (~4 Elo) @@ -920,11 +923,9 @@ namespace { && tte->depth() >= depth - 3 && ttValue >= probCutBeta && abs(ttValue) <= VALUE_KNOWN_WIN - && abs(beta) <= VALUE_KNOWN_WIN - ) + && abs(beta) <= VALUE_KNOWN_WIN) return probCutBeta; - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, nullptr , (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; @@ -1099,8 +1100,8 @@ namespace { else if (ttValue >= beta) extension = -2; - // If the eval of ttMove is less than alpha and value, we reduce it (negative extension) - else if (ttValue <= alpha && ttValue <= value) + // If the eval of ttMove is less than value, we reduce it (negative extension) + else if (ttValue <= value) extension = -1; } @@ -1227,11 +1228,11 @@ namespace { // Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. else if (!PvNode || moveCount > 1) { - // Increase reduction for cut nodes and not ttMove (~1 Elo) - if (!ttMove && cutNode) - r += 2; + // Increase reduction for cut nodes and not ttMove (~1 Elo) + if (!ttMove && cutNode) + r += 2; - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail @@ -1271,14 +1272,17 @@ namespace { rm.selDepth = thisThread->selDepth; rm.scoreLowerbound = rm.scoreUpperbound = false; - if (value >= beta) { - rm.scoreLowerbound = true; - rm.uciScore = beta; + if (value >= beta) + { + rm.scoreLowerbound = true; + rm.uciScore = beta; } - else if (value <= alpha) { - rm.scoreUpperbound = true; - rm.uciScore = alpha; + else if (value <= alpha) + { + rm.scoreUpperbound = true; + rm.uciScore = alpha; } + rm.pv.resize(1); assert((ss+1)->pv); @@ -1447,7 +1451,8 @@ namespace { // TT entry depth that we are going to use. Note that in qsearch we use // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS - : DEPTH_QS_NO_CHECKS; + : DEPTH_QS_NO_CHECKS; + // Step 3. Transposition table lookup posKey = pos.key(); tte = TT.probe(posKey, ss->ttHit); @@ -1577,7 +1582,6 @@ namespace { // Do not search moves with bad enough SEE values (~5 Elo) if (!pos.see_ge(move, Value(-110))) continue; - } // Speculative prefetch as early as possible From 728b963614a765f5cb64c44a078169cca977750f Mon Sep 17 00:00:00 2001 From: pb00067 Date: Sun, 26 Feb 2023 09:59:35 +0100 Subject: [PATCH 0008/1309] Use common_parent_position hint also at PVNodes TT hits. Credits to Stefan Geschwentner (locutus2) showing that the hint is useful on PvNodes. In contrast to his test, this version avoids to use the hint when in check. I believe checking positions aren't good candidates for the hint because: - evasion moves are rather few, so a checking pos. has much less childs than a normal position - if the king has to move the NNUE eval can't use incremental updates, so the child nodes have to do a full refresh anyway. Passed STC: https://tests.stockfishchess.org/tests/view/63f9c5b1e74a12625bcdf585 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 124472 W: 33268 L: 32846 D: 58358 Ptnml(0-2): 350, 12986, 35170, 13352, 378 closes https://github.com/official-stockfish/Stockfish/pull/4410 no functional change --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 6ccc70cc673..206779ed1be 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -744,7 +744,11 @@ namespace { if (eval == VALUE_NONE) ss->staticEval = eval = evaluate(pos, &complexity); else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost + { complexity = abs(ss->staticEval - pos.psq_eg_stm()); + if (PvNode) + Eval::NNUE::hint_common_parent_position(pos); + } // ttValue can be used as a better position evaluation (~7 Elo) if ( ttValue != VALUE_NONE From ff5a6f8df196d61a0d9b1ebe54d84eeb9af20079 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 26 Feb 2023 09:56:54 +0100 Subject: [PATCH 0009/1309] NNUE accumulator update in probcut. Call the recently added hint function for NNUE accumulator update after a failed probcut search. In this case we already searched at least some captures and tt move which, however, is not sufficient for a cutoff. So it seems we have a greater chance that the full search will also have no cutoff and hence all moves have to be searched. STC: https://tests.stockfishchess.org/tests/view/63fa74a4e74a12625bce1823 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 70096 W: 18770 L: 18423 D: 32903 Ptnml(0-2): 191, 7342, 19654, 7651, 210 To be sure that we have no heavy interaction retest on top of #4410. Rebased STC: https://tests.stockfishchess.org/tests/view/63fb2f62e74a12625bce3b03 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 137688 W: 36790 L: 36349 D: 64549 Ptnml(0-2): 397, 14373, 38919, 14702, 453 closes https://github.com/official-stockfish/Stockfish/pull/4411 No functional change --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 206779ed1be..cfb569b9093 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -899,6 +899,8 @@ namespace { return value; } } + + Eval::NNUE::hint_common_parent_position(pos); } // Step 11. If the position is not in TT, decrease depth by 3. From 564456a6a824bfca26d6d9af5b35a055eb9fc6c2 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 26 Feb 2023 19:42:31 +0100 Subject: [PATCH 0010/1309] Unify type alias declarations The commit unifies the declaration of type aliases by replacing all typedefs with corresponding using statements. closing https://github.com/official-stockfish/Stockfish/pull/4412 No functional change --- src/material.h | 2 +- src/misc.cpp | 12 ++++++------ src/misc.h | 4 ++-- src/movepick.h | 14 +++++++------- src/nnue/nnue_feature_transformer.h | 20 ++++++++++---------- src/pawns.h | 2 +- src/position.h | 2 +- src/search.h | 2 +- src/syzygy/tbprobe.cpp | 4 ++-- src/tune.h | 6 +++--- src/types.h | 6 +++--- src/uci.h | 4 ++-- 12 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/material.h b/src/material.h index 73c831006bf..9acf78f5ab4 100644 --- a/src/material.h +++ b/src/material.h @@ -62,7 +62,7 @@ struct Entry { uint8_t factor[COLOR_NB]; }; -typedef HashTable Table; +using Table = HashTable; Entry* probe(const Position& pos); diff --git a/src/misc.cpp b/src/misc.cpp index e65faab9422..c22126afe2d 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -32,12 +32,12 @@ // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. extern "C" { -typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP, - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); -typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY); -typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); -typedef bool(*fun4_t)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); -typedef WORD(*fun5_t)(); +using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY); +using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); +using fun5_t = WORD(*)(); } #endif diff --git a/src/misc.h b/src/misc.h index 9761da8addd..c20a816efa0 100644 --- a/src/misc.h +++ b/src/misc.h @@ -45,7 +45,7 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); -typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds +using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); inline TimePoint now() { return std::chrono::duration_cast @@ -165,7 +165,7 @@ class PRNG { inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #if defined(__GNUC__) && defined(IS_64BIT) - __extension__ typedef unsigned __int128 uint128; + __extension__ using uint128 = unsigned __int128; return ((uint128)a * (uint128)b) >> 64; #else uint64_t aL = (uint32_t)a, aH = a >> 32; diff --git a/src/movepick.h b/src/movepick.h index 90f60b8a961..b6c07378240 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -62,14 +62,14 @@ class StatsEntry { template struct Stats : public std::array, Size> { - typedef Stats stats; + using stats = Stats; void fill(const T& v) { // For standard-layout 'this' points to first struct member assert(std::is_standard_layout::value); - typedef StatsEntry entry; + using entry = StatsEntry; entry* p = reinterpret_cast(this); std::fill(p, p + sizeof(*this) / sizeof(entry), v); } @@ -87,23 +87,23 @@ enum StatsType { NoCaptures, Captures }; /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards /// (~11 elo) -typedef Stats ButterflyHistory; +using ButterflyHistory = Stats; /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous /// move, see www.chessprogramming.org/Countermove_Heuristic -typedef Stats CounterMoveHistory; +using CounterMoveHistory = Stats; /// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] -typedef Stats CapturePieceToHistory; +using CapturePieceToHistory = Stats; /// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] -typedef Stats PieceToHistory; +using PieceToHistory = Stats; /// ContinuationHistory is the combined history of a given pair of moves, usually /// the current one given a previous one. The nested history table is based on /// PieceToHistory instead of ButterflyBoards. /// (~63 elo) -typedef Stats ContinuationHistory; +using ContinuationHistory = Stats; /// MovePicker class is used to pick one pseudo-legal move at a time from the diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index b0d5743e669..8087ea55dd0 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -42,8 +42,8 @@ namespace Stockfish::Eval::NNUE { "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); #ifdef USE_AVX512 - typedef __m512i vec_t; - typedef __m256i psqt_vec_t; + using vec_t = __m512i; + using psqt_vec_t = __m256i; #define vec_load(a) _mm512_load_si512(a) #define vec_store(a,b) _mm512_store_si512(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b) @@ -66,8 +66,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 64 #elif USE_AVX2 - typedef __m256i vec_t; - typedef __m256i psqt_vec_t; + using vec_t = __m256i; + using psqt_vec_t = __m256i; #define vec_load(a) _mm256_load_si256(a) #define vec_store(a,b) _mm256_store_si256(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b) @@ -90,8 +90,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 32 #elif USE_SSE2 - typedef __m128i vec_t; - typedef __m128i psqt_vec_t; + using vec_t = __m128i; + using psqt_vec_t = __m128i; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_epi16(a,b) @@ -111,8 +111,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 16 #elif USE_MMX - typedef __m64 vec_t; - typedef __m64 psqt_vec_t; + using vec_t = __m64; + using psqt_vec_t = __m64; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_pi16(a,b) @@ -139,8 +139,8 @@ namespace Stockfish::Eval::NNUE { #define MaxChunkSize 8 #elif USE_NEON - typedef int16x8_t vec_t; - typedef int32x4_t psqt_vec_t; + using vec_t = int16x8_t; + using psqt_vec_t = int32x4_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) vaddq_s16(a,b) diff --git a/src/pawns.h b/src/pawns.h index 95c9ea3ce20..d20e7c2ebe5 100644 --- a/src/pawns.h +++ b/src/pawns.h @@ -61,7 +61,7 @@ struct Entry { int blockedCount; }; -typedef HashTable Table; +using Table = HashTable; Entry* probe(const Position& pos); diff --git a/src/position.h b/src/position.h index 3de24f22888..c82c7a8bf87 100644 --- a/src/position.h +++ b/src/position.h @@ -68,7 +68,7 @@ struct StateInfo { /// start position to the position just before the search starts). Needed by /// 'draw by repetition' detection. Use a std::deque because pointers to /// elements are not invalidated upon list resizing. -typedef std::unique_ptr> StateListPtr; +using StateListPtr = std::unique_ptr>; /// Position class stores information regarding the board representation as diff --git a/src/search.h b/src/search.h index 48a0f7ce710..806e4be63fa 100644 --- a/src/search.h +++ b/src/search.h @@ -80,7 +80,7 @@ struct RootMove { std::vector pv; }; -typedef std::vector RootMoves; +using RootMoves = std::vector; /// LimitsType struct stores information sent by GUI about available time to diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index bbfd819d3fa..b594ac3714e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -142,7 +142,7 @@ struct SparseEntry { static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); -typedef uint16_t Sym; // Huffman symbol +using Sym = uint16_t; // Huffman symbol struct LR { enum Side { Left, Right }; @@ -327,7 +327,7 @@ struct PairsData { // first access, when the corresponding file is memory mapped. template struct TBTable { - typedef typename std::conditional::type Ret; + using Ret = typename std::conditional::type; static constexpr int Sides = Type == WDL ? 2 : 1; diff --git a/src/tune.h b/src/tune.h index f5b787afce5..440d950a9ec 100644 --- a/src/tune.h +++ b/src/tune.h @@ -26,8 +26,8 @@ namespace Stockfish { -typedef std::pair Range; // Option's min-max values -typedef Range (RangeFun) (int); +using Range = std::pair; // Option's min-max values +using RangeFun = Range (int); // Default Range function, to calculate Option's min-max values inline Range default_range(int v) { @@ -75,7 +75,7 @@ struct SetRange { class Tune { - typedef void (PostUpdate) (); // Post-update function + using PostUpdate = void (); // Post-update function Tune() { read_results(); } Tune(const Tune&) = delete; diff --git a/src/types.h b/src/types.h index 8a021342e25..37ce343a4da 100644 --- a/src/types.h +++ b/src/types.h @@ -103,8 +103,8 @@ constexpr bool Is64Bit = true; constexpr bool Is64Bit = false; #endif -typedef uint64_t Key; -typedef uint64_t Bitboard; +using Key = uint64_t; +using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; @@ -218,7 +218,7 @@ constexpr Value PieceValue[PHASE_NB][PIECE_NB] = { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO } }; -typedef int Depth; +using Depth = int; enum : int { DEPTH_QS_CHECKS = 0, diff --git a/src/uci.h b/src/uci.h index 5d8ccf1afff..70e45accd1c 100644 --- a/src/uci.h +++ b/src/uci.h @@ -45,12 +45,12 @@ struct CaseInsensitiveLess { }; /// The options container is defined as a std::map -typedef std::map OptionsMap; +using OptionsMap = std::map; /// The Option class implements each option as specified by the UCI protocol class Option { - typedef void (*OnChange)(const Option&); + using OnChange = void (*)(const Option&); public: Option(OnChange = nullptr); From 6adbc6fa05bbd2a574a8c4c6a6ef2307280217ab Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 22 Feb 2023 05:45:43 -0600 Subject: [PATCH 0011/1309] Late counter bonus: boost underestimated moves The idea here is very intuitive: since we've just proven that the move is good, then if it previously had poor stats, boost those stats more than otherwise. Passed STC: https://tests.stockfishchess.org/tests/view/63fb504ce74a12625bce4154 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 21128 W: 5763 L: 5481 D: 9884 Ptnml(0-2): 52, 2212, 5759, 2484, 57 Passed LTC: https://tests.stockfishchess.org/tests/view/63fb7825e74a12625bce491b LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 91904 W: 24764 L: 24359 D: 42781 Ptnml(0-2): 45, 8735, 27984, 9146, 42 closes https://github.com/official-stockfish/Stockfish/pull/4415 bench 4318808 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cfb569b9093..6585f7bcabe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1383,8 +1383,7 @@ namespace { // Bonus for prior countermove that caused the fail low else if (!priorCapture) { - // Extra bonuses for PV/Cut nodes or bad fail lows - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } From 876906965b8d552866486c0e6eda1184fdb1d636 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 24 Feb 2023 09:16:55 -0500 Subject: [PATCH 0012/1309] Update default net to nn-52471d67216a.nnue Created by retraining the master net with modifications to the previous best dataset: * Improving T80 oct+nov 2022 endgame lambda accuracy by rescoring with 12-16tb of syzygy 7p tablebases * Filtering T78 jun+jul+aug 2022 with d6pv2 search to remove positions with bestmove captures or one good move * Adding T80 sep 2022 data, rescored with 16tb of 7p tablebases, unfiltered Trained with max-epoch 900, end-lambda 0.7, and early-fen-skipping 28. ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T80octnovT79aprmayT78junjulaugT60novdec-filt-v2-T78sep12tb7p-T77decT80sep16tb7p-lambda7-sk28 \ --training-dataset /data/leela96-dfrc99-T80octnovT79aprmayT78junjulaugT60novdec-filt-v2-T78sep12tb7p-T77decT80sep16tb7p.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/easy-train-early-fen-skipping \ --early-fen-skipping 28 \ --start-from-engine-test-net True \ --gpus "0," \ --max_epoch 900 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --gamma 0.995 \ --lr 4.375e-4 \ --tui False \ --seed $RANDOM ``` Training data was rescored and d6pv2 filtered in the same way as recent best datasets. For preparing the merged training dataset: ``` python3 interleave_binpacks.py \ leela96-eval-filt-v2.binpack \ dfrc99-eval-filt-v2.binpack \ test80-oct2022-16tb7p-eval-filt-v2-d6.binpack \ test80-nov2022-12tb7p-eval-filt-v2-d6.binpack \ T79-apr2022-12tb7p-eval-filt-v2.binpack \ T79-may2022-12tb7p-eval-filt-v2.binpack \ test78-junjulaug2022-16tb7p-eval-filt-v2-d6.binpack \ T60-nov2021-12tb7p-eval-filt-v2.binpack \ T60-dec2021-12tb7p-eval-filt-v2.binpack \ T78-sep2022-12tb7p.binpack \ test77-dec2021-16gb7p.binpack \ test80-sep2022-16tb7p.binpack \ /data/leela96-dfrc99-T80octnovT79aprmayT78junjulaugT60novdec-filt-v2-T78sep12tb7p-T77decT80sep16tb7p.binpack ``` Links for downloading the training data components can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch839.nnue : 0.6 +/- 1.4 Passed STC: https://tests.stockfishchess.org/tests/view/63f9ab4be74a12625bcdf02e LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 84656 W: 22681 L: 22302 D: 39673 Ptnml(0-2): 271, 9343, 22734, 9696, 284 Passed LTC: https://tests.stockfishchess.org/tests/view/63fa3833e74a12625bce0c0e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 184664 W: 49933 L: 49344 D: 85387 Ptnml(0-2): 111, 17977, 55561, 18578, 105 closes https://github.com/official-stockfish/Stockfish/pull/4416 bench: 4814343 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 46f202594e6..5238cb81c54 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1337b1adec5b.nnue" + #define EvalFileDefaultName "nn-52471d67216a.nnue" namespace NNUE { From 5c75c1c2fbb7bb4f0bf7c44fb855c415b788cbf7 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 24 Feb 2023 12:09:45 +0300 Subject: [PATCH 0013/1309] Fix duplicated moves generation in movepicker in a some of cases movepicker returned some moves more than once which lead to them being searched more than once. This bug was possible because of how we use queen promotions - they are generated as a captures but are not included in position function which checks if move is a capture. Thus if any refutation (killer or countermove) was a queen promotion it was searched twice - once as a capture and one as a refutation. This patch affects various things, namely stats assignments for queen promotions and other moves if best move is queen promotion, also some heuristics in search and qsearch. With this patch every queen promotion is now considered a capture. After this patch number of found duplicated moves is 0 during normal 13 depth bench run. Passed STC: https://tests.stockfishchess.org/tests/view/63f77e01e74a12625bcd87d7 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 80920 W: 21455 L: 21289 D: 38176 Ptnml(0-2): 198, 8839, 22241, 8963, 219 Passed LTC: https://tests.stockfishchess.org/tests/view/63f7e020e74a12625bcd9a76 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 89712 W: 23674 L: 23533 D: 42505 Ptnml(0-2): 24, 8737, 27202, 8860, 33 closes https://github.com/official-stockfish/Stockfish/pull/4405 bench 4681731 --- src/movepick.cpp | 6 +++--- src/position.h | 12 ++++++++---- src/search.cpp | 12 ++++++------ src/syzygy/tbprobe.cpp | 4 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 65155a73f79..36ee46b50f0 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -93,7 +93,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece { assert(!pos.checkers()); - stage = PROBCUT_TT + !(ttm && pos.capture(ttm) + stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } @@ -141,7 +141,7 @@ void MovePicker::score() { + bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384; else // Type == EVASIONS { - if (pos.capture(m)) + if (pos.capture_stage(m)) m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + (1 << 28); @@ -216,7 +216,7 @@ Move MovePicker::next_move(bool skipQuiets) { case REFUTATION: if (select([&](){ return *cur != MOVE_NONE - && !pos.capture(*cur) + && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); })) return *(cur - 1); ++stage; diff --git a/src/position.h b/src/position.h index c82c7a8bf87..485540ef866 100644 --- a/src/position.h +++ b/src/position.h @@ -125,7 +125,7 @@ class Position { // Properties of moves bool legal(Move m) const; bool pseudo_legal(const Move m) const; - bool capture(Move m) const; + bool capture_stage(Move m) const; bool gives_check(Move m) const; Piece moved_piece(Move m) const; Piece captured_piece() const; @@ -381,10 +381,14 @@ inline bool Position::is_chess960() const { return chess960; } -inline bool Position::capture(Move m) const { +// returns true if a move is generated from the capture stage +// having also queen promotions covered, i.e. consistency with the capture stage move generation +// is needed to avoid the generation of duplicate moves. +inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); - // Castling is encoded as "king captures rook" - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; + return (!empty(to_sq(m)) && type_of(m) != CASTLING) + || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN) + || type_of(m) == EN_PASSANT; } inline Piece Position::captured_piece() const { diff --git a/src/search.cpp b/src/search.cpp index 6585f7bcabe..fcdb8d67545 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -623,7 +623,7 @@ namespace { ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() : MOVE_NONE; - ttCapture = ttMove && pos.capture(ttMove); + ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, // to save indentation, we list the condition in all code between here and there. @@ -852,7 +852,7 @@ namespace { probCutBeta = beta + 186 - 54 * improving; // Step 10. ProbCut (~10 Elo) - // If we have a good enough capture and a reduced search returns a value + // 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. if ( !PvNode && depth > 4 @@ -873,7 +873,7 @@ namespace { while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) { - assert(pos.capture(move) || promotion_type(move) == QUEEN); + assert(pos.capture_stage(move)); ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] @@ -985,7 +985,7 @@ namespace { (ss+1)->pv = nullptr; extension = 0; - capture = pos.capture(move); + capture = pos.capture_stage(move); movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); @@ -1542,7 +1542,7 @@ namespace { continue; givesCheck = pos.gives_check(move); - capture = pos.capture(move); + capture = pos.capture_stage(move); moveCount++; @@ -1715,7 +1715,7 @@ namespace { PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); int bonus1 = stat_bonus(depth + 1); - if (!pos.capture(bestMove)) + if (!pos.capture_stage(bestMove)) { int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index b594ac3714e..2a9e1b68d37 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1203,7 +1203,7 @@ WDLScore search(Position& pos, ProbeState* result) { for (const Move move : moveList) { - if ( !pos.capture(move) + if ( !pos.capture_stage(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; @@ -1472,7 +1472,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { for (const Move move : MoveList(pos)) { - bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; + bool zeroing = pos.capture_stage(move) || type_of(pos.moved_piece(move)) == PAWN; pos.do_move(move, st); From 5c589142ae5714ecb9ded29bec12c591a1ba8f3f Mon Sep 17 00:00:00 2001 From: disservin Date: Sat, 4 Mar 2023 16:34:34 +0100 Subject: [PATCH 0014/1309] Add wiki to artifacts snapshot the wiki https://github.com/official-stockfish/stockfish/wiki as part of the artifacts generated. This will allow future release to include the wiki pages as a form of documentation closes https://github.com/official-stockfish/Stockfish/pull/4420 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 7 +++++++ .github/workflows/stockfish_binaries.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index a1b3cdab0ea..e088c441925 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -99,10 +99,17 @@ jobs: - name: Remove non src files run: rm -f *.o .depend *.nnue + - name: Download wiki + run: | + git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki + cd ../wiki + rm -rf .git + - name: Create tar archive. run: | cd .. mkdir stockfish + cp -r wiki stockfish/ cp -r src stockfish/ cp stockfish-android-$BINARY$EXT stockfish/ cp "Top CPU Contributors.txt" stockfish/ diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 06b13a9e095..b22897cf692 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -83,10 +83,17 @@ jobs: - name: Remove non src files run: rm -f *.o .depend *.nnue + - name: Download wiki + run: | + git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki + cd ../wiki + rm -rf .git + - name: Create tar archive. run: | cd .. mkdir stockfish + cp -r wiki stockfish/ cp -r src stockfish/ cp stockfish-$OS-$BINARY$EXT stockfish/ cp "Top CPU Contributors.txt" stockfish/ From 3a634f5282700484445c0f66b21b35e96fcb947b Mon Sep 17 00:00:00 2001 From: dav1312 <63931154+dav1312@users.noreply.github.com> Date: Sat, 4 Mar 2023 13:24:35 +0100 Subject: [PATCH 0015/1309] Update README.md Update and simplify the readme, removing duplicated and outdated stuff and pointing to the new wiki closes https://github.com/official-stockfish/Stockfish/pull/4421 No functional change --- README.md | 376 ++++++++++++------------------------------------------ 1 file changed, 81 insertions(+), 295 deletions(-) diff --git a/README.md b/README.md index ca90d5d4402..1f462d315af 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,21 @@ [![Stockfish][stockfish128-logo]][website-link] +

Stockfish

+ + A free and strong UCI chess engine. +
+ [Explore Stockfish docs »][wiki-link] +
+
+ [Report bug][issue-link] + · + [Open a discussion][discussions-link] + · + [Discord][discord-link] + · + [Blog][website-blog-link] + [![Build][build-badge]][build-link] [![License][license-badge]][license-link]
@@ -16,19 +31,16 @@ ## Overview -[Stockfish][website-link] is a free, powerful UCI chess engine derived from -Glaurung 2.1. Stockfish is not a complete chess program and requires a UCI-compatible -graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, -Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably. -Read the documentation for your GUI of choice for information about how to use +[Stockfish][website-link] is a **free and strong UCI chess engine** derived from +Glaurung 2.1 that analyzes chess positions and computes the optimal moves. + +Stockfish **does not include a graphical user interface** (GUI) that is required +to display a chessboard and to make it easy to input moves. These GUIs are +developed independently from Stockfish and are available online. **Read the +documentation for your GUI** of choice for information about how to use Stockfish with it. -The Stockfish engine features two evaluation functions for chess. The efficiently -updatable neural network (NNUE) based evaluation is the default and by far the strongest. -The classical evaluation based on handcrafted terms remains available. The strongest -network is integrated in the binary and downloaded automatically during the build process. -The NNUE evaluation benefits from the vector intrinsics available on most CPUs (sse2, -avx2, neon, or similar). +See also the Stockfish [documentation][wiki-usage-link] for further usage help. ## Files @@ -36,338 +48,112 @@ This distribution of Stockfish consists of the following files: * [README.md][readme-link], the file you are currently reading. - * [Copying.txt][license-link], a text file containing the GNU General Public License - version 3. + * [Copying.txt][license-link], a text file containing the GNU General Public + License version 3. * [AUTHORS][authors-link], a text file with the list of authors for the project. - * [src][src-link], a subdirectory containing the full source code, including a Makefile - that can be used to compile Stockfish on Unix-like systems. - - * a file with the .nnue extension, storing the neural network for the NNUE evaluation. - Binary distributions will have this file embedded. - -## The UCI protocol and available options - -The Universal Chess Interface (UCI) is a standard protocol used to communicate with -a chess engine, and is the recommended way to do so for typical graphical user interfaces -(GUI) or chess tools. Stockfish implements the majority of its options as described -in [the UCI protocol][uci-link]. - -Developers can see the default values for UCI options available in Stockfish by typing -`./stockfish uci` in a terminal, but the majority of users will typically see them and -change them via a chess GUI. This is a list of available UCI options in Stockfish: - - * #### Threads - The number of CPU threads used for searching a position. For best performance, set - this equal to the number of CPU cores available. - - * #### Hash - The size of the hash table in MB. It is recommended to set Hash after setting Threads. - - * #### Clear Hash - Clear the hash table. - - * #### Ponder - Let Stockfish ponder its next move while the opponent is thinking. - - * #### MultiPV - Output the N best lines (principal variations, PVs) when searching. - Leave at 1 for best performance. - - * #### Use NNUE - Toggle between the NNUE and classical evaluation functions. If set to "true", - the network parameters must be available to load from file (see also EvalFile), - if they are not embedded in the binary. - - * #### EvalFile - The name of the file of the NNUE evaluation parameters. Depending on the GUI the - filename might have to include the full path to the folder/directory that contains - the file. Other locations, such as the directory that contains the binary and the - working directory, are also searched. - - * #### UCI_AnalyseMode - An option handled by your GUI. - - * #### UCI_Chess960 - An option handled by your GUI. If true, Stockfish will play Chess960. + * [src][src-link], a subdirectory containing the full source code, including a + Makefile that can be used to compile Stockfish on Unix-like systems. - * #### UCI_ShowWDL - If enabled, show approximate WDL statistics as part of the engine output. - These WDL numbers model expected game outcomes for a given evaluation and - game ply for engine self-play at fishtest LTC conditions (60+0.6s per game). + * a file with the .nnue extension, storing the neural network for the NNUE + evaluation. Binary distributions will have this file embedded. - * #### UCI_LimitStrength - Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level. +## The UCI protocol - * #### UCI_Elo - If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo. - This Elo rating has been calibrated at a time control of 120s+1.0s and anchored to +- 100 Elo to CCRL Blitz. +The [Universal Chess Interface][uci-link] (UCI) is a standard text-based protocol +used to communicate with a chess engine and is the recommended way to do so for +typical graphical user interfaces (GUI) or chess tools. Stockfish implements the +majority of its options. - * #### Skill Level - Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength). - Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a - weaker move will be played. +Developers can see the default values for the UCI options available in Stockfish +by typing `./stockfish uci` in a terminal, but most users should typically use a +chess GUI to interact with Stockfish. - * #### SyzygyPath - Path to the folders/directories storing the Syzygy tablebase files. Multiple - directories are to be separated by ";" on Windows and by ":" on Unix-based - operating systems. Do not use spaces around the ";" or ":". +For more information on UCI or debug commands, see our [documentation][wiki-commands-link]. - Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6` +## Compiling Stockfish - It is recommended to store .rtbw files on an SSD. There is no loss in storing - the .rtbz files on a regular HDD. It is recommended to verify all md5 checksums - of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will - lead to engine crashes. +Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions, +big-endian machines such as Power PC, and other platforms. - * #### SyzygyProbeDepth - Minimum remaining search depth for which a position is probed. Set this option - to a higher value to probe less aggressively if you experience too much slowdown - (in terms of nps) due to tablebase probing. +On Unix-like systems, it should be easy to compile Stockfish directly from the +source code with the included Makefile in the folder `src`. In general, it is +recommended to run `make help` to see a list of make targets with corresponding +descriptions. - * #### Syzygy50MoveRule - Disable to let fifty-move rule draws detected by Syzygy tablebase probes count - as wins or losses. This is useful for ICCF correspondence games. - - * #### SyzygyProbeLimit - Limit Syzygy tablebase probing to positions with at most this many pieces left - (including kings and pawns). - - * #### Move Overhead - Assume a time delay of x ms due to network and GUI overheads. This is useful to - avoid losses on time in those cases. - - * #### Slow Mover - Lower values will make Stockfish take less time in games, higher values will - make it think longer. - - * #### nodestime - Tells the engine to use nodes searched instead of wall time to account for - elapsed time. Useful for engine testing. - - * #### Debug Log File - Write all communication to and from the engine into a text file. - -For developers the following non-standard commands might be of interest, mainly useful for debugging: - - * #### bench *ttSize threads limit fenFile limitType evalType* - Performs a standard benchmark using various options. The signature of a version - (standard node count) is obtained using all defaults. `bench` is currently - `bench 16 1 13 default depth mixed`. - - * #### compiler - Give information about the compiler and environment used for building a binary. - - * #### d - Display the current position, with ascii art and fen. - - * #### eval - Return the evaluation of the current position. - - * #### export_net [filename] - Exports the currently loaded network to a file. - If the currently loaded network is the embedded network and the filename - is not specified then the network is saved to the file matching the name - of the embedded network, as defined in evaluate.h. - If the currently loaded network is not the embedded network (some net set - through the UCI setoption) then the filename parameter is required and the - network is saved into that file. - - * #### flip - Flips the side to move. - - -## A note on classical evaluation versus NNUE evaluation - -Both approaches assign a value to a position that is used in alpha-beta (PVS) search -to find the best move. The classical evaluation computes this value as a function -of various chess concepts, handcrafted by experts, tested and tuned using fishtest. -The NNUE evaluation computes this value with a neural network based on basic -inputs (e.g. piece positions only). The network is optimized and trained -on the evaluations of millions of positions at moderate search depth. - -The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward. -It can be evaluated efficiently on CPUs, and exploits the fact that only parts -of the neural network need to be updated after a typical chess move. -[The nodchip repository][nodchip-link] provided the first version of the needed tools -to train and develop the NNUE networks. Today, more advanced training tools are -available in [the nnue-pytorch repository][pytorch-link], while data generation tools -are available in [a dedicated branch][tools-link]. - -On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation -results in much stronger playing strength, even if the nodes per second computed by -the engine is somewhat lower (roughly 80% of nps is typical). - -Notes: - -1) the NNUE evaluation depends on the Stockfish binary and the network parameter file -(see the EvalFile UCI option). Not every parameter file is compatible with a given -Stockfish binary, but the default value of the EvalFile UCI option is the name of a -network that is guaranteed to be compatible with that binary. - -2) to use the NNUE evaluation, the additional data file with neural network parameters -needs to be available. Normally, this file is already embedded in the binary or it can -be downloaded. The filename for the default (recommended) net can be found as the default -value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue` -(for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from ``` -https://tests.stockfishchess.org/api/nn/[filename] +cd src +make -j build ARCH=x86-64-modern ``` -replacing `[filename]` as needed. - -## What to expect from the Syzygy tablebases? - -If the engine is searching a position that is not in the tablebases (e.g. -a position with 8 pieces), it will access the tablebases during the search. -If the engine reports a very large score (typically 153.xx), this means -it has found a winning line into a tablebase position. - -If the engine is given a position to search that is in the tablebases, it -will use the tablebases at the beginning of the search to preselect all -good moves, i.e. all moves that preserve the win or preserve the draw while -taking into account the 50-move rule. -It will then perform a search only on those moves. **The engine will not move -immediately**, unless there is only a single good move. **The engine likely -will not report a mate score, even if the position is known to be won.** - -It is therefore clear that this behaviour is not identical to what one might -be used to with Nalimov tablebases. There are technical reasons for this -difference, the main technical reason being that Nalimov tablebases use the -DTM metric (distance-to-mate), while the Syzygy tablebases use a variation of the -DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move -counter). This special metric is one of the reasons that the Syzygy tablebases are -more compact than Nalimov tablebases, while still storing all information -needed for optimal play and in addition being able to take into account -the 50-move rule. - -## Large Pages - -Stockfish supports large pages on Linux and Windows. Large pages make -the hash access more efficient, improving the engine speed, especially -on large hash sizes. Typical increases are 5..10% in terms of nodes per -second, but speed increases up to 30% have been measured. The support is -automatic. Stockfish attempts to use large pages when available and -will fall back to regular memory allocation when this is not the case. - -### Support on Linux - -Large page support on Linux is obtained by the Linux kernel -transparent huge pages functionality. Typically, transparent huge pages -are already enabled, and no configuration is needed. - -### Support on Windows - -The use of large pages requires "Lock Pages in Memory" privilege. See -[Enable the Lock Pages in Memory Option (Windows)][lockpages-link] -on how to enable this privilege, then run [RAMMap][rammap-link] -to double-check that large pages are used. We suggest that you reboot -your computer after you have enabled large pages, because long Windows -sessions suffer from memory fragmentation, which may prevent Stockfish -from getting large pages: a fresh session is better in this regard. - -## Compiling Stockfish yourself from the sources - -Stockfish has support for 32 or 64-bit CPUs, certain hardware -instructions, big-endian machines such as Power PC, and other platforms. - -On Unix-like systems, it should be easy to compile Stockfish -directly from the source code with the included Makefile in the folder -`src`. In general it is recommended to run `make help` to see a list of make -targets with corresponding descriptions. -``` - cd src - make help - make net - make build ARCH=x86-64-modern -``` +Detailed compilation instructions for all platforms can be found in our +[documentation][wiki-compile-link]. -When not using the Makefile to compile (for instance, with Microsoft MSVC) you -need to manually set/unset some switches in the compiler command line; see -file *types.h* for a quick reference. - -When reporting an issue or a bug, please tell us which Stockfish version -and which compiler you used to create your executable. This information -can be found by typing the following command in a console: - -``` - ./stockfish compiler -``` - -## Understanding the code base and participating in the project - -Stockfish's improvement over the last decade has been a great community -effort. There are a few ways to help contribute to its growth. +## Contributing ### Donating hardware -Improving Stockfish requires a massive amount of testing. You can donate -your hardware resources by installing the [Fishtest Worker][worker-link] -and view the current tests on [Fishtest][fishtest-link]. +Improving Stockfish requires a massive amount of testing. You can donate your +hardware resources by installing the [Fishtest Worker][worker-link] and viewing +the current tests on [Fishtest][fishtest-link]. ### Improving the code -If you want to help improve the code, there are several valuable resources: - -* [In this wiki,][programming-link] many techniques used in +In the [chessprogramming wiki][programming-link], many techniques used in Stockfish are explained with a lot of background information. +The [section on Stockfish][programmingsf-link] describes many features +and techniques used by Stockfish. However, it is generic rather than +focused on Stockfish's precise implementation. -* [The section on Stockfish][programmingsf-link] -describes many features and techniques used by Stockfish. However, it is -generic rather than being focused on Stockfish's precise implementation. -Nevertheless, a helpful resource. - -* The latest source can always be found on [GitHub][github-link]. -Discussions about Stockfish take place these days mainly in the [FishCooking][fishcooking-link] -group and on the [Stockfish Discord channel][discord-link]. The engine testing is done on [Fishtest][fishtest-link]. If you want to help improve Stockfish, please read this [guideline][guideline-link] first, where the basics of Stockfish development are explained. +Discussions about Stockfish take place these days mainly in the Stockfish +[Discord server][discord-link]. This is also the best place to ask questions +about the codebase and how to improve it. ## Terms of use -Stockfish is free, and distributed under the **GNU General Public License version 3** -(GPL v3). Essentially, this means you are free to do almost exactly -what you want with the program, including distributing it among your -friends, making it available for download from your website, selling -it (either by itself or as part of some bigger software package), or -using it as the starting point for a software project of your own. - -The only real limitation is that whenever you distribute Stockfish in -some way, you MUST always include the license and the full source code -(or a pointer to where the source code can be found) to generate the -exact binary you are distributing. If you make any changes to the -source code, these changes must also be made available under the GPL v3. +Stockfish is free and distributed under the +[**GNU General Public License version 3**][license-link] (GPL v3). Essentially, +this means you are free to do almost exactly what you want with the program, +including distributing it among your friends, making it available for download +from your website, selling it (either by itself or as part of some bigger +software package), or using it as the starting point for a software project of +your own. -For full details, read the copy of the GPL v3 found in the file named -[*Copying.txt*][license-link]. +The only real limitation is that whenever you distribute Stockfish in some way, +you MUST always include the license and the full source code (or a pointer to +where the source code can be found) to generate the exact binary you are +distributing. If you make any changes to the source code, these changes must +also be made available under GPL v3. [authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS [build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml [commits-link]: https://github.com/official-stockfish/Stockfish/commits/master [discord-link]: https://discord.gg/GWDRS3kU6R -[fishcooking-link]: https://groups.google.com/g/fishcooking +[issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new [fishtest-link]: https://tests.stockfishchess.org/tests -[github-link]: https://github.com/official-stockfish/Stockfish [guideline-link]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test [license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt -[lockpages-link]: https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows -[nodchip-link]: https://github.com/nodchip/Stockfish [programming-link]: https://www.chessprogramming.org/Main_Page [programmingsf-link]: https://www.chessprogramming.org/Stockfish -[pytorch-link]: https://github.com/glinscott/nnue-pytorch -[rammap-link]: https://docs.microsoft.com/en-us/sysinternals/downloads/rammap [readme-link]: https://github.com/official-stockfish/Stockfish/blob/master/README.md [release-link]: https://github.com/official-stockfish/Stockfish/releases/latest [src-link]: https://github.com/official-stockfish/Stockfish/tree/master/src [stockfish128-logo]: https://stockfishchess.org/images/logo/icon_128x128.png -[tools-link]: https://github.com/official-stockfish/Stockfish/tree/tools -[uci-link]: https://www.shredderchess.com/download/div/uci.zip +[uci-link]: https://backscattering.de/chess/uci/ [website-link]: https://stockfishchess.org -[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview +[website-blog-link]: https://stockfishchess.org/blog/ +[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki +[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage +[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source +[wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands +[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker [build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github [commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge From cdec775a1555ef83cb9c878c42a11b7dde0a627b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 4 Mar 2023 18:14:23 +0100 Subject: [PATCH 0016/1309] Add CITATION.cff file Make the stockfish software more easily citable, for example in academic papers. fixes https://github.com/official-stockfish/Stockfish/issues/4419 closes https://github.com/official-stockfish/Stockfish/pull/4422 No functional change --- CITATION.cff | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000000..bc0889a8b69 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,23 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: Stockfish +message: >- + Please cite this software using the metadata from this + file. +type: software +authors: + - name: The Stockfish developers (see AUTHORS file) +repository-code: 'https://github.com/official-stockfish/Stockfish' +url: 'https://stockfishchess.org/' +repository-artifact: 'https://stockfishchess.org/download/' +abstract: Stockfish is a free and strong UCI chess engine. +keywords: + - chess + - artificial intelligence (AI) + - tree search + - alpha-beta search + - neural networks (NN) + - efficiently updatable neural networks (NNUE) +license: GPL-3.0 From 70dfa141d560c18cd1aa28884b7cd8ab0f094944 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sun, 5 Mar 2023 17:10:52 +0200 Subject: [PATCH 0017/1309] Clarify the description of the x86-64-vnni256 and x86-64-avxvnni architectures Now it is clearly explained that "x86-64-vnni256" requires full support of AVX512-VNNI, but only 256-bit operands are used. closes https://github.com/official-stockfish/Stockfish/pull/4427 No functional change --- AUTHORS | 1 + src/Makefile | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 634de4a3d90..49f7009f10b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -134,6 +134,7 @@ Matt Ginsberg (mattginsberg) Matthew Lai (matthewlai) Matthew Sullivan (Matt14916) Max A. (Disservin) +Maxim Masiutin (maximmasiutin) Maxim Molchanov (Maxim) Michael An (man) Michael Byrne (MichaelB7) diff --git a/src/Makefile b/src/Makefile index 3d6432fd96b..774ba6ea8c4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -92,7 +92,7 @@ VPATH = syzygy:nnue:nnue/features # avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 # avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX # avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 -# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 +# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands # vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 # neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture # dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions @@ -773,10 +773,10 @@ help: @echo "" @echo "Supported archs:" @echo "" - @echo "x86-64-vnni512 > x86 64-bit with vnni support 512bit wide" - @echo "x86-64-vnni256 > x86 64-bit with vnni support 256bit wide" + @echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" + @echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" @echo "x86-64-avx512 > x86 64-bit with avx512 support" - @echo "x86-64-avxvnni > x86 64-bit with avxvnni support" + @echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" @echo "x86-64-bmi2 > x86 64-bit with bmi2 support" @echo "x86-64-avx2 > x86 64-bit with avx2 support" @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" From 6ce225bb4c31298b131714eff67b56de3b8ee78d Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 5 Mar 2023 17:26:12 +0100 Subject: [PATCH 0018/1309] Fix TB after capture_stage fix https://github.com/official-stockfish/Stockfish/commit/5c75c1c2fbb7bb4f0bf7c44fb855c415b788cbf7 introduced a capture_stage() function, but TB usage needs a pure capture() function. closes https://github.com/official-stockfish/Stockfish/pull/4428 No functional change. --- src/position.h | 11 ++++++++--- src/syzygy/tbprobe.cpp | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/position.h b/src/position.h index 485540ef866..1fc6b3b89c4 100644 --- a/src/position.h +++ b/src/position.h @@ -125,6 +125,7 @@ class Position { // Properties of moves bool legal(Move m) const; bool pseudo_legal(const Move m) const; + bool capture(Move m) const; bool capture_stage(Move m) const; bool gives_check(Move m) const; Piece moved_piece(Move m) const; @@ -381,14 +382,18 @@ inline bool Position::is_chess960() const { return chess960; } +inline bool Position::capture(Move m) const { + assert(is_ok(m)); + return (!empty(to_sq(m)) && type_of(m) != CASTLING) + || type_of(m) == EN_PASSANT; +} + // returns true if a move is generated from the capture stage // having also queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) - || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN) - || type_of(m) == EN_PASSANT; + return capture(m) || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN); } inline Piece Position::captured_piece() const { diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 2a9e1b68d37..b594ac3714e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1203,7 +1203,7 @@ WDLScore search(Position& pos, ProbeState* result) { for (const Move move : moveList) { - if ( !pos.capture_stage(move) + if ( !pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; @@ -1472,7 +1472,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { for (const Move move : MoveList(pos)) { - bool zeroing = pos.capture_stage(move) || type_of(pos.moved_piece(move)) == PAWN; + bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; pos.do_move(move, st); From 39da50ed23ee3f1bd32a58c8f02471faa9a9fd63 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 7 Mar 2023 01:54:20 +0300 Subject: [PATCH 0019/1309] Do more negative extensions This patch does negatively extend transposition table move if singular search failed and tt value is not bigger than alpha. Logic is close to what we had before recent simplification of negative extensions but uses or condition instead of and condition. Passed STC: https://tests.stockfishchess.org/tests/view/6404c8102644b62c33934607 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 119040 W: 31841 L: 31416 D: 55783 Ptnml(0-2): 356, 13070, 32292, 13397, 405 Passed LTC: https://tests.stockfishchess.org/tests/view/6405abda2644b62c33937119 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 47216 W: 12816 L: 12496 D: 21904 Ptnml(0-2): 12, 4500, 14286, 4776, 34 closes https://github.com/official-stockfish/Stockfish/pull/4430 bench 4747020 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index fcdb8d67545..349d43894dd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1109,6 +1109,10 @@ namespace { // If the eval of ttMove is less than value, we reduce it (negative extension) else if (ttValue <= value) extension = -1; + + // If the eval of ttMove is less than alpha, we reduce it (negative extension) + else if (ttValue <= alpha) + extension = -1; } // Check extensions (~1 Elo) From a48573e15fbd18fecb087c9fc02c38a07cece68b Mon Sep 17 00:00:00 2001 From: Dubslow Date: Thu, 9 Mar 2023 18:33:13 -0600 Subject: [PATCH 0020/1309] More negative extensions on nonsingular nonpv nodes. Following up the previous gainer also in this nonsingular node section of code. Credit shared with @FauziAkram for realizing this nonsingular node stuff had some potential, and @XInTheDark for reminding us that !PvNodes better handle extensions/reductions than Pv. Passed STC: https://tests.stockfishchess.org/tests/view/640a7bb32644b62c339457c3 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 136776 W: 36598 L: 36149 D: 64029 Ptnml(0-2): 439, 14834, 37384, 15301, 430 Passed LTC: https://tests.stockfishchess.org/tests/view/640c43a02644b62c3394b23c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 79536 W: 21363 L: 20984 D: 37189 Ptnml(0-2): 28, 7525, 24285, 7900, 30 closes https://github.com/official-stockfish/Stockfish/pull/4441 Bench: 4444953 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 349d43894dd..582e44576b3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1104,7 +1104,7 @@ namespace { // If the eval of ttMove is greater than beta, we reduce it (negative extension) else if (ttValue >= beta) - extension = -2; + extension = -2 - !PvNode; // If the eval of ttMove is less than value, we reduce it (negative extension) else if (ttValue <= value) From 78532af9dc585a81730d5d28763cc9d5273f96d1 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 13 Mar 2023 15:57:58 +0300 Subject: [PATCH 0021/1309] Do more singular extensions This patch continues trend of last VLTC tuning - as measured by dubslow most of it gains was in lowering marging in calculation of singularBeta. This patch is a manual adjustment on top of it - it lowers multiplier of depth in calculation of singularBeta even further, from 2/3 to 1,5/2,5. Was negative at STC: https://tests.stockfishchess.org/tests/view/64089c632644b62c3393fc12 Elo: -2.49 +-1.7 (95%) LOS: 0.2% Total: 40000 W: 10601 L: 10888 D: 18511 Ptnml(0-2): 123, 4580, 10875, 4305, 117 nElo: -5.03 +-3.4 (95%) PairsRatio: 0.94 Passed 180+1.8 SPRT: https://tests.stockfishchess.org/tests/view/640096dae74a12625bcf3b33 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 160952 W: 43753 L: 43342 D: 73857 Ptnml(0-2): 25, 13984, 52039, 14411, 17 Passed 60+0.6 8 threads SPRT: https://tests.stockfishchess.org/tests/view/640dca8e65775d3b539cb7f6 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 38824 W: 10825 L: 10554 D: 17445 Ptnml(0-2): 0, 2939, 13268, 3200, 5 closes https://github.com/official-stockfish/Stockfish/pull/4443 bench 4776866 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 582e44576b3..7eb4a0c5c53 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1071,7 +1071,7 @@ namespace { && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (2 + (ss->ttPv && !PvNode)) * depth; + Value singularBeta = ttValue - (3 + 2 * (ss->ttPv && !PvNode)) * depth / 2; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; From 55896a1384b022624a8c33b9f7f51b6b551eed50 Mon Sep 17 00:00:00 2001 From: Alfredo Menezes Date: Sun, 12 Mar 2023 22:29:07 -0300 Subject: [PATCH 0022/1309] Change mode of incbin.h Keep incbin.h with the same mode as the other source files. A mode diff might show up when working with patch files or sending the source code between devices. This patch should fix such behaviour. closes https://github.com/official-stockfish/Stockfish/pull/4442 No functional change --- src/incbin/incbin.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/incbin/incbin.h diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h old mode 100755 new mode 100644 From d1e17989b51f220629e4e81a4bf413974f4b18e2 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sat, 11 Mar 2023 22:08:35 +0100 Subject: [PATCH 0023/1309] Fix Makefile for clang 16 The clang 16 release will remove the -fexperimental-new-pass-manager flag (see https://github.com/llvm/llvm-project/commit/69b2b7282e92a1b576b7bd26f3b16716a5027e8e). Thus, the commit adapts the Makefile to use this flag only for older clang versions. closes https://github.com/official-stockfish/Stockfish/pull/4437 No functional change --- src/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 774ba6ea8c4..e257bc6347d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -584,7 +584,10 @@ ifeq ($(optimize),yes) endif ifeq ($(comp),clang) - CXXFLAGS += -fexperimental-new-pass-manager + clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + ifeq ($(shell expr $(clangmajorversion) \< 16),1) + CXXFLAGS += -fexperimental-new-pass-manager + endif endif endif From 7077fbdd1481829a0a20b6975c4245609118b938 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 11 Mar 2023 11:51:08 -0800 Subject: [PATCH 0024/1309] Remove redundant condition from capture_stage() Change a non functional promotion check to an assert. closes https://github.com/official-stockfish/Stockfish/pull/4436 No functional change --- src/position.cpp | 3 +-- src/position.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37aa2e9edec..632a40b5d61 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -569,8 +569,7 @@ bool Position::pseudo_legal(const Move m) const { : MoveList(*this).contains(m); // Is not a promotion, so promotion piece must be empty - if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) - return false; + assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); // If the 'from' square is not occupied by a piece belonging to the side to // move, the move is obviously not legal. diff --git a/src/position.h b/src/position.h index 1fc6b3b89c4..cc606a5ab00 100644 --- a/src/position.h +++ b/src/position.h @@ -393,7 +393,7 @@ inline bool Position::capture(Move m) const { // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); - return capture(m) || (type_of(m) == PROMOTION && promotion_type(m) == QUEEN); + return capture(m) || promotion_type(m) == QUEEN; } inline Piece Position::captured_piece() const { From f0556dcbe3ba2fc804ab26d4552446602a75f064 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Wed, 1 Mar 2023 10:29:51 +0100 Subject: [PATCH 0025/1309] Small cleanups remove some unneeded assignments, typos, incorrect comments, add authors entry. closes https://github.com/official-stockfish/Stockfish/pull/4417 no functional change --- AUTHORS | 1 + src/nnue/nnue_feature_transformer.h | 2 +- src/position.cpp | 3 --- src/search.cpp | 2 -- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 49f7009f10b..9b36111ea77 100644 --- a/AUTHORS +++ b/AUTHORS @@ -190,6 +190,7 @@ Sergei Antonov (saproj) Sergei Ivanov (svivanov72) Sergio Vieri (sergiovieri) sf-x +Shahin M. Shahin (peregrine) Shane Booth (shane31) Shawn Varghese (xXH4CKST3RXx) Siad Daboul (Topologist) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 8087ea55dd0..a1888c7a365 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -363,7 +363,7 @@ namespace Stockfish::Eval::NNUE { // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. // All states must be sequential, that is states_to_update[i] must either be reachable // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. - // computed_st must be reachable by repeatadly applying ->previous on states_to_update[0], if not nullptr. + // computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { static_assert(N > 0); diff --git a/src/position.cpp b/src/position.cpp index 632a40b5d61..aeac23a33f4 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -765,9 +765,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update board and piece lists remove_piece(capsq); - if (type_of(m) == EN_PASSANT) - board[capsq] = NO_PIECE; - // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; diff --git a/src/search.cpp b/src/search.cpp index 7eb4a0c5c53..d6571a140f3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -601,7 +601,6 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss+1)->ttPv = false; (ss+1)->excludedMove = bestMove = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->cutoffCnt = 0; @@ -1075,7 +1074,6 @@ namespace { Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; - // the search with excludedMove will update ss->staticEval value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); ss->excludedMove = MOVE_NONE; From 515b66f18833ed87e97313d2ec4dfa4e2329d3df Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Sun, 12 Mar 2023 01:22:55 +0300 Subject: [PATCH 0026/1309] Fix null move issue Fix altering for stats landing on B1 Square after a null move and fix considering counter-moves on A1 for root node. fixes https://github.com/official-stockfish/Stockfish/issues/4333 by preventing calls to from_sq and to_sq functions over null-moves and none-moves. closes https://github.com/official-stockfish/Stockfish/pull/4448 bench: 4980082 --- src/search.cpp | 19 ++++++++++++------- src/types.h | 10 ++++++---- src/uci.cpp | 6 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d6571a140f3..466e0d6f935 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -605,7 +605,7 @@ namespace { (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; (ss+2)->cutoffCnt = 0; ss->doubleExtensions = (ss-1)->doubleExtensions; - Square prevSq = to_sq((ss-1)->currentMove); + Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; // Initialize statScore to zero for the grandchildren of the current position. // So statScore is shared between all grandchildren and only the first grandchild @@ -647,7 +647,7 @@ namespace { update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) - if ((ss-1)->moveCount <= 2 && !priorCapture) + if (prevSq != SQ_NONE && (ss-1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) @@ -935,7 +935,7 @@ namespace { nullptr , (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; - Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; + Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, @@ -1383,7 +1383,7 @@ namespace { quietsSearched, quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low - else if (!priorCapture) + else if (!priorCapture && prevSq != SQ_NONE) { int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1525,7 +1525,7 @@ namespace { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = to_sq((ss-1)->currentMove); + Square prevSq = (ss-1)->currentMove != MOVE_NULL ? to_sq((ss-1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, @@ -1714,7 +1714,8 @@ namespace { Thread* thisThread = pos.this_thread(); CapturePieceToHistory& captureHistory = thisThread->captureHistory; Piece moved_piece = pos.moved_piece(bestMove); - PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); + PieceType captured; + int bonus1 = stat_bonus(depth + 1); if (!pos.capture_stage(bestMove)) @@ -1733,12 +1734,16 @@ namespace { } } else + { // Increase stats for the best move in case it was a capture move + captured = type_of(pos.piece_on(to_sq(bestMove))); captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; + } // Extra penalty for a quiet early move that was not a TT move or // main killer move in previous ply when it gets refuted. - if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) + if ( prevSq != SQ_NONE + && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); diff --git a/src/types.h b/src/types.h index 37ce343a4da..06b0a05985a 100644 --- a/src/types.h +++ b/src/types.h @@ -416,6 +416,10 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } +constexpr bool is_ok(Move m) { + return m != MOVE_NONE && m != MOVE_NULL; +} + constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } @@ -445,10 +449,12 @@ constexpr Direction pawn_push(Color c) { } constexpr Square from_sq(Move m) { + assert(is_ok(m)); return Square((m >> 6) & 0x3F); } constexpr Square to_sq(Move m) { + assert(is_ok(m)); return Square(m & 0x3F); } @@ -473,10 +479,6 @@ constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -constexpr bool is_ok(Move m) { - return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE -} - /// Based on a congruential pseudo random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; diff --git a/src/uci.cpp b/src/uci.cpp index 3883b3d3707..d3d99243b09 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -358,15 +358,15 @@ std::string UCI::square(Square s) { string UCI::move(Move m, bool chess960) { - Square from = from_sq(m); - Square to = to_sq(m); - if (m == MOVE_NONE) return "(none)"; if (m == MOVE_NULL) return "0000"; + Square from = from_sq(m); + Square to = to_sq(m); + if (type_of(m) == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); From af4b62a593cc4fa6d7d34110c41301028a5c9695 Mon Sep 17 00:00:00 2001 From: disservin Date: Mon, 13 Mar 2023 19:35:27 +0100 Subject: [PATCH 0027/1309] NNUE namespace cleanup This patch moves the nnue namespace in the appropiate header that correspondes with the definition. It also makes navigation a bit easier. closes https://github.com/official-stockfish/Stockfish/pull/4445 No functional change --- src/evaluate.cpp | 6 +++--- src/evaluate.h | 8 -------- src/nnue/evaluate_nnue.h | 8 ++++++++ src/search.cpp | 1 + src/uci.cpp | 1 + 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index cf6f23eac55..99b873004ed 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -36,7 +36,7 @@ #include "timeman.h" #include "uci.h" #include "incbin/incbin.h" - +#include "nnue/evaluate_nnue.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). @@ -95,7 +95,7 @@ namespace Eval { if (directory != "") { ifstream stream(directory + eval_file, ios::binary); - if (load_eval(eval_file, stream)) + if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } @@ -111,7 +111,7 @@ namespace Eval { (void) gEmbeddedNNUEEnd; // Silence warning on unused variable istream stream(&buffer); - if (load_eval(eval_file, stream)) + if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } } diff --git a/src/evaluate.h b/src/evaluate.h index 5238cb81c54..3615fe6d7e6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -43,17 +43,9 @@ namespace Eval { namespace NNUE { - std::string trace(Position& pos); - Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); - void hint_common_parent_position(const Position& pos); - void init(); void verify(); - bool load_eval(std::string name, std::istream& stream); - bool save_eval(std::ostream& stream); - bool save_eval(const std::optional& filename); - } // namespace NNUE } // namespace Eval diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 15638caeeaf..b84bed8b90d 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -55,6 +55,14 @@ namespace Stockfish::Eval::NNUE { template using LargePagePtr = std::unique_ptr>; + std::string trace(Position& pos); + Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); + void hint_common_parent_position(const Position& pos); + + bool load_eval(std::string name, std::istream& stream); + bool save_eval(std::ostream& stream); + bool save_eval(const std::optional& filename); + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 466e0d6f935..17c1c28b264 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -34,6 +34,7 @@ #include "tt.h" #include "uci.h" #include "syzygy/tbprobe.h" +#include "nnue/evaluate_nnue.h" namespace Stockfish { diff --git a/src/uci.cpp b/src/uci.cpp index d3d99243b09..8f9684ee265 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -32,6 +32,7 @@ #include "tt.h" #include "uci.h" #include "syzygy/tbprobe.h" +#include "nnue/evaluate_nnue.h" using namespace std; From 02e4697055519ed206fa76e4ef9abb9f156cd1a0 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 13 Mar 2023 18:32:40 +0100 Subject: [PATCH 0028/1309] Remove 'si' StateInfo variable/parameter. Since st is a member of position we don't need to pass it separately as parameter. While being there also remove some line in pos_is_ok, where a copy of StateInfo was made by using default copy constructor and then verified it's correctedness by doing a memcmp. There is no point in doing that. Passed non-regression test https://tests.stockfishchess.org/tests/view/64098d562644b62c33942b35 LLR: 3.24 (-2.94,2.94) <-1.75,0.25> Total: 548960 W: 145834 L: 146134 D: 256992 Ptnml(0-2): 1617, 57652, 156261, 57314, 1636 closes https://github.com/official-stockfish/Stockfish/pull/4444 No functional change --- src/position.cpp | 58 ++++++++++++++++++++++-------------------------- src/position.h | 4 ++-- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index aeac23a33f4..171193ec131 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -282,7 +282,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th chess960 = isChess960; thisThread = th; - set_state(st); + set_state(); assert(pos_is_ok()); @@ -313,19 +313,19 @@ void Position::set_castling_right(Color c, Square rfrom) { /// Position::set_check_info() sets king attacks to detect if a move gives check -void Position::set_check_info(StateInfo* si) const { +void Position::set_check_info() const { - si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), si->pinners[BLACK]); - si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), si->pinners[WHITE]); + st->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), st->pinners[BLACK]); + st->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), st->pinners[WHITE]); Square ksq = square(~sideToMove); - si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); - si->checkSquares[KNIGHT] = attacks_bb(ksq); - si->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); - si->checkSquares[ROOK] = attacks_bb(ksq, pieces()); - si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK]; - si->checkSquares[KING] = 0; + st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[KNIGHT] = attacks_bb(ksq); + st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); + st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; + st->checkSquares[KING] = 0; } @@ -334,39 +334,39 @@ void Position::set_check_info(StateInfo* si) const { /// The function is only used when a new position is set up, and to verify /// the correctness of the StateInfo data when running in debug mode. -void Position::set_state(StateInfo* si) const { +void Position::set_state() const { - si->key = si->materialKey = 0; - si->pawnKey = Zobrist::noPawns; - si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; - si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + st->key = st->materialKey = 0; + st->pawnKey = Zobrist::noPawns; + st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; + st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); - set_check_info(si); + set_check_info(); for (Bitboard b = pieces(); b; ) { Square s = pop_lsb(b); Piece pc = piece_on(s); - si->key ^= Zobrist::psq[pc][s]; + st->key ^= Zobrist::psq[pc][s]; if (type_of(pc) == PAWN) - si->pawnKey ^= Zobrist::psq[pc][s]; + st->pawnKey ^= Zobrist::psq[pc][s]; else if (type_of(pc) != KING) - si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; + st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; } - if (si->epSquare != SQ_NONE) - si->key ^= Zobrist::enpassant[file_of(si->epSquare)]; + if (st->epSquare != SQ_NONE) + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; if (sideToMove == BLACK) - si->key ^= Zobrist::side; + st->key ^= Zobrist::side; - si->key ^= Zobrist::castling[si->castlingRights]; + st->key ^= Zobrist::castling[st->castlingRights]; for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - si->materialKey ^= Zobrist::psq[pc][cnt]; + st->materialKey ^= Zobrist::psq[pc][cnt]; } @@ -865,7 +865,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { sideToMove = ~sideToMove; // Update king attacks used for fast check detection - set_check_info(st); + set_check_info(); // Calculate the repetition info. It is the ply distance from the previous // occurrence of the same position, negative in the 3-fold case, or zero @@ -1017,7 +1017,7 @@ void Position::do_null_move(StateInfo& newSt) { sideToMove = ~sideToMove; - set_check_info(st); + set_check_info(); st->repetition = 0; @@ -1320,12 +1320,6 @@ bool Position::pos_is_ok() const { if (p1 != p2 && (pieces(p1) & pieces(p2))) assert(0 && "pos_is_ok: Bitboards"); - StateInfo si = *st; - ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize); - - set_state(&si); - if (std::memcmp(&si, st, sizeof(StateInfo))) - assert(0 && "pos_is_ok: State"); for (Piece pc : Pieces) if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) diff --git a/src/position.h b/src/position.h index cc606a5ab00..d3483bcf890 100644 --- a/src/position.h +++ b/src/position.h @@ -179,8 +179,8 @@ class Position { private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); - void set_state(StateInfo* si) const; - void set_check_info(StateInfo* si) const; + void set_state() const; + void set_check_info() const; // Other helpers void move_piece(Square from, Square to); From 24b37e4586ba610d331048446bd036bec5544c4f Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 20 Mar 2023 08:56:44 +0100 Subject: [PATCH 0029/1309] Verified SEE pruning for capturing and checking moves. Patch analyzes field after SEE exchanges concluded with a recapture by the opponent: if opponent Queen/Rook/King results under attack after the exchanges, we consider the move sharp and don't prune it. Important note: By accident I forgot to adjust 'occupied' when the king takes part in the exchanges. As result of this a move is considered sharp too, when opponent king apparently can evade check by recapturing. Surprisingly this seems contribute to patch's strength. STC: https://tests.stockfishchess.org/tests/view/640b16132644b62c33947397 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 116400 W: 31239 L: 30817 D: 54344 Ptnml(0-2): 350, 12742, 31618, 13116, 374 LTC: https://tests.stockfishchess.org/tests/view/640c88092644b62c3394c1c5 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 177600 W: 47988 L: 47421 D: 82191 Ptnml(0-2): 62, 16905, 54317, 17436, 80 closes https://github.com/official-stockfish/Stockfish/pull/4453 bench: 5012145 --- src/movepick.cpp | 6 +++--- src/movepick.h | 1 + src/position.cpp | 15 +++++++-------- src/position.h | 2 +- src/search.cpp | 30 +++++++++++++++++++++++++----- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 36ee46b50f0..855f2b1d67d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -95,7 +95,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, threshold)); + && pos.see_ge(ttm, occupied, threshold)); } /// MovePicker::score() assigns a numerical value to each move in a list, used @@ -197,7 +197,7 @@ Move MovePicker::next_move(bool skipQuiets) { case GOOD_CAPTURE: if (select([&](){ - return pos.see_ge(*cur, Value(-cur->value)) ? + return pos.see_ge(*cur, occupied, Value(-cur->value)) ? // Move losing capture to endBadCaptures to be tried later true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); @@ -264,7 +264,7 @@ Move MovePicker::next_move(bool skipQuiets) { return select([](){ return true; }); case PROBCUT: - return select([&](){ return pos.see_ge(*cur, threshold); }); + return select([&](){ return pos.see_ge(*cur, occupied, threshold); }); case QCAPTURE: if (select([&](){ return depth > DEPTH_QS_RECAPTURES diff --git a/src/movepick.h b/src/movepick.h index b6c07378240..725607b8a54 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -150,6 +150,7 @@ class MovePicker { Value threshold; Depth depth; ExtMove moves[MAX_MOVES]; + Bitboard occupied; }; } // namespace Stockfish diff --git a/src/position.cpp b/src/position.cpp index 171193ec131..ba6888eb948 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1062,7 +1062,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value threshold) const { +bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { assert(is_ok(m)); @@ -1081,7 +1081,7 @@ bool Position::see_ge(Move m, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - Bitboard occupied = pieces() ^ from ^ to; + occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1112,45 +1112,44 @@ bool Position::see_ge(Move m, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = PawnValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { + occupied ^= least_significant_square_bb(bb); if ((swap = KnightValueMg - swap) < res) break; - - occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { + occupied ^= least_significant_square_bb(bb); if ((swap = BishopValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { + occupied ^= least_significant_square_bb(bb); if ((swap = RookValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = QueenValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); } diff --git a/src/position.h b/src/position.h index d3483bcf890..670b621ce74 100644 --- a/src/position.h +++ b/src/position.h @@ -144,7 +144,7 @@ class Position { void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index 17c1c28b264..7564c10983e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1019,9 +1019,27 @@ namespace { + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; + Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-206) * depth)) - continue; + if (!pos.see_ge(move, occupied, Value(-206) * depth)) + { + if (depth < 2 - capture) + continue; + // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges + Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied; + Bitboard attacks = 0; + occupied |= to_sq(move); + while (leftEnemies && !attacks) + { + Square sq = pop_lsb(leftEnemies); + attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // exclude Queen/Rook(s) which were already threatened before SEE + if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) + attacks = 0; + } + if (!attacks) + continue; + } } else { @@ -1047,8 +1065,9 @@ namespace { lmrDepth = std::max(lmrDepth, 0); + Bitboard occupied; // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, occupied, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1533,6 +1552,7 @@ namespace { prevSq); int quietCheckEvasions = 0; + Bitboard occupied; // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1569,7 +1589,7 @@ namespace { continue; } - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, occupied, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1588,7 +1608,7 @@ namespace { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-110))) + if (!pos.see_ge(move, occupied, Value(-110))) continue; } From b973e40e45d75c3b3391141d149d4186494d4652 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 21 Mar 2023 23:58:25 +0300 Subject: [PATCH 0030/1309] Update Elo estimates for terms in search Setting the Elo value of some functions which were not set before. All tests run at 10+0.1 (STC), 25000 games (Same as #4294). Book used: UHO_XXL_+0.90_+1.19.epd Values are rounded to the nearest non-negative integer. Test links: https://tests.stockfishchess.org/tests/view/6419ab5b65775d3b539f46c6 https://tests.stockfishchess.org/tests/view/6419adb465775d3b539f4730 https://tests.stockfishchess.org/tests/view/6419ae9c65775d3b539f4756 https://tests.stockfishchess.org/tests/view/6419b03f65775d3b539f47a8 https://tests.stockfishchess.org/tests/view/6419b35d65775d3b539f4860 https://tests.stockfishchess.org/tests/view/6419b6b965775d3b539f48e6 https://tests.stockfishchess.org/tests/view/6419cade65775d3b539f4cd5 https://tests.stockfishchess.org/tests/view/6419cbb565775d3b539f4d01 https://tests.stockfishchess.org/tests/view/6419cc6965775d3b539f4d1e closes https://github.com/official-stockfish/Stockfish/pull/4459 No functional change --- src/search.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7564c10983e..b2983f6647a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -732,7 +732,7 @@ namespace { } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 elo) + // 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); eval = ss->staticEval; complexity = abs(ss->staticEval - pos.psq_eg_stm()); @@ -1120,15 +1120,15 @@ namespace { else if (singularBeta >= beta) return singularBeta; - // If the eval of ttMove is greater than beta, we reduce it (negative extension) + // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; - // If the eval of ttMove is less than value, we reduce it (negative extension) + // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; - // If the eval of ttMove is less than alpha, we reduce it (negative extension) + // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo) else if (ttValue <= alpha) extension = -1; } @@ -1182,7 +1182,7 @@ namespace { if (ttCapture) r++; - // Decrease reduction for PvNodes based on depth + // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) r -= 1 + 12 / (3 + depth); @@ -1195,11 +1195,11 @@ namespace { && (mp.threatenedPieces & from_sq(move))) r--; - // Increase reduction if next ply has a lot of fail high + // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss+1)->cutoffCnt > 3) r++; - // Decrease reduction if move is a killer and we have a good history + // Decrease reduction if move is a killer and we have a good history (~1 Elo) if (move == ss->killers[0] && (*contHist[0])[movedPiece][to_sq(move)] >= 3722) r--; @@ -1210,7 +1210,7 @@ namespace { + (*contHist[3])[movedPiece][to_sq(move)] - 4182; - // Decrease/increase reduction for moves with a good/bad history (~30 Elo) + // Decrease/increase reduction for moves with a good/bad history (~25 Elo) r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) @@ -1347,7 +1347,7 @@ namespace { { alpha = value; - // Reduce other moves if we have found at least one score improvement + // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 && depth < 6 && beta < 10534 @@ -1413,7 +1413,7 @@ namespace { 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. + // opponent move is probably good and the new position is added to the search tree. (~7 Elo) if (bestValue <= alpha) ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); @@ -1432,7 +1432,7 @@ namespace { // qsearch() is the quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. - // (~155 elo) + // (~155 Elo) template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { From 1b5738e0c958ac8d3d140a3d182b85f8c0c0cd2c Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 24 Mar 2023 07:45:22 +0300 Subject: [PATCH 0031/1309] Simplify statScore initialization This patch simplifies initialization of statScore to "always set it up to 0" instead of setting it up to 0 two plies deeper. Reason for why it was done in previous way partially was because of LMR usage of previous statScore which was simplified long time ago so it makes sense to make in more simple there. Passed STC: https://tests.stockfishchess.org/tests/view/641a86d1db43ab2ba6f7b31d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 115648 W: 30895 L: 30764 D: 53989 Ptnml(0-2): 368, 12741, 31473, 12876, 366 Passed LTC: https://tests.stockfishchess.org/tests/view/641b1c31db43ab2ba6f7d17a LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 175576 W: 47122 L: 47062 D: 81392 Ptnml(0-2): 91, 17077, 53390, 17141, 89 closes https://github.com/official-stockfish/Stockfish/pull/4460 bench 5081969 --- src/search.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b2983f6647a..d2358ea2e1c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -607,14 +607,7 @@ namespace { (ss+2)->cutoffCnt = 0; ss->doubleExtensions = (ss-1)->doubleExtensions; Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - - // Initialize statScore to zero for the grandchildren of the current position. - // So statScore is shared between all grandchildren and only the first grandchild - // starts with statScore = 0. Later grandchildren start with the last calculated - // statScore of the previous grandchild. This influences the reduction rules in - // LMR which are based on the statScore of parent position. - if (!rootNode) - (ss+2)->statScore = 0; + ss->statScore = 0; // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; From 587bc647d7d14b53d8625c4446006e23a4acd82a Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 22 Mar 2023 16:50:55 +0900 Subject: [PATCH 0032/1309] Remove non_pawn_material in NNUE::evaluate After "Use NNUE complexity in search, retune related parameters" commit, the effect of non-pawn material adjustment has been nearly diminished. This patch removes pos.non_pawn_material as a simplification, which passed non-regression tests with both STC and LTC. Passed non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 75152 W: 20030 L: 19856 D: 35266 Ptnml(0-2): 215, 8281, 20459, 8357, 264 https://tests.stockfishchess.org/tests/view/641ab471db43ab2ba6f7bc58 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 193864 W: 51870 L: 51829 D: 90165 Ptnml(0-2): 86, 18968, 58794, 18987, 97 https://tests.stockfishchess.org/tests/view/641b4fe6db43ab2ba6f7db96 closes https://github.com/official-stockfish/Stockfish/pull/4461 Bench: 5020718 --- src/nnue/evaluate_nnue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index f33aa3b889b..329adfdaa9e 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -148,7 +148,7 @@ namespace Stockfish::Eval::NNUE { // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - int delta = 24 - pos.non_pawn_material() / 9560; + constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType transformedFeaturesUnaligned[ From 43108a619899af084e45224e8744ca668a9efed2 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 28 Mar 2023 19:53:43 +0200 Subject: [PATCH 0033/1309] Reuse existing functions to read/write array of network parameters closes https://github.com/official-stockfish/Stockfish/pull/4463 No functional change --- src/nnue/layers/affine_transform.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 313b1568393..f84f054e7de 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -256,8 +256,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Read network parameters bool read_parameters(std::istream& stream) { - for (IndexType i = 0; i < OutputDimensions; ++i) - biases[i] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) weights[get_weight_index(i)] = read_little_endian(stream); @@ -267,8 +266,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Write network parameters bool write_parameters(std::ostream& stream) const { - for (IndexType i = 0; i < OutputDimensions; ++i) - write_little_endian(stream, biases[i]); + write_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, weights[get_weight_index(i)]); @@ -452,8 +450,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Read network parameters bool read_parameters(std::istream& stream) { - for (IndexType i = 0; i < OutputDimensions; ++i) - biases[i] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) weights[get_weight_index(i)] = read_little_endian(stream); @@ -462,8 +459,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Write network parameters bool write_parameters(std::ostream& stream) const { - for (IndexType i = 0; i < OutputDimensions; ++i) - write_little_endian(stream, biases[i]); + write_little_endian(stream, biases, OutputDimensions); for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, weights[get_weight_index(i)]); From 37160c4b1632245d46d86cec7bd22b76f5a87531 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 27 Mar 2023 01:00:54 -0400 Subject: [PATCH 0034/1309] Update default net to nn-dabb1ed23026.nnue Created by retraining the master net with these modifications: * New filtering methods for existing data from T80 sep+oct2022, T79 apr2022, T78 jun+jul+aug+sep2022, T77 dec2021 * Adding new filtered data from T80 aug2022 and T78 apr+may2022 * Increasing early-fen-skipping from 28 to 30 ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T80novT79mayT60novdec-v2-T80augsepoctT79aprT78aprtosep-v6-T77dec-v3-sk30 \ --training-dataset /data/leela96-dfrc99-T80novT79mayT60novdec-v2-T80augsepoctT79aprT78aprtosep-v6-T77dec-v3.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes \ --start-from-engine-test-net True \ --early-fen-skipping 30 \ --max_epoch 900 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --gpus "0," \ --seed $RANDOM ``` The v3 filtering used for data from T77dec 2021 differs from v2 filtering in that: * To improve binpack compression, positions after ply 28 were skipped during training by setting position scores to VALUE_NONE (32002) instead of removing them entirely * All early-game positions with ply <= 28 were removed to maximize binpack compression * Only bestmove captures at d6pv2 search were skipped, not 2nd bestmove captures * Binpack compression was repaired for the remaining positions by effectively replacing bestmoves with "played moves" to maintain contiguous sequences of positions in the training game data After improving binpack compression, The T77 dec2021 data size was reduced from 95G to 19G. The v6 filtering used for data from T80augsepoctT79aprT78aprtosep 2022 differs from v2 in that: * All positions with only one legal move were removed * Tighter score differences at d6pv2 search were used to remove more positions with only one good move than before * d6pv2 search was not used to remove positions where the best 2 moves were captures ``` python3 interleave_binpacks.py \ nn-547-dataset/leela96-eval-filt-v2.binpack \ nn-547-dataset/dfrc99-eval-filt-v2.binpack \ nn-547-dataset/test80-nov2022-12tb7p-eval-filt-v2-d6.binpack \ nn-547-dataset/T79-may2022-12tb7p-eval-filt-v2.binpack \ nn-547-dataset/T60-nov2021-12tb7p-eval-filt-v2.binpack \ nn-547-dataset/T60-dec2021-12tb7p-eval-filt-v2.binpack \ filt-v6/test80-aug2022-16tb7p-filter-v6.binpack \ filt-v6/test80-sep2022-16tb7p-filter-v6.binpack \ filt-v6/test80-oct2022-16tb7p-filter-v6.binpack \ filt-v6/test79-apr2022-16tb7p-filter-v6.binpack \ filt-v6/test78-aprmay2022-16tb7p-filter-v6.binpack \ filt-v6/test78-junjulaug2022-16tb7p-filter-v6.binpack \ filt-v6/test78-sep2022-16tb7p-filter-v6.binpack \ filt-v3/test77-dec2021-16tb7p-filt-v3.binpack \ /data/leela96-dfrc99-T80novT79mayT60novdec-v2-T80augsepoctT79aprT78aprtosep-v6-T77dec-v3.binpack ``` The code for the new data filtering methods is available at: https://github.com/linrock/Stockfish/tree/nnue-data-v3/nnue-data The code for giving hexword names to .nnue files is at: https://github.com/linrock/nnue-namer Links for downloading the training data components can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch779.nnue : 0.6 +/- 3.1 Passed STC: https://tests.stockfishchess.org/tests/view/64212412db43ab2ba6f8efb0 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 82256 W: 22185 L: 21809 D: 38262 Ptnml(0-2): 286, 9065, 22067, 9407, 303 Passed LTC: https://tests.stockfishchess.org/tests/view/64223726db43ab2ba6f91d6c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 30840 W: 8437 L: 8149 D: 14254 Ptnml(0-2): 14, 2891, 9323, 3177, 15 closes https://github.com/official-stockfish/Stockfish/pull/4465 bench 5101970 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 3615fe6d7e6..61846073300 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-52471d67216a.nnue" + #define EvalFileDefaultName "nn-dabb1ed23026.nnue" namespace NNUE { From a9c26357deb01c764cd16ef4e61acb4f687cbd77 Mon Sep 17 00:00:00 2001 From: Miguel Lahoz Date: Tue, 28 Mar 2023 00:06:24 +0800 Subject: [PATCH 0035/1309] Clean up repetitive declarations for see_ge The occupied bitboard is only used in one place and is otherwise thrown away. To simplify use, see_ge function can instead be overloaded. Repetitive declarations for occupied bitboard can be removed. Passed non-regression test https://tests.stockfishchess.org/tests/view/6421c286db43ab2ba6f908eb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 48912 W: 13196 L: 13001 D: 22715 Ptnml(0-2): 146, 5003, 13967, 5190, 150 closes https://github.com/official-stockfish/Stockfish/pull/4469 No functional change. --- src/movepick.cpp | 6 +++--- src/movepick.h | 1 - src/position.cpp | 5 +++++ src/position.h | 1 + src/search.cpp | 8 +++----- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 855f2b1d67d..36ee46b50f0 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -95,7 +95,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, occupied, threshold)); + && pos.see_ge(ttm, threshold)); } /// MovePicker::score() assigns a numerical value to each move in a list, used @@ -197,7 +197,7 @@ Move MovePicker::next_move(bool skipQuiets) { case GOOD_CAPTURE: if (select([&](){ - return pos.see_ge(*cur, occupied, Value(-cur->value)) ? + return pos.see_ge(*cur, Value(-cur->value)) ? // Move losing capture to endBadCaptures to be tried later true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); @@ -264,7 +264,7 @@ Move MovePicker::next_move(bool skipQuiets) { return select([](){ return true; }); case PROBCUT: - return select([&](){ return pos.see_ge(*cur, occupied, threshold); }); + return select([&](){ return pos.see_ge(*cur, threshold); }); case QCAPTURE: if (select([&](){ return depth > DEPTH_QS_RECAPTURES diff --git a/src/movepick.h b/src/movepick.h index 725607b8a54..b6c07378240 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -150,7 +150,6 @@ class MovePicker { Value threshold; Depth depth; ExtMove moves[MAX_MOVES]; - Bitboard occupied; }; } // namespace Stockfish diff --git a/src/position.cpp b/src/position.cpp index ba6888eb948..e6fdb511fcc 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1163,6 +1163,11 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return bool(res); } +bool Position::see_ge(Move m, Value threshold) const { + Bitboard occupied; + return see_ge(m, occupied, threshold); +} + /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index 670b621ce74..bb45c44a3bf 100644 --- a/src/position.h +++ b/src/position.h @@ -145,6 +145,7 @@ class Position { // Static Exchange Evaluation bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index d2358ea2e1c..ac74cdaf885 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1058,9 +1058,8 @@ namespace { lmrDepth = std::max(lmrDepth, 0); - Bitboard occupied; // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, occupied, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) continue; } } @@ -1545,7 +1544,6 @@ namespace { prevSq); int quietCheckEvasions = 0; - Bitboard occupied; // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1582,7 +1580,7 @@ namespace { continue; } - if (futilityBase <= alpha && !pos.see_ge(move, occupied, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1601,7 +1599,7 @@ namespace { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, occupied, Value(-110))) + if (!pos.see_ge(move, Value(-110))) continue; } From 3f01e3f41f11aa66befec2307a32ee023c699a2a Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Thu, 23 Mar 2023 13:35:34 +0300 Subject: [PATCH 0036/1309] Allow PvNode in futility pruning for captures. Passed non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 148128 W: 39428 L: 39333 D: 69367 Ptnml(0-2): 492, 16326, 40315, 16457, 474 https://tests.stockfishchess.org/tests/view/641c2dbfdb43ab2ba6f804e8 Passed non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 376256 W: 100906 L: 101039 D: 174311 Ptnml(0-2): 186, 36697, 114494, 36566, 185 https://tests.stockfishchess.org/tests/view/641d33b2db43ab2ba6f83338 closes https://github.com/official-stockfish/Stockfish/pull/4470 bench: 4935616 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ac74cdaf885..5f95a1bd718 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1005,7 +1005,6 @@ namespace { { // Futility pruning for captures (~2 Elo) if ( !givesCheck - && !PvNode && lmrDepth < 6 && !ss->inCheck && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] From 7a6fa34f5f9f0f193d9350cd58c82a8f98a6505d Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sun, 12 Mar 2023 15:16:51 +0200 Subject: [PATCH 0037/1309] Improve compatibility this makes it easier to compile under MSVC, even though we recommend gcc/clang for production compiles at the moment. In Win32 API, by default, most null-terminated character strings arguments are of wchar_t (UTF16, formerly UCS16-LE) type, i.e. 2 bytes (at least) per character. So, src/misc.cpp should have proper type. Respectively, for src/syzygy/tbprobe.cpp, in Widows, file paths should be std::wstring rather than std::string. However, this requires a very big number of changes, since the config files are also keeping the 8-bit-per-character std::string strings. Therefore, just one change of using 8-byte-per-character CreateFileA make it compile under MSVC. closes https://github.com/official-stockfish/Stockfish/pull/4438 No functional change --- src/misc.cpp | 4 ++-- src/syzygy/tbprobe.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index c22126afe2d..6469c5cf9ec 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -605,7 +605,7 @@ static int best_node(size_t idx) { DWORD byteOffset = 0; // Early exit if the needed API is not available at runtime - HMODULE k32 = GetModuleHandle("Kernel32.dll"); + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); if (!fun1) return -1; @@ -675,7 +675,7 @@ void bindThisThread(size_t idx) { return; // Early exit if the needed API are not available at runtime - HMODULE k32 = GetModuleHandle("Kernel32.dll"); + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2"); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index b594ac3714e..9cb0bfdbc0f 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -234,7 +234,7 @@ class TBFile : public std::ifstream { } #else // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. - HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); if (fd == INVALID_HANDLE_VALUE) From e8742bdab35c63253b5110d1861f27337e18f9fc Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Wed, 29 Mar 2023 12:43:36 +0300 Subject: [PATCH 0038/1309] Made advanced Windows API calls dynamically linked Made advanced Windows API calls (those from Advapi32.dll) dynamically linked to avoid link errors when compiling using Intel icx compiler for Windows. https://github.com/official-stockfish/Stockfish/pull/4467 No functional change --- src/misc.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 6469c5cf9ec..cac9dd94996 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -38,6 +38,9 @@ using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY); using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); using fun5_t = WORD(*)(); +using fun6_t = bool(*)(HANDLE, DWORD, PHANDLE); +using fun7_t = bool(*)(LPCSTR, LPCSTR, PLUID); +using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); } #endif @@ -488,11 +491,26 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize if (!largePageSize) return nullptr; + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + HMODULE k32 = GetModuleHandle("Advapi32.dll"); + auto fun6 = (fun6_t)(void(*)())GetProcAddress(k32, "OpenProcessToken"); + if (!fun6) + return nullptr; + auto fun7 = (fun7_t)(void(*)())GetProcAddress(k32, "LookupPrivilegeValueA"); + if (!fun7) + return nullptr; + auto fun8 = (fun8_t)(void(*)())GetProcAddress(k32, "AdjustTokenPrivileges"); + if (!fun8) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + // OpenProcessToken() + if (!fun6(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) return nullptr; - if (LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)) + // LookupPrivilegeValueA() + if (fun7(nullptr, SE_LOCK_MEMORY_NAME, &luid)) { TOKEN_PRIVILEGES tp { }; TOKEN_PRIVILEGES prevTp { }; @@ -504,7 +522,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (AdjustTokenPrivileges( + // AdjustTokenPrivileges() + if (fun8( hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && GetLastError() == ERROR_SUCCESS) { @@ -514,7 +533,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); // Privilege no longer needed, restore previous state - AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + // AdjustTokenPrivileges () + fun8(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); } } From c3c46feebba470dcbaa0a5a6ef83534091dffe6a Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:46:15 +0800 Subject: [PATCH 0039/1309] Remove reduction for moving threatened piece Simplify away "Decrease reduction if we move a threatened piece". Running a dbg_hit_on() shows that this line is only called ~0.12% of the time. Simplification STC: https://tests.stockfishchess.org/tests/view/641ec2dcdb43ab2ba6f88103 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 146128 W: 39168 L: 39070 D: 67890 Ptnml(0-2): 466, 16117, 39830, 16155, 496 Simplification LTC: https://tests.stockfishchess.org/tests/view/64200689db43ab2ba6f8bca8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 248016 W: 66703 L: 66714 D: 114599 Ptnml(0-2): 105, 24202, 75406, 24189, 106 closes https://github.com/official-stockfish/Stockfish/pull/4471 Bench: 4961236 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5f95a1bd718..2fcbc7df2a5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1181,11 +1181,6 @@ namespace { if (singularQuietLMR) r--; - // Decrease reduction if we move a threatened piece (~1 Elo) - if ( depth > 9 - && (mp.threatenedPieces & from_sq(move))) - r--; - // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss+1)->cutoffCnt > 3) r++; From 66bf45b99e2061c1ba74f9975bc5059ac0121dfd Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 1 Apr 2023 11:56:49 +0200 Subject: [PATCH 0040/1309] Stringify the git info passed avoid escaping the string in the Makefile. Alternative to https://github.com/official-stockfish/Stockfish/pull/4476 closes https://github.com/official-stockfish/Stockfish/pull/4481 No functional change. --- src/Makefile | 4 ++-- src/evaluate.cpp | 2 -- src/misc.cpp | 6 ++---- src/misc.h | 3 +++ 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Makefile b/src/Makefile index e257bc6347d..0b22fb4e6e9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -705,13 +705,13 @@ endif ### 3.7.1 Try to include git commit sha for versioning GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null) ifneq ($(GIT_SHA), ) - CXXFLAGS += -DGIT_SHA=\"$(GIT_SHA)\" + CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif ### 3.7.2 Try to include git commit date for versioning GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) ifneq ($(GIT_DATE), ) - CXXFLAGS += -DGIT_DATE=\"$(GIT_DATE)\" + CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif ### 3.8 Link Time Optimization diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 99b873004ed..12883fcc43e 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -82,8 +82,6 @@ namespace Eval { eval_file = EvalFileDefaultName; #if defined(DEFAULT_NNUE_DIRECTORY) - #define stringify2(x) #x - #define stringify(x) stringify2(x) vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; #else vector dirs = { "" , "" , CommandLine::binaryDirectory }; diff --git a/src/misc.cpp b/src/misc.cpp index cac9dd94996..e36a04bccfb 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -160,7 +160,7 @@ string engine_info(bool to_uci) { { ss << "-"; #ifdef GIT_DATE - ss << GIT_DATE; + ss << stringify(GIT_DATE); #else constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); string month, day, year; @@ -173,7 +173,7 @@ string engine_info(bool to_uci) { ss << "-"; #ifdef GIT_SHA - ss << GIT_SHA; + ss << stringify(GIT_SHA); #else ss << "nogit"; #endif @@ -190,8 +190,6 @@ string engine_info(bool to_uci) { std::string compiler_info() { - #define stringify2(x) #x - #define stringify(x) stringify2(x) #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) /// Predefined macros hell: diff --git a/src/misc.h b/src/misc.h index c20a816efa0..d4965156325 100644 --- a/src/misc.h +++ b/src/misc.h @@ -28,6 +28,9 @@ #include "types.h" +#define stringify2(x) #x +#define stringify(x) stringify2(x) + namespace Stockfish { std::string engine_info(bool to_uci = false); From 38a80c0b47397dbdd9167ec1476dfd3c033020d6 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Thu, 30 Mar 2023 11:14:30 +0000 Subject: [PATCH 0041/1309] Simplify away complexityAverage Instead of tracking the average of complexity values, calculate complexity of root position at the beginning of the search and use it as a scaling factor in time management. Passed non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 58752 W: 15738 L: 15551 D: 27463 Ptnml(0-2): 164, 6194, 16478, 6371, 169 https://tests.stockfishchess.org/tests/view/6423010edb43ab2ba6f9424a Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 92872 W: 24865 L: 24729 D: 43278 Ptnml(0-2): 38, 8652, 28929, 8770, 47 https://tests.stockfishchess.org/tests/view/6423c1f0db43ab2ba6f9644f closes https://github.com/official-stockfish/Stockfish/pull/4472 No functional change --- src/misc.h | 26 -------------------------- src/search.cpp | 17 ++++++++++------- src/thread.h | 2 +- 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/misc.h b/src/misc.h index d4965156325..69d470c22f8 100644 --- a/src/misc.h +++ b/src/misc.h @@ -89,32 +89,6 @@ static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; static inline const bool IsLittleEndian = (Le.c[0] == 4); -// RunningAverage : a class to calculate a running average of a series of values. -// For efficiency, all computations are done with integers. -class RunningAverage { - public: - - // Reset the running average to rational value p / q - void set(int64_t p, int64_t q) - { average = p * PERIOD * RESOLUTION / q; } - - // Update average with value v - void update(int64_t v) - { average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; } - - // Test if average is strictly greater than rational a / b - bool is_greater(int64_t a, int64_t b) const - { return b * average > a * (PERIOD * RESOLUTION); } - - int64_t value() const - { return average / (PERIOD * RESOLUTION); } - - private : - static constexpr int64_t PERIOD = 4096; - static constexpr int64_t RESOLUTION = 1024; - int64_t average; -}; - template class ValueList { diff --git a/src/search.cpp b/src/search.cpp index 2fcbc7df2a5..3136046d2f5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -293,6 +293,15 @@ void Thread::search() { if (mainThread) { + + int rootComplexity; + if (Eval::useNNUE) + Eval::NNUE::evaluate(rootPos, true, &rootComplexity); + else + Eval::evaluate(rootPos, &rootComplexity); + + mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); + if (mainThread->bestPreviousScore == VALUE_INFINITE) for (int i = 0; i < 4; ++i) mainThread->iterValue[i] = VALUE_ZERO; @@ -311,8 +320,6 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - complexityAverage.set(153, 1); - optimism[us] = optimism[~us] = VALUE_ZERO; int searchAgainCounter = 0; @@ -472,10 +479,8 @@ void Thread::search() { timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - int complexity = mainThread->complexityAverage.value(); - double complexPosition = std::min(1.03 + (complexity - 241) / 1552.0, 1.45); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * complexPosition; + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity; // Cap used time in case of a single legal move for a better viewer experience in tournaments // yielding correct scores and sufficiently fast moves. @@ -755,8 +760,6 @@ namespace { tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - thisThread->complexityAverage.update(complexity); - // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { diff --git a/src/thread.h b/src/thread.h index 46cdb11c36a..d6a48eca73d 100644 --- a/src/thread.h +++ b/src/thread.h @@ -60,7 +60,6 @@ class Thread { Pawns::Table pawnsTable; Material::Table materialTable; size_t pvIdx, pvLast; - RunningAverage complexityAverage; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; Color nmpColor; @@ -87,6 +86,7 @@ struct MainThread : public Thread { void search() override; void check_time(); + double complexity; double previousTimeReduction; Value bestPreviousScore; Value bestPreviousAverageScore; From bc50378ff1915a8ad6ac3e4946193c65e4cacb56 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Fri, 31 Mar 2023 18:16:50 +0300 Subject: [PATCH 0042/1309] Replace deprecated icc with icx Replace the deprecated Intel compiler icc with its newer icx variant. This newer compiler is based on clang, and yields good performance. As before, currently only linux is supported. closes https://github.com/official-stockfish/Stockfish/pull/4478 No functional change --- src/Makefile | 70 +++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Makefile b/src/Makefile index 0b22fb4e6e9..abcf11b0a11 100644 --- a/src/Makefile +++ b/src/Makefile @@ -425,10 +425,11 @@ ifeq ($(COMP),mingw) CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations endif -ifeq ($(COMP),icc) - comp=icc - CXX=icpc - CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi +ifeq ($(COMP),icx) + comp=icx + CXX=icpx + CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \ + -Wconditional-uninitialized -Wabi -Wdeprecated endif ifeq ($(COMP),clang) @@ -499,9 +500,9 @@ ifeq ($(COMP),ndk) LDFLAGS += -static-libstdc++ -pie -lm -latomic endif -ifeq ($(comp),icc) - profile_make = icc-profile-make - profile_use = icc-profile-use +ifeq ($(comp),icx) + profile_make = icx-profile-make + profile_use = icx-profile-use else ifeq ($(comp),clang) profile_make = clang-profile-make profile_use = clang-profile-use @@ -572,7 +573,7 @@ ifeq ($(optimize),yes) endif ifeq ($(KERNEL),Darwin) - ifeq ($(comp),$(filter $(comp),clang icc)) + ifeq ($(comp),$(filter $(comp),clang icx)) CXXFLAGS += -mdynamic-no-pic endif @@ -608,8 +609,6 @@ endif ifeq ($(popcnt),yes) ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64)) CXXFLAGS += -DUSE_POPCNT - else ifeq ($(comp),icc) - CXXFLAGS += -msse3 -DUSE_POPCNT else CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT endif @@ -618,63 +617,63 @@ endif ### 3.6 SIMD architectures ifeq ($(avx2),yes) CXXFLAGS += -DUSE_AVX2 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavx2 -mbmi endif endif ifeq ($(avxvnni),yes) CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavxvnni endif endif ifeq ($(avx512),yes) CXXFLAGS += -DUSE_AVX512 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavx512f -mavx512bw endif endif ifeq ($(vnni256),yes) CXXFLAGS += -DUSE_VNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 endif endif ifeq ($(vnni512),yes) CXXFLAGS += -DUSE_VNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) - CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512 endif endif ifeq ($(sse41),yes) CXXFLAGS += -DUSE_SSE41 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -msse4.1 endif endif ifeq ($(ssse3),yes) CXXFLAGS += -DUSE_SSSE3 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mssse3 endif endif ifeq ($(sse2),yes) CXXFLAGS += -DUSE_SSE2 - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -msse2 endif endif ifeq ($(mmx),yes) CXXFLAGS += -DUSE_MMX - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mmmx endif endif @@ -697,7 +696,7 @@ endif ### 3.7 pext ifeq ($(pext),yes) CXXFLAGS += -DUSE_PEXT - ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mbmi2 endif endif @@ -719,8 +718,11 @@ endif ### needs access to the optimization flags. ifeq ($(optimize),yes) ifeq ($(debug), no) - ifeq ($(comp),clang) + ifeq ($(comp),$(filter $(comp),clang icx)) CXXFLAGS += -flto=full + ifeq ($(comp),icx) + CXXFLAGS += -fwhole-program-vtables + endif ifeq ($(target_windows),yes) CXXFLAGS += -fuse-ld=lld endif @@ -807,7 +809,7 @@ help: @echo "gcc > Gnu compiler (default)" @echo "mingw > Gnu compiler with MinGW under Windows" @echo "clang > LLVM Clang compiler" - @echo "icc > Intel compiler" + @echo "icx > Intel oneAPI DPC++/C++ Compiler" @echo "ndk > Google NDK to cross-compile for Android" @echo "" @echo "Simple examples. If you don't know what to do, you likely want to run one of: " @@ -833,8 +835,10 @@ endif .PHONY: help build profile-build strip install clean net objclean profileclean \ - config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ - clang-profile-use clang-profile-make FORCE + config-sanity \ + icx-profile-use icx-profile-make \ + gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make FORCE build: net config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all @@ -949,7 +953,9 @@ config-sanity: net @echo "vnni256: '$(vnni256)'" @echo "vnni512: '$(vnni512)'" @echo "neon: '$(neon)'" + @echo "dotprod: '$(dotprod)'" @echo "arm_version: '$(arm_version)'" + @echo "target_windows: '$(target_windows)'" @echo "" @echo "Flags:" @echo "CXX: $(CXX)" @@ -978,7 +984,7 @@ config-sanity: net @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no" @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no" @test "$(neon)" = "yes" || test "$(neon)" = "no" - @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ + @test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ || test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang" $(EXE): $(OBJS) @@ -1016,15 +1022,17 @@ gcc-profile-use: EXTRALDFLAGS='-lgcov' \ all -icc-profile-make: - @mkdir -p profdir +icx-profile-make: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ all -icc-profile-use: +icx-profile-use: + $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \ + EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ all .depend: $(SRCS) From 6a6e32dfc80488dfdcd6c23e601063b47729e890 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 1 Apr 2023 15:22:53 +0300 Subject: [PATCH 0043/1309] Decrease Depth more for positions not in TT. If the position is not in TT, decrease depth by 2 or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth. Many thanks to Vizvezdenec as the main idea was his. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 70664 W: 18995 L: 18639 D: 33030 Ptnml(0-2): 228, 7712, 19090, 8080, 222 https://tests.stockfishchess.org/tests/view/64258a8bdb43ab2ba6f9b682 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 85040 W: 23227 L: 22836 D: 38977 Ptnml(0-2): 26, 8115, 25867, 8466, 46 https://tests.stockfishchess.org/tests/view/64262057db43ab2ba6f9d0e7 closes https://github.com/official-stockfish/Stockfish/pull/4482 bench: 4380438 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3136046d2f5..46cca50f4b2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -899,11 +899,11 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); } - // Step 11. If the position is not in TT, decrease depth by 3. + // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode && !ttMove) - depth -= 3; + depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); From 1fee996999364bedbd9ca4c29649d5c7321947c5 Mon Sep 17 00:00:00 2001 From: Miguel Lahoz Date: Sun, 2 Apr 2023 17:28:39 +0800 Subject: [PATCH 0044/1309] Remove unneeded bitboard from MP Recent simplification has removed the need for an extra bitboard in MP struct. Use a local variable instead. STC: Passed Non-regression test https://tests.stockfishchess.org/tests/view/64294ae677ff3301150cba16 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 64872 W: 17383 L: 17203 D: 30286 Ptnml(0-2): 179, 6675, 18546, 6859, 177 closes https://github.com/official-stockfish/Stockfish/pull/4490 No functional change. --- src/movepick.cpp | 3 +-- src/movepick.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 36ee46b50f0..6fbcb2c3d2f 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -69,7 +69,6 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); - threatenedPieces = 0; } /// MovePicker constructor for quiescence search @@ -106,7 +105,7 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook; + [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces; if constexpr (Type == QUIETS) { Color us = pos.side_to_move(); diff --git a/src/movepick.h b/src/movepick.h index b6c07378240..0b44557f198 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -131,8 +131,6 @@ class MovePicker { MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); - Bitboard threatenedPieces; - private: template Move select(Pred); template void score(); From 77e2b915e1e4f2469a414712e52b469633fb3273 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 1 Apr 2023 18:48:31 -0500 Subject: [PATCH 0045/1309] Simplifiy TM's root complexity Also requires moving optimism initialization, this is a very early `evaluate()` call. STC: https://tests.stockfishchess.org/tests/view/6428c39677ff3301150ca0d7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 51256 W: 13805 L: 13612 D: 23839 Ptnml(0-2): 145, 5283, 14592, 5450, 158 LTC: https://tests.stockfishchess.org/tests/view/64296ff377ff3301150cc519 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 106968 W: 28951 L: 28830 D: 49187 Ptnml(0-2): 47, 9746, 33789, 9843, 59 closes https://github.com/official-stockfish/Stockfish/pull/4492 no functional change --- src/search.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 46cca50f4b2..becaee3a20a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -290,15 +290,13 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; + optimism[us] = optimism[~us] = VALUE_ZERO; if (mainThread) { int rootComplexity; - if (Eval::useNNUE) - Eval::NNUE::evaluate(rootPos, true, &rootComplexity); - else - Eval::evaluate(rootPos, &rootComplexity); + Eval::evaluate(rootPos, &rootComplexity); mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); @@ -320,8 +318,6 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); - optimism[us] = optimism[~us] = VALUE_ZERO; - int searchAgainCounter = 0; // Iterative deepening loop until requested to stop or the target depth is reached From 9a42bbdf3163222db5e0fa764d48ca0a09a0dec2 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 4 Apr 2023 14:26:29 +0300 Subject: [PATCH 0046/1309] Parameters Tweak Passed STC LLR: 3.22 (-2.94,2.94) <0.00,2.00> Total: 664048 W: 177526 L: 176301 D: 310221 Ptnml(0-2): 2002, 72968, 180891, 74129, 2034 https://tests.stockfishchess.org/tests/view/64219901db43ab2ba6f901fa Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 77576 W: 21125 L: 20750 D: 35701 Ptnml(0-2): 24, 7350, 23669, 7717, 28 https://tests.stockfishchess.org/tests/view/642abe3377ff3301150d3a16 closes https://github.com/official-stockfish/Stockfish/pull/4493 bench: 4522076 --- src/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index becaee3a20a..749de792dd6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1449 - int(delta) * 1032 / int(rootDelta)) / 1024 + (!i && r > 941); + return (r + 1449 - int(delta) * 1001 / int(rootDelta)) / 1024 + (!i && r > 941); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(340 * d - 470, 1855); + return std::min(341 * d - 470, 1855); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -1057,7 +1057,7 @@ namespace { lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 15 * lmrDepth))) + if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth))) continue; } } @@ -1193,10 +1193,10 @@ namespace { + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4182; + - 4082; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (11791 + 3992 * (depth > 6 && depth < 19)); + r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has From a2737d8bb5e480563823820fb12a8887d61c991e Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 5 Apr 2023 07:25:00 +0800 Subject: [PATCH 0047/1309] Simplify away piece count condition for useClassical Simplify away the piece count condition for useClassical. In compensation, the psq requirement is increased by 15%. Also updated the Elo estimate for useClassical, based on recent testing. Simplification STC: https://tests.stockfishchess.org/tests/view/642acbb577ff3301150d3ef5 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 51984 W: 13906 L: 13707 D: 24371 Ptnml(0-2): 150, 5638, 14227, 5817, 160 Simplification LTC: https://tests.stockfishchess.org/tests/view/642b9c5777ff3301150d778a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 119696 W: 32412 L: 32300 D: 54984 Ptnml(0-2): 53, 11529, 36567, 11651, 48 closes https://github.com/official-stockfish/Stockfish/pull/4494 Bench: 5089321 --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 12883fcc43e..703cf869cee 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1053,8 +1053,8 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // We use the much less accurate but faster Classical eval when the NNUE // option is set to false. Otherwise we use the NNUE eval unless the - // PSQ advantage is decisive and several pieces remain. (~3 Elo) - bool useClassical = !useNNUE || (pos.count() > 7 && abs(psq) > 1781); + // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC) + bool useClassical = !useNNUE || abs(psq) > 2048; if (useClassical) v = Evaluation(pos).value(); From 510aca1ef62279dc35941fa45ed61fb9d3796f10 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 5 Apr 2023 05:48:34 +0300 Subject: [PATCH 0048/1309] Assign negative stat bonuses for quiet moves at Pv nodes This patch assigns negative stat bonuses for quiet moves at pv nodes which are searched at depth greater than this node assumes, so are extended. Passed STC: https://tests.stockfishchess.org/tests/view/6426198bdb43ab2ba6f9cfa2 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 548944 W: 147287 L: 146254 D: 255403 Ptnml(0-2): 1662, 59772, 150671, 60605, 1762 Passed LTC: https://tests.stockfishchess.org/tests/view/642be4f177ff3301150d892d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 93352 W: 25400 L: 24994 D: 42958 Ptnml(0-2): 44, 8817, 28547, 9225, 43 closes https://github.com/official-stockfish/Stockfish/pull/4495 bench 5044536 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 749de792dd6..aa1a3e8c17f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -290,7 +290,7 @@ void Thread::search() { bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; - optimism[us] = optimism[~us] = VALUE_ZERO; + optimism[WHITE] = optimism[BLACK] = VALUE_ZERO; if (mainThread) { @@ -1257,6 +1257,9 @@ namespace { (ss+1)->pv[0] = MOVE_NONE; value = -search(pos, ss+1, -beta, -alpha, newDepth, false); + + if (moveCount > 1 && newDepth >= depth && !capture) + update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth)); } // Step 19. Undo move From 59f2085469a7dd96146905a5d8d0c1a5d987187d Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 6 Apr 2023 00:35:05 +0300 Subject: [PATCH 0049/1309] Depth Tweak and tuning tunes reduction related parameters, and introduces more reduction on found good moves. credit for this patch goes also to candirufish Yoshie2000 dubslow peregrineshahin Vizvezdenec Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 38424 W: 10346 L: 10040 D: 18038 Ptnml(0-2): 103, 4111, 10473, 4427, 98 https://tests.stockfishchess.org/tests/view/642ca74277ff3301150db511 Passed LTC: LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 136968 W: 37151 L: 36660 D: 63157 Ptnml(0-2): 43, 13052, 41808, 13533, 48 https://tests.stockfishchess.org/tests/view/642d632377ff3301150dddbe closes https://github.com/official-stockfish/Stockfish/pull/4499 bench: 3672914 --- src/search.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index aa1a3e8c17f..fba9685b2e0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1449 - int(delta) * 1001 / int(rootDelta)) / 1024 + (!i && r > 941); + return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(341 * d - 470, 1855); + return std::min(341 * d - 470, 1710); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -775,7 +775,7 @@ namespace { // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 426 - 252 * depth * depth) + if (eval < alpha - 426 - 256 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -797,7 +797,7 @@ namespace { && (ss-1)->statScore < 18755 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 19 * depth - improvement / 13 + 253 + complexity / 25 + && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) @@ -805,7 +805,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 168, 6) + depth / 3 + 4 - (complexity > 825); + Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -1333,16 +1333,18 @@ namespace { if (PvNode && value < beta) // Update alpha! Always alpha < beta { - alpha = value; // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && depth < 6 - && beta < 10534 - && alpha > -10534) - depth -= 1; + && ((improving && complexity > 971) || (value < (5 * alpha + 75 * beta) / 87) || depth < 6) + && beta < 12535 + && value > -12535) { + bool extraReduction = depth > 2 && alpha > -12535 && bestValue != -VALUE_INFINITE && (value - bestValue) > (7 * (beta - alpha)) / 8; + depth -= 1 + extraReduction; + } assert(depth > 0); + alpha = value; } else { From b36d39de3d61b8f31c11d85233631aafaf760ee1 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 9 Apr 2023 09:18:29 +0200 Subject: [PATCH 0050/1309] Fix rootComplexity calculation The calculation of rootComplexity can't call eval when in check. Doing so triggers an assert if compiled in debug mode when the rootpos is evaluated using classical eval. Fixes https://github.com/official-stockfish/Stockfish/issues/4512 Passed STC: https://tests.stockfishchess.org/tests/view/6432697431feee5c6d306876 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 41096 W: 11017 L: 10815 D: 19264 Ptnml(0-2): 113, 4172, 11780, 4366, 117 Running LTC: https://tests.stockfishchess.org/tests/view/6432974d31feee5c6d306fc0 LLR: 1.76 (-2.94,2.94) <-1.75,0.25> Total: 73200 W: 19792 L: 19728 D: 33680 Ptnml(0-2): 24, 6659, 23182, 6699, 36 closes https://github.com/official-stockfish/Stockfish/pull/4515 No functional change --- src/evaluate.cpp | 2 ++ src/search.cpp | 10 ++++++---- src/thread.cpp | 1 + 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 703cf869cee..2d0df89c3e7 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1048,6 +1048,8 @@ namespace { Value Eval::evaluate(const Position& pos, int* complexity) { + assert(!pos.checkers()); + Value v; Value psq = pos.psq_eg_stm(); diff --git a/src/search.cpp b/src/search.cpp index fba9685b2e0..5d54a15d628 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -295,10 +295,12 @@ void Thread::search() { if (mainThread) { - int rootComplexity; - Eval::evaluate(rootPos, &rootComplexity); - - mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); + if (!rootPos.checkers()) + { + int rootComplexity; + Eval::evaluate(rootPos, &rootComplexity); + mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); + } if (mainThread->bestPreviousScore == VALUE_INFINITE) for (int i = 0; i < 4; ++i) diff --git a/src/thread.cpp b/src/thread.cpp index c680393e277..202768c863c 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -160,6 +160,7 @@ void ThreadPool::clear() { main()->bestPreviousScore = VALUE_INFINITE; main()->bestPreviousAverageScore = VALUE_INFINITE; main()->previousTimeReduction = 1.0; + main()->complexity = 1.0; } From 5d258e168f7ea9019ed640ae2e56f04b26aea6a2 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sat, 1 Apr 2023 20:14:41 +0300 Subject: [PATCH 0051/1309] Fix linking / character types of windows API calls ensures large pages can be allocated again. closes https://github.com/official-stockfish/Stockfish/pull/4509 No functional change --- src/misc.cpp | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index e36a04bccfb..f1554060d5e 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -490,25 +490,29 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize return nullptr; // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - HMODULE k32 = GetModuleHandle("Advapi32.dll"); - auto fun6 = (fun6_t)(void(*)())GetProcAddress(k32, "OpenProcessToken"); + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + auto fun6 = (fun6_t)(void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken"); if (!fun6) return nullptr; - auto fun7 = (fun7_t)(void(*)())GetProcAddress(k32, "LookupPrivilegeValueA"); + auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"); if (!fun7) return nullptr; - auto fun8 = (fun8_t)(void(*)())GetProcAddress(k32, "AdjustTokenPrivileges"); + auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"); if (!fun8) return nullptr; - // We need SeLockMemoryPrivilege, so try to enable it for the process - // OpenProcessToken() - if (!fun6(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; + if (!fun6( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; - // LookupPrivilegeValueA() - if (fun7(nullptr, SE_LOCK_MEMORY_NAME, &luid)) + if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) + nullptr, "SeLockMemoryPrivilege", &luid)) { TOKEN_PRIVILEGES tp { }; TOKEN_PRIVILEGES prevTp { }; @@ -520,8 +524,7 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, // we still need to query GetLastError() to ensure that the privileges were actually obtained. - // AdjustTokenPrivileges() - if (fun8( + if (fun8( // AdjustTokenPrivileges() hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && GetLastError() == ERROR_SUCCESS) { @@ -531,8 +534,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); // Privilege no longer needed, restore previous state - // AdjustTokenPrivileges () - fun8(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + fun8( // AdjustTokenPrivileges () + hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); } } From 6e63dd63a4f1e3074a9f5a8d7f64fdd0eba19c7e Mon Sep 17 00:00:00 2001 From: MinetaS Date: Fri, 7 Apr 2023 15:23:04 +0000 Subject: [PATCH 0052/1309] Use int conversion for Option class The current implementation generates warnings on MSVC. However, we have no real use cases for double-typed UCI option values now. Also parameter tuning only accepts following three types: int, Value, Score closes https://github.com/official-stockfish/Stockfish/pull/4505 No functional change --- src/tt.cpp | 4 ++-- src/uci.h | 2 +- src/ucioption.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 39f18d3d9c4..3339c993c41 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -87,7 +87,7 @@ void TranspositionTable::clear() { std::vector threads; - for (size_t idx = 0; idx < Options["Threads"]; ++idx) + for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) { threads.emplace_back([this, idx]() { @@ -98,7 +98,7 @@ void TranspositionTable::clear() { // Each thread will zero its part of the hash table const size_t stride = size_t(clusterCount / Options["Threads"]), start = size_t(stride * idx), - len = idx != Options["Threads"] - 1 ? + len = idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start; std::memset(&table[start], 0, len * sizeof(Cluster)); diff --git a/src/uci.h b/src/uci.h index 70e45accd1c..9ca0ed36b4d 100644 --- a/src/uci.h +++ b/src/uci.h @@ -61,7 +61,7 @@ class Option { Option& operator=(const std::string&); void operator<<(const Option&); - operator double() const; + operator int() const; operator std::string() const; bool operator==(const char*) const; diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 39933ea5e8d..f6342e5cb57 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -128,9 +128,9 @@ Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(min Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) { defaultValue = v; currentValue = cur; } -Option::operator double() const { +Option::operator int() const { assert(type == "check" || type == "spin"); - return (type == "spin" ? stof(currentValue) : currentValue == "true"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { From a5643b89fda5060d4b40dff54afe02816c899dd4 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Fri, 7 Apr 2023 14:30:59 +0000 Subject: [PATCH 0053/1309] Remove extraReduction Since bestValue becomes value and beta - alpha is always non-negative, extraReduction is always false, hence it has no effect. This patch includes small changes to improve readability. closes https://github.com/official-stockfish/Stockfish/pull/4505 No functional change --- src/search.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5d54a15d628..4463b42aaed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1335,15 +1335,14 @@ namespace { if (PvNode && value < beta) // Update alpha! Always alpha < beta { - // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && ((improving && complexity > 971) || (value < (5 * alpha + 75 * beta) / 87) || depth < 6) + && ( (improving && complexity > 971) + || value < (5 * alpha + 75 * beta) / 87 + || depth < 6) && beta < 12535 - && value > -12535) { - bool extraReduction = depth > 2 && alpha > -12535 && bestValue != -VALUE_INFINITE && (value - bestValue) > (7 * (beta - alpha)) / 8; - depth -= 1 + extraReduction; - } + && value > -12535) + depth -= 1; assert(depth > 0); alpha = value; From 2f2f45f9f47c1212f3229c22304456c9bad8f843 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Thu, 6 Apr 2023 14:08:50 +0300 Subject: [PATCH 0054/1309] Made two specializations for affine transform easier to understand. Added AVX-512 for the specialization for small inputs closes https://github.com/official-stockfish/Stockfish/pull/4502 No functional change --- src/nnue/layers/affine_transform.h | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index f84f054e7de..9e2f2f97323 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -31,7 +31,7 @@ This file contains the definition for a fully connected layer (aka affine transform). Two approaches are employed, depending on the sizes of the transform. - Approach 1: + Approach 1 (a specialization for large inputs): - used when the PaddedInputDimensions >= 128 - uses AVX512 if possible - processes inputs in batches of 2*InputSimdWidth @@ -42,9 +42,8 @@ depends on the architecture (the amount of registers) - accumulate + hadd is used - Approach 2: + Approach 2 (a specialization for small inputs): - used when the PaddedInputDimensions < 128 - - does not use AVX512 - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32. - that's why AVX512 is hard to implement - expected use-case is small layers @@ -169,7 +168,7 @@ namespace Stockfish::Eval::NNUE::Layers { constexpr IndexType LargeInputSize = std::numeric_limits::max(); #endif - // A specialization for large inputs. + // A specialization for large inputs template class AffineTransform(InDims, MaxSimdWidth) >= LargeInputSize)>> { public: @@ -188,7 +187,7 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization should not have been chosen."); + static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization (for large inputs) should not have been chosen."); #if defined (USE_AVX512) static constexpr IndexType InputSimdWidth = 64; @@ -396,6 +395,7 @@ namespace Stockfish::Eval::NNUE::Layers { alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; }; + // A specialization for small inputs template class AffineTransform(InDims, MaxSimdWidth) < LargeInputSize)>> { public: @@ -415,12 +415,7 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization should not have been chosen."); - -#if defined (USE_SSSE3) - static constexpr IndexType OutputSimdWidth = SimdWidth / 4; - static constexpr IndexType InputSimdWidth = SimdWidth; -#endif + static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization (for small inputs) should not have been chosen."); // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { @@ -470,7 +465,14 @@ namespace Stockfish::Eval::NNUE::Layers { const OutputType* propagate( const InputType* input, OutputType* output) const { -#if defined (USE_AVX2) +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 + #define vec_hadd Simd::m512_hadd +#elif defined (USE_AVX2) using vec_t = __m256i; #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 @@ -489,6 +491,8 @@ namespace Stockfish::Eval::NNUE::Layers { #if defined (USE_SSSE3) const auto inputVector = reinterpret_cast(input); + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); if constexpr (OutputDimensions % OutputSimdWidth == 0) From 7a9f67747f23e837a8691ba9e6e4f0d1fdafff73 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 4 Apr 2023 19:44:09 -0700 Subject: [PATCH 0055/1309] Reduce Position::pieces() overloads Reduce the number of overloads for pieces() by using a more general template implementation. Secondly simplify some code in search.cpp using the new general functionality. TC https://tests.stockfishchess.org/tests/view/642ce27877ff3301150dc193 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 269640 W: 71775 L: 71809 D: 126056 Ptnml(0-2): 687, 27294, 78885, 27274, 680 closes https://github.com/official-stockfish/Stockfish/pull/4501 No functional change. --- src/position.h | 19 ++++++++----------- src/search.cpp | 12 +++++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/position.h b/src/position.h index bb45c44a3bf..a736f3e677b 100644 --- a/src/position.h +++ b/src/position.h @@ -92,10 +92,9 @@ class Position { // Position representation Bitboard pieces(PieceType pt) const; - Bitboard pieces(PieceType pt1, PieceType pt2) const; + template Bitboard pieces(PieceType pt, PieceTypes... pts) const; Bitboard pieces(Color c) const; - Bitboard pieces(Color c, PieceType pt) const; - Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const; + template Bitboard pieces(Color c, PieceTypes... pts) const; Piece piece_on(Square s) const; Square ep_square() const; bool empty(Square s) const; @@ -229,20 +228,18 @@ inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const { return byTypeBB[pt]; } -inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const { - return pieces(pt1) | pieces(pt2); +template +inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const { + return pieces(pt) | pieces(pts...); } inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; } -inline Bitboard Position::pieces(Color c, PieceType pt) const { - return pieces(c) & pieces(pt); -} - -inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const { - return pieces(c) & (pieces(pt1) | pieces(pt2)); +template +inline Bitboard Position::pieces(Color c, PieceTypes... pts) const { + return pieces(c) & pieces(pts...); } template inline int Position::count(Color c) const { diff --git a/src/search.cpp b/src/search.cpp index 4463b42aaed..4187117b7df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1018,16 +1018,18 @@ namespace { { if (depth < 2 - capture) continue; - // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges - Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied; + // Don't prune the move if opp. King/Queen/Rook is attacked by a slider after the exchanges. + // Since in see_ge we don't update occupied when the king recaptures, we also don't prune the + // move when the opp. King gets a discovered slider attack DURING the exchanges. + Bitboard leftEnemies = pos.pieces(~us, ROOK, QUEEN, KING) & occupied; Bitboard attacks = 0; occupied |= to_sq(move); while (leftEnemies && !attacks) { Square sq = pop_lsb(leftEnemies); - attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // exclude Queen/Rook(s) which were already threatened before SEE - if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) + attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // Exclude Queen/Rook(s) which were already threatened before SEE + if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) attacks = 0; } if (!attacks) From 1a64afb1c65591ccd374504c791eb27b762f6c8f Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Sat, 1 Apr 2023 23:49:03 +0300 Subject: [PATCH 0056/1309] Do no initialize TM in all cases Avoid doing full TM initialization if it won't be used, avoids division by zero. closes https://github.com/official-stockfish/Stockfish/pull/4484 No functional change --- src/timeman.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 5c826b4f0c5..061de0182f7 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -36,6 +36,12 @@ TimeManagement Time; // Our global time management object void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + // if we have no time, no need to initialize TM, except for the start time, + // which is used by movetime. + startTime = limits.startTime; + if (limits.time[us] == 0) + return; + TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); TimePoint slowMover = TimePoint(Options["Slow Mover"]); TimePoint npmsec = TimePoint(Options["nodestime"]); @@ -59,8 +65,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { limits.npmsec = npmsec; } - startTime = limits.startTime; - // Maximum move horizon of 50 moves int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; From 7bd23d4d04d6644b6ccae8ea63cfc6646e4248dd Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 7 Apr 2023 12:33:28 -0400 Subject: [PATCH 0057/1309] Simplify away nnue scale pawn count multiplier Removes 2x multipliers in nnue scale calculation along with the pawn count term that was recently reintroduced. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/64305bc720eb941419bdf72e LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 38008 W: 10234 L: 10021 D: 17753 Ptnml(0-2): 96, 4151, 10323, 4312, 122 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6430b76a028b029b01ac9bfd LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 91232 W: 24686 L: 24547 D: 41999 Ptnml(0-2): 30, 8721, 27986, 8838, 41 closes https://github.com/official-stockfish/Stockfish/pull/4510 bench 4017320 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 2d0df89c3e7..873dc5d2069 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos, int* complexity) { else { int nnueComplexity; - int scale = 1001 + 5 * pos.count() + 61 * pos.non_pawn_material() / 4096; + int scale = 1001 + pos.non_pawn_material() / 64; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; From 4ad2713e19cbf8db1e588c4d24d1fe4af5c6e917 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Fri, 17 Mar 2023 00:04:41 +0300 Subject: [PATCH 0058/1309] Fix capturing underpromotions issue Fix underpromotion captures are generated amongst quiets although dealt with as a capture_stage in search, this makes not skipping them when move count pruning kicks-in consistent with updating their histories amongst captures. Passed STC: https://tests.stockfishchess.org/tests/view/6415579f65775d3b539e7537 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 118896 W: 31678 L: 31553 D: 55665 Ptnml(0-2): 356, 12911, 32793, 13028, 360 Passed LTC: https://tests.stockfishchess.org/tests/view/641633b965775d3b539e9e95 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 126800 W: 34255 L: 34148 D: 58397 Ptnml(0-2): 57, 12216, 38763, 12291, 73 see also discussion in https://github.com/official-stockfish/Stockfish/pull/4436 closes https://github.com/official-stockfish/Stockfish/pull/4452 bench: 3979409 --- src/movegen.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 255dce04c3c..6b28a52ecf0 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -25,13 +25,21 @@ namespace Stockfish { namespace { - template + template ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { *moveList++ = make(to - D, to, QUEEN); + if constexpr (Enemy && Type == CAPTURES) + { + *moveList++ = make(to - D, to, ROOK); + *moveList++ = make(to - D, to, BISHOP); + *moveList++ = make(to - D, to, KNIGHT); + } + } - if constexpr (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr ((Type == QUIETS && !Enemy) || Type == EVASIONS || Type == NON_EVASIONS) { *moveList++ = make(to - D, to, ROOK); *moveList++ = make(to - D, to, BISHOP); @@ -106,13 +114,13 @@ namespace { b3 &= target; while (b1) - moveList = make_promotions(moveList, pop_lsb(b1)); + moveList = make_promotions(moveList, pop_lsb(b1)); while (b2) - moveList = make_promotions(moveList, pop_lsb(b2)); + moveList = make_promotions(moveList, pop_lsb(b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(b3)); + moveList = make_promotions(moveList, pop_lsb(b3)); } // Standard and en passant captures From f66c36277fe57d0ce4f10a4aeb5b41eb0cb9ebd1 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 5 Apr 2023 20:30:01 -0500 Subject: [PATCH 0059/1309] Remove nmpColor no benefit seen, neither in game play nor for zugzwang test positions STC: https://tests.stockfishchess.org/tests/view/642e293977ff3301150e9b55 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 161848 W: 43332 L: 43254 D: 75262 Ptnml(0-2): 418, 16987, 46058, 17021, 440 LTC: https://tests.stockfishchess.org/tests/view/642fea9420eb941419bde296 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 120208 W: 32529 L: 32418 D: 55261 Ptnml(0-2): 35, 11424, 37080, 11525, 40 closes https://github.com/official-stockfish/Stockfish/pull/4511 bench 3979409 --- src/search.cpp | 5 ++--- src/thread.h | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4187117b7df..04299dbd887 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -802,7 +802,7 @@ namespace { && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25 && !excludedMove && pos.non_pawn_material(us) - && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) + && (ss->ply >= thisThread->nmpMinPly)) { assert(eval - beta >= 0); @@ -830,9 +830,8 @@ namespace { assert(!thisThread->nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled - // for us, until ply exceeds nmpMinPly. + // until ply exceeds nmpMinPly. thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; - thisThread->nmpColor = us; Value v = search(pos, ss, beta-1, beta, depth-R, false); diff --git a/src/thread.h b/src/thread.h index d6a48eca73d..3a114c797a6 100644 --- a/src/thread.h +++ b/src/thread.h @@ -62,7 +62,6 @@ class Thread { size_t pvIdx, pvLast; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; - Color nmpColor; Value bestValue, optimism[COLOR_NB]; Position rootPos; From 9829bceda90d025a5f5d7c04457902413e367041 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 11 Apr 2023 01:20:29 +0200 Subject: [PATCH 0060/1309] Remove good killer reduction rule. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 246544 W: 65646 L: 65657 D: 115241 Ptnml(0-2): 706, 27350, 67138, 27405, 673 https://tests.stockfishchess.org/tests/view/642e253277ff3301150e9aa2 LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 165136 W: 44878 L: 44809 D: 75449 Ptnml(0-2): 64, 15991, 50378, 16082, 53 https://tests.stockfishchess.org/tests/view/6430db07028b029b01acd87f closes https://github.com/official-stockfish/Stockfish/pull/4519 Bench: 3746080 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 04299dbd887..411befdedfb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1187,11 +1187,6 @@ namespace { if ((ss+1)->cutoffCnt > 3) r++; - // Decrease reduction if move is a killer and we have a good history (~1 Elo) - if (move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 3722) - r--; - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] From acb0d204d56e16398c58822df2cc60b90ef1ae85 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 11 Apr 2023 19:53:36 +0300 Subject: [PATCH 0061/1309] Simplify stats assignment for Pv nodes This patch is a simplification of my recent elo gainer. Logically the Elo gainer didn't make much sense and this patch simplifies it into smth more logical. Instead of assigning negative bonuses to all non-first moves that enter PV nodes we assign positive bonuses in full depth search after LMR only for moves that will result in a fail high - thus not assigning positive bonuses for moves that will go to pv search - so doing "almost" the same as we do in master now for them. Logic differs for some other moves, though, but this removes some lines of code. Passed STC: https://tests.stockfishchess.org/tests/view/642cf5cf77ff3301150dc5ec LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 409320 W: 109124 L: 109308 D: 190888 Ptnml(0-2): 1149, 45385, 111751, 45251, 1124 Passed LTC: https://tests.stockfishchess.org/tests/view/642fe75d20eb941419bde200 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 260336 W: 70280 L: 70303 D: 119753 Ptnml(0-2): 99, 25236, 79528, 25199, 106 closes https://github.com/official-stockfish/Stockfish/pull/4522 Bench: 4286815 --- src/search.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 411befdedfb..390c6b1c72f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1229,8 +1229,9 @@ namespace { if (newDepth > d) value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); - int bonus = value > alpha ? stat_bonus(newDepth) - : -stat_bonus(newDepth); + int bonus = value <= alpha ? -stat_bonus(newDepth) + : value >= beta ? stat_bonus(newDepth) + : 0; update_continuation_histories(ss, movedPiece, to_sq(move), bonus); } @@ -1255,9 +1256,6 @@ namespace { (ss+1)->pv[0] = MOVE_NONE; value = -search(pos, ss+1, -beta, -alpha, newDepth, false); - - if (moveCount > 1 && newDepth >= depth && !capture) - update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth)); } // Step 19. Undo move From 96b6c0b36f28f0ef5fa58f48405db710542521df Mon Sep 17 00:00:00 2001 From: MinetaS Date: Fri, 7 Apr 2023 14:49:05 +0000 Subject: [PATCH 0062/1309] Remove some conditions at PV improvement reduction Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 63664 W: 17007 L: 16823 D: 29834 Ptnml(0-2): 163, 6998, 17336, 7162, 173 https://tests.stockfishchess.org/tests/view/6430b124028b029b01ac99f2 Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 90016 W: 24399 L: 24258 D: 41359 Ptnml(0-2): 52, 8672, 27405, 8841, 38 https://tests.stockfishchess.org/tests/view/64310e74028b029b01ad3131 closes https://github.com/official-stockfish/Stockfish/pull/4526 Bench: 3661938 --- src/search.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 390c6b1c72f..ed81263a364 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1331,9 +1331,6 @@ namespace { { // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && ( (improving && complexity > 971) - || value < (5 * alpha + 75 * beta) / 87 - || depth < 6) && beta < 12535 && value > -12535) depth -= 1; From f9d9c69bc33dc7a17c28cd586d7e67c1bfff66f6 Mon Sep 17 00:00:00 2001 From: Torom Date: Thu, 13 Apr 2023 21:55:13 +0200 Subject: [PATCH 0063/1309] Set the length of GIT_SHA to 8 characters Previously, the length of git commit hashes could vary depending on the git environment. closes https://github.com/official-stockfish/Stockfish/pull/4527 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index abcf11b0a11..82664618bb7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -702,7 +702,7 @@ ifeq ($(pext),yes) endif ### 3.7.1 Try to include git commit sha for versioning -GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null) +GIT_SHA = $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) ifneq ($(GIT_SHA), ) CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif From c90dd38903206ede56fa73c15d7d2b366d56ebdb Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:48:22 +0800 Subject: [PATCH 0064/1309] Simplify away complexity in evaluation Simplification STC: https://tests.stockfishchess.org/tests/view/64394bc0605991a801b4f6f0 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 72360 W: 19313 L: 19138 D: 33909 Ptnml(0-2): 206, 7883, 19800, 8112, 179 Simplification LTC: https://tests.stockfishchess.org/tests/view/6439e788c233ce943b6bdac1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 224992 W: 60665 L: 60654 D: 103673 Ptnml(0-2): 96, 21875, 68526, 21920, 79 closes https://github.com/official-stockfish/Stockfish/pull/4530 Bench: 3709369 --- src/evaluate.cpp | 10 +--------- src/evaluate.h | 2 +- src/search.cpp | 27 ++++++++------------------- src/thread.cpp | 1 - src/thread.h | 1 - 5 files changed, 10 insertions(+), 31 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 873dc5d2069..851ccfe11c9 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1046,7 +1046,7 @@ namespace { /// evaluate() is the evaluator for the outer world. It returns a static /// evaluation of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, int* complexity) { +Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); @@ -1075,10 +1075,6 @@ Value Eval::evaluate(const Position& pos, int* complexity) { + (424 + optimism) * abs(psq - nnue) ) / 1024; - // Return hybrid NNUE complexity to caller - if (complexity) - *complexity = nnueComplexity; - optimism = optimism * (272 + nnueComplexity) / 256; v = (nnue * scale + optimism * (scale - 748)) / 1024; } @@ -1089,10 +1085,6 @@ Value Eval::evaluate(const Position& pos, int* complexity) { // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - // When not using NNUE, return classical complexity to caller - if (complexity && useClassical) - *complexity = abs(v - psq); - return v; } diff --git a/src/evaluate.h b/src/evaluate.h index 61846073300..48076670d2b 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -31,7 +31,7 @@ class Position; namespace Eval { std::string trace(Position& pos); - Value evaluate(const Position& pos, int* complexity = nullptr); + Value evaluate(const Position& pos); extern bool useNNUE; extern std::string currentEvalFileName; diff --git a/src/search.cpp b/src/search.cpp index ed81263a364..3a7f85a85e1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -294,14 +294,6 @@ void Thread::search() { if (mainThread) { - - if (!rootPos.checkers()) - { - int rootComplexity; - Eval::evaluate(rootPos, &rootComplexity); - mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45); - } - if (mainThread->bestPreviousScore == VALUE_INFINITE) for (int i = 0; i < 4; ++i) mainThread->iterValue[i] = VALUE_ZERO; @@ -478,7 +470,7 @@ void Thread::search() { double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity; + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; // Cap used time in case of a single legal move for a better viewer experience in tournaments // yielding correct scores and sufficiently fast moves. @@ -561,7 +553,7 @@ namespace { bool givesCheck, improving, priorCapture, singularQuietLMR; bool capture, moveCountPruning, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount, improvement, complexity; + int moveCount, captureCount, quietCount, improvement; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); @@ -723,7 +715,6 @@ namespace { ss->staticEval = eval = VALUE_NONE; improving = false; improvement = 0; - complexity = 0; goto moves_loop; } else if (excludedMove) @@ -731,17 +722,15 @@ namespace { // 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); eval = ss->staticEval; - complexity = abs(ss->staticEval - pos.psq_eg_stm()); } else if (ss->ttHit) { // Never assume anything about values stored in TT ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - ss->staticEval = eval = evaluate(pos, &complexity); - else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost + ss->staticEval = eval = evaluate(pos); + else { - complexity = abs(ss->staticEval - pos.psq_eg_stm()); if (PvNode) Eval::NNUE::hint_common_parent_position(pos); } @@ -753,7 +742,7 @@ namespace { } else { - ss->staticEval = eval = evaluate(pos, &complexity); + ss->staticEval = eval = evaluate(pos); // Save static evaluation into transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } @@ -799,15 +788,15 @@ namespace { && (ss-1)->statScore < 18755 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25 + && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) { assert(eval - beta >= 0); - // Null move dynamic reduction based on depth, eval and complexity of position - Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825); + // Null move dynamic reduction based on depth and eval + Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; diff --git a/src/thread.cpp b/src/thread.cpp index 202768c863c..c680393e277 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -160,7 +160,6 @@ void ThreadPool::clear() { main()->bestPreviousScore = VALUE_INFINITE; main()->bestPreviousAverageScore = VALUE_INFINITE; main()->previousTimeReduction = 1.0; - main()->complexity = 1.0; } diff --git a/src/thread.h b/src/thread.h index 3a114c797a6..09bdb470b21 100644 --- a/src/thread.h +++ b/src/thread.h @@ -85,7 +85,6 @@ struct MainThread : public Thread { void search() override; void check_time(); - double complexity; double previousTimeReduction; Value bestPreviousScore; Value bestPreviousAverageScore; From 7b9b793fd544aa7a599b113a40533cde16de640b Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Mon, 17 Apr 2023 11:38:26 +0200 Subject: [PATCH 0065/1309] Simplification of SEE verification logic Use same logic for all handled pieces. Don't prune the move if opponent King, Queen, Rook gets a discovered attack while or after the exchanges. remove an obsolete comment in position.cpp Passed STC non regression: https://tests.stockfishchess.org/tests/view/6437907594daa91835c290d0 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 107432 W: 28359 L: 28221 D: 50852 Ptnml(0-2): 298, 11724, 29524, 11882, 288 Passed LTC non-regression: https://tests.stockfishchess.org/tests/view/6438ed2ebd1a5470263c51e8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 236288 W: 63656 L: 63656 D: 108976 Ptnml(0-2): 99, 22960, 72011, 22990, 84 closes https://github.com/official-stockfish/Stockfish/pull/4533 bench: 3741125 --- src/position.cpp | 3 +-- src/search.cpp | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index e6fdb511fcc..2a9d798ff7d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -331,8 +331,7 @@ void Position::set_check_info() const { /// Position::set_state() computes the hash keys of the position, and other /// data that once computed is updated incrementally as moves are made. -/// The function is only used when a new position is set up, and to verify -/// the correctness of the StateInfo data when running in debug mode. +/// The function is only used when a new position is set up void Position::set_state() const { diff --git a/src/search.cpp b/src/search.cpp index 3a7f85a85e1..5205fb5729b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1006,17 +1006,15 @@ namespace { { if (depth < 2 - capture) continue; - // Don't prune the move if opp. King/Queen/Rook is attacked by a slider after the exchanges. - // Since in see_ge we don't update occupied when the king recaptures, we also don't prune the - // move when the opp. King gets a discovered slider attack DURING the exchanges. - Bitboard leftEnemies = pos.pieces(~us, ROOK, QUEEN, KING) & occupied; + // Don't prune the move if opp. King/Queen/Rook gets a discovered attack during or after the exchanges + Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); Bitboard attacks = 0; occupied |= to_sq(move); while (leftEnemies && !attacks) { Square sq = pop_lsb(leftEnemies); attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Exclude Queen/Rook(s) which were already threatened before SEE + // Exclude Queen/Rook(s) which were already threatened before SEE (opp King can't be in check when it's our turn) if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) attacks = 0; } From d64d4ac426e06cdd52249b0464d22f3cdb7fcf79 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 14 Apr 2023 20:33:33 +0800 Subject: [PATCH 0066/1309] Simplify away depth condition for aspiration window adjust Simplification STC: https://tests.stockfishchess.org/tests/view/64351654596a20f264276ded LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 374664 W: 98942 L: 99089 D: 176633 Ptnml(0-2): 1049, 41767, 101878, 41558, 1080 Simplification LTC: https://tests.stockfishchess.org/tests/view/6439499f605991a801b4f684 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 457880 W: 123021 L: 123233 D: 211626 Ptnml(0-2): 166, 44739, 139335, 44541, 159 closes https://github.com/official-stockfish/Stockfish/pull/4534 Bench: 3879281 --- src/search.cpp | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5205fb5729b..e322a1c99aa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -288,9 +288,7 @@ void Thread::search() { ss->pv = pv; - bestValue = delta = alpha = -VALUE_INFINITE; - beta = VALUE_INFINITE; - optimism[WHITE] = optimism[BLACK] = VALUE_ZERO; + bestValue = -VALUE_INFINITE; if (mainThread) { @@ -349,18 +347,15 @@ void Thread::search() { selDepth = 0; // Reset aspiration window starting size - if (rootDepth >= 4) - { - Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 16502; - alpha = std::max(prev - delta,-VALUE_INFINITE); - beta = std::min(prev + delta, VALUE_INFINITE); - - // Adjust optimism based on root move's previousScore - int opt = 120 * prev / (std::abs(prev) + 161); - optimism[ us] = Value(opt); - optimism[~us] = -optimism[us]; - } + Value prev = rootMoves[pvIdx].averageScore; + delta = Value(10) + int(prev) * prev / 16502; + alpha = std::max(prev - delta,-VALUE_INFINITE); + beta = std::min(prev + delta, VALUE_INFINITE); + + // Adjust optimism based on root move's previousScore + int opt = 120 * prev / (std::abs(prev) + 161); + optimism[ us] = Value(opt); + optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From ba06c480a752458a8159db0c9110bd3b7e34145a Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Fri, 21 Apr 2023 16:22:55 +0200 Subject: [PATCH 0067/1309] Less reduction for tt move. This idea is a result of my second condition combination tuning for reductions: https://tests.stockfishchess.org/tests/view/643ed5573806eca398f06d61 There were used two parameters per combination: one for the 'sign' of the first and the second condition in a combination. Values >= 50 indicate using a condition directly and values <= -50 means use the negation of a condition. Each condition pair (X,Y) had two occurances dependent of the order of the two conditions: - if X < Y the parameters used for more reduction - if X > Y the parameters used for less reduction - if X = Y then only one condition is present and A[X][X][0]/A[X][X][1] stands for using more/less reduction for only this condition. The parameter pair A[7][2][0] (value = -94.70) and A[7][2][1] (value = 93.60) was one of the strongest signals with values near 100/-100. Here condition nr. 7 was '(ss+1)->cutoffCnt > 3' and condition nr. 2 'move == ttMove'. For condition nr. 7 the negation is used because A[7][2][0] is negative. This translates finally to less reduction (because 7 > 2) for tt moves if child cutoffs <= 3. STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 65728 W: 17704 L: 17358 D: 30666 Ptnml(0-2): 184, 7092, 18008, 7354, 226 https://tests.stockfishchess.org/tests/view/643ff767ef2529086a7ed042 LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 139200 W: 37776 L: 37282 D: 64142 Ptnml(0-2): 58, 13241, 42509, 13733, 59 https://tests.stockfishchess.org/tests/view/6440bfa9ef2529086a7edbc7 closes https://github.com/official-stockfish/Stockfish/pull/4538 Bench: 3548023 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index e322a1c99aa..366065b8e9c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1169,6 +1169,9 @@ namespace { if ((ss+1)->cutoffCnt > 3) r++; + else if (move == ttMove) + r--; + ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] From b22a1b10bbae2bb773afb50eba23dbf15e426365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bo=C5=A1tjan=20Mejak?= Date: Tue, 11 Apr 2023 13:55:14 +0200 Subject: [PATCH 0068/1309] Update AUTHORS Improved some comments in the AUTHORS file, sort contributors closes https://github.com/official-stockfish/Stockfish/pull/4520 No functional change --- AUTHORS | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9b36111ea77..b6723246ada 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,17 +1,15 @@ -# List of authors for Stockfish - -# Founders of the Stockfish project and fishtest infrastructure +# Founders of the Stockfish project and Fishtest infrastructure Tord Romstad (romstad) Marco Costalba (mcostalba) Joona Kiiski (zamar) Gary Linscott (glinscott) -# Authors and inventors of NNUE, training, NNUE port +# Authors and inventors of NNUE, training, and NNUE port Yu Nasu (ynasu87) Motohiro Isozaki (yaneurao) Hisayori Noda (nodchip) -# all other authors of the code in alphabetical order +# All other authors of Stockfish code (in alphabetical order) Aditya (absimaldata) Adrian Petrescu (apetresc) Ajith Chandy Jose (ajithcj) @@ -47,12 +45,12 @@ Chess13234 Chris Cain (ceebo) clefrks Dale Weiler (graphitemaster) -Dan Schmidt (dfannius) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) +Dan Schmidt (dfannius) Dariusz Orzechowski (dorzechowski) -David Zar David (dav1312) +David Zar Daylen Yang (daylen) Deshawn Mohan-Smith (GoldenRare) Dieter Dobbelaere (ddobbelaere) @@ -66,7 +64,6 @@ Eelco de Groot (KingDefender) Elvin Liu (solarlight2) erbsenzaehler Ernesto Gatti -Linmiao Xu (linrock) Fabian Beuke (madnight) Fabian Fichter (ianfab) Fanael Linithien (Fanael) @@ -83,30 +80,30 @@ Gontran Lemaire (gonlem) Goodkov Vasiliy Aleksandrovich (goodkov) Gregor Cramer GuardianRM -Günther Demetz (pb00067, pb00068) Guy Vreuls (gvreuls) +Günther Demetz (pb00067, pb00068) Henri Wiechers Hiraoka Takuya (HiraokaTakuya) homoSapiensSapiens Hongzhi Cheng Ivan Ivec (IIvec) Jacques B. (Timshel) +Jake Senne (w1wwwwww) Jan Ondruš (hxim) Jared Kish (Kurtbusch, kurt22i) -Jake Senne (w1wwwwww) Jarrod Torriero (DU-jdto) -Jean Gauthier (OuaisBla) Jean-Francois Romang (jromang) +Jean Gauthier (OuaisBla) Jekaa Jerry Donald Watson (jerrydonaldwatson) jjoshua2 -Jonathan Calovski (Mysseno) Jonathan Buladas Dumale (SFisGOD) +Jonathan Calovski (Mysseno) Jonathan McDermid (jonathanmcdermid) Joost VandeVondele (vondele) -Jörg Oster (joergoster) Joseph Ellis (jhellis3) Joseph R. Prostko +Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery Justin Blanchard (UncombedCoconut) @@ -120,6 +117,7 @@ Krystian Kuzniarek (kuzkry) Leonardo Ljubičić (ICCF World Champion) Leonid Pechenik (lp--) Liam Keegan (lkeegan) +Linmiao Xu (linrock) Linus Arver (listx) loco-loco Lub van den Berg (ElbertoOne) @@ -151,11 +149,11 @@ Moez Jellouli (MJZ1977) Mohammed Li (tthsqe12) Muzhen J (XInTheDark) Nathan Rugg (nmrugg) -Nick Pelling (nickpelling) +Nguyen Pham (nguyenpham) Nicklas Persson (NicklasPersson) +Nick Pelling (nickpelling) Niklas Fiekas (niklasf) Nikolay Kostov (NikolayIT) -Nguyen Pham (nguyenpham) Norman Schmidt (FireFather) notruck Ofek Shochat (OfekShochat, ghostway) @@ -170,6 +168,7 @@ Peter Schneider (pschneider1968) Peter Zsifkovits (CoffeeOne) PikaCat Praveen Kumar Tummala (praveentml) +Prokop Randáček (ProkopRandacek) Rahul Dsilva (silversolver1) Ralph Stößer (Ralph Stoesser) Raminder Singh @@ -178,8 +177,8 @@ Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) -Ron Britvich (Britvich) Ronald de Man (syzygy1, syzygy) +Ron Britvich (Britvich) rqs Rui Coelho (ruicoelhopedro) Ryan Schmitt @@ -200,13 +199,12 @@ Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Syine Mineta (MinetaS) -Prokop Randáček (ProkopRandacek) Thanar2 thaspel theo77186 +Tomasz Sobczyk (Sopel97) Tom Truscott Tom Vijlbrief (tomtor) -Tomasz Sobczyk (Sopel97) Torsten Franz (torfranz, tfranzer) Torsten Hellwig (Torom) Tracey Emery (basepr1me) @@ -217,8 +215,7 @@ Vince Negri (cuddlestmonkey) xefoci7612 zz4032 - # Additionally, we acknowledge the authors and maintainers of fishtest, -# an amazing and essential framework for the development of Stockfish! +# an amazing and essential framework for Stockfish development! # # https://github.com/glinscott/fishtest/blob/master/AUTHORS From c3ce2204083400267592dc088b8ad9e88aed56b1 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 15 Apr 2023 09:51:01 -0400 Subject: [PATCH 0069/1309] Created by retraining the master net with these changes to the dataset: * Extending v6 filtering to data from T77 dec2021, T79 may2022, and T80 nov2022 * Reducing the number of duplicate positions, prioritizing position scores seen later in time * Using a binpack minimizer to reduce the overall data size Trained the same way as the previous master net, aside from the dataset changes: ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovT79aprmayT78jantosepT77dec-v6dd \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovT79aprmayT78jantosepT77dec-v6dd.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes \ --start-from-engine-test-net True \ --early-fen-skipping 30 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 900 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --gpus "0," \ --seed $RANDOM ``` The new v6-dd filtering reduces duplicate positions by iterating over hourly data files within leela test runs, starting with the most recent, then keeping positions the first time they're seen and ignoring positions that are seen again. This ordering was done with the assumption that position scores seen later in time are generally more accurate than scores seen earlier in the test run. Positions are de-duplicated based on piece orientations, the first token in fen strings. The binpack minimizer was run with default settings after first merging monthly data into single binpacks. ``` python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-filt-v2.binpack \ T60-nov2021-12tb7p-eval-filt-v2.binpack \ T60-dec2021-12tb7p-eval-filt-v2.binpack \ filt-v6/test80-aug2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6/test80-sep2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6-dd/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test80-jul2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-oct2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-nov2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-apr2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-may2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test78-juntosep2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test77-dec2021-16tb7p-filter-v6-dd.binpack \ /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovT79aprmayT78jantosepT77dec-v6dd.binpack ``` The code for v6-dd filtering is available along with training data preparation scripts at: https://github.com/linrock/nnue-data Links for downloading the training data components: https://robotmoon.com/nnue-training-data/ The binpack minimizer is from: #4447 Local elo at 25k nodes per move: nn-epoch859.nnue : 1.2 +/- 2.6 Passed STC: https://tests.stockfishchess.org/tests/view/643aad7db08900ff1bc5a832 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 565040 W: 150225 L: 149162 D: 265653 Ptnml(0-2): 1875, 62137, 153229, 63608, 1671 Passed LTC: https://tests.stockfishchess.org/tests/view/643ecf2fa43cf30e719d2042 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 1014840 W: 274645 L: 272456 D: 467739 Ptnml(0-2): 515, 98565, 306970, 100956, 414 closes https://github.com/official-stockfish/Stockfish/pull/4545 bench 3476305 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 48076670d2b..9dc45371e42 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-dabb1ed23026.nnue" + #define EvalFileDefaultName "nn-1ceb1a57d117.nnue" namespace NNUE { From 41f50b2c83a0ba36a2b9c507c1783e57c9b13485 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 21 Apr 2023 09:37:29 -0400 Subject: [PATCH 0070/1309] Update default net to nn-e1fb1ade4432.nnue Created by retraining nn-dabb1ed23026.nnue with a dataset composed of: * The previous best dataset (nn-1ceb1a57d117.nnue dataset) * Adding de-duplicated T80 data from feb2023 and the last 10 days of jan2023, filtered with v6-dd Initially trained with the same options as the recent master net (nn-1ceb1a57d117.nnue). Around epoch 890, training was manually stopped and max epoch increased to 1000. ``` python3 easy_train.py \ --experiment-name leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovjanfebT79aprmayT78jantosepT77dec-v6dd \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovjanfebT79aprmayT78jantosepT77dec-v6dd.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes \ --start-from-engine-test-net True \ --early-fen-skipping 30 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 900 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --gpus "0," \ --seed $RANDOM ``` The same v6-dd filtering and binpack minimizer was used for preparing the recent nn-1ceb1a57d117.nnue dataset. ``` python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-filt-v2.binpack \ T60-nov2021-12tb7p-eval-filt-v2.binpack \ T60-dec2021-12tb7p-eval-filt-v2.binpack \ filt-v6/test80-aug2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6/test80-sep2022-16tb7p-filter-v6.min-mar2023.binpack \ filt-v6-dd/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test80-jul2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-oct2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-nov2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-jan2022-3of3-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test79-apr2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-may2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.binpack \ filt-v6-dd/test78-juntosep2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test77-dec2021-16tb7p-filter-v6-dd.binpack \ /data/leela96-dfrc99-T60novdec-v2-T80augsep-v6-T80junjuloctnovjanfebT79aprmayT78jantosepT77dec-v6dd.binpack ``` Links for downloading the training data components can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch919.nnue : 2.6 +/- 2.8 Passed STC vs. nn-dabb1ed23026.nnue https://tests.stockfishchess.org/tests/view/644420df94ff3db5625f2af5 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 125960 W: 33898 L: 33464 D: 58598 Ptnml(0-2): 351, 13920, 34021, 14320, 368 Passed LTC vs. nn-1ceb1a57d117.nnue https://tests.stockfishchess.org/tests/view/64469f128d30316529b3dc46 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 24544 W: 6817 L: 6542 D: 11185 Ptnml(0-2): 8, 2252, 7488, 2505, 19 closes https://github.com/official-stockfish/Stockfish/pull/4546 bench 3714847 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 9dc45371e42..f5db1c1e622 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1ceb1a57d117.nnue" + #define EvalFileDefaultName "nn-e1fb1ade4432.nnue" namespace NNUE { From 21d6b69f7c8d0c0a71fe627714913a59d39a3b57 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 29 Apr 2023 15:45:22 -0400 Subject: [PATCH 0071/1309] Update 7 eval and optimism params Params found using spsa at 30+0.3 with this tuning config: ``` // evaluate.cpp int nnueOptScaleBase = 1001; int nnueComplexityMult = 406; int nnueComplexityOptOffset = 424; int evalOptComplexityOffset = 272; int evalOptScaleOffset = 748; TUNE(SetRange(801, 1201), nnueOptScaleBase); TUNE(SetRange(306, 506), nnueComplexityMult); TUNE(SetRange(324, 524), nnueComplexityOptOffset); TUNE(SetRange(172, 372), evalOptComplexityOffset); TUNE(SetRange(648, 848), evalOptScaleOffset); // search.cpp int searchOptBase = 120; int searchOptDenom = 161; TUNE(SetRange(20, 220), searchOptBase); TUNE(SetRange(111, 211), searchOptDenom); ``` Passed STC: https://tests.stockfishchess.org/tests/view/644dda8accf5e93df5e50cbe LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 136800 W: 36682 L: 36237 D: 63881 Ptnml(0-2): 353, 14910, 37492, 15229, 416 Passed LTC: https://tests.stockfishchess.org/tests/view/644eaedb3f31c3bbe4a3d345 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 64548 W: 17624 L: 17272 D: 29652 Ptnml(0-2): 33, 6112, 19631, 6466, 32 closes https://github.com/official-stockfish/Stockfish/pull/4550 bench 3670343 --- src/evaluate.cpp | 10 +++++----- src/search.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 851ccfe11c9..d8f4e2e194f 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos) { else { int nnueComplexity; - int scale = 1001 + pos.non_pawn_material() / 64; + int scale = 967 + pos.non_pawn_material() / 64; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; @@ -1071,12 +1071,12 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 406 * nnueComplexity - + (424 + optimism) * abs(psq - nnue) + nnueComplexity = ( 402 * nnueComplexity + + (454 + optimism) * abs(psq - nnue) ) / 1024; - optimism = optimism * (272 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 748)) / 1024; + optimism = optimism * (274 + nnueComplexity) / 256; + v = (nnue * scale + optimism * (scale - 791)) / 1024; } // Damp down the evaluation linearly when shuffling diff --git a/src/search.cpp b/src/search.cpp index 366065b8e9c..8ce9c56e42d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -353,7 +353,7 @@ void Thread::search() { beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 120 * prev / (std::abs(prev) + 161); + int opt = 102 * prev / (std::abs(prev) + 147); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; From 72d542f00026fd8437e6033c95802714e4cd45d1 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Fri, 28 Apr 2023 19:05:56 +0200 Subject: [PATCH 0072/1309] Adjust reductions Decrease further on cutNodes with tte->depth() >= depth + 3 condition. LTC: https://tests.stockfishchess.org/tests/view/644dc84bccf5e93df5e50c13 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 155346 W: 42184 L: 41660 D: 71502 Ptnml(0-2): 59, 14765, 47504, 15283, 62 STC: https://tests.stockfishchess.org/tests/view/644d05de68e01d8194cd9bbb LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 121888 W: 32868 L: 32444 D: 56576 Ptnml(0-2): 332, 13273, 33343, 13631, 365 closes https://github.com/official-stockfish/Stockfish/pull/4552 bench: 3739675 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ce9c56e42d..a6618c5b815 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -884,7 +884,7 @@ namespace { // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode && !ttMove) - depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); + depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); @@ -1141,9 +1141,10 @@ namespace { // Decrease reduction if position is or has been on the PV // and node is not likely to fail low. (~3 Elo) + // Decrease further on cutNodes. (~1 Elo) if ( ss->ttPv && !likelyFailLow) - r -= 2; + r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss-1)->moveCount > 7) From 2429e162890478a48ab9b1cf0c431de9ebaf9429 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 5 May 2023 07:54:12 +0300 Subject: [PATCH 0073/1309] Reduce more if current node has a lot of refuted moves. This patch refines idea of cutoff count - in master we reduce more if current node has at least 4 moves that are refuted by search, this patch increases this count by 1 if refutation happened without having a tt move. Passed STC: https://tests.stockfishchess.org/tests/view/645363c36206ee34ebf8191d LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 67616 W: 18220 L: 17874 D: 31522 Ptnml(0-2): 142, 7346, 18504, 7656, 160 Passed LTC: https://tests.stockfishchess.org/tests/view/6453a0ea6206ee34ebf82796 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 195228 W: 52741 L: 52140 D: 90347 Ptnml(0-2): 53, 18718, 59482, 19297, 64 closes https://github.com/official-stockfish/Stockfish/pull/4556 bench 3448916 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a6618c5b815..a1f916d0515 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1326,7 +1326,7 @@ namespace { } else { - ss->cutoffCnt++; + ss->cutoffCnt += 1 + !ttMove; assert(value >= beta); // Fail high break; } From 28442195c7d168a87221c6f1ae9ac51893427250 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Wed, 3 May 2023 14:11:55 +0300 Subject: [PATCH 0074/1309] Clean up after "Simplify away complexity in evaluation" closes https://github.com/official-stockfish/Stockfish/pull/4555 No functional change. --- src/search.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a1f916d0515..8d9dc04fc48 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -724,11 +724,8 @@ namespace { ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) ss->staticEval = eval = evaluate(pos); - else - { - if (PvNode) - Eval::NNUE::hint_common_parent_position(pos); - } + else if (PvNode) + Eval::NNUE::hint_common_parent_position(pos); // ttValue can be used as a better position evaluation (~7 Elo) if ( ttValue != VALUE_NONE From 464ebdf127273db0ccb0084c881b255b880e922e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 5 May 2023 23:32:56 +0300 Subject: [PATCH 0075/1309] Small cleanup In search remove one condition check and reorder conditions. Removes some code. Passed non-regression test: https://tests.stockfishchess.org/tests/view/64548fa06206ee34ebf853ad LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 282976 W: 75327 L: 75374 D: 132275 Ptnml(0-2): 604, 29673, 80995, 29598, 618 closes https://github.com/official-stockfish/Stockfish/pull/4557 No functional change --- src/search.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8d9dc04fc48..45fc1a7e2aa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1310,7 +1310,13 @@ namespace { if (PvNode && !rootNode) // Update pv even in fail-high case update_pv(ss->pv, move, (ss+1)->pv); - if (PvNode && value < beta) // Update alpha! Always alpha < beta + if (value >= beta) + { + ss->cutoffCnt += 1 + !ttMove; + assert(value >= beta); // Fail high + break; + } + else { // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 @@ -1319,13 +1325,7 @@ namespace { depth -= 1; assert(depth > 0); - alpha = value; - } - else - { - ss->cutoffCnt += 1 + !ttMove; - assert(value >= beta); // Fail high - break; + alpha = value; // Update alpha! Always alpha < beta } } } From 65e2150501b87e6ce00fae4e3f056444f39462fd Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 7 May 2023 23:33:04 +0300 Subject: [PATCH 0076/1309] Refine deeper post-lmr searches This patch improves logic conditions for performing deeper searches after passed LMR. Instead of exceeding alpha by some margin now it requires to exceed the current best value - which may be lower than alpha (but never bigger since we update alpha with bestvalue if it exceeds alpha). Passed STC: https://tests.stockfishchess.org/tests/view/6455f78008858de8313775b6 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 209344 W: 55993 L: 55448 D: 97903 Ptnml(0-2): 507, 22798, 57526, 23325, 516 Passed LTC: https://tests.stockfishchess.org/tests/view/64572d46eb75932ccfebff97 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66288 W: 17867 L: 17514 D: 30907 Ptnml(0-2): 21, 6240, 20269, 6593, 21 closes https://github.com/official-stockfish/Stockfish/pull/4559 bench 3808503 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 45fc1a7e2aa..bc495c0e5de 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1201,7 +1201,7 @@ namespace { { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (alpha + 58 + 12 * (newDepth - d)); + const bool doDeeperSearch = value > (bestValue + 68 + 12 * (newDepth - d)); const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5; const bool doShallowerSearch = value < bestValue + newDepth; From 5f7b26aaa0d0bbdeb50ea6b17f049f167c1eb996 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 19 May 2023 23:02:27 +0200 Subject: [PATCH 0077/1309] Update WLD model using data of May, recalibrate the WLD model. closes https://github.com/official-stockfish/Stockfish/pull/4577 No functional change --- src/uci.cpp | 4 ++-- src/uci.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 8f9684ee265..523d551e0c0 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,8 +207,8 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = { 0.33677609, -4.30175627, 33.08810557, 365.60223431}; - constexpr double bs[] = { -2.50471102, 14.23235405, -14.33066859, 71.42705250 }; + constexpr double as[] = { 1.07390458, -6.94334517, 31.95090161, 317.75424048}; + constexpr double bs[] = { -2.82843814, 16.64518180, -19.74439200, 68.39499088 }; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); diff --git a/src/uci.h b/src/uci.h index 9ca0ed36b4d..680d2d2cc8c 100644 --- a/src/uci.h +++ b/src/uci.h @@ -35,7 +35,7 @@ namespace UCI { // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win // from this position in selfplay at fishtest LTC time control. -const int NormalizeToPawnValue = 394; +const int NormalizeToPawnValue = 343; class Option; From f030a1c592eabfa602ff8560d365e516298363ce Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 16 May 2023 21:30:53 +0800 Subject: [PATCH 0078/1309] Search tuning at very long time control Many search parameter changes, tuned (https://tests.stockfishchess.org/tests/view/645e4c67d55cccb2e64220ff) at ~300k games @ VLTC (120+1.2). Failed STC: https://tests.stockfishchess.org/tests/view/6465fcd77968ca827c1410c2 LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 33824 W: 8863 L: 9067 D: 15894 Ptnml(0-2): 89, 3833, 9266, 3641, 83 Neutral LTC: https://tests.stockfishchess.org/tests/view/646385ce87f6567dd4df4e37 Elo: -0.48 +-1.2 (95%) LOS: 22.2% Total: 60000 W: 16235 L: 16318 D: 27447 Ptnml(0-2): 27, 5831, 18366, 5750, 26 nElo: -1.08 +-2.8 (95%) PairsRatio: 0.99 Passed VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/646385f787f6567dd4df4e3e LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 126448 W: 34704 L: 34258 D: 57486 Ptnml(0-2): 9, 10970, 40825, 11406, 14 Passed VLTC SMP 60+0.6 8thread: https://tests.stockfishchess.org/tests/view/646628de884ce93b65df2ac9 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 59456 W: 16791 L: 16487 D: 26178 Ptnml(0-2): 5, 4473, 20467, 4779, 4 closes https://github.com/official-stockfish/Stockfish/pull/4574 Bench: 3347573 --- src/search.cpp | 94 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bc495c0e5de..429db9a5b28 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -64,7 +64,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(154 * (d - improving)); + return Value(148 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941); + return (r + 1356 - int(delta) * 983 / int(rootDelta)) / 1024 + (!i && r > 901); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(341 * d - 470, 1710); + return std::min(337 * d - 497, 1632); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -162,7 +162,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((19.47 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((20.89 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -348,12 +348,12 @@ void Thread::search() { // Reset aspiration window starting size Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 16502; + delta = Value(11) + int(prev) * prev / 15368; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 102 * prev / (std::abs(prev) + 147); + int opt = 116 * prev / (std::abs(prev) + 143); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -742,7 +742,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1920, 1920); + int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1717, 1717); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -752,13 +752,13 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 156; + : 163; improving = improvement > 0; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 426 - 256 * depth * depth) + if (eval < alpha - 467 - 266 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -769,18 +769,18 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 280 >= beta + && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 25128) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 22761) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 18755 + && (ss-1)->statScore < 18404 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + && ss->staticEval >= beta - 19 * depth - improvement / 13 + 257 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) @@ -823,13 +823,13 @@ namespace { } } - probCutBeta = beta + 186 - 54 * improving; + probCutBeta = beta + 174 - 60 * improving; // Step 10. ProbCut (~10 Elo) // 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. if ( !PvNode - && depth > 4 + && depth > 3 && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY // if value from transposition table is lower than probCutBeta, don't attempt probCut // there and in further interactions with transposition table cutoff depth is set to depth - 3 @@ -887,20 +887,20 @@ namespace { return qsearch(pos, ss, alpha, beta); if ( cutNode - && depth >= 7 + && depth >= 8 && !ttMove) depth -= 2; moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 391; + probCutBeta = beta + 430; if ( ss->inCheck && !PvNode && depth >= 2 && ttCapture && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 3 + && tte->depth() >= depth - 4 && ttValue >= probCutBeta && abs(ttValue) <= VALUE_KNOWN_WIN && abs(beta) <= VALUE_KNOWN_WIN) @@ -986,15 +986,15 @@ namespace { { // Futility pruning for captures (~2 Elo) if ( !givesCheck - && lmrDepth < 6 + && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) + && ss->staticEval + 207 + 223 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] * 1078 / 7000 < alpha) continue; Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-206) * depth)) + if (!pos.see_ge(move, occupied, Value(-205) * depth)) { if (depth < 2 - capture) continue; @@ -1022,24 +1022,24 @@ namespace { // Continuation history based pruning (~2 Elo) if ( lmrDepth < 5 - && history < -4405 * (depth - 1)) + && history < -3792 * (depth - 1)) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7278; + lmrDepth += history / 7019; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck - && lmrDepth < 13 - && ss->staticEval + 103 + 138 * lmrDepth <= alpha) + && lmrDepth < 12 + && ss->staticEval + 111 + 136 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth))) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 33 * lmrDepth / 2))) continue; } } @@ -1054,7 +1054,7 @@ namespace { // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 21) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ @@ -1062,7 +1062,7 @@ namespace { && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (3 + 2 * (ss->ttPv && !PvNode)) * depth / 2; + Value singularBeta = ttValue - (99 + 65 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1076,8 +1076,8 @@ namespace { // Avoid search explosion by limiting the number of double extensions if ( !PvNode - && value < singularBeta - 25 - && ss->doubleExtensions <= 10) + && value < singularBeta - 22 + && ss->doubleExtensions <= 11) { extension = 2; depth += depth < 13; @@ -1107,15 +1107,15 @@ namespace { // Check extensions (~1 Elo) else if ( givesCheck - && depth > 10 - && abs(ss->staticEval) > 88) + && depth > 9 + && abs(ss->staticEval) > 87) extension = 1; // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5705) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5480) extension = 1; } @@ -1144,7 +1144,7 @@ namespace { r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 7) + if ((ss-1)->moveCount > 8) r--; // Increase reduction for cut nodes (~3 Elo) @@ -1157,7 +1157,7 @@ namespace { // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) - r -= 1 + 12 / (3 + depth); + r -= 1 + 11 / (3 + depth); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) @@ -1174,10 +1174,10 @@ namespace { + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4082; + - 3755; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19)); + r -= ss->statScore / (10445 + 4762 * (depth > 6 && depth < 21)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1201,8 +1201,8 @@ namespace { { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (bestValue + 68 + 12 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5; + const bool doDeeperSearch = value > (bestValue + 63 + 11 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 662 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1227,7 +1227,7 @@ namespace { if (!ttMove && cutNode) r += 2; - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode); + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail @@ -1320,8 +1320,8 @@ namespace { { // Reduce other moves if we have found at least one score improvement (~1 Elo) if ( depth > 1 - && beta < 12535 - && value > -12535) + && beta < 14001 + && value > -12754) depth -= 1; assert(depth > 0); @@ -1370,7 +1370,7 @@ namespace { // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 100 * depth) + ((ss-1)->moveCount > 11); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } @@ -1499,7 +1499,7 @@ namespace { if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 168; + futilityBase = bestValue + 190; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1572,7 +1572,7 @@ namespace { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-110))) + if (!pos.see_ge(move, Value(-94))) continue; } @@ -1705,7 +1705,7 @@ namespace { if (!pos.capture_stage(bestMove)) { - int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus + int bonus2 = bestValue > beta + 143 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 4f24ee086828e28df7d0b2dce5c13732139e7c19 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Fri, 19 May 2023 23:16:48 +0200 Subject: [PATCH 0079/1309] Small simplification in history pruning. Remove the constant term of the history threshold which lowers the chance that pruning occurs. As compensation allow pruning at a slightly higher depth. Passed STC: https://tests.stockfishchess.org/tests/view/64634c9a87f6567dd4df4901 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 101536 W: 27156 L: 27012 D: 47368 Ptnml(0-2): 266, 11165, 27772, 11289, 276 Passed LTC: https://tests.stockfishchess.org/tests/view/6463d68b17982fde89d2bc2b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 32154 W: 8741 L: 8543 D: 14870 Ptnml(0-2): 8, 3093, 9687, 3271, 18 Passed LTC: retest on top of VLTC tuning PR 4571 because this changes the history depth factor (use this new factor here) https://tests.stockfishchess.org/tests/view/6467300e165c4b29ec0afd3f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 99270 W: 26840 L: 26707 D: 45723 Ptnml(0-2): 36, 9753, 29928, 9878, 40 closes https://github.com/official-stockfish/Stockfish/pull/4578 Bench: 2984341 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 429db9a5b28..10844746ccc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1021,8 +1021,8 @@ namespace { + (*contHist[3])[movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if ( lmrDepth < 5 - && history < -3792 * (depth - 1)) + if ( lmrDepth < 6 + && history < -3792 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; From 4b085c4777c36939bd0a598f4bc3e0c04606e31b Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Fri, 19 May 2023 19:58:18 +0100 Subject: [PATCH 0080/1309] Simplify optimism calculation This change removes one of the constants in the calculation of optimism. It also changes the 2 constants used with the scale value so that they are independent, instead of applying a constant to the scale and then adjusting it again when it is applied to the optimism. This might make the tuning of these constants cleaner and more reliable in the future. STC 10+0.1 (accidentally run as an Elo gainer: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 154080 W: 41119 L: 40651 D: 72310 Ptnml(0-2): 375, 16840, 42190, 17212, 423 https://tests.stockfishchess.org/tests/live_elo/64653eabf3b1a4e86c317f77 LTC 60+0.6: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 217434 W: 58382 L: 58363 D: 100689 Ptnml(0-2): 66, 21075, 66419, 21088, 69 https://tests.stockfishchess.org/tests/live_elo/6465d077f3b1a4e86c318d6c closes https://github.com/official-stockfish/Stockfish/pull/4576 bench: 3190961 --- src/evaluate.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d8f4e2e194f..cc789e35d89 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1063,7 +1063,7 @@ Value Eval::evaluate(const Position& pos) { else { int nnueComplexity; - int scale = 967 + pos.non_pawn_material() / 64; + int npm = pos.non_pawn_material() / 64; Color stm = pos.side_to_move(); Value optimism = pos.this_thread()->optimism[stm]; @@ -1071,12 +1071,12 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 402 * nnueComplexity - + (454 + optimism) * abs(psq - nnue) + nnueComplexity = ( 397 * nnueComplexity + + (477 + optimism) * abs(psq - nnue) ) / 1024; - optimism = optimism * (274 + nnueComplexity) / 256; - v = (nnue * scale + optimism * (scale - 791)) / 1024; + optimism += optimism * nnueComplexity / 256; + v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; } // Damp down the evaluation linearly when shuffling From 7cd650f435715f73550d1f8031315e65b701d631 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Wed, 17 May 2023 09:22:02 +0200 Subject: [PATCH 0081/1309] Simplify SEE verfication logic Passed STC https://tests.stockfishchess.org/tests/view/6461d51887f6567dd4df27d0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 177056 W: 47181 L: 47118 D: 82757 Ptnml(0-2): 456, 19381, 48792, 19442, 457 Passed LTC https://tests.stockfishchess.org/tests/view/64631a9287f6567dd4df4502 2.94 (-2.94,2.94) <-1.75,0.25> Total: 104346 W: 28062 L: 27935 D: 48349 Ptnml(0-2): 25, 10190, 31631, 10287, 40 closes https://github.com/official-stockfish/Stockfish/pull/4578 bench: 2903251 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 10844746ccc..653cbf339a6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -996,9 +996,7 @@ namespace { // SEE based pruning (~11 Elo) if (!pos.see_ge(move, occupied, Value(-205) * depth)) { - if (depth < 2 - capture) - continue; - // Don't prune the move if opp. King/Queen/Rook gets a discovered attack during or after the exchanges + // Don't prune the move if opponent King/Queen/Rook gets a discovered attack during or after the exchanges Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); Bitboard attacks = 0; occupied |= to_sq(move); @@ -1006,7 +1004,7 @@ namespace { { Square sq = pop_lsb(leftEnemies); attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Exclude Queen/Rook(s) which were already threatened before SEE (opp King can't be in check when it's our turn) + // Exclude Queen/Rook(s) which were already threatened before SEE (opponent King can't be in check when it's our turn) if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) attacks = 0; } From df0fb8471e5015bb4ba0b398c203b7faad45840e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 14 May 2023 00:30:35 +0300 Subject: [PATCH 0082/1309] Small simplification in low depth pruning Uncap low depth pruning lmr depth. It's anyway capped for most cases apart from futility pruning for captures - removes one std::min call. Passed STC: https://tests.stockfishchess.org/tests/view/645e8fa6d55cccb2e64225a1 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 184064 W: 49039 L: 48982 D: 86043 Ptnml(0-2): 462, 20353, 50349, 20402, 466 Passed LTC: https://tests.stockfishchess.org/tests/view/645f4d48d55cccb2e6423335 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 83886 W: 22613 L: 22465 D: 38808 Ptnml(0-2): 31, 8090, 25546, 8252, 24 closes https://github.com/official-stockfish/Stockfish/pull/4566 bench 3201883 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 653cbf339a6..f58def60138 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -979,7 +979,7 @@ namespace { moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search - int lmrDepth = std::max(newDepth - r, 0); + int lmrDepth = newDepth - r; if ( capture || givesCheck) From d7e72d801fd68f2ee3c7d6b814bbc82916c30041 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Mon, 22 May 2023 00:14:55 +0200 Subject: [PATCH 0083/1309] More Depth Reduction Reduce more for depth > 3 and depth < 12 LTC: https://tests.stockfishchess.org/tests/view/646c5abbd1f14fd69a6f2fab LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 197280 W: 53405 L: 52797 D: 91078 Ptnml(0-2): 62, 19025, 59886, 19577, 90 STC: https://tests.stockfishchess.org/tests/view/646bee71d1f14fd69a6f259d LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 100832 W: 26861 L: 26466 D: 47505 Ptnml(0-2): 240, 10985, 27622, 11278, 291 https://github.com/official-stockfish/Stockfish/pull/4585 bench: 2276617 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f58def60138..130855c19c9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1317,10 +1317,11 @@ namespace { else { // Reduce other moves if we have found at least one score improvement (~1 Elo) + // Reduce more for depth > 3 and depth < 12 (~1 Elo) if ( depth > 1 && beta < 14001 && value > -12754) - depth -= 1; + depth -= depth > 3 && depth < 12 ? 2 : 1; assert(depth > 0); alpha = value; // Update alpha! Always alpha < beta From b64c97825eb473d9b5cbdb67afe65a8ac0d5ec9f Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 20 May 2023 21:28:54 +0800 Subject: [PATCH 0084/1309] Simplify delta calculation in aspiration window Simplification STC: https://tests.stockfishchess.org/tests/view/6468cb200db5177f2b76ecbb LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 220416 W: 58503 L: 58487 D: 103426 Ptnml(0-2): 596, 24384, 60188, 24488, 552 Simplification LTC: https://tests.stockfishchess.org/tests/view/646a15840db5177f2b770704 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 177756 W: 47882 L: 47825 D: 82049 Ptnml(0-2): 55, 17430, 53858, 17473, 62 closes https://github.com/official-stockfish/Stockfish/pull/4581 Bench: 2304063 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 130855c19c9..3632a469884 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -409,7 +409,7 @@ void Thread::search() { else break; - delta += delta / 4 + 2; + delta += delta / 3; assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); } From a989aa1825503ab39e6b2cf77bba2f1f022f367c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 21 May 2023 16:22:28 +0300 Subject: [PATCH 0085/1309] Simplify Prune moves with negative SEE Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 57760 W: 15472 L: 15286 D: 27002 Ptnml(0-2): 123, 6025, 16430, 6147, 155 https://tests.stockfishchess.org/tests/view/6468eb6b0db5177f2b76ef62 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 93966 W: 25274 L: 25141 D: 43551 Ptnml(0-2): 33, 8498, 29792, 8623, 37 https://tests.stockfishchess.org/tests/view/6469570b0db5177f2b76f81b closes: https://github.com/official-stockfish/Stockfish/pull/4579 Bench: 2304063 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 3632a469884..270c5e7c398 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1037,7 +1037,7 @@ namespace { lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 33 * lmrDepth / 2))) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 16 * lmrDepth))) continue; } } From cedd73f4aa9f83c2891105695ac11e743e6ceab7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 23 May 2023 20:36:47 +0300 Subject: [PATCH 0086/1309] Simplify Futility pruning for captures Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 412928 W: 109433 L: 109620 D: 193875 Ptnml(0-2): 1071, 45929, 112650, 45744, 1070 https://tests.stockfishchess.org/tests/view/6468eac40db5177f2b76ef4d Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 190200 W: 51465 L: 51420 D: 87315 Ptnml(0-2): 58, 18585, 57788, 18592, 77 https://tests.stockfishchess.org/tests/view/646b66520db5177f2b772a84 closes https://github.com/official-stockfish/Stockfish/pull/4583 bench: 2486604 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 270c5e7c398..32eeedab4ae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -989,7 +989,7 @@ namespace { && lmrDepth < 7 && !ss->inCheck && ss->staticEval + 207 + 223 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] * 1078 / 7000 < alpha) + + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; Bitboard occupied; From c701745cf243f4816754167e85bf5fabf5b34e47 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Thu, 25 May 2023 14:34:52 +0800 Subject: [PATCH 0087/1309] Remove ss->ttHit condition where ttValue != VALUE_NONE Simplification is done at 3 separate places in the code. Thanks to peregrineshahin for helping me find 2 of such places. (See original PR #4584) Passed non-regression test LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 120256 W: 32204 L: 32085 D: 55967 Ptnml(0-2): 292, 12473, 34483, 12584, 296 https://tests.stockfishchess.org/tests/view/646f045968661bfd984325e3 closes https://github.com/official-stockfish/Stockfish/pull/4587 No functional change --- AUTHORS | 1 + src/search.cpp | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index b6723246ada..884bffabe7e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,6 +76,7 @@ George Sobala (gsobala) gguliash Giacomo Lorenzetti (G-Lorenz) Gian-Carlo Pascutto (gcp) +Goh CJ (cj5716) Gontran Lemaire (gonlem) Goodkov Vasiliy Aleksandrovich (goodkov) Gregor Cramer diff --git a/src/search.cpp b/src/search.cpp index 32eeedab4ae..93212e239b9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -615,10 +615,9 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode - && ss->ttHit && !excludedMove && tte->depth() > depth - (tte->bound() == BOUND_EXACT) - && ttValue != VALUE_NONE // Possible in case of TT access race + && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) @@ -835,8 +834,7 @@ namespace { // 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 - && !( ss->ttHit - && tte->depth() >= depth - 3 + && !( tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) { @@ -1453,9 +1451,8 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode - && ss->ttHit && tte->depth() >= ttDepth - && ttValue != VALUE_NONE // Only in case of TT access race + && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; From 7f0b19dedf7bff7dbe2dd42e73788826486b36b6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 27 May 2023 18:01:08 +0200 Subject: [PATCH 0088/1309] Update CPU contributors list update CPU contributors list, the previous update was a couple of months ago, and unfortunately, was not quite accurate for the number of games played. This version is based clean calculation from the DB and an updated script that tracks things (see https://github.com/glinscott/fishtest/pull/1702). closes https://github.com/official-stockfish/Stockfish/pull/4589 No functional change --- Top CPU Contributors.txt | 196 ++++++++++++++++++++------------------- 1 file changed, 101 insertions(+), 95 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 30c963d7c7e..7b27959071a 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,212 +1,218 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2022-11-19. +Contributors to Fishtest with >10,000 CPU hours, as of 2023-05-27. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 36475307 2748033975 -technologov 14570711 760073590 +noobpwnftw 37304027 2833556221 +technologov 13508659 714674674 +linrock 4121386 280027751 mlang 3026000 200065824 -dew 1689222 100034318 -grandphish2 1442171 86798057 -okrout 1439985 133471766 -pemo 1405374 44189811 -linrock 1299003 28382783 -TueRens 1163420 71159522 -JojoM 897158 55177114 +dew 1689162 100033738 +okrout 1541122 145085726 +pemo 1481818 47546583 +grandphish2 1459364 91364265 +TueRens 1178700 69951886 +JojoM 937875 60821044 tvijlbrief 796125 51897690 mibere 703840 46867607 -gvreuls 635982 40652394 -oz 590763 41201352 -sebastronomy 581517 23307132 -cw 517915 34865769 -fastgm 504266 30264740 -CSU_Dynasty 479901 31846710 -ctoks 433503 28180725 +sebastronomy 687502 35585318 +gvreuls 645570 42437926 +oz 541224 39133532 +cw 517856 34869499 +fastgm 503862 30260818 +CSU_Dynasty 464691 31166478 +leszek 460426 32840277 +ctoks 434323 28497451 crunchy 427035 27344275 -leszek 416883 27493447 -bcross 409982 28062127 -velislav 345954 22232274 +maximmasiutin 424154 26534660 +bcross 415722 29060963 +rpngn 344368 24218047 +velislav 342559 22138408 Fisherman 327231 21829379 +mgrabiak 297057 20260882 Dantist 296386 18031762 -mgrabiak 288928 18869896 -rpngn 259965 16281463 -robal 237653 15148350 -ncfish1 231764 15275003 -nordlandia 226923 14624832 +nordlandia 242642 15922516 +robal 240199 15544104 +marrco 234581 17714473 +ncfish1 227517 15233777 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 -thirdlife 198844 5453268 +olafm 192342 14968698 Thanar 179852 12365359 vdv 175544 9904472 -armo9494 168201 11136452 spams 157128 10319326 -marrco 151599 9551115 sqrt2 147963 9724586 -vdbergh 137690 8971569 +DesolatedDodo 144759 9408038 +Calis007 143165 9478764 +vdbergh 138436 9042073 CoffeeOne 137100 5024116 malala 136182 8002293 -DesolatedDodo 135276 8657464 +armo9494 136010 9447548 xoto 133759 9159372 davar 129023 8376525 +DMBK 122960 8980062 dsmith 122059 7570238 amicic 119661 7938029 Data 113305 8220352 BrunoBanani 112960 7436849 CypressChess 108331 7759788 -skiminki 106518 7062598 +skiminki 107583 7218170 MaZePallas 102823 6633619 sterni1971 100532 5880772 +jcAEie 100392 7788270 sunu 100167 7040199 zeryl 99331 6221261 +thirdlife 99124 2242380 ElbertoOne 99028 7023771 -DMBK 97572 6950312 -Calis007 96779 5611552 -cuistot 93111 5536500 +cuistot 98360 6017102 +bigpen0r 94809 6529203 brabos 92118 6186135 -Wolfgang 91769 5720158 +Wolfgang 90855 5998076 psk 89957 5984901 racerschmacer 85805 6122790 -jcAEie 85527 5630616 +Dubslow 84986 6042456 Vizvezdenec 83761 5344740 -sschnee 83557 4853690 +sschnee 83564 4853834 0x3C33 82614 5271253 BRAVONE 81239 5054681 -Dubslow 78461 5042980 +Fifis 77355 5158211 nssy 76497 5259388 jromang 76106 5236025 teddybaer 75125 5407666 -yurikvelo 73933 5031096 -tolkki963 73885 4721430 +Wencey 74181 4711488 +megaman7de 73866 4894960 Pking_cda 73776 5293873 -Bobo1239 71675 4860987 +tolkki963 73531 5020500 +yurikvelo 72847 4972808 +Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 -Gelma 69304 3980932 +markkulix 70278 5068326 manap 66273 4121774 -megaman7de 65419 4120200 -markkulix 65331 4114860 -bigpen0r 64932 4683883 tinker 64333 4268790 qurashee 61208 3429862 -AGI 58325 4258646 +Mineta 58759 4399960 +AGI 58147 4325994 +Spprtr 58106 3858759 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 +MarcusTullius 56746 3762951 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 -Spprtr 52736 3410019 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 rap 49985 3219146 -unixwizard 49734 2536230 -pb00067 49727 3298270 +pb00067 49733 3298934 +javran 49178 4190632 +OuaisBla 48606 3442958 ronaldjerum 47654 3240695 biffhero 46564 3111352 -GPUex 45861 2926502 -Fifis 45843 3088497 -oryx 45578 3493978 VoyagerOne 45476 3452465 -Wencey 44943 2654490 +oryx 44532 3450170 +jmdana 43849 2955821 speedycpu 43842 3003273 jbwiebe 43305 2805433 Antihistamine 41788 2761312 mhunt 41735 2691355 -olafm 41277 3284344 +maposora 41534 3733078 +GPUex 41061 2998356 homyur 39893 2850481 gri 39871 2515779 -MarcusTullius 38303 2251097 Garf 37741 2999686 -kdave 37424 2557406 SC 37299 2731694 csnodgrass 36207 2688994 -jmdana 36157 2210661 strelock 34716 2074055 EthanOConnor 33370 2090311 slakovv 32915 2021889 -gopeto 31669 2060958 +Gelma 31771 1551204 +gopeto 31671 2060990 +szupaw 31248 2594920 +kdave 31157 2198362 manapbk 30987 1810399 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 -spcc 30135 1903728 +spcc 29925 1901692 hyperbolic.tom 29840 2017394 -xwziegtm 29763 2347412 chuckstablers 29659 2093438 Pyafue 29650 1902349 belzedar94 28846 1811530 -OuaisBla 27636 1578800 chriswk 26902 1868317 +xwziegtm 26897 2124586 achambord 26582 1767323 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25289 1674274 +Ulysses 25285 1689346 SFTUser 25182 1675689 nabildanial 24942 1519409 Sharaf_DG 24765 1786697 rodneyc 24376 1416402 agg177 23890 1395014 -Ente 23747 1674582 -Karpovbot 23629 1313186 +Ente 23639 1671638 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 -cisco2015 22934 1763773 +Goatminola 23338 1910634 +cisco2015 22920 1763301 +Jopo12321 22890 1424926 Zirie 22542 1472937 team-oh 22272 1636708 Roady 22220 1465606 MazeOfGalious 21978 1629593 sg4032 21947 1643353 +jsys14 21935 1499128 ianh2105 21725 1632562 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 user213718 21454 1404128 -AndreasKrug 21227 1577833 sphinx 21211 1384728 jjoshua2 21001 1423089 +Zake9298 20938 1565848 +AndreasKrug 20911 1615673 horst.prack 20878 1465656 -jsys14 20729 1221010 0xB00B1ES 20590 1208666 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 -bonsi 20022 1300682 wei 19973 1745989 -dapper 19754 1167758 -Zake9298 19745 1458416 +Serpensin 19840 1697528 fishtester 19617 1257388 rstoesser 19569 1293588 eudhan 19274 1283717 +Gaster319 18934 1596772 vulcan 18871 1729392 -Jopo12321 18803 1036284 +Karpovbot 18766 1053178 jundery 18445 1115855 +votoanthuan 18012 1508836 ville 17883 1384026 -5t0ckf15hTr4in3r 17809 1105858 chris 17698 1487385 -dju 17697 994333 purplefishies 17595 1092533 +qoo_charly_cai 17494 1182667 +dju 17414 981289 iisiraider 17275 1049015 DragonLord 17014 1162790 -Karby 16457 1010138 -Goatminola 16278 1145026 +redstone59 16842 1461780 +Alb11747 16787 1213926 IgorLeMasson 16064 1147232 -Gaster319 16056 1109070 -redstone59 15953 1161664 +Karby 15982 979610 +notchris 15818 1426762 scuzzi 15757 968735 ako027ako 15671 1173203 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 Naven94 15054 834762 OssumOpossum 14857 1007129 -qoo_charly_cai 14490 847865 +ZacHFX 14783 1021842 enedene 14476 905279 -szupaw 14252 929130 bpfliegel 14233 882523 mpx86 14019 759568 jpulman 13982 870599 +Skiff84 13826 721996 crocogoat 13803 1117422 Nesa92 13786 1114691 joster 13710 946160 @@ -214,47 +220,47 @@ mbeier 13650 1044928 Hjax 13535 915487 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 +pirt 13100 1009897 Machariel 13010 863104 infinigon 12991 943216 -pirt 12925 985437 -Skiff84 12923 649994 +Maxim 12963 985594 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 +korposzczur 12606 838168 +Nullvalue 12583 1048502 fatmurphy 12547 853210 -woutboat 12419 836696 SapphireBrand 12416 969604 -Oakwen 12406 840961 +Oakwen 12399 844109 deflectooor 12386 579392 modolief 12386 896470 Farseer 12249 694108 +Jackfish 12180 801372 pgontarz 12151 848794 +dbernier 12103 860824 +getraideBFF 12072 1024966 stocky 11954 699440 mschmidt 11941 803401 -MooTheCow 11871 773654 -Jackfish 11867 773550 -dbernier 11705 821780 +MooTheCow 11870 773598 +FormazChar 11689 877727 whelanh 11557 245188 -Maxim 11543 836024 -Nullvalue 11534 731410 -icewulf 11528 650470 -FormazChar 11523 861599 infinity 11470 727027 aga 11412 695127 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 -ali-al-zhrani 11272 781310 d64 11263 789184 -Bourbaki 11108 709144 +ali-al-zhrani 11245 779246 snicolet 11106 869170 -Alb11747 10855 696920 +dapper 11032 771402 +Karmatron 10828 677458 basepi 10637 744851 Cubox 10621 826448 -Karmatron 10616 674818 michaelrpg 10509 739239 OIVAS7572 10420 995586 -Garruk 10348 704905 +jojo2357 10419 929708 +WoodMan777 10380 873720 +Garruk 10365 706465 dzjp 10343 732529 ols 10259 570669 From 7e9b131efb832339ee6cd9e22b7c837c3e69a1b5 Mon Sep 17 00:00:00 2001 From: windfishballad Date: Mon, 22 May 2023 20:13:44 -0400 Subject: [PATCH 0089/1309] Removed quadratic term in optimism Remove term which is quadratic in optimism in the eval. Simplifies and should also remove the bias towards side to move making the eval better for analysis. STC: https://tests.stockfishchess.org/tests/view/6470a9d8c29e0d4352b0bca5 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 154432 W: 41127 L: 41040 D: 72265 Ptnml(0-2): 380, 17094, 42190, 17163, 389 LTC: https://tests.stockfishchess.org/tests/view/6471e9b3e549d9cf2fb219ef LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 127926 W: 34474 L: 34369 D: 59083 Ptnml(0-2): 43, 12505, 38776, 12582, 57 closes https://github.com/official-stockfish/Stockfish/pull/4590 Bench: 2541211 --- AUTHORS | 1 + src/evaluate.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 884bffabe7e..d01d23cd7e9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -213,6 +213,7 @@ tttak Unai Corzo (unaiic) Uri Blass (uriblass) Vince Negri (cuddlestmonkey) +windfishballad xefoci7612 zz4032 diff --git a/src/evaluate.cpp b/src/evaluate.cpp index cc789e35d89..7239fd1eca0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1072,7 +1072,7 @@ Value Eval::evaluate(const Position& pos) { // Blend nnue complexity with (semi)classical complexity nnueComplexity = ( 397 * nnueComplexity - + (477 + optimism) * abs(psq - nnue) + + 477 * abs(psq - nnue) ) / 1024; optimism += optimism * nnueComplexity / 256; From c1fff71650e2f8bf5a2d63bdc043161cdfe8e460 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 12 May 2023 18:07:20 -0400 Subject: [PATCH 0090/1309] Update NNUE architecture to SFNNv6 with larger L1 size of 1536 Created by training a new net from scratch with L1 size increased from 1024 to 1536. Thanks to Vizvezdenec for the idea of exploring larger net sizes after recent training data improvements. A new net was first trained with lambda 1.0 and constant LR 8.75e-4. Then a strong net from a later epoch in the training run was chosen for retraining with start-lambda 1.0 and initial LR 4.375e-4 decaying with gamma 0.995. Retraining was performed a total of 3 times, for this 4-step process: 1. 400 epochs, lambda 1.0 on filtered T77+T79 v6 deduplicated data 2. 800 epochs, end-lambda 0.75 on T60T70wIsRightFarseerT60T74T75T76.binpack 3. 800 epochs, end-lambda 0.75 and early-fen-skipping 28 on the master dataset 4. 800 epochs, end-lambda 0.7 and early-fen-skipping 28 on the master dataset In the training sequence that reached the new nn-8d69132723e2.nnue net, the epochs used for the 3x retraining runs were: 1. epoch 379 trained on T77T79-filter-v6-dd.min.binpack 2. epoch 679 trained on T60T70wIsRightFarseerT60T74T75T76.binpack 3. epoch 799 trained on the master dataset For training from scratch: python3 easy_train.py \ --experiment-name new-L1-1536-T77T79-filter-v6dd \ --training-dataset /data/T77T79-filter-v6-dd.min.binpack \ --max_epoch 400 \ --lambda 1.0 \ --start-from-engine-test-net False \ --engine-test-branch linrock/Stockfish/L1-1536 \ --nnue-pytorch-branch linrock/Stockfish/misc-fixes-L1-1536 \ --tui False \ --gpus "0," \ --seed $RANDOM Retraining commands were similar to each other. For the 3rd retraining run: python3 easy_train.py \ --experiment-name L1-1536-T77T79-v6dd-Re1-LeelaFarseer-Re2-masterDataset-Re3-sameData \ --training-dataset /data/leela96-dfrc99-v2-T60novdecT80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd.binpack \ --early-fen-skipping 28 \ --max_epoch 800 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-from-engine-test-net False \ --start-from-model /data/L1-1536-T77T79-v6dd-Re1-LeelaFarseer-Re2-masterDataset-nn-epoch799.nnue \ --engine-test-branch linrock/Stockfish/L1-1536 \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-1536 \ --tui False \ --gpus "0," \ --seed $RANDOM The T77+T79 data used is a subset of the master dataset available at: https://robotmoon.com/nnue-training-data/ T60T70wIsRightFarseerT60T74T75T76.binpack is available at: https://drive.google.com/drive/folders/1S9-ZiQa_3ApmjBtl2e8SyHxj4zG4V8gG Local elo at 25k nodes per move vs. nn-e1fb1ade4432.nnue (L1 size 1024): nn-epoch759.nnue : 26.9 +/- 1.6 Failed STC https://tests.stockfishchess.org/tests/view/64742485d29264e4cfa75f97 LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 13728 W: 3588 L: 3829 D: 6311 Ptnml(0-2): 71, 1661, 3610, 1482, 40 Failing LTC https://tests.stockfishchess.org/tests/view/64752d7c4a36543c4c9f3618 LLR: -1.91 (-2.94,2.94) <0.50,2.50> Total: 35424 W: 9522 L: 9603 D: 16299 Ptnml(0-2): 24, 3579, 10585, 3502, 22 Passed VLTC 180+1.8 https://tests.stockfishchess.org/tests/view/64752df04a36543c4c9f3638 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 47616 W: 13174 L: 12863 D: 21579 Ptnml(0-2): 13, 4261, 14952, 4566, 16 Passed VLTC SMP 60+0.6 th 8 https://tests.stockfishchess.org/tests/view/647446ced29264e4cfa761e5 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 19942 W: 5694 L: 5451 D: 8797 Ptnml(0-2): 6, 1504, 6707, 1749, 5 closes https://github.com/official-stockfish/Stockfish/pull/4593 bench 2222567 --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index f5db1c1e622..0990111cf6d 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-e1fb1ade4432.nnue" + #define EvalFileDefaultName "nn-8d69132723e2.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 508f3aae0a7..d10434f34b8 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -39,7 +39,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 1024; +constexpr IndexType TransformedFeatureDimensions = 1536; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; From 07bd8adcbce41f076c36f4b65c7f9a786de0b02d Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 28 May 2023 14:45:24 -0400 Subject: [PATCH 0091/1309] Simplify nnue eval complexity calculation Remove a multiplier when blending nnue complexity with semi-classical complexity. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6473a71dd29264e4cfa75839 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 124768 W: 33180 L: 33060 D: 58528 Ptnml(0-2): 314, 13797, 34030, 13941, 302 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6474af3dd29264e4cfa768f4 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 108180 W: 29008 L: 28884 D: 50288 Ptnml(0-2): 29, 10420, 33075, 10530, 36 closes https://github.com/official-stockfish/Stockfish/pull/4592 bench 2316827 --- src/evaluate.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7239fd1eca0..40c43d23043 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1071,9 +1071,7 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend nnue complexity with (semi)classical complexity - nnueComplexity = ( 397 * nnueComplexity - + 477 * abs(psq - nnue) - ) / 1024; + nnueComplexity = 25 * (nnueComplexity + abs(psq - nnue)) / 64; optimism += optimism * nnueComplexity / 256; v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; From d99942f25449789de78c9d36e3dcb67d4eb04e98 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 31 May 2023 09:22:26 +0300 Subject: [PATCH 0092/1309] Small simplification for probcut in check Remove depth condition from there as not longer needed. Passed STC: https://tests.stockfishchess.org/tests/view/647367cad29264e4cfa753e6 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 254336 W: 67830 L: 67847 D: 118659 Ptnml(0-2): 580, 28181, 69697, 28096, 614 Passed LTC: https://tests.stockfishchess.org/tests/view/647576184a36543c4c9f3af7 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 80706 W: 22048 L: 21898 D: 36760 Ptnml(0-2): 28, 7721, 24712, 7857, 35 closes https://github.com/official-stockfish/Stockfish/pull/4594 bench 2381945 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 93212e239b9..41116eb2206 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -895,7 +895,6 @@ namespace { probCutBeta = beta + 430; if ( ss->inCheck && !PvNode - && depth >= 2 && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 From 06186b786e4a73a29d6f0eef80fa7e20084a1e85 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 2 Jun 2023 19:55:25 +0800 Subject: [PATCH 0093/1309] Search tuning at very long time control with new net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The most significant change would be the singularBeta formula. It was first tested by cj5716 (see https://tests.stockfishchess.org/tests/view/647317c9d29264e4cfa74ec7), and I took much inspiration from that idea. LTC (fixed games): https://tests.stockfishchess.org/tests/view/6479d8da54dd118e1d990b12 Elo: 0.61 ± 1.2 (95%) LOS: 83.4% Total: 60000 W: 16278 L: 16172 D: 27550 Ptnml(0-2): 16, 5845, 18179, 5937, 23 nElo: 1.38 ± 2.8 (95%) PairsRatio: 1.02 VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/6479da1454dd118e1d990b2b LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 33224 W: 9261 L: 8984 D: 14979 Ptnml(0-2): 5, 2809, 10710, 3080, 8 SMP VLTC 8-thread: https://tests.stockfishchess.org/tests/view/647b0fe354dd118e1d992425 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 61398 W: 17386 L: 17081 D: 26931 Ptnml(0-2): 7, 4571, 21232, 4888, 1 closes https://github.com/official-stockfish/Stockfish/pull/4603 Bench: 2805878 --- src/search.cpp | 72 ++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 41116eb2206..16122315032 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -64,7 +64,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool improving) { - return Value(148 * (d - improving)); + return Value(140 * (d - improving)); } // Reductions lookup table, initialized at startup @@ -72,7 +72,7 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int r = Reductions[d] * Reductions[mn]; - return (r + 1356 - int(delta) * 983 / int(rootDelta)) / 1024 + (!i && r > 901); + return (r + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + (!i && r > 936); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -82,7 +82,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(337 * d - 497, 1632); + return std::min(336 * d - 547, 1561); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -162,7 +162,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.89 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((20.57 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -348,12 +348,12 @@ void Thread::search() { // Reset aspiration window starting size Value prev = rootMoves[pvIdx].averageScore; - delta = Value(11) + int(prev) * prev / 15368; + delta = Value(10) + int(prev) * prev / 15799; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore - int opt = 116 * prev / (std::abs(prev) + 143); + int opt = 109 * prev / (std::abs(prev) + 141); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -741,7 +741,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-19 * int((ss-1)->staticEval + ss->staticEval), -1717, 1717); + int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1817, 1817); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -751,13 +751,13 @@ namespace { // margin and the improving flag are used in various pruning heuristics. improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 163; + : 173; improving = improvement > 0; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 467 - 266 * depth * depth) + if (eval < alpha - 456 - 252 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -770,16 +770,16 @@ namespace { && depth < 9 && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 22761) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 18404 + && (ss-1)->statScore < 17329 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 19 * depth - improvement / 13 + 257 + && ss->staticEval >= beta - 21 * depth - improvement * 99 / 1300 + 258 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) @@ -787,7 +787,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 173, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -822,7 +822,7 @@ namespace { } } - probCutBeta = beta + 174 - 60 * improving; + probCutBeta = beta + 168 - 61 * improving; // Step 10. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -892,7 +892,7 @@ namespace { moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 430; + probCutBeta = beta + 413; if ( ss->inCheck && !PvNode && ttCapture @@ -985,13 +985,13 @@ namespace { if ( !givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 207 + 223 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-205) * depth)) + if (!pos.see_ge(move, occupied, Value(-212) * depth)) { // Don't prune the move if opponent King/Queen/Rook gets a discovered attack during or after the exchanges Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); @@ -1017,18 +1017,18 @@ namespace { // Continuation history based pruning (~2 Elo) if ( lmrDepth < 6 - && history < -3792 * depth) + && history < -3832 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7019; + lmrDepth += history / 7011; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck && lmrDepth < 12 - && ss->staticEval + 111 + 136 * lmrDepth <= alpha) + && ss->staticEval + 112 + 138 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); @@ -1057,7 +1057,7 @@ namespace { && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (99 + 65 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (82 + 65 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1071,7 +1071,7 @@ namespace { // Avoid search explosion by limiting the number of double extensions if ( !PvNode - && value < singularBeta - 22 + && value < singularBeta - 21 && ss->doubleExtensions <= 11) { extension = 2; @@ -1101,16 +1101,14 @@ namespace { } // Check extensions (~1 Elo) - else if ( givesCheck - && depth > 9 - && abs(ss->staticEval) > 87) + else if ( givesCheck && depth > 8) extension = 1; // Quiet ttMove extensions (~1 Elo) else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5480) + && (*contHist[0])[movedPiece][to_sq(move)] >= 5168) extension = 1; } @@ -1152,7 +1150,7 @@ namespace { // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) - r -= 1 + 11 / (3 + depth); + r -= 1 + 12 / (3 + depth); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) @@ -1169,10 +1167,10 @@ namespace { + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 3755; + - 4006; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (10445 + 4762 * (depth > 6 && depth < 21)); + r -= ss->statScore / (11124 + 4740 * (depth > 5 && depth < 22)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1196,8 +1194,8 @@ namespace { { // Adjust full depth search based on LMR results - if result // was good enough search deeper, if it was bad enough search shallower - const bool doDeeperSearch = value > (bestValue + 63 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 662 && ss->doubleExtensions <= 6; + const bool doDeeperSearch = value > (bestValue + 64 + 11 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1316,8 +1314,8 @@ namespace { // Reduce other moves if we have found at least one score improvement (~1 Elo) // Reduce more for depth > 3 and depth < 12 (~1 Elo) if ( depth > 1 - && beta < 14001 - && value > -12754) + && beta < 14362 + && value > -12393) depth -= depth > 3 && depth < 12 ? 2 : 1; assert(depth > 0); @@ -1366,7 +1364,7 @@ namespace { // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 100 * depth) + ((ss-1)->moveCount > 11); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); } @@ -1494,7 +1492,7 @@ namespace { if (PvNode && bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 190; + futilityBase = bestValue + 200; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1567,7 +1565,7 @@ namespace { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-94))) + if (!pos.see_ge(move, Value(-95))) continue; } @@ -1700,7 +1698,7 @@ namespace { if (!pos.capture_stage(bestMove)) { - int bonus2 = bestValue > beta + 143 ? bonus1 // larger bonus + int bonus2 = bestValue > beta + 145 ? bonus1 // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 6cf8d938c5950ddedb8a92cdea4712f7d507c614 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 1 Jun 2023 09:50:19 -0400 Subject: [PATCH 0094/1309] Simplify blending nnue complexity with optimism Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6478a26d54dd118e1d98f21c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 241248 W: 64058 L: 64063 D: 113127 Ptnml(0-2): 644, 26679, 65960, 26720, 621 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/647b464854dd118e1d9928b2 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 24336 W: 6658 L: 6451 D: 11227 Ptnml(0-2): 8, 2316, 7312, 2525, 7 closes https://github.com/official-stockfish/Stockfish/pull/4602 bench 2425813 --- src/evaluate.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 40c43d23043..bf6dd69a950 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1070,10 +1070,8 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - // Blend nnue complexity with (semi)classical complexity - nnueComplexity = 25 * (nnueComplexity + abs(psq - nnue)) / 64; - - optimism += optimism * nnueComplexity / 256; + // Blend optimism with nnue complexity and (semi)classical complexity + optimism += 25 * optimism * (nnueComplexity + abs(psq - nnue)) / 16384; v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; } From 5930c0defbe01576315d7d081447f94a01daf337 Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Wed, 31 May 2023 11:48:18 +0200 Subject: [PATCH 0095/1309] Simplify away SEE verification After 4 simplificatons over PR#4453 the idea does not yield significant improvement anymore. Maybe also https://tests.stockfishchess.org/tests/view/640c88092644b62c3394c1c5 was a fluke. Passed non-regression bounds: STC: https://tests.stockfishchess.org/tests/view/64705389c079b6583146d873 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 131936 W: 35040 L: 34930 D: 61966 Ptnml(0-2): 336, 14559, 36035, 14735, 303 LTC: https://tests.stockfishchess.org/tests/view/6471a2ade549d9cf2fb213cd LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 407700 W: 109999 L: 110164 D: 187537 Ptnml(0-2): 279, 39913, 123689, 39632, 337 closes https://github.com/official-stockfish/Stockfish/pull/4595 bench: 2675974 --- src/position.cpp | 19 +++++++------------ src/position.h | 1 - src/search.cpp | 18 +----------------- 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 2a9d798ff7d..af274d3f2c6 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1061,7 +1061,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { +bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); @@ -1080,7 +1080,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1111,43 +1111,43 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = PawnValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { - occupied ^= least_significant_square_bb(bb); if ((swap = KnightValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { - occupied ^= least_significant_square_bb(bb); if ((swap = BishopValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { - occupied ^= least_significant_square_bb(bb); if ((swap = RookValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = QueenValueMg - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); @@ -1162,11 +1162,6 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return bool(res); } -bool Position::see_ge(Move m, Value threshold) const { - Bitboard occupied; - return see_ge(m, occupied, threshold); -} - /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index a736f3e677b..780d463cc64 100644 --- a/src/position.h +++ b/src/position.h @@ -143,7 +143,6 @@ class Position { void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; bool see_ge(Move m, Value threshold = VALUE_ZERO) const; // Accessing hash keys diff --git a/src/search.cpp b/src/search.cpp index 16122315032..1e82203a777 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -989,25 +989,9 @@ namespace { + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; - Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-212) * depth)) - { - // Don't prune the move if opponent King/Queen/Rook gets a discovered attack during or after the exchanges - Bitboard leftEnemies = pos.pieces(~us, KING, QUEEN, ROOK); - Bitboard attacks = 0; - occupied |= to_sq(move); - while (leftEnemies && !attacks) - { - Square sq = pop_lsb(leftEnemies); - attacks = pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Exclude Queen/Rook(s) which were already threatened before SEE (opponent King can't be in check when it's our turn) - if (attacks && sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))) - attacks = 0; - } - if (!attacks) + if (!pos.see_ge(move, Value(-205) * depth)) continue; - } } else { From ced0311890add58ab516b9c19608cbd1e1f295ed Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 30 May 2023 18:24:54 -0400 Subject: [PATCH 0096/1309] Remove static eval threshold for extensions when giving check Passed non-regression STC: https://tests.stockfishchess.org/tests/view/647685d54a36543c4c9f4f2a LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 114688 W: 30701 L: 30571 D: 53416 Ptnml(0-2): 336, 12708, 31136, 12818, 346 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/64774b02b81f005b572de770 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 107310 W: 28920 L: 28796 D: 49594 Ptnml(0-2): 33, 10427, 32621, 10531, 43 closes https://github.com/official-stockfish/Stockfish/pull/4599 bench 2597974 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1e82203a777..4365b215c40 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1085,7 +1085,8 @@ namespace { } // Check extensions (~1 Elo) - else if ( givesCheck && depth > 8) + else if ( givesCheck + && depth > 9) extension = 1; // Quiet ttMove extensions (~1 Elo) From 8dea070538dcad790de3c5b9720bdbb836a32440 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 1 Jun 2023 15:24:13 +0300 Subject: [PATCH 0097/1309] Move internal iterative reduction before probcut This patch moves IIR before probcut which allows probcut to be produced at lower depths. Comments in IIR are also slightly updated. Passed STC: https://tests.stockfishchess.org/tests/view/6472d604d29264e4cfa749fd LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 387616 W: 103295 L: 102498 D: 181823 Ptnml(0-2): 976, 42322, 106381, 43187, 942 Passed LTC: https://tests.stockfishchess.org/tests/view/6475eb8c4a36543c4c9f42e8 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 202836 W: 54901 L: 54281 D: 93654 Ptnml(0-2): 85, 19609, 61422, 20205, 97 closes https://github.com/official-stockfish/Stockfish/pull/4597 bench 2551691 --- src/search.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4365b215c40..0e82f04e995 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -822,9 +822,24 @@ namespace { } } + // Step 10. If the position doesn't a have ttMove, decrease depth by 2 + // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). + // Use qsearch if depth is equal or below zero (~9 Elo) + if ( PvNode + && !ttMove) + depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); + + if (depth <= 0) + return qsearch(pos, ss, alpha, beta); + + if ( cutNode + && depth >= 8 + && !ttMove) + depth -= 2; + probCutBeta = beta + 168 - 61 * improving; - // Step 10. ProbCut (~10 Elo) + // Step 11. ProbCut (~10 Elo) // 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. if ( !PvNode @@ -875,20 +890,6 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); } - // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). - // Use qsearch if depth is equal or below zero (~9 Elo) - if ( PvNode - && !ttMove) - depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); - - if (depth <= 0) - return qsearch(pos, ss, alpha, beta); - - if ( cutNode - && depth >= 8 - && !ttMove) - depth -= 2; - moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) From b60738e01b5393e6f85bc10d2f257dd0cd26a2f9 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Mon, 5 Jun 2023 00:59:31 +0300 Subject: [PATCH 0098/1309] Fix no previous moves on root. guards against no previous move existing if qSearch is called on the root node (i.e. when razoring). Passed Non-regression STC: https://tests.stockfishchess.org/tests/view/647d242d726f6b400e408143 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 53120 W: 14167 L: 13976 D: 24977 Ptnml(0-2): 109, 5597, 14981, 5740, 133 closes https://github.com/official-stockfish/Stockfish/pull/4604 Bench: 2551691 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0e82f04e995..c7b00766553 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1489,7 +1489,7 @@ namespace { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = (ss-1)->currentMove != MOVE_NULL ? to_sq((ss-1)->currentMove) : SQ_NONE; + Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, From 295f57829ea189248c8b4afa8d11222d170f78da Mon Sep 17 00:00:00 2001 From: disservin Date: Mon, 17 Apr 2023 22:16:22 +0200 Subject: [PATCH 0099/1309] Add binaries to releases with github actions when a release is made with a tag matching sf_* the binaries will also be uploaded to the release as assets. closes https://github.com/official-stockfish/Stockfish/pull/4596 No functional change. --- .github/workflows/stockfish.yml | 6 +++-- .github/workflows/stockfish_arm_binaries.yml | 8 ++++++ .github/workflows/stockfish_binaries.yml | 27 +++++++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 6345b27cb74..082c65def18 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -1,6 +1,8 @@ name: Stockfish on: push: + tags: + - '*' branches: - master - tools @@ -17,8 +19,8 @@ jobs: Compiles: uses: ./.github/workflows/stockfish_compile_test.yml Binaries: - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') uses: ./.github/workflows/stockfish_binaries.yml ARM_Binaries: - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') uses: ./.github/workflows/stockfish_arm_binaries.yml diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index e088c441925..9a4734eeec6 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -115,6 +115,8 @@ jobs: cp "Top CPU Contributors.txt" stockfish/ cp Copying.txt stockfish/ cp AUTHORS stockfish/ + cp CITATION.cff stockfish/ + cp README.md stockfish/ tar -cvf stockfish-android-$BINARY.tar stockfish - name: Upload binaries @@ -122,3 +124,9 @@ jobs: with: name: stockfish-android-${{ matrix.binaries }} path: stockfish-android-${{ matrix.binaries }}.tar + + - name: Release + if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: stockfish-android-${{ matrix.binaries }}.tar \ No newline at end of file diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index b22897cf692..86449b97a4b 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -9,23 +9,26 @@ jobs: COMPILER: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} - OS: ${{ matrix.config.os }} + NAME: ${{ matrix.config.simple_name }} BINARY: ${{ matrix.binaries }} strategy: matrix: config: - name: Ubuntu 20.04 GCC os: ubuntu-20.04 + simple_name: ubuntu compiler: g++ comp: gcc shell: bash {0} - name: MacOS 12 Apple Clang os: macos-12 + simple_name: macos compiler: clang++ comp: clang shell: bash {0} - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 + simple_name: windows compiler: g++ comp: mingw msys_sys: mingw64 @@ -75,19 +78,17 @@ jobs: - name: Compile ${{ matrix.binaries }} build run: | - make clean make -j2 profile-build ARCH=$BINARY COMP=$COMP make strip ARCH=$BINARY COMP=$COMP - mv ./stockfish$EXT ../stockfish-$OS-$BINARY$EXT + mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT - name: Remove non src files - run: rm -f *.o .depend *.nnue + run: git clean -fx - name: Download wiki run: | git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki - cd ../wiki - rm -rf .git + rm -rf ../wiki/.git - name: Create tar archive. run: | @@ -95,14 +96,22 @@ jobs: mkdir stockfish cp -r wiki stockfish/ cp -r src stockfish/ - cp stockfish-$OS-$BINARY$EXT stockfish/ + cp stockfish-$NAME-$BINARY$EXT stockfish/ cp "Top CPU Contributors.txt" stockfish/ cp Copying.txt stockfish/ cp AUTHORS stockfish/ - tar -cvf stockfish-$OS-$BINARY.tar stockfish + cp CITATION.cff stockfish/ + cp README.md stockfish/ + tar -cvf stockfish-$NAME-$BINARY.tar stockfish - name: Upload binaries uses: actions/upload-artifact@v3 with: name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} - path: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}.tar + path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar + + - name: Release + if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar From 373359b44d0947cce2628a9a8c9b432a458615a8 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 4 Jun 2023 01:56:11 -0400 Subject: [PATCH 0100/1309] Update default net to nn-0dd1cebea573.nnue Created by retraining an earlier epoch of the experiment leading to the first SFNNv6 net on a more-randomized version of the nn-e1fb1ade4432.nnue dataset mixed with unfiltered T80 apr2023 data. Trained using early-fen-skipping 28 and max-epoch 960. The trainer settings and epochs used in the 5-step training sequence leading here were: 1. train from scratch for 400 epochs, lambda 1.0, constant LR 9.75e-4, T79T77-filter-v6-dd.min.binpack 2. retrain ep379, max-epoch 800, end-lambda 0.75, T60T70wIsRightFarseerT60T74T75T76.binpack 3. retrain ep679, max-epoch 800, end-lambda 0.75, skip 28, nn-e1fb1ade4432 dataset 4. retrain ep799, max-epoch 800, end-lambda 0.7, skip 28, nn-e1fb1ade4432 dataset 5. retrain ep439, max-epoch 960, end-lambda 0.7, skip 28, shuffled nn-e1fb1ade4432 + T80 apr2023 This net was epoch 559 of the final (step 5) retraining: ```bash python3 easy_train.py \ --experiment-name L1-1536-Re4-leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr-shuffled-sk28 \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-1536 \ --early-fen-skipping 28 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 960 \ --start-from-engine-test-net False \ --start-from-model /data/L1-1536-Re3-nn-epoch439.nnue \ --engine-test-branch linrock/Stockfish/L1-1536 \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --seed $RANDOM \ --gpus "0," ``` During data preparation, most binpacks were unminimized by removing positions with score 32002 (`VALUE_NONE`). This makes the tradeoff of increasing dataset filesize on disk to increase the randomness of positions in interleaved datasets. The code used for unminimizing is at: https://github.com/linrock/Stockfish/tree/tools-unminify For preparing the dataset used in this experiment: ```bash python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-16tb7p-eval-filt-v2.binpack \ filt-v6-dd-min/test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-aug2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd/test80-jul2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-oct2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test80-nov2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd-min/test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd-min/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd/test79-apr2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test79-may2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd-min/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.binpack \ filt-v6-dd/test78-juntosep2022-16tb7p-filter-v6-dd.binpack \ filt-v6-dd/test77-dec2021-16tb7p-filter-v6-dd.binpack \ test80-apr2023-2tb7p.binpack \ /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack ``` T80 apr2023 data was converted using lc0-rescorer with ~2tb of tablebases and can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move vs. nn-e1fb1ade4432.nnue (L1 size 1024): nn-epoch559.nnue : 25.7 +/- 1.6 Passed STC: https://tests.stockfishchess.org/tests/view/647cd3b87cf638f0f53f9cbb LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 59200 W: 16000 L: 15660 D: 27540 Ptnml(0-2): 159, 6488, 15996, 6768, 189 Passed LTC: https://tests.stockfishchess.org/tests/view/647d58de726f6b400e4085d8 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 58800 W: 16002 L: 15657 D: 27141 Ptnml(0-2): 44, 5607, 17748, 5962, 39 closes https://github.com/official-stockfish/Stockfish/pull/4606 bench 2141197 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 0990111cf6d..dcbe6b3c9bc 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-8d69132723e2.nnue" + #define EvalFileDefaultName "nn-0dd1cebea573.nnue" namespace NNUE { From 54ad986768eec524aeab721713ea2009931b51b3 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 5 Jun 2023 01:08:38 -0400 Subject: [PATCH 0101/1309] Remove optimism multiplier in nnue eval calculation The same formula had passed SPRT against an earlier version of master. Passed non-regression STC vs. d99942f: https://tests.stockfishchess.org/tests/view/6478e76654dd118e1d98f72e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 118720 W: 31402 L: 31277 D: 56041 Ptnml(0-2): 301, 13148, 32344, 13259, 308 Passed non-regression LTC vs. d99942f: https://tests.stockfishchess.org/tests/view/647a22c154dd118e1d991146 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 74286 W: 20019 L: 19863 D: 34404 Ptnml(0-2): 31, 7189, 22540, 7359, 24 The earlier patch had conflicted with a faster SPRT passer, so this was tested again after rebasing on latest master. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/647d6e46726f6b400e408790 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 166176 W: 44309 L: 44234 D: 77633 Ptnml(0-2): 461, 18252, 45557, 18387, 431 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/647eb00ba268d1bc11255e7b LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 28170 W: 7713 L: 7513 D: 12944 Ptnml(0-2): 14, 2609, 8635, 2817, 10 closes https://github.com/official-stockfish/Stockfish/pull/4607 bench 2503095 --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bf6dd69a950..35d054270ee 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1071,8 +1071,8 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); // Blend optimism with nnue complexity and (semi)classical complexity - optimism += 25 * optimism * (nnueComplexity + abs(psq - nnue)) / 16384; - v = (nnue * (945 + npm) + optimism * (174 + npm)) / 1024; + optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; + v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; } // Damp down the evaluation linearly when shuffling From a9a6915e0839d3f3f54659c86f15868a7db0e386 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Mon, 5 Jun 2023 07:59:56 +0800 Subject: [PATCH 0102/1309] Simplify multiplier for improvement This simplifies a `* 99 / 1300` term into just `/ 13`. Passed non-regression STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 58816 W: 15727 L: 15540 D: 27549 Ptnml(0-2): 149, 6370, 16203, 6517, 169 https://tests.stockfishchess.org/tests/view/647d25e4726f6b400e408165 Passed non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 154386 W: 41749 L: 41669 D: 70968 Ptnml(0-2): 94, 14992, 46956, 15042, 109 https://tests.stockfishchess.org/tests/view/647d9b3c726f6b400e408b2a closes https://github.com/official-stockfish/Stockfish/pull/4608 Bench: 2511327 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c7b00766553..593fdc722e0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -779,7 +779,7 @@ namespace { && (ss-1)->statScore < 17329 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 21 * depth - improvement * 99 / 1300 + 258 + && ss->staticEval >= beta - 21 * depth - improvement / 13 + 258 && !excludedMove && pos.non_pawn_material(us) && (ss->ply >= thisThread->nmpMinPly)) From e1dd005583bd6c2aaf58468efc5de86a3936380a Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Wed, 7 Jun 2023 09:01:05 +0200 Subject: [PATCH 0103/1309] Reintroduce SEE verification against discovered attacks Reintroduces https://github.com/official-stockfish/Stockfish/pull/4453 along with https://github.com/official-stockfish/Stockfish/pull/4469 Leaving out https://github.com/official-stockfish/Stockfish/pull/4533 https://github.com/official-stockfish/Stockfish/pull/4572 Passed STC: https://tests.stockfishchess.org/tests/view/647d8c37726f6b400e408a0a LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 143168 W: 38346 L: 37892 D: 66930 Ptnml(0-2): 352, 15672, 39164, 15962, 434 Passed LTC: https://tests.stockfishchess.org/tests/view/647ee8c528c4431bcb58e432 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 71538 W: 19560 L: 19190 D: 32788 Ptnml(0-2): 49, 6905, 21499, 7259, 57 closes https://github.com/official-stockfish/Stockfish/pull/4609 bench: 2595430 --- src/position.cpp | 19 ++++++++++++------- src/position.h | 1 + src/search.cpp | 23 +++++++++++++++++++++-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index af274d3f2c6..2a9d798ff7d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1061,7 +1061,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value threshold) const { +bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { assert(is_ok(m)); @@ -1080,7 +1080,7 @@ bool Position::see_ge(Move m, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1111,43 +1111,43 @@ bool Position::see_ge(Move m, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = PawnValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { + occupied ^= least_significant_square_bb(bb); if ((swap = KnightValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { + occupied ^= least_significant_square_bb(bb); if ((swap = BishopValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { + occupied ^= least_significant_square_bb(bb); if ((swap = RookValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { + occupied ^= least_significant_square_bb(bb); if ((swap = QueenValueMg - swap) < res) break; - occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); @@ -1162,6 +1162,11 @@ bool Position::see_ge(Move m, Value threshold) const { return bool(res); } +bool Position::see_ge(Move m, Value threshold) const { + Bitboard occupied; + return see_ge(m, occupied, threshold); +} + /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index 780d463cc64..2e6014dbe6e 100644 --- a/src/position.h +++ b/src/position.h @@ -144,6 +144,7 @@ class Position { // Static Exchange Evaluation bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index 593fdc722e0..b2c2344ac0a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -990,9 +990,28 @@ namespace { + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; + Bitboard occupied; // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, Value(-205) * depth)) - continue; + if (!pos.see_ge(move, occupied, Value(-205) * depth)) + { + if (depth < 2 - capture) + continue; + // Don't prune the move if opponent Queen/Rook is under discovered attack after the exchanges + // Don't prune the move if opponent King is under discovered attack after or during the exchanges + Bitboard leftEnemies = (pos.pieces(~us, KING, QUEEN, ROOK)) & occupied; + Bitboard attacks = 0; + occupied |= to_sq(move); + while (leftEnemies && !attacks) + { + Square sq = pop_lsb(leftEnemies); + attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; + // don't consider pieces which were already threatened/hanging before SEE exchanges + if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) + attacks = 0; + } + if (!attacks) + continue; + } } else { From 932f5a2d657c846c282adcf2051faef7ca17ae15 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 7 Jun 2023 14:57:17 -0400 Subject: [PATCH 0104/1309] Update default net to nn-ea57bea57e32.nnue Created by retraining an earlier epoch (ep659) of the experiment that led to the first SFNNv6 net: - First retrained on the nn-0dd1cebea573 dataset - Then retrained with skip 20 on a smaller dataset containing unfiltered Leela data - And then retrained again with skip 27 on the nn-0dd1cebea573 dataset The equivalent 7-step training sequence from scratch that led here was: 1. max-epoch 400, lambda 1.0, constant LR 9.75e-4, T79T77-filter-v6-dd.min.binpack ep379 chosen for retraining in step2 2. max-epoch 800, end-lambda 0.75, T60T70wIsRightFarseerT60T74T75T76.binpack ep679 chosen for retraining in step3 3. max-epoch 800, end-lambda 0.75, skip 28, nn-e1fb1ade4432 dataset ep799 chosen for retraining in step4 4. max-epoch 800, end-lambda 0.7, skip 28, nn-e1fb1ade4432 dataset ep759 became nn-8d69132723e2.nnue (first SFNNv6 net) ep659 chosen for retraining in step5 5. max-epoch 800, end-lambda 0.7, skip 28, nn-0dd1cebea573 dataset ep759 chosen for retraining in step6 6. max-epoch 800, end-lambda 0.7, skip 20, leela-dfrc-v2-T77decT78janfebT79aprT80apr.binpack ep639 chosen for retraining in step7 7. max-epoch 800, end-lambda 0.7, skip 27, nn-0dd1cebea573 dataset ep619 became nn-ea57bea57e32.nnue For the last retraining (step7): python3 easy_train.py --experiment-name L1-1536-Re6-masterShuffled-ep639-sk27-Re5-leela-dfrc-v2-T77toT80small-Re4-masterShuffled-ep659-Re3-sameAs-Re2-leela96-dfrc99-16t-v2-T60novdecT80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-Re1-LeelaFarseer-new-T77T79 \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-1536 \ --early-fen-skipping 27 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --max_epoch 800 \ --start-from-engine-test-net False \ --start-from-model /data/L1-1536-Re5-leela-dfrc-v2-T77toT80small-epoch639.nnue \ --lr 4.375e-4 \ --gamma 0.995 \ --tui False \ --seed $RANDOM \ --gpus "0," For preparing the step6 leela-dfrc-v2-T77decT78janfebT79aprT80apr.binpack dataset: python3 interleave_binpacks.py \ leela96-filt-v2.binpack \ dfrc99-16tb7p-eval-filt-v2.binpack \ test77-dec2021-16tb7p.no-db.min-mar2023.binpack \ test78-janfeb2022-16tb7p.no-db.min-mar2023.binpack \ test79-apr2022-16tb7p-filter-v6-dd.binpack \ test80-apr2022-16tb7p.no-db.min-mar2023.binpack \ /data/leela-dfrc-v2-T77decT78janfebT79aprT80apr.binpack The unfiltered Leela data used for the step6 dataset can be found at: https://robotmoon.com/nnue-training-data Local elo at 25k nodes per move: nn-epoch619.nnue : 2.3 +/- 1.9 Passed STC: https://tests.stockfishchess.org/tests/view/6480d43c6e6ce8d9fc6d7cc8 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 40992 W: 11017 L: 10706 D: 19269 Ptnml(0-2): 113, 4400, 11170, 4689, 124 Passed LTC: https://tests.stockfishchess.org/tests/view/648119ac6e6ce8d9fc6d8208 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 129174 W: 35059 L: 34579 D: 59536 Ptnml(0-2): 66, 12548, 38868, 13050, 55 closes https://github.com/official-stockfish/Stockfish/pull/4611 bench: 2370027 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index dcbe6b3c9bc..fc852c8d751 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-0dd1cebea573.nnue" + #define EvalFileDefaultName "nn-ea57bea57e32.nnue" namespace NNUE { From b7ee7290b552b21352491ec7f390565ff4748647 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 12 Jun 2023 09:51:28 +0200 Subject: [PATCH 0105/1309] Add network export to CI verify the network written by export_net matches the original closes https://github.com/official-stockfish/Stockfish/pull/4613 No functional change --- tests/instrumented.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index e9455eabddc..1b37c7a8b75 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -70,7 +70,8 @@ for args in "eval" \ "go depth 10" \ "go movetime 1000" \ "go wtime 8000 btime 8000 winc 500 binc 500" \ - "bench 128 $threads 8 default depth" + "bench 128 $threads 8 default depth" \ + "export_net verify.nnue" do echo "$prefix $exeprefix ./stockfish $args $postfix" @@ -78,6 +79,11 @@ do done +# verify the generated net equals the base net +network=`./stockfish uci | grep 'option name EvalFile type string default' | awk '{print $NF}'` +echo "Comparing $network to the written verify.nnue" +diff $network verify.nnue + # more general testing, following an uci protocol exchange cat << EOF > game.exp set timeout 240 From 38e61663d836e062af0bc002814ad5149c4b7729 Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Sun, 11 Jun 2023 03:24:04 +0200 Subject: [PATCH 0106/1309] Use block sparse input for the first layer. Use block sparse input for the first fully connected layer on architectures with at least SSSE3. Depending on the CPU architecture, this yields a speedup of up to 10%, e.g. ``` Result of 100 runs of 'bench 16 1 13 default depth NNUE' base (...ockfish-base) = 959345 +/- 7477 test (...ckfish-patch) = 1054340 +/- 9640 diff = +94995 +/- 3999 speedup = +0.0990 P(speedup > 0) = 1.0000 CPU: 8 x AMD Ryzen 7 5700U with Radeon Graphics Hyperthreading: on ``` Passed STC: https://tests.stockfishchess.org/tests/view/6485aa0965ffe077ca12409c LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 8864 W: 2479 L: 2223 D: 4162 Ptnml(0-2): 13, 829, 2504, 1061, 25 This commit includes a net with reordered weights, to increase the likelihood of block sparse inputs, but otherwise equivalent to the previous master net (nn-ea57bea57e32.nnue). Activation data collected with https://github.com/AndrovT/Stockfish/tree/log-activations, running bench 16 1 13 varied_1000.epd depth NNUE on this data. Net parameters permuted with https://gist.github.com/AndrovT/9e3fbaebb7082734dc84d27e02094cb3. closes https://github.com/official-stockfish/Stockfish/pull/4612 No functional change --- AUTHORS | 1 + src/evaluate.h | 2 +- .../layers/affine_transform_sparse_input.h | 286 ++++++++++++++++++ src/nnue/nnue_architecture.h | 3 +- 4 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src/nnue/layers/affine_transform_sparse_input.h diff --git a/AUTHORS b/AUTHORS index d01d23cd7e9..63b862ced05 100644 --- a/AUTHORS +++ b/AUTHORS @@ -159,6 +159,7 @@ Norman Schmidt (FireFather) notruck Ofek Shochat (OfekShochat, ghostway) Ondrej Mosnáček (WOnder93) +Ondřej Mišina (AndrovT) Oskar Werkelin Ahlin Pablo Vazquez Panthee diff --git a/src/evaluate.h b/src/evaluate.h index fc852c8d751..94cd42cc67a 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-ea57bea57e32.nnue" + #define EvalFileDefaultName "nn-fdc1d0fe6455.nnue" namespace NNUE { diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h new file mode 100644 index 00000000000..00b17c19f12 --- /dev/null +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -0,0 +1,286 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransformSparseInput of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED + +#include +#include +#include +#include +#include "../nnue_common.h" +#include "affine_transform.h" +#include "simd.h" + +/* + This file contains the definition for a fully connected layer (aka affine transform) with block sparse input. +*/ + +namespace Stockfish::Eval::NNUE::Layers { +#if defined(__GNUC__) // GCC, Clang, ICC + + static inline IndexType lsb_(std::uint32_t b) { + assert(b); + return IndexType(__builtin_ctzl(b)); + } + +#elif defined(_MSC_VER) // MSVC + + static inline IndexType lsb_(std::uint32_t b) { + assert(b); + unsigned long idx; + _BitScanForward(&idx, b); + return (IndexType) idx; + } + +#else // Compiler is neither GCC nor MSVC compatible + +#error "Compiler not supported." + +#endif + + +#if defined(USE_SSSE3) + alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ + std::array, 256> v{}; + for (int i = 0; i < 256; ++i) + { + int j = i; + int k = 0; + while(j) + { + const IndexType lsbIndex = lsb_(std::uint32_t(j)); + j &= j - 1; + v[i][k] = lsbIndex; + ++k; + } + } + return v; + }(); + alignas(CacheLineSize) static inline const std::array lookup_count = [](){ + std::array v; + for (int i = 0; i < 256; ++i) + { + int j = i; + int k = 0; + while(j) + { + j &= j - 1; + ++k; + } + v[i] = k; + } + return v; + }(); + + // Find indices of nonzero numbers in an int32_t array + template + void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) +#elif defined (USE_AVX2) + using vec_t = __m256i; + #define vec_nnz(a) _mm256_movemask_ps((__m256)_mm256_cmpgt_epi32(a, _mm256_setzero_si256())) +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_nnz(a) _mm_movemask_ps((__m128)_mm_cmpgt_epi32(a, _mm_setzero_si128())) +#endif + constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); + // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) + constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + constexpr IndexType OutputsPerChunk = ChunkSize / 8; + + const auto inputVector = reinterpret_cast(input); + IndexType count = 0; + __m128i base = _mm_set1_epi16(0); + __m128i increment = _mm_set1_epi16(8); + for (IndexType i = 0; i < NumChunks; ++i) + { + // bitmask of nonzero values in this chunk + unsigned nnz = 0; + for (IndexType j = 0; j < InputsPerChunk; ++j) + { + const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; + nnz |= (unsigned)vec_nnz(inputChunk) << (j * InputSimdWidth); + } + for (IndexType j = 0; j < OutputsPerChunk; ++j) + { + const auto lookup = (nnz >> (j * 8)) & 0xFF; + const auto offsets = _mm_loadu_si128(reinterpret_cast(&lookup_indices[lookup])); + _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets)); + count += lookup_count[lookup]; + base = _mm_add_epi16(base, increment); + } + } + count_out = count; + } +# undef vec_nnz +#endif + + // Sparse input implementation + template + class AffineTransformSparseInput { + public: + // Input/output type + // Input/output type + using InputType = std::uint8_t; + using OutputType = std::int32_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = OutDims; + + static_assert(OutputDimensions % 16 == 0, "Only implemented for OutputDimensions divisible by 16."); + + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, MaxSimdWidth); + +#if defined (USE_SSSE3) + static constexpr IndexType ChunkSize = 4; +#else + static constexpr IndexType ChunkSize = 1; +#endif + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; + } + + static IndexType get_weight_index_scrambled(IndexType i) + { + return + (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + + i / PaddedInputDimensions * ChunkSize + + i % ChunkSize; + } + + static IndexType get_weight_index(IndexType i) + { +#if defined (USE_SSSE3) + return get_weight_index_scrambled(i); +#else + return i; +#endif + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + write_little_endian(stream, biases, OutputDimensions); + + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); + + return !stream.fail(); + } + // Forward propagation + const OutputType* propagate( + const InputType* input, OutputType* output) const { + +#if defined (USE_SSSE3) +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 +#elif defined (USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 +#endif + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + std::uint16_t nnz[NumChunks]; + IndexType count; + + const auto input32 = reinterpret_cast(input); + + // Find indices of nonzero 32bit blocks + find_nnz(input32, nnz, count); + + const vec_t* biasvec = reinterpret_cast(biases); + vec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType j = 0; j < count; ++j) + { + const auto i = nnz[j]; + const vec_t in = vec_set_32(input32[i]); + const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in, col[k]); + } + + vec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; +# undef vec_setzero +# undef vec_set_32 +# undef vec_add_dpbusd_32 +#else + // Use dense implementation for the other architectures. + affine_transform_non_ssse3< + InputDimensions, + PaddedInputDimensions, + OutputDimensions>(output, weights, biases, input); +#endif + + return output; + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; + }; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index d10434f34b8..413dbb3dcd7 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -27,6 +27,7 @@ #include "features/half_ka_v2_hm.h" +#include "layers/affine_transform_sparse_input.h" #include "layers/affine_transform.h" #include "layers/clipped_relu.h" #include "layers/sqr_clipped_relu.h" @@ -48,7 +49,7 @@ struct Network static constexpr int FC_0_OUTPUTS = 15; static constexpr int FC_1_OUTPUTS = 32; - Layers::AffineTransform fc_0; + Layers::AffineTransformSparseInput fc_0; Layers::SqrClippedReLU ac_sqr_0; Layers::ClippedReLU ac_0; Layers::AffineTransform fc_1; From ece90bca9c513fe7b252da1521fc5ff701396f61 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 12 Jun 2023 22:13:08 +0300 Subject: [PATCH 0107/1309] Improve comments Fix comments for IIR, also document non-linear scaling in extensions. Also add explicitly the bench, to fix an issue after the last commit. closes https://github.com/official-stockfish/Stockfish/pull/4614 bench 2370027 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b2c2344ac0a..7ee6d43963f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -822,7 +822,7 @@ namespace { } } - // Step 10. If the position doesn't a have ttMove, decrease depth by 2 + // Step 10. If the position doesn't have a ttMove, decrease depth by 2 // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). // Use qsearch if depth is equal or below zero (~9 Elo) if ( PvNode @@ -1052,6 +1052,9 @@ namespace { // then that move is singular and should be extended. To verify this we do // a reduced search on all the other moves but the ttMove and if the // result is lower than ttValue minus a margin, then we will extend the ttMove. + // 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 this type of time controls. if ( !rootNode && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) && move == ttMove From 92c949e12e028cb4556de2786a77f2aec178d59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicolet?= Date: Mon, 12 Jun 2023 23:26:42 +0200 Subject: [PATCH 0108/1309] Clean-up code indentation in qsearch closes https://github.com/official-stockfish/Stockfish/pull/4615 No functional change --- src/search.cpp | 152 +++++++++++++++++++++++++------------------------ 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7ee6d43963f..d3b5642a236 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1481,10 +1481,11 @@ namespace { bestValue = ttValue; } else + { // In case of null move search use previous static eval with a different sign - ss->staticEval = bestValue = - (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - : -(ss-1)->staticEval; + ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) + : -(ss-1)->staticEval; + } // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) @@ -1523,97 +1524,98 @@ namespace { // or a beta cutoff occurs. while ((move = mp.next_move()) != MOVE_NONE) { - assert(is_ok(move)); + assert(is_ok(move)); - // Check for legality - if (!pos.legal(move)) - continue; + // Check for legality + if (!pos.legal(move)) + continue; - givesCheck = pos.gives_check(move); - capture = pos.capture_stage(move); + givesCheck = pos.gives_check(move); + capture = pos.capture_stage(move); - moveCount++; + moveCount++; - // Step 6. Pruning. - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) - { - // Futility pruning and moveCount pruning (~10 Elo) - if ( !givesCheck - && to_sq(move) != prevSq - && futilityBase > -VALUE_KNOWN_WIN - && type_of(move) != PROMOTION) - { - if (moveCount > 2) - continue; + // Step 6. Pruning. + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { + // Futility pruning and moveCount pruning (~10 Elo) + if ( !givesCheck + && to_sq(move) != prevSq + && futilityBase > -VALUE_KNOWN_WIN + && type_of(move) != PROMOTION) + { + if (moveCount > 2) + continue; - futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; + futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; - if (futilityValue <= alpha) - { - bestValue = std::max(bestValue, futilityValue); - continue; - } + if (futilityValue <= alpha) + { + bestValue = std::max(bestValue, futilityValue); + continue; + } - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) - { - bestValue = std::max(bestValue, futilityBase); - continue; - } - } + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + { + bestValue = std::max(bestValue, futilityBase); + continue; + } + } - // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter - // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead. - if (quietCheckEvasions > 1) - break; + // We prune after the second quiet check evasion move, where being 'in check' is + // implicitly checked through the counter, and being a 'quiet move' apart from + // being a tt move is assumed after an increment because captures are pushed ahead. + if (quietCheckEvasions > 1) + break; - // Continuation history based pruning (~3 Elo) - if ( !capture - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) - continue; + // Continuation history based pruning (~3 Elo) + if ( !capture + && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + continue; - // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-95))) - continue; - } + // Do not search moves with bad enough SEE values (~5 Elo) + if (!pos.see_ge(move, Value(-95))) + continue; + } - // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); - // Update the current move - ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [pos.moved_piece(move)] - [to_sq(move)]; + // Update the current move + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [capture] + [pos.moved_piece(move)] + [to_sq(move)]; - quietCheckEvasions += !capture && ss->inCheck; + quietCheckEvasions += !capture && ss->inCheck; - // Step 7. Make and search the move - pos.do_move(move, st, givesCheck); - value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); - pos.undo_move(move); + // Step 7. Make and search the move + pos.do_move(move, st, givesCheck); + value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); + pos.undo_move(move); - assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - // Step 8. Check for a new best move - if (value > bestValue) - { - bestValue = value; + // Step 8. Check for a new best move + if (value > bestValue) + { + bestValue = value; - if (value > alpha) - { - bestMove = move; + if (value > alpha) + { + bestMove = move; - if (PvNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss+1)->pv); - if (PvNode && value < beta) // Update alpha here! - alpha = value; - else - break; // Fail high - } - } + if (PvNode && value < beta) // Update alpha here! + alpha = value; + else + break; // Fail high + } + } } // Step 9. Check for mate From 7922e07af83dd472da6e5b38fb84214cfe46a630 Mon Sep 17 00:00:00 2001 From: Andreas Matthies Date: Tue, 13 Jun 2023 06:24:04 +0200 Subject: [PATCH 0109/1309] Fix for MSVC compilation. MSVC needs two more explicit casts to compile new affine_transform_sparse_input. See https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_castsi256_ps and https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castsi128_ps closes https://github.com/official-stockfish/Stockfish/pull/4616 No functional change --- AUTHORS | 1 + src/nnue/layers/affine_transform_sparse_input.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 63b862ced05..a89dc1304ad 100644 --- a/AUTHORS +++ b/AUTHORS @@ -19,6 +19,7 @@ Alexander Kure Alexander Pagel (Lolligerhans) Alfredo Menezes (lonfom169) Ali AlZhrani (Cooffe) +Andreas Matthies (Matthies) Andrei Vetrov (proukornew) Andrew Grant (AndyGrant) Andrey Neporada (nepal) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 00b17c19f12..e0c3a8a06bb 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -98,10 +98,10 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) #elif defined (USE_AVX2) using vec_t = __m256i; - #define vec_nnz(a) _mm256_movemask_ps((__m256)_mm256_cmpgt_epi32(a, _mm256_setzero_si256())) + #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) #elif defined (USE_SSSE3) using vec_t = __m128i; - #define vec_nnz(a) _mm_movemask_ps((__m128)_mm_cmpgt_epi32(a, _mm_setzero_si128())) + #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) #endif constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) From 887bbd8b3df8a01307a38bfe529a49842f810a9c Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Tue, 13 Jun 2023 22:26:20 +0100 Subject: [PATCH 0110/1309] Remove setting of static to none if in check in qsearch Small simplification Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6487924d713491385c8034ae LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 59616 W: 15885 L: 15703 D: 28028 Ptnml(0-2): 144, 6130, 17086, 6296, 152 closes https://github.com/official-stockfish/Stockfish/pull/4618 No functional change. --- AUTHORS | 1 + src/search.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index a89dc1304ad..ff224954707 100644 --- a/AUTHORS +++ b/AUTHORS @@ -215,6 +215,7 @@ tttak Unai Corzo (unaiic) Uri Blass (uriblass) Vince Negri (cuddlestmonkey) +Viren windfishballad xefoci7612 zz4032 diff --git a/src/search.cpp b/src/search.cpp index d3b5642a236..5de950eb147 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1464,7 +1464,6 @@ namespace { // Step 4. Static evaluation of the position if (ss->inCheck) { - ss->staticEval = VALUE_NONE; bestValue = futilityBase = -VALUE_INFINITE; } else From 14bfec2a981e906d1bfc08331a2e15bddd07ffe4 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Wed, 14 Jun 2023 15:57:36 +0300 Subject: [PATCH 0111/1309] Consistent bench extraction with fishtest. Consistent with recent fishtest commit https://github.com/glinscott/fishtest/commit/c0d174396f7fb1c0b3243aaa6cc73769079f3ff9 closes https://github.com/official-stockfish/Stockfish/pull/4619 No functional change --- .github/workflows/stockfish_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 8c383fe7cf8..28218402702 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -115,7 +115,7 @@ jobs: - name: Extract the bench number from the commit history run: | - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig + git log HEAD | grep -o "\b[Bb]ench[ :]\+[1-9][0-9]\{5,9\}\b" | head -n 1 | sed "s/[^0-9]//g" > git_sig [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" - name: Check compiler From 32d3284df5b2fd395504efa5319d64856902fef1 Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Tue, 13 Jun 2023 03:00:04 +0200 Subject: [PATCH 0112/1309] Permute master net weights to increase sparsity Activation data collection using https://github.com/AndrovT/Stockfish/commit/ac468039ab544b03ad9a22c859a4217729c10a77 run as bench 16 1 13 varied_1000.epd depth NNUE log.bin on FENs from https://gist.github.com/AndrovT/7eae6918eb50764227e2bafe7938953c. Permutation found using https://gist.github.com/AndrovT/359c831b7223c637e9156b01eb96949e. Uses a greedy algorithm that goes sequentially through the output positions and chooses a neuron for that position such that the number of nonzero quartets is the smallest. Net weights permuted using https://gist.github.com/AndrovT/9e3fbaebb7082734dc84d27e02094cb3. Benchmark: Result of 100 runs of 'bench 16 1 13 default depth NNUE' ======================================================== base (...kfish-master) = 885869 +/- 7395 test (./stockfish ) = 895885 +/- 7368 diff = +10016 +/- 2984 speedup = +0.0113 P(speedup > 0) = 1.0000 Passed STC: https://tests.stockfishchess.org/tests/view/648866c4713491385c804728 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 126784 W: 34003 L: 33586 D: 59195 Ptnml(0-2): 283, 13001, 36437, 13358, 313 closes https://github.com/official-stockfish/Stockfish/pull/4620 No functional change. --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 94cd42cc67a..c35b2f07bec 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-fdc1d0fe6455.nnue" + #define EvalFileDefaultName "nn-cd2ff4716c34.nnue" namespace NNUE { From 0187546275cb015d42a7d99789f2f6a650b03651 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Thu, 15 Jun 2023 21:05:01 +0800 Subject: [PATCH 0113/1309] Small cleanup This non-functional change keeps formatting consistent. closes https://github.com/official-stockfish/Stockfish/pull/4623 Bench 2370027 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5de950eb147..8ace674d11a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1463,9 +1463,7 @@ namespace { // Step 4. Static evaluation of the position if (ss->inCheck) - { bestValue = futilityBase = -VALUE_INFINITE; - } else { if (ss->ttHit) @@ -1480,11 +1478,9 @@ namespace { bestValue = ttValue; } else - { // In case of null move search use previous static eval with a different sign ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval; - } // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) From a46087ee30ace00e1edd8963ee33b0a1b87031b6 Mon Sep 17 00:00:00 2001 From: maxim Date: Tue, 13 Jun 2023 23:09:05 +0300 Subject: [PATCH 0114/1309] Compressed network parameters Implemented LEB128 (de)compression for the feature transformer. Reduces embedded network size from 70 MiB to 39 Mib. The new nn-78bacfcee510.nnue corresponds to the master net compressed. closes https://github.com/official-stockfish/Stockfish/pull/4617 No functional change --- src/evaluate.h | 2 +- src/nnue/nnue_common.h | 77 +++++++++++++++++++++++++++++ src/nnue/nnue_feature_transformer.h | 12 ++--- 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index c35b2f07bec..33effb1cfd8 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-cd2ff4716c34.nnue" + #define EvalFileDefaultName "nn-78bacfcee510.nnue" namespace NNUE { diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 12309d262b1..d338527d96b 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -57,6 +57,9 @@ namespace Stockfish::Eval::NNUE { // Size of cache line (in bytes) constexpr std::size_t CacheLineSize = 64; + constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; + constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; + // SIMD width (in bytes) #if defined(USE_AVX2) constexpr std::size_t SimdWidth = 32; @@ -159,6 +162,80 @@ namespace Stockfish::Eval::NNUE { write_little_endian(stream, values[i]); } + template + inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + auto bytes_left = read_little_endian(stream); + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) { + IntType result = 0; + size_t shift = 0; + do { + if (buf_pos == BUF_SIZE) { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; + if ((byte & 0x80) == 0) { + out[i] = sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); + } + assert(bytes_left == 0); + } + + template + inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + stream.write(Leb128MagicString, Leb128MagicStringSize); + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) { + IntType value = values[i]; + std::uint8_t byte; + do { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + write_little_endian(stream, byte_count); + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; + auto flush = [&]() { + if (buf_pos > 0) { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) flush(); + }; + for (std::size_t i = 0; i < count; ++i) { + IntType value = values[i]; + while (true) { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) { + write(byte); + break; + } + write(byte | 0x80); + } + } + flush(); + } + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index a1888c7a365..7571f398295 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -253,9 +253,9 @@ namespace Stockfish::Eval::NNUE { // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases , HalfDimensions ); - read_little_endian(stream, weights , HalfDimensions * InputDimensions); - read_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); + read_leb_128(stream, biases , HalfDimensions ); + read_leb_128(stream, weights , HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); return !stream.fail(); } @@ -263,9 +263,9 @@ namespace Stockfish::Eval::NNUE { // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases , HalfDimensions ); - write_little_endian(stream, weights , HalfDimensions * InputDimensions); - write_little_endian(stream, psqtWeights, PSQTBuckets * InputDimensions); + write_leb_128(stream, biases , HalfDimensions ); + write_leb_128(stream, weights , HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); return !stream.fail(); } From a68a1c11543eef6808181c92e0e7e5fb3f826f21 Mon Sep 17 00:00:00 2001 From: disservin Date: Tue, 13 Jun 2023 19:30:01 +0200 Subject: [PATCH 0115/1309] create prereleases upon push to master using github actions, create a prerelease for the latest commit to master. As such a development version will be available on github, in addition to the latest release. closes https://github.com/official-stockfish/Stockfish/pull/4622 No functional change --- .github/workflows/stockfish.yml | 24 +++++++++ .github/workflows/stockfish_arm_binaries.yml | 26 +++++++++ .github/workflows/stockfish_binaries.yml | 57 ++++++++++++++++++-- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 082c65def18..ca52ffe08b3 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -12,6 +12,30 @@ on: - master - tools jobs: + Prerelease: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # returns null if no pre-release exists + - name: Get Commit SHA of Latest Pre-release + run: | + # Install required packages + sudo apt-get update + sudo apt-get install -y curl jq + + echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV + + # delete old previous pre-release and tag + - uses: dev-drprasad/delete-tag-and-release@v0.2.1 + if: env.COMMIT_SHA != 'null' + with: + tag_name: ${{ env.COMMIT_SHA }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 9a4734eeec6..52105eb6ac7 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -129,4 +129,30 @@ jobs: if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' uses: softprops/action-gh-release@v1 with: + files: stockfish-android-${{ matrix.binaries }}.tar + + - name: Get last commit sha + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Make sure that an old ci which still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + continue-on-error: true + uses: softprops/action-gh-release@v1 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true files: stockfish-android-${{ matrix.binaries }}.tar \ No newline at end of file diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 86449b97a4b..0a53cb03a99 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -20,12 +20,14 @@ jobs: compiler: g++ comp: gcc shell: bash {0} + archive_ext: tar - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos compiler: clang++ comp: clang shell: bash {0} + archive_ext: tar - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 simple_name: windows @@ -35,6 +37,7 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe + archive_ext: zip binaries: - x86-64 - x86-64-modern @@ -60,7 +63,7 @@ jobs: uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.config.msys_sys }} - install: mingw-w64-${{ matrix.config.msys_env }} make git + install: mingw-w64-${{ matrix.config.msys_env }} make git zip - name: Download the used network from the fishtest framework run: make net @@ -90,7 +93,7 @@ jobs: git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki rm -rf ../wiki/.git - - name: Create tar archive. + - name: Create directory. run: | cd .. mkdir stockfish @@ -102,16 +105,64 @@ jobs: cp AUTHORS stockfish/ cp CITATION.cff stockfish/ cp README.md stockfish/ + + - name: Create tar + if: runner.os != 'Windows' + run: | + cd .. tar -cvf stockfish-$NAME-$BINARY.tar stockfish + - name: Create zip + if: runner.os == 'Windows' + run: | + cd .. + zip -r stockfish-$NAME-$BINARY.zip stockfish + - name: Upload binaries + if: runner.os != 'Windows' uses: actions/upload-artifact@v3 with: name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar + # Artifacts automatically get zipped + # to avoid double zipping, we use the unzipped directory + - name: Upload binaries + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} + path: stockfish + - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' uses: softprops/action-gh-release@v1 with: - files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + + - name: Get last commit sha + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Make sure that an old ci which still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + continue-on-error: true + uses: softprops/action-gh-release@v1 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + From 6eaa1c3ecd297404b28f9e80cddf81c4c6926a51 Mon Sep 17 00:00:00 2001 From: Joerg Oster Date: Tue, 20 Jun 2023 10:40:31 +0200 Subject: [PATCH 0116/1309] Fix indentation in qsearch. https://github.com/official-stockfish/Stockfish/pull/4630 No functional change. --- src/search.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ace674d11a..9b686a52974 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1555,23 +1555,23 @@ namespace { bestValue = std::max(bestValue, futilityBase); continue; } - } - - // We prune after the second quiet check evasion move, where being 'in check' is - // implicitly checked through the counter, and being a 'quiet move' apart from - // being a tt move is assumed after an increment because captures are pushed ahead. - if (quietCheckEvasions > 1) - break; - - // Continuation history based pruning (~3 Elo) - if ( !capture - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) - continue; + } - // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-95))) - continue; + // We prune after the second quiet check evasion move, where being 'in check' is + // implicitly checked through the counter, and being a 'quiet move' apart from + // being a tt move is assumed after an increment because captures are pushed ahead. + if (quietCheckEvasions > 1) + break; + + // Continuation history based pruning (~3 Elo) + if ( !capture + && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + continue; + + // Do not search moves with bad enough SEE values (~5 Elo) + if (!pos.see_ge(move, Value(-95))) + continue; } // Speculative prefetch as early as possible From aec8fb3fdec8309a0cc78222a4f674ea6fea9411 Mon Sep 17 00:00:00 2001 From: disservin Date: Tue, 20 Jun 2023 18:27:20 +0200 Subject: [PATCH 0117/1309] Fix failing CI of pull requests adds a guard to prevent pull requests from trying to delete the previous pre-release closing https://github.com/official-stockfish/Stockfish/pull/4631 No functional change. --- .github/workflows/stockfish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index ca52ffe08b3..99c4259ac75 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -13,6 +13,7 @@ on: - tools jobs: Prerelease: + if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From 02728736edd5915fc0abc8698635e633a3cba201 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Tue, 20 Jun 2023 09:46:02 +0200 Subject: [PATCH 0118/1309] Update top CPU contributors closes https://github.com/official-stockfish/Stockfish/pull/4629 No functional change --- Top CPU Contributors.txt | 117 ++++++++++++++++++++------------------- 1 file changed, 59 insertions(+), 58 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 7b27959071a..74c471b7404 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,55 +1,55 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2023-05-27. +Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 37304027 2833556221 -technologov 13508659 714674674 -linrock 4121386 280027751 +noobpwnftw 37457426 2850540907 +technologov 14135647 742892808 +linrock 4423514 303254809 mlang 3026000 200065824 dew 1689162 100033738 -okrout 1541122 145085726 -pemo 1481818 47546583 -grandphish2 1459364 91364265 -TueRens 1178700 69951886 -JojoM 937875 60821044 +okrout 1578136 148855886 +pemo 1508508 48814305 +grandphish2 1461406 91540343 +TueRens 1194790 70400852 +JojoM 947612 61773190 tvijlbrief 796125 51897690 +sebastronomy 742434 38218524 mibere 703840 46867607 -sebastronomy 687502 35585318 -gvreuls 645570 42437926 -oz 541224 39133532 -cw 517856 34869499 +gvreuls 651026 42988582 +oz 543438 39314736 +cw 517858 34869755 fastgm 503862 30260818 -CSU_Dynasty 464691 31166478 -leszek 460426 32840277 -ctoks 434323 28497451 +leszek 467278 33514883 +CSU_Dynasty 464940 31177118 +ctoks 434416 28506889 crunchy 427035 27344275 -maximmasiutin 424154 26534660 +maximmasiutin 424795 26577722 bcross 415722 29060963 -rpngn 344368 24218047 -velislav 342559 22138408 +olafm 395922 32268020 +rpngn 348378 24560289 +velislav 342567 22138992 Fisherman 327231 21829379 -mgrabiak 297057 20260882 +mgrabiak 300612 20608380 Dantist 296386 18031762 -nordlandia 242642 15922516 -robal 240199 15544104 +nordlandia 246201 16189678 +robal 241300 15656382 marrco 234581 17714473 ncfish1 227517 15233777 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 -olafm 192342 14968698 Thanar 179852 12365359 vdv 175544 9904472 spams 157128 10319326 sqrt2 147963 9724586 -DesolatedDodo 144759 9408038 +DesolatedDodo 146350 9536172 Calis007 143165 9478764 -vdbergh 138436 9042073 +vdbergh 138650 9064413 CoffeeOne 137100 5024116 +armo9494 136191 9460264 malala 136182 8002293 -armo9494 136010 9447548 xoto 133759 9159372 davar 129023 8376525 DMBK 122960 8980062 @@ -59,43 +59,43 @@ Data 113305 8220352 BrunoBanani 112960 7436849 CypressChess 108331 7759788 skiminki 107583 7218170 +jcAEie 105675 8238962 MaZePallas 102823 6633619 sterni1971 100532 5880772 -jcAEie 100392 7788270 sunu 100167 7040199 zeryl 99331 6221261 thirdlife 99124 2242380 ElbertoOne 99028 7023771 -cuistot 98360 6017102 +cuistot 98853 6069816 bigpen0r 94809 6529203 brabos 92118 6186135 -Wolfgang 90855 5998076 +Wolfgang 91939 6105872 psk 89957 5984901 +sschnee 88235 5268000 racerschmacer 85805 6122790 +Fifis 85722 5709729 Dubslow 84986 6042456 Vizvezdenec 83761 5344740 -sschnee 83564 4853834 0x3C33 82614 5271253 BRAVONE 81239 5054681 -Fifis 77355 5158211 nssy 76497 5259388 jromang 76106 5236025 teddybaer 75125 5407666 +tolkki963 74762 5149662 +megaman7de 74351 4940352 Wencey 74181 4711488 -megaman7de 73866 4894960 Pking_cda 73776 5293873 -tolkki963 73531 5020500 -yurikvelo 72847 4972808 +yurikvelo 73150 5004382 +markkulix 72607 5304642 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 -markkulix 70278 5068326 manap 66273 4121774 tinker 64333 4268790 qurashee 61208 3429862 -Mineta 58759 4399960 +Mineta 59357 4418202 +Spprtr 58723 3911011 AGI 58147 4325994 -Spprtr 58106 3858759 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 @@ -103,35 +103,35 @@ MarcusTullius 56746 3762951 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 +javran 53785 4627608 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 rap 49985 3219146 pb00067 49733 3298934 -javran 49178 4190632 -OuaisBla 48606 3442958 +OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 VoyagerOne 45476 3452465 -oryx 44532 3450170 -jmdana 43849 2955821 +jmdana 44893 3065205 +maposora 44597 4039578 +oryx 44570 3454238 speedycpu 43842 3003273 jbwiebe 43305 2805433 +GPUex 42378 3133332 Antihistamine 41788 2761312 mhunt 41735 2691355 -maposora 41534 3733078 -GPUex 41061 2998356 homyur 39893 2850481 gri 39871 2515779 Garf 37741 2999686 SC 37299 2731694 csnodgrass 36207 2688994 strelock 34716 2074055 +szupaw 34102 2880346 EthanOConnor 33370 2090311 slakovv 32915 2021889 Gelma 31771 1551204 gopeto 31671 2060990 -szupaw 31248 2594920 kdave 31157 2198362 manapbk 30987 1810399 Prcuvu 30377 2170122 @@ -147,52 +147,54 @@ xwziegtm 26897 2124586 achambord 26582 1767323 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25285 1689346 +Ulysses 25288 1689730 SFTUser 25182 1675689 nabildanial 24942 1519409 Sharaf_DG 24765 1786697 +Maxim 24705 1502062 rodneyc 24376 1416402 agg177 23890 1395014 +Goatminola 23763 1956036 Ente 23639 1671638 +Jopo12321 23467 1483172 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 -Goatminola 23338 1910634 cisco2015 22920 1763301 -Jopo12321 22890 1424926 +jsys14 22824 1591906 Zirie 22542 1472937 team-oh 22272 1636708 Roady 22220 1465606 MazeOfGalious 21978 1629593 sg4032 21947 1643353 -jsys14 21935 1499128 ianh2105 21725 1632562 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 user213718 21454 1404128 sphinx 21211 1384728 +AndreasKrug 21097 1634811 jjoshua2 21001 1423089 Zake9298 20938 1565848 -AndreasKrug 20911 1615673 horst.prack 20878 1465656 0xB00B1ES 20590 1208666 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 wei 19973 1745989 +notchris 19958 1800128 Serpensin 19840 1697528 +Gaster319 19712 1677310 fishtester 19617 1257388 rstoesser 19569 1293588 eudhan 19274 1283717 -Gaster319 18934 1596772 +votoanthuan 19108 1609992 vulcan 18871 1729392 Karpovbot 18766 1053178 +qoo_charly_cai 18543 1284937 jundery 18445 1115855 -votoanthuan 18012 1508836 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 -qoo_charly_cai 17494 1182667 dju 17414 981289 iisiraider 17275 1049015 DragonLord 17014 1162790 @@ -200,7 +202,6 @@ redstone59 16842 1461780 Alb11747 16787 1213926 IgorLeMasson 16064 1147232 Karby 15982 979610 -notchris 15818 1426762 scuzzi 15757 968735 ako027ako 15671 1173203 Nikolay.IT 15154 1068349 @@ -218,33 +219,33 @@ Nesa92 13786 1114691 joster 13710 946160 mbeier 13650 1044928 Hjax 13535 915487 +Nullvalue 13468 1140498 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 pirt 13100 1009897 Machariel 13010 863104 infinigon 12991 943216 -Maxim 12963 985594 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 korposzczur 12606 838168 -Nullvalue 12583 1048502 fatmurphy 12547 853210 SapphireBrand 12416 969604 Oakwen 12399 844109 deflectooor 12386 579392 modolief 12386 896470 Farseer 12249 694108 -Jackfish 12180 801372 +Jackfish 12213 805008 pgontarz 12151 848794 dbernier 12103 860824 getraideBFF 12072 1024966 stocky 11954 699440 mschmidt 11941 803401 MooTheCow 11870 773598 -FormazChar 11689 877727 +FormazChar 11766 885707 whelanh 11557 245188 +3cho 11494 1031076 infinity 11470 727027 aga 11412 695127 torbjo 11395 729145 @@ -254,6 +255,7 @@ d64 11263 789184 ali-al-zhrani 11245 779246 snicolet 11106 869170 dapper 11032 771402 +ols 10947 624903 Karmatron 10828 677458 basepi 10637 744851 Cubox 10621 826448 @@ -263,4 +265,3 @@ jojo2357 10419 929708 WoodMan777 10380 873720 Garruk 10365 706465 dzjp 10343 732529 -ols 10259 570669 From 52e84e4b4675aae52a619c309479684dc5478bf5 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 22 Jun 2023 09:59:03 +0200 Subject: [PATCH 0119/1309] Update winrate model with June data Retained 748191776 scored positions for analysis const int NormalizeToPawnValue = 328; Corresponding spread = 60; Corresponding normalized spread = 0.18337766691628035; Draw rate at 0.0 eval at move 32 = 0.9914715947898592; closes https://github.com/official-stockfish/Stockfish/pull/4636 No functional change --- src/uci.cpp | 4 ++-- src/uci.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 523d551e0c0..ed16f24c382 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -207,8 +207,8 @@ namespace { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = { 1.07390458, -6.94334517, 31.95090161, 317.75424048}; - constexpr double bs[] = { -2.82843814, 16.64518180, -19.74439200, 68.39499088 }; + constexpr double as[] = { 0.38036525, -2.82015070, 23.17882135, 307.36768407}; + constexpr double bs[] = { -2.29434733, 13.27689788, -14.26828904, 63.45318330 }; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); diff --git a/src/uci.h b/src/uci.h index 680d2d2cc8c..8f1be00c7cb 100644 --- a/src/uci.h +++ b/src/uci.h @@ -35,7 +35,7 @@ namespace UCI { // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win // from this position in selfplay at fishtest LTC time control. -const int NormalizeToPawnValue = 343; +const int NormalizeToPawnValue = 328; class Option; From 48f7c74f15f4cae4b77596cd468802054314d701 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Mon, 12 Jun 2023 12:57:41 +0300 Subject: [PATCH 0120/1309] Fix Potential in TB cutoffs for NMP. Removes the second dependency on beta and caps the return value to VALUE_TB_WIN_IN_MAX_PLY - 1 Earlier tests: STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 193632 W: 51372 L: 51326 D: 90934 Ptnml(0-2): 447, 20111, 55687, 20091, 480 https://tests.stockfishchess.org/tests/view/6486ee4465ffe077ca125bc1 LTC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 331758 W: 89538 L: 89624 D: 152596 Ptnml(0-2): 114, 30121, 105516, 29993, 135 https://tests.stockfishchess.org/tests/view/6489401af42a44347ed7be42 updated constant: LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 100260 W: 27143 L: 27017 D: 46100 Ptnml(0-2): 34, 8842, 32248, 8976, 30 https://tests.stockfishchess.org/tests/view/6492fcafdc7002ce609c818c closes: https://github.com/official-stockfish/Stockfish/pull/4632 fixes: https://github.com/official-stockfish/Stockfish/issues/4598 bench: 2370027 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9b686a52974..740ad71efee 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -801,10 +801,9 @@ namespace { if (nullValue >= beta) { // Do not return unproven mate or TB scores - if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY) - nullValue = beta; + nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1); - if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 14)) + if (thisThread->nmpMinPly || depth < 14) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed From a49b3ba7ed5d9be9151c8ceb5eed40efe3387c75 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 21 Jun 2023 00:43:46 -0400 Subject: [PATCH 0121/1309] Update default net to nn-5af11540bbfe.nnue Created by retraining the sparsified master net (nn-cd2ff4716c34.nnue) on a 100% minified dataset including Leela transformers data from T80 may2023. Weights permuted with the exact methods and code in: https://github.com/official-stockfish/Stockfish/pull/4620 LEB128 compression done with the new serialize.py param in: https://github.com/glinscott/nnue-pytorch/pull/251 Initially trained with max epoch 800. Around epoch 780, training was paused and max epoch raised to 960. python3 easy_train.py \ --experiment-name L1-1536-sparse-master-retrain \ --training-dataset /data/leela96-dfrc99-v2-T60novdecT77decT78jantosepT79aprmayT80juntonovjan-v6dd-T80febtomay2023.min.binpack \ --early-fen-skipping 27 \ --start-from-engine-test-net True \ --max_epoch 960 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --tui False \ --seed $RANDOM \ --gpus 0 For preparing the training dataset (interleaved size 328G): python3 interleave_binpacks.py \ leela96-filt-v2.min.binpack \ dfrc99-16tb7p-eval-filt-v2.min.binpack \ filt-v6-dd-min/test60-novdec2021-12tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test77-dec2021-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test78-jantomay2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test78-juntosep2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test79-apr2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test79-may2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-jun2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-jul2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-aug2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-sep2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-oct2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-nov2022-16tb7p-filter-v6-dd.min.binpack \ filt-v6-dd-min/test80-jan2023-16tb7p-filter-v6-dd.min.binpack \ test80-2023/test80-feb2023-16tb7p-no-db.min.binpack \ test80-2023/test80-mar2023-2tb7p-no-db.min.binpack \ test80-2023/test80-apr2023-2tb7p-no-db.min.binpack \ test80-2023/test80-may2023-2tb7p-no-db.min.binpack \ /data/leela96-dfrc99-v2-T60novdecT77decT78jantosepT79aprmayT80juntonovjan-v6dd-T80febtomay2023.min.binpack Minified binpacks and Leela T80 training data from 2023 available at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch879.nnue : 3.9 +/- 5.7 Passed STC: https://tests.stockfishchess.org/tests/view/64928c1bdc7002ce609c7690 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 72000 W: 19242 L: 18889 D: 33869 Ptnml(0-2): 182, 7787, 19716, 8126, 189 Passed LTC: https://tests.stockfishchess.org/tests/view/64930a37dc7002ce609c82e3 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 54552 W: 14978 L: 14647 D: 24927 Ptnml(0-2): 23, 5123, 16650, 5460, 20 closes https://github.com/official-stockfish/Stockfish/pull/4635 bench 2593605 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 33effb1cfd8..b9d7231d1e1 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-78bacfcee510.nnue" + #define EvalFileDefaultName "nn-5af11540bbfe.nnue" namespace NNUE { From 68e1e9b3811e16cad014b590d7443b9063b3eb52 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 24 Jun 2023 08:58:17 +0200 Subject: [PATCH 0122/1309] Stockfish 16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 16 Bench: 2593605 --- Stockfish 16 A new major release of Stockfish is now available at https://stockfishchess.org/download/ *Quality of chess play* Stockfish continues to demonstrate its ability to discover superior moves with remarkable speed. In self-play against Stockfish 15, this new release gains up to 50 Elo[1] and wins up to 12 times more game pairs[2] than it loses. In major chess engine tournaments, Stockfish reliably tops the rankings[3] winning the TCEC season 24 Superfinal, Swiss, Fischer Random, and Double Random Chess tournaments and the CCC 19 Bullet, 20 Blitz, and 20 Rapid competitions. Leela Chess Zero[4] was the challenger in most finals, putting top-engine chess now firmly in the hands of teams embracing free and open-source software. *Progress made* This updated version of Stockfish introduces several enhancements, including an upgraded neural net architecture (SFNNv6)[5], improved implementation, and refined parameterization. The ongoing utilization of Leela’s data combined with a novel inference approach exploiting sparsity[6], and network compression[7] ensure a speedy evaluation and modest binary sizes while allowing for more weights and higher accuracy. The search has undergone more optimization, leading to improved performance, particularly in longer analyses[8]. Additionally, the Fishtest framework has been improved and is now able to run the tests needed to validate new ideas with 10000s of CPU cores. *Usability improvements* Stockfish now comes with documentation, found in the wiki folder when downloading it or on GitHub[9]. Additionally, Stockfish now includes a clear and consistent forced tablebase win score, displaying a value of 200 minus the number of plies required to reach a tablebase win[10]. Furthermore, the UCI_Elo option, to reduce its strength, has been calibrated[11]. It is worth noting that the evaluation system remains consistent with Stockfish 15.1[12], maintaining the choice that 100cp means a 50% chance of winning the game against an equal opponent[13]. Finally, binaries of our latest development version are now provided continuously as pre-releases on GitHub making it easier for enthusiasts to download the latest and strongest version of the program[14], we thank Roman Korba for having provided a similar service for a long time. *Thank you* The success of the Stockfish project relies on the vibrant community of passionate enthusiasts (we appreciate each and every one of you!) who generously contribute their knowledge, time, and resources. Together, this dedicated community works towards the common goal of developing a powerful, freely accessible, and open-source chess engine. We invite all chess enthusiasts to join the Fishtest testing framework and contribute to the project[15]. The Stockfish team [1] https://tests.stockfishchess.org/tests/view/649409f0dc7002ce609c99cc [2] https://tests.stockfishchess.org/tests/view/649409d7dc7002ce609c99c6 [3] https://en.wikipedia.org/wiki/Stockfish_(chess)#Competition_results [4] https://lczero.org/ [5] https://github.com/official-stockfish/Stockfish/commit/c1fff71 [6] https://github.com/official-stockfish/Stockfish/commit/38e6166 [7] https://github.com/official-stockfish/Stockfish/commit/a46087e [8] https://github.com/official-stockfish/Stockfish/commit/472e726 [9] https://github.com/official-stockfish/Stockfish/wiki/ [10] https://github.com/official-stockfish/Stockfish/commit/def2966 [11] https://github.com/official-stockfish/Stockfish/commit/a08b8d4 [12] https://github.com/official-stockfish/Stockfish/commit/52e84e4 [13] https://github.com/official-stockfish/Stockfish/wiki/Stockfish-FAQ#interpretation-of-the-stockfish-evaluation [14] https://github.com/official-stockfish/Stockfish/releases?q=prerelease%3Atrue [15] https://stockfishchess.org/get-involved/ --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index f1554060d5e..bbfa40617db 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -73,7 +73,7 @@ namespace Stockfish { namespace { /// Version number or dev. -constexpr string_view version = "dev"; +constexpr string_view version = "16"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 0fd186fb28e7e1e5f2cc5ef8388115c950eaad9e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 1 Jul 2023 12:18:46 +0200 Subject: [PATCH 0123/1309] Restore development closes https://github.com/official-stockfish/Stockfish/pull/4651 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index bbfa40617db..f1554060d5e 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -73,7 +73,7 @@ namespace Stockfish { namespace { /// Version number or dev. -constexpr string_view version = "16"; +constexpr string_view version = "dev"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From ef94f77f8c827a2395f1c40f53311a3b1f20bc5b Mon Sep 17 00:00:00 2001 From: Daniel Monroe <39802758+Ergodice@users.noreply.github.com> Date: Fri, 23 Jun 2023 18:36:27 -0400 Subject: [PATCH 0124/1309] Update default net to nn-a3d1bfca1672.nnue faster permutation of master net weights Activation data taken from https://drive.google.com/drive/folders/1Ec9YuuRx4N03GPnVPoQOW70eucOKngQe?usp=sharing Permutation found using https://github.com/Ergodice/nnue-pytorch/blob/836387a0e5e690431d404158c46648710f13904d/ftperm.py See also https://github.com/glinscott/nnue-pytorch/pull/254 The algorithm greedily selects 2- and 3-cycles that can be permuted to increase the number of runs of zeroes. The percent of zero runs from the master net increased from 68.46 to 70.11 from 2-cycles and only increased to 70.32 when considering 3-cycles. Interestingly, allowing both halves of L1 to intermix when creating zero runs can give another 0.5% zero-run density increase with this method. Measured speedup: ``` CPU: 16 x AMD Ryzen 9 3950X 16-Core Processor Result of 50 runs base (./stockfish.master ) = 1561556 +/- 5439 test (./stockfish.patch ) = 1575788 +/- 5427 diff = +14231 +/- 2636 speedup = +0.0091 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/4640 No functional change --- AUTHORS | 1 + src/evaluate.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index ff224954707..2e9ae7805fd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,6 +48,7 @@ clefrks Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) +Daniel Monroe (Ergodice) Dan Schmidt (dfannius) Dariusz Orzechowski (dorzechowski) David (dav1312) diff --git a/src/evaluate.h b/src/evaluate.h index b9d7231d1e1..c3321965d52 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-5af11540bbfe.nnue" + #define EvalFileDefaultName "nn-a3d1bfca1672.nnue" namespace NNUE { From e355c7059468048bbb8b9f10e2b32606aa72eb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Tue, 27 Jun 2023 12:03:00 +0200 Subject: [PATCH 0125/1309] Document the LEB128 patch Add some comments and harmonize style for the LEB128 patch. closes https://github.com/official-stockfish/Stockfish/pull/4642 No functional change --- src/nnue/nnue_common.h | 78 +++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index d338527d96b..e8ed2bc68e7 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -86,6 +86,7 @@ namespace Stockfish::Eval::NNUE { return (n + base - 1) / base * base; } + // read_little_endian() is our utility to read an integer (signed or unsigned, any size) // from a stream in little-endian order. We swap the byte order after the read if // necessary to return a result with the byte ordering of the compiling machine. @@ -110,6 +111,7 @@ namespace Stockfish::Eval::NNUE { return result; } + // write_little_endian() is our utility to write an integer (signed or unsigned, any size) // to a stream in little-endian order. We swap the byte order before the write if // necessary to always write in little endian order, independently of the byte @@ -140,6 +142,7 @@ namespace Stockfish::Eval::NNUE { } } + // read_little_endian(s, out, N) : read integers in bulk from a little indian stream. // This reads N integers from stream s and put them in array out. template @@ -151,6 +154,7 @@ namespace Stockfish::Eval::NNUE { out[i] = read_little_endian(stream); } + // write_little_endian(s, values, N) : write integers in bulk to a little indian stream. // This takes N integers from array values and writes them on stream s. template @@ -162,77 +166,119 @@ namespace Stockfish::Eval::NNUE { write_little_endian(stream, values[i]); } + + // read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in + // the array out. The stream is assumed to be compressed using the signed LEB128 format. + // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { - static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + // Check the presence of our LEB128 magic string char leb128MagicString[Leb128MagicStringSize]; stream.read(leb128MagicString, Leb128MagicStringSize); assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + const std::uint32_t BUF_SIZE = 4096; std::uint8_t buf[BUF_SIZE]; + auto bytes_left = read_little_endian(stream); + std::uint32_t buf_pos = BUF_SIZE; - for (std::size_t i = 0; i < count; ++i) { + for (std::size_t i = 0; i < count; ++i) + { IntType result = 0; size_t shift = 0; - do { - if (buf_pos == BUF_SIZE) { + do + { + if (buf_pos == BUF_SIZE) + { stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); buf_pos = 0; } + std::uint8_t byte = buf[buf_pos++]; --bytes_left; result |= (byte & 0x7f) << shift; shift += 7; - if ((byte & 0x80) == 0) { - out[i] = sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1); + + if ((byte & 0x80) == 0) + { + out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) ? result + : result | ~((1 << shift) - 1); break; } - } while (shift < sizeof(IntType) * 8); + } + while (shift < sizeof(IntType) * 8); } + assert(bytes_left == 0); } + + // write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. + // This takes N integers from array values, compress them with the LEB128 algorithm and + // writes the result on the stream s. + // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { - static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + // Write our LEB128 magic string stream.write(Leb128MagicString, Leb128MagicStringSize); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + std::uint32_t byte_count = 0; - for (std::size_t i = 0; i < count; ++i) { + for (std::size_t i = 0; i < count; ++i) + { IntType value = values[i]; std::uint8_t byte; - do { + do + { byte = value & 0x7f; value >>= 7; ++byte_count; - } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + while ((byte & 0x40) == 0 ? value != 0 : value != -1); } + write_little_endian(stream, byte_count); + const std::uint32_t BUF_SIZE = 4096; std::uint8_t buf[BUF_SIZE]; std::uint32_t buf_pos = 0; + auto flush = [&]() { - if (buf_pos > 0) { + if (buf_pos > 0) + { stream.write(reinterpret_cast(buf), buf_pos); buf_pos = 0; } }; + auto write = [&](std::uint8_t byte) { buf[buf_pos++] = byte; - if (buf_pos == BUF_SIZE) flush(); + if (buf_pos == BUF_SIZE) + flush(); }; - for (std::size_t i = 0; i < count; ++i) { + + for (std::size_t i = 0; i < count; ++i) + { IntType value = values[i]; - while (true) { + while (true) + { std::uint8_t byte = value & 0x7f; value >>= 7; - if ((byte & 0x40) == 0 ? value == 0 : value == -1) { + if ((byte & 0x40) == 0 ? value == 0 : value == -1) + { write(byte); break; } write(byte | 0x80); } } + flush(); } From e551964ef63e4e4af4bb6132538b98fad4a51afe Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Mon, 26 Jun 2023 19:40:22 +0800 Subject: [PATCH 0126/1309] Negative extension on cutNodes based on depth This patch was inspired by candirufish original attempt at negative extensions here that failed yellow: https://tests.stockfishchess.org/tests/view/6486529065ffe077ca124f32 I tested some variations of the idea and tuned a depth condition for a modified version of it here https://tests.stockfishchess.org/tests/view/648db80a91c58631ce31fe00 after noticing abnormal scaling (ie many passed STC but not LTC) After some small tweaks I got the final version here Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 122208 W: 32776 L: 32350 D: 57082 Ptnml(0-2): 310, 13250, 33553, 13686, 305 https://tests.stockfishchess.org/tests/view/64997934dc7002ce609d01e3 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 145092 W: 39617 L: 39115 D: 66360 Ptnml(0-2): 54, 13691, 44552, 14197, 52 https://tests.stockfishchess.org/tests/view/649a1c5ddc7002ce609d0bff closes https://github.com/official-stockfish/Stockfish/pull/4644 Bench: 2637784 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 740ad71efee..fbc1755be73 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1097,6 +1097,10 @@ namespace { else if (ttValue >= beta) extension = -2 - !PvNode; + // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) + else if (cutNode) + extension = depth > 8 && depth < 17 ? -3 : -1; + // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; From 915532181f11812c80ef0b57bc018de4ea2155ec Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 25 Jun 2023 17:44:28 -0400 Subject: [PATCH 0127/1309] Update NNUE architecture to SFNNv7 with larger L1 size of 2048 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating this net involved: - a 5-step training process from scratch - greedy permuting L1 weights with https://github.com/official-stockfish/Stockfish/pull/4620 - leb128 compression with https://github.com/glinscott/nnue-pytorch/pull/251 - greedy 2- and 3- cycle permuting with https://github.com/official-stockfish/Stockfish/pull/4640 The 5 training steps were: 1. 400 epochs, lambda 1.0, lr 9.75e-4 UHOx2-wIsRight-multinet-dfrc-n5000-largeGensfen-d9.binpack (178G) nodes5000pv2_UHO.binpack data_pv-2_diff-100_nodes-5000.binpack wrongIsRight_nodes5000pv2.binpack multinet_pv-2_diff-100_nodes-5000.binpack dfrc_n5000.binpack large_gensfen_multipvdiff_100_d9.binpack ep399 chosen as start model for step2 2. 800 epochs, end-lambda 0.75, skip 16 LeelaFarseer-T78juntoaugT79marT80dec.binpack (141G) T60T70wIsRightFarseerT60T74T75T76.binpack test78-junjulaug2022-16tb7p.no-db.min.binpack test79-mar2022-16tb7p.no-db.min.binpack test80-dec2022-16tb7p.no-db.min.binpack ep559 chosen as start model for step3 3. 800 epochs, end-lambda 0.725, skip 20 leela96-dfrc99-v2-T80dectofeb-sk20-mar-v6-T77decT78janfebT79apr.binpack (223G) leela96-filt-v2.min.binpack dfrc99-16tb7p-eval-filt-v2.min.binpack test80-dec2022-16tb7p-filter-v6-sk20.min-mar2023.binpack test80-jan2023-16tb7p-filter-v6-sk20.min-mar2023.binpack test80-feb2023-16tb7p-filter-v6-sk20.min-mar2023.binpack test80-mar2023-2tb7p-filter-v6.min.binpack test77-dec2021-16tb7p.no-db.min.binpack test78-janfeb2022-16tb7p.no-db.min.binpack test79-apr2022-16tb7p.no-db.min.binpack ep499 chosen as start model for step4 4. 800 epochs, end-lambda 0.7, skip 24 0dd1cebea57 dataset https://github.com/official-stockfish/Stockfish/pull/4606 ep599 chosen as start model for step5 5. 800 epochs, end-lambda 0.7, skip 28 same dataset as step4 ep619 became nn-1b951f8b449d.nnue For the final step5 training: python3 easy_train.py \ --experiment-name L1-2048-S5-sameData-sk28-S4-0dd1cebea57-shuffled-S3-leela96-dfrc99-v2-T80dectofeb-sk20-mar-v6-T77decT78janfebT79apr-sk20-S2-LeelaFarseerT78T79T80-ep399-S1-UHOx2-wIsRight-multinet-dfrc-n5000-largeGensfen-d9 \ --training-dataset /data/leela96-dfrc99-T60novdec-v2-T80juntonovjanfebT79aprmayT78jantosepT77dec-v6dd-T80apr.binpack \ --early-fen-skipping 28 \ --nnue-pytorch-branch linrock/nnue-pytorch/misc-fixes-L1-2048 \ --engine-test-branch linrock/Stockfish/L1-2048 \ --start-from-engine-test-net False \ --start-from-model /data/experiments/experiment_L1-2048-S4-0dd1cebea57-shuffled-S3-leela96-dfrc99-v2-T80dectofeb-sk20-mar-v6-T77decT78janfebT79apr-sk20-S2-LeelaFarseerT78T79T80-ep399-S1-UHOx2-wIsRight-multinet-dfrc-n5000-largeGensfen-d9/training/run_0/nn-epoch599.nnue --max_epoch 800 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --tui False \ --seed $RANDOM \ --gpus 0 SF training data components for the step1 dataset: https://drive.google.com/drive/folders/1yLCEmioC3Xx9KQr4T7uB6GnLm5icAYGU Leela training data for steps 2-5 can be found at: https://robotmoon.com/nnue-training-data/ Due to larger L1 size and slower inference, the speed penalty loses elo at STC. Measurements from 100 bench runs at depth 13 with x86-64-modern on Intel Core i5-1038NG7 2.00GHz: sf_base = 1240730 +/- 3443 (95%) sf_test = 1153341 +/- 2832 (95%) diff = -87388 +/- 1616 (95%) speedup = -7.04330% +/- 0.130% (95%) Local elo at 25k nodes per move (vs. L1-1536 nn-fdc1d0fe6455.nnue): nn-epoch619.nnue : 21.1 +/- 3.2 Failed STC: https://tests.stockfishchess.org/tests/view/6498ee93dc7002ce609cf979 LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 11680 W: 3058 L: 3299 D: 5323 Ptnml(0-2): 44, 1422, 3149, 1181, 44 LTC: https://tests.stockfishchess.org/tests/view/649b32f5dc7002ce609d20cf Elo: 0.68 ± 1.5 (95%) LOS: 80.5% Total: 40000 W: 10887 L: 10809 D: 18304 Ptnml(0-2): 36, 3938, 11958, 4048, 20 nElo: 1.50 ± 3.4 (95%) PairsRatio: 1.02 Passed VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/64992b43dc7002ce609cfd20 LLR: 3.06 (-2.94,2.94) <0.00,2.00> Total: 38086 W: 10612 L: 10338 D: 17136 Ptnml(0-2): 9, 3316, 12115, 3598, 5 Passed VLTC SMP 60+0.6 th 8: https://tests.stockfishchess.org/tests/view/649a21fedc7002ce609d0c7d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 38936 W: 11091 L: 10820 D: 17025 Ptnml(0-2): 1, 2948, 13305, 3207, 7 closes https://github.com/official-stockfish/Stockfish/pull/4646 Bench: 2505168 --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index c3321965d52..a1d46111938 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-a3d1bfca1672.nnue" + #define EvalFileDefaultName "nn-1b951f8b449d.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 413dbb3dcd7..65319b14bde 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -40,7 +40,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 1536; +constexpr IndexType TransformedFeatureDimensions = 2048; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; From 9a2d50ecccfc737249245280586924ee3ef53abb Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Sat, 1 Jul 2023 14:36:52 +0200 Subject: [PATCH 0128/1309] Make posix and msys2 shells consistent in CI In CI, it is typical for the process to halt immediately when an error is encountered. However, with our `shell: bash {0}` configuration, the process continues despite errors for posix shells. This commit updates the behavior of posix and msys2 shells to ensure consistency in terms of pipeline exit codes and stop conditions. We adopt the most appropriate default behavior as recommended by the GitHub documentation. Update the code that searches for the bench value in the git log: - to be compatible with the new shell settings - to retry the value from the first line that contains only the template and spaces/tabs/newlines see also https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference https://github.com/msys2/setup-msys2/blob/main/main.js closes https://github.com/official-stockfish/Stockfish/pull/4653 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 4 ++-- .github/workflows/stockfish_binaries.yml | 4 ++-- .github/workflows/stockfish_compile_test.yml | 8 ++++---- .github/workflows/stockfish_sanitizers.yml | 2 +- .github/workflows/stockfish_test.yml | 16 ++++++++-------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 52105eb6ac7..1afd8efa7a4 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -20,13 +20,13 @@ jobs: compiler: aarch64-linux-android21-clang++ emu: qemu-aarch64 comp: ndk - shell: bash {0} + shell: bash - name: Android NDK arm os: ubuntu-22.04 compiler: armv7a-linux-androideabi21-clang++ emu: qemu-arm comp: ndk - shell: bash {0} + shell: bash binaries: - armv8 - armv7 diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 0a53cb03a99..5fe67d151d6 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -19,14 +19,14 @@ jobs: simple_name: ubuntu compiler: g++ comp: gcc - shell: bash {0} + shell: bash archive_ext: tar - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos compiler: clang++ comp: clang - shell: bash {0} + shell: bash archive_ext: tar - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index c7280a85537..41f61daba8a 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -15,22 +15,22 @@ jobs: os: ubuntu-20.04 compiler: g++ comp: gcc - shell: bash {0} + shell: bash - name: Ubuntu 20.04 Clang os: ubuntu-20.04 compiler: clang++ comp: clang - shell: bash {0} + shell: bash - name: MacOS 12 Apple Clang os: macos-12 compiler: clang++ comp: clang - shell: bash {0} + shell: bash - name: MacOS 12 GCC 11 os: macos-12 compiler: g++-11 comp: gcc - shell: bash {0} + shell: bash - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 compiler: g++ diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index 708c92275bf..ebfd809c295 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -16,7 +16,7 @@ jobs: os: ubuntu-20.04 compiler: g++ comp: gcc - shell: bash {0} + shell: bash sanitizers: - name: Run with thread sanitizer make_option: sanitize=thread diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 28218402702..8a71d76b8ea 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -18,38 +18,38 @@ jobs: comp: gcc run_32bit_tests: true run_64bit_tests: true - shell: bash {0} + shell: bash - name: Ubuntu 20.04 Clang os: ubuntu-20.04 compiler: clang++ comp: clang run_32bit_tests: true run_64bit_tests: true - shell: bash {0} + shell: bash - name: Android NDK aarch64 os: ubuntu-22.04 compiler: aarch64-linux-android21-clang++ comp: ndk run_armv8_tests: true - shell: bash {0} + shell: bash - name: Android NDK arm os: ubuntu-22.04 compiler: armv7a-linux-androideabi21-clang++ comp: ndk run_armv7_tests: true - shell: bash {0} + shell: bash - name: MacOS 12 Apple Clang os: macos-12 compiler: clang++ comp: clang run_64bit_tests: true - shell: bash {0} + shell: bash - name: MacOS 12 GCC 11 os: macos-12 compiler: g++-11 comp: gcc run_64bit_tests: true - shell: bash {0} + shell: bash - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 compiler: g++ @@ -115,8 +115,8 @@ jobs: - name: Extract the bench number from the commit history run: | - git log HEAD | grep -o "\b[Bb]ench[ :]\+[1-9][0-9]\{5,9\}\b" | head -n 1 | sed "s/[^0-9]//g" > git_sig - [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" + benchref=$(git log HEAD | grep -m 1 -o -x "[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*" | sed "s/[^0-9]//g") || true + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "Reference bench: $benchref" || echo "No bench found" - name: Check compiler run: | From 8634717c6457f2b5fb0127cfb81c18505ff0072c Mon Sep 17 00:00:00 2001 From: disservin <45608332+Disservin@users.noreply.github.com> Date: Mon, 3 Jul 2023 08:20:56 +0200 Subject: [PATCH 0129/1309] Add bmi2 to CI generated binaries verify bench for avx2 and bmi2 as well closes https://github.com/official-stockfish/Stockfish/pull/4658 No functional change --- .github/workflows/stockfish_binaries.yml | 6 ++-- .github/workflows/stockfish_test.yml | 36 ++++++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 5fe67d151d6..f7669b479aa 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -42,9 +42,12 @@ jobs: - x86-64 - x86-64-modern - x86-64-avx2 + - x86-64-bmi2 exclude: - binaries: x86-64-avx2 - config: {os: macos-12} + config: { os: macos-12 } + - binaries: x86-64-bmi2 + config: { os: macos-12 } defaults: run: working-directory: src @@ -165,4 +168,3 @@ jobs: tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} prerelease: true files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} - diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 8a71d76b8ea..9d6bc20cf5f 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -134,7 +134,7 @@ jobs: # x86-32 tests - name: Test debug x86-32 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean @@ -142,28 +142,28 @@ jobs: ../tests/signature.sh $benchref - name: Test x86-32 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=x86-32 build ../tests/signature.sh $benchref - name: Test x86-32-sse41-popcnt build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=x86-32-sse41-popcnt build ../tests/signature.sh $benchref - name: Test x86-32-sse2 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=x86-32-sse2 build ../tests/signature.sh $benchref - name: Test general-32 build - if: ${{ matrix.config.run_32bit_tests }} + if: matrix.config.run_32bit_tests run: | make clean make -j2 ARCH=general-32 build @@ -172,36 +172,50 @@ jobs: # x86-64 tests - name: Test debug x86-64-modern build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean make -j2 ARCH=x86-64-modern optimize=no debug=yes build ../tests/signature.sh $benchref + - name: Test x86-64-bmi2 build + if: matrix.config.run_64bit_tests && runner.os != 'macOS' + run: | + make clean + make -j2 ARCH=x86-64-bmi2 build + ../tests/signature.sh $benchref + + - name: Test x86-64-avx2 build + if: matrix.config.run_64bit_tests && runner.os != 'macOS' + run: | + make clean + make -j2 ARCH=x86-64-avx2 build + ../tests/signature.sh $benchref + - name: Test x86-64-modern build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-modern build ../tests/signature.sh $benchref - name: Test x86-64-ssse3 build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-ssse3 build ../tests/signature.sh $benchref - name: Test x86-64-sse3-popcnt build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-sse3-popcnt build ../tests/signature.sh $benchref - name: Test x86-64 build - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64 build @@ -248,7 +262,7 @@ jobs: # Other tests - name: Check perft and search reproducibility - if: ${{ matrix.config.run_64bit_tests }} + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-modern build From 80564bcfcd3523c2a61e7a2c4bee36d4aada49d1 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 2 Jul 2023 19:48:18 -0700 Subject: [PATCH 0130/1309] Simplify lookup_count and clean up pieces(). https://github.com/official-stockfish/Stockfish/pull/4656 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 12 ++---------- src/position.h | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index e0c3a8a06bb..18c166cd9d7 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "../nnue_common.h" #include "affine_transform.h" @@ -77,16 +78,7 @@ namespace Stockfish::Eval::NNUE::Layers { alignas(CacheLineSize) static inline const std::array lookup_count = [](){ std::array v; for (int i = 0; i < 256; ++i) - { - int j = i; - int k = 0; - while(j) - { - j &= j - 1; - ++k; - } - v[i] = k; - } + v[i] = unsigned(std::bitset<8>(i).count()); return v; }(); diff --git a/src/position.h b/src/position.h index 2e6014dbe6e..7d4b3771912 100644 --- a/src/position.h +++ b/src/position.h @@ -91,7 +91,7 @@ class Position { std::string fen() const; // Position representation - Bitboard pieces(PieceType pt) const; + Bitboard pieces(PieceType pt = ALL_PIECES) const; template Bitboard pieces(PieceType pt, PieceTypes... pts) const; Bitboard pieces(Color c) const; template Bitboard pieces(Color c, PieceTypes... pts) const; @@ -224,7 +224,7 @@ inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); } -inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const { +inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } From fa143922aecaab6f22fe818a5ef23b6ac42fe307 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Tue, 27 Jun 2023 06:07:20 +0300 Subject: [PATCH 0131/1309] Fix pruning to (in TB loss) in Null move pruning. Current logic can apply Null move pruning on a dead-lost position returning an unproven loss (i.e. in TB loss score or mated in losing score) on nonPv nodes. on a default bench, this can be observed by adding this debugging line: ``` if (nullValue >= beta) { // Do not return unproven mate or TB scores nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1); dbg_hit_on(nullValue <= VALUE_TB_LOSS_IN_MAX_PLY); // Hit #0: Total 73983 Hits 1 Hit Rate (%) 0.00135166 if (thisThread->nmpMinPly || depth < 14) return nullValue; ``` This fixes this very rare issue (happens at ~0.00135166% of the time) by eliminating the need to try Null Move Pruning with dead-lost positions and leaving it to be determined by a normal searching flow. The previous try to fix was not as safe enough because it was capping the returned value to (out of TB range) thus reviving the dead-lost position based on an artificial clamp (i.e. the in TB score/mate score can be lost on that nonPv node): https://tests.stockfishchess.org/tests/view/649756d5dc7002ce609cd794 Final fix: Passed STC: https://tests.stockfishchess.org/tests/view/649a5446dc7002ce609d1049 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 577280 W: 153613 L: 153965 D: 269702 Ptnml(0-2): 1320, 60594, 165190, 60190, 1346 Passed LTC: https://tests.stockfishchess.org/tests/view/649cd048dc7002ce609d4801 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 246432 W: 66769 L: 66778 D: 112885 Ptnml(0-2): 83, 22105, 78847, 22100, 81 closes https://github.com/official-stockfish/Stockfish/pull/4649 Bench: 2425978 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fbc1755be73..8bd5ec9b48b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -782,7 +782,8 @@ namespace { && ss->staticEval >= beta - 21 * depth - improvement / 13 + 258 && !excludedMove && pos.non_pawn_material(us) - && (ss->ply >= thisThread->nmpMinPly)) + && ss->ply >= thisThread->nmpMinPly + && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); From eb9aaf94891f17a62798c59226642fa172972204 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 28 Jun 2023 18:36:11 +0800 Subject: [PATCH 0132/1309] Simplify away improvement term in null move search passed STC: https://tests.stockfishchess.org/tests/view/649c0d2edc7002ce609d33b5 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 271104 W: 72181 L: 72217 D: 126706 Ptnml(0-2): 691, 30042, 74129, 29992, 698 passed LTC: https://tests.stockfishchess.org/tests/view/649d0dd7dc7002ce609d4efa LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 183120 W: 49469 L: 49418 D: 84233 Ptnml(0-2): 84, 17636, 56063, 17699, 78 closes https://github.com/official-stockfish/Stockfish/pull/4650 Bench: 2642851 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 8bd5ec9b48b..656558f87c4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -779,7 +779,7 @@ namespace { && (ss-1)->statScore < 17329 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 21 * depth - improvement / 13 + 258 + && ss->staticEval >= beta - 21 * depth + 258 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly From 5f8480a730cbc789d230dd28f276b8d35ce0a8a4 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Thu, 22 Jun 2023 19:19:21 +0800 Subject: [PATCH 0133/1309] Simplify score improvement reduction Reduce depth by 2 based on score improvement, only for depths 3 to 11. Simplification STC: https://tests.stockfishchess.org/tests/view/64929a53dc7002ce609c7807 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 238912 W: 63466 L: 63468 D: 111978 Ptnml(0-2): 564, 26262, 65805, 26262, 563 Simplification LTC: https://tests.stockfishchess.org/tests/view/64942e47dc7002ce609c9e07 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 64452 W: 17485 L: 17320 D: 29647 Ptnml(0-2): 19, 6161, 19706, 6316, 24 closes https://github.com/official-stockfish/Stockfish/pull/4637 Bench: 2740142 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 656558f87c4..ed2b5743e6e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1323,12 +1323,12 @@ namespace { } else { - // Reduce other moves if we have found at least one score improvement (~1 Elo) - // Reduce more for depth > 3 and depth < 12 (~1 Elo) - if ( depth > 1 + // Reduce other moves if we have found at least one score improvement (~2 Elo) + if ( depth > 2 + && depth < 12 && beta < 14362 && value > -12393) - depth -= depth > 3 && depth < 12 ? 2 : 1; + depth -= 2; assert(depth > 0); alpha = value; // Update alpha! Always alpha < beta From 9cd563cb54b4091c48e16b524b3c9c15b7824c4f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 27 Jun 2023 15:13:59 +0300 Subject: [PATCH 0134/1309] Improving grammar and readability of comments closes https://github.com/official-stockfish/Stockfish/pull/4643 No functional change --- src/search.cpp | 62 +++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ed2b5743e6e..76d055e3039 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -67,7 +67,7 @@ namespace { return Value(140 * (d - improving)); } - // Reductions lookup table, initialized at startup + // Reductions lookup table initialized at startup int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { @@ -92,7 +92,7 @@ namespace { // Skill structure is used to implement strength limit. If we have an uci_elo then // we convert it to a suitable fractional skill level using anchoring to CCRL Elo - // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for match (TC 60+0.6) + // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for a match (TC 60+0.6) // results spanning a wide range of k values. struct Skill { Skill(int skill_level, int uci_elo) { @@ -304,7 +304,7 @@ void Thread::search() { Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); // When playing with strength handicap enable MultiPV search that we will - // use behind the scenes to retrieve a set of possible moves. + // use behind-the-scenes to retrieve a set of possible moves. if (skill.enabled()) multiPV = std::max(multiPV, (size_t)4); @@ -321,7 +321,7 @@ void Thread::search() { if (mainThread) totBestMoveChanges /= 2; - // Save the last iteration's scores before first PV line is searched and + // Save the last iteration's scores before the first PV line is searched and // all the move scores except the (new) PV are set to -VALUE_INFINITE. for (RootMove& rm : rootMoves) rm.previousScore = rm.score; @@ -363,16 +363,16 @@ void Thread::search() { int failedHighCnt = 0; while (true) { - // Adjust the effective depth searched, but ensuring at least one effective increment for every + // Adjust the effective depth searched, but ensure at least one effective increment for every // four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the - // first and eventually the new best one are set to -VALUE_INFINITE + // first and eventually the new best one is set to -VALUE_INFINITE // and we want to keep the same order for all the moves except the - // new PV that goes to the front. Note that in case of MultiPV + // new PV that goes to the front. Note that in the case of MultiPV // search the already searched PV lines are preserved. std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); @@ -440,7 +440,7 @@ void Thread::search() { if (!mainThread) continue; - // If skill level is enabled and time is up, pick a sub-optimal best move + // If the skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(multiPV); @@ -498,7 +498,7 @@ void Thread::search() { mainThread->previousTimeReduction = timeReduction; - // If skill level is enabled, swap best PV line with the sub-optimal one + // If the skill level is enabled, swap the best PV line with the sub-optimal one if (skill.enabled()) std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), skill.best ? skill.best : skill.pick_best(multiPV))); @@ -515,7 +515,7 @@ namespace { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; - // Check if we have an upcoming move which draws by repetition, or + // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( !rootNode && pos.rule50_count() >= 3 @@ -580,8 +580,8 @@ namespace { // would be at best mate_in(ss->ply+1), but if alpha is already bigger because // a shorter mate was found upward in the tree then there is no need to search // because we will never beat the current alpha. Same logic but with reversed - // signs applies also in the opposite condition of being mated instead of giving - // mate. In this case return a fail-high score. + // signs apply also in the opposite condition of being mated instead of giving + // mate. In this case, return a fail-high score. alpha = std::max(mated_in(ss->ply), alpha); beta = std::min(mate_in(ss->ply+1), beta); if (alpha >= beta) @@ -734,7 +734,7 @@ namespace { else { ss->staticEval = eval = evaluate(pos); - // Save static evaluation into transposition table + // Save static evaluation into the transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } @@ -845,10 +845,10 @@ namespace { if ( !PvNode && depth > 3 && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - // if value from transposition table is lower than probCutBeta, don't attempt probCut + // If value from transposition table is lower than probCutBeta, don't attempt 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 + // So effective depth is equal to depth - 3 && !( tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) @@ -920,7 +920,7 @@ namespace { moveCountPruning = singularQuietLMR = false; // Indicate PvNodes that will probably fail low if the node was searched - // at a depth equal or greater than the current depth, and the result of this search was a fail low. + // at a depth equal to or greater than the current depth, and the result of this search was a fail low. bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) @@ -936,8 +936,8 @@ namespace { continue; // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence any illegal move is also skipped. In MultiPV - // mode we also skip PV moves which have been already searched and those + // Move List. As a consequence, any illegal move is also skipped. In MultiPV + // mode we also skip PV moves that have been already searched and those // of lower "TB rank" if we are in a TB root position. if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, thisThread->rootMoves.begin() + thisThread->pvLast, move)) @@ -1005,7 +1005,7 @@ namespace { { Square sq = pop_lsb(leftEnemies); attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // don't consider pieces which were already threatened/hanging before SEE exchanges + // Don't consider pieces that were already threatened/hanging before SEE exchanges if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) attacks = 0; } @@ -1090,7 +1090,7 @@ namespace { // Our ttMove is assumed to fail high, and now we failed high also on a reduced // search without the ttMove. So we assume this expected Cut-node is not singular, // that multiple moves fail high, and we can prune the whole subtree by returning - // a soft bound. + // a softbound. else if (singularBeta >= beta) return singularBeta; @@ -1186,7 +1186,7 @@ namespace { // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has - // been searched. In general we would like to reduce them, but there are many + // been searched. In general, we would like to reduce them, but there are many // cases where we extend a son if it has good chances to be "interesting". if ( depth >= 2 && moveCount > 1 + (PvNode && ss->ply <= 1) @@ -1201,10 +1201,10 @@ namespace { value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); - // Do full depth search when reduced LMR search fails high + // Do a full-depth search when reduced LMR search fails high if (value > alpha && d < newDepth) { - // Adjust full depth search based on LMR results - if result + // 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 + 64 + 11 * (newDepth - d)); const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6; @@ -1225,7 +1225,7 @@ namespace { } } - // Step 18. Full depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. + // Step 18. Full-depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. else if (!PvNode || moveCount > 1) { // Increase reduction for cut nodes and not ttMove (~1 Elo) @@ -1298,7 +1298,7 @@ namespace { ++thisThread->bestMoveChanges; } else - // All other moves but the PV are set to the lowest value: this + // All other moves but the PV, are set to the lowest value: this // is not a problem when sorting because the sort is stable and the // move position in the list is preserved - just the PV is pushed up. rm.score = -VALUE_INFINITE; @@ -1337,7 +1337,7 @@ namespace { } - // If the move is worse than some previously searched move, remember it to update its stats later + // If the move is worse than some previously searched move, remember it, to update its stats later if (move != bestMove) { if (capture && captureCount < 32) @@ -1349,7 +1349,7 @@ namespace { } // The following condition would detect a stop only after move loop has been - // completed. But in this case bestValue is valid because we have fully + // completed. But in this case, bestValue is valid because we have fully // searched our subtree, and we can anyhow save the result in TT. /* if (Threads.stop) @@ -1368,7 +1368,7 @@ namespace { ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; - // If there is a move which produces search value greater than alpha we update stats of searched moves + // If there is a move that produces search value greater than alpha we update the stats of searched moves else if (bestMove) update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, capturesSearched, captureCount, depth); @@ -1751,7 +1751,7 @@ namespace { for (int i : {1, 2, 4, 6}) { - // Only update first 2 continuation histories if we are in check + // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (is_ok((ss-i)->currentMove)) @@ -1784,7 +1784,7 @@ namespace { } } - // When playing with strength handicap, choose best move among a set of RootMoves + // When playing with strength handicap, choose the best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. Move Skill::pick_best(size_t multiPV) { @@ -1915,7 +1915,7 @@ string UCI::pv(const Position& pos, Depth depth) { /// RootMove::extract_ponder_from_tt() is called in case we have no ponder move /// before exiting the search, for instance, in case we stop the search during a /// fail high at root. We try hard to have a ponder move to return to the GUI, -/// otherwise in case of 'ponder on' we have nothing to think on. +/// otherwise in case of 'ponder on' we have nothing to think about. bool RootMove::extract_ponder_from_tt(Position& pos) { From 95ce443aaacadea777f34d87b0abf984e724f0dd Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Fri, 16 Jun 2023 18:49:31 +0200 Subject: [PATCH 0135/1309] simplified gives check castling tested verifying perft and bench is unchanged on a larger set of epds for both standard and FRC chess. Passed non-regression STC: https://tests.stockfishchess.org/tests/live_elo/648587be65ffe077ca123d78 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 153632 W: 41015 L: 40928 D: 71689 Ptnml(0-2): 377, 16077, 43816, 16174, 372 closes https://github.com/official-stockfish/Stockfish/pull/4628 No functional change --- AUTHORS | 1 + src/position.cpp | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2e9ae7805fd..792893944c4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -179,6 +179,7 @@ Raminder Singh renouve Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) +rn5f107s2 Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) Ronald de Man (syzygy1, syzygy) diff --git a/src/position.cpp b/src/position.cpp index 2a9d798ff7d..a052cf32f03 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -638,9 +638,9 @@ bool Position::gives_check(Move m) const { return true; // Is there a discovered check? - if ( (blockers_for_king(~sideToMove) & from) - && !aligned(from, to, square(~sideToMove))) - return true; + if (blockers_for_king(~sideToMove) & from) + return !aligned(from, to, square(~sideToMove)) + || type_of(m) == CASTLING; switch (type_of(m)) { @@ -665,11 +665,9 @@ bool Position::gives_check(Move m) const { default: //CASTLING { // Castling is encoded as 'king captures the rook' - Square ksq = square(~sideToMove); Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); - return (attacks_bb(rto) & ksq) - && (attacks_bb(rto, pieces() ^ from ^ to) & ksq); + return check_squares(ROOK) & rto; } } } From ca5d9a5ff0739a63f7b0a184193dfb9de3c57156 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Fri, 16 Jun 2023 13:01:20 +0200 Subject: [PATCH 0136/1309] Extract bench according to wiki instructions - loop through the commits starting from the latest one - read the bench value from the last match, if any, of the template in the commit body text closes https://github.com/official-stockfish/Stockfish/pull/4627 No functional change --- .github/workflows/stockfish_test.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 9d6bc20cf5f..1ea4b309235 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -103,6 +103,10 @@ jobs: echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV fi + - name: Download required macOS packages + if: runner.os == 'macOS' + run: brew install coreutils + - name: Setup msys and install required packages if: runner.os == 'Windows' uses: msys2/setup-msys2@v2 @@ -115,8 +119,10 @@ jobs: - name: Extract the bench number from the commit history run: | - benchref=$(git log HEAD | grep -m 1 -o -x "[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*" | sed "s/[^0-9]//g") || true - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "Reference bench: $benchref" || echo "No bench found" + for ((n=0; n<100; n++)); do + benchref=$(git log HEAD~$n -1 | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $(git rev-parse HEAD~$n)" && echo "Reference bench: $benchref" || echo "No bench found" - name: Check compiler run: | From e87e103ca994570d42f30f61f923986656a5df14 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Mon, 3 Jul 2023 19:41:13 +0200 Subject: [PATCH 0137/1309] Remove leftover braces for if conditional in CI closes https://github.com/official-stockfish/Stockfish/pull/4660 No functional change --- .github/workflows/stockfish_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 1ea4b309235..b53d7e275d0 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -237,7 +237,7 @@ jobs: # armv8 tests - name: Test armv8 build - if: ${{ matrix.config.run_armv8_tests }} + if: matrix.config.run_armv8_tests run: | export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" @@ -248,7 +248,7 @@ jobs: # armv7 tests - name: Test armv7 build - if: ${{ matrix.config.run_armv7_tests }} + if: matrix.config.run_armv7_tests run: | export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" @@ -257,7 +257,7 @@ jobs: ../tests/signature.sh $benchref - name: Test armv7-neon build - if: ${{ matrix.config.run_armv7_tests }} + if: matrix.config.run_armv7_tests run: | export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" From 19e2a8850483c67835c0829ef016c6ede988817b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 6 Jul 2023 18:30:51 +0200 Subject: [PATCH 0138/1309] Revise extract bench from git log in CI order commits differently closes https://github.com/official-stockfish/Stockfish/pull/4668 No functional change --- .github/workflows/stockfish_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index b53d7e275d0..05592dae530 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -119,8 +119,8 @@ jobs: - name: Extract the bench number from the commit history run: | - for ((n=0; n<100; n++)); do - benchref=$(git log HEAD~$n -1 | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true done [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $(git rev-parse HEAD~$n)" && echo "Reference bench: $benchref" || echo "No bench found" From f8e65d82ebf5754427a63116532733b7b7002f29 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 3 Jul 2023 12:59:42 -0700 Subject: [PATCH 0139/1309] Simplify away lookup_count. https://tests.stockfishchess.org/tests/view/64a3c1a93ee09aa549c53167 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 32832 W: 8497 L: 8280 D: 16055 Ptnml(0-2): 80, 3544, 8967, 3729, 96 closes https://github.com/official-stockfish/Stockfish/pull/4662 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 18c166cd9d7..3c7defcc42c 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -24,7 +24,6 @@ #include #include #include -#include #include #include "../nnue_common.h" #include "affine_transform.h" @@ -75,12 +74,6 @@ namespace Stockfish::Eval::NNUE::Layers { } return v; }(); - alignas(CacheLineSize) static inline const std::array lookup_count = [](){ - std::array v; - for (int i = 0; i < 256; ++i) - v[i] = unsigned(std::bitset<8>(i).count()); - return v; - }(); // Find indices of nonzero numbers in an int32_t array template @@ -120,7 +113,7 @@ namespace Stockfish::Eval::NNUE::Layers { const auto lookup = (nnz >> (j * 8)) & 0xFF; const auto offsets = _mm_loadu_si128(reinterpret_cast(&lookup_indices[lookup])); _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets)); - count += lookup_count[lookup]; + count += popcount(lookup); base = _mm_add_epi16(base, increment); } } From 9ba24912c1bac753fdbde0ae78e19867dccb7500 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 5 Jul 2023 20:15:49 +0200 Subject: [PATCH 0140/1309] Add armv8-dotprod to CI binaries also generate binaries for more recent Android hardware. closes https://github.com/official-stockfish/Stockfish/pull/4663 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 1afd8efa7a4..4db216eb5fb 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -28,10 +28,13 @@ jobs: comp: ndk shell: bash binaries: + - armv8-dotprod - armv8 - armv7 - armv7-neon exclude: + - binaries: armv8-dotprod + config: {compiler: armv7a-linux-androideabi21-clang++} - binaries: armv8 config: {compiler: armv7a-linux-androideabi21-clang++} - binaries: armv7 @@ -155,4 +158,4 @@ jobs: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} prerelease: true - files: stockfish-android-${{ matrix.binaries }}.tar \ No newline at end of file + files: stockfish-android-${{ matrix.binaries }}.tar From e699fee513ce26b3794ac43d08826c89106e10ea Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 25 Jun 2023 20:57:50 -0400 Subject: [PATCH 0141/1309] Update default net to nn-c38c3d8d3920.nnue This was a later epoch from the same experiment that led to the previous master net. After training, it was prepared the same way: 1. greedy permuting L1 weights with https://github.com/official-stockfish/Stockfish/pull/4620 2. leb128 compression with https://github.com/glinscott/nnue-pytorch/pull/251 3. greedy 2- and 3- cycle permuting with https://github.com/official-stockfish/Stockfish/pull/4640 Local elo at 25k nodes per move (vs. L1-1536 nn-fdc1d0fe6455.nnue): nn-epoch739.nnue : 20.2 +/- 1.7 Passed STC: https://tests.stockfishchess.org/tests/view/64a050b33ee09aa549c4e4c8 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 195552 W: 49977 L: 49430 D: 96145 Ptnml(0-2): 556, 22775, 50607, 23242, 596 Passed LTC: https://tests.stockfishchess.org/tests/view/64a127bd3ee09aa549c4f60c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 235452 W: 60327 L: 59609 D: 115516 Ptnml(0-2): 119, 25173, 66426, 25887, 121 closes https://github.com/official-stockfish/Stockfish/pull/4666 bench 2427629 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index a1d46111938..abdbef9010d 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1b951f8b449d.nnue" + #define EvalFileDefaultName "nn-c38c3d8d3920.nnue" namespace NNUE { From ee023d7fd78a96c10ae157c0d3174f091a4e09d1 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 6 Jul 2023 23:29:11 +0200 Subject: [PATCH 0142/1309] Fix CI output closes https://github.com/official-stockfish/Stockfish/pull/4669 No functional change --- .github/workflows/stockfish_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 05592dae530..cd80e223853 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -122,7 +122,7 @@ jobs: for hash in $(git rev-list -100 HEAD); do benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true done - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $(git rev-parse HEAD~$n)" && echo "Reference bench: $benchref" || echo "No bench found" + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" - name: Check compiler run: | From 6a8767a0d5d9502e6d4de1bef97468b5d6fab80a Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 7 Jul 2023 20:19:31 +0800 Subject: [PATCH 0143/1309] Simplify PvNode reduction Simplification STC: https://tests.stockfishchess.org/tests/view/64a415803ee09aa549c539c3 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 37856 W: 9719 L: 9504 D: 18633 Ptnml(0-2): 98, 4277, 9977, 4464, 112 Simplification LTC: https://tests.stockfishchess.org/tests/view/64a5ffe202cd07745c60f360 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 55878 W: 14323 L: 14138 D: 27417 Ptnml(0-2): 21, 5993, 15732, 6166, 27 closes https://github.com/official-stockfish/Stockfish/pull/4673 Bench: 2604965 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 76d055e3039..1f8f361c409 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1162,7 +1162,7 @@ namespace { // Decrease reduction for PvNodes based on depth (~2 Elo) if (PvNode) - r -= 1 + 12 / (3 + depth); + r -= 1 + (depth < 6); // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) From af110e02ec96cdb46cf84c68252a1da15a902395 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 1 Jun 2023 08:09:07 +0200 Subject: [PATCH 0144/1309] Remove classical evaluation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit since the introduction of NNUE (first released with Stockfish 12), we have maintained the classical evaluation as part of SF in frozen form. The idea that this code could lead to further inputs to the NN or search did not materialize. Now, after five releases, this PR removes the classical evaluation from SF. Even though this evaluation is probably the best of its class, it has become unimportant for the engine's strength, and there is little need to maintain this code (roughly 25% of SF) going forward, or to expend resources on trying to improve its integration in the NNUE eval. Indeed, it had still a very limited use in the current SF, namely for the evaluation of positions that are nearly decided based on material difference, where the speed of the classical evaluation outweights its inaccuracies. This impact on strength is small, roughly 2Elo, and probably decreasing in importance as the TC grows. Potentially, removal of this code could lead to the development of techniques to have faster, but less accurate NN evaluation, for certain positions. STC https://tests.stockfishchess.org/tests/view/64a320173ee09aa549c52157 Elo: -2.35 ± 1.1 (95%) LOS: 0.0% Total: 100000 W: 24916 L: 25592 D: 49492 Ptnml(0-2): 287, 12123, 25841, 11477, 272 nElo: -4.62 ± 2.2 (95%) PairsRatio: 0.95 LTC https://tests.stockfishchess.org/tests/view/64a320293ee09aa549c5215b Elo: -1.74 ± 1.0 (95%) LOS: 0.0% Total: 100000 W: 25010 L: 25512 D: 49478 Ptnml(0-2): 44, 11069, 28270, 10579, 38 nElo: -3.72 ± 2.2 (95%) PairsRatio: 0.96 VLTC SMP https://tests.stockfishchess.org/tests/view/64a3207c3ee09aa549c52168 Elo: -1.70 ± 0.9 (95%) LOS: 0.0% Total: 100000 W: 25673 L: 26162 D: 48165 Ptnml(0-2): 8, 9455, 31569, 8954, 14 nElo: -3.95 ± 2.2 (95%) PairsRatio: 0.95 closes https://github.com/official-stockfish/Stockfish/pull/4674 Bench: 1444646 --- src/Makefile | 4 +- src/benchmark.cpp | 9 - src/bitbase.cpp | 172 ------- src/bitboard.h | 7 - src/endgame.cpp | 747 ---------------------------- src/endgame.h | 126 ----- src/evaluate.cpp | 992 +------------------------------------ src/main.cpp | 3 - src/material.cpp | 229 --------- src/material.h | 71 --- src/nnue/evaluate_nnue.cpp | 3 +- src/pawns.cpp | 305 ------------ src/pawns.h | 70 --- src/position.cpp | 38 +- src/thread.h | 4 - src/ucioption.cpp | 2 - 16 files changed, 37 insertions(+), 2745 deletions(-) delete mode 100644 src/bitbase.cpp delete mode 100644 src/endgame.cpp delete mode 100644 src/endgame.h delete mode 100644 src/material.cpp delete mode 100644 src/material.h delete mode 100644 src/pawns.cpp delete mode 100644 src/pawns.h diff --git a/src/Makefile b/src/Makefile index 82664618bb7..a0f098fa678 100644 --- a/src/Makefile +++ b/src/Makefile @@ -56,8 +56,8 @@ else endif ### Source and object files -SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ - material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ +SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ + misc.cpp movegen.cpp movepick.cpp position.cpp psqt.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp diff --git a/src/benchmark.cpp b/src/benchmark.cpp index a1ad055057b..baa90140f9b 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -153,24 +153,15 @@ vector setup_bench(const Position& current, istream& is) { list.emplace_back("setoption name Hash value " + ttSize); list.emplace_back("ucinewgame"); - size_t posCounter = 0; - for (const string& fen : fens) if (fen.find("setoption") != string::npos) list.emplace_back(fen); else { - if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0)) - list.emplace_back("setoption name Use NNUE value false"); - else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0)) - list.emplace_back("setoption name Use NNUE value true"); list.emplace_back("position fen " + fen); list.emplace_back(go); - ++posCounter; } - list.emplace_back("setoption name Use NNUE value true"); - return list; } diff --git a/src/bitbase.cpp b/src/bitbase.cpp deleted file mode 100644 index e21d1fe9536..00000000000 --- a/src/bitbase.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include -#include - -#include "bitboard.h" -#include "types.h" - -namespace Stockfish { - -namespace { - - // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. - // Positions with the pawn on files E to H will be mirrored before probing. - constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608 - - std::bitset KPKBitbase; - - // A KPK bitbase index is an integer in [0, IndexMax] range - // - // Information is mapped in a way that minimizes the number of iterations: - // - // bit 0- 5: white king square (from SQ_A1 to SQ_H8) - // bit 6-11: black king square (from SQ_A1 to SQ_H8) - // bit 12: side to move (WHITE or BLACK) - // bit 13-14: white pawn file (from FILE_A to FILE_D) - // bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2) - unsigned index(Color stm, Square bksq, Square wksq, Square psq) { - return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15); - } - - enum Result { - INVALID = 0, - UNKNOWN = 1, - DRAW = 2, - WIN = 4 - }; - - Result& operator|=(Result& r, Result v) { return r = Result(r | v); } - - struct KPKPosition { - KPKPosition() = default; - explicit KPKPosition(unsigned idx); - operator Result() const { return result; } - Result classify(const std::vector& db); - - Color stm; - Square ksq[COLOR_NB], psq; - Result result; - }; - -} // namespace - -bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) { - - assert(file_of(wpsq) <= FILE_D); - - return KPKBitbase[index(stm, bksq, wksq, wpsq)]; -} - - -void Bitbases::init() { - - std::vector db(MAX_INDEX); - unsigned idx, repeat = 1; - - // Initialize db with known win / draw positions - for (idx = 0; idx < MAX_INDEX; ++idx) - db[idx] = KPKPosition(idx); - - // Iterate through the positions until none of the unknown positions can be - // changed to either wins or draws (15 cycles needed). - while (repeat) - for (repeat = idx = 0; idx < MAX_INDEX; ++idx) - repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN); - - // Fill the bitbase with the decisive results - for (idx = 0; idx < MAX_INDEX; ++idx) - if (db[idx] == WIN) - KPKBitbase.set(idx); -} - -namespace { - - KPKPosition::KPKPosition(unsigned idx) { - - ksq[WHITE] = Square((idx >> 0) & 0x3F); - ksq[BLACK] = Square((idx >> 6) & 0x3F); - stm = Color ((idx >> 12) & 0x01); - psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); - - // Invalid if two pieces are on the same square or if a king can be captured - if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 - || ksq[WHITE] == psq - || ksq[BLACK] == psq - || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK]))) - result = INVALID; - - // Win if the pawn can be promoted without getting captured - else if ( stm == WHITE - && rank_of(psq) == RANK_7 - && ksq[WHITE] != psq + NORTH - && ( distance(ksq[BLACK], psq + NORTH) > 1 - || (distance(ksq[WHITE], psq + NORTH) == 1))) - result = WIN; - - // Draw if it is stalemate or the black king can capture the pawn - else if ( stm == BLACK - && ( !(attacks_bb(ksq[BLACK]) & ~(attacks_bb(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq))) - || (attacks_bb(ksq[BLACK]) & ~attacks_bb(ksq[WHITE]) & psq))) - result = DRAW; - - // Position will be classified later - else - result = UNKNOWN; - } - - Result KPKPosition::classify(const std::vector& db) { - - // White to move: If one move leads to a position classified as WIN, the result - // of the current position is WIN. If all moves lead to positions classified - // as DRAW, the current position is classified as DRAW, otherwise the current - // position is classified as UNKNOWN. - // - // Black to move: If one move leads to a position classified as DRAW, the result - // of the current position is DRAW. If all moves lead to positions classified - // as WIN, the position is classified as WIN, otherwise the current position is - // classified as UNKNOWN. - const Result Good = (stm == WHITE ? WIN : DRAW); - const Result Bad = (stm == WHITE ? DRAW : WIN); - - Result r = INVALID; - Bitboard b = attacks_bb(ksq[stm]); - - while (b) - r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)] - : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)]; - - if (stm == WHITE) - { - if (rank_of(psq) < RANK_7) // Single push - r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)]; - - if ( rank_of(psq) == RANK_2 // Double push - && psq + NORTH != ksq[WHITE] - && psq + NORTH != ksq[BLACK]) - r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)]; - } - - return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; - } - -} // namespace - -} // namespace Stockfish diff --git a/src/bitboard.h b/src/bitboard.h index 42fd0e97ec6..d21d390b1fb 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -25,13 +25,6 @@ namespace Stockfish { -namespace Bitbases { - -void init(); -bool probe(Square wksq, Square wpsq, Square bksq, Color us); - -} // namespace Stockfish::Bitbases - namespace Bitboards { void init(); diff --git a/src/endgame.cpp b/src/endgame.cpp deleted file mode 100644 index 9021f242313..00000000000 --- a/src/endgame.cpp +++ /dev/null @@ -1,747 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include - -#include "bitboard.h" -#include "endgame.h" -#include "movegen.h" - -namespace Stockfish { - -namespace { - - // Used to drive the king towards the edge of the board - // in KX vs K and KQ vs KR endgames. - // Values range from 27 (center squares) to 90 (in the corners) - inline int push_to_edge(Square s) { - int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s)); - return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2); - } - - // Used to drive the king towards A1H8 corners in KBN vs K endgames. - // Values range from 0 on A8H1 diagonal to 7 in A1H8 corners - inline int push_to_corner(Square s) { - return abs(7 - rank_of(s) - file_of(s)); - } - - // Drive a piece close to or away from another piece - inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); } - inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); } - -#ifndef NDEBUG - bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) { - return pos.non_pawn_material(c) == npm && pos.count(c) == pawnsCnt; - } -#endif - - // Map the square as if strongSide is white and strongSide's only pawn - // is on the left half of the board. - Square normalize(const Position& pos, Color strongSide, Square sq) { - - assert(pos.count(strongSide) == 1); - - if (file_of(pos.square(strongSide)) >= FILE_E) - sq = flip_file(sq); - - return strongSide == WHITE ? sq : flip_rank(sq); - } - -} // namespace - - -namespace Endgames { - - std::pair, Map> maps; - - void init() { - - add("KPK"); - add("KNNK"); - add("KBNK"); - add("KRKP"); - add("KRKB"); - add("KRKN"); - add("KQKP"); - add("KQKR"); - add("KNNKP"); - - add("KRPKR"); - add("KRPKB"); - add("KBPKB"); - add("KBPKN"); - add("KBPPKB"); - add("KRPPKRP"); - } -} - - -/// Mate with KX vs K. This function is used to evaluate positions with -/// king and plenty of material vs a lone king. It simply gives the -/// attacking side a bonus for driving the defending king towards the edge -/// of the board, and for keeping the distance between the two kings small. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - assert(!pos.checkers()); // Eval is never called when in check - - // Stalemate detection with lone king - if (pos.side_to_move() == weakSide && !MoveList(pos).size()) - return VALUE_DRAW; - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - Value result = pos.non_pawn_material(strongSide) - + pos.count(strongSide) * PawnValueEg - + push_to_edge(weakKing) - + push_close(strongKing, weakKing); - - if ( pos.count(strongSide) - || pos.count(strongSide) - ||(pos.count(strongSide) && pos.count(strongSide)) - || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) - && (pos.pieces(strongSide, BISHOP) & DarkSquares))) - result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the -/// defending king towards a corner square that our bishop attacks. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - Square strongKing = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - // If our bishop does not attack A1/H8, we flip the enemy king square - // to drive to opposite corners (A8/H1). - - Value result = (VALUE_KNOWN_WIN + 3520) - + push_close(strongKing, weakKing) - + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing); - - assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KP vs K. This endgame is evaluated with the help of a bitbase -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - - Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; - - if (!Bitbases::probe(strongKing, strongPawn, weakKing, us)) - return VALUE_DRAW; - - Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn)); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without -/// a bitbase. The function below returns drawish scores when the pawn is -/// far advanced with support of the king, while the attacking king is far -/// away. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square strongRook = pos.square(strongSide); - Square weakPawn = pos.square(weakSide); - Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8)); - Value result; - - // If the stronger side's king is in front of the pawn, it's a win - if (forward_file_bb(strongSide, strongKing) & weakPawn) - result = RookValueEg - distance(strongKing, weakPawn); - - // If the weaker side's king is too far from the pawn and the rook, - // it's a win. - else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide) - && distance(weakKing, strongRook) >= 3) - result = RookValueEg - distance(strongKing, weakPawn); - - // If the pawn is far advanced and supported by the defending king, - // the position is drawish - else if ( relative_rank(strongSide, weakKing) <= RANK_3 - && distance(weakKing, weakPawn) == 1 - && relative_rank(strongSide, strongKing) >= RANK_4 - && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide)) - result = Value(80) - 8 * distance(strongKing, weakPawn); - - else - result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide)) - - distance(weakKing, weakPawn + pawn_push(weakSide)) - - distance(weakPawn, queeningSquare)); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KB. This is very simple, and always returns drawish scores. The -/// score is slightly bigger when the defending king is close to the edge. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Value result = Value(push_to_edge(pos.square(weakSide))); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KR vs KN. The attacking side has slightly better winning chances than -/// in KR vs KB, particularly if the king and the knight are far apart. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 0)); - assert(verify_material(pos, weakSide, KnightValueMg, 0)); - - Square weakKing = pos.square(weakSide); - Square weakKnight = pos.square(weakSide); - Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight)); - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KQ vs KP. In general, this is a win for the stronger side, but there are a -/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files -/// with a king positioned next to it can be a draw, so in that case, we only -/// use the distance between the kings. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square weakPawn = pos.square(weakSide); - - Value result = Value(push_close(strongKing, weakKing)); - - if ( relative_rank(weakSide, weakPawn) != RANK_7 - || distance(weakKing, weakPawn) != 1 - || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn)) - result += QueenValueEg - PawnValueEg; - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KQ vs KR. This is almost identical to KX vs K: we give the attacking -/// king a bonus for having the kings close together, and for forcing the -/// defending king towards the edge. If we also take care to avoid null move for -/// the defending side in the search, this is usually sufficient to win KQ vs KR. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(verify_material(pos, weakSide, RookValueMg, 0)); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - Value result = QueenValueEg - - RookValueEg - + push_to_edge(weakKing) - + push_close(strongKing, weakKing); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// KNN vs KP. Very drawish, but there are some mate opportunities if we can -/// press the weakSide King to a corner before the pawn advances too much. -template<> -Value Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - Square weakKing = pos.square(weakSide); - Square weakPawn = pos.square(weakSide); - - Value result = PawnValueEg - + 2 * push_to_edge(weakKing) - - 10 * relative_rank(weakSide, weakPawn); - - return strongSide == pos.side_to_move() ? result : -result; -} - - -/// Some cases of trivial draws -template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } - - -/// KB and one or more pawns vs K. It checks for draws with rook pawns and -/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW -/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling -/// will be used. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(pos.non_pawn_material(strongSide) == BishopValueMg); - assert(pos.count(strongSide) >= 1); - - // No assertions about the material of weakSide, because we want draws to - // be detected even when the weaker side has some pawns. - - Bitboard strongPawns = pos.pieces(strongSide, PAWN); - Bitboard allPawns = pos.pieces(PAWN); - - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square strongKing = pos.square(strongSide); - - // All strongSide pawns are on a single rook file? - if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB)) - { - Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8)); - - if ( opposite_colors(queeningSquare, strongBishop) - && distance(queeningSquare, weakKing) <= 1) - return SCALE_FACTOR_DRAW; - } - - // If all the pawns are on the same B or G file, then it's potentially a draw - if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB)) - && pos.non_pawn_material(weakSide) == 0 - && pos.count(weakSide) >= 1) - { - // Get the least advanced weakSide pawn - Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN)); - - // There's potential for a draw if our pawn is blocked on the 7th rank, - // the bishop cannot attack it or they only have one pawn left. - if ( relative_rank(strongSide, weakPawn) == RANK_7 - && (strongPawns & (weakPawn + pawn_push(weakSide))) - && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns))) - { - int strongKingDist = distance(weakPawn, strongKing); - int weakKingDist = distance(weakPawn, weakKing); - - // It's a draw if the weak king is on its back two ranks, within 2 - // squares of the blocking pawn and the strong king is not - // closer. (I think this rule only fails in practically - // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w - // and positions where qsearch will immediately correct the - // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w). - if ( relative_rank(strongSide, weakKing) >= RANK_7 - && weakKingDist <= 2 - && weakKingDist <= strongKingDist) - return SCALE_FACTOR_DRAW; - } - } - - return SCALE_FACTOR_NONE; -} - - -/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on -/// the third rank defended by a pawn. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, QueenValueMg, 0)); - assert(pos.count(weakSide) == 1); - assert(pos.count(weakSide) >= 1); - - Square strongKing = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - Square weakRook = pos.square(weakSide); - - if ( relative_rank(weakSide, weakKing) <= RANK_2 - && relative_rank(weakSide, strongKing) >= RANK_4 - && relative_rank(weakSide, weakRook) == RANK_3 - && ( pos.pieces(weakSide, PAWN) - & attacks_bb(weakKing) - & pawn_attacks_bb(strongSide, weakRook))) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KRP vs KR. This function knows a handful of the most important classes of -/// drawn positions, but is far from perfect. It would probably be a good idea -/// to add more knowledge in the future. -/// -/// It would also be nice to rewrite the actual code for this function, -/// which is mostly copied from Glaurung 1.x, and isn't very pretty. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 1)); - assert(verify_material(pos, weakSide, RookValueMg, 0)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square strongRook = normalize(pos, strongSide, pos.square(strongSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - Square weakRook = normalize(pos, strongSide, pos.square(weakSide)); - - File pawnFile = file_of(strongPawn); - Rank pawnRank = rank_of(strongPawn); - Square queeningSquare = make_square(pawnFile, RANK_8); - int tempo = (pos.side_to_move() == strongSide); - - // If the pawn is not too far advanced and the defending king defends the - // queening square, use the third-rank defence. - if ( pawnRank <= RANK_5 - && distance(weakKing, queeningSquare) <= 1 - && strongKing <= SQ_H5 - && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6))) - return SCALE_FACTOR_DRAW; - - // The defending side saves a draw by checking from behind in case the pawn - // has advanced to the 6th rank with the king behind. - if ( pawnRank == RANK_6 - && distance(weakKing, queeningSquare) <= 1 - && rank_of(strongKing) + tempo <= RANK_6 - && (rank_of(weakRook) == RANK_1 || (!tempo && distance(weakRook, strongPawn) >= 3))) - return SCALE_FACTOR_DRAW; - - if ( pawnRank >= RANK_6 - && weakKing == queeningSquare - && rank_of(weakRook) == RANK_1 - && (!tempo || distance(strongKing, strongPawn) >= 2)) - return SCALE_FACTOR_DRAW; - - // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7 - // and the black rook is behind the pawn. - if ( strongPawn == SQ_A7 - && strongRook == SQ_A8 - && (weakKing == SQ_H7 || weakKing == SQ_G7) - && file_of(weakRook) == FILE_A - && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5)) - return SCALE_FACTOR_DRAW; - - // If the defending king blocks the pawn and the attacking king is too far - // away, it's a draw. - if ( pawnRank <= RANK_5 - && weakKing == strongPawn + NORTH - && distance(strongKing, strongPawn) - tempo >= 2 - && distance(strongKing, weakRook) - tempo >= 2) - return SCALE_FACTOR_DRAW; - - // Pawn on the 7th rank supported by the rook from behind usually wins if the - // attacking king is closer to the queening square than the defending king, - // and the defending king cannot gain tempi by threatening the attacking rook. - if ( pawnRank == RANK_7 - && pawnFile != FILE_A - && file_of(strongRook) == pawnFile - && strongRook != queeningSquare - && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) - && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo)) - return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare)); - - // Similar to the above, but with the pawn further back - if ( pawnFile != FILE_A - && file_of(strongRook) == pawnFile - && strongRook < strongPawn - && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) - && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo) - && ( distance(weakKing, strongRook) + tempo >= 3 - || ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo - && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo)))) - return ScaleFactor( SCALE_FACTOR_MAX - - 8 * distance(strongPawn, queeningSquare) - - 2 * distance(strongKing, queeningSquare)); - - // If the pawn is not far advanced and the defending king is somewhere in - // the pawn's path, it's probably a draw. - if (pawnRank <= RANK_4 && weakKing > strongPawn) - { - if (file_of(weakKing) == file_of(strongPawn)) - return ScaleFactor(10); - if ( distance(weakKing, strongPawn) == 1 - && distance(strongKing, weakKing) > 2) - return ScaleFactor(24 - 2 * distance(strongKing, weakKing)); - } - return SCALE_FACTOR_NONE; -} - -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 1)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - // Test for a rook pawn - if (pos.pieces(PAWN) & (FileABB | FileHBB)) - { - Square weakKing = pos.square(weakSide); - Square weakBishop = pos.square(weakSide); - Square strongKing = pos.square(strongSide); - Square strongPawn = pos.square(strongSide); - Rank pawnRank = relative_rank(strongSide, strongPawn); - Direction push = pawn_push(strongSide); - - // If the pawn is on the 5th rank and the pawn (currently) is on - // the same color square as the bishop then there is a chance of - // a fortress. Depending on the king position give a moderate - // reduction or a stronger one if the defending king is near the - // corner but not trapped there. - if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn)) - { - int d = distance(strongPawn + 3 * push, weakKing); - - if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push)) - return ScaleFactor(24); - else - return ScaleFactor(48); - } - - // When the pawn has moved to the 6th rank we can be fairly sure - // it's drawn if the bishop attacks the square in front of the - // pawn from a reasonable distance and the defending king is near - // the corner - if ( pawnRank == RANK_6 - && distance(strongPawn + 2 * push, weakKing) <= 1 - && (attacks_bb(weakBishop) & (strongPawn + push)) - && distance(weakBishop, strongPawn) >= 2) - return ScaleFactor(8); - } - - return SCALE_FACTOR_NONE; -} - -/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed -/// pawns and the defending king is actively placed, the position is drawish. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, RookValueMg, 2)); - assert(verify_material(pos, weakSide, RookValueMg, 1)); - - Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); - Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); - Square weakKing = pos.square(weakSide); - - // Does the stronger side have a passed pawn? - if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2)) - return SCALE_FACTOR_NONE; - - Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2)); - - if ( distance(weakKing, strongPawn1) <= 1 - && distance(weakKing, strongPawn2) <= 1 - && relative_rank(strongSide, weakKing) > pawnRank) - { - assert(pawnRank > RANK_1 && pawnRank < RANK_7); - return ScaleFactor(7 * pawnRank); - } - return SCALE_FACTOR_NONE; -} - - -/// K and two or more pawns vs K. There is just a single rule here: if all pawns -/// are on the same rook file and are blocked by the defending king, it's a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(pos.non_pawn_material(strongSide) == VALUE_ZERO); - assert(pos.count(strongSide) >= 2); - assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); - - Square weakKing = pos.square(weakSide); - Bitboard strongPawns = pos.pieces(strongSide, PAWN); - - // If all pawns are ahead of the king on a single rook file, it's a draw. - if ( !(strongPawns & ~(FileABB | FileHBB)) - && !(strongPawns & ~passed_pawn_span(weakSide, weakKing))) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KBP vs KB. There are two rules: if the defending king is somewhere along the -/// path of the pawn, and the square of the king is not of the same color as the -/// stronger side's bishop, it's a draw. If the two bishops have opposite color, -/// it's almost always a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 1)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Square strongPawn = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakBishop = pos.square(weakSide); - Square weakKing = pos.square(weakSide); - - // Case 1: Defending king blocks the pawn, and cannot be driven away - if ( (forward_file_bb(strongSide, strongPawn) & weakKing) - && ( opposite_colors(weakKing, strongBishop) - || relative_rank(strongSide, weakKing) <= RANK_6)) - return SCALE_FACTOR_DRAW; - - // Case 2: Opposite colored bishops - if (opposite_colors(strongBishop, weakBishop)) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 2)); - assert(verify_material(pos, weakSide, BishopValueMg, 0)); - - Square strongBishop = pos.square(strongSide); - Square weakBishop = pos.square(weakSide); - - if (!opposite_colors(strongBishop, weakBishop)) - return SCALE_FACTOR_NONE; - - Square weakKing = pos.square(weakSide); - Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); - Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); - Square blockSq1, blockSq2; - - if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) - { - blockSq1 = strongPawn1 + pawn_push(strongSide); - blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1)); - } - else - { - blockSq1 = strongPawn2 + pawn_push(strongSide); - blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2)); - } - - switch (distance(strongPawn1, strongPawn2)) - { - case 0: - // Both pawns are on the same file. It's an easy draw if the defender firmly - // controls some square in the frontmost pawn's path. - if ( file_of(weakKing) == file_of(blockSq1) - && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1) - && opposite_colors(weakKing, strongBishop)) - return SCALE_FACTOR_DRAW; - else - return SCALE_FACTOR_NONE; - - case 1: - // Pawns on adjacent files. It's a draw if the defender firmly controls the - // square in front of the frontmost pawn's path, and the square diagonally - // behind this square on the file of the other pawn. - if ( weakKing == blockSq1 - && opposite_colors(weakKing, strongBishop) - && ( weakBishop == blockSq2 - || (attacks_bb(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP)) - || distance(strongPawn1, strongPawn2) >= 2)) - return SCALE_FACTOR_DRAW; - - else if ( weakKing == blockSq2 - && opposite_colors(weakKing, strongBishop) - && ( weakBishop == blockSq1 - || (attacks_bb(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP)))) - return SCALE_FACTOR_DRAW; - else - return SCALE_FACTOR_NONE; - - default: - // The pawns are not on the same file or adjacent files. No scaling. - return SCALE_FACTOR_NONE; - } -} - - -/// KBP vs KN. There is a single rule: if the defending king is somewhere along -/// the path of the pawn, and the square of the king is not of the same color as -/// the stronger side's bishop, it's a draw. -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, BishopValueMg, 1)); - assert(verify_material(pos, weakSide, KnightValueMg, 0)); - - Square strongPawn = pos.square(strongSide); - Square strongBishop = pos.square(strongSide); - Square weakKing = pos.square(weakSide); - - if ( file_of(weakKing) == file_of(strongPawn) - && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing) - && ( opposite_colors(weakKing, strongBishop) - || relative_rank(strongSide, weakKing) <= RANK_6)) - return SCALE_FACTOR_DRAW; - - return SCALE_FACTOR_NONE; -} - - -/// KP vs KP. This is done by removing the weakest side's pawn and probing the -/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably -/// has at least a draw with the pawn as well. The exception is when the stronger -/// side's pawn is far advanced and not on a rook file; in this case it is often -/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1). -template<> -ScaleFactor Endgame::operator()(const Position& pos) const { - - assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); - assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); - - // Assume strongSide is white and the pawn is on files A-D - Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); - Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); - Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); - - Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; - - // If the pawn has advanced to the fifth rank or further, and is not a - // rook pawn, it's too dangerous to assume that it's at least a draw. - if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A) - return SCALE_FACTOR_NONE; - - // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, - // it's probably at least a draw even with the pawn. - return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; -} - -} // namespace Stockfish diff --git a/src/endgame.h b/src/endgame.h deleted file mode 100644 index c184cb3fd50..00000000000 --- a/src/endgame.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef ENDGAME_H_INCLUDED -#define ENDGAME_H_INCLUDED - -#include -#include -#include -#include -#include - -#include "position.h" -#include "types.h" - -namespace Stockfish { - -/// EndgameCode lists all supported endgame functions by corresponding codes - -enum EndgameCode { - - EVALUATION_FUNCTIONS, - KNNK, // KNN vs K - KNNKP, // KNN vs KP - KXK, // Generic "mate lone king" eval - KBNK, // KBN vs K - KPK, // KP vs K - KRKP, // KR vs KP - KRKB, // KR vs KB - KRKN, // KR vs KN - KQKP, // KQ vs KP - KQKR, // KQ vs KR - - SCALING_FUNCTIONS, - KBPsK, // KB and pawns vs K - KQKRPs, // KQ vs KR and pawns - KRPKR, // KRP vs KR - KRPKB, // KRP vs KB - KRPPKRP, // KRPP vs KRP - KPsK, // K and pawns vs K - KBPKB, // KBP vs KB - KBPPKB, // KBPP vs KB - KBPKN, // KBP vs KN - KPKP // KP vs KP -}; - - -/// Endgame functions can be of two types depending on whether they return a -/// Value or a ScaleFactor. - -template using -eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; - - -/// Base and derived functors for endgame evaluation and scaling functions - -template -struct EndgameBase { - - explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} - virtual ~EndgameBase() = default; - virtual T operator()(const Position&) const = 0; - - const Color strongSide, weakSide; -}; - - -template> -struct Endgame : public EndgameBase { - - explicit Endgame(Color c) : EndgameBase(c) {} - T operator()(const Position&) const override; -}; - - -/// The Endgames namespace handles the pointers to endgame evaluation and scaling -/// base objects in two std::map. We use polymorphism to invoke the actual -/// endgame function by calling its virtual operator(). - -namespace Endgames { - - template using Ptr = std::unique_ptr>; - template using Map = std::unordered_map>; - - extern std::pair, Map> maps; - - void init(); - - template - Map& map() { - return std::get::value>(maps); - } - - template> - void add(const std::string& code) { - - StateInfo st; - map()[Position().set(code, WHITE, &st).material_key()] = Ptr(new Endgame(WHITE)); - map()[Position().set(code, BLACK, &st).material_key()] = Ptr(new Endgame(BLACK)); - } - - template - const EndgameBase* probe(Key key) { - auto it = map().find(key); - return it != map().end() ? it->second.get() : nullptr; - } -} - -} // namespace Stockfish - -#endif // #ifndef ENDGAME_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 35d054270ee..2ab4fa404e0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -18,8 +18,6 @@ #include #include -#include -#include // For std::memset #include #include #include @@ -29,9 +27,7 @@ #include "bitboard.h" #include "evaluate.h" -#include "material.h" #include "misc.h" -#include "pawns.h" #include "thread.h" #include "timeman.h" #include "uci.h" @@ -60,9 +56,10 @@ namespace Stockfish { namespace Eval { - bool useNNUE; string currentEvalFileName = "None"; + static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } + /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" /// The name of the NNUE network is always retrieved from the EvalFile option. @@ -73,10 +70,6 @@ namespace Eval { void NNUE::init() { - useNNUE = Options["Use NNUE"]; - if (!useNNUE) - return; - string eval_file = string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; @@ -122,10 +115,10 @@ namespace Eval { if (eval_file.empty()) eval_file = EvalFileDefaultName; - if (useNNUE && currentEvalFileName != eval_file) + if (currentEvalFileName != eval_file) { - string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; + string msg1 = "Network evaluation parameters compatible with the engine must be available."; string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); @@ -140,909 +133,10 @@ namespace Eval { exit(EXIT_FAILURE); } - if (useNNUE) - sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; - else - sync_cout << "info string classical evaluation enabled" << sync_endl; - } -} - -namespace Trace { - - enum Tracing { NO_TRACE, TRACE }; - - enum Term { // The first 8 entries are reserved for PieceType - MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB - }; - - Score scores[TERM_NB][COLOR_NB]; - - static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } - - static void add(int idx, Color c, Score s) { - scores[idx][c] = s; - } - - static void add(int idx, Score w, Score b = SCORE_ZERO) { - scores[idx][WHITE] = w; - scores[idx][BLACK] = b; - } - - static std::ostream& operator<<(std::ostream& os, Score s) { - os << std::setw(5) << to_cp(mg_value(s)) << " " - << std::setw(5) << to_cp(eg_value(s)); - return os; - } - - static std::ostream& operator<<(std::ostream& os, Term t) { - - if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL) - os << " ---- ----" << " | " << " ---- ----"; - else - os << scores[t][WHITE] << " | " << scores[t][BLACK]; - - os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n"; - return os; + sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; } } -using namespace Trace; - -namespace { - - // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(3622); - constexpr Value LazyThreshold2 = Value(1962); - constexpr Value SpaceThreshold = Value(11551); - - // KingAttackWeights[PieceType] contains king attack weights by piece type - constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 }; - - // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, - // higher if multiple safe checks are possible for that piece type. - constexpr int SafeCheck[][2] = { - {}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128} - }; - -#define S(mg, eg) make_score(mg, eg) - - // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, - // indexed by piece type and number of attacked squares in the mobility area. - constexpr Score MobilityBonus[][32] = { - { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight - S( 21, 16), S( 28, 21), S( 37, 26) }, - { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop - S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87), - S( 91, 88), S( 96, 98) }, - { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook - S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160), - S( 57,165), S( 58,170), S( 67,175) }, - { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen - S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101), - S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140), - S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171), - S(112,178), S(114,185), S(114,187), S(119,221) } - }; - - // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on - // squares of the same color as our bishop. - constexpr Score BishopPawns[int(FILE_NB) / 2] = { - S(3, 8), S(3, 9), S(2, 7), S(3, 7) - }; - - // KingProtector[knight/bishop] contains penalty for each distance unit to own king - constexpr Score KingProtector[] = { S(9, 9), S(7, 9) }; - - // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a - // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. - constexpr Score Outpost[] = { S(54, 34), S(31, 25) }; - - // PassedRank[Rank] contains a bonus according to the rank of a passed pawn - constexpr Score PassedRank[RANK_NB] = { - S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269) - }; - - constexpr Score RookOnClosedFile = S(10, 5); - constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) }; - - // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to - // which piece type attacks which one. Attacks on lesser pieces which are - // pawn-defended are not considered. - constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { - S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163) - }; - - constexpr Score ThreatByRook[PIECE_TYPE_NB] = { - S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39) - }; - - constexpr Value CorneredBishop = Value(50); - - // Assorted bonuses and penalties - constexpr Score UncontestedOutpost = S( 0, 10); - constexpr Score BishopOnKingRing = S( 24, 0); - constexpr Score BishopXRayPawns = S( 4, 5); - constexpr Score FlankAttacks = S( 8, 0); - constexpr Score Hanging = S( 72, 40); - constexpr Score KnightOnQueen = S( 16, 11); - constexpr Score LongDiagonalBishop = S( 45, 0); - constexpr Score MinorBehindPawn = S( 18, 3); - constexpr Score PassedFile = S( 13, 8); - constexpr Score PawnlessFlank = S( 19, 97); - constexpr Score ReachableOutpost = S( 33, 19); - constexpr Score RestrictedPiece = S( 6, 7); - constexpr Score RookOnKingRing = S( 16, 0); - constexpr Score SliderOnQueen = S( 62, 21); - constexpr Score ThreatByKing = S( 24, 87); - constexpr Score ThreatByPawnPush = S( 48, 39); - constexpr Score ThreatBySafePawn = S(167, 99); - constexpr Score TrappedRook = S( 55, 13); - constexpr Score WeakQueenProtection = S( 14, 0); - constexpr Score WeakQueen = S( 57, 19); - - -#undef S - - // Evaluation class computes and stores attacks tables and other working data - template - class Evaluation { - - public: - Evaluation() = delete; - explicit Evaluation(const Position& p) : pos(p) {} - Evaluation& operator=(const Evaluation&) = delete; - Value value(); - - private: - template void initialize(); - template Score pieces(); - template Score king() const; - template Score threats() const; - template Score passed() const; - template Score space() const; - Value winnable(Score score) const; - - const Position& pos; - Material::Entry* me; - Pawns::Entry* pe; - Bitboard mobilityArea[COLOR_NB]; - Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; - - // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type. Special "piece types" which - // is also calculated is ALL_PIECES. - Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; - - // attackedBy2[color] are the squares attacked by at least 2 units of a given - // color, including x-rays. But diagonal x-rays through pawns are not computed. - Bitboard attackedBy2[COLOR_NB]; - - // kingRing[color] are the squares adjacent to the king plus some other - // very near squares, depending on king position. - Bitboard kingRing[COLOR_NB]; - - // kingAttackersCount[color] is the number of pieces of the given color - // which attack a square in the kingRing of the enemy king. - int kingAttackersCount[COLOR_NB]; - - // kingAttackersWeight[color] is the sum of the "weights" of the pieces of - // the given color which attack a square in the kingRing of the enemy king. - // The weights of the individual piece types are given by the elements in - // the KingAttackWeights array. - int kingAttackersWeight[COLOR_NB]; - - // kingAttacksCount[color] is the number of attacks by the given color to - // squares directly adjacent to the enemy king. Pieces which attack more - // than one square are counted multiple times. For instance, if there is - // a white knight on g5 and black's king is on g8, this white knight adds 2 - // to kingAttacksCount[WHITE]. - int kingAttacksCount[COLOR_NB]; - }; - - - // Evaluation::initialize() computes king and pawn attacks, and the king ring - // bitboard for a given color. This is done at the beginning of the evaluation. - - template template - void Evaluation::initialize() { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB); - - const Square ksq = pos.square(Us); - - Bitboard dblAttackByPawn = pawn_double_attacks_bb(pos.pieces(Us, PAWN)); - - // Find our pawns that are blocked or on the first two ranks - Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); - - // Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king - // or controlled by enemy pawns are excluded from the mobility area. - mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them)); - - // Initialize attackedBy[] for king and pawns - attackedBy[Us][KING] = attacks_bb(ksq); - attackedBy[Us][PAWN] = pe->pawn_attacks(Us); - attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN]; - attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]); - - // Init our king safety tables - Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G), - std::clamp(rank_of(ksq), RANK_2, RANK_7)); - kingRing[Us] = attacks_bb(s) | s; - - kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them)); - kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; - - // Remove from kingRing[] the squares defended by two pawns - kingRing[Us] &= ~dblAttackByPawn; - } - - - // Evaluation::pieces() scores pieces of a given color and type - - template template - Score Evaluation::pieces() { - - constexpr Color Them = ~Us; - [[maybe_unused]] constexpr Direction Down = -pawn_push(Us); - [[maybe_unused]] constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB - : Rank5BB | Rank4BB | Rank3BB); - Bitboard b1 = pos.pieces(Us, Pt); - Bitboard b, bb; - Score score = SCORE_ZERO; - - attackedBy[Us][Pt] = 0; - - while (b1) - { - Square s = pop_lsb(b1); - - // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) - : attacks_bb(s, pos.pieces()); - - if (pos.blockers_for_king(Us) & s) - b &= line_bb(pos.square(Us), s); - - attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; - attackedBy[Us][Pt] |= b; - attackedBy[Us][ALL_PIECES] |= b; - - if (b & kingRing[Them]) - { - kingAttackersCount[Us]++; - kingAttackersWeight[Us] += KingAttackWeights[Pt]; - kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); - } - - else if (Pt == ROOK && (file_bb(s) & kingRing[Them])) - score += RookOnKingRing; - - else if (Pt == BISHOP && (attacks_bb(s, pos.pieces(PAWN)) & kingRing[Them])) - score += BishopOnKingRing; - - int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt - 2][mob]; - - if constexpr (Pt == BISHOP || Pt == KNIGHT) - { - // Bonus if the piece is on an outpost square or can reach one - // Bonus for knights (UncontestedOutpost) if few relevant targets - bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) - & ~pe->pawn_attacks_span(Them); - Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); - - if ( Pt == KNIGHT - && bb & s & ~CenterFiles // on a side outpost - && !(b & targets) // no relevant attacks - && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) - score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); - else if (bb & s) - score += Outpost[Pt == BISHOP]; - else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) - score += ReachableOutpost; - - // Bonus for a knight or bishop shielded by pawn - if (shift(pos.pieces(PAWN)) & s) - score += MinorBehindPawn; - - // Penalty if the piece is far from the king - score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); - - if constexpr (Pt == BISHOP) - { - // Penalty according to the number of our pawns on the same color square as the - // bishop, bigger when the center files are blocked with pawns and smaller - // when the bishop is outside the pawn chain. - Bitboard blocked = pos.pieces(Us, PAWN) & shift(pos.pieces()); - - score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s) - * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles)); - - // Penalty for all enemy pawns x-rayed - score -= BishopXRayPawns * popcount(attacks_bb(s) & pos.pieces(Them, PAWN)); - - // Bonus for bishop on a long diagonal which can "see" both center squares - if (more_than_one(attacks_bb(s, pos.pieces(PAWN)) & Center)) - score += LongDiagonalBishop; - - // An important Chess960 pattern: a cornered bishop blocked by a friendly - // pawn diagonally in front of it is a very serious problem, especially - // when that pawn is also blocked. - if ( pos.is_chess960() - && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) - { - Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); - if (pos.piece_on(s + d) == make_piece(Us, PAWN)) - score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) - : 3 * make_score(CorneredBishop, CorneredBishop); - } - } - } - - if constexpr (Pt == ROOK) - { - // Bonuses for rook on a (semi-)open or closed file - if (pos.is_on_semiopen_file(Us, s)) - { - score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; - } - else - { - // If our pawn on this file is blocked, increase penalty - if ( pos.pieces(Us, PAWN) - & shift(pos.pieces()) - & file_bb(s)) - { - score -= RookOnClosedFile; - } - - // Penalty when trapped by the king, even more if the king cannot castle - if (mob <= 3) - { - File kf = file_of(pos.square(Us)); - if ((kf < FILE_E) == (file_of(s) < kf)) - score -= TrappedRook * (1 + !pos.castling_rights(Us)); - } - } - } - - if constexpr (Pt == QUEEN) - { - // Penalty if any relative pin or discovered attack against the queen - Bitboard queenPinners; - if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners)) - score -= WeakQueen; - } - } - if constexpr (T) - Trace::add(Pt, Us, score); - - return score; - } - - - // Evaluation::king() assigns bonuses and penalties to a king of a given color - - template template - Score Evaluation::king() const { - - constexpr Color Them = ~Us; - constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB - : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); - - Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0; - Bitboard rookChecks, queenChecks, bishopChecks, knightChecks; - int kingDanger = 0; - const Square ksq = pos.square(Us); - - // Init the score with king shelter and enemy pawns storm - Score score = pe->king_safety(pos); - - // Attacked squares defended at most once by our queen or king - weak = attackedBy[Them][ALL_PIECES] - & ~attackedBy2[Us] - & (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]); - - // Analyse the safe enemy's checks which are possible on next move - safe = ~pos.pieces(Them); - safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); - - b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - - // Enemy rooks checks - rookChecks = b1 & attackedBy[Them][ROOK] & safe; - if (rookChecks) - kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)]; - else - unsafeChecks |= b1 & attackedBy[Them][ROOK]; - - // Enemy queen safe checks: count them only if the checks are from squares from - // which opponent cannot give a rook check, because rook checks are more valuable. - queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe - & ~(attackedBy[Us][QUEEN] | rookChecks); - if (queenChecks) - kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)]; - - // Enemy bishops checks: count them only if they are from squares from which - // opponent cannot give a queen check, because queen checks are more valuable. - bishopChecks = b2 & attackedBy[Them][BISHOP] & safe - & ~queenChecks; - if (bishopChecks) - kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)]; - - else - unsafeChecks |= b2 & attackedBy[Them][BISHOP]; - - // Enemy knights checks - knightChecks = attacks_bb(ksq) & attackedBy[Them][KNIGHT]; - if (knightChecks & safe) - kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)]; - else - unsafeChecks |= knightChecks; - - // Find the squares that opponent attacks in our king flank, the squares - // which they attack twice in that flank, and the squares that we defend. - b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; - b2 = b1 & attackedBy2[Them]; - b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp; - - int kingFlankAttack = popcount(b1) + popcount(b2); - int kingFlankDefense = popcount(b3); - - kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] // (~10 Elo) - + 183 * popcount(kingRing[Us] & weak) // (~15 Elo) - + 148 * popcount(unsafeChecks) // (~4 Elo) - + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo) - + 69 * kingAttacksCount[Them] // (~0.5 Elo) - + 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo) - + mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo) - - 873 * !pos.count(Them) // (~24 Elo) - - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo) - - 6 * mg_value(score) / 8 // (~8 Elo) - - 4 * kingFlankDefense // (~5 Elo) - + 37; // (~0.5 Elo) - - // Transform the kingDanger units into a Score, and subtract it from the evaluation - if (kingDanger > 100) - score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); - - // Penalty when our king is on a pawnless flank - if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)])) - score -= PawnlessFlank; - - // Penalty if king flank is under attack, potentially moving toward the king - score -= FlankAttacks * kingFlankAttack; - - if constexpr (T) - Trace::add(KING, Us, score); - - return score; - } - - - // Evaluation::threats() assigns bonuses according to the types of the - // attacking and the attacked pieces. - - template template - Score Evaluation::threats() const { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - - Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe; - Score score = SCORE_ZERO; - - // Non-pawn enemies - nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN); - - // Squares strongly protected by the enemy, either because they defend the - // square with a pawn, or because they defend the square twice and we don't. - stronglyProtected = attackedBy[Them][PAWN] - | (attackedBy2[Them] & ~attackedBy2[Us]); - - // Non-pawn enemies, strongly protected - defended = nonPawnEnemies & stronglyProtected; - - // Enemies not strongly protected and under our attack - weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES]; - - // Bonus according to the kind of attacking pieces - if (defended | weak) - { - b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); - while (b) - score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))]; - - b = weak & attackedBy[Us][ROOK]; - while (b) - score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))]; - - if (weak & attackedBy[Us][KING]) - score += ThreatByKing; - - b = ~attackedBy[Them][ALL_PIECES] - | (nonPawnEnemies & attackedBy2[Us]); - score += Hanging * popcount(weak & b); - - // Additional bonus if weak piece is only protected by a queen - score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]); - } - - // Bonus for restricting their piece moves - b = attackedBy[Them][ALL_PIECES] - & ~stronglyProtected - & attackedBy[Us][ALL_PIECES]; - score += RestrictedPiece * popcount(b); - - // Protected or unattacked squares - safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES]; - - // Bonus for attacking enemy pieces with our relatively safe pawns - b = pos.pieces(Us, PAWN) & safe; - b = pawn_attacks_bb(b) & nonPawnEnemies; - score += ThreatBySafePawn * popcount(b); - - // Find squares where our pawns can push on the next move - b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); - b |= shift(b & TRank3BB) & ~pos.pieces(); - - // Keep only the squares which are relatively safe - b &= ~attackedBy[Them][PAWN] & safe; - - // Bonus for safe pawn threats on the next move - b = pawn_attacks_bb(b) & nonPawnEnemies; - score += ThreatByPawnPush * popcount(b); - - // Bonus for threats on the next moves against enemy queen - if (pos.count(Them) == 1) - { - bool queenImbalance = pos.count() == 1; - - Square s = pos.square(Them); - safe = mobilityArea[Us] - & ~pos.pieces(Us, PAWN) - & ~stronglyProtected; - - b = attackedBy[Us][KNIGHT] & attacks_bb(s); - - score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance); - - b = (attackedBy[Us][BISHOP] & attacks_bb(s, pos.pieces())) - | (attackedBy[Us][ROOK ] & attacks_bb(s, pos.pieces())); - - score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); - } - - if constexpr (T) - Trace::add(THREAT, Us, score); - - return score; - } - - // Evaluation::passed() evaluates the passed pawns and candidate passed - // pawns of the given color. - - template template - Score Evaluation::passed() const { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - - auto king_proximity = [&](Color c, Square s) { - return std::min(distance(pos.square(c), s), 5); - }; - - Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers; - Score score = SCORE_ZERO; - - b = pe->passed_pawns(Us); - - blockedPassers = b & shift(pos.pieces(Them, PAWN)); - if (blockedPassers) - { - helpers = shift(pos.pieces(Us, PAWN)) - & ~pos.pieces(Them) - & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]); - - // Remove blocked candidate passers that don't have help to pass - b &= ~blockedPassers - | shift(helpers) - | shift(helpers); - } - - while (b) - { - Square s = pop_lsb(b); - - assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); - - int r = relative_rank(Us, s); - - Score bonus = PassedRank[r]; - - if (r > RANK_3) - { - int w = 5 * r - 13; - Square blockSq = s + Up; - - // Adjust bonus based on the king's proximity - bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4 - - king_proximity(Us, blockSq) * 2) * w); - - // If blockSq is not the queening square then consider also a second push - if (r != RANK_7) - bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w); - - // If the pawn is free to advance, then increase the bonus - if (pos.empty(blockSq)) - { - squaresToQueen = forward_file_bb(Us, s); - unsafeSquares = passed_pawn_span(Us, s); - - bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); - - if (!(pos.pieces(Them) & bb)) - unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); - - // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. - // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. - // Otherwise assign a smaller bonus if the path to queen is not attacked - // and even smaller bonus if it is attacked but block square is not. - int k = !unsafeSquares ? 36 : - !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : - !(unsafeSquares & squaresToQueen) ? 17 : - !(unsafeSquares & blockSq) ? 7 : - 0 ; - - // Assign a larger bonus if the block square is defended - if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq)) - k += 5; - - bonus += make_score(k * w, k * w); - } - } // r > RANK_3 - - score += bonus - PassedFile * edge_distance(file_of(s)); - } - - if constexpr (T) - Trace::add(PASSED, Us, score); - - return score; - } - - - // Evaluation::space() computes a space evaluation for a given side, aiming to improve game - // play in the opening. It is based on the number of safe squares on the four central files - // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice. - // Finally, the space bonus is multiplied by a weight which decreases according to occupancy. - - template template - Score Evaluation::space() const { - - // Early exit if, for example, both queens or 6 minor pieces have been exchanged - if (pos.non_pawn_material() < SpaceThreshold) - return SCORE_ZERO; - - constexpr Color Them = ~Us; - constexpr Direction Down = -pawn_push(Us); - constexpr Bitboard SpaceMask = - Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB) - : CenterFiles & (Rank7BB | Rank6BB | Rank5BB); - - // Find the available squares for our pieces inside the area defined by SpaceMask - Bitboard safe = SpaceMask - & ~pos.pieces(Us, PAWN) - & ~attackedBy[Them][PAWN]; - - // Find all squares which are at most three squares behind some friendly pawn - Bitboard behind = pos.pieces(Us, PAWN); - behind |= shift(behind); - behind |= shift(behind); - - // Compute space score based on the number of safe squares and number of our pieces - // increased with number of total blocked pawns in position. - int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); - int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); - Score score = make_score(bonus * weight * weight / 16, 0); - - if constexpr (T) - Trace::add(SPACE, Us, score); - - return score; - } - - - // Evaluation::winnable() adjusts the midgame and endgame score components, based on - // the known attacking/defending status of the players. The final value is derived - // by interpolation from the midgame and endgame values. - - template - Value Evaluation::winnable(Score score) const { - - int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) - + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); - - bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) - && (pos.pieces(PAWN) & KingSide); - - bool almostUnwinnable = outflanking < 0 - && !pawnsOnBothFlanks; - - bool infiltration = rank_of(pos.square(WHITE)) > RANK_4 - || rank_of(pos.square(BLACK)) < RANK_5; - - // Compute the initiative bonus for the attacking side - int complexity = 9 * pe->passed_count() - + 12 * pos.count() - + 9 * outflanking - + 21 * pawnsOnBothFlanks - + 24 * infiltration - + 51 * !pos.non_pawn_material() - - 43 * almostUnwinnable - -110 ; - - Value mg = mg_value(score); - Value eg = eg_value(score); - - // Now apply the bonus: note that we find the attacking side by extracting the - // sign of the midgame or endgame values, and that we carefully cap the bonus - // so that the midgame and endgame scores do not change sign after the bonus. - int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0); - int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); - - mg += u; - eg += v; - - // Compute the scale factor for the winning side - Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; - int sf = me->scale_factor(pos, strongSide); - - // If scale factor is not already specific, scale up/down via general heuristics - if (sf == SCALE_FACTOR_NORMAL) - { - if (pos.opposite_bishops()) - { - // For pure opposite colored bishops endgames use scale factor - // based on the number of passed pawns of the strong side. - if ( pos.non_pawn_material(WHITE) == BishopValueMg - && pos.non_pawn_material(BLACK) == BishopValueMg) - sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); - // For every other opposite colored bishops endgames use scale factor - // based on the number of all pieces of the strong side. - else - sf = 22 + 3 * pos.count(strongSide); - } - // For rook endgames with strong side not having overwhelming pawn number advantage - // and its pawns being on one flank and weak side protecting its pieces with a king - // use lower scale factor. - else if ( pos.non_pawn_material(WHITE) == RookValueMg - && pos.non_pawn_material(BLACK) == RookValueMg - && pos.count(strongSide) - pos.count(~strongSide) <= 1 - && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) - && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) - sf = 36; - // For queen vs no queen endgames use scale factor - // based on number of minors of side that doesn't have queen. - else if (pos.count() == 1) - sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) - : pos.count(WHITE) + pos.count(WHITE)); - // In every other case use scale factor based on - // the number of pawns of the strong side reduced if pawns are on a single flank. - else - sf = std::min(sf, 36 + 7 * pos.count(strongSide)) - 4 * !pawnsOnBothFlanks; - - // Reduce scale factor in case of pawns being on a single flank - sf -= 4 * !pawnsOnBothFlanks; - } - - // Interpolate between the middlegame and (scaled by 'sf') endgame score - v = mg * int(me->game_phase()) - + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; - v /= PHASE_MIDGAME; - - if constexpr (T) - { - Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); - Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); - } - - return Value(v); - } - - - // Evaluation::value() is the main function of the class. It computes the various - // parts of the evaluation and returns the value of the position from the point - // of view of the side to move. - - template - Value Evaluation::value() { - - assert(!pos.checkers()); - - // Probe the material hash table - me = Material::probe(pos); - - // If we have a specialized evaluation function for the current material - // configuration, call it and return. - if (me->specialized_eval_exists()) - return me->evaluate(pos); - - // Initialize score by reading the incrementally updated scores included in - // the position object (material + piece square tables) and the material - // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance(); - - // Probe the pawn hash table - pe = Pawns::probe(pos); - score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK); - - // Early exit if score is high - auto lazy_skip = [&](Value lazyThreshold) { - return abs(mg_value(score) + eg_value(score)) > lazyThreshold - + std::abs(pos.this_thread()->bestValue) * 5 / 4 - + pos.non_pawn_material() / 32; - }; - - if (lazy_skip(LazyThreshold1)) - goto make_v; - - // Main evaluation begins here - initialize(); - initialize(); - - // Pieces evaluated first (also populates attackedBy, attackedBy2). - // Note that the order of evaluation of the terms is left unspecified. - score += pieces() - pieces() - + pieces() - pieces() - + pieces() - pieces() - + pieces() - pieces(); - - score += mobility[WHITE] - mobility[BLACK]; - - // More complex interactions that require fully populated attack bitboards - score += king< WHITE>() - king< BLACK>() - + passed< WHITE>() - passed< BLACK>(); - - if (lazy_skip(LazyThreshold2)) - goto make_v; - - score += threats() - threats() - + space< WHITE>() - space< BLACK>(); - -make_v: - // Derive single value from mg and eg parts of score - Value v = winnable(score); - - // In case of tracing add all remaining individual evaluation terms - if constexpr (T) - { - Trace::add(MATERIAL, pos.psq_score()); - Trace::add(IMBALANCE, me->imbalance()); - Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK)); - Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); - } - - // Evaluation grain - v = (v / 16) * 16; - - // Side to move point of view - v = (pos.side_to_move() == WHITE ? v : -v); - - return v; - } - -} // namespace Eval - - /// evaluate() is the evaluator for the outer world. It returns a static /// evaluation of the position from the point of view of the side to move. @@ -1053,27 +147,17 @@ Value Eval::evaluate(const Position& pos) { Value v; Value psq = pos.psq_eg_stm(); - // We use the much less accurate but faster Classical eval when the NNUE - // option is set to false. Otherwise we use the NNUE eval unless the - // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC) - bool useClassical = !useNNUE || abs(psq) > 2048; + int nnueComplexity; + int npm = pos.non_pawn_material() / 64; - if (useClassical) - v = Evaluation(pos).value(); - else - { - int nnueComplexity; - int npm = pos.non_pawn_material() / 64; + Color stm = pos.side_to_move(); + Value optimism = pos.this_thread()->optimism[stm]; - Color stm = pos.side_to_move(); - Value optimism = pos.this_thread()->optimism[stm]; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - - // Blend optimism with nnue complexity and (semi)classical complexity - optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; - v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; - } + // Blend optimism with nnue complexity and (semi)classical complexity + optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; + v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; // Damp down the evaluation linearly when shuffling v = v * (200 - pos.rule50_count()) / 214; @@ -1094,62 +178,26 @@ std::string Eval::trace(Position& pos) { if (pos.checkers()) return "Final evaluation: none (in check)"; - std::stringstream ss; - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - - Value v; - - std::memset(scores, 0, sizeof(scores)); - // Reset any global variable used in eval pos.this_thread()->bestValue = VALUE_ZERO; pos.this_thread()->optimism[WHITE] = VALUE_ZERO; pos.this_thread()->optimism[BLACK] = VALUE_ZERO; - v = Evaluation(pos).value(); - - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) - << " Contributing terms for the classical eval:\n" - << "+------------+-------------+-------------+-------------+\n" - << "| Term | White | Black | Total |\n" - << "| | MG EG | MG EG | MG EG |\n" - << "+------------+-------------+-------------+-------------+\n" - << "| Material | " << Term(MATERIAL) - << "| Imbalance | " << Term(IMBALANCE) - << "| Pawns | " << Term(PAWN) - << "| Knights | " << Term(KNIGHT) - << "| Bishops | " << Term(BISHOP) - << "| Rooks | " << Term(ROOK) - << "| Queens | " << Term(QUEEN) - << "| Mobility | " << Term(MOBILITY) - << "|King safety | " << Term(KING) - << "| Threats | " << Term(THREAT) - << "| Passed | " << Term(PASSED) - << "| Space | " << Term(SPACE) - << "| Winnable | " << Term(WINNABLE) - << "+------------+-------------+-------------+-------------+\n" - << "| Total | " << Term(TOTAL) - << "+------------+-------------+-------------+-------------+\n"; - - if (Eval::useNNUE) - ss << '\n' << NNUE::trace(pos) << '\n'; + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + Value v; + v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n"; - if (Eval::useNNUE) - { - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; - } + ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; v = evaluate(pos); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << to_cp(v) << " (white side)"; - if (Eval::useNNUE) - ss << " [with scaled NNUE, hybrid, ...]"; + ss << " [with scaled NNUE, ...]"; ss << "\n"; return ss.str(); diff --git a/src/main.cpp b/src/main.cpp index c40e0fa3492..593408f63a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,6 @@ #include #include "bitboard.h" -#include "endgame.h" #include "position.h" #include "psqt.h" #include "search.h" @@ -40,8 +39,6 @@ int main(int argc, char* argv[]) { PSQT::init(); Bitboards::init(); Position::init(); - Bitbases::init(); - Endgames::init(); Threads.set(size_t(Options["Threads"])); Search::clear(); // After threads are up Eval::NNUE::init(); diff --git a/src/material.cpp b/src/material.cpp deleted file mode 100644 index 7102f8799ea..00000000000 --- a/src/material.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include // For std::memset - -#include "material.h" -#include "thread.h" - -using namespace std; - -namespace Stockfish { - -namespace { - #define S(mg, eg) make_score(mg, eg) - - // Polynomial material imbalance parameters - - // One Score parameter for each pair (our piece, another of our pieces) - constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = { - // OUR PIECE 2 - // bishop pair pawn knight bishop rook queen - {S(1419, 1455) }, // Bishop pair - {S( 101, 28), S( 37, 39) }, // Pawn - {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1 - {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop - {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook - {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen - }; - - // One Score parameter for each pair (our piece, their piece) - constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = { - // THEIR PIECE - // bishop pair pawn knight bishop rook queen - { }, // Bishop pair - {S( 33, 30) }, // Pawn - {S( 46, 18), S(106, 84) }, // Knight OUR PIECE - {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop - {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook - {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen - }; - - #undef S - - // Endgame evaluation and scaling functions are accessed directly and not through - // the function maps because they correspond to more than one material hash key. - Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; - - Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; - Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; - - // Helper used to detect a given material distribution - bool is_KXK(const Position& pos, Color us) { - return !more_than_one(pos.pieces(~us)) - && pos.non_pawn_material(us) >= RookValueMg; - } - - bool is_KBPsK(const Position& pos, Color us) { - return pos.non_pawn_material(us) == BishopValueMg - && pos.count(us) >= 1; - } - - bool is_KQKRPs(const Position& pos, Color us) { - return !pos.count(us) - && pos.non_pawn_material(us) == QueenValueMg - && pos.count(~us) == 1 - && pos.count(~us) >= 1; - } - - - /// imbalance() calculates the imbalance by comparing the piece count of each - /// piece type for both colors. - - template - Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) { - - constexpr Color Them = ~Us; - - Score bonus = SCORE_ZERO; - - // Second-degree polynomial material imbalance, by Tord Romstad - for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) - { - if (!pieceCount[Us][pt1]) - continue; - - int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1]; - - for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2) - v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] - + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; - - bonus += pieceCount[Us][pt1] * v; - } - - return bonus; - } - -} // namespace - -namespace Material { - - -/// Material::probe() looks up the current position's material configuration in -/// the material hash table. It returns a pointer to the Entry if the position -/// is found. Otherwise a new Entry is computed and stored there, so we don't -/// have to recompute all when the same material configuration occurs again. - -Entry* probe(const Position& pos) { - - Key key = pos.material_key(); - Entry* e = pos.this_thread()->materialTable[key]; - - if (e->key == key) - return e; - - std::memset(e, 0, sizeof(Entry)); - e->key = key; - e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; - - Value npm_w = pos.non_pawn_material(WHITE); - Value npm_b = pos.non_pawn_material(BLACK); - Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); - - // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] - e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); - - // Let's look if we have a specialized evaluation function for this particular - // material configuration. Firstly we look for a fixed configuration one, then - // for a generic one if the previous search failed. - if ((e->evaluationFunction = Endgames::probe(key)) != nullptr) - return e; - - for (Color c : { WHITE, BLACK }) - if (is_KXK(pos, c)) - { - e->evaluationFunction = &EvaluateKXK[c]; - return e; - } - - // OK, we didn't find any special evaluation function for the current material - // configuration. Is there a suitable specialized scaling function? - const auto* sf = Endgames::probe(key); - - if (sf) - { - e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned - return e; - } - - // We didn't find any specialized scaling function, so fall back on generic - // ones that refer to more than one material distribution. Note that in this - // case we don't return after setting the function. - for (Color c : { WHITE, BLACK }) - { - if (is_KBPsK(pos, c)) - e->scalingFunction[c] = &ScaleKBPsK[c]; - - else if (is_KQKRPs(pos, c)) - e->scalingFunction[c] = &ScaleKQKRPs[c]; - } - - if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board - { - if (!pos.count(BLACK)) - { - assert(pos.count(WHITE) >= 2); - - e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; - } - else if (!pos.count(WHITE)) - { - assert(pos.count(BLACK) >= 2); - - e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; - } - else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) - { - // This is a special case because we set scaling functions - // for both colors instead of only one. - e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; - e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; - } - } - - // Zero or just one pawn makes it difficult to win, even with a small material - // advantage. This catches some trivial draws like KK, KBK and KNK and gives a - // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). - if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) - e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW : - npm_b <= BishopValueMg ? 4 : 14); - - if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) - e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW : - npm_w <= BishopValueMg ? 4 : 14); - - // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder - // for the bishop pair "extended piece", which allows us to be more flexible - // in defining bishop pair bonuses. - const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = { - { pos.count(WHITE) > 1, pos.count(WHITE), pos.count(WHITE), - pos.count(WHITE) , pos.count(WHITE), pos.count(WHITE) }, - { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), - pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; - - e->score = (imbalance(pieceCount) - imbalance(pieceCount)) / 16; - return e; -} - -} // namespace Material - -} // namespace Stockfish diff --git a/src/material.h b/src/material.h deleted file mode 100644 index 9acf78f5ab4..00000000000 --- a/src/material.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef MATERIAL_H_INCLUDED -#define MATERIAL_H_INCLUDED - -#include "endgame.h" -#include "misc.h" -#include "position.h" -#include "types.h" - -namespace Stockfish::Material { - -/// Material::Entry contains various information about a material configuration. -/// It contains a material imbalance evaluation, a function pointer to a special -/// endgame evaluation function (which in most cases is nullptr, meaning that the -/// standard evaluation function will be used), and scale factors. -/// -/// The scale factors are used to scale the evaluation score up or down. For -/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4, -/// which will result in scores of absolute value less than one pawn. - -struct Entry { - - Score imbalance() const { return score; } - Phase game_phase() const { return (Phase)gamePhase; } - bool specialized_eval_exists() const { return evaluationFunction != nullptr; } - Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } - - // scale_factor() takes a position and a color as input and returns a scale factor - // for the given color. We have to provide the position in addition to the color - // because the scale factor may also be a function which should be applied to - // the position. For instance, in KBP vs K endgames, the scaling function looks - // for rook pawns and wrong-colored bishops. - ScaleFactor scale_factor(const Position& pos, Color c) const { - ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos) - : SCALE_FACTOR_NONE; - return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]); - } - - Key key; - const EndgameBase* evaluationFunction; - const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each - // side (e.g. KPKP, KBPsK) - Score score; - int16_t gamePhase; - uint8_t factor[COLOR_NB]; -}; - -using Table = HashTable; - -Entry* probe(const Position& pos); - -} // namespace Stockfish::Material - -#endif // #ifndef MATERIAL_H_INCLUDED diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 329adfdaa9e..a1a90023909 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -137,8 +137,7 @@ namespace Stockfish::Eval::NNUE { } void hint_common_parent_position(const Position& pos) { - if (Eval::useNNUE) - featureTransformer->hint_common_access(pos); + featureTransformer->hint_common_access(pos); } // Evaluation function. Perform differential calculation. diff --git a/src/pawns.cpp b/src/pawns.cpp deleted file mode 100644 index 0ccafd9e9c8..00000000000 --- a/src/pawns.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include -#include - -#include "bitboard.h" -#include "pawns.h" -#include "position.h" -#include "thread.h" - -namespace Stockfish { - -namespace { - - #define V Value - #define S(mg, eg) make_score(mg, eg) - - // Pawn penalties - constexpr Score Backward = S( 6, 19); - constexpr Score Doubled = S(11, 51); - constexpr Score DoubledEarly = S(17, 7); - constexpr Score Isolated = S( 1, 20); - constexpr Score WeakLever = S( 2, 57); - constexpr Score WeakUnopposed = S(15, 18); - - // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) }; - - constexpr Score BlockedStorm[RANK_NB] = { - S(0, 0), S(0, 0), S(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5) - }; - - // Connected pawn bonus - constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 86 }; - - // Strength of pawn shelter for our king by [distance from edge][rank]. - // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. - constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = { - { V(-2), V(85), V(95), V(53), V(39), V(23), V(25) }, - { V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) }, - { V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) }, - { V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) } - }; - - // Danger of enemy pawns moving toward our king by [distance from edge][rank]. - // RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn - // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn - // on edge, likely blocked by our king. - constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { - { V(94), V(-280), V(-170), V(90), V(59), V(47), V(53) }, - { V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) }, - { V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) }, - { V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) } - }; - - - // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties - // for king when the king is on a semi-open or open file. - constexpr Score KingOnFile[2][2] = {{ S(-18,11), S(-6,-3) }, - { S( 0, 0), S( 5,-4) }}; - - #undef S - #undef V - - - /// evaluate() calculates a score for the static pawn structure of the given position. - /// We cannot use the location of pieces or king in this function, as the evaluation - /// of the pawn structure will be stored in a small cache for speed reasons, and will - /// be re-used even when the pieces have moved. - - template - Score evaluate(const Position& pos, Pawns::Entry* e) { - - constexpr Color Them = ~Us; - constexpr Direction Up = pawn_push(Us); - constexpr Direction Down = -Up; - - Bitboard neighbours, stoppers, support, phalanx, opposed; - Bitboard lever, leverPush, blocked; - Square s; - bool backward, passed, doubled; - Score score = SCORE_ZERO; - Bitboard b = pos.pieces(Us, PAWN); - - Bitboard ourPawns = pos.pieces( Us, PAWN); - Bitboard theirPawns = pos.pieces(Them, PAWN); - - Bitboard doubleAttackThem = pawn_double_attacks_bb(theirPawns); - - e->passedPawns[Us] = 0; - e->kingSquares[Us] = SQ_NONE; - e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb(ourPawns); - e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); - - // Loop through all pawns of the current color and score each pawn - while (b) - { - s = pop_lsb(b); - - assert(pos.piece_on(s) == make_piece(Us, PAWN)); - - Rank r = relative_rank(Us, s); - - // Flag the pawn - opposed = theirPawns & forward_file_bb(Us, s); - blocked = theirPawns & (s + Up); - stoppers = theirPawns & passed_pawn_span(Us, s); - lever = theirPawns & pawn_attacks_bb(Us, s); - leverPush = theirPawns & pawn_attacks_bb(Us, s + Up); - doubled = ourPawns & (s - Up); - neighbours = ourPawns & adjacent_files_bb(s); - phalanx = neighbours & rank_bb(s); - support = neighbours & rank_bb(s - Up); - - if (doubled) - { - // Additional doubled penalty if none of their pawns is fixed - if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) - score -= DoubledEarly; - } - - // A pawn is backward when it is behind all pawns of the same color on - // the adjacent files and cannot safely advance. - backward = !(neighbours & forward_ranks_bb(Them, s + Up)) - && (leverPush | blocked); - - // Compute additional span if pawn is not backward nor blocked - if (!backward && !blocked) - e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); - - // A pawn is passed if one of the three following conditions is true: - // (a) there is no stoppers except some levers - // (b) the only stoppers are the leverPush, but we outnumber them - // (c) there is only one front stopper which can be levered. - // (Refined in Evaluation::passed) - passed = !(stoppers ^ lever) - || ( !(stoppers ^ leverPush) - && popcount(phalanx) >= popcount(leverPush)) - || ( stoppers == blocked && r >= RANK_5 - && (shift(support) & ~(theirPawns | doubleAttackThem))); - - passed &= !(forward_file_bb(Us, s) & ourPawns); - - // Passed pawns will be properly scored later in evaluation when we have - // full attack info. - if (passed) - e->passedPawns[Us] |= s; - - // Score this pawn - if (support | phalanx) - { - int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) - + 22 * popcount(support); - - score += make_score(v, v * (r - 2) / 4); - } - - else if (!neighbours) - { - if ( opposed - && (ourPawns & forward_file_bb(Them, s)) - && !(theirPawns & adjacent_files_bb(s))) - score -= Doubled; - else - score -= Isolated - + WeakUnopposed * !opposed; - } - - else if (backward) - score -= Backward - + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s); - - if (!support) - score -= Doubled * doubled - + WeakLever * more_than_one(lever); - - if (blocked && r >= RANK_5) - score += BlockedPawn[r - RANK_5]; - } - - return score; - } - -} // namespace - -namespace Pawns { - - -/// Pawns::probe() looks up the current position's pawns configuration in -/// the pawns hash table. It returns a pointer to the Entry if the position -/// is found. Otherwise a new Entry is computed and stored there, so we don't -/// have to recompute all when the same pawns configuration occurs again. - -Entry* probe(const Position& pos) { - - Key key = pos.pawn_key(); - Entry* e = pos.this_thread()->pawnsTable[key]; - - if (e->key == key) - return e; - - e->key = key; - e->blockedCount = 0; - e->scores[WHITE] = evaluate(pos, e); - e->scores[BLACK] = evaluate(pos, e); - - return e; -} - - -/// Entry::evaluate_shelter() calculates the shelter bonus and the storm -/// penalty for a king, looking at the king file and the two closest files. - -template -Score Entry::evaluate_shelter(const Position& pos, Square ksq) const { - - constexpr Color Them = ~Us; - - Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq); - Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them]; - Bitboard theirPawns = b & pos.pieces(Them); - - Score bonus = make_score(5, 5); - - File center = std::clamp(file_of(ksq), FILE_B, FILE_G); - for (File f = File(center - 1); f <= File(center + 1); ++f) - { - b = ourPawns & file_bb(f); - int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; - - b = theirPawns & file_bb(f); - int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0; - - int d = edge_distance(f); - bonus += make_score(ShelterStrength[d][ourRank], 0); - - if (ourRank && (ourRank == theirRank - 1)) - bonus -= BlockedStorm[theirRank]; - else - bonus -= make_score(UnblockedStorm[d][theirRank], 0); - } - - // King On File - bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)]; - - return bonus; -} - - -/// Entry::do_king_safety() calculates a bonus for king safety. It is called only -/// when king square changes, which is about 20% of total king_safety() calls. - -template -Score Entry::do_king_safety(const Position& pos) { - - Square ksq = pos.square(Us); - kingSquares[Us] = ksq; - castlingRights[Us] = pos.castling_rights(Us); - auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); }; - - Score shelter = evaluate_shelter(pos, ksq); - - // If we can castle use the bonus after castling if it is bigger - - if (pos.can_castle(Us & KING_SIDE)) - shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_G1)), compare); - - if (pos.can_castle(Us & QUEEN_SIDE)) - shelter = std::max(shelter, evaluate_shelter(pos, relative_square(Us, SQ_C1)), compare); - - // In endgame we like to bring our king near our closest pawn - Bitboard pawns = pos.pieces(Us, PAWN); - int minPawnDist = 6; - - if (pawns & attacks_bb(ksq)) - minPawnDist = 1; - else while (pawns) - minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns))); - - return shelter - make_score(0, 16 * minPawnDist); -} - -// Explicit template instantiation -template Score Entry::do_king_safety(const Position& pos); -template Score Entry::do_king_safety(const Position& pos); - -} // namespace Pawns - -} // namespace Stockfish diff --git a/src/pawns.h b/src/pawns.h deleted file mode 100644 index d20e7c2ebe5..00000000000 --- a/src/pawns.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef PAWNS_H_INCLUDED -#define PAWNS_H_INCLUDED - -#include "misc.h" -#include "position.h" -#include "types.h" - -namespace Stockfish::Pawns { - -/// Pawns::Entry contains various information about a pawn structure. A lookup -/// to the pawn hash table (performed by calling the probe function) returns a -/// pointer to an Entry object. - -struct Entry { - - Score pawn_score(Color c) const { return scores[c]; } - Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } - Bitboard passed_pawns(Color c) const { return passedPawns[c]; } - Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } - int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); } - int blocked_count() const { return blockedCount; } - - template - Score king_safety(const Position& pos) { - return kingSquares[Us] == pos.square(Us) && castlingRights[Us] == pos.castling_rights(Us) - ? kingSafety[Us] : (kingSafety[Us] = do_king_safety(pos)); - } - - template - Score do_king_safety(const Position& pos); - - template - Score evaluate_shelter(const Position& pos, Square ksq) const; - - Key key; - Score scores[COLOR_NB]; - Bitboard passedPawns[COLOR_NB]; - Bitboard pawnAttacks[COLOR_NB]; - Bitboard pawnAttacksSpan[COLOR_NB]; - Square kingSquares[COLOR_NB]; - Score kingSafety[COLOR_NB]; - int castlingRights[COLOR_NB]; - int blockedCount; -}; - -using Table = HashTable; - -Entry* probe(const Position& pos); - -} // namespace Stockfish::Pawns - -#endif // #ifndef PAWNS_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp index a052cf32f03..6ecc52f8446 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -751,13 +751,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { else st->nonPawnMaterial[them] -= PieceValue[MG][captured]; - if (Eval::useNNUE) - { - dp.dirty_num = 2; // 1 piece moved, 1 piece captured - dp.piece[1] = captured; - dp.from[1] = capsq; - dp.to[1] = SQ_NONE; - } + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; // Update board and piece lists remove_piece(capsq); @@ -765,7 +762,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; - prefetch(thisThread->materialTable[st->materialKey]); // Reset rule 50 counter st->rule50 = 0; @@ -792,12 +788,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Move the piece. The tricky Chess960 castling is handled earlier if (type_of(m) != CASTLING) { - if (Eval::useNNUE) - { - dp.piece[0] = pc; - dp.from[0] = from; - dp.to[0] = to; - } + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; move_piece(from, to); } @@ -823,15 +816,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { remove_piece(to); put_piece(promotion, to); - if (Eval::useNNUE) - { - // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE - dp.to[0] = SQ_NONE; - dp.piece[dp.dirty_num] = promotion; - dp.from[dp.dirty_num] = SQ_NONE; - dp.to[dp.dirty_num] = to; - dp.dirty_num++; - } + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; // Update hash keys k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; @@ -961,7 +951,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); - if (Do && Eval::useNNUE) + if (Do) { auto& dp = st->dirtyPiece; dp.piece[0] = make_piece(us, KING); diff --git a/src/thread.h b/src/thread.h index 09bdb470b21..aa9db2f3633 100644 --- a/src/thread.h +++ b/src/thread.h @@ -25,9 +25,7 @@ #include #include -#include "material.h" #include "movepick.h" -#include "pawns.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" @@ -57,8 +55,6 @@ class Thread { void wait_for_search_finished(); size_t id() const { return idx; } - Pawns::Table pawnsTable; - Material::Table materialTable; size_t pvIdx, pvLast; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; diff --git a/src/ucioption.cpp b/src/ucioption.cpp index f6342e5cb57..27f436d3b37 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -43,7 +43,6 @@ static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } static void on_logger(const Option& o) { start_logger(o); } static void on_threads(const Option& o) { Threads.set(size_t(o)); } static void on_tb_path(const Option& o) { Tablebases::init(o); } -static void on_use_NNUE(const Option&) { Eval::NNUE::init(); } static void on_eval_file(const Option&) { Eval::NNUE::init(); } /// Our case insensitive less() function as required by UCI protocol @@ -79,7 +78,6 @@ void init(OptionsMap& o) { o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["Use NNUE"] << Option(true, on_use_NNUE); o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); } From f972947492c513b7c0c4046058354b44a9ae9f04 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 12 Jul 2023 19:46:33 +0200 Subject: [PATCH 0145/1309] Cleanup code after removal of classical evaluation This includes the following changes: - Remove declaration of removed global variable - Adapt string that mentions removed UCI option closes https://github.com/official-stockfish/Stockfish/pull/4675 No functional change --- src/evaluate.cpp | 2 +- src/evaluate.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 2ab4fa404e0..a1bcdd20420 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -119,7 +119,7 @@ namespace Eval { { string msg1 = "Network evaluation parameters compatible with the engine must be available."; - string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; + string msg2 = "The network file " + eval_file + " was not loaded successfully."; string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); string msg5 = "The engine will be terminated now."; diff --git a/src/evaluate.h b/src/evaluate.h index abdbef9010d..586e3b81f04 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,6 @@ namespace Eval { std::string trace(Position& pos); Value evaluate(const Position& pos); - extern bool useNNUE; extern std::string currentEvalFileName; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue From 529d3be8e245c06b62a45525cb9325ed6e50b636 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 11 Jul 2023 22:19:48 -0700 Subject: [PATCH 0146/1309] More simplifications and cleanup in affine_transform_sparse_input.h closes https://github.com/official-stockfish/Stockfish/pull/4677 No functional change --- .../layers/affine_transform_sparse_input.h | 44 +++++-------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 3c7defcc42c..a5bea08e74b 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -34,43 +34,15 @@ */ namespace Stockfish::Eval::NNUE::Layers { -#if defined(__GNUC__) // GCC, Clang, ICC - - static inline IndexType lsb_(std::uint32_t b) { - assert(b); - return IndexType(__builtin_ctzl(b)); - } - -#elif defined(_MSC_VER) // MSVC - - static inline IndexType lsb_(std::uint32_t b) { - assert(b); - unsigned long idx; - _BitScanForward(&idx, b); - return (IndexType) idx; - } - -#else // Compiler is neither GCC nor MSVC compatible - -#error "Compiler not supported." - -#endif - #if defined(USE_SSSE3) alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ std::array, 256> v{}; - for (int i = 0; i < 256; ++i) + for (unsigned i = 0; i < 256; ++i) { - int j = i; - int k = 0; + std::uint64_t j = i, k = 0; while(j) - { - const IndexType lsbIndex = lsb_(std::uint32_t(j)); - j &= j - 1; - v[i][k] = lsbIndex; - ++k; - } + v[i][k++] = pop_lsb(j); } return v; }(); @@ -83,7 +55,11 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) #elif defined (USE_AVX2) using vec_t = __m256i; - #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif #elif defined (USE_SSSE3) using vec_t = __m128i; #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) @@ -97,8 +73,8 @@ namespace Stockfish::Eval::NNUE::Layers { const auto inputVector = reinterpret_cast(input); IndexType count = 0; - __m128i base = _mm_set1_epi16(0); - __m128i increment = _mm_set1_epi16(8); + __m128i base = _mm_setzero_si128(); + const __m128i increment = _mm_set1_epi16(8); for (IndexType i = 0; i < NumChunks; ++i) { // bitmask of nonzero values in this chunk From f5ab5832c6d02ec742b507bcb42181403a848bb9 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 12 Jul 2023 19:34:07 +0200 Subject: [PATCH 0147/1309] Generate binaries for more advanced architectures use intel's Software Development Emulator (SDE) in the actions that build the binaries. This allows for building on Windows and Linux binaries for - x86-64-avx512 - x86-64-vnni256 - x86-64-vnni512 (x86-64-avxvnni needs more recent gcc in the actions) also build x86-64-avx2 on macos. closes https://github.com/official-stockfish/Stockfish/pull/4679 No functional change --- .github/workflows/stockfish_binaries.yml | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index f7669b479aa..fa330974dd2 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -9,6 +9,7 @@ jobs: COMPILER: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} + SDE: ${{ matrix.config.sde }} NAME: ${{ matrix.config.simple_name }} BINARY: ${{ matrix.binaries }} strategy: @@ -21,6 +22,7 @@ jobs: comp: gcc shell: bash archive_ext: tar + sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos @@ -37,17 +39,28 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe + sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future archive_ext: zip binaries: - x86-64 - x86-64-modern - x86-64-avx2 - x86-64-bmi2 + # - x86-64-avxvnni needs more recent gcc + - x86-64-avx512 + - x86-64-vnni256 + - x86-64-vnni512 exclude: - - binaries: x86-64-avx2 - config: { os: macos-12 } - binaries: x86-64-bmi2 config: { os: macos-12 } + #- binaries: x86-64-avxvnni + # config: { os: macos-12 } + - binaries: x86-64-avx512 + config: { os: macos-12 } + - binaries: x86-64-vnni256 + config: { os: macos-12 } + - binaries: x86-64-vnni512 + config: { os: macos-12 } defaults: run: working-directory: src @@ -68,6 +81,13 @@ jobs: msystem: ${{ matrix.config.msys_sys }} install: mingw-w64-${{ matrix.config.msys_env }} make git zip + - name: Download SDE package + if: runner.os == 'Linux' || runner.os == 'Windows' + uses: petarpetrovt/setup-sde@v2.1 + with: + environmentVariableName: SDE_DIR + sdeVersion: 9.14.0 + - name: Download the used network from the fishtest framework run: make net @@ -84,7 +104,7 @@ jobs: - name: Compile ${{ matrix.binaries }} build run: | - make -j2 profile-build ARCH=$BINARY COMP=$COMP + make -j2 profile-build ARCH=$BINARY COMP=$COMP SDE_PATH="$SDE" make strip ARCH=$BINARY COMP=$COMP mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT From acdbf45171ba8bd0322e3bda0900e9cb2f8fb846 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 15 Jul 2023 00:51:14 +0300 Subject: [PATCH 0148/1309] Use more expressive names for bonus1 and bonus2 closes https://github.com/official-stockfish/Stockfish/pull/4683 No functional change --- src/search.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1f8f361c409..11a52326dbe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1703,28 +1703,28 @@ namespace { Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus1 = stat_bonus(depth + 1); + int quietMoveBonus = stat_bonus(depth + 1); if (!pos.capture_stage(bestMove)) { - int bonus2 = bestValue > beta + 145 ? bonus1 // larger bonus - : stat_bonus(depth); // smaller bonus + int bestMoveBonus = bestValue > beta + 145 ? quietMoveBonus // larger bonus + : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move - update_quiet_stats(pos, ss, bestMove, bonus2); + update_quiet_stats(pos, ss, bestMove, bestMoveBonus); // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; + update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bestMoveBonus); } } else { // Increase stats for the best move in case it was a capture move captured = type_of(pos.piece_on(to_sq(bestMove))); - captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; + captureHistory[moved_piece][to_sq(bestMove)][captured] << quietMoveBonus; } // Extra penalty for a quiet early move that was not a TT move or @@ -1732,14 +1732,14 @@ namespace { if ( prevSq != SQ_NONE && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) && !pos.captured_piece()) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; + captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveBonus; } } From a3a91f3f9f4184a699a827c66e806d3a01656446 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 14 Jul 2023 17:43:56 +0200 Subject: [PATCH 0149/1309] Build and test more binaries in CI use a fixed compiler on Linux and Windows (right now gcc 11). build avxvnni on Windows (Linux needs updated core utils) build x86-32 on Linux (Windows needs other mingw) fix a Makefile issue where a failed PGOBENCH would not stop the build reuse the WINE_PATH for SDE as we do for QEMU use WINE_PATH variable also for the signature verify the bench for each of the binaries do not build x86-64-avx2 on macos closes https://github.com/official-stockfish/Stockfish/pull/4682 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 8 ++++ .github/workflows/stockfish_binaries.yml | 45 ++++++++++++++++---- src/Makefile | 11 ++--- tests/signature.sh | 2 +- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 4db216eb5fb..dfe4e2a24ce 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -70,6 +70,13 @@ jobs: echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV fi + - name: Extract the bench number from the commit history + run: | + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" + - name: Download the used network from the fishtest framework run: make net @@ -97,6 +104,7 @@ jobs: make clean make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU make strip ARCH=$BINARY COMP=$COMP + WINE_PATH=$EMU ../tests/signature.sh $benchref mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT - name: Remove non src files diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index fa330974dd2..e761c845396 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -22,7 +22,7 @@ jobs: comp: gcc shell: bash archive_ext: tar - sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future + sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future -- - name: MacOS 12 Apple Clang os: macos-12 simple_name: macos @@ -39,22 +39,29 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe - sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future + sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future -- archive_ext: zip binaries: + - x86-32 - x86-64 - x86-64-modern - x86-64-avx2 - x86-64-bmi2 - # - x86-64-avxvnni needs more recent gcc + - x86-64-avxvnni - x86-64-avx512 - x86-64-vnni256 - x86-64-vnni512 exclude: + - binaries: x86-32 + config: { os: macos-12 } + - binaries: x86-64-avx2 + config: { os: macos-12 } - binaries: x86-64-bmi2 config: { os: macos-12 } - #- binaries: x86-64-avxvnni - # config: { os: macos-12 } + - binaries: x86-64-avxvnni + config: { os: macos-12 } + - binaries: x86-64-avxvnni + config: { ubuntu-20.04 } - binaries: x86-64-avx512 config: { os: macos-12 } - binaries: x86-64-vnni256 @@ -70,9 +77,17 @@ jobs: with: fetch-depth: 0 - - name: Download required linux packages + - name: Download required Linux packages + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install g++-multilib g++-11-multilib + + - name: Install fixed GCC on Linux if: runner.os == 'Linux' - run: sudo apt update + uses: egor-tensin/setup-gcc@v1 + with: + version: 11 - name: Setup msys and install required packages if: runner.os == 'Windows' @@ -81,6 +96,12 @@ jobs: msystem: ${{ matrix.config.msys_sys }} install: mingw-w64-${{ matrix.config.msys_env }} make git zip + - name: Install fixed GCC on Windows + if: runner.os == 'Windows' + run: | + wget https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst + pacman -U mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst --noconfirm + - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' uses: petarpetrovt/setup-sde@v2.1 @@ -91,6 +112,13 @@ jobs: - name: Download the used network from the fishtest framework run: make net + - name: Extract the bench number from the commit history + run: | + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" + - name: Check compiler run: $COMPILER -v @@ -104,8 +132,9 @@ jobs: - name: Compile ${{ matrix.binaries }} build run: | - make -j2 profile-build ARCH=$BINARY COMP=$COMP SDE_PATH="$SDE" + make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE" make strip ARCH=$BINARY COMP=$COMP + WINE_PATH="$SDE" ../tests/signature.sh $benchref mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT - name: Remove non src files diff --git a/src/Makefile b/src/Makefile index a0f098fa678..966ac2a7a60 100644 --- a/src/Makefile +++ b/src/Makefile @@ -49,11 +49,7 @@ PREFIX = /usr/local BINDIR = $(PREFIX)/bin ### Built-in benchmark for pgo-builds -ifeq ($(SDE_PATH),) - PGOBENCH = $(WINE_PATH) ./$(EXE) bench -else - PGOBENCH = $(SDE_PATH) -- $(WINE_PATH) ./$(EXE) bench -endif +PGOBENCH = $(WINE_PATH) ./$(EXE) bench ### Source and object files SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ @@ -849,7 +845,8 @@ profile-build: net config-sanity objclean profileclean $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) @echo "" @echo "Step 2/4. Running benchmark for pgo-build ..." - $(PGOBENCH) 2>&1 | tail -n 4 + $(PGOBENCH) > PGOBENCH.out 2>&1 + tail -n 4 PGOBENCH.out @echo "" @echo "Step 3/4. Building optimized executable ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean @@ -913,7 +910,7 @@ objclean: # clean auxiliary profiling files profileclean: @rm -rf profdir - @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s + @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s PGOBENCH.out @rm -f stockfish.profdata *.profraw @rm -f stockfish.*args* @rm -f stockfish.*lt* diff --git a/tests/signature.sh b/tests/signature.sh index 2e5c183a073..06bd1892e6c 100755 --- a/tests/signature.sh +++ b/tests/signature.sh @@ -11,7 +11,7 @@ trap 'error ${LINENO}' ERR # obtain -signature=`./stockfish bench 2>&1 | grep "Nodes searched : " | awk '{print $4}'` +signature=`eval "$WINE_PATH ./stockfish bench 2>&1" | grep "Nodes searched : " | awk '{print $4}'` if [ $# -gt 0 ]; then # compare to given reference From ee53f8ed2f9418b55b05811da93ddf62f03c255f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 2 Jul 2023 22:26:52 -0400 Subject: [PATCH 0150/1309] Reintroduce nnue eval pawn count multipliers again With separate multipliers for nnue eval and optimism scaling. This patch used 4 out of 7 params tuned with spsa at 30+0.3 using this tuning config: Value LazyThreshold1 = Value(3622); Value LazyThreshold2 = Value(1962); int psqThresh = 2048; int nnueNpmBase = 945; int nnuePcMult = 0; int optNpmBase = 150; int optPcMult = 0; TUNE(SetRange(3322, 3922), LazyThreshold1); TUNE(SetRange(1662, 2262), LazyThreshold2); TUNE(SetRange(1748, 2348), psqThresh); TUNE(SetRange(745, 1145), nnueNpmBase); TUNE(SetRange(-16, 16), nnuePcMult); TUNE(SetRange(0, 300), optNpmBase); TUNE(SetRange(-16, 16), optPcMult); Passed STC: https://tests.stockfishchess.org/tests/view/64a5a9b402cd07745c60ed07 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 173632 W: 44417 L: 43903 D: 85312 Ptnml(0-2): 547, 20025, 45068, 20719, 457 Passed LTC: https://tests.stockfishchess.org/tests/view/64a972a302cd07745c6136af LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 277644 W: 70955 L: 70147 D: 136542 Ptnml(0-2): 193, 29902, 77787, 30784, 156 closes https://github.com/official-stockfish/Stockfish/pull/4681 bench 1556301 --- src/evaluate.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index a1bcdd20420..d4d8daee4da 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -157,7 +157,9 @@ Value Eval::evaluate(const Position& pos) { // Blend optimism with nnue complexity and (semi)classical complexity optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; - v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024; + + v = ( nnue * (915 + npm + 9 * pos.count()) + + optimism * (154 + npm + pos.count())) / 1024; // Damp down the evaluation linearly when shuffling v = v * (200 - pos.rule50_count()) / 214; From a42ab95e1f2392406499b385149385a60cac5b66 Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Sat, 15 Jul 2023 11:00:18 +0200 Subject: [PATCH 0151/1309] remove large input specialization Removes unused large input specialization for dense affine transform. It has been obsolete since #4612 was merged. closes https://github.com/official-stockfish/Stockfish/pull/4684 No functional change --- src/nnue/layers/affine_transform.h | 261 +---------------------------- 1 file changed, 3 insertions(+), 258 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 9e2f2f97323..b0169306cb7 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -29,25 +29,10 @@ /* This file contains the definition for a fully connected layer (aka affine transform). - Two approaches are employed, depending on the sizes of the transform. - - Approach 1 (a specialization for large inputs): - - used when the PaddedInputDimensions >= 128 - - uses AVX512 if possible - - processes inputs in batches of 2*InputSimdWidth - - so in batches of 128 for AVX512 - - the weight blocks of size InputSimdWidth are transposed such that - access is sequential - - N columns of the weight matrix are processed a time, where N - depends on the architecture (the amount of registers) - - accumulate + hadd is used - - Approach 2 (a specialization for small inputs): - - used when the PaddedInputDimensions < 128 + - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32. - that's why AVX512 is hard to implement - expected use-case is small layers - - not optimized as well as the approach 1 - inputs are processed in chunks of 4, weights are respectively transposed - accumulation happens directly to int32s */ @@ -55,7 +40,7 @@ namespace Stockfish::Eval::NNUE::Layers { // Fallback implementation for older/other architectures. -// Identical for both approaches. Requires the input to be padded to at least 16 values. +// Requires the input to be padded to at least 16 values. #if !defined(USE_SSSE3) template static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) @@ -159,18 +144,8 @@ namespace Stockfish::Eval::NNUE::Layers { } #endif - template - class AffineTransform; - -#if defined (USE_AVX512) - constexpr IndexType LargeInputSize = 2 * 64; -#else - constexpr IndexType LargeInputSize = std::numeric_limits::max(); -#endif - - // A specialization for large inputs template - class AffineTransform(InDims, MaxSimdWidth) >= LargeInputSize)>> { + class AffineTransform { public: // Input/output type using InputType = std::uint8_t; @@ -187,236 +162,6 @@ namespace Stockfish::Eval::NNUE::Layers { using OutputBuffer = OutputType[PaddedOutputDimensions]; - static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization (for large inputs) should not have been chosen."); - -#if defined (USE_AVX512) - static constexpr IndexType InputSimdWidth = 64; - static constexpr IndexType MaxNumOutputRegs = 16; -#elif defined (USE_AVX2) - static constexpr IndexType InputSimdWidth = 32; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_SSSE3) - static constexpr IndexType InputSimdWidth = 16; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_NEON_DOTPROD) - static constexpr IndexType InputSimdWidth = 16; - static constexpr IndexType MaxNumOutputRegs = 8; -#elif defined (USE_NEON) - static constexpr IndexType InputSimdWidth = 8; - static constexpr IndexType MaxNumOutputRegs = 8; -#else - // The fallback implementation will not have permuted weights. - // We define these to avoid a lot of ifdefs later. - static constexpr IndexType InputSimdWidth = 1; - static constexpr IndexType MaxNumOutputRegs = 1; -#endif - - // A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs]. - // A small block is a region of size [InputSimdWidth, 1] - - static constexpr IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions); - static constexpr IndexType SmallBlockSize = InputSimdWidth; - static constexpr IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions; - static constexpr IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize; - static constexpr IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize; - static constexpr IndexType NumBigBlocks = OutputDimensions / NumOutputRegs; - - static_assert(OutputDimensions % NumOutputRegs == 0); - - // Hash value embedded in the evaluation file - static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; - } - - /* - Transposes the small blocks within a block. - Effectively means that weights can be traversed sequentially during inference. - */ - static IndexType get_weight_index(IndexType i) - { - const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock; - const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput; - const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput; - const IndexType bigBlock = i / BigBlockSize; - const IndexType rest = i % SmallBlockSize; - - const IndexType idx = - bigBlock * BigBlockSize - + smallBlockRow * SmallBlockSize * NumOutputRegs - + smallBlockCol * SmallBlockSize - + rest; - - return idx; - } - - // Read network parameters - bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); - - return !stream.fail(); - } - - // Write network parameters - bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); - - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); - - return !stream.fail(); - } - - // Forward propagation - const OutputType* propagate( - const InputType* input, OutputType* output) const { - -#if defined (USE_AVX512) - using acc_vec_t = __m512i; - using bias_vec_t = __m128i; - using weight_vec_t = __m512i; - using in_vec_t = __m512i; - #define vec_zero _mm512_setzero_si512() - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 - #define vec_hadd Simd::m512_hadd - #define vec_haddx4 Simd::m512_haddx4 -#elif defined (USE_AVX2) - using acc_vec_t = __m256i; - using bias_vec_t = __m128i; - using weight_vec_t = __m256i; - using in_vec_t = __m256i; - #define vec_zero _mm256_setzero_si256() - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd - #define vec_haddx4 Simd::m256_haddx4 -#elif defined (USE_SSSE3) - using acc_vec_t = __m128i; - using bias_vec_t = __m128i; - using weight_vec_t = __m128i; - using in_vec_t = __m128i; - #define vec_zero _mm_setzero_si128() - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd - #define vec_haddx4 Simd::m128_haddx4 -#elif defined (USE_NEON_DOTPROD) - using acc_vec_t = int32x4_t; - using bias_vec_t = int32x4_t; - using weight_vec_t = int8x16_t; - using in_vec_t = int8x16_t; - #define vec_zero {0} - #define vec_add_dpbusd_32x2 Simd::dotprod_m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::neon_m128_hadd - #define vec_haddx4 Simd::neon_m128_haddx4 -#elif defined (USE_NEON) - using acc_vec_t = int32x4_t; - using bias_vec_t = int32x4_t; - using weight_vec_t = int8x8_t; - using in_vec_t = int8x8_t; - #define vec_zero {0} - #define vec_add_dpbusd_32x2 Simd::neon_m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::neon_m128_hadd - #define vec_haddx4 Simd::neon_m128_haddx4 -#endif - -#if defined (USE_SSSE3) || defined (USE_NEON) - const in_vec_t* invec = reinterpret_cast(input); - - // Perform accumulation to registers for each big block - for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock) - { - acc_vec_t acc[NumOutputRegs] = { vec_zero }; - - // Each big block has NumOutputRegs small blocks in each "row", one per register. - // We process two small blocks at a time to save on one addition without VNNI. - for (IndexType smallBlock = 0; smallBlock < NumSmallBlocksPerOutput; smallBlock += 2) - { - const weight_vec_t* weightvec = - reinterpret_cast( - weights - + bigBlock * BigBlockSize - + smallBlock * SmallBlockSize * NumOutputRegs); - - const in_vec_t in0 = invec[smallBlock + 0]; - const in_vec_t in1 = invec[smallBlock + 1]; - - for (IndexType k = 0; k < NumOutputRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, weightvec[k], in1, weightvec[k + NumOutputRegs]); - } - - // Horizontally add all accumulators. - if constexpr (NumOutputRegs % 4 == 0) - { - bias_vec_t* outputvec = reinterpret_cast(output); - const bias_vec_t* biasvec = reinterpret_cast(biases); - - for (IndexType k = 0; k < NumOutputRegs; k += 4) - { - const IndexType idx = (bigBlock * NumOutputRegs + k) / 4; - outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]); - } - } - else - { - for (IndexType k = 0; k < NumOutputRegs; ++k) - { - const IndexType idx = (bigBlock * NumOutputRegs + k); - output[idx] = vec_hadd(acc[k], biases[idx]); - } - } - } - -# undef vec_zero -# undef vec_add_dpbusd_32x2 -# undef vec_hadd -# undef vec_haddx4 -#else - // Use old implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); - -#endif - - return output; - } - - private: - using BiasType = OutputType; - using WeightType = std::int8_t; - - alignas(CacheLineSize) BiasType biases[OutputDimensions]; - alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; - - // A specialization for small inputs - template - class AffineTransform(InDims, MaxSimdWidth) < LargeInputSize)>> { - public: - // Input/output type - // Input/output type - using InputType = std::uint8_t; - using OutputType = std::int32_t; - - // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; - static constexpr IndexType OutputDimensions = OutDims; - - static constexpr IndexType PaddedInputDimensions = - ceil_to_multiple(InputDimensions, MaxSimdWidth); - static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, MaxSimdWidth); - - using OutputBuffer = OutputType[PaddedOutputDimensions]; - - static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization (for small inputs) should not have been chosen."); - // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { std::uint32_t hashValue = 0xCC03DAE4u; From e89469925d9afd060f85d4047edbaad8903e67ef Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 15 Jul 2023 11:59:27 +0200 Subject: [PATCH 0152/1309] Remove some CI parts not yet working downgrading compiler didn't work for windows build. Stick to gcc 13 for now. Windows x86-32 not a 32bit binary, remove. closes https://github.com/official-stockfish/Stockfish/pull/4685 No functional change --- .github/workflows/stockfish_binaries.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index e761c845396..f856d403af4 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -54,6 +54,8 @@ jobs: exclude: - binaries: x86-32 config: { os: macos-12 } + - binaries: x86-32 + config: { os: windows-2022} - binaries: x86-64-avx2 config: { os: macos-12 } - binaries: x86-64-bmi2 @@ -96,12 +98,6 @@ jobs: msystem: ${{ matrix.config.msys_sys }} install: mingw-w64-${{ matrix.config.msys_env }} make git zip - - name: Install fixed GCC on Windows - if: runner.os == 'Windows' - run: | - wget https://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst - pacman -U mingw-w64-x86_64-gcc-11.3.0-2-any.pkg.tar.zst --noconfirm - - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' uses: petarpetrovt/setup-sde@v2.1 From e8a5c64988d4c0d3d6c87e3ba3204ab4cf512bcb Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 15 Jul 2023 13:34:16 +0200 Subject: [PATCH 0153/1309] Consolidate to centipawns conversion avoid doing this calculations in multiple places. closes https://github.com/official-stockfish/Stockfish/pull/4686 No functional change --- src/evaluate.cpp | 6 ++---- src/nnue/evaluate_nnue.cpp | 8 ++++---- src/uci.cpp | 9 ++++++++- src/uci.h | 1 + 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d4d8daee4da..7f0ea4bc604 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -58,8 +58,6 @@ namespace Eval { string currentEvalFileName = "None"; - static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; } - /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" /// The name of the NNUE network is always retrieved from the EvalFile option. @@ -194,11 +192,11 @@ std::string Eval::trace(Position& pos) { Value v; v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; v = evaluate(pos); v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << to_cp(v) << " (white side)"; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; ss << "\n"; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index a1a90023909..d90f59a222b 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -224,7 +224,7 @@ namespace Stockfish::Eval::NNUE { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(100 * v / UCI::NormalizeToPawnValue); + int cp = std::abs(UCI::to_cp(v)); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; cp %= 10000; @@ -249,15 +249,15 @@ namespace Stockfish::Eval::NNUE { } - // format_cp_aligned_dot() converts a Value into (centi)pawns, always keeping two decimals. + // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals. static void format_cp_aligned_dot(Value v, std::stringstream &stream) { - const double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue; + const double pawns = std::abs(0.01 * UCI::to_cp(v)); stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) - << cp; + << pawns; } diff --git a/src/uci.cpp b/src/uci.cpp index ed16f24c382..f893bd9cece 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -303,6 +303,13 @@ void UCI::loop(int argc, char* argv[]) { } +/// Turns a Value to an integer centipawn number, +/// without treatment of mate and similar special scores. +int UCI::to_cp(Value v) { + + return 100 * v / UCI::NormalizeToPawnValue; +} + /// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: /// /// cp The score from the engine's point of view in centipawns. @@ -316,7 +323,7 @@ string UCI::value(Value v) { stringstream ss; if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << v * 100 / NormalizeToPawnValue; + ss << "cp " << UCI::to_cp(v); else if (abs(v) < VALUE_MATE_IN_MAX_PLY) { const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply diff --git a/src/uci.h b/src/uci.h index 8f1be00c7cb..2e40c9125d1 100644 --- a/src/uci.h +++ b/src/uci.h @@ -76,6 +76,7 @@ class Option { void init(OptionsMap&); void loop(int argc, char* argv[]); +int to_cp(Value v); std::string value(Value v); std::string square(Square s); std::string move(Move m, bool chess960); From 18fdc2df3c1fe0eeaae26a9235fb9bc17221e9c0 Mon Sep 17 00:00:00 2001 From: maxim Date: Thu, 13 Jul 2023 19:09:02 +0300 Subject: [PATCH 0154/1309] Remove pawnKey from StateInfo Remove pawnKey since it is not used anymore. Passed Non-regression STC: https://tests.stockfishchess.org/tests/view/64b023110cdec37b9573265c LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 334848 W: 85440 L: 85545 D: 163863 Ptnml(0-2): 1134, 38101, 89075, 37964, 1150 closes https://github.com/official-stockfish/Stockfish/pull/4687 No functional change --- src/position.cpp | 12 +----------- src/position.h | 5 ----- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 6ecc52f8446..31cdbc06b67 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -336,7 +336,6 @@ void Position::set_check_info() const { void Position::set_state() const { st->key = st->materialKey = 0; - st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); @@ -348,10 +347,7 @@ void Position::set_state() const { Piece pc = piece_on(s); st->key ^= Zobrist::psq[pc][s]; - if (type_of(pc) == PAWN) - st->pawnKey ^= Zobrist::psq[pc][s]; - - else if (type_of(pc) != KING) + if (type_of(pc) != KING && type_of(pc) != PAWN) st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; } @@ -745,8 +741,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); } - - st->pawnKey ^= Zobrist::psq[captured][capsq]; } else st->nonPawnMaterial[them] -= PieceValue[MG][captured]; @@ -825,7 +819,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // 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]]; @@ -833,9 +826,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[MG][promotion]; } - // Update pawn hash key - st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - // Reset rule 50 draw counter st->rule50 = 0; } diff --git a/src/position.h b/src/position.h index 7d4b3771912..e3917ede040 100644 --- a/src/position.h +++ b/src/position.h @@ -40,7 +40,6 @@ namespace Stockfish { struct StateInfo { // Copied when making a move - Key pawnKey; Key materialKey; Value nonPawnMaterial[COLOR_NB]; int castlingRights; @@ -338,10 +337,6 @@ inline Key Position::adjust_key50(Key k) const ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } -inline Key Position::pawn_key() const { - return st->pawnKey; -} - inline Key Position::material_key() const { return st->materialKey; } From 913574f42123d16b0b473dcd8e373e95d3103633 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 16 Jul 2023 11:52:37 +0300 Subject: [PATCH 0155/1309] Remove improvement variable No longer used in a meaningful way. Improve comments. Closes https://github.com/official-stockfish/Stockfish/pull/4688 No functional change --- src/search.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 11a52326dbe..61c75d7dd9f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -548,7 +548,7 @@ namespace { bool givesCheck, improving, priorCapture, singularQuietLMR; bool capture, moveCountPruning, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount, improvement; + int moveCount, captureCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); @@ -708,7 +708,6 @@ namespace { // Skip early pruning when in check ss->staticEval = eval = VALUE_NONE; improving = false; - improvement = 0; goto moves_loop; } else if (excludedMove) @@ -745,14 +744,14 @@ namespace { thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } - // Set up the improvement variable, which is the difference between the current - // static evaluation and the previous static evaluation at our turn (if we were - // in check at our previous move we look at the move prior to it). The improvement - // margin and the improving flag are used in various pruning heuristics. - improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval - : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval - : 173; - improving = improvement > 0; + // Set up the improving flag, which is true if current static evaluation is + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we look at static evaluaion at move prior to it + // and if we were in check at move prior to it flag is set to true) and is + // false otherwise. The improving flag is used in various pruning heuristics. + improving = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval > (ss-2)->staticEval + : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval > (ss-4)->staticEval + : true; // Step 7. Razoring (~1 Elo). // If eval is really low check with qsearch if it can exceed alpha, if it can't, From d70a905ce3bc7372529affa8df898fc946b91282 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 16 Jul 2023 16:25:03 +0200 Subject: [PATCH 0156/1309] Deprecate the x86-64-modern arch Explicitly describe the architecture as deprecated, it remains available as its current alias x86-64-sse41-popcnt CPUs that support just this instruction set are now years old, any few years old Intel or AMD CPU supports x86-64-avx2. However, naming things 'modern' doesn't age well, so instead use explicit names. Adjust CI accordingly. Wiki, fishtest, downloader done as well. closes https://github.com/official-stockfish/Stockfish/pull/4691 No functional change. --- .github/workflows/stockfish_binaries.yml | 2 +- .github/workflows/stockfish_sanitizers.yml | 2 +- .github/workflows/stockfish_test.yml | 14 +++++++++++--- README.md | 4 ++-- src/Makefile | 12 +++++++----- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index f856d403af4..cd90507ec4e 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -44,7 +44,7 @@ jobs: binaries: - x86-32 - x86-64 - - x86-64-modern + - x86-64-sse41-popcnt - x86-64-avx2 - x86-64-bmi2 - x86-64-avxvnni diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index ebfd809c295..305b8557bfb 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -62,5 +62,5 @@ jobs: run: | export CXXFLAGS="-O1 -fno-inline" make clean - make -j2 ARCH=x86-64-modern ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null + make -j2 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index cd80e223853..c2ed7a4b94d 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -177,12 +177,12 @@ jobs: # x86-64 tests - - name: Test debug x86-64-modern build + - name: Test debug x86-64-sse41-popcnt build if: matrix.config.run_64bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-64-modern optimize=no debug=yes build + make -j2 ARCH=x86-64-sse41-popcnt optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-64-bmi2 build @@ -199,6 +199,7 @@ jobs: make -j2 ARCH=x86-64-avx2 build ../tests/signature.sh $benchref + # Test a deprecated arch - name: Test x86-64-modern build if: matrix.config.run_64bit_tests run: | @@ -206,6 +207,13 @@ jobs: make -j2 ARCH=x86-64-modern build ../tests/signature.sh $benchref + - name: Test x86-64-sse41-popcnt build + if: matrix.config.run_64bit_tests + run: | + make clean + make -j2 ARCH=x86-64-sse41-popcnt build + ../tests/signature.sh $benchref + - name: Test x86-64-ssse3 build if: matrix.config.run_64bit_tests run: | @@ -271,6 +279,6 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-modern build + make -j2 ARCH=x86-64-sse41-popcnt build ../tests/perft.sh ../tests/reprosearch.sh diff --git a/README.md b/README.md index 1f462d315af..e0e3da394f5 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,11 @@ big-endian machines such as Power PC, and other platforms. On Unix-like systems, it should be easy to compile Stockfish directly from the source code with the included Makefile in the folder `src`. In general, it is recommended to run `make help` to see a list of make targets with corresponding -descriptions. +descriptions. An example suitable for most Intel and AMD chips: ``` cd src -make -j build ARCH=x86-64-modern +make -j profile-build ARCH=x86-64-avx2 ``` Detailed compilation instructions for all platforms can be found in our diff --git a/src/Makefile b/src/Makefile index 966ac2a7a60..f66d84d5547 100644 --- a/src/Makefile +++ b/src/Makefile @@ -104,7 +104,7 @@ VPATH = syzygy:nnue:nnue/features ### 2.1. General and architecture defaults ifeq ($(ARCH),) - ARCH = x86-64-modern + ARCH = x86-64-avx2 help_skip_sanity = yes endif # explicitly check for the list of supported architectures (as listed with make help), @@ -189,6 +189,8 @@ ifeq ($(findstring -sse41,$(ARCH)),-sse41) endif ifeq ($(findstring -modern,$(ARCH)),-modern) + $(warning *** ARCH=$(ARCH) is deprecated, defaulting to ARCH=x86-64-sse41-popcnt. Execute `make help` for a list of available architectures. ***) + $(shell sleep 5) popcnt = yes sse = yes sse2 = yes @@ -781,7 +783,7 @@ help: @echo "x86-64-bmi2 > x86 64-bit with bmi2 support" @echo "x86-64-avx2 > x86 64-bit with avx2 support" @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" - @echo "x86-64-modern > common modern CPU, currently x86-64-sse41-popcnt" + @echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" @echo "x86-64-ssse3 > x86 64-bit with ssse3 support" @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support" @echo "x86-64 > x86 64-bit generic (with sse2 support)" @@ -811,13 +813,13 @@ help: @echo "Simple examples. If you don't know what to do, you likely want to run one of: " @echo "" @echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " - @echo "make -j profile-build ARCH=x86-64-modern # A more portable compile for 64-bit systems " + @echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " @echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " @echo "" @echo "Advanced examples, for experienced users: " @echo "" - @echo "make -j profile-build ARCH=x86-64-bmi2" - @echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0" + @echo "make -j profile-build ARCH=x86-64-avxvnni" + @echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" @echo "" @echo "-------------------------------" From 34d0c1b5272a7af322825426e90c6fbc6b69c6b4 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 16 Jul 2023 18:14:38 +0200 Subject: [PATCH 0157/1309] Switch to macos 13 for CI allows for building x86-64-avx2 and x86-64-bmi2 binaries for mac install coreutils show hardware capabilities as seen by the compilers move some tests from sse41 to avx2 as platforms support it closes https://github.com/official-stockfish/Stockfish/pull/4692 No functional change --- .github/workflows/stockfish_binaries.yml | 36 ++++++++++++-------- .github/workflows/stockfish_compile_test.yml | 8 ++--- .github/workflows/stockfish_test.yml | 18 +++++----- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index cd90507ec4e..7c7341ef655 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -23,8 +23,8 @@ jobs: shell: bash archive_ext: tar sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future -- - - name: MacOS 12 Apple Clang - os: macos-12 + - name: MacOS 13 Apple Clang + os: macos-13 simple_name: macos compiler: clang++ comp: clang @@ -52,24 +52,20 @@ jobs: - x86-64-vnni256 - x86-64-vnni512 exclude: - - binaries: x86-32 - config: { os: macos-12 } + - binaries: x86-64-avxvnni + config: { ubuntu-20.04 } - binaries: x86-32 config: { os: windows-2022} - - binaries: x86-64-avx2 - config: { os: macos-12 } - - binaries: x86-64-bmi2 - config: { os: macos-12 } - - binaries: x86-64-avxvnni - config: { os: macos-12 } + - binaries: x86-32 + config: { os: macos-13 } - binaries: x86-64-avxvnni - config: { ubuntu-20.04 } + config: { os: macos-13 } - binaries: x86-64-avx512 - config: { os: macos-12 } + config: { os: macos-13 } - binaries: x86-64-vnni256 - config: { os: macos-12 } + config: { os: macos-13 } - binaries: x86-64-vnni512 - config: { os: macos-12 } + config: { os: macos-13 } defaults: run: working-directory: src @@ -85,6 +81,10 @@ jobs: sudo apt update sudo apt install g++-multilib g++-11-multilib + - name: Download required macOS packages + if: runner.os == 'macOS' + run: brew install coreutils + - name: Install fixed GCC on Linux if: runner.os == 'Linux' uses: egor-tensin/setup-gcc@v1 @@ -118,6 +118,14 @@ jobs: - name: Check compiler run: $COMPILER -v + - name: Show g++ cpu info + if: runner.os != 'macOS' + run: g++ -Q -march=native --help=target + + - name: Show clang++ cpu info + if: runner.os == 'macOS' + run: clang++ -E - -march=native -### + - name: Test help target run: make help diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 41f61daba8a..90e01537d57 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -21,13 +21,13 @@ jobs: compiler: clang++ comp: clang shell: bash - - name: MacOS 12 Apple Clang - os: macos-12 + - name: MacOS 13 Apple Clang + os: macos-13 compiler: clang++ comp: clang shell: bash - - name: MacOS 12 GCC 11 - os: macos-12 + - name: MacOS 13 GCC 11 + os: macos-13 compiler: g++-11 comp: gcc shell: bash diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index c2ed7a4b94d..cb6c4c5901b 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -38,14 +38,14 @@ jobs: comp: ndk run_armv7_tests: true shell: bash - - name: MacOS 12 Apple Clang - os: macos-12 + - name: MacOS 13 Apple Clang + os: macos-13 compiler: clang++ comp: clang run_64bit_tests: true shell: bash - - name: MacOS 12 GCC 11 - os: macos-12 + - name: MacOS 13 GCC 11 + os: macos-13 compiler: g++-11 comp: gcc run_64bit_tests: true @@ -177,23 +177,23 @@ jobs: # x86-64 tests - - name: Test debug x86-64-sse41-popcnt build + - name: Test debug x86-64-avx2 build if: matrix.config.run_64bit_tests run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-64-sse41-popcnt optimize=no debug=yes build + make -j2 ARCH=x86-64-avx2 optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-64-bmi2 build - if: matrix.config.run_64bit_tests && runner.os != 'macOS' + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-bmi2 build ../tests/signature.sh $benchref - name: Test x86-64-avx2 build - if: matrix.config.run_64bit_tests && runner.os != 'macOS' + if: matrix.config.run_64bit_tests run: | make clean make -j2 ARCH=x86-64-avx2 build @@ -279,6 +279,6 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-sse41-popcnt build + make -j2 ARCH=x86-64-avx2 build ../tests/perft.sh ../tests/reprosearch.sh From 6a31f54d3cacf8c4dc57f301e0e3aa40d7aec435 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Mon, 17 Jul 2023 18:02:22 +0200 Subject: [PATCH 0158/1309] remove evalType from bench no longer used closes https://github.com/official-stockfish/Stockfish/pull/4694 No functional change --- AUTHORS | 1 + src/benchmark.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 792893944c4..462ae5e82d5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -180,6 +180,7 @@ renouve Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) rn5f107s2 +Robert Nürnberg (robertnurnberg) Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) Ronald de Man (syzygy1, syzygy) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index baa90140f9b..c41092a9de8 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -121,7 +121,6 @@ vector setup_bench(const Position& current, istream& is) { string limit = (is >> token) ? token : "13"; string fenFile = (is >> token) ? token : "default"; string limitType = (is >> token) ? token : "depth"; - string evalType = (is >> token) ? token : "mixed"; go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; From 42d28424bc64246e141e9f06a2a518592272f8fd Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Mon, 17 Jul 2023 21:41:03 +0200 Subject: [PATCH 0159/1309] Removes a few Bitboards and functions No longer used closes https://github.com/official-stockfish/Stockfish/pull/4695 No functional change --- src/bitboard.h | 74 -------------------------------------------------- src/position.h | 30 -------------------- 2 files changed, 104 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index d21d390b1fb..bbed9177507 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -32,9 +32,6 @@ std::string pretty(Bitboard b); } // namespace Stockfish::Bitboards -constexpr Bitboard AllSquares = ~Bitboard(0); -constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; - constexpr Bitboard FileABB = 0x0101010101010101ULL; constexpr Bitboard FileBBB = FileABB << 1; constexpr Bitboard FileCBB = FileABB << 2; @@ -53,17 +50,6 @@ constexpr Bitboard Rank6BB = Rank1BB << (8 * 5); constexpr Bitboard Rank7BB = Rank1BB << (8 * 6); constexpr Bitboard Rank8BB = Rank1BB << (8 * 7); -constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; -constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; -constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; -constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); - -constexpr Bitboard KingFlank[FILE_NB] = { - QueenSide ^ FileDBB, QueenSide, QueenSide, - CenterFiles, CenterFiles, - KingSide, KingSide, KingSide ^ FileEBB -}; - extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; @@ -124,11 +110,6 @@ constexpr bool more_than_one(Bitboard b) { } -constexpr bool opposite_colors(Square s1, Square s2) { - return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1; -} - - /// rank_bb() and file_bb() return a bitboard representing all the squares on /// the given file or rank. @@ -177,25 +158,6 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { return PawnAttacks[c][s]; } - -/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the -/// given color from the squares in the given bitboard. - -template -constexpr Bitboard pawn_double_attacks_bb(Bitboard b) { - return C == WHITE ? shift(b) & shift(b) - : shift(b) & shift(b); -} - - -/// adjacent_files_bb() returns a bitboard representing all the squares on the -/// adjacent files of a given square. - -constexpr Bitboard adjacent_files_bb(Square s) { - return shift(file_bb(s)) | shift(file_bb(s)); -} - - /// line_bb() returns a bitboard representing an entire line (from board edge /// to board edge) that intersects the two given squares. If the given squares /// are not on a same file/rank/diagonal, the function returns 0. For instance, @@ -234,32 +196,6 @@ constexpr Bitboard forward_ranks_bb(Color c, Square s) { : ~Rank8BB >> 8 * relative_rank(BLACK, s); } - -/// forward_file_bb() returns a bitboard representing all the squares along the -/// line in front of the given one, from the point of view of the given color. - -constexpr Bitboard forward_file_bb(Color c, Square s) { - return forward_ranks_bb(c, s) & file_bb(s); -} - - -/// pawn_attack_span() returns a bitboard representing all the squares that can -/// be attacked by a pawn of the given color when it moves along its file, starting -/// from the given square. - -constexpr Bitboard pawn_attack_span(Color c, Square s) { - return forward_ranks_bb(c, s) & adjacent_files_bb(s); -} - - -/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of -/// the given color and on the given square is a passed pawn. - -constexpr Bitboard passed_pawn_span(Color c, Square s) { - return pawn_attack_span(c, s) | forward_file_bb(c, s); -} - - /// aligned() returns true if the squares s1, s2 and s3 are aligned either on a /// straight or on a diagonal line. @@ -277,8 +213,6 @@ template<> inline int distance(Square x, Square y) { return std::abs(rank_ template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); } - /// attacks_bb(Square) returns the pseudo attacks of the give piece type /// assuming an empty board. @@ -430,14 +364,6 @@ inline Square pop_lsb(Bitboard& b) { return s; } - -/// frontmost_sq() returns the most advanced square for the given color, -/// requires a non-zero bitboard. -inline Square frontmost_sq(Color c, Bitboard b) { - assert(b); - return c == WHITE ? msb(b) : lsb(b); -} - } // namespace Stockfish #endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/position.h b/src/position.h index e3917ede040..dc4c5837299 100644 --- a/src/position.h +++ b/src/position.h @@ -100,7 +100,6 @@ class Position { template int count(Color c) const; template int count() const; template Square square(Color c) const; - bool is_on_semiopen_file(Color c, Square s) const; // Castling CastlingRights castling_rights(Color c) const; @@ -129,11 +128,6 @@ class Position { Piece moved_piece(Move m) const; Piece captured_piece() const; - // Piece specific - bool pawn_passed(Color c, Square s) const; - bool opposite_bishops() const; - int pawns_on_same_color_squares(Color c, Square s) const; - // Doing and undoing moves void do_move(Move m, StateInfo& newSt); void do_move(Move m, StateInfo& newSt, bool givesCheck); @@ -149,7 +143,6 @@ class Position { Key key() const; Key key_after(Move m) const; Key material_key() const; - Key pawn_key() const; // Other properties of the position Color side_to_move() const; @@ -160,7 +153,6 @@ class Position { bool has_game_cycle(int ply) const; bool has_repeated() const; int rule50_count() const; - Score psq_score() const; Value psq_eg_stm() const; Value non_pawn_material(Color c) const; Value non_pawn_material() const; @@ -258,10 +250,6 @@ inline Square Position::ep_square() const { return st->epSquare; } -inline bool Position::is_on_semiopen_file(Color c, Square s) const { - return !(pieces(c, PAWN) & file_bb(s)); -} - inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; } @@ -318,14 +306,6 @@ inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline bool Position::pawn_passed(Color c, Square s) const { - return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); -} - -inline int Position::pawns_on_same_color_squares(Color c, Square s) const { - return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); -} - inline Key Position::key() const { return adjust_key50(st->key); } @@ -341,10 +321,6 @@ inline Key Position::material_key() const { return st->materialKey; } -inline Score Position::psq_score() const { - return psq; -} - inline Value Position::psq_eg_stm() const { return (sideToMove == WHITE ? 1 : -1) * eg_value(psq); } @@ -365,12 +341,6 @@ inline int Position::rule50_count() const { return st->rule50; } -inline bool Position::opposite_bishops() const { - return count(WHITE) == 1 - && count(BLACK) == 1 - && opposite_colors(square(WHITE), square(BLACK)); -} - inline bool Position::is_chess960() const { return chess960; } From 6abd0bd9fbd8aff7dad653d33ab344d3f47f0042 Mon Sep 17 00:00:00 2001 From: Jorge <46056498+jorgectf@users.noreply.github.com> Date: Mon, 3 Jul 2023 09:17:24 +0200 Subject: [PATCH 0160/1309] Add CodeQL workflow add CI tooling to detect security bugs. closes https://github.com/official-stockfish/Stockfish/pull/4659 No functional change --- .github/workflows/codeql.yml | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000000..863f219ca7b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,53 @@ +name: "CodeQL" + +on: + push: + branches: [ 'master' ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ 'master' ] + schedule: + - cron: '17 18 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Build + working-directory: src + run: make -j build ARCH=x86-64-modern + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From 3fe0d5c53389b3d548cf1fbbba3f6c978e9f02ae Mon Sep 17 00:00:00 2001 From: Cody Ho Date: Tue, 18 Jul 2023 13:03:26 -0700 Subject: [PATCH 0161/1309] Unused code cleanup closes https://github.com/official-stockfish/Stockfish/pull/4696 No functional change --- AUTHORS | 1 + src/bitboard.h | 10 ---------- src/misc.h | 8 -------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/AUTHORS b/AUTHORS index 462ae5e82d5..b345ff0b34b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,6 +45,7 @@ candirufish Chess13234 Chris Cain (ceebo) clefrks +Cody Ho (aesrentai) Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) diff --git a/src/bitboard.h b/src/bitboard.h index bbed9177507..244dc034c33 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -186,16 +186,6 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } - -/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in -/// front of the given one, from the point of view of the given color. For instance, -/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. - -constexpr Bitboard forward_ranks_bb(Color c, Square s) { - return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s) - : ~Rank8BB >> 8 * relative_rank(BLACK, s); -} - /// aligned() returns true if the squares s1, s2 and s3 are aligned either on a /// straight or on a diagonal line. diff --git a/src/misc.h b/src/misc.h index 69d470c22f8..0005fc0fb09 100644 --- a/src/misc.h +++ b/src/misc.h @@ -55,14 +55,6 @@ inline TimePoint now() { (std::chrono::steady_clock::now().time_since_epoch()).count(); } -template -struct HashTable { - Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } - -private: - std::vector table = std::vector(Size); // Allocate on the heap -}; - enum SyncCout { IO_LOCK, IO_UNLOCK }; std::ostream& operator<<(std::ostream&, SyncCout); From 1444837887e5982a2f1f343312e9080248ed9ca8 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 18 Jul 2023 11:50:34 -0700 Subject: [PATCH 0162/1309] Remove inline assembly closes https://github.com/official-stockfish/Stockfish/pull/4698 No functional change --- src/nnue/layers/simd.h | 118 ----------------------------------------- 1 file changed, 118 deletions(-) diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 22c51980ecc..fae31a62955 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -38,21 +38,6 @@ # include #endif -// The inline asm is only safe for GCC, where it is necessary to get good codegen. -// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101693 -// Clang does fine without it. -// Play around here: https://godbolt.org/z/7EWqrYq51 -#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)) -#define USE_INLINE_ASM -#endif - -// Use either the AVX512 or AVX-VNNI version of the VNNI instructions. -#if defined(USE_AVXVNNI) -#define VNNI_PREFIX "%{vex%} " -#else -#define VNNI_PREFIX "" -#endif - namespace Stockfish::Simd { #if defined (USE_AVX512) @@ -117,29 +102,11 @@ namespace Stockfish::Simd { __m512i b) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - "vpdpbusd %[b], %[a], %[acc]\n\t" - : [acc]"+v"(acc) - : [a]"v"(a), [b]"vm"(b) - ); -# else acc = _mm512_dpbusd_epi32(acc, a, b); -# endif # else -# if defined (USE_INLINE_ASM) - __m512i tmp = _mm512_maddubs_epi16(a, b); - asm( - "vpmaddwd %[tmp], %[ones], %[tmp]\n\t" - "vpaddd %[acc], %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm512_set1_epi16(1)) - ); -# else __m512i product0 = _mm512_maddubs_epi16(a, b); product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); acc = _mm512_add_epi32(acc, product0); -# endif # endif } @@ -149,36 +116,14 @@ namespace Stockfish::Simd { __m512i a1, __m512i b1) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - "vpdpbusd %[b0], %[a0], %[acc]\n\t" - "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+&v"(acc) - : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) - ); -# else acc = _mm512_dpbusd_epi32(acc, a0, b0); acc = _mm512_dpbusd_epi32(acc, a1, b1); -# endif # else -# if defined (USE_INLINE_ASM) - __m512i tmp0 = _mm512_maddubs_epi16(a0, b0); - __m512i tmp1 = _mm512_maddubs_epi16(a1, b1); - asm( - "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" - "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" - "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" - "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm512_set1_epi16(1)) - ); -# else __m512i product0 = _mm512_maddubs_epi16(a0, b0); __m512i product1 = _mm512_maddubs_epi16(a1, b1); product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); -# endif # endif } @@ -214,29 +159,11 @@ namespace Stockfish::Simd { __m256i b) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - VNNI_PREFIX "vpdpbusd %[b], %[a], %[acc]\n\t" - : [acc]"+v"(acc) - : [a]"v"(a), [b]"vm"(b) - ); -# else acc = _mm256_dpbusd_epi32(acc, a, b); -# endif # else -# if defined (USE_INLINE_ASM) - __m256i tmp = _mm256_maddubs_epi16(a, b); - asm( - "vpmaddwd %[tmp], %[ones], %[tmp]\n\t" - "vpaddd %[acc], %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm256_set1_epi16(1)) - ); -# else __m256i product0 = _mm256_maddubs_epi16(a, b); product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); acc = _mm256_add_epi32(acc, product0); -# endif # endif } @@ -246,36 +173,14 @@ namespace Stockfish::Simd { __m256i a1, __m256i b1) { # if defined (USE_VNNI) -# if defined (USE_INLINE_ASM) - asm( - VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t" - VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t" - : [acc]"+&v"(acc) - : [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1) - ); -# else acc = _mm256_dpbusd_epi32(acc, a0, b0); acc = _mm256_dpbusd_epi32(acc, a1, b1); -# endif # else -# if defined (USE_INLINE_ASM) - __m256i tmp0 = _mm256_maddubs_epi16(a0, b0); - __m256i tmp1 = _mm256_maddubs_epi16(a1, b1); - asm( - "vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t" - "vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t" - "vpaddd %[tmp0], %[tmp1], %[tmp0]\n\t" - "vpaddd %[acc], %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm256_set1_epi16(1)) - ); -# else __m256i product0 = _mm256_maddubs_epi16(a0, b0); __m256i product1 = _mm256_maddubs_epi16(a1, b1); product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); -# endif # endif } @@ -304,19 +209,9 @@ namespace Stockfish::Simd { __m128i a, __m128i b) { -# if defined (USE_INLINE_ASM) - __m128i tmp = _mm_maddubs_epi16(a, b); - asm( - "pmaddwd %[ones], %[tmp]\n\t" - "paddd %[tmp], %[acc]\n\t" - : [acc]"+v"(acc), [tmp]"+&v"(tmp) - : [ones]"v"(_mm_set1_epi16(1)) - ); -# else __m128i product0 = _mm_maddubs_epi16(a, b); product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); acc = _mm_add_epi32(acc, product0); -# endif } [[maybe_unused]] static void m128_add_dpbusd_epi32x2( @@ -324,24 +219,11 @@ namespace Stockfish::Simd { __m128i a0, __m128i b0, __m128i a1, __m128i b1) { -# if defined (USE_INLINE_ASM) - __m128i tmp0 = _mm_maddubs_epi16(a0, b0); - __m128i tmp1 = _mm_maddubs_epi16(a1, b1); - asm( - "pmaddwd %[ones], %[tmp0]\n\t" - "pmaddwd %[ones], %[tmp1]\n\t" - "paddd %[tmp1], %[tmp0]\n\t" - "paddd %[tmp0], %[acc]\n\t" - : [acc]"+v"(acc), [tmp0]"+&v"(tmp0), [tmp1]"+&v"(tmp1) - : [ones]"v"(_mm_set1_epi16(1)) - ); -# else __m128i product0 = _mm_maddubs_epi16(a0, b0); __m128i product1 = _mm_maddubs_epi16(a1, b1); product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); -# endif } #endif From 5ea1cbc778508a9a7b720becaf22dd96a4472826 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 19 Jul 2023 11:58:02 +0300 Subject: [PATCH 0163/1309] Do more futility pruning for cutNodes that are not in TT This is somewhat similar to IIR for cutnodes but instead of reducing depth for cutnodes that don't have tt move we reduce margin multiplier in futility pruning for cutnodes that are not in TT. Passed STC: https://tests.stockfishchess.org/tests/view/64b244b90cdec37b95734c5b LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 75552 W: 19404 L: 19029 D: 37119 Ptnml(0-2): 233, 8806, 19378, 9071, 288 Passed LTC: https://tests.stockfishchess.org/tests/view/64b3ae5a0cdec37b95736516 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 104988 W: 27152 L: 26697 D: 51139 Ptnml(0-2): 41, 11259, 29446, 11700, 48 closes https://github.com/official-stockfish/Stockfish/pull/4700 bench 1727577 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 61c75d7dd9f..8fdaca7cff0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,8 +63,8 @@ namespace { enum NodeType { NonPV, PV, Root }; // Futility margin - Value futility_margin(Depth d, bool improving) { - return Value(140 * (d - improving)); + Value futility_margin(Depth d, bool noTtCutNode, bool improving) { + return Value((140 - 40 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -767,7 +767,7 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 - && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins return eval; From 4b2979760f3862700c6a0b8d3ab0f6a6e0a638c0 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Jul 2023 09:41:55 +0200 Subject: [PATCH 0164/1309] Check clock more often This patch changes the frequency with which the time is checked, changing frequency from every 1024 counted nodes to every 512 counted nodes. The master value was tuned for the old classical eval, the patch takes the roughly 2x slowdown in nps with SFNNUEv7 into account. This could reduce a bit the losses on time on fishtest, but they are probably unrelated. passed STC: https://tests.stockfishchess.org/tests/view/64bb8ae5dc56e1650abb1b11 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 76576 W: 19677 L: 19501 D: 37398 Ptnml(0-2): 274, 8592, 20396, 8736, 290 closes https://github.com/official-stockfish/Stockfish/pull/4704 No functional change --- src/search.cpp | 4 ++-- src/timeman.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8fdaca7cff0..db9a5a8d01d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1828,7 +1828,7 @@ void MainThread::check_time() { return; // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024; + callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; static TimePoint lastInfoTime = now(); @@ -1845,7 +1845,7 @@ void MainThread::check_time() { if (ponder) return; - if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) + if ( (Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) || (Limits.movetime && elapsed >= Limits.movetime) || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) Threads.stop = true; diff --git a/src/timeman.cpp b/src/timeman.cpp index 061de0182f7..169c7821c94 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -100,7 +100,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // Never use more than 80% of the available time for this move optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)); + maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (Options["Ponder"]) optimumTime += optimumTime / 4; From 78e3d2ad78f1835d627ec40b9228e8b7dbb676ef Mon Sep 17 00:00:00 2001 From: windfishballad Date: Sat, 22 Jul 2023 20:35:40 -0400 Subject: [PATCH 0165/1309] Simplify some qsearch conditions Use the assert which ensures that beta == alpha+1 at PVNodes to simplify a little bit the conditions further down in the code. passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 56160 W: 14370 L: 14173 D: 27617 Ptnml(0-2): 210, 6192, 15076, 6395, 207 https://tests.stockfishchess.org/tests/view/64bc769cdc56e1650abb2e26 closes https://tests.stockfishchess.org/tests/view/64bc769cdc56e1650abb2e26 No functional change --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index db9a5a8d01d..616d11336ee 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1496,7 +1496,7 @@ namespace { return bestValue; } - if (PvNode && bestValue > alpha) + if (bestValue > alpha) alpha = bestValue; futilityBase = bestValue + 200; @@ -1608,7 +1608,7 @@ namespace { if (PvNode) // Update pv even in fail-high case update_pv(ss->pv, move, (ss+1)->pv); - if (PvNode && value < beta) // Update alpha here! + if (value < beta) // Update alpha here! alpha = value; else break; // Fail high From 76e1e8fd39a08c0586259a76c880c3267cb85f62 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 20 Jul 2023 22:12:15 +0200 Subject: [PATCH 0166/1309] Simplify TT cutoff Remove the exact bound condition from TT depth check. STC: https://tests.stockfishchess.org/tests/view/64b30b320cdec37b957359e9 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 332928 W: 84895 L: 85003 D: 163030 Ptnml(0-2): 1242, 39200, 85604, 39260, 1158 LTC: https://tests.stockfishchess.org/tests/view/64b74e2adc56e1650abac0b6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 92946 W: 23628 L: 23482 D: 45836 Ptnml(0-2): 38, 10033, 26192, 10165, 45 closes https://github.com/official-stockfish/Stockfish/pull/4702 Bench: 1601764 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 616d11336ee..c2d35796231 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -616,7 +616,7 @@ namespace { // At non-PV nodes we check for an early TT cutoff if ( !PvNode && !excludedMove - && tte->depth() > depth - (tte->bound() == BOUND_EXACT) + && tte->depth() > depth && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { From 027713c4b4738446818aedfbfb480e962b624e31 Mon Sep 17 00:00:00 2001 From: Stephen Touset Date: Tue, 25 Jul 2023 00:06:14 +0200 Subject: [PATCH 0167/1309] Remove Zobrist::noPawns Zobrist::noPawns is no longer used. closes https://github.com/official-stockfish/Stockfish/pull/4344 no functional change --- AUTHORS | 1 + src/position.cpp | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index b345ff0b34b..2f323d64334 100644 --- a/AUTHORS +++ b/AUTHORS @@ -205,6 +205,7 @@ Stefano Cardanobile (Stefano80) Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) +Stephen Touset (stouset) Syine Mineta (MinetaS) Thanar2 thaspel diff --git a/src/position.cpp b/src/position.cpp index 31cdbc06b67..16181e96249 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -42,7 +42,7 @@ namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; - Key side, noPawns; + Key side; } namespace { @@ -125,7 +125,6 @@ void Position::init() { Zobrist::castling[cr] = rng.rand(); Zobrist::side = rng.rand(); - Zobrist::noPawns = rng.rand(); // Prepare the cuckoo tables std::memset(cuckoo, 0, sizeof(cuckoo)); From 2667316ffcf1b3396e42be3d5cb6bcbdcc98c216 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 25 Jul 2023 13:55:29 +0300 Subject: [PATCH 0168/1309] Simplify one multicut extension Simplify away the ttValue <= alpha extension in the multicut block. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 318336 W: 81307 L: 81398 D: 155631 Ptnml(0-2): 1088, 37291, 82469, 37264, 1056 https://tests.stockfishchess.org/tests/view/64b8589fdc56e1650abad61d Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 89388 W: 22925 L: 22775 D: 43688 Ptnml(0-2): 34, 9635, 25210, 9777, 38 https://tests.stockfishchess.org/tests/view/64bc41d0dc56e1650abb29cb closes https://github.com/official-stockfish/Stockfish/pull/4709 bench: 1604592 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c2d35796231..96b29d1e8ed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1104,10 +1104,6 @@ namespace { // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; - - // If the eval of ttMove is less than alpha, we reduce it (negative extension) (~1 Elo) - else if (ttValue <= alpha) - extension = -1; } // Check extensions (~1 Elo) From cb22520a9c7e1d716a56f08390aa638a23a597b2 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Jul 2023 19:02:49 -0700 Subject: [PATCH 0169/1309] Remove unused return type from propagate() Also make two get_weight_index() static methods constexpr, for consistency with the other static get_hash_value() method right above. Tested for speed by user Torom (thanks). closes https://github.com/official-stockfish/Stockfish/pull/4708 No functional change --- src/nnue/layers/affine_transform.h | 8 +++----- src/nnue/layers/affine_transform_sparse_input.h | 9 +++------ src/nnue/layers/clipped_relu.h | 4 +--- src/nnue/layers/sqr_clipped_relu.h | 4 +--- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index b0169306cb7..c936a83ed66 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -171,7 +171,7 @@ namespace Stockfish::Eval::NNUE::Layers { return hashValue; } - static IndexType get_weight_index_scrambled(IndexType i) + static constexpr IndexType get_weight_index_scrambled(IndexType i) { return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + @@ -179,7 +179,7 @@ namespace Stockfish::Eval::NNUE::Layers { i % 4; } - static IndexType get_weight_index(IndexType i) + static constexpr IndexType get_weight_index(IndexType i) { #if defined (USE_SSSE3) return get_weight_index_scrambled(i); @@ -207,7 +207,7 @@ namespace Stockfish::Eval::NNUE::Layers { return !stream.fail(); } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined (USE_AVX512) @@ -291,8 +291,6 @@ namespace Stockfish::Eval::NNUE::Layers { PaddedInputDimensions, OutputDimensions>(output, weights, biases, input); #endif - - return output; } private: diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index a5bea08e74b..134b7d13e19 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -102,7 +102,6 @@ namespace Stockfish::Eval::NNUE::Layers { template class AffineTransformSparseInput { public: - // Input/output type // Input/output type using InputType = std::uint8_t; using OutputType = std::int32_t; @@ -135,7 +134,7 @@ namespace Stockfish::Eval::NNUE::Layers { return hashValue; } - static IndexType get_weight_index_scrambled(IndexType i) + static constexpr IndexType get_weight_index_scrambled(IndexType i) { return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + @@ -143,7 +142,7 @@ namespace Stockfish::Eval::NNUE::Layers { i % ChunkSize; } - static IndexType get_weight_index(IndexType i) + static constexpr IndexType get_weight_index(IndexType i) { #if defined (USE_SSSE3) return get_weight_index_scrambled(i); @@ -171,7 +170,7 @@ namespace Stockfish::Eval::NNUE::Layers { return !stream.fail(); } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined (USE_SSSE3) @@ -230,8 +229,6 @@ namespace Stockfish::Eval::NNUE::Layers { PaddedInputDimensions, OutputDimensions>(output, weights, biases, input); #endif - - return output; } private: diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 51e562dad34..d5aa6fbfbd1 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -59,7 +59,7 @@ namespace Stockfish::Eval::NNUE::Layers { } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined(USE_AVX2) @@ -170,8 +170,6 @@ namespace Stockfish::Eval::NNUE::Layers { output[i] = static_cast( std::max(0, std::min(127, input[i] >> WeightScaleBits))); } - - return output; } }; diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 3fbb243cfd6..69bd51471d7 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -59,7 +59,7 @@ namespace Stockfish::Eval::NNUE::Layers { } // Forward propagation - const OutputType* propagate( + void propagate( const InputType* input, OutputType* output) const { #if defined(USE_SSE2) @@ -110,8 +110,6 @@ namespace Stockfish::Eval::NNUE::Layers { // needs to be accounted for in the trainer std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128))); } - - return output; } }; From f84eb1f3ef9dc4078368b849f8deb55982882390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Fri, 28 Jul 2023 23:38:37 +0200 Subject: [PATCH 0170/1309] Improve some comments - clarify the examples for the bench command - typo in search.cpp closes https://github.com/official-stockfish/Stockfish/pull/4710 No functional change --- src/benchmark.cpp | 15 +++++++-------- src/nnue/evaluate_nnue.cpp | 6 +++--- src/search.cpp | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index c41092a9de8..e340ebcd309 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -100,15 +100,14 @@ namespace Stockfish { /// setup_bench() builds a list of UCI commands to be run by bench. There /// are five parameters: TT size in MB, number of search threads that /// should be used, the limit value spent for each position, a file name -/// where to look for positions in FEN format, the type of the limit: -/// depth, perft, nodes and movetime (in millisecs), and evaluation type -/// mixed (default), classical, NNUE. +/// where to look for positions in FEN format, and the type of the limit: +/// depth, perft, nodes and movetime (in milliseconds). Examples: /// -/// bench -> search default positions up to depth 13 -/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) -/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec -/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each -/// bench 16 1 5 default perft -> run a perft 5 on default positions +/// bench : search default positions up to depth 13 +/// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) +/// bench 64 1 100000 default nodes : search default positions for 100K nodes each +/// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec +/// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" vector setup_bench(const Position& current, istream& is) { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index d90f59a222b..cff1d0243db 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -82,6 +82,7 @@ namespace Stockfish::Eval::NNUE { } // namespace Detail + // Initialize the evaluation function parameters static void initialize() { @@ -187,7 +188,6 @@ namespace Stockfish::Eval::NNUE { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. - constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) @@ -249,8 +249,9 @@ namespace Stockfish::Eval::NNUE { } - // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals. + // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals static void format_cp_aligned_dot(Value v, std::stringstream &stream) { + const double pawns = std::abs(0.01 * UCI::to_cp(v)); stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') @@ -263,7 +264,6 @@ namespace Stockfish::Eval::NNUE { // trace() returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. - std::string trace(Position& pos) { std::stringstream ss; diff --git a/src/search.cpp b/src/search.cpp index 96b29d1e8ed..45758031669 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -745,8 +745,8 @@ namespace { } // Set up the improving flag, which is true if current static evaluation is - // bigger than the previous static evaluation at our turn (if we were in - // check at our previous move we look at static evaluaion at move prior to it + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. improving = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval > (ss-2)->staticEval From 65ece7d985291cc787d6c804a33f1dd82b75736d Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Wed, 26 Jul 2023 14:31:16 +0200 Subject: [PATCH 0171/1309] Malus during move ordering for putting pieces en prise The original idea is the reverse of a previous patch [1] which added bonuses in our move picker to moves escaping threats. In this patch, in addition to bonuses for evading threats, we apply penalties to moves moving to threatened squares. Further tweaks of that basic idea resulted in this specific version which further increases the penalty of moves moving to squares threatend depending on the piece threatening it. So for example a queen moving to a square attacked by a pawn would receive a larger penalty than a queen moving to square attacked by a rook. [1]: https://github.com/official-stockfish/Stockfish/commit/08e0f52b77edb929989c68c49e954b9bc5d7d67e -------- Passed STC: https://tests.stockfishchess.org/tests/live_elo/64c11269dc56e1650abb935d LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 95552 W: 24654 L: 24250 D: 46648 Ptnml(0-2): 322, 11098, 24562, 11442, 352 Passed LTC: https://tests.stockfishchess.org/tests/live_elo/64c2004ddc56e1650abba8b3 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 190230 W: 48806 L: 48178 D: 93246 Ptnml(0-2): 90, 20439, 53453, 21019, 114 ------- closes https://github.com/official-stockfish/Stockfish/pull/4711 Bench: 1350831 --- src/movepick.cpp | 50 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6fbcb2c3d2f..4050810338f 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -123,21 +123,45 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = (7 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; else if constexpr (Type == QUIETS) - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)] - + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)] - + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)] - + (threatenedPieces & from_sq(m) ? - (type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000 - : type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000 - : !(to_sq(m) & threatenedByPawn) ? 15000 - : 0) - : 0) - + bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384; + { + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pos.moved_piece(m)); + Square from = from_sq(m); + Square to = to_sq(m); + + // histories + m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value += 2 * (*continuationHistory[0])[pc][to]; + m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[5])[pc][to]; + + // bonus for checks + m.value += bool(pos.check_squares(pt) & to) * 16384; + + // bonus for escaping from capture + m.value += threatenedPieces & from ? + (pt == QUEEN && !(to & threatenedByRook) ? 50000 + : pt == ROOK && !(to & threatenedByMinor) ? 25000 + : !(to & threatenedByPawn) ? 15000 + : 0 ) + : 0 ; + + // malus for putting piece en prise + m.value -= !(threatenedPieces & from) ? + (pt == QUEEN ? bool(to & threatenedByRook) * 50000 + + bool(to & threatenedByMinor) * 10000 + + bool(to & threatenedByPawn) * 20000 + : pt == ROOK ? bool(to & threatenedByMinor) * 25000 + + bool(to & threatenedByPawn) * 10000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0 ) + : 0 ; + } + else // Type == EVASIONS { if (pos.capture_stage(m)) From 002a50457ce1975bf049b7bac904f23d0eab4d3b Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Sat, 29 Jul 2023 14:34:58 +0000 Subject: [PATCH 0172/1309] Identify NEON_DOTPROD in compiler_info() closes https://github.com/official-stockfish/Stockfish/pull/4712 No functional change --- src/misc.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index f1554060d5e..29ef757e938 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -280,7 +280,9 @@ std::string compiler_info() { #if defined(USE_MMX) compiler += " MMX"; #endif - #if defined(USE_NEON) + #if defined(USE_NEON_DOTPROD) + compiler += " NEON_DOTPROD"; + #elif defined(USE_NEON) compiler += " NEON"; #endif From 4c43e1e27ce990735fb0226e35248fc82ea6a519 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Mon, 31 Jul 2023 13:41:28 +0200 Subject: [PATCH 0173/1309] Add new CPU archs in CI Tests workflow Add CPU archs: armv8-dotprod, riscv64 and ppc64le. The last two archs are built using QEMU multiarch docker container. References: https://docs.docker.com/build/building/multi-platform/ https://github.com/docker/setup-buildx-action https://github.com/docker/setup-qemu-action https://github.com/tonistiigi/binfmt https://stackoverflow.com/questions/72444103/what-does-running-the-multiarch-qemu-user-static-does-before-building-a-containe closes https://github.com/official-stockfish/Stockfish/pull/4718 No functional change --- .github/workflows/stockfish_test.yml | 63 +++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index cb6c4c5901b..307d3a02b30 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -38,6 +38,22 @@ jobs: comp: ndk run_armv7_tests: true shell: bash + - name: Linux GCC riscv64 + os: ubuntu-22.04 + compiler: g++ + comp: gcc + run_riscv64_tests: true + base_image: 'riscv64/alpine:edge' + platform: linux/riscv64 + shell: bash + - name: Linux GCC ppc64 + os: ubuntu-22.04 + compiler: g++ + comp: gcc + run_ppc64_tests: true + base_image: 'ppc64le/alpine:latest' + platform: linux/ppc64le + shell: bash - name: MacOS 13 Apple Clang os: macos-13 compiler: clang++ @@ -87,7 +103,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt update - sudo apt install expect valgrind g++-multilib qemu-user + sudo apt install expect valgrind g++-multilib qemu-user-static - name: Install NDK if: runner.os == 'Linux' @@ -103,6 +119,24 @@ jobs: echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV fi + - name: Set up QEMU + if: matrix.config.base_image + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + if: matrix.config.base_image + uses: docker/setup-buildx-action@v2 + + - name: Build Docker container + if: matrix.config.base_image + run: | + docker buildx build --load -t sf_builder - << EOF + FROM ${{ matrix.config.base_image }} + WORKDIR /app + RUN apk update && apk add make g++ + CMD sh make_sf.sh + EOF + - name: Download required macOS packages if: runner.os == 'macOS' run: brew install coreutils @@ -253,6 +287,15 @@ jobs: make -j2 ARCH=armv8 build ../tests/signature.sh $benchref + - name: Test armv8-dotprod build + if: matrix.config.run_armv8_tests + run: | + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + make clean + make -j2 ARCH=armv8-dotprod build + ../tests/signature.sh $benchref + # armv7 tests - name: Test armv7 build @@ -273,6 +316,24 @@ jobs: make -j2 ARCH=armv7-neon build ../tests/signature.sh $benchref + # riscv64 tests + + - name: Test riscv64 build + if: matrix.config.run_riscv64_tests + run: | + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > make_sf.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder + ../tests/signature.sh $benchref + + # ppc64 tests + + - name: Test ppc64 build + if: matrix.config.run_ppc64_tests + run: | + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > make_sf.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder + ../tests/signature.sh $benchref + # Other tests - name: Check perft and search reproducibility From a6d9a302b867a76c3df5b658de6206e77b649a4d Mon Sep 17 00:00:00 2001 From: AndrovT <31534597+AndrovT@users.noreply.github.com> Date: Tue, 1 Aug 2023 14:43:37 +0200 Subject: [PATCH 0174/1309] Implement AffineTransformSparseInput for armv8 Implements AffineTransformSparseInput layer for the NNUE evaluation for the armv8 and armv8-dotprod architectures. We measured some nice speed improvements via 10 runs of our benchmark: armv8, Cortex-X1 : 18.5% speed-up armv8, Cortex-A76 : 13.2% speed-up armv8-dotprod, Cortex-X1 : 27.1% speed-up armv8-dotprod, Cortex-A76 : 12.1% speed-up armv8, Cortex-A72, Raspberry Pi 4 : 8.2% speed-up (thanks Torom!) closes https://github.com/official-stockfish/Stockfish/pull/4719 No functional change --- .../layers/affine_transform_sparse_input.h | 100 ++++++++++++------ src/nnue/layers/simd.h | 18 +++- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 134b7d13e19..63cbaf45a34 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -35,7 +35,7 @@ namespace Stockfish::Eval::NNUE::Layers { -#if defined(USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ std::array, 256> v{}; for (unsigned i = 0; i < 256; ++i) @@ -50,19 +50,37 @@ namespace Stockfish::Eval::NNUE::Layers { // Find indices of nonzero numbers in an int32_t array template void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { -#if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) -#elif defined (USE_AVX2) - using vec_t = __m256i; - #if defined(USE_VNNI) && !defined(USE_AVXVNNI) - #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) - #else - #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) +#if defined (USE_SSSE3) + #if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #elif defined (USE_AVX2) + using vec_t = __m256i; + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) #endif -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) + using vec128_t = __m128i; + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #define vec128_load(a) _mm_load_si128(a) + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) +#elif defined (USE_NEON) + using vec_t = int32x4_t; + static const std::uint32_t Mask[4] = {1, 2, 4, 8}; + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + using vec128_t = int16x8_t; + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) #endif constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) @@ -73,8 +91,8 @@ namespace Stockfish::Eval::NNUE::Layers { const auto inputVector = reinterpret_cast(input); IndexType count = 0; - __m128i base = _mm_setzero_si128(); - const __m128i increment = _mm_set1_epi16(8); + vec128_t base = vec128_zero; + const vec128_t increment = vec128_set_16(8); for (IndexType i = 0; i < NumChunks; ++i) { // bitmask of nonzero values in this chunk @@ -87,15 +105,20 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType j = 0; j < OutputsPerChunk; ++j) { const auto lookup = (nnz >> (j * 8)) & 0xFF; - const auto offsets = _mm_loadu_si128(reinterpret_cast(&lookup_indices[lookup])); - _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets)); + const auto offsets = vec128_load(reinterpret_cast(&lookup_indices[lookup])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); count += popcount(lookup); - base = _mm_add_epi16(base, increment); + base = vec128_add(base, increment); } } count_out = count; } # undef vec_nnz +# undef vec128_zero +# undef vec128_set_16 +# undef vec128_load +# undef vec128_storeu +# undef vec128_add #endif // Sparse input implementation @@ -117,7 +140,7 @@ namespace Stockfish::Eval::NNUE::Layers { static constexpr IndexType PaddedOutputDimensions = ceil_to_multiple(OutputDimensions, MaxSimdWidth); -#if defined (USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) static constexpr IndexType ChunkSize = 4; #else static constexpr IndexType ChunkSize = 1; @@ -144,7 +167,7 @@ namespace Stockfish::Eval::NNUE::Layers { static constexpr IndexType get_weight_index(IndexType i) { -#if defined (USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) return get_weight_index_scrambled(i); #else return i; @@ -173,24 +196,34 @@ namespace Stockfish::Eval::NNUE::Layers { void propagate( const InputType* input, OutputType* output) const { -#if defined (USE_SSSE3) +#if (USE_SSSE3 | (USE_NEON >= 8)) #if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_setzero _mm512_setzero_si512 + using invec_t = __m512i; + using outvec_t = __m512i; #define vec_set_32 _mm512_set1_epi32 #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 #elif defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 + using invec_t = __m256i; + using outvec_t = __m256i; #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 + using invec_t = __m128i; + using outvec_t = __m128i; #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 +#elif defined (USE_NEON_DOTPROD) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 +#elif defined (USE_NEON) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 #endif - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; @@ -202,24 +235,23 @@ namespace Stockfish::Eval::NNUE::Layers { // Find indices of nonzero 32bit blocks find_nnz(input32, nnz, count); - const vec_t* biasvec = reinterpret_cast(biases); - vec_t acc[NumRegs]; + const outvec_t* biasvec = reinterpret_cast(biases); + outvec_t acc[NumRegs]; for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasvec[k]; for (IndexType j = 0; j < count; ++j) { const auto i = nnz[j]; - const vec_t in = vec_set_32(input32[i]); - const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + const invec_t in = vec_set_32(input32[i]); + const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); for (IndexType k = 0; k < NumRegs; ++k) vec_add_dpbusd_32(acc[k], in, col[k]); } - vec_t* outptr = reinterpret_cast(output); + outvec_t* outptr = reinterpret_cast(output); for (IndexType k = 0; k < NumRegs; ++k) outptr[k] = acc[k]; -# undef vec_setzero # undef vec_set_32 # undef vec_add_dpbusd_32 #else diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index fae31a62955..638e39941a8 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -239,6 +239,12 @@ namespace Stockfish::Simd { acc = vdotq_s32(acc, a1, b1); } + [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32( + int32x4_t& acc, + int8x16_t a, int8x16_t b) { + + acc = vdotq_s32(acc, a, b); + } #endif #if defined (USE_NEON) @@ -277,9 +283,19 @@ namespace Stockfish::Simd { product = vmlal_s8(product, a1, b1); acc = vpadalq_s16(acc, product); } - #endif +#if USE_NEON >= 8 + [[maybe_unused]] static void neon_m128_add_dpbusd_epi32( + int32x4_t& acc, + int8x16_t a, int8x16_t b) { + + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); + } +#endif } #endif // STOCKFISH_SIMD_H_INCLUDED From 0ad9b51deaaa1f2a8273ed064fbf6425cfbbe4f2 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 24 Jul 2023 01:22:21 -0400 Subject: [PATCH 0175/1309] Remove classical psqt Based on vondele's deletepsqt branch: https://github.com/vondele/Stockfish/commit/369f5b051 This huge simplification uses a weighted material differences instead of the positional piece square tables (psqt) in the semi-classical complexity calculation. Tuned weights using spsa at 45+0.45 with: int pawnMult = 100; int knightMult = 325; int bishopMult = 350; int rookMult = 500; int queenMult = 900; TUNE(SetRange(0, 200), pawnMult); TUNE(SetRange(0, 650), knightMult); TUNE(SetRange(0, 700), bishopMult); TUNE(SetRange(200, 800), rookMult); TUNE(SetRange(600, 1200), queenMult); The values obtained via this tuning session were for a model where the psqt replacement formula was always from the point of view of White, even if the side to move was Black. We re-used the same values for an implementation with a psqt replacement from the point of view of the side to move, testing the result both on our standard book on positions with a strong White bias, and an alternate book with positions with a strong Black bias. We note that with the patch the last use of the venerable "Score" type disappears in Stockfish codebase (the Score type was used in classical evaluation to get a tampered eval interpolating values smoothly from the early midgame stage to the endgame stage). We leave it to another commit to clean all occurrences of Score in the code and the comments. ------- Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142542 W: 36264 L: 36168 D: 70110 Ptnml(0-2): 76, 15578, 39856, 15696, 65 https://tests.stockfishchess.org/tests/view/64c8cb495b17f7c21c0cf9f8 Passed non-regression LTC (with a book with Black bias): https://tests.stockfishchess.org/tests/view/64c8f9295b17f7c21c0cfdaf LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 494814 W: 125565 L: 125827 D: 243422 Ptnml(0-2): 244, 53926, 139346, 53630, 261 ------ closes https://github.com/official-stockfish/Stockfish/pull/4713 Bench: 1655985 --- src/Makefile | 2 +- src/evaluate.cpp | 9 +++- src/main.cpp | 2 - src/position.h | 10 ---- src/psqt.cpp | 131 ----------------------------------------------- src/psqt.h | 38 -------------- 6 files changed, 8 insertions(+), 184 deletions(-) delete mode 100644 src/psqt.cpp delete mode 100644 src/psqt.h diff --git a/src/Makefile b/src/Makefile index f66d84d5547..8811d15e868 100644 --- a/src/Makefile +++ b/src/Makefile @@ -53,7 +53,7 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench ### Source and object files SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ - misc.cpp movegen.cpp movepick.cpp position.cpp psqt.cpp \ + misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7f0ea4bc604..c37dd98ad1c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -143,7 +143,6 @@ Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); Value v; - Value psq = pos.psq_eg_stm(); int nnueComplexity; int npm = pos.non_pawn_material() / 64; @@ -153,8 +152,14 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + int material = 67 * (pos.count(stm) - pos.count(~stm)) + + 395 * (pos.count(stm) - pos.count(~stm)) + + 288 * (pos.count(stm) - pos.count(~stm)) + + 630 * (pos.count(stm) - pos.count(~stm)) + + 857 * (pos.count(stm) - pos.count(~stm)); + // Blend optimism with nnue complexity and (semi)classical complexity - optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512; + optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; v = ( nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm + pos.count())) / 1024; diff --git a/src/main.cpp b/src/main.cpp index 593408f63a7..c854ac0cd16 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,7 +20,6 @@ #include "bitboard.h" #include "position.h" -#include "psqt.h" #include "search.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -36,7 +35,6 @@ int main(int argc, char* argv[]) { CommandLine::init(argc, argv); UCI::init(Options); Tune::init(); - PSQT::init(); Bitboards::init(); Position::init(); Threads.set(size_t(Options["Threads"])); diff --git a/src/position.h b/src/position.h index dc4c5837299..393c1ac9226 100644 --- a/src/position.h +++ b/src/position.h @@ -26,7 +26,6 @@ #include "bitboard.h" #include "evaluate.h" -#include "psqt.h" #include "types.h" #include "nnue/nnue_accumulator.h" @@ -153,7 +152,6 @@ class Position { bool has_game_cycle(int ply) const; bool has_repeated() const; int rule50_count() const; - Value psq_eg_stm() const; Value non_pawn_material(Color c) const; Value non_pawn_material() const; @@ -192,7 +190,6 @@ class Position { StateInfo* st; int gamePly; Color sideToMove; - Score psq; bool chess960; }; @@ -321,10 +318,6 @@ inline Key Position::material_key() const { return st->materialKey; } -inline Value Position::psq_eg_stm() const { - return (sideToMove == WHITE ? 1 : -1) * eg_value(psq); -} - inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } @@ -374,7 +367,6 @@ inline void Position::put_piece(Piece pc, Square s) { byColorBB[color_of(pc)] |= s; pieceCount[pc]++; pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; - psq += PSQT::psq[pc][s]; } inline void Position::remove_piece(Square s) { @@ -386,7 +378,6 @@ inline void Position::remove_piece(Square s) { board[s] = NO_PIECE; pieceCount[pc]--; pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; - psq -= PSQT::psq[pc][s]; } inline void Position::move_piece(Square from, Square to) { @@ -398,7 +389,6 @@ inline void Position::move_piece(Square from, Square to) { byColorBB[color_of(pc)] ^= fromTo; board[from] = NO_PIECE; board[to] = pc; - psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; } inline void Position::do_move(Move m, StateInfo& newSt) { diff --git a/src/psqt.cpp b/src/psqt.cpp deleted file mode 100644 index d3ebb20da1b..00000000000 --- a/src/psqt.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - - -#include "psqt.h" - -#include - -#include "bitboard.h" -#include "types.h" - -namespace Stockfish { - -namespace -{ - -auto constexpr S = make_score; - -// 'Bonus' contains Piece-Square parameters. -// Scores are explicit for files A to D, implicitly mirrored for E to H. -constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { - { }, - { }, - { // Knight - { S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) }, - { S( -77, -67), S(-41,-54), S(-27,-18), S(-15, 8) }, - { S( -61, -40), S(-17,-27), S( 6, -8), S( 12, 29) }, - { S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) }, - { S( -34, -45), S( 13,-16), S( 44, 9), S( 51, 39) }, - { S( -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) }, - { S( -67, -69), S(-27,-50), S( 4,-51), S( 37, 12) }, - { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) } - }, - { // Bishop - { S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) }, - { S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) }, - { S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) }, - { S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) }, - { S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) }, - { S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) }, - { S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) }, - { S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) } - }, - { // Rook - { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) }, - { S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) }, - { S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) }, - { S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) }, - { S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) }, - { S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) }, - { S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) }, - { S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) } - }, - { // Queen - { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, - { S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) }, - { S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) }, - { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) }, - { S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) }, - { S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) }, - { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, - { S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) } - }, - { // King - { S(271, 1), S(327, 45), S(271, 85), S(198, 76) }, - { S(278, 53), S(303,100), S(234,133), S(179,135) }, - { S(195, 88), S(258,130), S(169,169), S(120,175) }, - { S(164,103), S(190,156), S(138,172), S( 98,172) }, - { S(154, 96), S(179,166), S(105,199), S( 70,199) }, - { S(123, 92), S(145,172), S( 81,184), S( 31,191) }, - { S( 88, 47), S(120,121), S( 65,116), S( 33,131) }, - { S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) } - } -}; - -constexpr Score PBonus[RANK_NB][FILE_NB] = - { // Pawn (asymmetric distribution) - { }, - { S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) }, - { S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) }, - { S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) }, - { S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) }, - { S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) }, - { S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) } - }; - -} // namespace - - -namespace PSQT -{ - -Score psq[PIECE_NB][SQUARE_NB]; - -// PSQT::init() initializes piece-square tables: the white halves of the tables are -// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of -// the tables are initialized by flipping and changing the sign of the white scores. -void init() { - - for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING}) - { - Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); - - for (Square s = SQ_A1; s <= SQ_H8; ++s) - { - File f = File(edge_distance(file_of(s))); - psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] - : Bonus[pc][rank_of(s)][f]); - psq[~pc][flip_rank(s)] = -psq[pc][s]; - } - } -} - -} // namespace PSQT - -} // namespace Stockfish diff --git a/src/psqt.h b/src/psqt.h deleted file mode 100644 index 9630f446bc3..00000000000 --- a/src/psqt.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - - -#ifndef PSQT_H_INCLUDED -#define PSQT_H_INCLUDED - - -#include "types.h" - - -namespace Stockfish::PSQT -{ - -extern Score psq[PIECE_NB][SQUARE_NB]; - -// Fill psqt array from a set of internally linked parameters -void init(); - -} // namespace Stockfish::PSQT - - -#endif // PSQT_H_INCLUDED From a26f8d37e108c103ada129e619a17597c7e50046 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 5 Aug 2023 14:21:08 +0300 Subject: [PATCH 0176/1309] Tweak formula for pruning moves losing material Simplify the "Prune moves with negative SEE" formula, by removing one multiplication and subtraction operation. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 214272 W: 54596 L: 54572 D: 105104 Ptnml(0-2): 741, 25160, 55320, 25164, 751 https://tests.stockfishchess.org/tests/view/64c430d1dc56e1650abbdbf6 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 238380 W: 60600 L: 60601 D: 117179 Ptnml(0-2): 132, 26069, 66791, 26064, 134 https://tests.stockfishchess.org/tests/view/64c81f155b17f7c21c0cee2b closes https://github.com/official-stockfish/Stockfish/pull/4721 bench: 1655337 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 45758031669..dc439ed00e5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1037,7 +1037,7 @@ namespace { lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 16 * lmrDepth))) + if (!pos.see_ge(move, Value(-31 * lmrDepth * lmrDepth))) continue; } } From 5c2111cc30b283aa5b7e1cc1c1e9d7c52e1e910b Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 7 Aug 2023 02:32:38 +0300 Subject: [PATCH 0177/1309] Adjust futility pruning base in qsearch Current master used value from transposition table there if it existed, this patch uses minimum between this tt value and the static eval instead (this thus is closer to the main search function, which uses the static eval). Passed STC: https://tests.stockfishchess.org/tests/view/64cd57285b17f7c21c0d6a8c LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 252544 W: 64671 L: 64039 D: 123834 Ptnml(0-2): 839, 29207, 65575, 29785, 866 Passed LTC: https://tests.stockfishchess.org/tests/view/64cf6c915b17f7c21c0d9fcb LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 60150 W: 15374 L: 15012 D: 29764 Ptnml(0-2): 24, 6321, 17024, 6681, 25 closes https://github.com/official-stockfish/Stockfish/pull/4725 Bench: 1573024 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index dc439ed00e5..24f54c32bb3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1495,7 +1495,7 @@ namespace { if (bestValue > alpha) alpha = bestValue; - futilityBase = bestValue + 200; + futilityBase = std::min(ss->staticEval, bestValue) + 200; } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, From e64b817e0a2335824ef1f1f7ba9f5cd4310994e1 Mon Sep 17 00:00:00 2001 From: Cody Ho Date: Sun, 6 Aug 2023 14:21:22 -0700 Subject: [PATCH 0178/1309] Remove all references to Score type Score is obsolete with the removal of psqt. No functional change. Signed-off-by: Cody Ho closes https://github.com/official-stockfish/Stockfish/pull/4724 --- src/tune.cpp | 13 ------------- src/tune.h | 6 ++---- src/types.h | 55 ---------------------------------------------------- 3 files changed, 2 insertions(+), 72 deletions(-) diff --git a/src/tune.cpp b/src/tune.cpp index 41f6664d9ca..ccfc33c5082 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -92,19 +92,6 @@ template<> void Tune::Entry::read_option() { value = Value(int(Options[name])); } -template<> void Tune::Entry::init_option() { - make_option("m" + name, mg_value(value), range); - make_option("e" + name, eg_value(value), range); -} - -template<> void Tune::Entry::read_option() { - if (Options.count("m" + name)) - value = make_score(int(Options["m" + name]), eg_value(value)); - - if (Options.count("e" + name)) - value = make_score(mg_value(value), int(Options["e" + name])); -} - // Instead of a variable here we have a PostUpdate function: just call it template<> void Tune::Entry::init_option() {} template<> void Tune::Entry::read_option() { value(); } diff --git a/src/tune.h b/src/tune.h index 440d950a9ec..bdbee14e014 100644 --- a/src/tune.h +++ b/src/tune.h @@ -51,18 +51,17 @@ struct SetRange { /// qualifiers from the variables you want to tune and flag them for tuning, so /// if you have: /// -/// const Score myScore = S(10, 15); /// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; /// /// If you have a my_post_update() function to run after values have been updated, /// and a my_range() function to set custom Option's min-max values, then you just /// remove the 'const' qualifiers and write somewhere below in the file: /// -/// TUNE(SetRange(my_range), myScore, myValue, my_post_update); +/// TUNE(SetRange(my_range), myValue, my_post_update); /// /// You can also set the range directly, and restore the default at the end /// -/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange); +/// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); /// /// In case update function is slow and you have many parameters, you can add: /// @@ -98,7 +97,6 @@ class Tune { static_assert( std::is_same::value || std::is_same::value - || std::is_same::value || std::is_same::value, "Parameter type not supported!"); Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} diff --git a/src/types.h b/src/types.h index 06b0a05985a..b0c11778c9d 100644 --- a/src/types.h +++ b/src/types.h @@ -154,8 +154,6 @@ enum CastlingRights { }; enum Phase { - PHASE_ENDGAME, - PHASE_MIDGAME = 128, MG = 0, EG = 1, PHASE_NB = 2 }; @@ -194,8 +192,6 @@ enum Value : int { BishopValueMg = 825, BishopValueEg = 915, RookValueMg = 1276, RookValueEg = 1380, QueenValueMg = 2538, QueenValueEg = 2682, - - MidgameLimit = 15258, EndgameLimit = 3915 }; enum PieceType { @@ -281,29 +277,6 @@ struct DirtyPiece { Square to[3]; }; -/// Score enum stores a middlegame and an endgame value in a single integer (enum). -/// The least significant 16 bits are used to store the middlegame value and the -/// upper 16 bits are used to store the endgame value. We have to take care to -/// avoid left-shifting a signed int to avoid undefined behavior. -enum Score : int { SCORE_ZERO }; - -constexpr Score make_score(int mg, int eg) { - return Score((int)((unsigned int)eg << 16) + mg); -} - -/// Extracting the signed lower and upper 16 bits is not so trivial because -/// according to the standard a simple cast to short is implementation defined -/// and so is a right shift of a signed integer. -inline Value eg_value(Score s) { - union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; - return Value(eg.s); -} - -inline Value mg_value(Score s) { - union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; - return Value(mg.s); -} - #define ENABLE_BASE_OPERATORS_ON(T) \ constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ @@ -333,8 +306,6 @@ ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(Rank) -ENABLE_BASE_OPERATORS_ON(Score) - #undef ENABLE_FULL_OPERATORS_ON #undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON @@ -345,32 +316,6 @@ constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d inline Square& operator+=(Square& s, Direction d) { return s = s + d; } inline Square& operator-=(Square& s, Direction d) { return s = s - d; } -/// Only declared but not defined. We don't want to multiply two scores due to -/// a very high risk of overflow. So user should explicitly convert to integer. -Score operator*(Score, Score) = delete; - -/// Division of a Score must be handled separately for each term -inline Score operator/(Score s, int i) { - return make_score(mg_value(s) / i, eg_value(s) / i); -} - -/// Multiplication of a Score by an integer. We check for overflow in debug mode. -inline Score operator*(Score s, int i) { - - Score result = Score(int(s) * i); - - assert(eg_value(result) == (i * eg_value(s))); - assert(mg_value(result) == (i * mg_value(s))); - assert((i == 0) || (result / i) == s); - - return result; -} - -/// Multiplication of a Score by a boolean -inline Score operator*(Score s, bool b) { - return b ? s : SCORE_ZERO; -} - constexpr Color operator~(Color c) { return Color(c ^ BLACK); // Toggle color } From 0d2ddb81ef44211e7bf40369dc8fc52160d0ee18 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Mon, 7 Aug 2023 13:11:09 +0200 Subject: [PATCH 0179/1309] Fix Makefile for incorrect nnue file If an incorrect network file is present at the start of the compilation stage, the Makefile script now correctly removes it before trying to download a clean version. closes https://github.com/official-stockfish/Stockfish/pull/4726 No functional change --- src/Makefile | 83 +++++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/src/Makefile b/src/Makefile index 8811d15e868..c7e059ea925 100644 --- a/src/Makefile +++ b/src/Makefile @@ -869,42 +869,6 @@ install: clean: objclean profileclean @rm -f .depend *~ core -# evaluation network (nnue) -net: - $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) - @echo "Default net: $(nnuenet)" - $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) - $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) - $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) - @if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ - fi - $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) - @if [ "x$(shasum_command)" = "x" ]; then \ - echo "shasum / sha256sum not found, skipping net validation"; \ - fi - @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ - if test -f "$(nnuenet)"; then \ - echo "$(nnuenet) available."; \ - else \ - if [ "x$(curl_or_wget)" != "x" ]; then \ - echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ - else \ - echo "No net found and download not possible"; exit 1;\ - fi; \ - fi; \ - if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Removing failed download"; rm -f $(nnuenet); \ - else \ - echo "Network validated"; break; \ - fi; \ - fi; \ - done - @if ! test -f "$(nnuenet)"; then \ - echo "Failed to download $(nnuenet)."; \ - fi - # clean binaries and objects objclean: @rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o @@ -919,6 +883,53 @@ profileclean: @rm -f stockfish.res @rm -f ./-lstdc++.res +# set up shell variables for the net stuff +netvariables: + $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) + $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) + $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) + $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) + +# evaluation network (nnue) +net: netvariables + @echo "Default net: $(nnuenet)" + @if [ "x$(curl_or_wget)" = "x" ]; then \ + echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ + fi + @if [ "x$(shasum_command)" = "x" ]; then \ + echo "shasum / sha256sum not found, skipping net validation"; \ + elif test -f "$(nnuenet)"; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing invalid network"; rm -f $(nnuenet); \ + fi; \ + fi; + @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ + if test -f "$(nnuenet)"; then \ + echo "$(nnuenet) available : OK"; break; \ + else \ + if [ "x$(curl_or_wget)" != "x" ]; then \ + echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ + else \ + echo "No net found and download not possible"; exit 1;\ + fi; \ + fi; \ + if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing failed download"; rm -f $(nnuenet); \ + fi; \ + fi; \ + done + @if ! test -f "$(nnuenet)"; then \ + echo "Failed to download $(nnuenet)."; \ + fi; + @if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Network validated"; break; \ + fi; \ + fi; \ + +# default target default: help From 8192945870967fb9c8801247d0b040b2bc657443 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 9 Aug 2023 15:34:53 +0200 Subject: [PATCH 0180/1309] Improve testing coverage, remove unused code a) Add further tests to CI to cover most features. This uncovered a potential race in case setoption was sent between two searches. As the UCI protocol requires this sent to be went the engine is not searching, setoption now ensures that this is the case. b) Remove some unused code closes https://github.com/official-stockfish/Stockfish/pull/4730 No functional change --- src/nnue/layers/simd.h | 55 ------------------------------------------ src/syzygy/tbprobe.h | 23 ------------------ src/types.h | 1 - src/uci.cpp | 2 ++ tests/instrumented.sh | 52 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 52 insertions(+), 81 deletions(-) diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 638e39941a8..f478cd7819f 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -79,23 +79,6 @@ namespace Stockfish::Simd { return _mm512_add_epi32(sum0123a, sum0123b); } - [[maybe_unused]] static __m128i m512_haddx4( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3, - __m128i bias) { - - __m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3); - - __m256i sum256lo = _mm512_castsi512_si256(sum); - __m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1); - - sum256lo = _mm256_add_epi32(sum256lo, sum256hi); - - __m128i sum128lo = _mm256_castsi256_si128(sum256lo); - __m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - } - [[maybe_unused]] static void m512_add_dpbusd_epi32( __m512i& acc, __m512i a, @@ -138,21 +121,6 @@ namespace Stockfish::Simd { return _mm_cvtsi128_si32(sum128) + bias; } - [[maybe_unused]] static __m128i m256_haddx4( - __m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3, - __m128i bias) { - - sum0 = _mm256_hadd_epi32(sum0, sum1); - sum2 = _mm256_hadd_epi32(sum2, sum3); - - sum0 = _mm256_hadd_epi32(sum0, sum2); - - __m128i sum128lo = _mm256_castsi256_si128(sum0); - __m128i sum128hi = _mm256_extracti128_si256(sum0, 1); - - return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias); - } - [[maybe_unused]] static void m256_add_dpbusd_epi32( __m256i& acc, __m256i a, @@ -194,16 +162,6 @@ namespace Stockfish::Simd { return _mm_cvtsi128_si32(sum) + bias; } - [[maybe_unused]] static __m128i m128_haddx4( - __m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3, - __m128i bias) { - - sum0 = _mm_hadd_epi32(sum0, sum1); - sum2 = _mm_hadd_epi32(sum2, sum3); - sum0 = _mm_hadd_epi32(sum0, sum2); - return _mm_add_epi32(sum0, bias); - } - [[maybe_unused]] static void m128_add_dpbusd_epi32( __m128i& acc, __m128i a, @@ -261,19 +219,6 @@ namespace Stockfish::Simd { return neon_m128_reduce_add_epi32(sum) + bias; } - [[maybe_unused]] static int32x4_t neon_m128_haddx4( - int32x4_t sum0, int32x4_t sum1, int32x4_t sum2, int32x4_t sum3, - int32x4_t bias) { - - int32x4_t hsums { - neon_m128_reduce_add_epi32(sum0), - neon_m128_reduce_add_epi32(sum1), - neon_m128_reduce_add_epi32(sum2), - neon_m128_reduce_add_epi32(sum3) - }; - return vaddq_s32(hsums, bias); - } - [[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2( int32x4_t& acc, int8x8_t a0, int8x8_t b0, diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 159c68652d1..fe994f68e91 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,8 +19,6 @@ #ifndef TBPROBE_H #define TBPROBE_H -#include - #include "../search.h" namespace Stockfish::Tablebases { @@ -50,27 +48,6 @@ bool root_probe(Position& pos, Search::RootMoves& rootMoves); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); -inline std::ostream& operator<<(std::ostream& os, const WDLScore v) { - - os << (v == WDLLoss ? "Loss" : - v == WDLBlessedLoss ? "Blessed loss" : - v == WDLDraw ? "Draw" : - v == WDLCursedWin ? "Cursed win" : - v == WDLWin ? "Win" : "None"); - - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { - - os << (v == FAIL ? "Failed" : - v == OK ? "Success" : - v == CHANGE_STM ? "Probed opponent side" : - v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None"); - - return os; -} - } // namespace Stockfish::Tablebases #endif diff --git a/src/types.h b/src/types.h index b0c11778c9d..5d78377690a 100644 --- a/src/types.h +++ b/src/types.h @@ -300,7 +300,6 @@ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(Direction) -ENABLE_INCR_OPERATORS_ON(Piece) ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) diff --git a/src/uci.cpp b/src/uci.cpp index f893bd9cece..ffe5e0576ea 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -98,6 +98,8 @@ namespace { void setoption(istringstream& is) { + Threads.main()->wait_for_search_finished(); + string token, name, value; is >> token; // Consume the "name" token diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 1b37c7a8b75..637d19f9d63 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -64,14 +64,32 @@ EOF ;; esac +cat << EOF > bench_tmp.epd +Rn6/1rbq1bk1/2p2n1p/2Bp1p2/3Pp1pP/1N2P1P1/2Q1NPB1/6K1 w - - 2 26 +rnbqkb1r/ppp1pp2/5n1p/3p2p1/P2PP3/5P2/1PP3PP/RNBQKBNR w KQkq - 0 3 +3qnrk1/4bp1p/1p2p1pP/p2bN3/1P1P1B2/P2BQ3/5PP1/4R1K1 w - - 9 28 +r4rk1/1b2ppbp/pq4pn/2pp1PB1/1p2P3/1P1P1NN1/1PP3PP/R2Q1RK1 w - - 0 13 +EOF + # simple command line testing for args in "eval" \ "go nodes 1000" \ "go depth 10" \ + "go perft 4" \ "go movetime 1000" \ "go wtime 8000 btime 8000 winc 500 binc 500" \ + "go wtime 1000 btime 1000 winc 0 binc 0" \ + "go wtime 1000 btime 1000 winc 0 binc 0" \ + "go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5" \ + "go movetime 200" \ + "go nodes 20000 searchmoves e2e4 d2d4" \ "bench 128 $threads 8 default depth" \ - "export_net verify.nnue" + "bench 128 $threads 3 bench_tmp.epd depth" \ + "export_net verify.nnue" \ + "d" \ + "compiler" \ + "license" \ + "uci" do echo "$prefix $exeprefix ./stockfish $args $postfix" @@ -92,6 +110,7 @@ cat << EOF > game.exp send "uci\n" expect "uciok" + # send "setoption name Debug Log File value debug.log\n" send "setoption name Threads value $threads\n" send "ucinewgame\n" @@ -107,6 +126,28 @@ cat << EOF > game.exp send "go depth 10\n" expect "bestmove" + send "setoption name UCI_ShowWDL value true\n" + send "position startpos\n" + send "flip\n" + send "go depth 5\n" + expect "bestmove" + + send "setoption name Skill Level value 10\n" + send "position startpos\n" + send "go depth 5\n" + expect "bestmove" + + send "setoption name Clear Hash\n" + + send "setoption name EvalFile value verify.nnue\n" + send "position startpos\n" + send "go depth 5\n" + expect "bestmove" + + send "setoption name MultiPV value 4\n" + send "position startpos\n" + send "go depth 5\n" + send "quit\n" expect eof @@ -128,6 +169,13 @@ cat << EOF > syzygy.exp send "setoption name SyzygyPath value ../tests/syzygy/\n" expect "info string Found 35 tablebases" {} timeout {exit 1} send "bench 128 1 8 default depth\n" + send "ucinewgame\n" + send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n" + send "go depth 5\n" + expect "bestmove" + send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n" + send "go depth 5\n" + expect "bestmove" send "quit\n" expect eof @@ -146,6 +194,6 @@ do done -rm -f tsan.supp +rm -f tsan.supp bench_tmp.epd echo "instrumented testing OK" From 4be94f41a6119f6d463e13adc6aaf5e02383da63 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 13 Aug 2023 10:59:35 +0200 Subject: [PATCH 0181/1309] Update sanitizer CI to ubuntu 22.04 might fix the tsan errors closes https://github.com/official-stockfish/Stockfish/pull/4745 No functional change --- .github/workflows/stockfish_sanitizers.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index 305b8557bfb..228742b3f12 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -12,8 +12,8 @@ jobs: strategy: matrix: config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 + - name: Ubuntu 22.04 GCC + os: ubuntu-22.04 compiler: g++ comp: gcc shell: bash From d97a02ea2b9328e666aff7a906820c9ec65ab381 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 13 Aug 2023 11:03:28 +0300 Subject: [PATCH 0182/1309] Give extra bonus to main history for moves that caused a fail low. #4744 Current master gives this type of bonuses to continuation history, this patch also gives them to main history. Passed STC: https://tests.stockfishchess.org/tests/view/64d4802a5b17f7c21c0e27b3 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 480768 W: 122767 L: 121798 D: 236203 Ptnml(0-2): 1563, 56190, 123834, 57309, 1488 Passed LTC: https://tests.stockfishchess.org/tests/view/64d7e4c05b17f7c21c0e7456 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 32052 W: 8329 L: 8022 D: 15701 Ptnml(0-2): 19, 3335, 9015, 3634, 23 closes https://github.com/official-stockfish/Stockfish/pull/4744 Bench: 1711793 --- src/search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/search.cpp b/src/search.cpp index 24f54c32bb3..ce9ed9508aa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1373,6 +1373,7 @@ namespace { { int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; } if (PvNode) From 222f3ea55bab2414c4c260391ffd14dabc1684df Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 8 Aug 2023 17:06:31 +0800 Subject: [PATCH 0183/1309] Simplify a depth condition As the negative extension term has sensitive scaling, it would make more sense to allow more negative extension also at lower depth, and not just a region between low and high depth. Passed STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 124096 W: 31611 L: 31485 D: 61000 Ptnml(0-2): 422, 14437, 32205, 14561, 423 https://tests.stockfishchess.org/tests/view/64d205d75b17f7c21c0dea82 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 387882 W: 97840 L: 97993 D: 192049 Ptnml(0-2): 198, 42004, 109668, 41895, 176 https://tests.stockfishchess.org/tests/view/64d333f85b17f7c21c0e06c6 closes https://github.com/official-stockfish/Stockfish/pull/4743 Bench: 1542357 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ce9ed9508aa..03d72b95373 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1099,7 +1099,7 @@ namespace { // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) else if (cutNode) - extension = depth > 8 && depth < 17 ? -3 : -1; + extension = depth < 17 ? -3 : -1; // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) From b7b7a3f3fa786449832bd84d501c1183290f3e3a Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Thu, 10 Aug 2023 10:19:40 +0300 Subject: [PATCH 0184/1309] Detect repetitions before they happen in qsearch Passed STC: https://tests.stockfishchess.org/tests/view/64d495995b17f7c21c0e29ed LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 340288 W: 86664 L: 85910 D: 167714 Ptnml(0-2): 1030, 38855, 89697, 39455, 1107 Passed LTC: https://tests.stockfishchess.org/tests/view/64d5e1085b17f7c21c0e4ab5 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 193230 W: 49235 L: 48606 D: 95389 Ptnml(0-2): 98, 20432, 54921, 21071, 93 closes https://github.com/official-stockfish/Stockfish/pull/4742 Bench: 1579576 --- src/search.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 03d72b95373..44e13dae8d3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1410,6 +1410,18 @@ namespace { assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); + // Check if we have an upcoming move that draws by repetition, or + // if the opponent had an alternative move earlier to this position. + if ( depth < 0 + && pos.rule50_count() >= 3 + && alpha < VALUE_DRAW + && pos.has_game_cycle(ss->ply)) + { + alpha = value_draw(pos.this_thread()); + if (alpha >= beta) + return alpha; + } + Move pv[MAX_PLY+1]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); From 84e97a38a3966a38add0f3b07bd011fa707ec5be Mon Sep 17 00:00:00 2001 From: Gabrik <> Date: Fri, 11 Aug 2023 23:54:48 +0200 Subject: [PATCH 0185/1309] Remove the unused enum ScaleFactor closes https://github.com/official-stockfish/Stockfish/pull/4740 No functional change --- AUTHORS | 1 + src/types.h | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 2f323d64334..5622ca8cec9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -74,6 +74,7 @@ fanon Fauzi Akram Dabat (FauziAkram) Felix Wittmann gamander +Gabriele Lombardo (gabe) Gary Heckman (gheckman) George Sobala (gsobala) gguliash diff --git a/src/types.h b/src/types.h index 5d78377690a..637e1675abf 100644 --- a/src/types.h +++ b/src/types.h @@ -157,13 +157,6 @@ enum Phase { MG = 0, EG = 1, PHASE_NB = 2 }; -enum ScaleFactor { - SCALE_FACTOR_DRAW = 0, - SCALE_FACTOR_NORMAL = 64, - SCALE_FACTOR_MAX = 128, - SCALE_FACTOR_NONE = 255 -}; - enum Bound { BOUND_NONE, BOUND_UPPER, From 796d9df6438b416a63364c7cf5edbc9be5434101 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Fri, 11 Aug 2023 16:57:26 +0200 Subject: [PATCH 0186/1309] Check compiler for docker builds in CI closes https://github.com/official-stockfish/Stockfish/pull/4739 No functional change --- .github/workflows/stockfish_test.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 307d3a02b30..72f0c22e136 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -134,7 +134,7 @@ jobs: FROM ${{ matrix.config.base_image }} WORKDIR /app RUN apk update && apk add make g++ - CMD sh make_sf.sh + CMD ["sh", "script.sh"] EOF - name: Download required macOS packages @@ -160,10 +160,15 @@ jobs: - name: Check compiler run: | - if [ $COMP == ndk ]; then - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + if [ -z "${{ matrix.config.base_image }}" ]; then + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + fi + $COMPILER -v + else + echo "$COMPILER -v" > script.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder fi - $COMPILER -v - name: Test help target run: make help @@ -321,7 +326,7 @@ jobs: - name: Test riscv64 build if: matrix.config.run_riscv64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > make_sf.sh + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref @@ -330,7 +335,7 @@ jobs: - name: Test ppc64 build if: matrix.config.run_ppc64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > make_sf.sh + echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref From c02ee70927bcb90240f40d8e580e30dc622d5ce9 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 10 Aug 2023 18:44:58 +0300 Subject: [PATCH 0187/1309] Simplify prior countermove bonus Swapping a multiplication operation between two terms with a simple constant Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 60512 W: 15424 L: 15231 D: 29857 Ptnml(0-2): 200, 6985, 15712, 7140, 219 https://tests.stockfishchess.org/tests/view/64d2addf5b17f7c21c0dfae6 Passed LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 108456 W: 27545 L: 27414 D: 53497 Ptnml(0-2): 63, 11629, 30698, 11790, 48 https://tests.stockfishchess.org/tests/view/64d3ab6e5b17f7c21c0e1188 closes https://github.com/official-stockfish/Stockfish/pull/4738 Bench: 1636213 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 44e13dae8d3..e51e2f4dae1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1371,7 +1371,7 @@ namespace { // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12); + int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 800) + ((ss-1)->moveCount > 12); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; } From 495852fecdd9ce0fe0c1e9c0518f1bc01ccfa239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicolet?= Date: Thu, 10 Aug 2023 06:31:48 +0200 Subject: [PATCH 0188/1309] Simplify SEE pruning for captures It seems that the current search is smart enough to allow us to remove (again) the block of code that checks for discovered attacks after the first pruning condition for captures. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 278848 W: 70856 L: 70903 D: 137089 Ptnml(0-2): 960, 32829, 71894, 32780, 961 https://tests.stockfishchess.org/tests/view/64d0af095b17f7c21c0dc440 LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 100704 W: 25564 L: 25425 D: 49715 Ptnml(0-2): 56, 10858, 28381, 11005, 52 https://tests.stockfishchess.org/tests/view/64d293e85b17f7c21c0df844 closes https://github.com/official-stockfish/Stockfish/pull/4736 Bench: 1470572 --- src/search.cpp | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e51e2f4dae1..970b0f4764b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -989,28 +989,9 @@ namespace { + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; - Bitboard occupied; - // SEE based pruning (~11 Elo) - if (!pos.see_ge(move, occupied, Value(-205) * depth)) - { - if (depth < 2 - capture) - continue; - // Don't prune the move if opponent Queen/Rook is under discovered attack after the exchanges - // Don't prune the move if opponent King is under discovered attack after or during the exchanges - Bitboard leftEnemies = (pos.pieces(~us, KING, QUEEN, ROOK)) & occupied; - Bitboard attacks = 0; - occupied |= to_sq(move); - while (leftEnemies && !attacks) - { - Square sq = pop_lsb(leftEnemies); - attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied; - // Don't consider pieces that were already threatened/hanging before SEE exchanges - if (attacks && (sq != pos.square(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us)))) - attacks = 0; - } - if (!attacks) - continue; - } + // SEE based pruning for captures and checks (~11 Elo) + if (!pos.see_ge(move, Value(-205) * depth)) + continue; } else { From 3322349c1a3dbe2f4c42f84141745c4d94efde2e Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Mon, 7 Aug 2023 22:27:12 +0300 Subject: [PATCH 0189/1309] Simplify pieceValue to one phase. Simplifies the usage of pieceValues to mg values with the exception of pawnValues, After the removal of PSQT. passed STC: https://tests.stockfishchess.org/tests/view/64d147845b17f7c21c0dd86c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 197248 W: 50168 L: 50125 D: 96955 Ptnml(0-2): 651, 23029, 51222, 23070, 652 passed LTC: https://tests.stockfishchess.org/tests/view/64d212de5b17f7c21c0debbb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 181170 W: 45949 L: 45893 D: 89328 Ptnml(0-2): 84, 19656, 51052, 19706, 87 closes https://github.com/official-stockfish/Stockfish/pull/4734 Bench: 1494401 --- src/movepick.cpp | 4 ++-- src/position.cpp | 20 ++++++++++---------- src/search.cpp | 6 +++--- src/syzygy/tbprobe.cpp | 4 ++-- src/types.h | 22 +++++++--------------- 5 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 4050810338f..9d5805a70a4 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -122,7 +122,7 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) - m.value = (7 * int(PieceValue[MG][pos.piece_on(to_sq(m))]) + m.value = (7 * int(PieceValue[pos.piece_on(to_sq(m))]) + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; else if constexpr (Type == QUIETS) @@ -165,7 +165,7 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + (1 << 28); else diff --git a/src/position.cpp b/src/position.cpp index 16181e96249..675dec99f04 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -347,7 +347,7 @@ void Position::set_state() const { st->key ^= Zobrist::psq[pc][s]; if (type_of(pc) != KING && type_of(pc) != PAWN) - st->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; + st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; } if (st->epSquare != SQ_NONE) @@ -742,7 +742,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } } else - st->nonPawnMaterial[them] -= PieceValue[MG][captured]; + st->nonPawnMaterial[them] -= PieceValue[captured]; dp.dirty_num = 2; // 1 piece moved, 1 piece captured dp.piece[1] = captured; @@ -822,7 +822,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ^ Zobrist::psq[pc][pieceCount[pc]]; // Update material - st->nonPawnMaterial[us] += PieceValue[MG][promotion]; + st->nonPawnMaterial[us] += PieceValue[promotion]; } // Reset rule 50 draw counter @@ -1048,11 +1048,11 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { Square from = from_sq(m), to = to_sq(m); - int swap = PieceValue[MG][piece_on(to)] - threshold; + int swap = PieceValue[piece_on(to)] - threshold; if (swap < 0) return false; - swap = PieceValue[MG][piece_on(from)] - swap; + swap = PieceValue[piece_on(from)] - swap; if (swap <= 0) return true; @@ -1089,7 +1089,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { if ((bb = stmAttackers & pieces(PAWN))) { occupied ^= least_significant_square_bb(bb); - if ((swap = PawnValueMg - swap) < res) + if ((swap = PawnValue - swap) < res) break; attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); @@ -1098,14 +1098,14 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { else if ((bb = stmAttackers & pieces(KNIGHT))) { occupied ^= least_significant_square_bb(bb); - if ((swap = KnightValueMg - swap) < res) + if ((swap = KnightValue - swap) < res) break; } else if ((bb = stmAttackers & pieces(BISHOP))) { occupied ^= least_significant_square_bb(bb); - if ((swap = BishopValueMg - swap) < res) + if ((swap = BishopValue - swap) < res) break; attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); @@ -1114,7 +1114,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { else if ((bb = stmAttackers & pieces(ROOK))) { occupied ^= least_significant_square_bb(bb); - if ((swap = RookValueMg - swap) < res) + if ((swap = RookValue - swap) < res) break; attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); @@ -1123,7 +1123,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { else if ((bb = stmAttackers & pieces(QUEEN))) { occupied ^= least_significant_square_bb(bb); - if ((swap = QueenValueMg - swap) < res) + if ((swap = QueenValue - swap) < res) break; attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) diff --git a/src/search.cpp b/src/search.cpp index 970b0f4764b..697c8cfed1f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -985,7 +985,7 @@ namespace { if ( !givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))] + && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; @@ -1535,7 +1535,7 @@ namespace { if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; + futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; if (futilityValue <= alpha) { @@ -1783,7 +1783,7 @@ namespace { // RootMoves are already sorted by score in descending order Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg); + int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); int maxScore = -VALUE_INFINITE; double weakness = 120 - 2 * level; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 9cb0bfdbc0f..56cc016a4d5 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1573,9 +1573,9 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // 1 cp to cursed wins and let it grow to 49 cp as the positions gets // closer to a real win. m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 - : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) + : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) : r == 0 ? VALUE_DRAW - : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValueEg)) / 200) + : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) : -VALUE_MATE + MAX_PLY + 1; } diff --git a/src/types.h b/src/types.h index 637e1675abf..34dc42e173e 100644 --- a/src/types.h +++ b/src/types.h @@ -153,10 +153,6 @@ enum CastlingRights { CASTLING_RIGHT_NB = 16 }; -enum Phase { - MG = 0, EG = 1, PHASE_NB = 2 -}; - enum Bound { BOUND_NONE, BOUND_UPPER, @@ -180,11 +176,11 @@ enum Value : int { // In the code, we make the assumption that these values // are such that non_pawn_material() can be used to uniquely // identify the material on the board. - PawnValueMg = 126, PawnValueEg = 208, - KnightValueMg = 781, KnightValueEg = 854, - BishopValueMg = 825, BishopValueEg = 915, - RookValueMg = 1276, RookValueEg = 1380, - QueenValueMg = 2538, QueenValueEg = 2682, + PawnValue = 208, + KnightValue = 781, + BishopValue = 825, + RookValue = 1276, + QueenValue = 2538, }; enum PieceType { @@ -200,12 +196,8 @@ enum Piece { PIECE_NB = 16 }; -constexpr Value PieceValue[PHASE_NB][PIECE_NB] = { - { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO }, - { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO } -}; +constexpr Value PieceValue[PIECE_NB] = { VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO }; using Depth = int; From 9b80897657bde99cfb6568d8bd3386c3999f22c4 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 9 Aug 2023 11:48:33 -0700 Subject: [PATCH 0190/1309] Simplify material difference in evaluate STC: https://tests.stockfishchess.org/tests/view/64d166235b17f7c21c0ddc15 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 100032 W: 25698 L: 25547 D: 48787 Ptnml(0-2): 308, 11748, 25771, 11863, 326 LTC: https://tests.stockfishchess.org/tests/view/64d28c085b17f7c21c0df775 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 123870 W: 31463 L: 31348 D: 61059 Ptnml(0-2): 63, 13487, 34719, 13604, 62 Besides rebasing I replaced PawnValueMg w/ 126 explicitly to decouple from https://tests.stockfishchess.org/tests/view/64d212de5b17f7c21c0debbb by @peregrineshahin which also passed. #4734 closes https://github.com/official-stockfish/Stockfish/pull/4731 Bench: 1447866 --- src/evaluate.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c37dd98ad1c..728990680f6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -152,11 +152,8 @@ Value Eval::evaluate(const Position& pos) { Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - int material = 67 * (pos.count(stm) - pos.count(~stm)) - + 395 * (pos.count(stm) - pos.count(~stm)) - + 288 * (pos.count(stm) - pos.count(~stm)) - + 630 * (pos.count(stm) - pos.count(~stm)) - + 857 * (pos.count(stm) - pos.count(~stm)); + int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) + + 126 * (pos.count(stm) - pos.count(~stm)); // Blend optimism with nnue complexity and (semi)classical complexity optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; From a77a8448ffe3736e44a0125eece5d87abf082a60 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 13 Aug 2023 17:14:38 +0200 Subject: [PATCH 0191/1309] Add CONTRIBUTING.md closes https://github.com/official-stockfish/Stockfish/pull/4741 No functional change --- .github/CONTRIBUTING.md | 85 +++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 87 insertions(+) create mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000000..0dff8a9dfb0 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing to Stockfish + +Welcome to the Stockfish project! We are excited that you are interested in +contributing. This document outlines the guidelines and steps to follow when +making contributions to Stockfish. + +## Table of Contents + +- [Building Stockfish](#building-stockfish) +- [Making Contributions](#making-contributions) + - [Reporting Issues](#reporting-issues) + - [Submitting Pull Requests](#submitting-pull-requests) +- [Code Style](#code-style) +- [Community and Communication](#community-and-communication) +- [License](#license) + +## Building Stockfish + +In case you do not have a C++ compiler installed, you can follow the +instructions from our wiki. + +- [Linux][linux-compiling-link] +- [Windows][windows-compiling-link] +- [macOS][macos-compiling-link] + +## Making Contributions + +### Reporting Issues + +If you find a bug, please open an issue on the +[issue tracker][issue-tracker-link]. Be sure to include relevant information +like your operating system, build environment, and a detailed description of the +problem. + +_Please note that Stockfish's development is not focused on adding new features. +Thus any issue regarding missing features will potentially be closed without +further discussion._ + +### Submitting Pull Requests + +- Functional changes need to be tested on fishtest. See + [Creating my First Test][creating-my-first-test] for more details. + The accompanying pull request should include a link to the test results and + the new bench. + +- Non-functional changes (e.g. refactoring, code style, documentation) do not + need to be tested on fishtest, unless they might impact performance. + +- Provide a clear and concise description of the changes in the pull request + description. + +_First time contributors should add their name to [AUTHORS](../AUTHORS)._ + +_Stockfish's development is not focused on adding new features. Thus any pull +request introducing new features will potentially be closed without further +discussion._ + +## Code Style + +We do not have a strict code style. But it is best to stick to the existing +style of the file you are editing. + +## Community and Communication + +- Join the [Stockfish discord][discord-link] to discuss ideas, issues, and + development. +- Participate in the [Stockfish GitHub discussions][discussions-link] for + broader conversations. + +## License + +By contributing to Stockfish, you agree that your contributions will be licensed +under the GNU General Public License v3.0. See [Copying.txt][copying-link] for +more details. + +Thank you for contributing to Stockfish and helping us make it even better! + +[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[discord-link]: https://discord.gg/GWDRS3kU6R +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new +[creating-my-first-test]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test#create-your-test +[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues +[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux +[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#windows +[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos diff --git a/README.md b/README.md index e0e3da394f5..249bff1c04f 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ Detailed compilation instructions for all platforms can be found in our ## Contributing +__See [Contributing Guide](./.github/CONTRIBUTING.md).__ + ### Donating hardware Improving Stockfish requires a massive amount of testing. You can donate your From 3e5a817fd243bf395474c88b3bf351e57e56a119 Mon Sep 17 00:00:00 2001 From: SzilBalazs Date: Sun, 13 Aug 2023 17:52:08 +0200 Subject: [PATCH 0192/1309] Fix dead link to compression algorithm in tbprobe closes https://github.com/official-stockfish/Stockfish/pull/4746 No functional change --- AUTHORS | 1 + src/syzygy/tbprobe.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 5622ca8cec9..d20278e1e13 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ Aram Tumanian (atumanian) Arjun Temurnikar Artem Solopiy (EntityFX) Auguste Pop +Balazs Szilagyi Balint Pfliegel Ben Chaney (Chaneybenjamini) Ben Koshy (BKSpurgeon) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 56cc016a4d5..838453b6645 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1023,7 +1023,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // frequent adjacent pair of symbols in the source message by a new symbol, // reevaluating the frequencies of all of the symbol pairs with respect to // the extended alphabet, and then repeating the process. - // See http://www.larsson.dogma.net/dcc99.pdf + // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf std::vector visited(d->symlen.size()); for (Sym sym = 0; sym < d->symlen.size(); ++sym) From fe0dca12f1207a3a1ecca678d7d13bc533fd5332 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 13 Aug 2023 22:02:17 +0800 Subject: [PATCH 0193/1309] Simplify PvNode Reduction Remove the depth condition for PvNode reduction. Simplification STC: https://tests.stockfishchess.org/tests/view/64d308fa5b17f7c21c0e0303 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 38976 W: 10106 L: 9889 D: 18981 Ptnml(0-2): 129, 4479, 10040, 4726, 114 Simplification LTC: https://tests.stockfishchess.org/tests/view/64d457db5b17f7c21c0e236f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 156402 W: 39727 L: 39645 D: 77030 Ptnml(0-2): 71, 17143, 43696, 17215, 76 closes https://github.com/official-stockfish/Stockfish/pull/4747 Bench: 1493904 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 697c8cfed1f..c7e6fff053c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1136,9 +1136,9 @@ namespace { if (ttCapture) r++; - // Decrease reduction for PvNodes based on depth (~2 Elo) + // Decrease reduction for PvNodes (~2 Elo) if (PvNode) - r -= 1 + (depth < 6); + r--; // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) From 46756996e7884c665da18f357208c2344a0f9374 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 14 Aug 2023 13:49:41 +0200 Subject: [PATCH 0194/1309] Add -funroll-loops to CXXFLAGS Optimize profiling data accuracy by enabling -funroll-loops during the profile generation phase, in addition to its default activation by -fprofile-use. This seems to produce a slightly faster binary, for most compilers. make -j profile-build ARCH=x86-64-avx2 sf_base = 1392875 +/- 5905 (95%) sf_test = 1402332 +/- 7303 (95%) diff = 9457 +/- 4413 (95%) speedup = 0.67896% +/- 0.317% (95%) STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 34784 W: 8970 L: 8665 D: 17149 Ptnml(0-2): 115, 3730, 9405, 4019, 123 https://tests.stockfishchess.org/tests/view/64d944815b17f7c21c0e92e1 closes https://github.com/official-stockfish/Stockfish/pull/4750 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index c7e059ea925..0a3f8329859 100644 --- a/src/Makefile +++ b/src/Makefile @@ -562,7 +562,7 @@ endif ### 3.3 Optimization ifeq ($(optimize),yes) - CXXFLAGS += -O3 + CXXFLAGS += -O3 -funroll-loops ifeq ($(comp),gcc) ifeq ($(OS), Android) From a9a0dbbcd0749b4e6255c7e9a17f19cffedaa531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Tue, 22 Aug 2023 10:39:03 +0200 Subject: [PATCH 0195/1309] Fix some 'possible loss of data' warnings Patch by Maxim Masiutin closes https://github.com/official-stockfish/Stockfish/pull/4440 No functional change --- src/misc.cpp | 6 +++--- src/syzygy/tbprobe.cpp | 14 ++++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 29ef757e938..922fad96c13 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -375,7 +375,7 @@ void dbg_print() { for (int i = 0; i < MaxDebugSlots; ++i) if ((n = stdev[i][0])) { - double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1]))); + double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1]))); std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl; @@ -385,8 +385,8 @@ void dbg_print() { if ((n = correl[i][0])) { double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) - / ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1]))) - * sqrtl(E(correl[i][4]) - sqr(E(correl[i][3])))); + / ( sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) + * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 838453b6645..ba727825d4c 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -995,13 +995,19 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { d->lowestSym = (Sym*)data; d->base64.resize(d->maxSymLen - d->minSymLen + 1); + // See https://en.wikipedia.org/wiki/Huffman_coding // The canonical code is ordered such that longer symbols (in terms of // the number of bits of their Huffman code) have lower numeric value, // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. - // See https://en.wikipedia.org/wiki/Huffman_coding - for (int i = d->base64.size() - 2; i >= 0; --i) { + + // Implementation note: we first cast the unsigned size_t "base64.size()" + // to a signed int "base64_size" variable and then we are able to subtract 2, + // avoiding unsigned overflow warnings. + + int base64_size = static_cast(d->base64.size()); + for (int i = base64_size - 2; i >= 0; --i) { d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - number(&d->lowestSym[i + 1])) / 2; @@ -1012,10 +1018,10 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // than d->base64[i+1] and given the above assert condition, we ensure that // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. - for (size_t i = 0; i < d->base64.size(); ++i) + for (int i = 0; i < base64_size; ++i) d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits - data += d->base64.size() * sizeof(Sym); + data += base64_size * sizeof(Sym); d->symlen.resize(number(data)); data += sizeof(uint16_t); d->btree = (LR*)data; From 9abef246a9ce7c17a21c2cc0d609dc61ddc5be67 Mon Sep 17 00:00:00 2001 From: Matthies Date: Wed, 16 Aug 2023 11:11:27 +0200 Subject: [PATCH 0196/1309] Allow compilation on Raspi (for ARMv8) Current master fails to compile for ARMv8 on Raspi cause gcc (version 10.2.1) does not like to cast between signed and unsigned vector types. This patch fixes it by using unsigned vector pointer for ARM to avoid implicite cast. closes https://github.com/official-stockfish/Stockfish/pull/4752 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 63cbaf45a34..2cd77e49933 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -72,10 +72,10 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) #elif defined (USE_NEON) - using vec_t = int32x4_t; + using vec_t = uint32x4_t; static const std::uint32_t Mask[4] = {1, 2, 4, 8}; #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) - using vec128_t = int16x8_t; + using vec128_t = uint16x8_t; #define vec128_zero vdupq_n_u16(0) #define vec128_set_16(a) vdupq_n_u16(a) #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) From fe7353f7027e2d49b7ffb60b01d88ce0d3b04fff Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 21 Aug 2023 22:45:26 +0200 Subject: [PATCH 0197/1309] Update links to fishtest Fishtest has moved to https://github.com/official-stockfish/fishtest/ closes https://github.com/official-stockfish/Stockfish/pull/4758 No functional change --- .github/CONTRIBUTING.md | 15 ++++++++------- AUTHORS | 2 +- README.md | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0dff8a9dfb0..7667a942325 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -75,11 +75,12 @@ more details. Thank you for contributing to Stockfish and helping us make it even better! -[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt -[discord-link]: https://discord.gg/GWDRS3kU6R -[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new -[creating-my-first-test]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test#create-your-test -[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues -[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux + +[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt +[discord-link]: https://discord.gg/GWDRS3kU6R +[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new +[creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test +[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues +[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux [windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#windows -[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos +[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos diff --git a/AUTHORS b/AUTHORS index d20278e1e13..9314f5cbd33 100644 --- a/AUTHORS +++ b/AUTHORS @@ -230,4 +230,4 @@ zz4032 # Additionally, we acknowledge the authors and maintainers of fishtest, # an amazing and essential framework for Stockfish development! # -# https://github.com/glinscott/fishtest/blob/master/AUTHORS +# https://github.com/official-stockfish/fishtest/blob/master/AUTHORS diff --git a/README.md b/README.md index 249bff1c04f..4d63b71e35b 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ also be made available under GPL v3. [issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml [discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new [fishtest-link]: https://tests.stockfishchess.org/tests -[guideline-link]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test +[guideline-link]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test [license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt [programming-link]: https://www.chessprogramming.org/Main_Page [programmingsf-link]: https://www.chessprogramming.org/Stockfish @@ -155,7 +155,7 @@ also be made available under GPL v3. [wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage [wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source [wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands -[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker +[worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker [build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github [commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge From 4c5919fa95d543b1cd5d0403f3a89e10a2bdd10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Tue, 22 Aug 2023 10:00:03 +0200 Subject: [PATCH 0198/1309] Fix some tabs in Makefile Avoid mixing spaces and tabs for indentation in Makefile closes https://github.com/official-stockfish/Stockfish/pull/4759 No functional change --- src/Makefile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Makefile b/src/Makefile index 0a3f8329859..d6443845477 100644 --- a/src/Makefile +++ b/src/Makefile @@ -895,33 +895,33 @@ netvariables: net: netvariables @echo "Default net: $(nnuenet)" @if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ + echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ fi @if [ "x$(shasum_command)" = "x" ]; then \ - echo "shasum / sha256sum not found, skipping net validation"; \ - elif test -f "$(nnuenet)"; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Removing invalid network"; rm -f $(nnuenet); \ - fi; \ + echo "shasum / sha256sum not found, skipping net validation"; \ + elif test -f "$(nnuenet)"; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing invalid network"; rm -f $(nnuenet); \ + fi; \ fi; @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ if test -f "$(nnuenet)"; then \ - echo "$(nnuenet) available : OK"; break; \ + echo "$(nnuenet) available : OK"; break; \ else \ - if [ "x$(curl_or_wget)" != "x" ]; then \ + if [ "x$(curl_or_wget)" != "x" ]; then \ echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ else \ echo "No net found and download not possible"; exit 1;\ - fi; \ + fi; \ fi; \ if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ echo "Removing failed download"; rm -f $(nnuenet); \ - fi; \ + fi; \ fi; \ done @if ! test -f "$(nnuenet)"; then \ - echo "Failed to download $(nnuenet)."; \ + echo "Failed to download $(nnuenet)."; \ fi; @if [ "x$(shasum_command)" != "x" ]; then \ if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ From c6f62363a657263a567a0cc9bae09f3c4016156d Mon Sep 17 00:00:00 2001 From: Gian-Carlo Pascutto Date: Mon, 14 Aug 2023 17:30:10 +0200 Subject: [PATCH 0199/1309] Simplify Square Clipped ReLU code. Squared numbers are never negative, so barring any wraparound there is no need to clamp to 0. From reading the code, there's no obvious way to get wraparound, so the entire operation can be simplified away. Updated original truncated code comments to be sensible. Verified by running ./stockfish bench 128 1 24 and by the following test: STC: https://tests.stockfishchess.org/tests/view/64da4db95b17f7c21c0eabe7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60224 W: 15425 L: 15236 D: 29563 Ptnml(0-2): 195, 6576, 16382, 6763, 196 closes https://github.com/official-stockfish/Stockfish/pull/4751 No functional change --- src/nnue/layers/sqr_clipped_relu.h | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 69bd51471d7..5c1b9e6cd06 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -65,12 +65,6 @@ namespace Stockfish::Eval::NNUE::Layers { #if defined(USE_SSE2) constexpr IndexType NumChunks = InputDimensions / 16; - #ifdef USE_SSE41 - const __m128i Zero = _mm_setzero_si128(); - #else - const __m128i k0x80s = _mm_set1_epi8(-128); - #endif - static_assert(WeightScaleBits == 6); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); @@ -82,21 +76,13 @@ namespace Stockfish::Eval::NNUE::Layers { _mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])); - // Not sure if + // We shift by WeightScaleBits * 2 = 12 and divide by 128 + // which is an additional shift-right of 7, meaning 19 in total. + // MulHi strips the lower 16 bits so we need to shift out 3 more to match. words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - - _mm_store_si128(&out[i], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, Zero) - #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) - #endif - - ); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); } constexpr IndexType Start = NumChunks * 16; @@ -108,7 +94,7 @@ namespace Stockfish::Eval::NNUE::Layers { output[i] = static_cast( // really should be /127 but we need to make it fast // needs to be accounted for in the trainer - std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128))); + std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)); } } }; From 030b87182a7fff98b1724b857d6d40cda5d90b9f Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sat, 12 Aug 2023 14:56:23 +0800 Subject: [PATCH 0200/1309] Do more full window searches Remove the value < beta condition for doing full window searches. As an added bonus the condition for full-window search is now much more similar to other fail-soft engines. Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 244608 W: 62286 L: 62294 D: 120028 Ptnml(0-2): 758, 28772, 63214, 28840, 720 https://tests.stockfishchess.org/tests/view/64d72d365b17f7c21c0e6675 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 311460 W: 78909 L: 78985 D: 153566 Ptnml(0-2): 129, 33959, 87656, 33831, 155 https://tests.stockfishchess.org/tests/view/64dca2265b17f7c21c0ee06c closes https://github.com/official-stockfish/Stockfish/pull/4755 Bench: 1624221 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c7e6fff053c..d911593c031 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1211,10 +1211,9 @@ namespace { value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); } - // For PV nodes only, do a full PV search on the first move or after a fail - // high (in the latter case search only if value < beta), otherwise let the - // parent node fail low with value <= alpha and try another move. - if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta)))) + // For PV nodes only, do a full PV search on the first move or after a fail high, + // otherwise let the parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || value > alpha)) { (ss+1)->pv = pv; (ss+1)->pv[0] = MOVE_NONE; From 440feecb4da2f051da6dafb70b4eb6cf443ccc1e Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sun, 20 Aug 2023 01:15:22 +0300 Subject: [PATCH 0201/1309] Reduce repetitions branches Increase reduction on retrying a move we just retreated that falls in a repetition: if current move can be the same move from previous previous turn then we retreated that move on the previous turn, this patch increases reduction if retrying that move results in a repetition. How to continue from there? Maybe we some variants of this idea could bring Elo too (only testing the destination square, or triangulations, etc.) Passed STC: https://tests.stockfishchess.org/tests/view/64e1aede883cbb7cbd9ad976 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 424000 W: 108675 L: 107809 D: 207516 Ptnml(0-2): 1296, 47350, 113896, 48108, 1350 Passed LTC: https://tests.stockfishchess.org/tests/view/64e32d629970091252666872 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 89682 W: 22976 L: 22569 D: 44137 Ptnml(0-2): 39, 8843, 26675, 9240, 44 closes https://github.com/official-stockfish/Stockfish/pull/4757 bench: 1574347 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index d911593c031..d9b41cb35c4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1143,6 +1143,11 @@ namespace { // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; + + // Increase reduction on repetition (~1 Elo) + if ( move == (ss-4)->currentMove + && pos.has_repeated()) + r += 2; // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss+1)->cutoffCnt > 3) From 4c4cb185aaaa0b3175ca35ab6473f17e9ec64055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Wed, 23 Aug 2023 07:50:36 +0200 Subject: [PATCH 0202/1309] Play turbulent when defending, simpler when attacking This patch decays a little the evaluation (up to a few percent) for positions which have a large complexity measure (material imbalance, positional compensations, etc). This may have nice consequences on the playing style, as it modifies the search differently for attack and defense, both effects being desirable: - to see the effect on positions when Stockfish is defending, let us suppose for instance that the side to move is Stockfish and the nnue evaluation on the principal variation is -100 : this patch will decay positions with an evaluation of -103 (say) to the same level, provided they have huge material imbalance or huge positional compensation. In other words, chaotic positions with an evaluation of -103 are now comparable in our search tree to stable positions with an evaluation of -100, and chaotic positions with an evaluation of -102 are now preferred to stable positions with an evaluation of -100. - the effect on positions when Stockfish is attacking is the opposite. Let us suppose for instance that the side to move is Stockfish and the nnue evaluation on the principal variation is +100 : this patch will decay the evaluation to +97 if the positions on the principal variation have huge material imbalance or huge positional compensation. In other words, stable positions with an evaluation of +97 are now comparable in our search tree to chaotic positions with an evaluation of +100, and stable positions with an evaluation of +98 are now preferred to chaotic positions with an evaluation of +100. So the effect of this small change of evaluation on the playing style is that Stockfish should now play a little bit more turbulent when defending, and choose slightly simpler lines when attacking. passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 268448 W: 68713 L: 68055 D: 131680 Ptnml(0-2): 856, 31514, 68943, 31938, 973 https://tests.stockfishchess.org/tests/view/64e252bb99700912526653ed passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 141060 W: 36066 L: 35537 D: 69457 Ptnml(0-2): 71, 15179, 39522, 15666, 92 https://tests.stockfishchess.org/tests/view/64e4447a9009777747553725 closes https://github.com/official-stockfish/Stockfish/pull/4762 Bench: 1426295 --- src/evaluate.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 728990680f6..54216b97206 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -155,8 +155,9 @@ Value Eval::evaluate(const Position& pos) { int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) + 126 * (pos.count(stm) - pos.count(~stm)); - // Blend optimism with nnue complexity and (semi)classical complexity + // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; + nnue -= nnue * (nnueComplexity + abs(material - nnue)) / 32768; v = ( nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm + pos.count())) / 1024; From 8cd5cbf6939d76b33a744f1379a6f84a4ac3a6cb Mon Sep 17 00:00:00 2001 From: ttruscott Date: Fri, 25 Aug 2023 15:47:52 -0400 Subject: [PATCH 0203/1309] Omit two unneeded tests These redundant tests were intended as a speed-up, but they do not seem to provide any speed anymore. STC: https://tests.stockfishchess.org/tests/view/64e9079c85e3e95030fd8259 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 134688 W: 34338 L: 34226 D: 66124 Ptnml(0-2): 426, 15122, 36124, 15258, 414 closes https://github.com/official-stockfish/Stockfish/pull/4767 No functional change --- src/search.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d9b41cb35c4..18e4aa56df2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -518,7 +518,6 @@ namespace { // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( !rootNode - && pos.rule50_count() >= 3 && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { @@ -1398,7 +1397,6 @@ namespace { // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( depth < 0 - && pos.rule50_count() >= 3 && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { From 3c0e86a91e48baea273306e45fb6cf13a59373cf Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 23 Aug 2023 19:36:55 +0200 Subject: [PATCH 0204/1309] Cleanup includes Reorder a few includes, include "position.h" where it was previously missing and apply include-what-you-use suggestions. Also make the order of the includes consistent, in the following way: 1. Related header (for .cpp files) 2. A blank line 3. C/C++ headers 4. A blank line 5. All other header files closes https://github.com/official-stockfish/Stockfish/pull/4763 fixes https://github.com/official-stockfish/Stockfish/issues/4707 No functional change --- src/benchmark.cpp | 2 +- src/bitboard.cpp | 4 +++- src/bitboard.h | 5 +++++ src/evaluate.cpp | 15 ++++++++------- src/evaluate.h | 4 +--- src/main.cpp | 7 +++++-- src/misc.cpp | 11 ++++++----- src/misc.h | 8 +++----- src/movegen.cpp | 5 ++++- src/movegen.h | 1 + src/movepick.cpp | 9 +++++++-- src/movepick.h | 5 ++++- src/nnue/evaluate_nnue.cpp | 15 ++++++++++----- src/nnue/evaluate_nnue.h | 13 ++++++++++++- src/nnue/features/half_ka_v2_hm.cpp | 3 +++ src/nnue/features/half_ka_v2_hm.h | 6 ++++-- src/nnue/layers/affine_transform.h | 4 ++-- .../layers/affine_transform_sparse_input.h | 6 ++++-- src/nnue/layers/clipped_relu.h | 4 ++++ src/nnue/layers/sqr_clipped_relu.h | 4 ++++ src/nnue/nnue_accumulator.h | 3 +++ src/nnue/nnue_architecture.h | 12 +++++------- src/nnue/nnue_common.h | 6 +++++- src/nnue/nnue_feature_transformer.h | 15 +++++++++++---- src/position.cpp | 15 +++++++++++---- src/position.h | 7 +++---- src/search.cpp | 19 ++++++++++++++----- src/search.h | 1 + src/syzygy/tbprobe.cpp | 19 ++++++++++++------- src/syzygy/tbprobe.h | 6 ++++++ src/thread.cpp | 16 ++++++++++++---- src/thread.h | 4 +++- src/timeman.cpp | 4 ++-- src/timeman.h | 3 +++ src/tt.cpp | 9 ++++++--- src/tt.h | 3 +++ src/tune.cpp | 11 ++++++++--- src/tune.h | 3 +++ src/types.h | 3 --- src/uci.cpp | 17 ++++++++++++----- src/uci.h | 2 ++ src/ucioption.cpp | 9 ++++++++- 42 files changed, 224 insertions(+), 94 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index e340ebcd309..f3401c61c8b 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -18,9 +18,9 @@ #include "benchmark.h" +#include #include #include -#include #include #include "position.h" diff --git a/src/bitboard.cpp b/src/bitboard.cpp index fd5c3c22536..bed2b3ee309 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -16,10 +16,12 @@ along with this program. If not, see . */ +#include "bitboard.h" + #include #include +#include -#include "bitboard.h" #include "misc.h" namespace Stockfish { diff --git a/src/bitboard.h b/src/bitboard.h index 244dc034c33..f9175333745 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -19,6 +19,11 @@ #ifndef BITBOARD_H_INCLUDED #define BITBOARD_H_INCLUDED +#include +#include +#include +#include +#include #include #include "types.h" diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 54216b97206..25a6545564a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -16,23 +16,24 @@ along with this program. If not, see . */ +#include "evaluate.h" + #include #include +#include #include #include -#include #include -#include +#include #include -#include "bitboard.h" -#include "evaluate.h" +#include "incbin/incbin.h" #include "misc.h" +#include "nnue/evaluate_nnue.h" +#include "position.h" #include "thread.h" -#include "timeman.h" +#include "types.h" #include "uci.h" -#include "incbin/incbin.h" -#include "nnue/evaluate_nnue.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). diff --git a/src/evaluate.h b/src/evaluate.h index 586e3b81f04..a222da7361a 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -20,13 +20,11 @@ #define EVALUATE_H_INCLUDED #include -#include - -#include "types.h" namespace Stockfish { class Position; +enum Value : int; namespace Eval { diff --git a/src/main.cpp b/src/main.cpp index c854ac0cd16..eee149fb455 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,14 +16,17 @@ along with this program. If not, see . */ +#include #include #include "bitboard.h" +#include "evaluate.h" +#include "misc.h" #include "position.h" #include "search.h" -#include "syzygy/tbprobe.h" #include "thread.h" -#include "tt.h" +#include "tune.h" +#include "types.h" #include "uci.h" using namespace Stockfish; diff --git a/src/misc.cpp b/src/misc.cpp index 922fad96c13..42083e0a94f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -16,6 +16,8 @@ along with this program. If not, see . */ +#include "misc.h" + #ifdef _WIN32 #if _WIN32_WINNT < 0x0601 #undef _WIN32_WINNT @@ -44,17 +46,19 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES } #endif +#include #include #include #include #include #include +#include #include #include -#include + +#include "types.h" #if defined(__linux__) && !defined(__ANDROID__) -#include #include #endif @@ -63,9 +67,6 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES #include #endif -#include "misc.h" -#include "thread.h" - using namespace std; namespace Stockfish { diff --git a/src/misc.h b/src/misc.h index 0005fc0fb09..aed677b5b29 100644 --- a/src/misc.h +++ b/src/misc.h @@ -21,12 +21,10 @@ #include #include -#include -#include -#include +#include #include - -#include "types.h" +#include +#include #define stringify2(x) #x #define stringify(x) stringify2(x) diff --git a/src/movegen.cpp b/src/movegen.cpp index 6b28a52ecf0..f0733c73b66 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -16,9 +16,12 @@ along with this program. If not, see . */ +#include "movegen.h" + #include +#include -#include "movegen.h" +#include "bitboard.h" #include "position.h" namespace Stockfish { diff --git a/src/movegen.h b/src/movegen.h index b8df3e65d5c..6449de25794 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -20,6 +20,7 @@ #define MOVEGEN_H_INCLUDED #include +#include #include "types.h" diff --git a/src/movepick.cpp b/src/movepick.cpp index 9d5805a70a4..d4f8ab092a8 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -16,10 +16,15 @@ along with this program. If not, see . */ +#include "movepick.h" + +#include #include +#include +#include #include "bitboard.h" -#include "movepick.h" +#include "position.h" namespace Stockfish { @@ -161,7 +166,7 @@ void MovePicker::score() { : 0 ) : 0 ; } - + else // Type == EVASIONS { if (pos.capture_stage(m)) diff --git a/src/movepick.h b/src/movepick.h index 0b44557f198..5243f89cf2c 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -20,14 +20,17 @@ #define MOVEPICK_H_INCLUDED #include +#include +#include +#include #include #include #include "movegen.h" -#include "position.h" #include "types.h" namespace Stockfish { +class Position; /// StatsEntry stores the stat table value. It is usually a number but could /// be a move or even a nested history. We use a class instead of naked value diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index cff1d0243db..456f2edfdf3 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -18,19 +18,24 @@ // Code for calculating NNUE evaluation function +#include "evaluate_nnue.h" + +#include +#include +#include #include #include #include -#include #include #include #include "../evaluate.h" +#include "../misc.h" #include "../position.h" -#include "../uci.h" #include "../types.h" - -#include "evaluate_nnue.h" +#include "../uci.h" +#include "nnue_accumulator.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { @@ -251,7 +256,7 @@ namespace Stockfish::Eval::NNUE { // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals static void format_cp_aligned_dot(Value v, std::stringstream &stream) { - + const double pawns = std::abs(0.01 * UCI::to_cp(v)); stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index b84bed8b90d..8faec6cce43 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -21,9 +21,20 @@ #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED #define NNUE_EVALUATE_NNUE_H_INCLUDED +#include +#include +#include +#include +#include + +#include "../misc.h" +#include "nnue_architecture.h" #include "nnue_feature_transformer.h" -#include +namespace Stockfish { + class Position; + enum Value : int; +} namespace Stockfish::Eval::NNUE { diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 19ebb15fca4..016934b8c4d 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -20,7 +20,10 @@ #include "half_ka_v2_hm.h" +#include "../../bitboard.h" #include "../../position.h" +#include "../../types.h" +#include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Features { diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 78063c3684c..9da1cc05531 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -21,13 +21,15 @@ #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED #define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED -#include "../nnue_common.h" +#include -#include "../../evaluate.h" #include "../../misc.h" +#include "../../types.h" +#include "../nnue_common.h" namespace Stockfish { struct StateInfo; + class Position; } namespace Stockfish::Eval::NNUE::Features { diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index c936a83ed66..e9d0beace17 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -21,9 +21,9 @@ #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#include #include -#include -#include + #include "../nnue_common.h" #include "simd.h" diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 2cd77e49933..c9894f5d96e 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -21,10 +21,12 @@ #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED -#include #include #include -#include +#include +#include + +#include "../../bitboard.h" #include "../nnue_common.h" #include "affine_transform.h" #include "simd.h" diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index d5aa6fbfbd1..2856bfb0a63 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -21,6 +21,10 @@ #ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED #define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#include +#include +#include + #include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Layers { diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 5c1b9e6cd06..503b283b25e 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -21,6 +21,10 @@ #ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED #define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#include +#include +#include + #include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Layers { diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 8eba4497464..03fc3bd5cd8 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -21,7 +21,10 @@ #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED +#include + #include "nnue_architecture.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 65319b14bde..b50c52df31f 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -21,18 +21,16 @@ #ifndef NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED -#include - -#include "nnue_common.h" +#include +#include +#include #include "features/half_ka_v2_hm.h" - -#include "layers/affine_transform_sparse_input.h" #include "layers/affine_transform.h" +#include "layers/affine_transform_sparse_input.h" #include "layers/clipped_relu.h" #include "layers/sqr_clipped_relu.h" - -#include "../misc.h" +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index e8ed2bc68e7..a42a86c980d 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -21,10 +21,14 @@ #ifndef NNUE_COMMON_H_INCLUDED #define NNUE_COMMON_H_INCLUDED +#include +#include +#include #include #include +#include -#include "../misc.h" // for IsLittleEndian +#include "../misc.h" #if defined(USE_AVX2) #include diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7571f398295..0af0ed96cc5 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -21,11 +21,18 @@ #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED #define NNUE_FEATURE_TRANSFORMER_H_INCLUDED -#include "nnue_common.h" +#include +#include +#include +#include +#include +#include + +#include "../position.h" +#include "../types.h" +#include "nnue_accumulator.h" #include "nnue_architecture.h" - -#include // std::memset() -#include // std::pair +#include "nnue_common.h" namespace Stockfish::Eval::NNUE { diff --git a/src/position.cpp b/src/position.cpp index 675dec99f04..0f15727d2ca 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -16,22 +16,29 @@ along with this program. If not, see . */ +#include "position.h" + #include +#include #include -#include // For offsetof() -#include // For std::memset, std::memcmp +#include +#include +#include +#include #include +#include #include #include +#include #include "bitboard.h" #include "misc.h" #include "movegen.h" -#include "position.h" +#include "nnue/nnue_common.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" using std::string; diff --git a/src/position.h b/src/position.h index 393c1ac9226..f0546af33f7 100644 --- a/src/position.h +++ b/src/position.h @@ -21,14 +21,13 @@ #include #include -#include // For std::unique_ptr +#include +#include #include #include "bitboard.h" -#include "evaluate.h" -#include "types.h" - #include "nnue/nnue_accumulator.h" +#include "types.h" namespace Stockfish { diff --git a/src/search.cpp b/src/search.cpp index 18e4aa56df2..76d0545ac50 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -16,25 +16,34 @@ along with this program. If not, see . */ +#include "search.h" + #include +#include +#include #include #include -#include // For std::memset +#include +#include +#include #include #include +#include +#include +#include "bitboard.h" #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" +#include "nnue/evaluate_nnue.h" +#include "nnue/nnue_common.h" #include "position.h" -#include "search.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "timeman.h" #include "tt.h" #include "uci.h" -#include "syzygy/tbprobe.h" -#include "nnue/evaluate_nnue.h" namespace Stockfish { @@ -1142,7 +1151,7 @@ namespace { // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; - + // Increase reduction on repetition (~1 Elo) if ( move == (ss-4)->currentMove && pos.has_repeated()) diff --git a/src/search.h b/src/search.h index 806e4be63fa..c6dbffce0c7 100644 --- a/src/search.h +++ b/src/search.h @@ -19,6 +19,7 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include #include #include "misc.h" diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index ba727825d4c..d1b32d242c9 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -16,33 +16,38 @@ along with this program. If not, see . */ +#include "tbprobe.h" + +#include #include #include +#include #include -#include // For std::memset and std::memcpy +#include +#include #include #include +#include #include -#include #include #include #include #include +#include +#include #include "../bitboard.h" +#include "../misc.h" #include "../movegen.h" #include "../position.h" #include "../search.h" #include "../types.h" #include "../uci.h" -#include "tbprobe.h" - #ifndef _WIN32 #include -#include #include -#include +#include #else #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX @@ -1002,7 +1007,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. - // Implementation note: we first cast the unsigned size_t "base64.size()" + // Implementation note: we first cast the unsigned size_t "base64.size()" // to a signed int "base64_size" variable and then we are able to subtract 2, // avoiding unsigned overflow warnings. diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index fe994f68e91..b2ba35ff4b0 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,8 +19,14 @@ #ifndef TBPROBE_H #define TBPROBE_H +#include + #include "../search.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Tablebases { enum WDLScore { diff --git a/src/thread.cpp b/src/thread.cpp index c680393e277..9cf85310c39 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -16,15 +16,23 @@ along with this program. If not, see . */ -#include +#include "thread.h" -#include // For std::count +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" #include "movegen.h" #include "search.h" -#include "thread.h" -#include "uci.h" #include "syzygy/tbprobe.h" #include "tt.h" +#include "uci.h" namespace Stockfish { diff --git a/src/thread.h b/src/thread.h index aa9db2f3633..a421af9e3bc 100644 --- a/src/thread.h +++ b/src/thread.h @@ -21,14 +21,16 @@ #include #include +#include +#include #include -#include #include #include "movepick.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" +#include "types.h" namespace Stockfish { diff --git a/src/timeman.cpp b/src/timeman.cpp index 169c7821c94..5e57f8f98c5 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -16,12 +16,12 @@ along with this program. If not, see . */ +#include "timeman.h" + #include -#include #include #include "search.h" -#include "timeman.h" #include "uci.h" namespace Stockfish { diff --git a/src/timeman.h b/src/timeman.h index 3462b8236a7..9ad6bdcccf9 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -19,9 +19,12 @@ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED +#include + #include "misc.h" #include "search.h" #include "thread.h" +#include "types.h" namespace Stockfish { diff --git a/src/tt.cpp b/src/tt.cpp index 3339c993c41..1582121fd6d 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -16,14 +16,17 @@ along with this program. If not, see . */ -#include // For std::memset +#include "tt.h" + +#include +#include +#include #include #include +#include -#include "bitboard.h" #include "misc.h" #include "thread.h" -#include "tt.h" #include "uci.h" namespace Stockfish { diff --git a/src/tt.h b/src/tt.h index 3e335b44696..df962faaa7b 100644 --- a/src/tt.h +++ b/src/tt.h @@ -19,6 +19,9 @@ #ifndef TT_H_INCLUDED #define TT_H_INCLUDED +#include +#include + #include "misc.h" #include "types.h" diff --git a/src/tune.cpp b/src/tune.cpp index ccfc33c5082..97baeb784e9 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -16,14 +16,20 @@ along with this program. If not, see . */ +#include "tune.h" + #include #include +#include #include +#include -#include "types.h" -#include "misc.h" #include "uci.h" +namespace Stockfish { +enum Value : int; +} + using std::string; namespace Stockfish { @@ -108,7 +114,6 @@ template<> void Tune::Entry::read_option() { value(); } // // Then paste the output below, as the function body -#include namespace Stockfish { diff --git a/src/tune.h b/src/tune.h index bdbee14e014..3e94f7efc6c 100644 --- a/src/tune.h +++ b/src/tune.h @@ -19,12 +19,15 @@ #ifndef TUNE_H_INCLUDED #define TUNE_H_INCLUDED +#include #include #include #include +#include #include namespace Stockfish { +enum Value : int; using Range = std::pair; // Option's min-max values using RangeFun = Range (int); diff --git a/src/types.h b/src/types.h index 34dc42e173e..bb319c2bb08 100644 --- a/src/types.h +++ b/src/types.h @@ -37,10 +37,7 @@ /// | only in 64-bit mode and requires hardware with pext support. #include -#include #include -#include -#include #if defined(_MSC_VER) // Disable some silly and noisy warning from MSVC compiler diff --git a/src/uci.cpp b/src/uci.cpp index ffe5e0576ea..2a35a40fd4e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -16,23 +16,30 @@ along with this program. If not, see . */ +#include "uci.h" + +#include #include +#include #include +#include +#include +#include #include +#include +#include #include #include +#include #include "benchmark.h" #include "evaluate.h" +#include "misc.h" #include "movegen.h" +#include "nnue/evaluate_nnue.h" #include "position.h" #include "search.h" #include "thread.h" -#include "timeman.h" -#include "tt.h" -#include "uci.h" -#include "syzygy/tbprobe.h" -#include "nnue/evaluate_nnue.h" using namespace std; diff --git a/src/uci.h b/src/uci.h index 2e40c9125d1..7ca97d5c6bc 100644 --- a/src/uci.h +++ b/src/uci.h @@ -19,6 +19,8 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED +#include +#include #include #include diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 27f436d3b37..8d2c5c098ed 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -18,16 +18,23 @@ #include #include +#include +#include +#include +#include +#include #include #include +#include #include "evaluate.h" #include "misc.h" #include "search.h" +#include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" +#include "types.h" #include "uci.h" -#include "syzygy/tbprobe.h" using std::string; From 4f7fe255c7ac9cf705350cabfc231d87a3e75018 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 26 Aug 2023 09:49:04 +0200 Subject: [PATCH 0205/1309] Simplify README The UCI protocol is rather technical and has little value in our README. Instead it should be explained in our wiki. "Contributing" is moved above "Compiling Stockfish" to make it more prominent. Also move the CONTRIBUTING.md into the root directory and include it in the distributed artifacts/releases. closes https://github.com/official-stockfish/Stockfish/pull/4766 No functional change --- .github/workflows/stockfish_arm_binaries.yml | 1 + .github/workflows/stockfish_binaries.yml | 1 + .github/CONTRIBUTING.md => CONTRIBUTING.md | 0 README.md | 56 ++++++++------------ 4 files changed, 24 insertions(+), 34 deletions(-) rename .github/CONTRIBUTING.md => CONTRIBUTING.md (100%) diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index dfe4e2a24ce..4d7f3d55c1a 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -128,6 +128,7 @@ jobs: cp AUTHORS stockfish/ cp CITATION.cff stockfish/ cp README.md stockfish/ + cp CONTRIBUTING.md stockfish/ tar -cvf stockfish-android-$BINARY.tar stockfish - name: Upload binaries diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 7c7341ef655..fadfbcfcd04 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -161,6 +161,7 @@ jobs: cp AUTHORS stockfish/ cp CITATION.cff stockfish/ cp README.md stockfish/ + cp CONTRIBUTING.md stockfish/ - name: Create tar if: runner.os != 'Windows' diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 100% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md diff --git a/README.md b/README.md index 4d63b71e35b..52b123cbd25 100644 --- a/README.md +++ b/README.md @@ -59,40 +59,9 @@ This distribution of Stockfish consists of the following files: * a file with the .nnue extension, storing the neural network for the NNUE evaluation. Binary distributions will have this file embedded. -## The UCI protocol - -The [Universal Chess Interface][uci-link] (UCI) is a standard text-based protocol -used to communicate with a chess engine and is the recommended way to do so for -typical graphical user interfaces (GUI) or chess tools. Stockfish implements the -majority of its options. - -Developers can see the default values for the UCI options available in Stockfish -by typing `./stockfish uci` in a terminal, but most users should typically use a -chess GUI to interact with Stockfish. - -For more information on UCI or debug commands, see our [documentation][wiki-commands-link]. - -## Compiling Stockfish - -Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions, -big-endian machines such as Power PC, and other platforms. - -On Unix-like systems, it should be easy to compile Stockfish directly from the -source code with the included Makefile in the folder `src`. In general, it is -recommended to run `make help` to see a list of make targets with corresponding -descriptions. An example suitable for most Intel and AMD chips: - -``` -cd src -make -j profile-build ARCH=x86-64-avx2 -``` - -Detailed compilation instructions for all platforms can be found in our -[documentation][wiki-compile-link]. - ## Contributing -__See [Contributing Guide](./.github/CONTRIBUTING.md).__ +__See [Contributing Guide](CONTRIBUTING.md).__ ### Donating hardware @@ -116,6 +85,25 @@ Discussions about Stockfish take place these days mainly in the Stockfish [Discord server][discord-link]. This is also the best place to ask questions about the codebase and how to improve it. +## Compiling Stockfish + +Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions, +big-endian machines such as Power PC, and other platforms. + +On Unix-like systems, it should be easy to compile Stockfish directly from the +source code with the included Makefile in the folder `src`. In general, it is +recommended to run `make help` to see a list of make targets with corresponding +descriptions. An example suitable for most Intel and AMD chips: + +``` +cd src +make -j profile-build ARCH=x86-64-avx2 +``` + +Detailed compilation instructions for all platforms can be found in our +[documentation][wiki-compile-link]. Our wiki also has information about +the [UCI commands][wiki-uci-link] supported by Stockfish. + ## Terms of use Stockfish is free and distributed under the @@ -152,9 +140,9 @@ also be made available under GPL v3. [website-link]: https://stockfishchess.org [website-blog-link]: https://stockfishchess.org/blog/ [wiki-link]: https://github.com/official-stockfish/Stockfish/wiki -[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage [wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source -[wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands +[wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands +[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage [worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker [build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github From 1f7ff8406d323e634a2aa1e1264042340707cdd9 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Thu, 17 Aug 2023 14:31:05 +0200 Subject: [PATCH 0206/1309] Simplify slider_blocker calculation Now that classical evaluation was removed, we can adapt this method to the needs of set_check_info. STC: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 298176 W: 75802 L: 75868 D: 146506 Ptnml(0-2): 908, 33608, 80192, 33402, 978 https://tests.stockfishchess.org/tests/view/64e70b899009777747557b43 closes https://github.com/official-stockfish/Stockfish/pull/4753 no functional change --- src/position.cpp | 34 +++++++++++++++------------------- src/position.h | 2 +- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 0f15727d2ca..120677432b6 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -321,8 +321,8 @@ void Position::set_castling_right(Color c, Square rfrom) { void Position::set_check_info() const { - st->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), st->pinners[BLACK]); - st->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), st->pinners[WHITE]); + update_slider_blockers(WHITE); + update_slider_blockers(BLACK); Square ksq = square(~sideToMove); @@ -443,37 +443,33 @@ string Position::fen() const { return ss.str(); } +/// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], +/// which store respectively the pieces preventing king of color c from being in check +/// and the slider pieces of color ~c pinning pieces of color c to the king. +void Position::update_slider_blockers(Color c) const { -/// Position::slider_blockers() returns a bitboard of all the pieces (both colors) -/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a -/// slider if removing that piece from the board would result in a position where -/// square 's' is attacked. For example, a king-attack blocking piece can be either -/// a pinned or a discovered check piece, according if its color is the opposite -/// or the same of the color of the slider. + Square ksq = square(c); -Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const { - - Bitboard blockers = 0; - pinners = 0; + st->blockersForKing[c] = 0; + st->pinners[~c] = 0; // Snipers are sliders that attack 's' when a piece and other snipers are removed - Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK)) - | (attacks_bb(s) & pieces(QUEEN, BISHOP))) & sliders; + Bitboard snipers = ( (attacks_bb< ROOK>(ksq) & pieces(QUEEN, ROOK)) + | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) & pieces(~c); Bitboard occupancy = pieces() ^ snipers; while (snipers) { Square sniperSq = pop_lsb(snipers); - Bitboard b = between_bb(s, sniperSq) & occupancy; + Bitboard b = between_bb(ksq, sniperSq) & occupancy; if (b && !more_than_one(b)) { - blockers |= b; - if (b & pieces(color_of(piece_on(s)))) - pinners |= sniperSq; + st->blockersForKing[c] |= b; + if (b & pieces(c)) + st->pinners[~c] |= sniperSq; } } - return blockers; } diff --git a/src/position.h b/src/position.h index f0546af33f7..ca7c3ace811 100644 --- a/src/position.h +++ b/src/position.h @@ -114,7 +114,7 @@ class Position { // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occupied) const; - Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; + void update_slider_blockers(Color c) const; template Bitboard attacks_by(Color c) const; // Properties of moves From adf29b3fd69cdca035d1aa6675e01acafbf4d07f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 25 Aug 2023 15:42:44 +0300 Subject: [PATCH 0207/1309] Rename one variable To enhance code clarity and prevent potential confusion with the 'r' variable assigned to reduction later in the code, this pull request renames it to 'reductionScale' when we use the same name in the reduction() function. Using distinct variable names for separate functions improves code readability and maintainability. closes https://github.com/official-stockfish/Stockfish/pull/4765 No functional change --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 76d0545ac50..eefe5a3bcf9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -80,8 +80,9 @@ namespace { int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { - int r = Reductions[d] * Reductions[mn]; - return (r + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + (!i && r > 936); + int reductionScale = Reductions[d] * Reductions[mn]; + return (reductionScale + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + + (!i && reductionScale > 936); } constexpr int futility_move_count(bool improving, Depth depth) { From b25d68f6ee2d016cc0c14b076e79e6c44fdaea2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sat, 2 Sep 2023 08:39:16 +0200 Subject: [PATCH 0208/1309] Introduce simple_eval() for lazy evaluations This patch implements the pure materialistic evaluation called simple_eval() to gain a speed-up during Stockfish search. We use the so-called lazy evaluation trick: replace the accurate but slow NNUE network evaluation by the super-fast simple_eval() if the position seems to be already won (high material advantage). To guard against some of the most obvious blunders introduced by this idea, this patch uses the following features which will raise the lazy evaluation threshold in some situations: - avoid lazy evals on shuffling branches in the search tree - avoid lazy evals if the position at root already has a material imbalance - avoid lazy evals if the search value at root is already winning/losing. Moreover, we add a small random noise to the simple_eval() term. This idea (stochastic mobility in the minimax tree) was worth about 200 Elo in the pure simple_eval() player on Lichess. Overall, the current implementation in this patch evaluates about 2% of the leaves in the search tree lazily. -------------------------------------------- STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 60352 W: 15585 L: 15234 D: 29533 Ptnml(0-2): 216, 6906, 15578, 7263, 213 https://tests.stockfishchess.org/tests/view/64f1d9bcbd9967ffae366209 LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 35106 W: 8990 L: 8678 D: 17438 Ptnml(0-2): 14, 3668, 9887, 3960, 24 https://tests.stockfishchess.org/tests/view/64f25204f5b0c54e3f04c0e7 verification run at VLTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 74362 W: 19088 L: 18716 D: 36558 Ptnml(0-2): 6, 7226, 22348, 7592, 9 https://tests.stockfishchess.org/tests/view/64f2ecdbf5b0c54e3f04d3ae All three tests above were run with adjudication off, we also verified that there was no regression on matetracker (thanks Disservin!). ---------------------------------------------- closes https://github.com/official-stockfish/Stockfish/pull/4771 Bench: 1393714 --- src/evaluate.cpp | 60 ++++++++++++++++++++++++++++++++---------------- src/evaluate.h | 4 ++++ src/thread.cpp | 2 ++ src/thread.h | 1 + 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 25a6545564a..46ebbb49920 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -136,35 +136,54 @@ namespace Eval { } } -/// evaluate() is the evaluator for the outer world. It returns a static -/// evaluation of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos) { - - assert(!pos.checkers()); - - Value v; +/// simple_eval() returns a static, purely materialistic evaluation of the position +/// from the point of view of the given color. It can be divided by PawnValue to get +/// an approximation of the material advantage on the board in terms of pawns. - int nnueComplexity; - int npm = pos.non_pawn_material() / 64; +Value Eval::simple_eval(const Position& pos, Color c) { + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); +} - Color stm = pos.side_to_move(); - Value optimism = pos.this_thread()->optimism[stm]; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); +/// evaluate() is the evaluator for the outer world. It returns a static evaluation +/// of the position from the point of view of the side to move. - int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) - + 126 * (pos.count(stm) - pos.count(~stm)); +Value Eval::evaluate(const Position& pos) { - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(material - nnue)) / 32768; + assert(!pos.checkers()); - v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm + pos.count())) / 1024; + Value v; + Color stm = pos.side_to_move(); + int shuffling = pos.rule50_count(); + int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); + + bool lazy = abs(simpleEval) >= RookValue + KnightValue + + 16 * shuffling * shuffling + + abs(pos.this_thread()->bestValue) + + abs(pos.this_thread()->rootSimpleEval); + + if (lazy) + v = Value(simpleEval); + else + { + int nnueComplexity; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + + Value optimism = pos.this_thread()->optimism[stm]; + + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; + + int npm = pos.non_pawn_material() / 64; + v = ( nnue * (915 + npm + 9 * pos.count()) + + optimism * (154 + npm + pos.count())) / 1024; + } // Damp down the evaluation linearly when shuffling - v = v * (200 - pos.rule50_count()) / 214; + v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); @@ -184,6 +203,7 @@ std::string Eval::trace(Position& pos) { // Reset any global variable used in eval pos.this_thread()->bestValue = VALUE_ZERO; + pos.this_thread()->rootSimpleEval = VALUE_ZERO; pos.this_thread()->optimism[WHITE] = VALUE_ZERO; pos.this_thread()->optimism[BLACK] = VALUE_ZERO; diff --git a/src/evaluate.h b/src/evaluate.h index a222da7361a..7f4feedf0c6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -21,6 +21,8 @@ #include +#include "types.h" + namespace Stockfish { class Position; @@ -29,6 +31,8 @@ enum Value : int; namespace Eval { std::string trace(Position& pos); + + Value simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); extern std::string currentEvalFileName; diff --git a/src/thread.cpp b/src/thread.cpp index 9cf85310c39..60f760ed46e 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -27,6 +27,7 @@ #include #include +#include "evaluate.h" #include "misc.h" #include "movegen.h" #include "search.h" @@ -212,6 +213,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, th->rootMoves = rootMoves; th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); th->rootState = setupStates->back(); + th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); } main()->start_searching(); diff --git a/src/thread.h b/src/thread.h index a421af9e3bc..8d0adcf0340 100644 --- a/src/thread.h +++ b/src/thread.h @@ -67,6 +67,7 @@ class Thread { Search::RootMoves rootMoves; Depth rootDepth, completedDepth; Value rootDelta; + Value rootSimpleEval; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; From a8b4fd16716e74a9819e798fc09e5926e003013e Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Mon, 4 Sep 2023 22:01:20 +0200 Subject: [PATCH 0209/1309] Avoid "using namespace std" This is a cleanup PR that prepares the automatic checking of missing or superfluous #include directives via the include-what-you-use (IWYU) tool on the CI. Unfortunately, IWYU proposes additional includes for "namespace std" although we don't need them. To avoid the problem, the commit removes all "using namespace std" statements from the code and directly uses the std:: prefix instead. Alternatively, we could add specific usings (e.g. "using std::string") foreach used type. Also, a mix of both approaches would be possible. I decided for the prefix approach because most of the files were already using the std:: prefixes despite the "using namespace std". closes https://github.com/official-stockfish/Stockfish/pull/4772 No functional change --- src/benchmark.cpp | 30 +++++++++++------------- src/evaluate.cpp | 30 +++++++++++------------- src/misc.cpp | 48 ++++++++++++++++++------------------- src/uci.cpp | 60 +++++++++++++++++++++++------------------------ 4 files changed, 80 insertions(+), 88 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index f3401c61c8b..8e28184a3cd 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -25,11 +25,9 @@ #include "position.h" -using namespace std; - namespace { -const vector Defaults = { +const std::vector Defaults = { "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", @@ -109,17 +107,17 @@ namespace Stockfish { /// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec /// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" -vector setup_bench(const Position& current, istream& is) { +std::vector setup_bench(const Position& current, std::istream& is) { - vector fens, list; - string go, token; + std::vector fens, list; + std::string go, token; // Assign default values to missing arguments - string ttSize = (is >> token) ? token : "16"; - string threads = (is >> token) ? token : "1"; - string limit = (is >> token) ? token : "13"; - string fenFile = (is >> token) ? token : "default"; - string limitType = (is >> token) ? token : "depth"; + std::string ttSize = (is >> token) ? token : "16"; + std::string threads = (is >> token) ? token : "1"; + std::string limit = (is >> token) ? token : "13"; + std::string fenFile = (is >> token) ? token : "default"; + std::string limitType = (is >> token) ? token : "depth"; go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; @@ -131,12 +129,12 @@ vector setup_bench(const Position& current, istream& is) { else { - string fen; - ifstream file(fenFile); + std::string fen; + std::ifstream file(fenFile); if (!file.is_open()) { - cerr << "Unable to open file " << fenFile << endl; + std::cerr << "Unable to open file " << fenFile << std::endl; exit(EXIT_FAILURE); } @@ -151,8 +149,8 @@ vector setup_bench(const Position& current, istream& is) { list.emplace_back("setoption name Hash value " + ttSize); list.emplace_back("ucinewgame"); - for (const string& fen : fens) - if (fen.find("setoption") != string::npos) + for (const std::string& fen : fens) + if (fen.find("setoption") != std::string::npos) list.emplace_back(fen); else { diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 46ebbb49920..9ca0e4566f6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -51,13 +51,11 @@ #endif -using namespace std; - namespace Stockfish { namespace Eval { - string currentEvalFileName = "None"; + std::string currentEvalFileName = "None"; /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -69,22 +67,22 @@ namespace Eval { void NNUE::init() { - string eval_file = string(Options["EvalFile"]); + std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; #if defined(DEFAULT_NNUE_DIRECTORY) - vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; + std::vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; #else - vector dirs = { "" , "" , CommandLine::binaryDirectory }; + std::vector dirs = { "" , "" , CommandLine::binaryDirectory }; #endif - for (const string& directory : dirs) + for (const std::string& directory : dirs) if (currentEvalFileName != eval_file) { if (directory != "") { - ifstream stream(directory + eval_file, ios::binary); + std::ifstream stream(directory + eval_file, std::ios::binary); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } @@ -92,7 +90,7 @@ namespace Eval { if (directory == "" && eval_file == EvalFileDefaultName) { // C++ way to prepare a buffer for a memory stream - class MemoryBuffer : public basic_streambuf { + class MemoryBuffer : public std::basic_streambuf { public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } }; @@ -100,7 +98,7 @@ namespace Eval { size_t(gEmbeddedNNUESize)); (void) gEmbeddedNNUEEnd; // Silence warning on unused variable - istream stream(&buffer); + std::istream stream(&buffer); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } @@ -110,18 +108,18 @@ namespace Eval { /// NNUE::verify() verifies that the last net used was loaded successfully void NNUE::verify() { - string eval_file = string(Options["EvalFile"]); + std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; if (currentEvalFileName != eval_file) { - string msg1 = "Network evaluation parameters compatible with the engine must be available."; - string msg2 = "The network file " + eval_file + " was not loaded successfully."; - string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); - string msg5 = "The engine will be terminated now."; + std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); + std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; sync_cout << "info string ERROR: " << msg2 << sync_endl; diff --git a/src/misc.cpp b/src/misc.cpp index 42083e0a94f..a72a1c13319 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -67,14 +67,12 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES #include #endif -using namespace std; - namespace Stockfish { namespace { /// Version number or dev. -constexpr string_view version = "dev"; +constexpr std::string_view version = "dev"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -82,16 +80,16 @@ constexpr string_view version = "dev"; /// usual I/O functionality, all without changing a single line of code! /// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 -struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout +struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout - Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} + Tie(std::streambuf* b, std::streambuf* l) : buf(b), logBuf(l) {} int sync() override { return logBuf->pubsync(), buf->pubsync(); } int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } int underflow() override { return buf->sgetc(); } int uflow() override { return log(buf->sbumpc(), ">> "); } - streambuf *buf, *logBuf; + std::streambuf *buf, *logBuf; int log(int c, const char* prefix) { @@ -106,10 +104,10 @@ struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout class Logger { - Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {} + Logger() : in(std::cin.rdbuf(), file.rdbuf()), out(std::cout.rdbuf(), file.rdbuf()) {} ~Logger() { start(""); } - ofstream file; + std::ofstream file; Tie in, out; public: @@ -119,23 +117,23 @@ class Logger { if (l.file.is_open()) { - cout.rdbuf(l.out.buf); - cin.rdbuf(l.in.buf); + std::cout.rdbuf(l.out.buf); + std::cin.rdbuf(l.in.buf); l.file.close(); } if (!fname.empty()) { - l.file.open(fname, ifstream::out); + l.file.open(fname, std::ifstream::out); if (!l.file.is_open()) { - cerr << "Unable to open debug log file " << fname << endl; + std::cerr << "Unable to open debug log file " << fname << std::endl; exit(EXIT_FAILURE); } - cin.rdbuf(&l.in); - cout.rdbuf(&l.out); + std::cin.rdbuf(&l.in); + std::cout.rdbuf(&l.out); } } }; @@ -153,9 +151,9 @@ class Logger { /// For releases (non dev builds) we only include the version number: /// Stockfish version -string engine_info(bool to_uci) { - stringstream ss; - ss << "Stockfish " << version << setfill('0'); +std::string engine_info(bool to_uci) { + std::stringstream ss; + ss << "Stockfish " << version << std::setfill('0'); if constexpr (version == "dev") { @@ -163,12 +161,12 @@ string engine_info(bool to_uci) { #ifdef GIT_DATE ss << stringify(GIT_DATE); #else - constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); - string month, day, year; - stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + std::string month, day, year; + std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" date >> month >> day >> year; - ss << year << setw(2) << setfill('0') << (1 + months.find(month) / 4) << setw(2) << setfill('0') << day; + ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) << std::setw(2) << std::setfill('0') << day; #endif ss << "-"; @@ -741,12 +739,12 @@ void bindThisThread(size_t idx) { namespace CommandLine { -string argv0; // path+name of the executable binary, as given by argv[0] -string binaryDirectory; // path of the executable directory -string workingDirectory; // path of the working directory +std::string argv0; // path+name of the executable binary, as given by argv[0] +std::string binaryDirectory; // path of the executable directory +std::string workingDirectory; // path of the working directory void init([[maybe_unused]] int argc, char* argv[]) { - string pathSeparator; + std::string pathSeparator; // extract the path+name of the executable binary argv0 = argv[0]; diff --git a/src/uci.cpp b/src/uci.cpp index 2a35a40fd4e..f3e436ef3aa 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -41,8 +41,6 @@ #include "search.h" #include "thread.h" -using namespace std; - namespace Stockfish { namespace { @@ -56,10 +54,10 @@ namespace { // the initial position ("startpos") and then makes the moves given in the following // move list ("moves"). - void position(Position& pos, istringstream& is, StateListPtr& states) { + void position(Position& pos, std::istringstream& is, StateListPtr& states) { Move m; - string token, fen; + std::string token, fen; is >> token; @@ -103,11 +101,11 @@ namespace { // setoption() is called when the engine receives the "setoption" UCI command. // The function updates the UCI option ("name") to the given value ("value"). - void setoption(istringstream& is) { + void setoption(std::istringstream& is) { Threads.main()->wait_for_search_finished(); - string token, name, value; + std::string token, name, value; is >> token; // Consume the "name" token @@ -130,10 +128,10 @@ namespace { // sets the thinking time and other parameters from the input string, then starts // with a search. - void go(Position& pos, istringstream& is, StateListPtr& states) { + void go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits; - string token; + std::string token; bool ponderMode = false; limits.startTime = now(); // The search starts as early as possible @@ -164,24 +162,24 @@ namespace { // Firstly, a list of UCI commands is set up according to the bench // parameters, then it is run one by one, printing a summary at the end. - void bench(Position& pos, istream& args, StateListPtr& states) { + void bench(Position& pos, std::istream& args, StateListPtr& states) { - string token; + std::string token; uint64_t num, nodes = 0, cnt = 1; - vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), [](const string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + std::vector list = setup_bench(pos, args); + num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); TimePoint elapsed = now(); for (const auto& cmd : list) { - istringstream is(cmd); - is >> skipws >> token; + std::istringstream is(cmd); + is >> std::skipws >> token; if (token == "go" || token == "eval") { - cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl; + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << std::endl; if (token == "go") { go(pos, is, states); @@ -200,10 +198,10 @@ namespace { dbg_print(); - cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes - << "\nNodes/second : " << 1000 * nodes / elapsed << endl; + std::cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed + << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; } // The win rate model returns the probability of winning (in per mille units) given an @@ -244,7 +242,7 @@ namespace { void UCI::loop(int argc, char* argv[]) { Position pos; - string token, cmd; + std::string token, cmd; StateListPtr states(new std::deque(1)); pos.set(StartFEN, false, &states->back(), Threads.main()); @@ -253,13 +251,13 @@ void UCI::loop(int argc, char* argv[]) { cmd += std::string(argv[i]) + " "; do { - if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + if (argc == 1 && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication cmd = "quit"; - istringstream is(cmd); + std::istringstream is(cmd); token.clear(); // Avoid a stale if getline() returns nothing or a blank line - is >> skipws >> token; + is >> std::skipws >> token; if ( token == "quit" || token == "stop") @@ -294,7 +292,7 @@ void UCI::loop(int argc, char* argv[]) { { std::optional filename; std::string f; - if (is >> skipws >> f) + if (is >> std::skipws >> f) filename = f; Eval::NNUE::save_eval(filename); } @@ -325,11 +323,11 @@ int UCI::to_cp(Value v) { /// mate Mate in 'y' moves (not plies). If the engine is getting mated, /// uses negative values for 'y'. -string UCI::value(Value v) { +std::string UCI::value(Value v) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - stringstream ss; + std::stringstream ss; if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) ss << "cp " << UCI::to_cp(v); @@ -348,9 +346,9 @@ string UCI::value(Value v) { /// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation /// and a game ply based on the data gathered for fishtest LTC games. -string UCI::wdl(Value v, int ply) { +std::string UCI::wdl(Value v, int ply) { - stringstream ss; + std::stringstream ss; int wdl_w = win_rate_model( v, ply); int wdl_l = win_rate_model(-v, ply); @@ -373,7 +371,7 @@ std::string UCI::square(Square s) { /// standard chess mode and in e1h1 notation it is printed in Chess960 mode. /// Internally, all castling moves are always encoded as 'king captures rook'. -string UCI::move(Move m, bool chess960) { +std::string UCI::move(Move m, bool chess960) { if (m == MOVE_NONE) return "(none)"; @@ -387,7 +385,7 @@ string UCI::move(Move m, bool chess960) { if (type_of(m) == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - string move = UCI::square(from) + UCI::square(to); + std::string move = UCI::square(from) + UCI::square(to); if (type_of(m) == PROMOTION) move += " pnbrqk"[promotion_type(m)]; @@ -399,7 +397,7 @@ string UCI::move(Move m, bool chess960) { /// UCI::to_move() converts a string representing a move in coordinate notation /// (g1f3, a7a8q) to the corresponding legal Move, if any. -Move UCI::to_move(const Position& pos, string& str) { +Move UCI::to_move(const Position& pos, std::string& str) { if (str.length() == 5) str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased From 1461d861c8240e29df690f1e34dc50eee37ae1b5 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Mon, 4 Sep 2023 13:53:30 +0200 Subject: [PATCH 0210/1309] Prevent usage of AVX-512 for the last layer. Add more static checks regarding the SIMD width match. STC: https://tests.stockfishchess.org/tests/view/64f5c568a9bc5a78c669e70e LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 125216 W: 31756 L: 31636 D: 61824 Ptnml(0-2): 327, 13993, 33848, 14113, 327 Fixes a bug introduced in 2f2f45f, where with AVX-512 the weights and input to the last layer were being read out of bounds. Now AVX-512 is only used for the layers it can be used for. Additional static assertions have been added to prevent more errors like this in the future. closes https://github.com/official-stockfish/Stockfish/pull/4773 No functional change --- src/nnue/layers/affine_transform.h | 50 ++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index e9d0beace17..61cdb781866 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -210,6 +210,11 @@ namespace Stockfish::Eval::NNUE::Layers { void propagate( const InputType* input, OutputType* output) const { +#if defined (USE_SSSE3) + + if constexpr (OutputDimensions > 1) + { + #if defined (USE_AVX512) using vec_t = __m512i; #define vec_setzero _mm512_setzero_si512 @@ -233,15 +238,10 @@ namespace Stockfish::Eval::NNUE::Layers { #define vec_hadd Simd::m128_hadd #endif -#if defined (USE_SSSE3) - const auto inputVector = reinterpret_cast(input); + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + static_assert(OutputDimensions % OutputSimdWidth == 0); - static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); - - if constexpr (OutputDimensions % OutputSimdWidth == 0) - { constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; @@ -264,10 +264,41 @@ namespace Stockfish::Eval::NNUE::Layers { vec_t* outptr = reinterpret_cast(output); for (IndexType k = 0; k < NumRegs; ++k) outptr[k] = acc[k]; + +# undef vec_setzero +# undef vec_set_32 +# undef vec_add_dpbusd_32 +# undef vec_add_dpbusd_32x2 +# undef vec_hadd + } else if constexpr (OutputDimensions == 1) { - constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; + +// We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. +#if defined (USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 + #define vec_hadd Simd::m256_hadd +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::m128_hadd +#endif + + const auto inputVector = reinterpret_cast(input); + + static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); + + static_assert(PaddedInputDimensions % InputSimdWidth == 0); + + constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; vec_t sum0 = vec_setzero(); const auto row0 = reinterpret_cast(&weights[0]); @@ -277,13 +308,14 @@ namespace Stockfish::Eval::NNUE::Layers { vec_add_dpbusd_32(sum0, in, row0[j]); } output[0] = vec_hadd(sum0, biases[0]); - } # undef vec_setzero # undef vec_set_32 # undef vec_add_dpbusd_32 # undef vec_add_dpbusd_32x2 # undef vec_hadd + + } #else // Use old implementation for the other architectures. affine_transform_non_ssse3< From 46a5cedc11bbad4a35f36aec660f270680008f37 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 10 Sep 2023 12:15:06 +0200 Subject: [PATCH 0211/1309] Cleanup git checkout actions We now fetch only the current commit for jobs that don't need the git history. For the Prerelease job, we don't checkout the code at all. closes https://github.com/official-stockfish/Stockfish/pull/4779 No functional change --- .github/workflows/stockfish.yml | 4 ---- .github/workflows/stockfish_compile_test.yml | 2 -- .github/workflows/stockfish_sanitizers.yml | 2 -- 3 files changed, 8 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 99c4259ac75..8ea1837d032 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -16,10 +16,6 @@ jobs: if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - # returns null if no pre-release exists - name: Get Commit SHA of Latest Pre-release run: | diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 90e01537d57..808fcb55f7b 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -52,8 +52,6 @@ jobs: shell: ${{ matrix.config.shell }} steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Setup msys and install required packages if: runner.os == 'Windows' diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index 228742b3f12..b137f50eb06 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -36,8 +36,6 @@ jobs: shell: ${{ matrix.config.shell }} steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Download required linux packages run: | From 6d85f43e26cb8632337e67cea5ef88bab78121f3 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 10 Sep 2023 08:49:18 +0800 Subject: [PATCH 0212/1309] Simplify cutnode depth condition With this patch, the depth condition for the cutnodes reduction is loosened from tte->depth() >= depth + 3 to just tte->depth() >= depth. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 101152 W: 25830 L: 25682 D: 49640 Ptnml(0-2): 312, 11788, 26258, 11876, 342 https://tests.stockfishchess.org/tests/view/64fd15635dab775b5359eaa6 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82542 W: 20980 L: 20824 D: 40738 Ptnml(0-2): 42, 8795, 23440, 8953, 41 https://tests.stockfishchess.org/tests/view/64fda3545dab775b5359fbf1 closes https://github.com/official-stockfish/Stockfish/pull/4780 Bench: 1479029 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index eefe5a3bcf9..a745d3bfdd0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1131,7 +1131,7 @@ namespace { // Decrease further on cutNodes. (~1 Elo) if ( ss->ttPv && !likelyFailLow) - r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2; + r -= cutNode && tte->depth() >= depth ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss-1)->moveCount > 8) From ef2282961602f47a9c0c11adc2c0da7af39dab0f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 11 Sep 2023 15:37:18 +0300 Subject: [PATCH 0213/1309] Do more futility pruning in qsearch This patch introduces a third futility pruning heuristic in qsearch. The idea is that the static exchange evaluation is much worse than the difference between futility base and alpha. Thus we can assume that the probability of the move being good enough to beat alpha is low so it can be pruned. Passed STC: https://tests.stockfishchess.org/tests/view/64fc982a5dab775b5359dc83 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 36576 W: 9484 L: 9170 D: 17922 Ptnml(0-2): 121, 4119, 9495, 4431, 122 Passed LTC: https://tests.stockfishchess.org/tests/view/64fcc7935dab775b5359e1a9 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 135408 W: 34556 L: 34041 D: 66811 Ptnml(0-2): 56, 14462, 38165, 14953, 68 closes https://github.com/official-stockfish/Stockfish/pull/4781 Bench: 1330793 --- src/search.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index a745d3bfdd0..beb1cb54822 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1549,17 +1549,29 @@ namespace { futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; + // If static eval + value of piece we are going to capture is much lower + // than alpha we can prune this move if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); continue; } + // If static eval is much lower than alpha and move is not winning material + // we can prune this move if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; } + + // If static exchange evaluation is much worse than what is needed to not + // fall below alpha we can prune this move + if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) + { + bestValue = alpha; + continue; + } } // We prune after the second quiet check evasion move, where being 'in check' is From 3d1b067d853d6e8cc22cf18c1abb4cd9833dd38f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 9 Sep 2023 10:24:57 -0400 Subject: [PATCH 0214/1309] Update default net to nn-1ee1aba5ed4c.nnue Created by retraining the master net on a dataset composed by: - adding Leela data from T60 jul-dec 2020, T77 nov 2021, T80 jun-jul 2023 - deduplicating and unminimizing parts of the dataset before interleaving Trained initially with max epoch 800, then increased near the end of training twice. First to 960, then 1200. After training, post-processing involved: - greedy permuting L1 weights with https://github.com/official-stockfish/Stockfish/pull/4620 - greedy 2- and 3- cycle permuting with https://github.com/official-stockfish/Stockfish/pull/4640 python3 easy_train.py \ --experiment-name 2048-retrain-S6-sk28 \ --training-dataset /data/S6.binpack \ --early-fen-skipping 28 \ --start-from-engine-test-net True \ --max_epoch 1200 \ --lr 4.375e-4 \ --gamma 0.995 \ --start-lambda 1.0 \ --end-lambda 0.7 \ --tui False \ --seed $RANDOM \ --gpus 0 In the list of datasets below, periods in the filename represent the sequence of steps applied to arrive at the particular binpack. For example: test77-dec2021-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack 1. test77 dec2021 data rescored with 16 TB of syzygy tablebases during data conversion 2. filtered with csv_filter_v6_dd.py - v6 filtering and deduplication in one step 3. minimized with the original mar2023 implementation of `minimize_binpack` in the tools branch 4. unminimized by removing all positions with score == 32002 (`VALUE_NONE`) Binpacks were: - filtered with: https://github.com/linrock/nnue-data - unminimized with: https://github.com/linrock/Stockfish/tree/tools-unminify - deduplicated with: https://github.com/linrock/Stockfish/tree/tools-dd DATASETS=( leela96-filt-v2.min.unminimized.binpack dfrc99-16tb7p-eval-filt-v2.min.unminimized.binpack # most of the 0dd1cebea57 v6-dd dataset (without test80-jul2022) # https://github.com/official-stockfish/Stockfish/pull/4606 test60-novdec2021-12tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test77-dec2021-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test78-jantomay2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test78-juntosep2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test79-apr2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test79-may2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-jun2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-aug2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-sep2022-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-oct2022-16tb7p.filter-v6-dd.min.binpack test80-nov2022-16tb7p.filter-v6-dd.min.binpack test80-jan2023-3of3-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack test80-feb2023-16tb7p.filter-v6-dd.min-mar2023.unminimized.binpack # older Leela data, recently converted test60-octnovdec2020-2tb7p.min.unminimized.binpack test60-julaugsep2020-2tb7p.min.binpack test77-nov2021-2tb7p.min.dd.binpack # newer Leela data test80-mar2023-2tb7p.min.unminimized.binpack test80-apr2023-2tb7p.filter-v6-sk16.min.unminimized.binpack test80-may2023-2tb7p.min.dd.binpack test80-jun2023-2tb7p.min.binpack test80-jul2023-2tb7p.binpack ) python3 interleave_binpacks.py ${DATASETS[@]} /data/S6.binpack Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch1059 : 2.7 +/- 1.6 Passed STC: https://tests.stockfishchess.org/tests/view/64fc8d705dab775b5359db42 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 168352 W: 43216 L: 42704 D: 82432 Ptnml(0-2): 599, 19672, 43134, 20160, 611 Passed LTC: https://tests.stockfishchess.org/tests/view/64fd44a75dab775b5359f065 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 154194 W: 39436 L: 38881 D: 75877 Ptnml(0-2): 78, 16577, 43238, 17120, 84 closes https://github.com/official-stockfish/Stockfish/pull/4782 Bench: 1603079 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 7f4feedf0c6..8ac24daea17 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -40,7 +40,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-c38c3d8d3920.nnue" + #define EvalFileDefaultName "nn-1ee1aba5ed4c.nnue" namespace NNUE { From b9319c4fa4f42438f484d144be9a1306765cf998 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Thu, 31 Aug 2023 21:56:34 +0200 Subject: [PATCH 0215/1309] Cleanup code after dropping ICC support in favor of ICX The commit removes all uses of ICC's __INTEL_COMPILER macro and other references to ICC. It also adds ICX info to the compiler command and fixes two typos in Makefile's help output. closes https://github.com/official-stockfish/Stockfish/pull/4769 No functional change --- src/Makefile | 4 ++-- src/bitboard.h | 4 ++-- src/misc.cpp | 29 +++++++++++------------------ src/types.h | 19 ++++++++++--------- 4 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/Makefile b/src/Makefile index d6443845477..f5a420b7ce0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -804,8 +804,8 @@ help: @echo "" @echo "Supported compilers:" @echo "" - @echo "gcc > Gnu compiler (default)" - @echo "mingw > Gnu compiler with MinGW under Windows" + @echo "gcc > GNU compiler (default)" + @echo "mingw > GNU compiler with MinGW under Windows" @echo "clang > LLVM Clang compiler" @echo "icx > Intel oneAPI DPC++/C++ Compiler" @echo "ndk > Google NDK to cross-compile for Android" diff --git a/src/bitboard.h b/src/bitboard.h index f9175333745..c05b6e3f8cf 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -262,7 +262,7 @@ inline int popcount(Bitboard b) { 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]]; -#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) +#elif defined(_MSC_VER) return (int)_mm_popcnt_u64(b); @@ -276,7 +276,7 @@ inline int popcount(Bitboard b) { /// lsb() and msb() return the least/most significant bit in a non-zero bitboard -#if defined(__GNUC__) // GCC, Clang, ICC +#if defined(__GNUC__) // GCC, Clang, ICX inline Square lsb(Bitboard b) { assert(b); diff --git a/src/misc.cpp b/src/misc.cpp index a72a1c13319..83ea8e10fbf 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -193,22 +193,21 @@ std::string compiler_info() { /// Predefined macros hell: /// -/// __GNUC__ Compiler is gcc, Clang or Intel on Linux -/// __INTEL_COMPILER Compiler is Intel -/// _MSC_VER Compiler is MSVC or Intel on Windows -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +/// __GNUC__ Compiler is GCC, Clang or ICX +/// __clang__ Compiler is Clang or ICX +/// __INTEL_LLVM_COMPILER Compiler is ICX +/// _MSC_VER Compiler is MSVC +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit std::string compiler = "\nCompiled by "; - #ifdef __clang__ + #if defined(__INTEL_LLVM_COMPILER) + compiler += "ICX "; + compiler += stringify(__INTEL_LLVM_COMPILER); + #elif defined(__clang__) compiler += "clang++ "; compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); - #elif __INTEL_COMPILER - compiler += "Intel compiler "; - compiler += "(version "; - compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE); - compiler += ")"; #elif _MSC_VER compiler += "MSVC "; compiler += "(version "; @@ -425,13 +424,7 @@ void prefetch(void*) {} void prefetch(void* addr) { -# if defined(__INTEL_COMPILER) - // This hack prevents prefetches from being optimized away by - // Intel compiler. Both MSVC and gcc seem not be affected by this. - __asm__ (""); -# endif - -# if defined(__INTEL_COMPILER) || defined(_MSC_VER) +# if defined(_MSC_VER) _mm_prefetch((char*)addr, _MM_HINT_T0); # else __builtin_prefetch(addr); diff --git a/src/types.h b/src/types.h index bb319c2bb08..f81d30fe032 100644 --- a/src/types.h +++ b/src/types.h @@ -48,11 +48,12 @@ /// Predefined macros hell: /// -/// __GNUC__ Compiler is gcc, Clang or Intel on Linux -/// __INTEL_COMPILER Compiler is Intel -/// _MSC_VER Compiler is MSVC or Intel on Windows -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +/// __GNUC__ Compiler is GCC, Clang or ICX +/// __clang__ Compiler is Clang or ICX +/// __INTEL_LLVM_COMPILER Compiler is ICX +/// _MSC_VER Compiler is MSVC +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit #if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) #define ALIGNAS_ON_STACK_VARIABLES_BROKEN @@ -65,12 +66,12 @@ # define IS_64BIT #endif -#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) -# include // Intel and Microsoft header for _mm_popcnt_u64() +#if defined(USE_POPCNT) && defined(_MSC_VER) +# include // Microsoft header for _mm_popcnt_u64() #endif -#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) -# include // Intel and Microsoft header for _mm_prefetch() +#if !defined(NO_PREFETCH) && defined(_MSC_VER) +# include // Microsoft header for _mm_prefetch() #endif #if defined(USE_PEXT) From 3f7fb5ac1d58e1c90db063053e9f913b9df79994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Mon, 11 Sep 2023 23:19:06 +0200 Subject: [PATCH 0216/1309] Reformat some comments Also include the bench to make Continuation Integration happy on Github. Bench: 1603079 --- src/search.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index beb1cb54822..4b403c49d8e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -928,7 +928,8 @@ namespace { moveCountPruning = singularQuietLMR = false; // Indicate PvNodes that will probably fail low if the node was searched - // at a depth equal to or greater than the current depth, and the result of this search was a fail low. + // at a depth equal to or greater than the current depth, and the result + // of this search was a fail low. bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) @@ -1039,10 +1040,10 @@ namespace { // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the - // result is lower than ttValue minus a margin, then we will extend the ttMove. - // 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 + // a reduced search on all the other moves but the ttMove and if the result + // is lower than ttValue minus a margin, then we will extend the ttMove. Note + // that 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 this type of time controls. if ( !rootNode && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) @@ -1076,10 +1077,10 @@ namespace { } // Multi-cut pruning - // Our ttMove is assumed to fail high, and now we failed high also on a reduced - // search without the ttMove. So we assume this expected Cut-node is not singular, - // that multiple moves fail high, and we can prune the whole subtree by returning - // a softbound. + // Our ttMove is assumed to fail high, and now we failed high also on a + // reduced search without the ttMove. So we assume this expected cut-node + // is not singular, that multiple moves fail high, and we can prune the + // whole subtree by returning a softbound. else if (singularBeta >= beta) return singularBeta; @@ -1126,8 +1127,7 @@ namespace { // Step 16. Make the move pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV - // and node is not likely to fail low. (~3 Elo) + // Decrease reduction if position is or has been on the PV and not likely to fail low. (~3 Elo) // Decrease further on cutNodes. (~1 Elo) if ( ss->ttPv && !likelyFailLow) @@ -1162,6 +1162,7 @@ namespace { if ((ss+1)->cutoffCnt > 3) r++; + // Decrease reduction for first generated move (ttMove) else if (move == ttMove) r--; From 0e32287af470dee230a30d9f513682c3ce798668 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Thu, 14 Sep 2023 14:15:43 +0300 Subject: [PATCH 0217/1309] Reorder some lines Now that qsearch has its own repetition detection we can flip the order of lines and remove the guard of depth < 0 which is not needed after reordering (i.e. it was there to prevent checking repetition again at depth ==0). Passed STC: https://tests.stockfishchess.org/tests/view/6502ecbb2cd016da89abc3fb LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 69536 W: 17668 L: 17490 D: 34378 Ptnml(0-2): 190, 7652, 18929, 7784, 213 Passed LTC: https://tests.stockfishchess.org/tests/view/6505ce9072620bc881ea9086 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 52116 W: 13294 L: 13113 D: 25709 Ptnml(0-2): 26, 5176, 15471, 5361, 24 closes https://github.com/official-stockfish/Stockfish/pull/4791 No functional change --- src/search.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4b403c49d8e..cae91018931 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -525,6 +525,10 @@ namespace { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; + // Dive into quiescence search when the depth reaches zero + if (depth <= 0) + return qsearch(pos, ss, alpha, beta); + // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( !rootNode @@ -536,10 +540,6 @@ namespace { return alpha; } - // Dive into quiescence search when the depth reaches zero - if (depth <= 0) - return qsearch(pos, ss, alpha, beta); - assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(0 < depth && depth < MAX_PLY); @@ -1407,8 +1407,7 @@ namespace { // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. - if ( depth < 0 - && alpha < VALUE_DRAW + if ( alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); From 97f706ecc11459c8d0aa1901134d12fba00b4b15 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 12 Sep 2023 12:23:24 -0700 Subject: [PATCH 0218/1309] Sparse impl of affine_transform_non_ssse3() deal with the general case About a 8.6% speedup (for general arch) Results for 200 tests for each version: Base Test Diff Mean 141741 153998 -12257 StDev 2990 3042 3742 p-value: 0.999 speedup: 0.086 closes https://github.com/official-stockfish/Stockfish/pull/4786 No functional change --- src/nnue/layers/affine_transform.h | 20 ++++++++++++++------ src/nnue/layers/clipped_relu.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 6 +++--- src/nnue/nnue_feature_transformer.h | 6 +++--- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 61cdb781866..af85c817c2a 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -45,6 +45,7 @@ namespace Stockfish::Eval::NNUE::Layers { template static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) { +# if defined(USE_SSE2) || defined(USE_MMX) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) # if defined(USE_SSE2) // At least a multiple of 16, with SSE2. constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; @@ -129,18 +130,25 @@ namespace Stockfish::Eval::NNUE::Layers { } output[i] = sum[0] + sum[1] + sum[2] + sum[3]; -# else - std::int32_t sum = biases[i]; - for (IndexType j = 0; j < InputDimensions; ++j) { - sum += weights[offset + j] * input[j]; - } - output[i] = sum; # endif } # if defined(USE_MMX) _mm_empty(); # endif + +# else + std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); + + // Traverse weights in transpose order to take advantage of input sparsity + for (IndexType i = 0; i < InputDimensions; ++i) + if (input[i]) { + const std::int8_t* w = &weights[i]; + const int in = input[i]; + for (IndexType j = 0; j < OutputDimensions; ++j) + output[j] += w[j * PaddedInputDimensions] * in; + } +# endif } #endif diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 2856bfb0a63..aab824b3572 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -172,7 +172,7 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - std::max(0, std::min(127, input[i] >> WeightScaleBits))); + std::clamp(input[i] >> WeightScaleBits, 0, 127)); } } }; diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 503b283b25e..a3d2059b4de 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -96,9 +96,9 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - // really should be /127 but we need to make it fast - // needs to be accounted for in the trainer - std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)); + // Really should be /127 but we need to make it fast so we right shift + // by an extra 7 bits instead. Needs to be accounted for in the trainer. + std::min(127ll, ((long long)input[i] * input[i]) >> (2 * WeightScaleBits + 7))); } } }; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 0af0ed96cc5..56442bac9b1 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -327,9 +327,9 @@ namespace Stockfish::Eval::NNUE { for (IndexType j = 0; j < HalfDimensions / 2; ++j) { BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - sum0 = std::max(0, std::min(127, sum0)); - sum1 = std::max(0, std::min(127, sum1)); - output[offset + j] = static_cast(sum0 * sum1 / 128); + sum0 = std::clamp(sum0, 0, 127); + sum1 = std::clamp(sum1, 0, 127); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); } #endif From 708319a433951ee5d5d74e0bf1cda218c14dd18e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 26 Aug 2023 11:38:16 +0200 Subject: [PATCH 0219/1309] Enable a default native ARCH uses a posix compatible script to find the native arch. (based on ppigazzini's https://github.com/ppigazzini/stockfish-downloader ) use that native arch by default, no changes if ARCH is specified explicitly. SF can now be compiled in an optimal way simply using make -j profile-build closes https://github.com/official-stockfish/Stockfish/pull/4777 No functional change --- scripts/get_native_properties.sh | 121 +++++++++++++++++++++++++++++++ src/Makefile | 18 ++--- 2 files changed, 130 insertions(+), 9 deletions(-) create mode 100755 scripts/get_native_properties.sh diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh new file mode 100755 index 00000000000..cffb0ce2731 --- /dev/null +++ b/scripts/get_native_properties.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +# +# Returns properties of the native system. +# best architecture as supported by the CPU +# filename of the best binary uploaded as an artifact during CI +# + +# Check if all the given flags are present in the CPU flags list +check_flags() { + for flag; do + printf '%s\n' "$flags" | grep -q -w "$flag" || return 1 + done +} + +# Set the CPU flags list +get_flags() { + flags="$(awk '/^flags[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo) $(awk '/^Features[ \t]*:/{gsub(/^Features[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo)" + # remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 + flags=$(printf '%s' "$flags" | sed "s/[_.]//g") +} + +# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid +check_znver_1_2() { + vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo) + cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo) + [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true +} + +# Set the file CPU x86_64 architecture +set_arch_x86_64() { + if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then + true_arch='x86-64-vnni256' + elif check_flags 'avx512f' 'avx512bw'; then + true_arch='x86-64-avx512' + elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then + true_arch='x86-64-bmi2' + elif check_flags 'avx2'; then + true_arch='x86-64-avx2' + elif check_flags 'sse41' && check_flags 'popcnt'; then + true_arch='x86-64-sse41-popcnt' + else + true_arch='x86-64' + fi +} + +# Check the system type +uname_s=$(uname -s) +uname_m=$(uname -m) +case $uname_s in + 'Darwin') # Mac OSX system + case $uname_m in + 'arm64') + true_arch='apple-silicon' + file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2 + ;; + 'x86_64') + flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | sed "s/[_.]//g") + set_arch_x86_64 + if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then + file_arch='x86-64-bmi2' + fi + ;; + esac + file_os='macos' + file_ext='tar' + ;; + 'Linux') # Linux system + get_flags + case $uname_m in + 'x86_64') + file_os='ubuntu' + check_znver_1_2 + set_arch_x86_64 + ;; + 'i686') + file_os='ubuntu' + true_arch='x86-32' + ;; + 'aarch64') + file_os='android' + true_arch='armv8' + if check_flags 'dotprod'; then + true_arch="$true_arch-dotprod" + fi + ;; + 'armv7'*) + file_os='android' + true_arch='armv7' + if check_flags 'neon'; then + true_arch="$true_arch-neon" + fi + ;; + *) # Unsupported machine type, exit with error + printf 'Unsupported machine type: %s\n' "$uname_m" + exit 1 + ;; + esac + file_ext='tar' + ;; + 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows system with POSIX compatibility layer + get_flags + check_znver_1_2 + set_arch_x86_64 + file_os='windows' + file_ext='zip' + ;; + *) + # Unknown system type, exit with error + printf 'Unsupported system type: %s\n' "$uname_s" + exit 1 + ;; +esac + +if [ -z "$file_arch" ]; then + file_arch=$true_arch +fi + +file_name="stockfish-$file_os-$file_arch.$file_ext" + +printf '%s %s\n' "$true_arch" "$file_name" diff --git a/src/Makefile b/src/Makefile index f5a420b7ce0..bf483f8c77e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -104,9 +104,13 @@ VPATH = syzygy:nnue:nnue/features ### 2.1. General and architecture defaults ifeq ($(ARCH),) - ARCH = x86-64-avx2 - help_skip_sanity = yes + ARCH = native endif + +ifeq ($(ARCH), native) + override ARCH = $(shell ../scripts/get_native_properties.sh | cut -d " " -f 1) +endif + # explicitly check for the list of supported architectures (as listed with make help), # the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true` ifeq ($(ARCH), $(filter $(ARCH), \ @@ -757,12 +761,11 @@ endif ### Section 4. Public Targets ### ========================================================================== - help: @echo "" @echo "To compile stockfish, type: " @echo "" - @echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]" + @echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]" @echo "" @echo "Supported targets:" @echo "" @@ -776,6 +779,7 @@ help: @echo "" @echo "Supported archs:" @echo "" + @echo "native > select the best architecture for the host processor (default)" @echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" @echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" @echo "x86-64-avx512 > x86 64-bit with avx512 support" @@ -822,11 +826,7 @@ help: @echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" @echo "" - @echo "-------------------------------" -ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true) - @echo "The selected architecture $(ARCH) will enable the following configuration: " - @$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity -else +ifneq ($(SUPPORTED_ARCH), true) @echo "Specify a supported architecture with the ARCH option for more details" @echo "" endif From e594aa74290cf37881432f268befde9ad3f3c498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Thu, 14 Sep 2023 11:04:36 +0200 Subject: [PATCH 0220/1309] Export makefile ARCH in binary Example of the `./stockfish compiler` command: Compiled by : g++ (GNUC) 10.3.0 on Apple Compilation architecture : x86-64-bmi2 Compilation settings : 64bit BMI2 AVX2 SSE41 SSSE3 SSE2 POPCNT Compiler __VERSION__ macro : 10.3.0 closes https://github.com/official-stockfish/Stockfish/pull/4789 no functional change --- src/Makefile | 5 +++++ src/misc.cpp | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Makefile b/src/Makefile index bf483f8c77e..e7c06389bdd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -715,6 +715,11 @@ ifneq ($(GIT_DATE), ) CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif +### 3.7.3 Try to include architecture +ifneq ($(ARCH), ) + CXXFLAGS += -DARCH=$(ARCH) +endif + ### 3.8 Link Time Optimization ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. diff --git a/src/misc.cpp b/src/misc.cpp index 83ea8e10fbf..aecc4d23e8c 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -200,7 +200,7 @@ std::string compiler_info() { /// _WIN32 Building on Windows (any) /// _WIN64 Building on Windows 64 bit - std::string compiler = "\nCompiled by "; + std::string compiler = "\nCompiled by : "; #if defined(__INTEL_LLVM_COMPILER) compiler += "ICX "; @@ -253,8 +253,15 @@ std::string compiler_info() { compiler += " on unknown system"; #endif - compiler += "\nCompilation settings include: "; - compiler += (Is64Bit ? " 64bit" : " 32bit"); + compiler += "\nCompilation architecture : "; + #if defined(ARCH) + compiler += stringify(ARCH); + #else + compiler += "(undefined architecture)"; + #endif + + compiler += "\nCompilation settings : "; + compiler += (Is64Bit ? "64bit" : "32bit"); #if defined(USE_VNNI) compiler += " VNNI"; #endif @@ -288,12 +295,13 @@ std::string compiler_info() { compiler += " DEBUG"; #endif - compiler += "\n__VERSION__ macro expands to: "; + compiler += "\nCompiler __VERSION__ macro : "; #ifdef __VERSION__ compiler += __VERSION__; #else compiler += "(undefined macro)"; #endif + compiler += "\n"; return compiler; From 952740b36ca46961a64457767f58dfbe71ae1ead Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Tue, 12 Sep 2023 00:03:04 +0200 Subject: [PATCH 0221/1309] Let CI check C++ includes The commit adds a CI workflow that uses the included-what-you-use (IWYU) tool to check for missing or superfluous includes in .cpp files and their corresponding .h files. This means that some .h files (especially in the nnue folder) are not checked yet. The CI setup looks like this: - We build IWYU from source to include some yet unreleased fixes. This IWYU version targets LLVM 17. Thus, we get the latest release candidate of LLVM 17 from LLVM's nightly packages. - The Makefile now has an analyze target that just build the object files (without linking) - The CI uses the analyze target with the IWYU tool as compiler to analyze the compiled .cpp file and its corresponding .h file. - If IWYU suggests a change the build fails (-Xiwyu --error). - To avoid false positives we use LLVM's libc++ as standard library - We have a custom mappings file that adds some mappings that are missing in IWYU's default mappings We also had to add one IWYU pragma to prevent a false positive in movegen.h. https://github.com/official-stockfish/Stockfish/pull/4783 No functional change --- .github/workflows/libcxx17.imp | 21 ++++++++++ .github/workflows/stockfish.yml | 2 + .github/workflows/stockfish_analyzers.yml | 47 +++++++++++++++++++++++ src/Makefile | 7 +++- src/evaluate.h | 1 - src/movegen.h | 2 +- 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/libcxx17.imp create mode 100644 .github/workflows/stockfish_analyzers.yml diff --git a/.github/workflows/libcxx17.imp b/.github/workflows/libcxx17.imp new file mode 100644 index 00000000000..7bdcf5bc2de --- /dev/null +++ b/.github/workflows/libcxx17.imp @@ -0,0 +1,21 @@ +[ + # Mappings for libcxx's internal headers + { include: [ "<__fwd/fstream.h>", private, "", public ] }, + { include: [ "<__fwd/ios.h>", private, "", public ] }, + { include: [ "<__fwd/istream.h>", private, "", public ] }, + { include: [ "<__fwd/ostream.h>", private, "", public ] }, + { include: [ "<__fwd/sstream.h>", private, "", public ] }, + { include: [ "<__fwd/streambuf.h>", private, "", public ] }, + { include: [ "<__fwd/string_view.h>", private, "", public ] }, + + # Mappings for includes between public headers + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + { include: [ "", public, "", public ] }, + + # Missing mappings in include-what-you-use's libcxx.imp + { include: ["@<__condition_variable/.*>", private, "", public ] }, + { include: ["@<__mutex/.*>", private, "", public ] }, +] diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 8ea1837d032..1ed4b92d4ca 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -33,6 +33,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + Analyzers: + uses: ./.github/workflows/stockfish_analyzers.yml Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: diff --git a/.github/workflows/stockfish_analyzers.yml b/.github/workflows/stockfish_analyzers.yml new file mode 100644 index 00000000000..5f985cc859f --- /dev/null +++ b/.github/workflows/stockfish_analyzers.yml @@ -0,0 +1,47 @@ +name: Stockfish +on: + workflow_call: +jobs: + Analyzers: + name: Check includes + runs-on: ubuntu-22.04 + defaults: + run: + working-directory: Stockfish/src + shell: bash + steps: + - name: Checkout Stockfish + uses: actions/checkout@v3 + with: + path: Stockfish + + - name: Checkout include-what-you-use + uses: actions/checkout@v3 + with: + repository: include-what-you-use/include-what-you-use + ref: f25caa280dc3277c4086ec345ad279a2463fea0f + path: include-what-you-use + + - name: Download required linux packages + run: | + sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main' + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo apt update + sudo apt install -y libclang-17-dev clang-17 libc++-17-dev + + - name: Set up include-what-you-use + run: | + mkdir build && cd build + cmake -G "Unix Makefiles" -DCMAKE_PREFIX_PATH="/usr/lib/llvm-17" .. + sudo make install + working-directory: include-what-you-use + + - name: Check include-what-you-use + run: include-what-you-use --version + + - name: Check includes + run: > + make analyze + COMP=clang + CXX=include-what-you-use + CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/workflows/libcxx17.imp' -Xiwyu --error" diff --git a/src/Makefile b/src/Makefile index e7c06389bdd..1b03bbc2b0a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -837,12 +837,15 @@ ifneq ($(SUPPORTED_ARCH), true) endif -.PHONY: help build profile-build strip install clean net objclean profileclean \ - config-sanity \ +.PHONY: help analyze build profile-build strip install clean net \ + objclean profileclean config-sanity \ icx-profile-use icx-profile-make \ gcc-profile-use gcc-profile-make \ clang-profile-use clang-profile-make FORCE +analyze: net config-sanity objclean + $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS) + build: net config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all diff --git a/src/evaluate.h b/src/evaluate.h index 8ac24daea17..fd1b0de1f6e 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -26,7 +26,6 @@ namespace Stockfish { class Position; -enum Value : int; namespace Eval { diff --git a/src/movegen.h b/src/movegen.h index 6449de25794..b15f1230b13 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -19,7 +19,7 @@ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED -#include +#include // IWYU pragma: keep #include #include "types.h" From fce4cc1829f25fd52c5dd637ab54d867eec065fb Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 19 Sep 2023 18:06:12 +0800 Subject: [PATCH 0222/1309] Make casting styles consistent Make casting styles consistent with the rest of the code. closes https://github.com/official-stockfish/Stockfish/pull/4793 No functional change --- src/bitboard.h | 2 +- src/misc.cpp | 24 ++++++++-------- src/misc.h | 8 +++--- src/movegen.cpp | 2 +- src/nnue/evaluate_nnue.cpp | 6 ++-- src/nnue/layers/affine_transform.h | 2 +- .../layers/affine_transform_sparse_input.h | 2 +- src/search.cpp | 6 ++-- src/syzygy/tbprobe.cpp | 28 +++++++++---------- src/tt.cpp | 20 ++++++------- src/tt.h | 12 ++++---- 11 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index c05b6e3f8cf..dee73b4b3f7 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -264,7 +264,7 @@ inline int popcount(Bitboard b) { #elif defined(_MSC_VER) - return (int)_mm_popcnt_u64(b); + return int(_mm_popcnt_u64(b)); #else // Assumed gcc or compatible compiler diff --git a/src/misc.cpp b/src/misc.cpp index aecc4d23e8c..98e346a6689 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -85,7 +85,7 @@ struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and Tie(std::streambuf* b, std::streambuf* l) : buf(b), logBuf(l) {} int sync() override { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } + int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } int underflow() override { return buf->sgetc(); } int uflow() override { return log(buf->sbumpc(), ">> "); } @@ -98,7 +98,7 @@ struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and if (last == '\n') logBuf->sputn(prefix, 3); - return last = logBuf->sputc((char)c); + return last = logBuf->sputc(char(c)); } }; @@ -215,9 +215,9 @@ std::string compiler_info() { compiler += ")"; #elif defined(__e2k__) && defined(__LCC__) #define dot_ver2(n) \ - compiler += (char)'.'; \ - compiler += (char)('0' + (n) / 10); \ - compiler += (char)('0' + (n) % 10); + compiler += char('.'); \ + compiler += char('0' + (n) / 10); \ + compiler += char('0' + (n) % 10); compiler += "MCST LCC "; compiler += "(version "; @@ -498,13 +498,13 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize if (!hAdvapi32) hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - auto fun6 = (fun6_t)(void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken"); + auto fun6 = fun6_t((void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken")); if (!fun6) return nullptr; - auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"); + auto fun7 = fun7_t((void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); if (!fun7) return nullptr; - auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"); + auto fun8 = fun8_t((void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); if (!fun8) return nullptr; @@ -699,10 +699,10 @@ void bindThisThread(size_t idx) { // Early exit if the needed API are not available at runtime HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); - auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); - auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2"); - auto fun5 = (fun5_t)(void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount"); + auto fun2 = fun2_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); + auto fun3 = fun3_t((void(*)())GetProcAddress(k32, "SetThreadGroupAffinity")); + auto fun4 = fun4_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2")); + auto fun5 = fun5_t((void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount")); if (!fun2 || !fun3) return; diff --git a/src/misc.h b/src/misc.h index aed677b5b29..c0387f7c4c9 100644 --- a/src/misc.h +++ b/src/misc.h @@ -133,13 +133,13 @@ class PRNG { inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #if defined(__GNUC__) && defined(IS_64BIT) __extension__ using uint128 = unsigned __int128; - return ((uint128)a * (uint128)b) >> 64; + return (uint128(a) * uint128(b)) >> 64; #else - uint64_t aL = (uint32_t)a, aH = a >> 32; - uint64_t bL = (uint32_t)b, bH = b >> 32; + uint64_t aL = uint32_t(a), aH = a >> 32; + uint64_t bL = uint32_t(b), bH = b >> 32; uint64_t c1 = (aL * bL) >> 32; uint64_t c2 = aH * bL + c1; - uint64_t c3 = aL * bH + (uint32_t)c2; + uint64_t c3 = aL * bH + uint32_t(c2); return aH * bH + (c2 >> 32) + (c3 >> 32); #endif } diff --git a/src/movegen.cpp b/src/movegen.cpp index f0733c73b66..c6a8dbb8cb7 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -246,7 +246,7 @@ template ExtMove* generate(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate()"); - assert((Type == EVASIONS) == (bool)pos.checkers()); + assert((Type == EVASIONS) == bool(pos.checkers())); Color us = pos.side_to_move(); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 456f2edfdf3..e1fa3b814a1 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -139,7 +139,7 @@ namespace Stockfish::Eval::NNUE { if (!Detail::write_parameters(stream, *featureTransformer)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) if (!Detail::write_parameters(stream, *(network[i]))) return false; - return (bool)stream; + return bool(stream); } void hint_common_parent_position(const Position& pos) { @@ -281,8 +281,8 @@ namespace Stockfish::Eval::NNUE { // A lambda to output one box of the board auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - const int x = ((int)file) * 8; - const int y = (7 - (int)rank) * 3; + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; for (int i = 1; i < 8; ++i) board[y][x+i] = board[y+3][x+i] = '-'; for (int i = 1; i < 3; ++i) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index af85c817c2a..42839bb5bdc 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -310,7 +310,7 @@ namespace Stockfish::Eval::NNUE::Layers { vec_t sum0 = vec_setzero(); const auto row0 = reinterpret_cast(&weights[0]); - for (int j = 0; j < (int)NumChunks; ++j) + for (int j = 0; j < int(NumChunks); ++j) { const vec_t in = inputVector[j]; vec_add_dpbusd_32(sum0, in, row0[j]); diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index c9894f5d96e..1dc42109844 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -102,7 +102,7 @@ namespace Stockfish::Eval::NNUE::Layers { for (IndexType j = 0; j < InputsPerChunk; ++j) { const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; - nnz |= (unsigned)vec_nnz(inputChunk) << (j * InputSimdWidth); + nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); } for (IndexType j = 0; j < OutputsPerChunk; ++j) { diff --git a/src/search.cpp b/src/search.cpp index cae91018931..936aa0db77f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -316,7 +316,7 @@ void Thread::search() { // When playing with strength handicap enable MultiPV search that we will // use behind-the-scenes to retrieve a set of possible moves. if (skill.enabled()) - multiPV = std::max(multiPV, (size_t)4); + multiPV = std::max(multiPV, size_t(4)); multiPV = std::min(multiPV, rootMoves.size()); @@ -1861,7 +1861,7 @@ void MainThread::check_time() { if ( (Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) || (Limits.movetime && elapsed >= Limits.movetime) - || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) + || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) Threads.stop = true; } @@ -1875,7 +1875,7 @@ string UCI::pv(const Position& pos, Depth depth) { TimePoint elapsed = Time.elapsed() + 1; const RootMoves& rootMoves = pos.this_thread()->rootMoves; size_t pvIdx = pos.this_thread()->pvIdx; - size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); + size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); uint64_t nodesSearched = Threads.nodes_searched(); uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index d1b32d242c9..13d271fce8a 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -114,7 +114,7 @@ template T number(void* addr) { T v; - if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) std::memcpy(&v, addr, sizeof(T)); else v = *((T*)addr); @@ -263,7 +263,7 @@ class TBFile : public std::ifstream { exit(EXIT_FAILURE); } - *mapping = (uint64_t)mmap; + *mapping = uint64_t(mmap); *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); if (!*baseAddress) @@ -429,7 +429,7 @@ class TBTables { std::deque> dtzTable; void insert(Key key, TBTable* wdl, TBTable* dtz) { - uint32_t homeBucket = (uint32_t)key & (Size - 1); + uint32_t homeBucket = uint32_t(key) & (Size - 1); Entry entry{ key, wdl, dtz }; // Ensure last element is empty to avoid overflow when looking up @@ -442,7 +442,7 @@ class TBTables { // Robin Hood hashing: If we've probed for longer than this element, // insert here and search for a new spot for the other element instead. - uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1); + uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1); if (otherHomeBucket > homeBucket) { std::swap(entry, hashTable[bucket]); key = otherKey; @@ -456,7 +456,7 @@ class TBTables { public: template TBTable* get(Key key) { - for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) { + for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)]; ; ++entry) { if (entry->key == key || !entry->get()) return entry->get(); } @@ -489,7 +489,7 @@ void TBTables::add(const std::vector& pieces) { file.close(); - MaxCardinality = std::max((int)pieces.size(), MaxCardinality); + MaxCardinality = std::max(int(pieces.size()), MaxCardinality); wdlTable.emplace_back(code); dtzTable.emplace_back(wdlTable.back()); @@ -560,7 +560,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { offset -= d->blockLength[block++] + 1; // Finally, we find the start address of our block of canonical Huffman symbols - uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock)); + uint32_t* ptr = (uint32_t*)(d->data + (uint64_t(block) * d->sizeofBlock)); // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one @@ -600,7 +600,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { if (buf64Size <= 32) { // Refill the buffer buf64Size += 32; - buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); + buf64 |= uint64_t(number(ptr++)) << (64 - buf64Size); } } @@ -1054,22 +1054,22 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { auto flags = e.get(0, f)->flags; if (flags & TBFlag::Mapped) { if (flags & TBFlag::Wide) { - data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table + data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x - e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1); + e.get(0, f)->map_idx[i] = uint16_t((uint16_t*)data - (uint16_t*)e.map + 1); data += 2 * number(data) + 2; } } else { for (int i = 0; i < 4; ++i) { - e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1); + e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1); data += *data + 1; } } } } - return data += (uintptr_t)data & 1; // Word alignment + return data += uintptr_t(data) & 1; // Word alignment } // Populate entry's PairsData records with data from the just memory mapped file. @@ -1110,7 +1110,7 @@ void set(T& e, uint8_t* data) { set_groups(e, e.get(i, f), order[i], f); } - data += (uintptr_t)data & 1; // Word alignment + data += uintptr_t(data) & 1; // Word alignment for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) @@ -1132,7 +1132,7 @@ void set(T& e, uint8_t* data) { for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) { - data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment + data = (uint8_t*)((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment (d = e.get(i, f))->data = data; data += d->blocksNum * d->sizeofBlock; } diff --git a/src/tt.cpp b/src/tt.cpp index 1582121fd6d..adcfe6289a0 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -39,22 +39,22 @@ TranspositionTable TT; // Our global transposition table void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { // Preserve any existing move for the same position - if (m || (uint16_t)k != key16) - move16 = (uint16_t)m; + if (m || uint16_t(k) != key16) + move16 = uint16_t(m); // Overwrite less valuable entries (cheapest checks first) if ( b == BOUND_EXACT - || (uint16_t)k != key16 + || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) { assert(d > DEPTH_OFFSET); assert(d < 256 + DEPTH_OFFSET); - key16 = (uint16_t)k; - depth8 = (uint8_t)(d - DEPTH_OFFSET); - genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); - value16 = (int16_t)v; - eval16 = (int16_t)ev; + key16 = uint16_t(k); + depth8 = uint8_t(d - DEPTH_OFFSET); + genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); + value16 = int16_t(v); + eval16 = int16_t(ev); } } @@ -123,14 +123,14 @@ void TranspositionTable::clear() { TTEntry* TranspositionTable::probe(const Key key, bool& found) const { TTEntry* const tte = first_entry(key); - const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster + const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) { tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh - return found = (bool)tte[i].depth8, &tte[i]; + return found = bool(tte[i].depth8), &tte[i]; } // Find an entry to be replaced according to the replacement strategy diff --git a/src/tt.h b/src/tt.h index df962faaa7b..c11cf085220 100644 --- a/src/tt.h +++ b/src/tt.h @@ -40,12 +40,12 @@ namespace Stockfish { struct TTEntry { - Move move() const { return (Move )move16; } - Value value() const { return (Value)value16; } - Value eval() const { return (Value)eval16; } - Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; } - bool is_pv() const { return (bool)(genBound8 & 0x4); } - Bound bound() const { return (Bound)(genBound8 & 0x3); } + Move move() const { return Move (move16); } + Value value() const { return Value(value16); } + Value eval() const { return Value(eval16); } + Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } + bool is_pv() const { return bool (genBound8 & 0x4); } + Bound bound() const { return Bound(genBound8 & 0x3); } void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); private: From 95fe2b9a9d33811a7fcad1cdfea79c54e8fdb074 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 21 Sep 2023 19:26:11 -0700 Subject: [PATCH 0223/1309] Reduce SIMD register count from 32 to 16 in the case of avx512 and vnni512 archs. Up to 17% speedup, depending on the compiler, e.g. ``` AMD pro 7840u (zen4 phoenix apu 4nm) bash bench_parallel.sh ./stockfish_avx512_gcc13 ./stockfish_avx512_pr_gcc13 20 10 sf_base = 1077737 +/- 8446 (95%) sf_test = 1264268 +/- 8543 (95%) diff = 186531 +/- 4280 (95%) speedup = 17.308% +/- 0.397% (95%) ``` Prior to this patch, it appears gcc spills registers. closes https://github.com/official-stockfish/Stockfish/pull/4796 No functional change --- src/nnue/nnue_feature_transformer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 56442bac9b1..902918b2c6f 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -69,7 +69,7 @@ namespace Stockfish::Eval::NNUE { #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 32 + #define NumRegistersSIMD 16 #define MaxChunkSize 64 #elif USE_AVX2 From 154b8d3ecb19d0b3fa9ec11cc3a1e666dfe0d2ce Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Tue, 19 Sep 2023 09:08:58 +0200 Subject: [PATCH 0224/1309] Remove VALUE_KNOWN_WIN. After removing classic evaluation VALUE_KNOWN_WIN is not anymore returned explicit evaluation. So remove and replace it with VALUE_TB_WIN_IN_MAX_PLY. Measurement on my big bench (bench 16 1 16 pos1000.fen) verifies that at least with current net the calculated evaluation lies always in the open interval (-VALUE_KNOWN_WIN, VALUE_KNOWN_WIN). So i consider this a non-functional change. But to be safe i tested this also at LTC as requested by Stephane Nicolet. STC: https://tests.stockfishchess.org/tests/view/64f9db40eaf01be8259a6ed5 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 455296 W: 115981 L: 116217 D: 223098 Ptnml(0-2): 1415, 50835, 123420, 50527, 1451 LTC: https://tests.stockfishchess.org/tests/view/650bfd867ca0d3f7bbf25feb LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 35826 W: 9170 L: 8973 D: 17683 Ptnml(0-2): 12, 3523, 10645, 3722, 11 closes https://github.com/official-stockfish/Stockfish/pull/4792 Bench: 1603079 --- src/search.cpp | 10 +++++----- src/types.h | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 936aa0db77f..3e19000a5d6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -778,7 +778,7 @@ namespace { && depth < 9 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins + && eval < 24923) // smaller than TB wins return eval; // Step 9. Null move search with verification search (~35 Elo) @@ -908,8 +908,8 @@ namespace { && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta - && abs(ttValue) <= VALUE_KNOWN_WIN - && abs(beta) <= VALUE_KNOWN_WIN) + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY + && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, @@ -1050,7 +1050,7 @@ namespace { && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ - && abs(ttValue) < VALUE_KNOWN_WIN + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { @@ -1541,7 +1541,7 @@ namespace { // Futility pruning and moveCount pruning (~10 Elo) if ( !givesCheck && to_sq(move) != prevSq - && futilityBase > -VALUE_KNOWN_WIN + && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY && type_of(move) != PROMOTION) { if (moveCount > 2) diff --git a/src/types.h b/src/types.h index f81d30fe032..340c47a5fc2 100644 --- a/src/types.h +++ b/src/types.h @@ -161,7 +161,6 @@ enum Bound { enum Value : int { VALUE_ZERO = 0, VALUE_DRAW = 0, - VALUE_KNOWN_WIN = 10000, VALUE_MATE = 32000, VALUE_INFINITE = 32001, VALUE_NONE = 32002, From 70ba9de85cddc5460b1ec53e0a99bee271e26ece Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 12 May 2023 18:07:20 -0400 Subject: [PATCH 0225/1309] Update NNUE architecture to SFNNv8: L1-2560 nn-ac1dbea57aa3.nnue Creating this net involved: - a 6-stage training process from scratch. The datasets used in stages 1-5 were fully minimized. - permuting L1 weights with https://github.com/official-stockfish/nnue-pytorch/pull/254 A strong epoch after each training stage was chosen for the next. The 6 stages were: ``` 1. 400 epochs, lambda 1.0, default LR and gamma UHOx2-wIsRight-multinet-dfrc-n5000 (135G) nodes5000pv2_UHO.binpack data_pv-2_diff-100_nodes-5000.binpack wrongIsRight_nodes5000pv2.binpack multinet_pv-2_diff-100_nodes-5000.binpack dfrc_n5000.binpack 2. 800 epochs, end-lambda 0.75, LR 4.375e-4, gamma 0.995, skip 12 LeelaFarseer-T78juntoaugT79marT80dec.binpack (141G) T60T70wIsRightFarseerT60T74T75T76.binpack test78-junjulaug2022-16tb7p.no-db.min.binpack test79-mar2022-16tb7p.no-db.min.binpack test80-dec2022-16tb7p.no-db.min.binpack 3. 800 epochs, end-lambda 0.725, LR 4.375e-4, gamma 0.995, skip 20 leela93-v1-dfrc99-v2-T78juntosepT80jan-v6dd-T78janfebT79aprT80aprmay.min.binpack leela93-filt-v1.min.binpack dfrc99-16tb7p-filt-v2.min.binpack test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.binpack test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.binpack test78-janfeb2022-16tb7p.min.binpack test79-apr2022-16tb7p.min.binpack test80-apr2022-16tb7p.min.binpack test80-may2022-16tb7p.min.binpack 4. 800 epochs, end-lambda 0.7, LR 4.375e-4, gamma 0.995, skip 24 leela96-dfrc99-v2-T78juntosepT79mayT80junsepnovjan-v6dd-T80mar23-v6-T60novdecT77decT78aprmayT79aprT80may23.min.binpack leela96-filt-v2.min.binpack dfrc99-16tb7p-filt-v2.min.binpack test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.binpack test79-may2022-16tb7p.filter-v6-dd.min.binpack test80-jun2022-16tb7p.filter-v6-dd.min.binpack test80-sep2022-16tb7p.filter-v6-dd.min.binpack test80-nov2022-16tb7p.filter-v6-dd.min.binpack test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.binpack test80-mar2023-2tb7p.v6-sk16.min.binpack test60-novdec2021-16tb7p.min.binpack test77-dec2021-16tb7p.min.binpack test78-aprmay2022-16tb7p.min.binpack test79-apr2022-16tb7p.min.binpack test80-may2023-2tb7p.min.binpack 5. 960 epochs, end-lambda 0.7, LR 4.375e-4, gamma 0.995, skip 28 Increased max-epoch to 960 near the end of the first 800 epochs 5af11540bbfe dataset: https://github.com/official-stockfish/Stockfish/pull/4635 6. 1000 epochs, end-lambda 0.7, LR 4.375e-4, gamma 0.995, skip 28 Increased max-epoch to 1000 near the end of the first 800 epochs 1ee1aba5ed dataset: https://github.com/official-stockfish/Stockfish/pull/4782 ``` L1 weights permuted with: ```bash python3 serialize.py $nnue $nnue_permuted \ --features=HalfKAv2_hm \ --ft_optimize \ --ft_optimize_data=/data/fishpack32.binpack \ --ft_optimize_count=10000 ``` Speed measurements from 100 bench runs at depth 13 with profile-build x86-64-avx2: ``` sf_base = 1329051 +/- 2224 (95%) sf_test = 1163344 +/- 2992 (95%) diff = -165706 +/- 4913 (95%) speedup = -12.46807% +/- 0.370% (95%) ``` Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move (vs. L1-2048 nn-1ee1aba5ed4c.nnue) ep959 : 16.2 +/- 2.3 Failed 10+0.1 STC: https://tests.stockfishchess.org/tests/view/6501beee2cd016da89abab21 LLR: -2.92 (-2.94,2.94) <0.00,2.00> Total: 13184 W: 3285 L: 3535 D: 6364 Ptnml(0-2): 85, 1662, 3334, 1440, 71 Failed 180+1.8 VLTC: https://tests.stockfishchess.org/tests/view/6505cf9a72620bc881ea908e LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 64248 W: 16224 L: 16374 D: 31650 Ptnml(0-2): 26, 6788, 18640, 6650, 20 Passed 60+0.6 th 8 VLTC SMP (STC bounds): https://tests.stockfishchess.org/tests/view/65084a4618698b74c2e541dc LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 90630 W: 23372 L: 23033 D: 44225 Ptnml(0-2): 13, 8490, 27968, 8833, 11 Passed 60+0.6 th 8 VLTC SMP: https://tests.stockfishchess.org/tests/view/6501d45d2cd016da89abacdb LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 137804 W: 35764 L: 35276 D: 66764 Ptnml(0-2): 31, 13006, 42326, 13522, 17 closes https://github.com/official-stockfish/Stockfish/pull/4795 bench 1246812 --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index fd1b0de1f6e..acf9edd2b34 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-1ee1aba5ed4c.nnue" + #define EvalFileDefaultName "nn-ac1dbea57aa3.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index b50c52df31f..2a7f064bbaa 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -38,7 +38,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 2048; +constexpr IndexType TransformedFeatureDimensions = 2560; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; From 22cdb6c1ea1f5ca429333bcbe26706c8b4dd38d7 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 23 Sep 2023 23:26:29 +0200 Subject: [PATCH 0226/1309] Explicitly invoke shell in some cases the permission on the script might be incorrect (zip downloads?). Explicitly invoke the shell closes https://github.com/official-stockfish/Stockfish/pull/4803 No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 1b03bbc2b0a..95f0fe9a577 100644 --- a/src/Makefile +++ b/src/Makefile @@ -108,7 +108,7 @@ ifeq ($(ARCH),) endif ifeq ($(ARCH), native) - override ARCH = $(shell ../scripts/get_native_properties.sh | cut -d " " -f 1) + override ARCH = $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) endif # explicitly check for the list of supported architectures (as listed with make help), From ce99b4b2ef74d09499f35f09bc33102d203791cd Mon Sep 17 00:00:00 2001 From: Jasper Shovelton Date: Fri, 29 Sep 2023 22:02:24 +0200 Subject: [PATCH 0227/1309] Increment minor section number from 3.7.1 to 3.8.1. Pext has nothing to do with git commit sha/date, so separate the two sub-sections. closes https://github.com/official-stockfish/Stockfish/pull/4785 No functional change --- AUTHORS | 1 + src/Makefile | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9314f5cbd33..b1e82806644 100644 --- a/AUTHORS +++ b/AUTHORS @@ -98,6 +98,7 @@ Jake Senne (w1wwwwww) Jan Ondruš (hxim) Jared Kish (Kurtbusch, kurt22i) Jarrod Torriero (DU-jdto) +Jasper Shovelton (Beanie496) Jean-Francois Romang (jromang) Jean Gauthier (OuaisBla) Jekaa diff --git a/src/Makefile b/src/Makefile index 95f0fe9a577..a59303acd79 100644 --- a/src/Makefile +++ b/src/Makefile @@ -703,24 +703,24 @@ ifeq ($(pext),yes) endif endif -### 3.7.1 Try to include git commit sha for versioning +### 3.8.1 Try to include git commit sha for versioning GIT_SHA = $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) ifneq ($(GIT_SHA), ) CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif -### 3.7.2 Try to include git commit date for versioning +### 3.8.2 Try to include git commit date for versioning GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) ifneq ($(GIT_DATE), ) CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif -### 3.7.3 Try to include architecture +### 3.8.3 Try to include architecture ifneq ($(ARCH), ) CXXFLAGS += -DARCH=$(ARCH) endif -### 3.8 Link Time Optimization +### 3.9 Link Time Optimization ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. ifeq ($(optimize),yes) @@ -755,7 +755,7 @@ ifeq ($(debug), no) endif endif -### 3.9 Android 5 can only run position independent executables. Note that this +### 3.10 Android 5 can only run position independent executables. Note that this ### breaks Android 4.0 and earlier. ifeq ($(OS), Android) CXXFLAGS += -fPIE From 9739ed7a97c153c3223b608b24717edbf2dfd7bc Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 23 Sep 2023 13:24:09 +0300 Subject: [PATCH 0228/1309] Simplify pawn count in evaluation This simplifies the evaluation by removing the unnecessary pawn count term when combining nnue and optimism values. Passed STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 61472 W: 15748 L: 15554 D: 30170 Ptnml(0-2): 191, 7123, 15933, 7279, 210 https://tests.stockfishchess.org/tests/view/650c34cf7ca0d3f7bbf264ff Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 81264 W: 20657 L: 20500 D: 40107 Ptnml(0-2): 30, 8713, 22997, 8854, 38 https://tests.stockfishchess.org/tests/view/650cc30efb151d43ae6d5987 closes https://github.com/official-stockfish/Stockfish/pull/4800 Bench: 1530568 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9ca0e4566f6..208e3ed5ba4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -177,7 +177,7 @@ Value Eval::evaluate(const Position& pos) { int npm = pos.non_pawn_material() / 64; v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm + pos.count())) / 1024; + + optimism * (154 + npm )) / 1024; } // Damp down the evaluation linearly when shuffling From 243f7b264a81c2981cec2818b47d609d9d3ca119 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 29 Sep 2023 22:16:57 +0200 Subject: [PATCH 0229/1309] Improve grammar of comments closes https://github.com/official-stockfish/Stockfish/pull/4801 No functional change --- src/bitboard.h | 2 +- src/movegen.h | 5 +++-- src/movepick.cpp | 8 ++++---- src/position.cpp | 17 +++++++++-------- src/search.cpp | 25 +++++++++++++------------ src/types.h | 4 ++-- src/uci.cpp | 6 +++--- 7 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index dee73b4b3f7..eb2f949d5aa 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -209,7 +209,7 @@ template<> inline int distance(Square x, Square y) { return SquareDistan inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -/// attacks_bb(Square) returns the pseudo attacks of the give piece type +/// attacks_bb(Square) returns the pseudo attacks of the given piece type /// assuming an empty board. template diff --git a/src/movegen.h b/src/movegen.h index b15f1230b13..5eee2f1acf8 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -56,8 +56,9 @@ inline bool operator<(const ExtMove& f, const ExtMove& s) { template ExtMove* generate(const Position& pos, ExtMove* moveList); -/// The MoveList struct is a simple wrapper around generate(). It sometimes comes -/// in handy to use this class instead of the low level generate() function. +/// The MoveList struct wraps the generate() function and returns a convenient +/// list of moves. Using MoveList is sometimes preferable to directly calling +/// the lower level generate() function. template struct MoveList { diff --git a/src/movepick.cpp b/src/movepick.cpp index d4f8ab092a8..eea1d49e447 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -55,11 +55,11 @@ namespace { } // namespace -/// Constructors of the MovePicker class. As arguments we pass information -/// to help it to return the (presumably) good moves first, to decide which +/// Constructors of the MovePicker class. As arguments, we pass information +/// to help it return the (presumably) good moves first, to decide which /// moves to return (in the quiescence search, for instance, we only want to -/// search captures, promotions, and some checks) and how important good move -/// ordering is at the current node. +/// search captures, promotions, and some checks) and how important a good +/// move ordering is at the current node. /// MovePicker constructor for the main search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, diff --git a/src/position.cpp b/src/position.cpp index 120677432b6..67dafd8dd78 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -102,8 +102,9 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { } -// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition" -// situations. Description of the algorithm in the following paper: +// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions +// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes to +// allow fast detection of recurring positions. For details see: // http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf // First and second hash functions for indexing the cuckoo tables @@ -549,7 +550,7 @@ bool Position::legal(Move m) const { /// Position::pseudo_legal() takes a random move and tests whether the move is -/// pseudo legal. It is used to validate moves from TT that can be corrupted +/// pseudo-legal. It is used to validate moves from TT that can be corrupted /// due to SMP concurrent access or hash position key aliasing. bool Position::pseudo_legal(const Move m) const { @@ -565,7 +566,7 @@ bool Position::pseudo_legal(const Move m) const { return checkers() ? MoveList< EVASIONS>(*this).contains(m) : MoveList(*this).contains(m); - // Is not a promotion, so promotion piece must be empty + // Is not a promotion, so the promotion piece must be empty assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); // If the 'from' square is not occupied by a piece belonging to the side to @@ -603,7 +604,7 @@ bool Position::pseudo_legal(const Move m) const { { if (type_of(pc) != KING) { - // Double check? In this case a king move is required + // Double check? In this case, a king move is required if (more_than_one(checkers())) return false; @@ -611,7 +612,7 @@ bool Position::pseudo_legal(const Move m) const { if (!(between_bb(square(us), lsb(checkers())) & to)) return false; } - // In case of king moves under check we have to remove king so as to catch + // In case of king moves under check we have to remove the king so as to catch // invalid moves like b1a1 when opposite queen is on c1. else if (attackers_to(to, pieces() ^ from) & pieces(~us)) return false; @@ -1134,7 +1135,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { } else // KING - // If we "capture" with the king but opponent still has attackers, + // If we "capture" with the king but the opponent still has attackers, // reverse the result. return (attackers & ~pieces(stm)) ? res ^ 1 : res; } @@ -1265,7 +1266,7 @@ void Position::flip() { /// Position::pos_is_ok() performs some consistency checks for the -/// position object and raises an asserts if something wrong is detected. +/// position object and raise an assert if something wrong is detected. /// This is meant to be helpful when debugging. bool Position::pos_is_ok() const { diff --git a/src/search.cpp b/src/search.cpp index 3e19000a5d6..9949e2aeb23 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -100,10 +100,12 @@ namespace { return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); } - // Skill structure is used to implement strength limit. If we have an uci_elo then - // we convert it to a suitable fractional skill level using anchoring to CCRL Elo - // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for a match (TC 60+0.6) - // results spanning a wide range of k values. + // Skill structure is used to implement strength limit. + // If we have a UCI_Elo, we convert it to an appropriate skill level, anchored to the Stash engine. + // This method is based on a fit of the Elo results for games played between the master at various + // skill levels and various versions of the Stash engine, all ranked at CCRL. + // Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately + // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c20acedbfe17d618c3c384b339ec struct Skill { Skill(int skill_level, int uci_elo) { if (uci_elo) @@ -272,10 +274,9 @@ void MainThread::search() { void Thread::search() { - // To allow access to (ss-7) up to (ss+2), the stack must be oversized. - // The former is needed to allow update_continuation_histories(ss-1, ...), - // which accesses its argument at ss-6, also near the root. - // The latter is needed for statScore and killer initialization. + // Allocate stack with extra size to allow access from (ss-7) to (ss+2) + // (ss-7) is needed for update_continuation_histories(ss-1, ...) which accesses (ss-6) + // (ss+2) is needed for initialization of statScore and killers Stack stack[MAX_PLY+10], *ss = stack+7; Move pv[MAX_PLY+1]; Value alpha, beta, delta; @@ -362,7 +363,7 @@ void Thread::search() { alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); - // Adjust optimism based on root move's previousScore + // Adjust optimism based on root move's previousScore (~4 Elo) int opt = 109 * prev / (std::abs(prev) + 141); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -721,7 +722,7 @@ namespace { } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (13 Elo) + // 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); eval = ss->staticEval; } @@ -762,7 +763,7 @@ namespace { : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval > (ss-4)->staticEval : true; - // Step 7. Razoring (~1 Elo). + // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. if (eval < alpha - 456 - 252 * depth * depth) @@ -772,7 +773,7 @@ namespace { return value; } - // Step 8. Futility pruning: child node (~40 Elo). + // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 diff --git a/src/types.h b/src/types.h index 340c47a5fc2..f682e764f17 100644 --- a/src/types.h +++ b/src/types.h @@ -40,7 +40,7 @@ #include #if defined(_MSC_VER) -// Disable some silly and noisy warning from MSVC compiler +// Disable some silly and noisy warnings from MSVC compiler #pragma warning(disable: 4127) // Conditional expression is constant #pragma warning(disable: 4146) // Unary minus operator applied to unsigned type #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' @@ -405,7 +405,7 @@ constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -/// Based on a congruential pseudo random number generator +/// Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } diff --git a/src/uci.cpp b/src/uci.cpp index f3e436ef3aa..f62bb8bf4e0 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -159,7 +159,7 @@ namespace { // bench() is called when the engine receives the "bench" command. - // Firstly, a list of UCI commands is set up according to the bench + // First, a list of UCI commands is set up according to the bench // parameters, then it is run one by one, printing a summary at the end. void bench(Position& pos, std::istream& args, StateListPtr& states) { @@ -226,14 +226,14 @@ namespace { // Transform the eval to centipawns with limited range double x = std::clamp(double(v), -4000.0, 4000.0); - // Return the win rate in per mille units rounded to the nearest value + // Return the win rate in per mille units, rounded to the nearest integer return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); } } // namespace -/// UCI::loop() waits for a command from the stdin, parses it and then calls the appropriate +/// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate /// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a /// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, /// like running 'bench', the function returns immediately after the command is executed. From 31d0b7fe932458d6661f4d4c2ce88502086616c5 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 24 Sep 2023 18:09:52 -0700 Subject: [PATCH 0230/1309] Remove unused see_ge() code closes https://github.com/official-stockfish/Stockfish/pull/4805 No functional change --- src/position.cpp | 20 +++++++------------- src/position.h | 1 - 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 67dafd8dd78..a2b377af9e5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1042,7 +1042,7 @@ Key Position::key_after(Move m) const { /// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { +bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); @@ -1061,7 +1061,7 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return true; assert(color_of(piece_on(from)) == sideToMove); - occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic Color stm = sideToMove; Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; @@ -1092,43 +1092,43 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = PawnValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { - occupied ^= least_significant_square_bb(bb); if ((swap = KnightValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { - occupied ^= least_significant_square_bb(bb); if ((swap = BishopValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { - occupied ^= least_significant_square_bb(bb); if ((swap = RookValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { - occupied ^= least_significant_square_bb(bb); if ((swap = QueenValue - swap) < res) break; + occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); @@ -1143,12 +1143,6 @@ bool Position::see_ge(Move m, Bitboard& occupied, Value threshold) const { return bool(res); } -bool Position::see_ge(Move m, Value threshold) const { - Bitboard occupied; - return see_ge(m, occupied, threshold); -} - - /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. diff --git a/src/position.h b/src/position.h index ca7c3ace811..aae4db94a9c 100644 --- a/src/position.h +++ b/src/position.h @@ -135,7 +135,6 @@ class Position { // Static Exchange Evaluation bool see_ge(Move m, Value threshold = VALUE_ZERO) const; - bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; From 4f0fecad8a0f5258114f63f0ac0c905a54d65219 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Mon, 25 Sep 2023 12:24:48 +0200 Subject: [PATCH 0231/1309] Use C++17 variable templates for type traits The C++17 variable templates are slightly more readable and allow us to remove the typename keyword in a few cases. closes https://github.com/official-stockfish/Stockfish/pull/4806 No functional change --- src/movepick.h | 4 ++-- src/nnue/nnue_common.h | 4 ++-- src/syzygy/tbprobe.cpp | 4 ++-- src/tune.h | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 5243f89cf2c..dd9de0b2161 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -24,7 +24,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #include "movegen.h" #include "types.h" @@ -70,7 +70,7 @@ struct Stats : public std::array, Size> void fill(const T& v) { // For standard-layout 'this' points to first struct member - assert(std::is_standard_layout::value); + assert(std::is_standard_layout_v); using entry = StatsEntry; entry* p = reinterpret_cast(this); diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index a42a86c980d..e159c5dc3da 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -103,7 +103,7 @@ namespace Stockfish::Eval::NNUE { else { std::uint8_t u[sizeof(IntType)]; - typename std::make_unsigned::type v = 0; + std::make_unsigned_t v = 0; stream.read(reinterpret_cast(u), sizeof(IntType)); for (std::size_t i = 0; i < sizeof(IntType); ++i) @@ -128,7 +128,7 @@ namespace Stockfish::Eval::NNUE { else { std::uint8_t u[sizeof(IntType)]; - typename std::make_unsigned::type v = value; + std::make_unsigned_t v = value; std::size_t i = 0; // if constexpr to silence the warning about shift by 8 diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 13d271fce8a..ffe29ce1626 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -102,7 +102,7 @@ constexpr Value WDL_to_value[] = { template inline void swap_endian(T& x) { - static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); + static_assert(std::is_unsigned_v, "Argument of swap_endian not unsigned"); uint8_t tmp, *c = (uint8_t*)&x; for (int i = 0; i < Half; ++i) @@ -332,7 +332,7 @@ struct PairsData { // first access, when the corresponding file is memory mapped. template struct TBTable { - using Ret = typename std::conditional::type; + using Ret = std::conditional_t; static constexpr int Sides = Type == WDL ? 2 : 1; diff --git a/src/tune.h b/src/tune.h index 3e94f7efc6c..dde03b324ea 100644 --- a/src/tune.h +++ b/src/tune.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include // IWYU pragma: keep #include #include @@ -96,11 +96,11 @@ class Tune { template struct Entry : public EntryBase { - static_assert(!std::is_const::value, "Parameter cannot be const!"); + static_assert(!std::is_const_v, "Parameter cannot be const!"); - static_assert( std::is_same::value - || std::is_same::value - || std::is_same::value, "Parameter type not supported!"); + static_assert( std::is_same_v + || std::is_same_v + || std::is_same_v, "Parameter type not supported!"); Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} void operator=(const Entry&) = delete; // Because 'value' is a reference From 660da1ca7b4c2c03dce03d14ef3496d9fb4aead2 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Mon, 25 Sep 2023 13:18:05 +0800 Subject: [PATCH 0232/1309] Skip moves-loop pruning in qsearch if we have only pawns At first my idea was only to cover movecount and futility pruning, but @peregrineshahin suggested to test it on all moves-loop pruning and it worked. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 167968 W: 42970 L: 42480 D: 82518 Ptnml(0-2): 444, 18324, 46002, 18726, 488 https://tests.stockfishchess.org/tests/view/6511181a55b420c569d0d54c Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 40794 W: 10496 L: 10182 D: 20116 Ptnml(0-2): 12, 4021, 12025, 4319, 20 https://tests.stockfishchess.org/tests/view/6512ccc4b3e74811c8aee86c closes https://github.com/official-stockfish/Stockfish/pull/4809 Bench: 1338472 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9949e2aeb23..97b70b8fefd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1427,6 +1427,7 @@ namespace { Value bestValue, value, ttValue, futilityValue, futilityBase; bool pvHit, givesCheck, capture; int moveCount; + Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) @@ -1537,7 +1538,7 @@ namespace { moveCount++; // Step 6. Pruning. - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) if ( !givesCheck From afe7f4d9b0c5e1a1aa224484d2cd9e04c7f099b9 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 27 Sep 2023 20:58:28 -0400 Subject: [PATCH 0233/1309] Update default net to nn-0000000000a0.nnue This is a later epoch from the same experiment that led to the previous master net. In training stage 6, max-epoch was raised to 1,200 near the end of the first 1,000 epochs. For more details, see https://github.com/official-stockfish/Stockfish/pull/4795 Local elo at 25k nodes per move (vs. L1-2048 nn-1ee1aba5ed4c.nnue) ep1079 : 15.6 +/- 1.2 Passed STC: https://tests.stockfishchess.org/tests/view/651503b3b3e74811c8af1e2a LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 29408 W: 7607 L: 7304 D: 14497 Ptnml(0-2): 97, 3277, 7650, 3586, 94 Passed LTC: https://tests.stockfishchess.org/tests/view/651585ceb3e74811c8af2a5f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 73164 W: 18828 L: 18440 D: 35896 Ptnml(0-2): 30, 7749, 20644, 8121, 38 closes https://github.com/official-stockfish/Stockfish/pull/4810 Bench: 1453057 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index acf9edd2b34..26f2fc4f985 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ namespace Eval { // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-ac1dbea57aa3.nnue" + #define EvalFileDefaultName "nn-0000000000a0.nnue" namespace NNUE { From 8a912951de6d4bff78d3ff5258213a0c7e6f494e Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 24 Sep 2023 15:15:50 -0700 Subject: [PATCH 0234/1309] Remove handcrafted MMX code too small a benefit to maintain this old target closes https://github.com/official-stockfish/Stockfish/pull/4804 No functional change --- src/Makefile | 5 ++-- src/misc.cpp | 3 --- src/nnue/layers/affine_transform.h | 32 +---------------------- src/nnue/layers/clipped_relu.h | 18 ------------- src/nnue/layers/simd.h | 3 --- src/nnue/nnue_common.h | 6 ----- src/nnue/nnue_feature_transformer.h | 40 ----------------------------- 7 files changed, 3 insertions(+), 104 deletions(-) diff --git a/src/Makefile b/src/Makefile index a59303acd79..5b43c35fdd7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -674,7 +674,6 @@ ifeq ($(sse2),yes) endif ifeq ($(mmx),yes) - CXXFLAGS += -DUSE_MMX ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) CXXFLAGS += -mmmx endif @@ -794,11 +793,11 @@ help: @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" @echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" @echo "x86-64-ssse3 > x86 64-bit with ssse3 support" - @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support" + @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support" @echo "x86-64 > x86 64-bit generic (with sse2 support)" @echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" @echo "x86-32-sse2 > x86 32-bit with sse2 support" - @echo "x86-32 > x86 32-bit generic (with mmx and sse support)" + @echo "x86-32 > x86 32-bit generic (with mmx compile support)" @echo "ppc-64 > PPC 64-bit" @echo "ppc-32 > PPC 32-bit" @echo "armv7 > ARMv7 32-bit" diff --git a/src/misc.cpp b/src/misc.cpp index 98e346a6689..2f6ffd28e70 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -282,9 +282,6 @@ std::string compiler_info() { compiler += " SSE2"; #endif compiler += (HasPopCnt ? " POPCNT" : ""); - #if defined(USE_MMX) - compiler += " MMX"; - #endif #if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; #elif defined(USE_NEON) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 42839bb5bdc..fc65c34339f 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -45,18 +45,13 @@ namespace Stockfish::Eval::NNUE::Layers { template static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) { -# if defined(USE_SSE2) || defined(USE_MMX) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) +# if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) # if defined(USE_SSE2) // At least a multiple of 16, with SSE2. constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const __m128i Zeros = _mm_setzero_si128(); const auto inputVector = reinterpret_cast(input); -# elif defined(USE_MMX) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 8; - const __m64 Zeros = _mm_setzero_si64(); - const auto inputVector = reinterpret_cast(input); - # elif defined(USE_NEON_DOTPROD) constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const auto inputVector = reinterpret_cast(input); @@ -92,26 +87,6 @@ namespace Stockfish::Eval::NNUE::Layers { sum = _mm_add_epi32(sum, sum_second_32); output[i] = _mm_cvtsi128_si32(sum); -# elif defined(USE_MMX) - __m64 sumLo = _mm_cvtsi32_si64(biases[i]); - __m64 sumHi = Zeros; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - __m64 row_j = row[j]; - __m64 input_j = inputVector[j]; - __m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8); - __m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8); - __m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros); - __m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros); - __m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo); - __m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi); - sumLo = _mm_add_pi32(sumLo, productLo); - sumHi = _mm_add_pi32(sumHi, productHi); - } - __m64 sum = _mm_add_pi32(sumLo, sumHi); - sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); - output[i] = _mm_cvtsi64_si32(sum); - # elif defined(USE_NEON_DOTPROD) int32x4_t sum = {biases[i]}; const auto row = reinterpret_cast(&weights[offset]); @@ -132,11 +107,6 @@ namespace Stockfish::Eval::NNUE::Layers { # endif } - -# if defined(USE_MMX) - _mm_empty(); -# endif - # else std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index aab824b3572..48cd6c69345 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -135,24 +135,6 @@ namespace Stockfish::Eval::NNUE::Layers { } constexpr IndexType Start = NumChunks * SimdWidth; - #elif defined(USE_MMX) - constexpr IndexType NumChunks = InputDimensions / SimdWidth; - const __m64 k0x80s = _mm_set1_pi8(-128); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m64*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m64 words0 = _mm_srai_pi16( - _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), - WeightScaleBits); - const __m64 words1 = _mm_srai_pi16( - _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), - WeightScaleBits); - const __m64 packedbytes = _mm_packs_pi16(words0, words1); - out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); - } - _mm_empty(); - constexpr IndexType Start = NumChunks * SimdWidth; - #elif defined(USE_NEON) constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); const int8x8_t Zero = {0}; diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index f478cd7819f..349217edb7a 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -31,9 +31,6 @@ #elif defined(USE_SSE2) # include -#elif defined(USE_MMX) -# include - #elif defined(USE_NEON) # include #endif diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index e159c5dc3da..779f4e75555 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -42,9 +42,6 @@ #elif defined(USE_SSE2) #include -#elif defined(USE_MMX) -#include - #elif defined(USE_NEON) #include #endif @@ -71,9 +68,6 @@ namespace Stockfish::Eval::NNUE { #elif defined(USE_SSE2) constexpr std::size_t SimdWidth = 16; - #elif defined(USE_MMX) - constexpr std::size_t SimdWidth = 8; - #elif defined(USE_NEON) constexpr std::size_t SimdWidth = 16; #endif diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 902918b2c6f..77a175f50c9 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -117,34 +117,6 @@ namespace Stockfish::Eval::NNUE { #define NumRegistersSIMD (Is64Bit ? 16 : 8) #define MaxChunkSize 16 - #elif USE_MMX - using vec_t = __m64; - using psqt_vec_t = __m64; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) _mm_add_pi16(a,b) - #define vec_sub_16(a,b) _mm_sub_pi16(a,b) - #define vec_mul_16(a,b) _mm_mullo_pi16(a,b) - #define vec_zero() _mm_setzero_si64() - #define vec_set_16(a) _mm_set1_pi16(a) - inline vec_t vec_max_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b)); - } - inline vec_t vec_min_16(vec_t a,vec_t b){ - vec_t comparison = _mm_cmpgt_pi16(a,b); - return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a)); - } - #define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7)) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) _mm_add_pi32(a,b) - #define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b) - #define vec_zero_psqt() _mm_setzero_si64() - #define vec_cleanup() _mm_empty() - #define NumRegistersSIMD 8 - #define MaxChunkSize 8 - #elif USE_NEON using vec_t = int16x8_t; using psqt_vec_t = int32x4_t; @@ -335,10 +307,6 @@ namespace Stockfish::Eval::NNUE { #endif } -#if defined(vec_cleanup) - vec_cleanup(); -#endif - return psqt; } // end of function transform() @@ -529,10 +497,6 @@ namespace Stockfish::Eval::NNUE { } } #endif - - #if defined(USE_MMX) - _mm_empty(); - #endif } template @@ -613,10 +577,6 @@ namespace Stockfish::Eval::NNUE { accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } #endif - - #if defined(USE_MMX) - _mm_empty(); - #endif } template From f1ce1cd4751a098b7ee09e304fa6397d08fe8d7f Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 30 Sep 2023 08:11:14 +0200 Subject: [PATCH 0235/1309] Update links in license matches https://www.gnu.org/licenses/gpl-3.0.txt closes https://github.com/official-stockfish/Stockfish/pull/4813 No functional change --- Copying.txt | 1348 +++++++++++++++++++++++++-------------------------- 1 file changed, 674 insertions(+), 674 deletions(-) diff --git a/Copying.txt b/Copying.txt index 818433ecc0e..f288702d2fa 100644 --- a/Copying.txt +++ b/Copying.txt @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 040dfedb3457ca6971d98c754362cde4dc767aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sun, 1 Oct 2023 20:19:49 +0200 Subject: [PATCH 0236/1309] Remove one test in the move loop Simplification passed STC test: https://tests.stockfishchess.org/tests/view/6519fc91cff46e538ee014f6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 191264 W: 48550 L: 48501 D: 94213 Ptnml(0-2): 576, 21529, 51392, 21540, 595 closes #4815 Non functional change --- src/search.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 97b70b8fefd..5508478862f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -945,18 +945,17 @@ namespace { if (move == excludedMove) continue; + // Check for legality + if (!pos.legal(move)) + continue; + // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence, any illegal move is also skipped. In MultiPV - // mode we also skip PV moves that have been already searched and those - // of lower "TB rank" if we are in a TB root position. + // Move List. In MultiPV mode we also skip PV moves that have been already + // searched and those of lower "TB rank" if we are in a TB root position. if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, thisThread->rootMoves.begin() + thisThread->pvLast, move)) continue; - // Check for legality - if (!rootNode && !pos.legal(move)) - continue; - ss->moveCount = ++moveCount; if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) From c17a657b045d4dc720c8c36558fe649a1c3f4a05 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 30 Sep 2023 23:12:02 -0700 Subject: [PATCH 0237/1309] Optimize the most common update accumalator cases w/o tiling In the most common case where we only update a single state it's faster to not use temporary accumulation registers and tiling. (Also includes a couple of small cleanups.) passed STC https://tests.stockfishchess.org/tests/view/651918e3cff46e538ee0023b LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 34944 W: 8989 L: 8687 D: 17268 Ptnml(0-2): 88, 3743, 9512, 4037, 92 A simpler version https://tests.stockfishchess.org/tests/view/65190dfacff46e538ee00155 also passed but this version is stronger still https://tests.stockfishchess.org/tests/view/6519b95fcff46e538ee00fa2 closes https://github.com/official-stockfish/Stockfish/pull/4816 No functional change --- src/misc.h | 1 + src/nnue/nnue_feature_transformer.h | 176 +++++++++++++++++++--------- 2 files changed, 120 insertions(+), 57 deletions(-) diff --git a/src/misc.h b/src/misc.h index c0387f7c4c9..52595fb906b 100644 --- a/src/misc.h +++ b/src/misc.h @@ -87,6 +87,7 @@ class ValueList { void push_back(const T& value) { values_[size_++] = value; } const T* begin() const { return values_; } const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } private: T values_[MaxSize]; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 77a175f50c9..25f686dacbc 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -370,13 +370,13 @@ namespace Stockfish::Eval::NNUE { while (states_to_update[i] == nullptr) --i; - StateInfo *st2 = states_to_update[i]; + StateInfo* st2 = states_to_update[i]; for (; i >= 0; --i) { states_to_update[i]->accumulator.computed[Perspective] = true; - StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; for (; st2 != end_state; st2 = st2->previous) FeatureSet::append_changed_indices( @@ -388,78 +388,140 @@ namespace Stockfish::Eval::NNUE { // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + + if ( states_to_update[1] == nullptr + && (removed[0].size() == 1 || removed[0].size() == 2) + && added[0].size() == 1) { - // Load accumulator - auto accTile = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTile[k]); + assert(states_to_update[0]); - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) + auto accTileIn = reinterpret_cast( + &st->accumulator.accumulation[Perspective][0]); + auto accTileOut = reinterpret_cast( + &states_to_update[0]->accumulator.accumulation[Perspective][0]); + + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); + + if (removed[0].size() == 1) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) + accTileOut[k] = vec_add_16(vec_sub_16(accTileIn[k], columnR0[k]), columnA[k]); } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); + + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) + accTileOut[k] = vec_sub_16( + vec_add_16(accTileIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); + } + + auto accTilePsqtIn = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][0]); + auto accTilePsqtOut = reinterpret_cast( + &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); - // Difference calculation for the activated features - for (const auto index : added[i]) + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; + auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); + const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; + auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); + + if (removed[0].size() == 1) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) + accTilePsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( + accTilePsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); } + else + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; + auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); - // Store accumulator - accTile = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTile[k], acc[k]); - } + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) + accTilePsqtOut[k] = vec_sub_psqt_32( + vec_add_psqt_32(accTilePsqtIn[k], columnPsqtA[k]), + vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); + } } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + else { - // Load accumulator - auto accTilePsqt = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqt[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) - { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + // Load accumulator + auto accTileIn = reinterpret_cast( + &st->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); + } } - // Difference calculation for the activated features - for (const auto index : added[i]) + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + // Load accumulator + auto accTilePsqtIn = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + // Store accumulator + auto accTilePsqtOut = reinterpret_cast( + &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + } } - - // Store accumulator - accTilePsqt = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } } - #else for (IndexType i = 0; states_to_update[i]; ++i) { From 008d59512ac38e1e4a2f7880fe4e07b902845bb0 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 30 Sep 2023 06:57:39 +0200 Subject: [PATCH 0238/1309] Simplify collection of bad moves for history updates. 1. collect only the first 32 moves searched and ignore the rest. So late bad moves get no further negative history updates. 2. collect now for quiet moves also at most 32 bad moves STC: https://tests.stockfishchess.org/tests/view/6517b3aeb3e74811c8af5651 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 51168 W: 13013 L: 12810 D: 25345 Ptnml(0-2): 120, 6006, 13186, 6095, 177 LTC: https://tests.stockfishchess.org/tests/view/651adafecff46e538ee02734 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 109866 W: 27786 L: 27656 D: 54424 Ptnml(0-2): 52, 11816, 31069, 11942, 54 closes https://github.com/official-stockfish/Stockfish/pull/4818 Bench: 1338617 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5508478862f..f49c23aed65 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -546,7 +546,7 @@ namespace { assert(0 < depth && depth < MAX_PLY); assert(!(PvNode && cutNode)); - Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; + Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[32]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); @@ -1328,12 +1328,12 @@ namespace { // If the move is worse than some previously searched move, remember it, to update its stats later - if (move != bestMove) + if (move != bestMove && moveCount <= 32) { - if (capture && captureCount < 32) + if (capture) capturesSearched[captureCount++] = move; - else if (!capture && quietCount < 64) + else quietsSearched[quietCount++] = move; } } From 25d444ed60e3873c02a70525776b145f03833103 Mon Sep 17 00:00:00 2001 From: candirufish <38038147+candirufish@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:46:03 +0200 Subject: [PATCH 0239/1309] Razor more if ss+1 cutoffCnt > 3 STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 221760 W: 56726 L: 56144 D: 108890 Ptnml(0-2): 655, 25453, 58123, 25953, 696 https://tests.stockfishchess.org/tests/view/651d34dbcff46e538ee05d91 LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 130326 W: 33188 L: 32681 D: 64457 Ptnml(0-2): 69, 13949, 36620, 14456, 69 https://tests.stockfishchess.org/tests/view/651f844eac577114367273d5 closes https://github.com/official-stockfish/Stockfish/pull/4822 bench: 1291708 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f49c23aed65..a5b5c101e69 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -766,7 +766,8 @@ namespace { // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 456 - 252 * depth * depth) + // Adjust razor margin according to cutoffCnt. (~1 Elo) + if (eval < alpha - 456 - (252 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) From f7fbc6880efae2ec9d97d6f1d65e2ad00547e32c Mon Sep 17 00:00:00 2001 From: gabe Date: Fri, 6 Oct 2023 22:59:22 +0200 Subject: [PATCH 0240/1309] Avoid recomputing moveCountPruning In search, when moveCountPruning becomes true, it can never turn false again. Passed STC https://tests.stockfishchess.org/tests/view/652075ceac57711436728aac LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 136448 W: 34923 L: 34472 D: 67053 Ptnml(0-2): 420, 15094, 36767, 15501, 442 closes https://github.com/official-stockfish/Stockfish/pull/4823 Non functional change --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a5b5c101e69..69d8decff36 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -984,7 +984,8 @@ namespace { && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - moveCountPruning = moveCount >= futility_move_count(improving, depth); + if (!moveCountPruning) + moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search int lmrDepth = newDepth - r; From 7a4de96159f76f2465d474d76e08a1c8ca3383b8 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Sat, 7 Oct 2023 23:25:34 +0200 Subject: [PATCH 0241/1309] Skip futility pruning if ttMove has bad history Passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 52416 W: 13465 L: 13128 D: 25823 Ptnml(0-2): 128, 6024, 13604, 6287, 165 https://tests.stockfishchess.org/tests/view/651fadd4ac577114367277bf Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 87348 W: 22234 L: 21818 D: 43296 Ptnml(0-2): 38, 9240, 24698, 9664, 34 https://tests.stockfishchess.org/tests/view/65201932ac57711436728218 closes https://github.com/official-stockfish/Stockfish/pull/4825 bench: 1246560 --- AUTHORS | 1 + src/search.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index b1e82806644..d7b64b62e8c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -210,6 +210,7 @@ Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Stephen Touset (stouset) Syine Mineta (MinetaS) +Taras Vuk (TarasVuk) Thanar2 thaspel theo77186 diff --git a/src/search.cpp b/src/search.cpp index 69d8decff36..8510651360c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -780,7 +780,10 @@ namespace { && depth < 9 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta && eval >= beta - && eval < 24923) // smaller than TB wins + && eval < 24923 // smaller than TB wins + && !( !ttCapture + && ttMove + && thisThread->mainHistory[us][from_to(ttMove)] < 989)) return eval; // Step 9. Null move search with verification search (~35 Elo) From 002636362e175134c6d0d53b332b527ec4a12db0 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 10 Oct 2023 17:43:36 +0200 Subject: [PATCH 0242/1309] Search parameters tune at 180+1.8 Passed VLTC: https://tests.stockfishchess.org/tests/view/65200c58ac577114367280bc LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 146180 W: 37407 L: 36988 D: 71785 Ptnml(0-2): 21, 14474, 43675, 14905, 15 Passed VLTC SMP: https://tests.stockfishchess.org/tests/view/652403da3125598fc7eb4b6d LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 57580 W: 15061 L: 14739 D: 27780 Ptnml(0-2): 2, 5001, 18460, 5327, 0 closes https://github.com/official-stockfish/Stockfish/pull/4826 Bench: 1099336 --- src/search.cpp | 78 +++++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8510651360c..71332e506df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -73,7 +73,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((140 - 40 * noTtCutNode) * (d - improving)); + return Value((126 - 42 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -81,8 +81,8 @@ namespace { Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 - + (!i && reductionScale > 936); + return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 + + (!i && reductionScale > 791); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -92,7 +92,7 @@ namespace { // History and stats update bonus, based on depth int stat_bonus(Depth d) { - return std::min(336 * d - 547, 1561); + return std::min(334 * d - 531, 1538); } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -174,7 +174,7 @@ namespace { void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.57 + std::log(Threads.size()) / 2) * std::log(i)); + Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -359,12 +359,12 @@ void Thread::search() { // Reset aspiration window starting size Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 15799; + delta = Value(10) + int(prev) * prev / 17470; alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust optimism based on root move's previousScore (~4 Elo) - int opt = 109 * prev / (std::abs(prev) + 141); + int opt = 113 * prev / (std::abs(prev) + 109); optimism[ us] = Value(opt); optimism[~us] = -optimism[us]; @@ -750,7 +750,7 @@ namespace { // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { - int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1817, 1817); + int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1812, 1812); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } @@ -767,7 +767,7 @@ namespace { // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 456 - (252 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 492 - (257 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -778,9 +778,9 @@ namespace { // The depth condition is important for mate finding. if ( !ss->ttPv && depth < 9 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 306 >= beta + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 321 >= beta && eval >= beta - && eval < 24923 // smaller than TB wins + && eval < 29462 // smaller than TB wins && !( !ttCapture && ttMove && thisThread->mainHistory[us][from_to(ttMove)] < 989)) @@ -789,10 +789,10 @@ namespace { // Step 9. Null move search with verification search (~35 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 17329 + && (ss-1)->statScore < 17257 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 21 * depth + 258 + && ss->staticEval >= beta - 24 * depth + 281 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly @@ -801,7 +801,7 @@ namespace { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 173, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -850,7 +850,7 @@ namespace { && !ttMove) depth -= 2; - probCutBeta = beta + 168 - 61 * improving; + probCutBeta = beta + 168 - 70 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -906,7 +906,7 @@ namespace { moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 413; + probCutBeta = beta + 416; if ( ss->inCheck && !PvNode && ttCapture @@ -1000,12 +1000,12 @@ namespace { if ( !givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) continue; // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-205) * depth)) + if (!pos.see_ge(move, Value(-185) * depth)) continue; } else @@ -1016,24 +1016,24 @@ namespace { // Continuation history based pruning (~2 Elo) if ( lmrDepth < 6 - && history < -3832 * depth) + && history < -3232 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7011; + lmrDepth += history / 5793; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) if ( !ss->inCheck - && lmrDepth < 12 - && ss->staticEval + 112 + 138 * lmrDepth <= alpha) + && lmrDepth < 13 + && ss->staticEval + 115 + 122 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-31 * lmrDepth * lmrDepth))) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) continue; } } @@ -1051,7 +1051,7 @@ namespace { // scaling. Their values are optimized to time controls of 180+1.8 and longer // so changing them requires tests at this type of time controls. if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search /* && ttValue != VALUE_NONE Already implicit in the next condition */ @@ -1059,7 +1059,7 @@ namespace { && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (82 + 65 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; @@ -1073,11 +1073,11 @@ namespace { // Avoid search explosion by limiting the number of double extensions if ( !PvNode - && value < singularBeta - 21 + && value < singularBeta - 18 && ss->doubleExtensions <= 11) { extension = 2; - depth += depth < 13; + depth += depth < 15; } } @@ -1095,7 +1095,7 @@ namespace { // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) else if (cutNode) - extension = depth < 17 ? -3 : -1; + extension = depth < 19 ? -2 : -1; // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) else if (ttValue <= value) @@ -1111,7 +1111,7 @@ namespace { else if ( PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 5168) + && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) extension = 1; } @@ -1139,7 +1139,7 @@ namespace { r -= cutNode && tte->depth() >= depth ? 3 : 2; // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 8) + if ((ss-1)->moveCount > 7) r--; // Increase reduction for cut nodes (~3 Elo) @@ -1175,10 +1175,10 @@ namespace { + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)] - - 4006; + - 3848; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (11124 + 4740 * (depth > 5 && depth < 22)); + r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1202,8 +1202,8 @@ namespace { { // 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 + 64 + 11 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6; + const bool doDeeperSearch = value > (bestValue + 51 + 10 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; @@ -1321,8 +1321,8 @@ namespace { // Reduce other moves if we have found at least one score improvement (~2 Elo) if ( depth > 2 && depth < 12 - && beta < 14362 - && value > -12393) + && beta < 13828 + && value > -11369) depth -= 2; assert(depth > 0); @@ -1371,7 +1371,7 @@ namespace { // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 800) + ((ss-1)->moveCount > 12); + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) + ((ss-1)->moveCount > 11); update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; } @@ -1593,7 +1593,7 @@ namespace { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-95))) + if (!pos.see_ge(move, Value(-90))) continue; } @@ -1726,7 +1726,7 @@ namespace { if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 145 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 38e830af4bfa6c9e9c11279a8e6a60b6ca4ec2cd Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 14 Oct 2023 17:41:41 +0300 Subject: [PATCH 0243/1309] Use more continuation histories. This patch allows stats updates and movepicker bonuses for continuation history 3 plies deep - so counter counter move. Updates and movepicker usage are done with 1/4 multiplier compared to other histories. Passed STC: https://tests.stockfishchess.org/tests/view/6528f28d3125598fc7ebb5a3 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 161344 W: 41369 L: 40868 D: 79107 Ptnml(0-2): 535, 18720, 41679, 19185, 553 Passed LTC: https://tests.stockfishchess.org/tests/view/652a397a3125598fc7ebd1d6 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 48564 W: 12556 L: 12215 D: 23793 Ptnml(0-2): 25, 5149, 13595, 5486, 27 closes https://github.com/official-stockfish/Stockfish/pull/4827 bench 1327410 --- src/movepick.cpp | 1 + src/search.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index eea1d49e447..bc3fcf7ed6b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -141,6 +141,7 @@ void MovePicker::score() { m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[2])[pc][to] / 4; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; diff --git a/src/search.cpp b/src/search.cpp index 71332e506df..a1834ab99b3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -918,7 +918,7 @@ namespace { return probCutBeta; const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - nullptr , (ss-4)->continuationHistory, + (ss-3)->continuationHistory, (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; @@ -1511,7 +1511,7 @@ namespace { } const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - nullptr , (ss-4)->continuationHistory, + (ss-3)->continuationHistory, (ss-4)->continuationHistory, nullptr , (ss-6)->continuationHistory }; // Initialize a MovePicker object for the current position, and prepare @@ -1768,13 +1768,13 @@ namespace { void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - for (int i : {1, 2, 4, 6}) + for (int i : {1, 2, 3, 4, 6}) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (is_ok((ss-i)->currentMove)) - (*(ss-i)->continuationHistory)[pc][to] << bonus; + (*(ss-i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } } From a4fedd8152e717a37ec8b8ddda0658364c1f5ee4 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Thu, 27 Jul 2023 06:21:54 +0300 Subject: [PATCH 0244/1309] Fix greater than TB scores in null move pruning. This patch is a simplification and a fix to dealing with null moves scores that returns proven mates or TB scores by preventing 'null move pruning' if the nullvalue is in that range. Current solution downgrades nullValues on the non-PV node but the value can be used in a transposed PV-node to the same position afterwards (Triangulation), the later is prone to propagate a wrong score (96.05) to root that will not be refuted unless we search further. Score of (96.05) can be obtained be two methods, maxim static-eval returned on Pv update (mostly qSearch) this downgrade (clamp) in NMP and theoretically can happen with or without TBs but the second scenario is more dangerous than the first. This fixes the reproducible case in very common scenarios with TBs as shown in the debugging at discord. Fixes: #4699 Passed STC: https://tests.stockfishchess.org/tests/view/64c1eca8dc56e1650abba6f9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 670048 W: 171132 L: 171600 D: 327316 Ptnml(0-2): 2134, 75687, 179820, 75279, 2104 Passed LTC: https://tests.stockfishchess.org/tests/view/64c5e130dc56e1650abc0438 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 92868 W: 23642 L: 23499 D: 45727 Ptnml(0-2): 52, 9509, 27171, 9648, 54 closes https://github.com/official-stockfish/Stockfish/pull/4715 Bench: 1327410 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a1834ab99b3..1b019a08e82 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -812,11 +812,9 @@ namespace { pos.undo_null_move(); - if (nullValue >= beta) + // Do not return unproven mate or TB scores + if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { - // Do not return unproven mate or TB scores - nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1); - if (thisThread->nmpMinPly || depth < 14) return nullValue; From fe53a18f7a149f7e6d1a9dde8a7478692ef82997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sun, 1 Oct 2023 19:18:05 +0200 Subject: [PATCH 0245/1309] Reformat some comments and conditions closes https://github.com/official-stockfish/Stockfish/pull/4814 No functional change --- src/movegen.cpp | 5 +-- src/position.cpp | 18 +++++----- src/search.cpp | 88 ++++++++++++++++++++++++------------------------ 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index c6a8dbb8cb7..cda43b3a582 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -236,9 +236,10 @@ namespace { /// Generates all pseudo-legal captures plus queen promotions /// Generates all pseudo-legal non-captures and underpromotions -/// Generates all pseudo-legal check evasions when the side to move is in check -/// Generates all pseudo-legal non-captures giving check, except castling and promotions +/// Generates all pseudo-legal check evasions /// Generates all pseudo-legal captures and non-captures +/// Generates all pseudo-legal non-captures giving check, +/// except castling and promotions /// /// Returns a pointer to the end of the move list. diff --git a/src/position.cpp b/src/position.cpp index a2b377af9e5..0d7d957141f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -102,9 +102,9 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { } -// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions -// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes to -// allow fast detection of recurring positions. For details see: +// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions +// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes +// to allow fast detection of recurring positions. For details see: // http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf // First and second hash functions for indexing the cuckoo tables @@ -188,9 +188,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th 4) En passant target square (in algebraic notation). If there's no en passant target square, this is "-". If a pawn has just made a 2-square move, this - is the position "behind" the pawn. Following X-FEN standard, this is recorded only - if there is a pawn in position to make an en passant capture, and if there really - is a pawn that might have advanced two squares. + is the position "behind" the pawn. Following X-FEN standard, this is recorded + only if there is a pawn in position to make an en passant capture, and if + there really is a pawn that might have advanced two squares. 5) Halfmove clock. This is the number of halfmoves since the last pawn advance or capture. This is used to determine if a draw can be claimed under the @@ -587,8 +587,8 @@ bool Position::pseudo_legal(const Move m) const { return false; if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !( (from + 2 * pawn_push(us) == to) // Not a double push + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !( (from + 2 * pawn_push(us) == to) // Not a double push && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) @@ -959,7 +959,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ // Remove both pieces first since squares could overlap in Chess960 remove_piece(Do ? from : to); remove_piece(Do ? rfrom : rto); - board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us + board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // remove_piece does not do this for us put_piece(make_piece(us, KING), Do ? to : from); put_piece(make_piece(us, ROOK), Do ? rto : rfrom); } diff --git a/src/search.cpp b/src/search.cpp index 1b019a08e82..2d4a3f3d35b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -100,12 +100,12 @@ namespace { return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); } - // Skill structure is used to implement strength limit. - // If we have a UCI_Elo, we convert it to an appropriate skill level, anchored to the Stash engine. - // This method is based on a fit of the Elo results for games played between the master at various - // skill levels and various versions of the Stash engine, all ranked at CCRL. + // Skill structure is used to implement strength limit. If we have a UCI_Elo, + // we convert it to an appropriate skill level, anchored to the Stash engine. + // This method is based on a fit of the Elo results for games played between + // Stockfish at various skill levels and various versions of the Stash engine. // Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately - // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c20acedbfe17d618c3c384b339ec + // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 struct Skill { Skill(int skill_level, int uci_elo) { if (uci_elo) @@ -274,9 +274,9 @@ void MainThread::search() { void Thread::search() { - // Allocate stack with extra size to allow access from (ss-7) to (ss+2) - // (ss-7) is needed for update_continuation_histories(ss-1, ...) which accesses (ss-6) - // (ss+2) is needed for initialization of statScore and killers + // Allocate stack with extra size to allow access from (ss-7) to (ss+2): + // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), + // (ss+2) is needed for initialization of statScore and killers. Stack stack[MAX_PLY+10], *ss = stack+7; Move pv[MAX_PLY+1]; Value alpha, beta, delta; @@ -478,8 +478,7 @@ void Thread::search() { double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; - // Cap used time in case of a single legal move for a better viewer experience in tournaments - // yielding correct scores and sufficiently fast moves. + // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); @@ -574,7 +573,8 @@ namespace { static_cast(thisThread)->check_time(); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) - if (PvNode && thisThread->selDepth < ss->ply + 1) + if ( PvNode + && thisThread->selDepth < ss->ply + 1) thisThread->selDepth = ss->ply + 1; if (!rootNode) @@ -640,7 +640,9 @@ namespace { update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) - if (prevSq != SQ_NONE && (ss-1)->moveCount <= 2 && !priorCapture) + if ( prevSq != SQ_NONE + && (ss-1)->moveCount <= 2 + && !priorCapture) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) @@ -748,7 +750,9 @@ namespace { } // Use static evaluation difference to improve quiet move ordering (~4 Elo) - if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) + if ( is_ok((ss-1)->currentMove) + && !(ss-1)->inCheck + && !priorCapture) { int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1812, 1812); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; @@ -979,7 +983,8 @@ namespace { Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); - // Step 14. Pruning at shallow depth (~120 Elo). Depth conditions are important for mate finding. + // Step 14. Pruning at shallow depth (~120 Elo). + // Depth conditions are important for mate finding. if ( !rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) @@ -1052,7 +1057,6 @@ namespace { && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) && move == ttMove && !excludedMove // Avoid recursive singular search - /* && ttValue != VALUE_NONE Already implicit in the next condition */ && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) @@ -1130,8 +1134,7 @@ namespace { // Step 16. Make the move pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV and not likely to fail low. (~3 Elo) - // Decrease further on cutNodes. (~1 Elo) + // Decrease reduction if position is or has been on the PV (~4 Elo) if ( ss->ttPv && !likelyFailLow) r -= cutNode && tte->depth() >= depth ? 3 : 2; @@ -1196,10 +1199,11 @@ namespace { value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); // Do a full-depth search when reduced LMR search fails high - if (value > alpha && d < newDepth) + if ( value > alpha + && d < newDepth) { // Adjust full-depth search based on LMR results - if the result - // was good enough search deeper, if it was bad enough search shallower + // was good enough search deeper, if it was bad enough search shallower. const bool doDeeperSearch = value > (bestValue + 51 + 10 * (newDepth - d)); const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; @@ -1219,19 +1223,22 @@ namespace { } } - // Step 18. Full-depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1. + // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { // Increase reduction for cut nodes and not ttMove (~1 Elo) - if (!ttMove && cutNode) + if ( !ttMove + && cutNode) r += 2; + // Note that if expected reduction is high, we reduce search depth by 1 here value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. - if (PvNode && (moveCount == 1 || value > alpha)) + if ( PvNode + && (moveCount == 1 || value > alpha)) { (ss+1)->pv = pv; (ss+1)->pv[0] = MOVE_NONE; @@ -1329,8 +1336,8 @@ namespace { } } - - // If the move is worse than some previously searched move, remember it, to update its stats later + // If the move is worse than some previously searched move, + // remember it, to update its stats later. if (move != bestMove && moveCount <= 32) { if (capture) @@ -1341,14 +1348,6 @@ namespace { } } - // The following condition would detect a stop only after move loop has been - // completed. But in this case, bestValue is valid because we have fully - // searched our subtree, and we can anyhow save the result in TT. - /* - if (Threads.stop) - return VALUE_DRAW; - */ - // Step 21. Check for mate and stalemate // All legal moves have been searched and if there are no legal moves, it // must be a mate or a stalemate. If we are in a singular extension search then @@ -1494,7 +1493,6 @@ namespace { // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - // Save gathered info in transposition table if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval); @@ -1539,8 +1537,9 @@ namespace { moveCount++; - // Step 6. Pruning. - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) + // Step 6. Pruning + if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY + && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) if ( !givesCheck @@ -1554,7 +1553,7 @@ namespace { futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; // If static eval + value of piece we are going to capture is much lower - // than alpha we can prune this move + // than alpha we can prune this move. if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); @@ -1562,15 +1561,16 @@ namespace { } // If static eval is much lower than alpha and move is not winning material - // we can prune this move - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + // we can prune this move. + if ( futilityBase <= alpha + && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; } // If static exchange evaluation is much worse than what is needed to not - // fall below alpha we can prune this move + // fall below alpha we can prune this move. if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) { bestValue = alpha; @@ -1655,8 +1655,8 @@ namespace { } - // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to - // "plies to mate from the current position". Standard scores are unchanged. + // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" + // to "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. Value value_to_tt(Value v, int ply) { @@ -1670,9 +1670,9 @@ namespace { // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score // from the transposition table (which refers to the plies to mate/be mated from - // current position) to "plies to mate/be mated (TB win/loss) from the root". However, - // for mate scores, to avoid potentially false mate scores related to the 50 moves rule - // and the graph history interaction, we return an optimal TB score instead. + // current position) to "plies to mate/be mated (TB win/loss) from the root". + // However, to avoid potentially false mate scores related to the 50 moves rule + // and the graph history interaction problem, we return an optimal TB score instead. Value value_from_tt(Value v, int ply, int r50c) { From edb4ab924f09abd7c6836c7017365dceccd76b80 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 4 Oct 2023 18:14:40 +0300 Subject: [PATCH 0246/1309] Standardize Comments use double slashes (//) only for comments. closes #4820 No functional change. --- src/benchmark.cpp | 22 ++++---- src/bitboard.cpp | 12 ++--- src/bitboard.h | 66 +++++++++++------------ src/evaluate.cpp | 34 ++++++------ src/misc.cpp | 88 +++++++++++++++---------------- src/misc.h | 44 ++++++++-------- src/movegen.cpp | 18 +++---- src/movegen.h | 6 +-- src/movepick.cpp | 34 ++++++------ src/movepick.h | 60 ++++++++++----------- src/nnue/evaluate_nnue.cpp | 2 +- src/position.cpp | 104 ++++++++++++++++++------------------- src/position.h | 26 +++++----- src/search.cpp | 30 +++++------ src/search.h | 16 +++--- src/syzygy/tbprobe.cpp | 95 +++++++++++++++++---------------- src/thread.cpp | 40 +++++++------- src/thread.h | 16 +++--- src/thread_win32_osx.h | 10 ++-- src/timeman.cpp | 6 +-- src/timeman.h | 4 +- src/tt.cpp | 28 +++++----- src/tt.h | 30 +++++------ src/tune.h | 51 +++++++++--------- src/types.h | 74 +++++++++++++------------- src/uci.cpp | 42 +++++++-------- src/uci.h | 6 +-- src/ucioption.cpp | 20 +++---- 28 files changed, 491 insertions(+), 493 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 8e28184a3cd..d67e37f66ed 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -95,17 +95,17 @@ const std::vector Defaults = { namespace Stockfish { -/// setup_bench() builds a list of UCI commands to be run by bench. There -/// are five parameters: TT size in MB, number of search threads that -/// should be used, the limit value spent for each position, a file name -/// where to look for positions in FEN format, and the type of the limit: -/// depth, perft, nodes and movetime (in milliseconds). Examples: -/// -/// bench : search default positions up to depth 13 -/// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) -/// bench 64 1 100000 default nodes : search default positions for 100K nodes each -/// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec -/// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" +// setup_bench() builds a list of UCI commands to be run by bench. There +// are five parameters: TT size in MB, number of search threads that +// should be used, the limit value spent for each position, a file name +// where to look for positions in FEN format, and the type of the limit: +// depth, perft, nodes and movetime (in milliseconds). Examples: +// +// bench : search default positions up to depth 13 +// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB) +// bench 64 1 100000 default nodes : search default positions for 100K nodes each +// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec +// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" std::vector setup_bench(const Position& current, std::istream& is) { diff --git a/src/bitboard.cpp b/src/bitboard.cpp index bed2b3ee309..89eeee611f8 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -46,8 +46,8 @@ namespace { } -/// safe_destination() returns the bitboard of target square for the given step -/// from the given square. If the step is off the board, returns empty bitboard. +// safe_destination() returns the bitboard of target square for the given step +// from the given square. If the step is off the board, returns empty bitboard. inline Bitboard safe_destination(Square s, int step) { Square to = Square(s + step); @@ -55,8 +55,8 @@ inline Bitboard safe_destination(Square s, int step) { } -/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable -/// to be printed to standard output. Useful for debugging. +// Bitboards::pretty() returns an ASCII representation of a bitboard suitable +// to be printed to standard output. Useful for debugging. std::string Bitboards::pretty(Bitboard b) { @@ -75,8 +75,8 @@ std::string Bitboards::pretty(Bitboard b) { } -/// Bitboards::init() initializes various bitboard tables. It is called at -/// startup and relies on global objects to be already zero-initialized. +// Bitboards::init() initializes various bitboard tables. It is called at +// startup and relies on global objects to be already zero-initialized. void Bitboards::init() { diff --git a/src/bitboard.h b/src/bitboard.h index eb2f949d5aa..0908c957058 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -64,7 +64,7 @@ extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; -/// Magic holds all magic bitboards relevant data for a single square +// Magic holds all magic bitboards relevant data for a single square struct Magic { Bitboard mask; Bitboard magic; @@ -95,8 +95,8 @@ inline Bitboard square_bb(Square s) { } -/// Overloads of bitwise operators between a Bitboard and a Square for testing -/// whether a given bit is set in a bitboard, and for setting and clearing bits. +// Overloads of bitwise operators between a Bitboard and a Square for testing +// whether a given bit is set in a bitboard, and for setting and clearing bits. inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } @@ -115,8 +115,8 @@ constexpr bool more_than_one(Bitboard b) { } -/// rank_bb() and file_bb() return a bitboard representing all the squares on -/// the given file or rank. +// rank_bb() and file_bb() return a bitboard representing all the squares on +// the given file or rank. constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); @@ -135,7 +135,7 @@ constexpr Bitboard file_bb(Square s) { } -/// shift() moves a bitboard one or two steps as specified by the direction D +// shift() moves a bitboard one or two steps as specified by the direction D template constexpr Bitboard shift(Bitboard b) { @@ -148,8 +148,8 @@ constexpr Bitboard shift(Bitboard b) { } -/// pawn_attacks_bb() returns the squares attacked by pawns of the given color -/// from the squares in the given bitboard. +// pawn_attacks_bb() returns the squares attacked by pawns of the given color +// from the squares in the given bitboard. template constexpr Bitboard pawn_attacks_bb(Bitboard b) { @@ -163,10 +163,10 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { return PawnAttacks[c][s]; } -/// line_bb() returns a bitboard representing an entire line (from board edge -/// to board edge) that intersects the two given squares. If the given squares -/// are not on a same file/rank/diagonal, the function returns 0. For instance, -/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. +// line_bb() returns a bitboard representing an entire line (from board edge +// to board edge) that intersects the two given squares. If the given squares +// are not on a same file/rank/diagonal, the function returns 0. For instance, +// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. inline Bitboard line_bb(Square s1, Square s2) { @@ -176,13 +176,13 @@ inline Bitboard line_bb(Square s1, Square s2) { } -/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open -/// segment between the squares s1 and s2 (excluding s1 but including s2). If the -/// given squares are not on a same file/rank/diagonal, it returns s2. For instance, -/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but -/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick -/// allows to generate non-king evasion moves faster: the defending piece must either -/// interpose itself to cover the check or capture the checking piece. +// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open +// segment between the squares s1 and s2 (excluding s1 but including s2). If the +// given squares are not on a same file/rank/diagonal, it returns s2. For instance, +// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but +// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick +// allows to generate non-king evasion moves faster: the defending piece must either +// interpose itself to cover the check or capture the checking piece. inline Bitboard between_bb(Square s1, Square s2) { @@ -191,16 +191,16 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } -/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a -/// straight or on a diagonal line. +// aligned() returns true if the squares s1, s2 and s3 are aligned either on a +// straight or on a diagonal line. inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } -/// distance() functions return the distance between x and y, defined as the -/// number of steps for a king in x to reach y. +// distance() functions return the distance between x and y, defined as the +// number of steps for a king in x to reach y. template inline int distance(Square x, Square y); template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } @@ -209,8 +209,8 @@ template<> inline int distance(Square x, Square y) { return SquareDistan inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -/// attacks_bb(Square) returns the pseudo attacks of the given piece type -/// assuming an empty board. +// attacks_bb(Square) returns the pseudo attacks of the given piece type +// assuming an empty board. template inline Bitboard attacks_bb(Square s) { @@ -221,9 +221,9 @@ inline Bitboard attacks_bb(Square s) { } -/// attacks_bb(Square, Bitboard) returns the attacks by the given piece -/// assuming the board is occupied according to the passed Bitboard. -/// Sliding piece attacks do not continue passed an occupied square. +// attacks_bb(Square, Bitboard) returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. template inline Bitboard attacks_bb(Square s, Bitboard occupied) { @@ -253,7 +253,7 @@ inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { } -/// popcount() counts the number of non-zero bits in a bitboard +// popcount() counts the number of non-zero bits in a bitboard inline int popcount(Bitboard b) { @@ -274,7 +274,7 @@ inline int popcount(Bitboard b) { } -/// lsb() and msb() return the least/most significant bit in a non-zero bitboard +// lsb() and msb() return the least/most significant bit in a non-zero bitboard #if defined(__GNUC__) // GCC, Clang, ICX @@ -342,15 +342,15 @@ inline Square msb(Bitboard b) { #endif -/// least_significant_square_bb() returns the bitboard of the least significant -/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). +// least_significant_square_bb() returns the bitboard of the least significant +// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). inline Bitboard least_significant_square_bb(Bitboard b) { assert(b); return b & -b; } -/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard +// pop_lsb() finds and clears the least significant bit in a non-zero bitboard inline Square pop_lsb(Bitboard& b) { assert(b); diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 208e3ed5ba4..3eb7ee850a3 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -57,13 +57,13 @@ namespace Eval { std::string currentEvalFileName = "None"; - /// NNUE::init() tries to load a NNUE network at startup time, or when the engine - /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - /// The name of the NNUE network is always retrieved from the EvalFile option. - /// We search the given network in three locations: internally (the default - /// network may be embedded in the binary), in the active working directory and - /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY - /// variable to have the engine search in a special directory in their distro. + // NNUE::init() tries to load a NNUE network at startup time, or when the engine + // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" + // The name of the NNUE network is always retrieved from the EvalFile option. + // We search the given network in three locations: internally (the default + // network may be embedded in the binary), in the active working directory and + // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY + // variable to have the engine search in a special directory in their distro. void NNUE::init() { @@ -105,7 +105,7 @@ namespace Eval { } } - /// NNUE::verify() verifies that the last net used was loaded successfully + // NNUE::verify() verifies that the last net used was loaded successfully void NNUE::verify() { std::string eval_file = std::string(Options["EvalFile"]); @@ -135,9 +135,9 @@ namespace Eval { } -/// simple_eval() returns a static, purely materialistic evaluation of the position -/// from the point of view of the given color. It can be divided by PawnValue to get -/// an approximation of the material advantage on the board in terms of pawns. +// simple_eval() returns a static, purely materialistic evaluation of the position +// from the point of view of the given color. It can be divided by PawnValue to get +// an approximation of the material advantage on the board in terms of pawns. Value Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) @@ -145,8 +145,8 @@ Value Eval::simple_eval(const Position& pos, Color c) { } -/// evaluate() is the evaluator for the outer world. It returns a static evaluation -/// of the position from the point of view of the side to move. +// evaluate() is the evaluator for the outer world. It returns a static evaluation +// of the position from the point of view of the side to move. Value Eval::evaluate(const Position& pos) { @@ -189,10 +189,10 @@ Value Eval::evaluate(const Position& pos) { return v; } -/// trace() is like evaluate(), but instead of returning a value, it returns -/// a string (suitable for outputting to stdout) that contains the detailed -/// descriptions and values of each evaluation term. Useful for debugging. -/// Trace scores are from white's point of view +// trace() is like evaluate(), but instead of returning a value, it returns +// a string (suitable for outputting to stdout) that contains the detailed +// descriptions and values of each evaluation term. Useful for debugging. +// Trace scores are from white's point of view std::string Eval::trace(Position& pos) { diff --git a/src/misc.cpp b/src/misc.cpp index 2f6ffd28e70..5abdaf07a31 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -71,14 +71,14 @@ namespace Stockfish { namespace { -/// Version number or dev. +// Version number or dev. constexpr std::string_view version = "dev"; -/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and -/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We -/// can toggle the logging of std::cout and std:cin at runtime whilst preserving -/// usual I/O functionality, all without changing a single line of code! -/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 +// Our fancy logging facility. The trick here is to replace cin.rdbuf() and +// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We +// can toggle the logging of std::cout and std:cin at runtime whilst preserving +// usual I/O functionality, all without changing a single line of code! +// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout @@ -141,15 +141,15 @@ class Logger { } // namespace -/// engine_info() returns the full name of the current Stockfish version. -/// For local dev compiles we try to append the commit sha and commit date -/// from git if that fails only the local compilation date is set and "nogit" is specified: -/// Stockfish dev-YYYYMMDD-SHA -/// or -/// Stockfish dev-YYYYMMDD-nogit -/// -/// For releases (non dev builds) we only include the version number: -/// Stockfish version +// engine_info() returns the full name of the current Stockfish version. +// For local dev compiles we try to append the commit sha and commit date +// from git if that fails only the local compilation date is set and "nogit" is specified: +// Stockfish dev-YYYYMMDD-SHA +// or +// Stockfish dev-YYYYMMDD-nogit +// +// For releases (non-dev builds) we only include the version number: +// Stockfish version std::string engine_info(bool to_uci) { std::stringstream ss; @@ -185,20 +185,20 @@ std::string engine_info(bool to_uci) { } -/// compiler_info() returns a string trying to describe the compiler we use +// compiler_info() returns a string trying to describe the compiler we use std::string compiler_info() { #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) -/// Predefined macros hell: -/// -/// __GNUC__ Compiler is GCC, Clang or ICX -/// __clang__ Compiler is Clang or ICX -/// __INTEL_LLVM_COMPILER Compiler is ICX -/// _MSC_VER Compiler is MSVC -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +// Predefined macros hell: +// +// __GNUC__ Compiler is GCC, Clang or ICX +// __clang__ Compiler is Clang or ICX +// __INTEL_LLVM_COMPILER Compiler is ICX +// _MSC_VER Compiler is MSVC +// _WIN32 Building on Windows (any) +// _WIN64 Building on Windows 64 bit std::string compiler = "\nCompiled by : "; @@ -305,7 +305,7 @@ std::string compiler_info() { } -/// Debug functions used mainly to collect run-time statistics +// Debug functions used mainly to collect run-time statistics constexpr int MaxDebugSlots = 32; namespace { @@ -397,8 +397,8 @@ void dbg_print() { } -/// Used to serialize access to std::cout to avoid multiple threads writing at -/// the same time. +// Used to serialize access to std::cout to avoid multiple threads writing at +// the same time. std::ostream& operator<<(std::ostream& os, SyncCout sc) { @@ -414,13 +414,13 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) { } -/// Trampoline helper to avoid moving Logger to misc.h +// Trampoline helper to avoid moving Logger to misc.h void start_logger(const std::string& fname) { Logger::start(fname); } -/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking -/// function that doesn't stall the CPU waiting for data to be loaded from memory, -/// which can be quite slow. +// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking +// function that doesn't stall the CPU waiting for data to be loaded from memory, +// which can be quite slow. #ifdef NO_PREFETCH void prefetch(void*) {} @@ -439,9 +439,9 @@ void prefetch(void* addr) { #endif -/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation -/// does not guarantee the availability of aligned_alloc(). Memory allocated with -/// std_aligned_alloc() must be freed with std_aligned_free(). +// std_aligned_alloc() is our wrapper for systems where the c++17 implementation +// does not guarantee the availability of aligned_alloc(). Memory allocated with +// std_aligned_alloc() must be freed with std_aligned_free(). void* std_aligned_alloc(size_t alignment, size_t size) { @@ -470,7 +470,7 @@ void std_aligned_free(void* ptr) { #endif } -/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. +// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. #if defined(_WIN32) @@ -550,7 +550,7 @@ void* aligned_large_pages_alloc(size_t allocSize) { // Try to allocate large pages void* mem = aligned_large_pages_alloc_windows(allocSize); - // Fall back to regular, page aligned, allocation if necessary + // Fall back to regular, page-aligned, allocation if necessary if (!mem) mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); @@ -579,7 +579,7 @@ void* aligned_large_pages_alloc(size_t allocSize) { #endif -/// aligned_large_pages_free() will free the previously allocated ttmem +// aligned_large_pages_free() will free the previously allocated ttmem #if defined(_WIN32) @@ -612,9 +612,9 @@ void bindThisThread(size_t) {} #else -/// best_node() retrieves logical processor information using Windows specific -/// API and returns the best node id for the thread with index idx. Original -/// code from Texel by Peter Österlund. +// best_node() retrieves logical processor information using Windows specific +// API and returns the best node id for the thread with index idx. Original +// code from Texel by Peter Österlund. static int best_node(size_t idx) { @@ -666,8 +666,8 @@ static int best_node(size_t idx) { std::vector groups; - // Run as many threads as possible on the same node until core limit is - // reached, then move on filling the next node. + // Run as many threads as possible on the same node until the core limit is + // reached, then move on to filling the next node. for (int n = 0; n < nodes; n++) for (int i = 0; i < cores / nodes; i++) groups.push_back(n); @@ -684,7 +684,7 @@ static int best_node(size_t idx) { } -/// bindThisThread() set the group affinity of the current thread +// bindThisThread() sets the group affinity of the current thread void bindThisThread(size_t idx) { @@ -751,7 +751,7 @@ void init([[maybe_unused]] int argc, char* argv[]) { pathSeparator = "\\"; #ifdef _MSC_VER // Under windows argv[0] may not have the extension. Also _get_pgmptr() had - // issues in some windows 10 versions, so check returned values carefully. + // issues in some Windows 10 versions, so check returned values carefully. char* pgmptr = nullptr; if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) argv0 = pgmptr; diff --git a/src/misc.h b/src/misc.h index 52595fb906b..60602048b9c 100644 --- a/src/misc.h +++ b/src/misc.h @@ -74,7 +74,7 @@ T* align_ptr_up(T* ptr) } -// IsLittleEndian : true if and only if the binary is compiled on a little endian machine +// IsLittleEndian : 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); @@ -95,20 +95,20 @@ class ValueList { }; -/// xorshift64star Pseudo-Random Number Generator -/// This class is based on original code written and dedicated -/// to the public domain by Sebastiano Vigna (2014). -/// It has the following characteristics: -/// -/// - Outputs 64-bit numbers -/// - Passes Dieharder and SmallCrush test batteries -/// - Does not require warm-up, no zeroland to escape -/// - Internal state is a single 64-bit integer -/// - Period is 2^64 - 1 -/// - Speed: 1.60 ns/call (Core i7 @3.40GHz) -/// -/// For further analysis see -/// +// xorshift64star Pseudo-Random Number Generator +// This class is based on original code written and dedicated +// to the public domain by Sebastiano Vigna (2014). +// It has the following characteristics: +// +// - Outputs 64-bit numbers +// - Passes Dieharder and SmallCrush test batteries +// - Does not require warm-up, no zeroland to escape +// - Internal state is a single 64-bit integer +// - Period is 2^64 - 1 +// - Speed: 1.60 ns/call (Core i7 @3.40GHz) +// +// For further analysis see +// class PRNG { @@ -125,8 +125,8 @@ class PRNG { template T rand() { return T(rand64()); } - /// Special generator used to fast init magic numbers. - /// Output values only have 1/8th of their bits set on average. + // Special generator used to fast init magic numbers. + // Output values only have 1/8th of their bits set on average. template T sparse_rand() { return T(rand64() & rand64() & rand64()); } }; @@ -145,11 +145,11 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #endif } -/// Under Windows it is not possible for a process to run on more than one -/// logical processor group. This usually means to be limited to use max 64 -/// cores. To overcome this, some special platform specific API should be -/// called to set group affinity for each thread. Original code from Texel by -/// Peter Österlund. +// Under Windows it is not possible for a process to run on more than one +// logical processor group. This usually means being limited to using max 64 +// cores. To overcome this, some special platform-specific API should be +// called to set group affinity for each thread. Original code from Texel by +// Peter Österlund. namespace WinProcGroup { void bindThisThread(size_t idx); diff --git a/src/movegen.cpp b/src/movegen.cpp index cda43b3a582..82ad60613c2 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -234,14 +234,14 @@ namespace { } // namespace -/// Generates all pseudo-legal captures plus queen promotions -/// Generates all pseudo-legal non-captures and underpromotions -/// Generates all pseudo-legal check evasions -/// Generates all pseudo-legal captures and non-captures -/// Generates all pseudo-legal non-captures giving check, -/// except castling and promotions -/// -/// Returns a pointer to the end of the move list. +// Generates all pseudo-legal captures plus queen promotions +// Generates all pseudo-legal non-captures and underpromotions +// Generates all pseudo-legal check evasions +// Generates all pseudo-legal captures and non-captures +// Generates all pseudo-legal non-captures giving check, +// except castling and promotions +// +// Returns a pointer to the end of the move list. template ExtMove* generate(const Position& pos, ExtMove* moveList) { @@ -263,7 +263,7 @@ template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); -/// generate generates all the legal moves in the given position +// generate generates all the legal moves in the given position template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { diff --git a/src/movegen.h b/src/movegen.h index 5eee2f1acf8..e913a13ea13 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -56,9 +56,9 @@ inline bool operator<(const ExtMove& f, const ExtMove& s) { template ExtMove* generate(const Position& pos, ExtMove* moveList); -/// The MoveList struct wraps the generate() function and returns a convenient -/// list of moves. Using MoveList is sometimes preferable to directly calling -/// the lower level generate() function. +// The MoveList struct wraps the generate() function and returns a convenient +// list of moves. Using MoveList is sometimes preferable to directly calling +// the lower level generate() function. template struct MoveList { diff --git a/src/movepick.cpp b/src/movepick.cpp index bc3fcf7ed6b..5bb0fd6c274 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -55,13 +55,13 @@ namespace { } // namespace -/// Constructors of the MovePicker class. As arguments, we pass information -/// to help it return the (presumably) good moves first, to decide which -/// moves to return (in the quiescence search, for instance, we only want to -/// search captures, promotions, and some checks) and how important a good -/// move ordering is at the current node. +// Constructors of the MovePicker class. As arguments, we pass information +// to help it return the (presumably) good moves first, to decide which +// moves to return (in the quiescence search, for instance, we only want to +// search captures, promotions, and some checks) and how important a good +// move ordering is at the current node. -/// MovePicker constructor for the main search +// MovePicker constructor for the main search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, @@ -76,7 +76,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist !(ttm && pos.pseudo_legal(ttm)); } -/// MovePicker constructor for quiescence search +// MovePicker constructor for quiescence search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, @@ -90,8 +90,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist && pos.pseudo_legal(ttm)); } -/// MovePicker constructor for ProbCut: we generate captures with SEE greater -/// than or equal to the given threshold. +// MovePicker constructor for ProbCut: we generate captures with SEE greater +// than or equal to the given threshold. MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) { @@ -102,9 +102,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece && pos.see_ge(ttm, threshold)); } -/// MovePicker::score() assigns a numerical value to each move in a list, used -/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring -/// captures with a good history. Quiets moves are ordered using the history tables. +// MovePicker::score() assigns a numerical value to each move in a list, used +// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring +// captures with a good history. Quiets moves are ordered using the history tables. template void MovePicker::score() { @@ -180,8 +180,8 @@ void MovePicker::score() { } } -/// MovePicker::select() returns the next move satisfying a predicate function. -/// It never returns the TT move. +// MovePicker::select() returns the next move satisfying a predicate function. +// It never returns the TT move. template Move MovePicker::select(Pred filter) { @@ -198,9 +198,9 @@ Move MovePicker::select(Pred filter) { return MOVE_NONE; } -/// MovePicker::next_move() is the most important method of the MovePicker class. It -/// returns a new pseudo-legal move every time it is called until there are no more -/// moves left, picking the move with the highest score from a list of generated moves. +// MovePicker::next_move() is the most important method of the MovePicker class. It +// returns a new pseudo-legal move every time it is called until there are no more +// moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { top: diff --git a/src/movepick.h b/src/movepick.h index dd9de0b2161..652ef16166d 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -32,10 +32,10 @@ namespace Stockfish { class Position; -/// StatsEntry stores the stat table value. It is usually a number but could -/// be a move or even a nested history. We use a class instead of naked value -/// to directly call history update operator<<() on the entry so to use stats -/// tables at caller sites as simple multi-dim arrays. +// StatsEntry stores the stat table value. It is usually a number but could +// be a move or even a nested history. We use a class instead of a naked value +// to directly call history update operator<<() on the entry so to use stats +// tables at caller sites as simple multi-dim arrays. template class StatsEntry { @@ -57,11 +57,11 @@ class StatsEntry { } }; -/// Stats is a generic N-dimensional array used to store various statistics. -/// The first template parameter T is the base type of the array, the second -/// template parameter D limits the range of updates in [-D, D] when we update -/// values with the << operator, while the last parameters (Size and Sizes) -/// encode the dimensions of the array. +// Stats is a generic N-dimensional array used to store various statistics. +// The first template parameter T is the base type of the array, and the second +// template parameter D limits the range of updates in [-D, D] when we update +// values with the << operator, while the last parameters (Size and Sizes) +// encode the dimensions of the array. template struct Stats : public std::array, Size> { @@ -69,7 +69,7 @@ struct Stats : public std::array, Size> void fill(const T& v) { - // For standard-layout 'this' points to first struct member + // For standard-layout 'this' points to the first struct member assert(std::is_standard_layout_v); using entry = StatsEntry; @@ -81,40 +81,40 @@ struct Stats : public std::array, Size> template struct Stats : public std::array, Size> {}; -/// In stats table, D=0 means that the template parameter is not used +// In stats table, D=0 means that the template parameter is not used enum StatsParams { NOT_USED = 0 }; enum StatsType { NoCaptures, Captures }; -/// ButterflyHistory records how often quiet moves have been successful or -/// unsuccessful during the current search, and is used for reduction and move -/// ordering decisions. It uses 2 tables (one for each color) indexed by -/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards -/// (~11 elo) +// ButterflyHistory records how often quiet moves have been successful or +// unsuccessful during the current search, and is used for reduction and move +// ordering decisions. It uses 2 tables (one for each color) indexed by +// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards +// (~11 elo) using ButterflyHistory = Stats; -/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous -/// move, see www.chessprogramming.org/Countermove_Heuristic +// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous +// move, see www.chessprogramming.org/Countermove_Heuristic using CounterMoveHistory = Stats; -/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; -/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] using PieceToHistory = Stats; -/// ContinuationHistory is the combined history of a given pair of moves, usually -/// the current one given a previous one. The nested history table is based on -/// PieceToHistory instead of ButterflyBoards. -/// (~63 elo) +// ContinuationHistory is the combined history of a given pair of moves, usually +// the current one given a previous one. The nested history table is based on +// PieceToHistory instead of ButterflyBoards. +// (~63 elo) using ContinuationHistory = Stats; -/// MovePicker class is used to pick one pseudo-legal move at a time from the -/// current position. The most important method is next_move(), which returns a -/// new pseudo-legal move each time it is called, until there are no moves left, -/// when MOVE_NONE is returned. In order to improve the efficiency of the -/// alpha-beta algorithm, MovePicker attempts to return the moves which are most -/// likely to get a cut-off first. +// MovePicker class is used to pick one pseudo-legal move at a time from the +// current position. The most important method is next_move(), which returns a +// new pseudo-legal move each time it is called, until there are no moves left, +// when MOVE_NONE is returned. In order to improve the efficiency of the +// alpha-beta algorithm, MovePicker attempts to return the moves which are most +// likely to get a cut-off first. class MovePicker { enum PickType { Next, Best }; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index e1fa3b814a1..1f821cf9a3b 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -375,7 +375,7 @@ namespace Stockfish::Eval::NNUE { return write_parameters(stream); } - /// Save eval, to a file given by its name + // Save eval, to a file given by its name bool save_eval(const std::optional& filename) { std::string actualFilename; diff --git a/src/position.cpp b/src/position.cpp index 0d7d957141f..ada371eb951 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -61,7 +61,7 @@ constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING } // namespace -/// operator<<(Position) returns an ASCII representation of the position +// operator<<(Position) returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { @@ -116,7 +116,7 @@ Key cuckoo[8192]; Move cuckooMove[8192]; -/// Position::init() initializes at startup the various arrays used to compute hash keys +// Position::init() initializes at startup the various arrays used to compute hash keys void Position::init() { @@ -160,9 +160,9 @@ void Position::init() { } -/// Position::set() initializes the position object with the given FEN string. -/// This function is not very robust - make sure that input FENs are correct, -/// this is assumed to be the responsibility of the GUI. +// Position::set() initializes the position object with the given FEN string. +// This function is not very robust - make sure that input FENs are correct, +// this is assumed to be the responsibility of the GUI. Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* @@ -297,8 +297,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th } -/// Position::set_castling_right() is a helper function used to set castling -/// rights given the corresponding color and the rook starting square. +// Position::set_castling_right() is a helper function used to set castling +// rights given the corresponding color and the rook starting square. void Position::set_castling_right(Color c, Square rfrom) { @@ -318,7 +318,7 @@ void Position::set_castling_right(Color c, Square rfrom) { } -/// Position::set_check_info() sets king attacks to detect if a move gives check +// Position::set_check_info() sets king attacks to detect if a move gives check void Position::set_check_info() const { @@ -336,9 +336,9 @@ void Position::set_check_info() const { } -/// Position::set_state() computes the hash keys of the position, and other -/// data that once computed is updated incrementally as moves are made. -/// The function is only used when a new position is set up +// Position::set_state() computes the hash keys of the position, and other +// data that once computed is updated incrementally as moves are made. +// The function is only used when a new position is set up void Position::set_state() const { @@ -372,9 +372,9 @@ void Position::set_state() const { } -/// Position::set() is an overload to initialize the position object with -/// the given endgame code string like "KBPKN". It is mainly a helper to -/// get the material key out of an endgame code. +// Position::set() is an overload to initialize the position object with +// the given endgame code string like "KBPKN". It is mainly a helper to +// get the material key out of an endgame code. Position& Position::set(const string& code, Color c, StateInfo* si) { @@ -395,8 +395,8 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { } -/// Position::fen() returns a FEN representation of the position. In case of -/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. +// Position::fen() returns a FEN representation of the position. In case of +// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. string Position::fen() const { @@ -444,9 +444,9 @@ string Position::fen() const { return ss.str(); } -/// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], -/// which store respectively the pieces preventing king of color c from being in check -/// and the slider pieces of color ~c pinning pieces of color c to the king. +// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], +// which store respectively the pieces preventing king of color c from being in check +// and the slider pieces of color ~c pinning pieces of color c to the king. void Position::update_slider_blockers(Color c) const { Square ksq = square(c); @@ -474,8 +474,8 @@ void Position::update_slider_blockers(Color c) const { } -/// Position::attackers_to() computes a bitboard of all pieces which attack a -/// given square. Slider attacks use the occupied bitboard to indicate occupancy. +// Position::attackers_to() computes a bitboard of all pieces which attack a +// given square. Slider attacks use the occupied bitboard to indicate occupancy. Bitboard Position::attackers_to(Square s, Bitboard occupied) const { @@ -488,7 +488,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { } -/// Position::legal() tests whether a pseudo-legal move is legal +// Position::legal() tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { @@ -532,7 +532,7 @@ bool Position::legal(Move m) const { if (attackers_to(s) & pieces(~us)) return false; - // In case of Chess960, verify if the Rook blocks some checks + // In case of Chess960, verify if the Rook blocks some checks. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. return !chess960 || !(blockers_for_king(us) & to_sq(m)); } @@ -549,9 +549,9 @@ bool Position::legal(Move m) const { } -/// Position::pseudo_legal() takes a random move and tests whether the move is -/// pseudo-legal. It is used to validate moves from TT that can be corrupted -/// due to SMP concurrent access or hash position key aliasing. +// Position::pseudo_legal() takes a random move and tests whether the move is +// pseudo-legal. It is used to validate moves from TT that can be corrupted +// due to SMP concurrent access or hash position key aliasing. bool Position::pseudo_legal(const Move m) const { @@ -622,7 +622,7 @@ bool Position::pseudo_legal(const Move m) const { } -/// Position::gives_check() tests whether a pseudo-legal move gives a check +// Position::gives_check() tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { @@ -672,9 +672,9 @@ bool Position::gives_check(Move m) const { } -/// Position::do_move() makes a move, and saves all information necessary -/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal -/// moves should be filtered out before this function is called. +// Position::do_move() makes a move, and saves all information necessary +// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +// moves should be filtered out before this function is called. void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { @@ -870,8 +870,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } -/// Position::undo_move() unmakes a move. When it returns, the position should -/// be restored to exactly the same state as before the move was made. +// Position::undo_move() unmakes a move. When it returns, the position should +// be restored to exactly the same state as before the move was made. void Position::undo_move(Move m) { @@ -934,8 +934,8 @@ void Position::undo_move(Move m) { } -/// Position::do_castling() is a helper used to do/undo a castling move. This -/// is a bit tricky in Chess960 where from/to squares can overlap. +// Position::do_castling() is a helper used to do/undo a castling move. This +// is a bit tricky in Chess960 where from/to squares can overlap. template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { @@ -965,8 +965,8 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ } -/// Position::do_null_move() is used to do a "null move": it flips -/// the side to move without executing any move on the board. +// Position::do_null_move() is used to do a "null move": it flips +// the side to move without executing any move on the board. void Position::do_null_move(StateInfo& newSt) { @@ -1005,7 +1005,7 @@ void Position::do_null_move(StateInfo& newSt) { } -/// Position::undo_null_move() must be used to undo a "null move" +// Position::undo_null_move() must be used to undo a "null move" void Position::undo_null_move() { @@ -1016,9 +1016,9 @@ void Position::undo_null_move() { } -/// Position::key_after() computes the new hash key after the given move. Needed -/// for speculative prefetch. It doesn't recognize special moves like castling, -/// en passant and promotions. +// Position::key_after() computes the new hash key after the given move. Needed +// for speculative prefetch. It doesn't recognize special moves like castling, +// en passant and promotions. Key Position::key_after(Move m) const { @@ -1038,9 +1038,9 @@ Key Position::key_after(Move m) const { } -/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the -/// SEE value of move is greater or equal to the given threshold. We'll use an -/// algorithm similar to alpha-beta pruning with a null window. +// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the +// SEE value of move is greater or equal to the given threshold. We'll use an +// algorithm similar to alpha-beta pruning with a null window. bool Position::see_ge(Move m, Value threshold) const { @@ -1143,8 +1143,8 @@ bool Position::see_ge(Move m, Value threshold) const { return bool(res); } -/// Position::is_draw() tests whether the position is drawn by 50-move rule -/// or by repetition. It does not detect stalemates. +// Position::is_draw() tests whether the position is drawn by 50-move rule +// or by repetition. It does not detect stalemates. bool Position::is_draw(int ply) const { @@ -1175,8 +1175,8 @@ bool Position::has_repeated() const { } -/// Position::has_game_cycle() tests if the position has a move which draws by repetition, -/// or an earlier position has a move that directly reaches the current position. +// Position::has_game_cycle() tests if the position has a move which draws by repetition, +// or an earlier position has a move that directly reaches the current position. bool Position::has_game_cycle(int ply) const { @@ -1224,8 +1224,8 @@ bool Position::has_game_cycle(int ply) const { } -/// Position::flip() flips position with the white and black sides reversed. This -/// is only useful for debugging e.g. for finding evaluation symmetry bugs. +// Position::flip() flips position with the white and black sides reversed. This +// is only useful for debugging e.g. for finding evaluation symmetry bugs. void Position::flip() { @@ -1259,9 +1259,9 @@ void Position::flip() { } -/// Position::pos_is_ok() performs some consistency checks for the -/// position object and raise an assert if something wrong is detected. -/// This is meant to be helpful when debugging. +// Position::pos_is_ok() performs some consistency checks for the +// position object and raise an assert if something wrong is detected. +// This is meant to be helpful when debugging. bool Position::pos_is_ok() const { diff --git a/src/position.h b/src/position.h index aae4db94a9c..23fd5bf5688 100644 --- a/src/position.h +++ b/src/position.h @@ -31,9 +31,9 @@ namespace Stockfish { -/// StateInfo struct stores information needed to restore a Position object to -/// its previous state when we retract a move. Whenever a move is made on the -/// board (by calling Position::do_move), a StateInfo object must be passed. +// StateInfo struct stores information needed to restore a Position object to +// its previous state when we retract a move. Whenever a move is made on the +// board (by calling Position::do_move), a StateInfo object must be passed. struct StateInfo { @@ -61,17 +61,17 @@ struct StateInfo { }; -/// A list to keep track of the position states along the setup moves (from the -/// start position to the position just before the search starts). Needed by -/// 'draw by repetition' detection. Use a std::deque because pointers to -/// elements are not invalidated upon list resizing. +// A list to keep track of the position states along the setup moves (from the +// start position to the position just before the search starts). Needed by +// 'draw by repetition' detection. Use a std::deque because pointers to +// elements are not invalidated upon list resizing. using StateListPtr = std::unique_ptr>; -/// Position class stores information regarding the board representation as -/// pieces, side to move, hash keys, castling info, etc. Important methods are -/// do_move() and undo_move(), used by the search to update node info when -/// traversing the search tree. +// Position class stores information regarding the board representation as +// pieces, side to move, hash keys, castling info, etc. Important methods are +// do_move() and undo_move(), used by the search to update node info when +// traversing the search tree. class Thread; class Position { @@ -342,8 +342,8 @@ inline bool Position::capture(Move m) const { || type_of(m) == EN_PASSANT; } -// returns true if a move is generated from the capture stage -// having also queen promotions covered, i.e. consistency with the capture stage move generation +// Returns true if a move is generated from the capture stage, having also +// queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { assert(is_ok(m)); diff --git a/src/search.cpp b/src/search.cpp index 2d4a3f3d35b..16c6b0f38f8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -169,7 +169,7 @@ namespace { } // namespace -/// Search::init() is called at startup to initialize various lookup tables +// Search::init() is called at startup to initialize various lookup tables void Search::init() { @@ -178,7 +178,7 @@ void Search::init() { } -/// Search::clear() resets search state to its initial value +// Search::clear() resets search state to its initial value void Search::clear() { @@ -191,8 +191,8 @@ void Search::clear() { } -/// MainThread::search() is started when the program receives the UCI 'go' -/// command. It searches from the root position and outputs the "bestmove". +// MainThread::search() is started when the program receives the UCI 'go' +// command. It searches from the root position and outputs the "bestmove". void MainThread::search() { @@ -268,9 +268,9 @@ void MainThread::search() { } -/// Thread::search() is the main iterative deepening loop. It calls search() -/// repeatedly with increasing depth until the allocated thinking time has been -/// consumed, the user stops the search, or the maximum search depth is reached. +// Thread::search() is the main iterative deepening loop. It calls search() +// repeatedly with increasing depth until the allocated thinking time has been +// consumed, the user stops the search, or the maximum search depth is reached. void Thread::search() { @@ -1837,8 +1837,8 @@ namespace { } // namespace -/// MainThread::check_time() is used to print debug info and, more importantly, -/// to detect when we are out of available time and thus stop the search. +// MainThread::check_time() is used to print debug info and, more importantly, +// to detect when we are out of available time and thus stop the search. void MainThread::check_time() { @@ -1870,8 +1870,8 @@ void MainThread::check_time() { } -/// UCI::pv() formats PV information according to the UCI protocol. UCI requires -/// that all (if any) unsearched PV lines are sent using a previous search score. +// UCI::pv() formats PV information according to the UCI protocol. UCI requires +// that all (if any) unsearched PV lines are sent using a previous search score. string UCI::pv(const Position& pos, Depth depth) { @@ -1929,10 +1929,10 @@ string UCI::pv(const Position& pos, Depth depth) { } -/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move -/// before exiting the search, for instance, in case we stop the search during a -/// fail high at root. We try hard to have a ponder move to return to the GUI, -/// otherwise in case of 'ponder on' we have nothing to think about. +// RootMove::extract_ponder_from_tt() is called in case we have no ponder move +// before exiting the search, for instance, in case we stop the search during a +// fail high at root. We try hard to have a ponder move to return to the GUI, +// otherwise in case of 'ponder on' we have nothing to think about. bool RootMove::extract_ponder_from_tt(Position& pos) { diff --git a/src/search.h b/src/search.h index c6dbffce0c7..c434ba752d6 100644 --- a/src/search.h +++ b/src/search.h @@ -33,9 +33,9 @@ class Position; namespace Search { -/// Stack struct keeps track of the information we need to remember from nodes -/// shallower and deeper in the tree during the search. Each search thread has -/// its own array of Stack objects, indexed by the current ply. +// Stack struct keeps track of the information we need to remember from nodes +// shallower and deeper in the tree during the search. Each search thread has +// its own array of Stack objects, indexed by the current ply. struct Stack { Move* pv; @@ -55,9 +55,9 @@ struct Stack { }; -/// RootMove struct is used for moves at the root of the tree. For each root move -/// we store a score and a PV (really a refutation in the case of moves which -/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. +// RootMove struct is used for moves at the root of the tree. For each root move +// we store a score and a PV (really a refutation in the case of moves which +// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. struct RootMove { @@ -84,8 +84,8 @@ struct RootMove { using RootMoves = std::vector; -/// LimitsType struct stores information sent by GUI about available time to -/// search the current move, maximum depth/time, or if we are in analysis mode. +// LimitsType struct stores information sent by GUI about available time to +// search the current move, maximum depth/time, or if we are in analysis mode. struct LimitsType { diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index ffe29ce1626..4114db605d2 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -139,7 +139,7 @@ template int sign_of(T val) { return (T(0) < val) - (val < T(0)); } -// Numbers in little endian used by sparseIndex[] to point into blockLength[] +// Numbers in little-endian used by sparseIndex[] to point into blockLength[] struct SparseEntry { char block[4]; // Number of block char offset[2]; // Offset within the block @@ -153,7 +153,7 @@ struct LR { enum Side { Left, Right }; uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 - // bits is the right-hand symbol. If symbol has length 1, + // bits is the right-hand symbol. If the symbol has length 1, // then the left-hand symbol is the stored value. template Sym get() { @@ -301,9 +301,9 @@ class TBFile : public std::ifstream { std::string TBFile::Paths; -// struct PairsData contains low level indexing information to access TB data. -// There are 8, 4 or 2 PairsData records for each TBTable, according to type of -// table and if positions have pawns or not. It is populated at first access. +// struct PairsData contains low-level indexing information to access TB data. +// There are 8, 4, or 2 PairsData records for each TBTable, according to the type +// of table and if positions have pawns or not. It is populated at first access. struct PairsData { uint8_t flags; // Table flags, see enum TBFlag uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols @@ -379,7 +379,7 @@ TBTable::TBTable(const std::string& code) : TBTable() { hasUniquePieces = true; // Set the leading color. In case both sides have pawns the leading color - // is the side with less pawns because this leads to better compression. + // is the side with fewer pawns because this leads to better compression. bool c = !pos.count(BLACK) || ( pos.count(WHITE) && pos.count(BLACK) >= pos.count(WHITE)); @@ -404,7 +404,7 @@ TBTable::TBTable(const TBTable& wdl) : TBTable() { } // class TBTables creates and keeps ownership of the TBTable objects, one for -// each TB file found. It supports a fast, hash based, table lookup. Populated +// each TB file found. It supports a fast, hash-based, table lookup. Populated // at init time, accessed at probe time. class TBTables { @@ -511,9 +511,9 @@ void TBTables::add(const std::vector& pieces) { // mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so // in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. // The generator picks the size that leads to the smallest table. The "book" of symbols and -// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file +// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file // will have one table for wtm and one for btm, a TB file with pawns will have tables per -// file a,b,c,d also in this case one set for wtm and one for btm. +// file a,b,c,d also, in this case, one set for wtm and one for btm. int decompress_pairs(PairsData* d, uint64_t idx) { // Special case where all table positions store the same value @@ -541,7 +541,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { uint32_t block = number(&d->sparseIndex[k].block); int offset = number(&d->sparseIndex[k].offset); - // Now compute the difference idx - I(k). From definition of k we know that + // Now compute the difference idx - I(k). From the definition of k, we know that // // idx = k * d->span + idx % d->span (2) // @@ -551,7 +551,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Sum the above to offset to find the offset corresponding to our idx offset += diff; - // Move to previous/next block, until we reach the correct block that contains idx, + // Move to the previous/next block, until we reach the correct block that contains idx, // that is when 0 <= offset <= d->blockLength[block] while (offset < 0) offset += d->blockLength[--block] + 1; @@ -564,7 +564,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one - // is at the beginning of this 64 bits sequence. + // is at the beginning of this 64-bit sequence. uint64_t buf64 = number(ptr); ptr += 2; int buf64Size = 64; Sym sym; @@ -587,8 +587,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // Now add the value of the lowest symbol of length len to get our symbol sym += number(&d->lowestSym[len]); - // If our offset is within the number of values represented by symbol sym - // we are done... + // If our offset is within the number of values represented by symbol sym, + // we are done. if (offset < d->symlen[sym] + 1) break; @@ -604,7 +604,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { } } - // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // Now we have our symbol that expands into d->symlen[sym] + 1 symbols. // We binary-search for our value recursively expanding into the left and // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 // that will store the value we need. @@ -614,7 +614,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then - // we know that, for instance the ten-th value (offset = 10) will be on + // we know that, for instance, the tenth value (offset = 10) will be on // the left side because in Recursive Pairing child symbols are adjacent. if (offset < d->symlen[left] + 1) sym = left; @@ -639,7 +639,7 @@ bool check_dtz_stm(TBTable* entry, int stm, File f) { // DTZ scores are sorted by frequency of occurrence and then assigned the // values 0, 1, 2, ... in order of decreasing frequency. This is done for each // of the four WDLScore values. The mapping information necessary to reconstruct -// the original values is stored in the TB file and read during map[] init. +// the original values are stored in the TB file and read during map[] init. WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); } int map_score(TBTable* entry, File f, int value, WDLScore wdl) { @@ -658,7 +658,7 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { } // DTZ tables store distance to zero in number of moves or plies. We - // want to return plies, so we have convert to plies when needed. + // want to return plies, so we have to convert to plies when needed. if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin @@ -669,7 +669,7 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { } // Compute a unique index out of a position and use it to probe the TB file. To -// encode k pieces of same type and color, first sort the pieces by square in +// encode k pieces of the same type and color, first sort the pieces by square in // ascending order s1 <= s2 <= ... <= sk then compute the unique index as: // // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] @@ -687,13 +687,13 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. // If both sides have the same pieces keys are equal. In this case TB tables - // only store the 'white to move' case, so if the position to lookup has black + // only stores the 'white to move' case, so if the position to lookup has black // to move, we need to switch the color and flip the squares before to lookup. bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); - // TB files are calculated for white as stronger side. For instance we have - // KRvK, not KvKR. A position where stronger side is white will have its - // material key == entry->key, otherwise we have to switch the color and + // TB files are calculated for white as the stronger side. For instance, we + // have KRvK, not KvKR. A position where the stronger side is white will have + // its material key == entry->key, otherwise we have to switch the color and // flip the squares before to lookup. bool blackStronger = (pos.material_key() != entry->key); @@ -816,7 +816,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be // swapped and still get the same position.) // - // In case we have at least 3 unique pieces (included kings) we encode them + // In case we have at least 3 unique pieces (including kings) we encode them // together. if (entry->hasUniquePieces) { @@ -861,7 +861,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu idx *= d->groupIdx[0]; Square* groupSq = squares + d->groupLen[0]; - // Encode remaining pawns then pieces according to square, in ascending order + // Encode remaining pawns and then pieces according to square, in ascending order bool remainingPawns = entry->hasPawns && entry->pawnCount[1]; while (d->groupLen[++next]) @@ -870,7 +870,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu uint64_t n = 0; // Map down a square if "comes later" than a square in the previous - // groups (similar to what done earlier for leading group pieces). + // groups (similar to what was done earlier for leading group pieces). for (int i = 0; i < d->groupLen[next]; ++i) { auto f = [&](Square s) { return groupSq[i] > s; }; @@ -888,7 +888,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu } // Group together pieces that will be encoded together. The general rule is that -// a group contains pieces of same type and color. The exception is the leading +// a group contains pieces of the same type and color. The exception is the leading // group that, in case of positions without pawns, can be formed by 3 different // pieces (default) or by the king pair when there is not a unique piece apart // from the kings. When there are pawns, pawns are always first in pieces[]. @@ -953,7 +953,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // In Recursive Pairing each symbol represents a pair of children symbols. So // read d->btree[] symbols data and expand each one in his left and right child -// symbol until reaching the leafs that represent the symbol value. +// symbol until reaching the leaves that represent the symbol value. uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { visited[s] = true; // We can set it now because tree is acyclic @@ -1002,7 +1002,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // See https://en.wikipedia.org/wiki/Huffman_coding // The canonical code is ordered such that longer symbols (in terms of - // the number of bits of their Huffman code) have lower numeric value, + // the number of bits of their Huffman code) have a lower numeric value, // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. @@ -1072,7 +1072,7 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { return data += uintptr_t(data) & 1; // Word alignment } -// Populate entry's PairsData records with data from the just memory mapped file. +// Populate entry's PairsData records with data from the just memory-mapped file. // Called at first access. template void set(T& e, uint8_t* data) { @@ -1138,9 +1138,9 @@ void set(T& e, uint8_t* data) { } } -// If the TB file corresponding to the given position is already memory mapped -// then return its base address, otherwise try to memory map and init it. Called -// at every probe, memory map and init only at first access. Function is thread +// If the TB file corresponding to the given position is already memory-mapped +// then return its base address, otherwise, try to memory map and init it. Called +// at every probe, memory map, and init only at first access. Function is thread // safe and can be called concurrently. template void* mapped(TBTable& e, const Position& pos) { @@ -1191,7 +1191,7 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) } // For a position where the side to move has a winning capture it is not necessary -// to store a winning value so the generator treats such positions as "don't cares" +// to store a winning value so the generator treats such positions as "don't care" // and tries to assign to it a value that improves the compression ratio. Similarly, // if the side to move has a drawing capture, then the position is at least drawn. // If the position is won, then the TB needs to store a win value. But if the @@ -1200,7 +1200,7 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) // their results and must probe the position itself. The "best" result of these // probes is the correct result for the position. // DTZ tables do not store values when a following move is a zeroing winning move -// (winning capture or winning pawn move). Also DTZ store wrong values for positions +// (winning capture or winning pawn move). Also, DTZ store wrong values for positions // where the best move is an ep-move (even if losing). So in all these cases set // the state to ZEROING_BEST_MOVE. template @@ -1268,9 +1268,9 @@ WDLScore search(Position& pos, ProbeState* result) { } // namespace -/// Tablebases::init() is called at startup and after every change to -/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread -/// safe, nor it needs to be. +// Tablebases::init() is called at startup and after every change to +// "SyzygyPath" UCI option to (re)create the various tables. It is not thread +// safe, nor it needs to be. void Tablebases::init(const std::string& paths) { TBTables.clear(); @@ -1302,7 +1302,7 @@ void Tablebases::init(const std::string& paths) { // MapKK[] encodes all the 462 possible legal positions of two kings where // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 - // diagonal, the other one shall not to be above the a1-h8 diagonal. + // diagonal, the other one shall not be above the a1-h8 diagonal. std::vector> bothOnDiagonal; code = 0; for (int idx = 0; idx < 10; idx++) @@ -1323,7 +1323,7 @@ void Tablebases::init(const std::string& paths) { MapKK[idx][s2] = code++; } - // Legal positions with both kings on diagonal are encoded as last ones + // Legal positions with both kings on a diagonal are encoded as last ones for (auto p : bothOnDiagonal) MapKK[p.first][p.second] = code++; @@ -1338,8 +1338,8 @@ void Tablebases::init(const std::string& paths) { // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible // available squares when the leading one is in 's'. Moreover the pawn with - // highest MapPawns[] is the leading pawn, the one nearest the edge and, - // among pawns with same file, the one with lowest rank. + // highest MapPawns[] is the leading pawn, the one nearest the edge, and + // among pawns with the same file, the one with the lowest rank. int availableSquares = 47; // Available squares when lead pawn is in a2 // Init the tables for the encoding of leading pawns group: with 7-men TB we @@ -1463,7 +1463,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws return 0; - // DTZ stores a 'don't care' value in this case, or even a plain wrong + // DTZ stores a 'don't care value in this case, or even a plain wrong // one as in case the best move is a losing ep, so it cannot be probed. if (*result == ZEROING_BEST_MOVE) return dtz_before_zeroing(wdl); @@ -1490,7 +1490,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // For zeroing moves we want the dtz of the move _before_ doing it, // otherwise we will get the dtz of the next move sequence. Search the // position after the move to get the score sign (because even in a - // winning position we could make a losing capture or going for a draw). + // winning position we could make a losing capture or go for a draw). dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) : -probe_dtz(pos, result); @@ -1548,10 +1548,9 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { } else if (pos.is_draw(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, this must be a true 3-fold - // repetition inside the game history. + // 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, + // this must be a true 3-fold repetition inside the game history. dtz = 0; } else diff --git a/src/thread.cpp b/src/thread.cpp index 60f760ed46e..c752e7326cd 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -40,8 +40,8 @@ namespace Stockfish { ThreadPool Threads; // Global object -/// Thread constructor launches the thread and waits until it goes to sleep -/// in idle_loop(). Note that 'searching' and 'exit' should be already set. +// Thread constructor launches the thread and waits until it goes to sleep +// in idle_loop(). Note that 'searching' and 'exit' should be already set. Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { @@ -49,8 +49,8 @@ Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { } -/// Thread destructor wakes up the thread in idle_loop() and waits -/// for its termination. Thread should be already waiting. +// Thread destructor wakes up the thread in idle_loop() and waits +// for its termination. Thread should be already waiting. Thread::~Thread() { @@ -62,7 +62,7 @@ Thread::~Thread() { } -/// Thread::clear() reset histories, usually before a new game +// Thread::clear() reset histories, usually before a new game void Thread::clear() { @@ -78,7 +78,7 @@ void Thread::clear() { } -/// Thread::start_searching() wakes up the thread that will start the search +// Thread::start_searching() wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -88,8 +88,8 @@ void Thread::start_searching() { } -/// Thread::wait_for_search_finished() blocks on the condition variable -/// until the thread has finished searching. +// Thread::wait_for_search_finished() blocks on the condition variable +// until the thread has finished searching. void Thread::wait_for_search_finished() { @@ -98,15 +98,15 @@ void Thread::wait_for_search_finished() { } -/// Thread::idle_loop() is where the thread is parked, blocked on the -/// condition variable, when it has no work to do. +// Thread::idle_loop() is where the thread is parked, blocked on the +// condition variable, when it has no work to do. void Thread::idle_loop() { // If OS already scheduled us on a different group than 0 then don't overwrite // the choice, eventually we are one of many one-threaded processes running on // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case all this + // just check if running threads are below a threshold, in this case, all this // NUMA machinery is not needed. if (Options["Threads"] > 8) WinProcGroup::bindThisThread(idx); @@ -127,9 +127,9 @@ void Thread::idle_loop() { } } -/// ThreadPool::set() creates/destroys threads to match the requested number. -/// Created and launched threads will immediately go to sleep in idle_loop. -/// Upon resizing, threads are recreated to allow for binding if necessary. +// ThreadPool::set() creates/destroys threads to match the requested number. +// Created and launched threads will immediately go to sleep in idle_loop. +// Upon resizing, threads are recreated to allow for binding if necessary. void ThreadPool::set(size_t requested) { @@ -158,7 +158,7 @@ void ThreadPool::set(size_t requested) { } -/// ThreadPool::clear() sets threadPool data to initial values +// ThreadPool::clear() sets threadPool data to initial values void ThreadPool::clear() { @@ -172,8 +172,8 @@ void ThreadPool::clear() { } -/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and -/// returns immediately. Main thread will wake up other threads and start the search. +// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +// returns immediately. Main thread will wake up other threads and start the search. void ThreadPool::start_thinking(Position& pos, StateListPtr& states, const Search::LimitsType& limits, bool ponderMode) { @@ -225,7 +225,7 @@ Thread* ThreadPool::get_best_thread() const { std::map votes; Value minScore = VALUE_NONE; - // Find minimum score of all threads + // Find the minimum score of all threads for (Thread* th: threads) minScore = std::min(minScore, th->rootMoves[0].score); @@ -256,7 +256,7 @@ Thread* ThreadPool::get_best_thread() const { } -/// Start non-main threads +// Start non-main threads void ThreadPool::start_searching() { @@ -266,7 +266,7 @@ void ThreadPool::start_searching() { } -/// Wait for non-main threads +// Wait for non-main threads void ThreadPool::wait_for_search_finished() const { diff --git a/src/thread.h b/src/thread.h index 8d0adcf0340..44cc56729e3 100644 --- a/src/thread.h +++ b/src/thread.h @@ -34,10 +34,10 @@ namespace Stockfish { -/// Thread class keeps together all the thread-related stuff. We use -/// per-thread pawn and material hash tables so that once we get a -/// pointer to an entry its life time is unlimited and we don't have -/// to care about someone changing the entry under our feet. +// Thread class keeps together all the thread-related stuff. We use +// per-thread pawn and material hash tables so that once we get a +// pointer to an entry its lifetime is unlimited and we don't have +// to care about someone changing the entry under our feet. class Thread { @@ -75,7 +75,7 @@ class Thread { }; -/// MainThread is a derived class specific for main thread +// MainThread is a derived class specific for main thread struct MainThread : public Thread { @@ -94,9 +94,9 @@ struct MainThread : public Thread { }; -/// ThreadPool struct handles all the threads-related stuff like init, starting, -/// parking and, most importantly, launching a thread. All the access to threads -/// is done through this class. +// ThreadPool struct handles all the threads-related stuff like init, starting, +// parking and, most importantly, launching a thread. All the access to threads +// is done through this class. struct ThreadPool { diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 330a8341dd8..77352aa087a 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -21,11 +21,11 @@ #include -/// On OSX threads other than the main thread are created with a reduced stack -/// size of 512KB by default, this is too low for deep searches, which require -/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. -/// The implementation calls pthread_create() with the stack size parameter -/// equal to the linux 8MB default, on platforms that support it. +// On OSX threads other than the main thread are created with a reduced stack +// size of 512KB by default, this is too low for deep searches, which require +// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. +// The implementation calls pthread_create() with the stack size parameter +// equal to the Linux 8MB default, on platforms that support it. #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) diff --git a/src/timeman.cpp b/src/timeman.cpp index 5e57f8f98c5..74f59d90574 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -29,14 +29,14 @@ namespace Stockfish { TimeManagement Time; // Our global time management object -/// TimeManagement::init() is called at the beginning of the search and calculates -/// the bounds of time allowed for the current game ply. We currently support: +// TimeManagement::init() is called at the beginning of the search and calculates +// the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - // if we have no time, no need to initialize TM, except for the start time, + // If we have no time, no need to initialize TM, except for the start time, // which is used by movetime. startTime = limits.startTime; if (limits.time[us] == 0) diff --git a/src/timeman.h b/src/timeman.h index 9ad6bdcccf9..6acdf0ac6db 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -28,8 +28,8 @@ namespace Stockfish { -/// The TimeManagement class computes the optimal time to think depending on -/// the maximum available time, the game move number and other parameters. +// The TimeManagement class computes the optimal time to think depending on +// the maximum available time, the game move number, and other parameters. class TimeManagement { public: diff --git a/src/tt.cpp b/src/tt.cpp index adcfe6289a0..c3aec8d3e7c 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -33,8 +33,8 @@ namespace Stockfish { TranspositionTable TT; // Our global transposition table -/// TTEntry::save() populates the TTEntry with a new node's data, possibly -/// overwriting an old position. Update is not atomic and can be racy. +// TTEntry::save() populates the TTEntry with a new node's data, possibly +// overwriting an old position. The update is not atomic and can be racy. void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { @@ -59,9 +59,9 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) } -/// TranspositionTable::resize() sets the size of the transposition table, -/// measured in megabytes. Transposition table consists of a power of 2 number -/// of clusters and each cluster consists of ClusterSize number of TTEntry. +// TranspositionTable::resize() sets the size of the transposition table, +// measured in megabytes. Transposition table consists of a power of 2 number +// of clusters and each cluster consists of ClusterSize number of TTEntry. void TranspositionTable::resize(size_t mbSize) { @@ -83,7 +83,7 @@ void TranspositionTable::resize(size_t mbSize) { } -/// TranspositionTable::clear() initializes the entire transposition table to zero, +// TranspositionTable::clear() initializes the entire transposition table to zero, // in a multi-threaded way. void TranspositionTable::clear() { @@ -113,12 +113,12 @@ void TranspositionTable::clear() { } -/// TranspositionTable::probe() looks up the current position in the transposition -/// table. It returns true and a pointer to the TTEntry if the position is found. -/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry -/// to be replaced later. The replace value of an entry is calculated as its depth -/// minus 8 times its relative age. TTEntry t1 is considered more valuable than -/// TTEntry t2 if its replace value is greater than that of t2. +// TranspositionTable::probe() looks up the current position in the transposition +// table. It returns true and a pointer to the TTEntry if the position is found. +// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry +// to be replaced later. The replace value of an entry is calculated as its depth +// minus 8 times its relative age. TTEntry t1 is considered more valuable than +// TTEntry t2 if its replace value is greater than that of t2. TTEntry* TranspositionTable::probe(const Key key, bool& found) const { @@ -149,8 +149,8 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { } -/// TranspositionTable::hashfull() returns an approximation of the hashtable -/// occupation during a search. The hash is x permill full, as per UCI protocol. +// TranspositionTable::hashfull() returns an approximation of the hashtable +// occupation during a search. The hash is x permill full, as per UCI protocol. int TranspositionTable::hashfull() const { diff --git a/src/tt.h b/src/tt.h index c11cf085220..fdea4933507 100644 --- a/src/tt.h +++ b/src/tt.h @@ -27,16 +27,16 @@ namespace Stockfish { -/// TTEntry struct is the 10 bytes transposition table entry, defined as below: -/// -/// key 16 bit -/// depth 8 bit -/// generation 5 bit -/// pv node 1 bit -/// bound type 2 bit -/// move 16 bit -/// value 16 bit -/// eval value 16 bit +// TTEntry struct is the 10 bytes transposition table entry, defined as below: +// +// key 16 bit +// depth 8 bit +// generation 5 bit +// pv node 1 bit +// bound type 2 bit +// move 16 bit +// value 16 bit +// eval value 16 bit struct TTEntry { @@ -60,11 +60,11 @@ struct TTEntry { }; -/// A TranspositionTable is an array of Cluster, of size clusterCount. Each -/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry -/// contains information on exactly one position. The size of a Cluster should -/// divide the size of a cache line for best performance, as the cacheline is -/// prefetched when possible. +// A TranspositionTable is an array of Cluster, of size clusterCount. Each +// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry +// contains information on exactly one position. The size of a Cluster should +// divide the size of a cache line for best performance, as the cacheline is +// prefetched when possible. class TranspositionTable { diff --git a/src/tune.h b/src/tune.h index dde03b324ea..a9a7331e566 100644 --- a/src/tune.h +++ b/src/tune.h @@ -49,31 +49,30 @@ struct SetRange { #define SetDefaultRange SetRange(default_range) -/// Tune class implements the 'magic' code that makes the setup of a fishtest -/// tuning session as easy as it can be. Mainly you have just to remove const -/// qualifiers from the variables you want to tune and flag them for tuning, so -/// if you have: -/// -/// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; -/// -/// If you have a my_post_update() function to run after values have been updated, -/// and a my_range() function to set custom Option's min-max values, then you just -/// remove the 'const' qualifiers and write somewhere below in the file: -/// -/// TUNE(SetRange(my_range), myValue, my_post_update); -/// -/// You can also set the range directly, and restore the default at the end -/// -/// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); -/// -/// In case update function is slow and you have many parameters, you can add: -/// -/// UPDATE_ON_LAST(); -/// -/// And the values update, including post update function call, will be done only -/// once, after the engine receives the last UCI option, that is the one defined -/// and created as the last one, so the GUI should send the options in the same -/// order in which have been defined. +// Tune class implements the 'magic' code that makes the setup of a fishtest tuning +// session as easy as it can be. Mainly you have just to remove const qualifiers +// from the variables you want to tune and flag them for tuning, so if you have: +// +// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; +// +// If you have a my_post_update() function to run after values have been updated, +// and a my_range() function to set custom Option's min-max values, then you just +// remove the 'const' qualifiers and write somewhere below in the file: +// +// TUNE(SetRange(my_range), myValue, my_post_update); +// +// You can also set the range directly, and restore the default at the end +// +// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); +// +// In case update function is slow and you have many parameters, you can add: +// +// UPDATE_ON_LAST(); +// +// And the values update, including post update function call, will be done only +// once, after the engine receives the last UCI option, that is the one defined +// and created as the last one, so the GUI should send the options in the same +// order in which have been defined. class Tune { @@ -151,7 +150,7 @@ class Tune { static bool update_on_last; }; -// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add() +// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() #define STRINGIFY(x) #x #define UNIQUE2(x, y) x ## y #define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ diff --git a/src/types.h b/src/types.h index f682e764f17..1fc4d33a95f 100644 --- a/src/types.h +++ b/src/types.h @@ -19,22 +19,22 @@ #ifndef TYPES_H_INCLUDED #define TYPES_H_INCLUDED -/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration -/// is done automatically. To get started type 'make help'. -/// -/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches -/// need to be set manually: -/// -/// -DNDEBUG | Disable debugging mode. Always use this for release. -/// -/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to -/// | run on some very old machines. -/// -/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works -/// | only in 64-bit mode and requires hardware with popcnt support. -/// -/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works -/// | only in 64-bit mode and requires hardware with pext support. +// When compiling with provided Makefile (e.g. for Linux and OSX), configuration +// is done automatically. To get started type 'make help'. +// +// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches +// need to be set manually: +// +// -DNDEBUG | Disable debugging mode. Always use this for release. +// +// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to +// | run on some very old machines. +// +// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +// | only in 64-bit mode and requires hardware with popcnt support. +// +// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works +// | only in 64-bit mode and requires hardware with pext support. #include #include @@ -46,14 +46,14 @@ #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' #endif -/// Predefined macros hell: -/// -/// __GNUC__ Compiler is GCC, Clang or ICX -/// __clang__ Compiler is Clang or ICX -/// __INTEL_LLVM_COMPILER Compiler is ICX -/// _MSC_VER Compiler is MSVC -/// _WIN32 Building on Windows (any) -/// _WIN64 Building on Windows 64 bit +// Predefined macros hell: +// +// __GNUC__ Compiler is GCC, Clang or ICX +// __clang__ Compiler is Clang or ICX +// __INTEL_LLVM_COMPILER Compiler is ICX +// _MSC_VER Compiler is MSVC +// _WIN32 Building on Windows (any) +// _WIN64 Building on Windows 64 bit #if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) #define ALIGNAS_ON_STACK_VARIABLES_BROKEN @@ -107,17 +107,17 @@ using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -/// A move needs 16 bits to be stored -/// -/// bit 0- 5: destination square (from 0 to 63) -/// bit 6-11: origin square (from 0 to 63) -/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) -/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -/// NOTE: en passant bit is set only when a pawn can be captured -/// -/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in -/// any normal move destination square is always different from origin square -/// while MOVE_NONE and MOVE_NULL have the same origin and destination square. +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in +// any normal move destination square is always different from origin square +// while MOVE_NONE and MOVE_NULL have the same origin and destination square. enum Move : int { MOVE_NONE, @@ -291,7 +291,7 @@ ENABLE_INCR_OPERATORS_ON(Rank) #undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON -/// Additional operators to add a Direction to a Square +// Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } inline Square& operator+=(Square& s, Direction d) { return s = s + d; } @@ -405,7 +405,7 @@ constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -/// Based on a congruential pseudo-random number generator +// Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } diff --git a/src/uci.cpp b/src/uci.cpp index f62bb8bf4e0..81bf7aff768 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -233,11 +233,11 @@ namespace { } // namespace -/// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate -/// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a -/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, -/// like running 'bench', the function returns immediately after the command is executed. -/// In addition to the UCI ones, some additional debug commands are also supported. +// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate +// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a +// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, +// like running 'bench', the function returns immediately after the command is executed. +// In addition to the UCI ones, some additional debug commands are also supported. void UCI::loop(int argc, char* argv[]) { @@ -310,18 +310,18 @@ void UCI::loop(int argc, char* argv[]) { } -/// Turns a Value to an integer centipawn number, -/// without treatment of mate and similar special scores. +// Turns a Value to an integer centipawn number, +// without treatment of mate and similar special scores. int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } -/// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: -/// -/// cp The score from the engine's point of view in centipawns. -/// mate Mate in 'y' moves (not plies). If the engine is getting mated, -/// uses negative values for 'y'. +// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: +// +// cp The score from the engine's point of view in centipawns. +// mate Mate in 'y' moves (not plies). If the engine is getting mated, +// uses negative values for 'y'. std::string UCI::value(Value v) { @@ -343,8 +343,8 @@ std::string UCI::value(Value v) { } -/// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation -/// and a game ply based on the data gathered for fishtest LTC games. +// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation +// and a game ply based on the data gathered for fishtest LTC games. std::string UCI::wdl(Value v, int ply) { @@ -359,17 +359,17 @@ std::string UCI::wdl(Value v, int ply) { } -/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) +// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; } -/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). -/// The only special case is castling where the e1g1 notation is printed in -/// standard chess mode and in e1h1 notation it is printed in Chess960 mode. -/// Internally, all castling moves are always encoded as 'king captures rook'. +// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). +// The only special case is castling where the e1g1 notation is printed in +// standard chess mode and in e1h1 notation it is printed in Chess960 mode. +// Internally, all castling moves are always encoded as 'king captures rook'. std::string UCI::move(Move m, bool chess960) { @@ -394,8 +394,8 @@ std::string UCI::move(Move m, bool chess960) { } -/// UCI::to_move() converts a string representing a move in coordinate notation -/// (g1f3, a7a8q) to the corresponding legal Move, if any. +// UCI::to_move() converts a string representing a move in coordinate notation +// (g1f3, a7a8q) to the corresponding legal Move, if any. Move UCI::to_move(const Position& pos, std::string& str) { diff --git a/src/uci.h b/src/uci.h index 7ca97d5c6bc..048f8c1166e 100644 --- a/src/uci.h +++ b/src/uci.h @@ -41,15 +41,15 @@ const int NormalizeToPawnValue = 328; class Option; -/// Define a custom comparator, because the UCI options should be case-insensitive +// Define a custom comparator, because the UCI options should be case-insensitive struct CaseInsensitiveLess { bool operator() (const std::string&, const std::string&) const; }; -/// The options container is defined as a std::map +// The options container is defined as a std::map using OptionsMap = std::map; -/// The Option class implements each option as specified by the UCI protocol +// The Option class implements each option as specified by the UCI protocol class Option { using OnChange = void (*)(const Option&); diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 8d2c5c098ed..b822ccf936f 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -44,7 +44,7 @@ UCI::OptionsMap Options; // Global object namespace UCI { -/// 'On change' actions, triggered by an option's value change +// 'On change' actions, triggered by an option's value change static void on_clear_hash(const Option&) { Search::clear(); } static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } static void on_logger(const Option& o) { start_logger(o); } @@ -52,7 +52,7 @@ static void on_threads(const Option& o) { Threads.set(size_t(o)); } static void on_tb_path(const Option& o) { Tablebases::init(o); } static void on_eval_file(const Option&) { Eval::NNUE::init(); } -/// Our case insensitive less() function as required by UCI protocol +// Our case insensitive less() function as required by UCI protocol bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), @@ -60,7 +60,7 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const } -/// UCI::init() initializes the UCI options to their hard-coded default values +// UCI::init() initializes the UCI options to their hard-coded default values void init(OptionsMap& o) { @@ -89,8 +89,8 @@ void init(OptionsMap& o) { } -/// operator<<() is used to print all the options default values in chronological -/// insertion order (the idx field) and in the format defined by the UCI protocol. +// operator<<() is used to print all the options default values in chronological +// insertion order (the idx field) and in the format defined by the UCI protocol. std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { @@ -116,7 +116,7 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { } -/// Option class constructors and conversion operators +// Option class constructors and conversion operators Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) { defaultValue = currentValue = v; } @@ -150,7 +150,7 @@ bool Option::operator==(const char* s) const { } -/// operator<<() inits options and assigns idx in the correct printing order +// operator<<() inits options and assigns idx in the correct printing order void Option::operator<<(const Option& o) { @@ -161,9 +161,9 @@ void Option::operator<<(const Option& o) { } -/// operator=() 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. +// operator=() 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. Option& Option::operator=(const string& v) { From 057046cc9a114e38d9f616fa58cf230e9b15b9a7 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sat, 14 Oct 2023 18:08:51 +0300 Subject: [PATCH 0247/1309] Cleanup qsearch continuation histories Only (ss-1) and (ss-2) are used in qsearch. closes https://github.com/official-stockfish/Stockfish/pull/4828 No functional change --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 16c6b0f38f8..0ebf4e20276 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1506,9 +1506,7 @@ namespace { futilityBase = std::min(ss->staticEval, bestValue) + 200; } - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - (ss-3)->continuationHistory, (ss-4)->continuationHistory, - nullptr , (ss-6)->continuationHistory }; + const PieceToHistory* contHist[] = {(ss-1)->continuationHistory, (ss-2)->continuationHistory}; // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, From d3d0c69dc1baf03c93252da3583b1b746c5a7ab6 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 18 Oct 2023 19:25:09 -0700 Subject: [PATCH 0248/1309] Remove outdated Tile naming. cleanup variable naming after #4816 closes #4833 No functional change --- src/nnue/nnue_feature_transformer.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 25f686dacbc..9f02830a63e 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -395,9 +395,9 @@ namespace Stockfish::Eval::NNUE { { assert(states_to_update[0]); - auto accTileIn = reinterpret_cast( + auto accIn = reinterpret_cast( &st->accumulator.accumulation[Perspective][0]); - auto accTileOut = reinterpret_cast( + auto accOut = reinterpret_cast( &states_to_update[0]->accumulator.accumulation[Perspective][0]); const IndexType offsetR0 = HalfDimensions * removed[0][0]; @@ -408,7 +408,7 @@ namespace Stockfish::Eval::NNUE { if (removed[0].size() == 1) { for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accTileOut[k] = vec_add_16(vec_sub_16(accTileIn[k], columnR0[k]), columnA[k]); + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); } else { @@ -416,14 +416,14 @@ namespace Stockfish::Eval::NNUE { auto columnR1 = reinterpret_cast(&weights[offsetR1]); for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accTileOut[k] = vec_sub_16( - vec_add_16(accTileIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); + accOut[k] = vec_sub_16( + vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); } - auto accTilePsqtIn = reinterpret_cast( + auto accPsqtIn = reinterpret_cast( &st->accumulator.psqtAccumulation[Perspective][0]); - auto accTilePsqtOut = reinterpret_cast( + auto accPsqtOut = reinterpret_cast( &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; @@ -434,8 +434,8 @@ namespace Stockfish::Eval::NNUE { if (removed[0].size() == 1) { for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accTilePsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( - accTilePsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); + accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( + accPsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); } else { @@ -443,9 +443,9 @@ namespace Stockfish::Eval::NNUE { auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accTilePsqtOut[k] = vec_sub_psqt_32( - vec_add_psqt_32(accTilePsqtIn[k], columnPsqtA[k]), - vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); + accPsqtOut[k] = vec_sub_psqt_32( + vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), + vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); } } else From 90c18b0b500a5226717353a37a82cd026d71b616 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 19 Oct 2023 14:38:07 +0300 Subject: [PATCH 0249/1309] Removing history condition Removing the bad history condition from the skip futility pruning formula. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 142688 W: 36420 L: 36317 D: 69951 Ptnml(0-2): 481, 16653, 36970, 16762, 478 https://tests.stockfishchess.org/tests/view/65270a663125598fc7eb8c67 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 435378 W: 110723 L: 110925 D: 213730 Ptnml(0-2): 278, 47251, 122788, 47139, 233 https://tests.stockfishchess.org/tests/view/6528595f3125598fc7eba8f5 closes https://github.com/official-stockfish/Stockfish/pull/4834 Bench: 1110579 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0ebf4e20276..6214fb5b39b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -786,8 +786,7 @@ namespace { && eval >= beta && eval < 29462 // smaller than TB wins && !( !ttCapture - && ttMove - && thisThread->mainHistory[us][from_to(ttMove)] < 989)) + && ttMove)) return eval; // Step 9. Null move search with verification search (~35 Elo) From e18619d07802882136b583a01e56791b61abede6 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:31:23 +0200 Subject: [PATCH 0250/1309] Subtract the margin from the value returned by ProbCut This patch subtracts the margin from the value returned by ProbCut. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 46112 W: 11940 L: 11610 D: 22562 Ptnml(0-2): 131, 5318, 11842, 5620, 145 https://tests.stockfishchess.org/tests/view/652ea42ade6d262d08d329dd Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 86880 W: 22192 L: 21776 D: 42912 Ptnml(0-2): 43, 9213, 24510, 9633, 41 https://tests.stockfishchess.org/tests/view/652f70ffde6d262d08d33e8d closes https://github.com/official-stockfish/Stockfish/pull/4835 bench: 1135313 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 6214fb5b39b..baf819687c5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -897,7 +897,7 @@ namespace { { // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); - return value; + return value - (probCutBeta - beta); } } From 8366ec48ae6fc57dffad849b20844d5b07f963b4 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 20 Oct 2023 02:23:46 -0700 Subject: [PATCH 0251/1309] Scale down stat bonus STC https://tests.stockfishchess.org/tests/view/652eff58de6d262d08d33353 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 88224 W: 22618 L: 22228 D: 43378 Ptnml(0-2): 282, 10177, 22783, 10609, 261 LTC https://tests.stockfishchess.org/tests/view/652fd13bde6d262d08d3481a LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 143508 W: 36674 L: 36142 D: 70692 Ptnml(0-2): 92, 15240, 40534, 15820, 68 Reduces the stat bonus by 20%. Maybe future patches can tune the actual bonus calculations for different histories. closes https://github.com/official-stockfish/Stockfish/pull/4836 bench: 1185932 --- src/movepick.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.h b/src/movepick.h index 652ef16166d..457defa525a 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -51,7 +51,7 @@ class StatsEntry { assert(abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += bonus - entry * abs(bonus) / D; + entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); assert(abs(entry) <= D); } From 2d0237db3f0e596fb06e3ffbadba84dcc4e018f6 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 21 Oct 2023 11:40:56 +0200 Subject: [PATCH 0252/1309] add clang-format This introduces clang-format to enforce a consistent code style for Stockfish. Having a documented and consistent style across the code will make contributing easier for new developers, and will make larger changes to the codebase easier to make. To facilitate formatting, this PR includes a Makefile target (`make format`) to format the code, this requires clang-format (version 17 currently) to be installed locally. Installing clang-format is straightforward on most OS and distros (e.g. with https://apt.llvm.org/, brew install clang-format, etc), as this is part of quite commonly used suite of tools and compilers (llvm / clang). Additionally, a CI action is present that will verify if the code requires formatting, and comment on the PR as needed. Initially, correct formatting is not required, it will be done by maintainers as part of the merge or in later commits, but obviously this is encouraged. fixes https://github.com/official-stockfish/Stockfish/issues/3608 closes https://github.com/official-stockfish/Stockfish/pull/4790 Co-Authored-By: Joost VandeVondele --- .clang-format | 44 + .github/workflows/stockfish_format_check.yml | 51 + CONTRIBUTING.md | 5 +- src/Makefile | 17 + src/benchmark.cpp | 84 +- src/benchmark.h | 4 +- src/bitboard.cpp | 136 +- src/bitboard.h | 280 ++- src/evaluate.cpp | 172 +- src/evaluate.h | 30 +- src/main.cpp | 24 +- src/misc.cpp | 852 ++++--- src/misc.h | 106 +- src/movegen.cpp | 127 +- src/movegen.h | 53 +- src/movepick.cpp | 520 ++-- src/movepick.h | 143 +- src/nnue/evaluate_nnue.cpp | 358 +-- src/nnue/evaluate_nnue.h | 54 +- src/nnue/features/half_ka_v2_hm.cpp | 93 +- src/nnue/features/half_ka_v2_hm.h | 70 +- src/nnue/layers/affine_transform.h | 430 ++-- .../layers/affine_transform_sparse_input.h | 322 +-- src/nnue/layers/clipped_relu.h | 214 +- src/nnue/layers/simd.h | 312 ++- src/nnue/layers/sqr_clipped_relu.h | 103 +- src/nnue/nnue_accumulator.h | 10 +- src/nnue/nnue_architecture.h | 163 +- src/nnue/nnue_common.h | 451 ++-- src/nnue/nnue_feature_transformer.h | 1041 ++++---- src/position.cpp | 1829 +++++++------- src/position.h | 485 ++-- src/search.cpp | 2176 ++++++++--------- src/search.h | 92 +- src/syzygy/tbprobe.cpp | 613 ++--- src/syzygy/tbprobe.h | 30 +- src/thread.cpp | 237 +- src/thread.h | 150 +- src/thread_win32_osx.h | 45 +- src/timeman.cpp | 138 +- src/timeman.h | 31 +- src/tt.cpp | 156 +- src/tt.h | 106 +- src/tune.cpp | 93 +- src/tune.h | 191 +- src/types.h | 432 ++-- src/uci.cpp | 405 +-- src/uci.h | 52 +- src/ucioption.cpp | 190 +- 49 files changed, 6963 insertions(+), 6757 deletions(-) create mode 100644 .clang-format create mode 100644 .github/workflows/stockfish_format_check.yml diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000000..c71f0368ed5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,44 @@ +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveDeclarations: Consecutive +AlignEscapedNewlines: DontAlign +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AlwaysBreakTemplateDeclarations: Yes +BasedOnStyle: WebKit +BitFieldColonSpacing: After +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: false + AfterClass: false + AfterControlStatement: true + BeforeElse: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +BreakStringLiterals: false +ColumnLimit: 100 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +IndentGotoLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 4 +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +PackConstructorInitializers: Never +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: false +SpaceBeforeCaseColon: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeInheritanceColon: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 diff --git a/.github/workflows/stockfish_format_check.yml b/.github/workflows/stockfish_format_check.yml new file mode 100644 index 00000000000..cb16b327871 --- /dev/null +++ b/.github/workflows/stockfish_format_check.yml @@ -0,0 +1,51 @@ +# This workflow will run clang-format and comment on the PR. +# Because of security reasons, it is crucial that this workflow +# executes no shell script nor runs make. +# Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + +name: Stockfish +on: + pull_request_target: + branches: + - 'master' + paths: + - '**.cpp' + - '**.h' +jobs: + Stockfish: + name: clang-format check + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Run clang-format style check + uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e + id: clang-format + continue-on-error: true + with: + clang-format-version: '17' + exclude-regex: 'incbin' + + - name: Comment on PR + if: steps.clang-format.outcome == 'failure' + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + with: + message: | + clang-format 17 needs to be run on this PR. + If you do not have clang-format installed, the maintainer will run it when merging. + For the exact version please see https://packages.ubuntu.com/mantic/clang-format-17. + + _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ + comment_tag: execution + + - name: Comment on PR + if: steps.clang-format.outcome != 'failure' + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + with: + message: | + _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ + create_if_not_exists: false + comment_tag: execution + mode: delete diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7667a942325..9e72e1db6bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,8 +57,9 @@ discussion._ ## Code Style -We do not have a strict code style. But it is best to stick to the existing -style of the file you are editing. +Changes to Stockfish C++ code should respect our coding style defined by +[.clang-format](.clang-format). You can format your changes by running +`make format`. This requires clang-format version 17 to be installed on your system. ## Community and Communication diff --git a/src/Makefile b/src/Makefile index 5b43c35fdd7..7b7ee41b654 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,6 +57,14 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp +HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ + nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ + nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ + nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ + search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ + tt.h tune.h types.h uci.h + OBJS = $(notdir $(SRCS:.cpp=.o)) VPATH = syzygy:nnue:nnue/features @@ -145,6 +153,12 @@ dotprod = no arm_version = 0 STRIP = strip +ifneq ($(shell command -v clang-format-17),) + CLANG-FORMAT = clang-format-17 +else + CLANG-FORMAT = clang-format +endif + ### 2.2 Architecture specific ifeq ($(findstring x86,$(ARCH)),x86) @@ -936,6 +950,9 @@ net: netvariables fi; \ fi; \ +format: + $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file:../.clang-format + # default target default: help diff --git a/src/benchmark.cpp b/src/benchmark.cpp index d67e37f66ed..63598e750e8 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -27,6 +27,7 @@ namespace { +// clang-format off const std::vector Defaults = { "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", @@ -90,8 +91,9 @@ const std::vector Defaults = { "nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1", "setoption name UCI_Chess960 value false" }; +// clang-format on -} // namespace +} // namespace namespace Stockfish { @@ -109,56 +111,56 @@ namespace Stockfish { std::vector setup_bench(const Position& current, std::istream& is) { - std::vector fens, list; - std::string go, token; + std::vector fens, list; + std::string go, token; - // Assign default values to missing arguments - std::string ttSize = (is >> token) ? token : "16"; - std::string threads = (is >> token) ? token : "1"; - std::string limit = (is >> token) ? token : "13"; - std::string fenFile = (is >> token) ? token : "default"; - std::string limitType = (is >> token) ? token : "depth"; + // Assign default values to missing arguments + std::string ttSize = (is >> token) ? token : "16"; + std::string threads = (is >> token) ? token : "1"; + std::string limit = (is >> token) ? token : "13"; + std::string fenFile = (is >> token) ? token : "default"; + std::string limitType = (is >> token) ? token : "depth"; - go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; + go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; - if (fenFile == "default") - fens = Defaults; + if (fenFile == "default") + fens = Defaults; - else if (fenFile == "current") - fens.push_back(current.fen()); + else if (fenFile == "current") + fens.push_back(current.fen()); - else - { - std::string fen; - std::ifstream file(fenFile); + else + { + std::string fen; + std::ifstream file(fenFile); - if (!file.is_open()) - { - std::cerr << "Unable to open file " << fenFile << std::endl; - exit(EXIT_FAILURE); - } + if (!file.is_open()) + { + std::cerr << "Unable to open file " << fenFile << std::endl; + exit(EXIT_FAILURE); + } - while (getline(file, fen)) - if (!fen.empty()) - fens.push_back(fen); + while (getline(file, fen)) + if (!fen.empty()) + fens.push_back(fen); - file.close(); - } + file.close(); + } - list.emplace_back("setoption name Threads value " + threads); - list.emplace_back("setoption name Hash value " + ttSize); - list.emplace_back("ucinewgame"); + list.emplace_back("setoption name Threads value " + threads); + list.emplace_back("setoption name Hash value " + ttSize); + list.emplace_back("ucinewgame"); - for (const std::string& fen : fens) - if (fen.find("setoption") != std::string::npos) - list.emplace_back(fen); - else - { - list.emplace_back("position fen " + fen); - list.emplace_back(go); - } + for (const std::string& fen : fens) + if (fen.find("setoption") != std::string::npos) + list.emplace_back(fen); + else + { + list.emplace_back("position fen " + fen); + list.emplace_back(go); + } - return list; + return list; } -} // namespace Stockfish +} // namespace Stockfish \ No newline at end of file diff --git a/src/benchmark.h b/src/benchmark.h index 64acf833ac0..e6206d19349 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -29,6 +29,6 @@ class Position; std::vector setup_bench(const Position&, std::istream&); -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef BENCHMARK_H_INCLUDED +#endif // #ifndef BENCHMARK_H_INCLUDED diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 89eeee611f8..fff7eba9e6f 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -39,10 +39,10 @@ Magic BishopMagics[SQUARE_NB]; namespace { - Bitboard RookTable[0x19000]; // To store rook attacks - Bitboard BishopTable[0x1480]; // To store bishop attacks +Bitboard RookTable[0x19000]; // To store rook attacks +Bitboard BishopTable[0x1480]; // To store bishop attacks - void init_magics(PieceType pt, Bitboard table[], Magic magics[]); +void init_magics(PieceType pt, Bitboard table[], Magic magics[]); } @@ -60,18 +60,18 @@ inline Bitboard safe_destination(Square s, int step) { std::string Bitboards::pretty(Bitboard b) { - std::string s = "+---+---+---+---+---+---+---+---+\n"; + std::string s = "+---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - s += b & make_square(f, r) ? "| X " : "| "; + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + s += b & make_square(f, r) ? "| X " : "| "; - s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; - } - s += " a b c d e f g h\n"; + s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; + } + s += " a b c d e f g h\n"; - return s; + return s; } @@ -80,49 +80,50 @@ std::string Bitboards::pretty(Bitboard b) { void Bitboards::init() { - for (unsigned i = 0; i < (1 << 16); ++i) - PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); - - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - - init_magics(ROOK, RookTable, RookMagics); - init_magics(BISHOP, BishopTable, BishopMagics); - - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - { - PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); - - for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} ) - PseudoAttacks[KING][s1] |= safe_destination(s1, step); - - for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} ) - PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); - - PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); - PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); - - for (PieceType pt : { BISHOP, ROOK }) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - { - if (PseudoAttacks[pt][s1] & s2) - { - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); - } - BetweenBB[s1][s2] |= s2; - } - } + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + + init_magics(ROOK, RookTable, RookMagics); + init_magics(BISHOP, BishopTable, BishopMagics); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + + for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) + PseudoAttacks[KING][s1] |= safe_destination(s1, step); + + for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) + PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); + + PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); + PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb(s1, 0); + + for (PieceType pt : {BISHOP, ROOK}) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + if (PseudoAttacks[pt][s1] & s2) + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = + (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + } + BetweenBB[s1][s2] |= s2; + } + } } namespace { - Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { +Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { - Bitboard attacks = 0; - Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + Bitboard attacks = 0; + Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) @@ -133,22 +134,22 @@ namespace { } return attacks; - } +} - // init_magics() computes all rook and bishop attacks at startup. Magic - // bitboards are used to look up attacks of sliding pieces. As a reference see - // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so - // called "fancy" approach. +// init_magics() computes all rook and bishop attacks at startup. Magic +// bitboards are used to look up attacks of sliding pieces. As a reference see +// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so +// called "fancy" approach. - void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { +void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { // Optimal PRNG seeds to pick the correct magics in the shortest time - int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, - { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; + int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, + {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; Bitboard occupancy[4096], reference[4096], edges, b; - int epoch[4096] = {}, cnt = 0, size = 0; + int epoch[4096] = {}, cnt = 0, size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { @@ -161,8 +162,8 @@ namespace { // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. Magic& m = magics[s]; - m.mask = sliding_attack(pt, s, 0) & ~edges; - m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); + m.mask = sliding_attack(pt, s, 0) & ~edges; + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); // Set the offset for the attacks table of the square. We have individual // table sizes for each square with "Fancy Magic Bitboards". @@ -171,7 +172,8 @@ namespace { // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. b = size = 0; - do { + do + { occupancy[size] = b; reference[size] = sliding_attack(pt, s, b); @@ -189,9 +191,9 @@ namespace { // Find a magic for square 's' picking up an (almost) random number // until we find the one that passes the verification test. - for (int i = 0; i < size; ) + for (int i = 0; i < size;) { - for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; ) + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;) m.magic = rng.sparse_rand(); // A good magic must map every possible occupancy to an index that @@ -206,7 +208,7 @@ namespace { if (epoch[idx] < cnt) { - epoch[idx] = cnt; + epoch[idx] = cnt; m.attacks[idx] = reference[i]; } else if (m.attacks[idx] != reference[i]) @@ -214,7 +216,7 @@ namespace { } } } - } +} } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/bitboard.h b/src/bitboard.h index 0908c957058..03a511361f4 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -32,10 +32,10 @@ namespace Stockfish { namespace Bitboards { -void init(); +void init(); std::string pretty(Bitboard b); -} // namespace Stockfish::Bitboards +} // namespace Stockfish::Bitboards constexpr Bitboard FileABB = 0x0101010101010101ULL; constexpr Bitboard FileBBB = FileABB << 1; @@ -66,85 +66,80 @@ extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square struct Magic { - Bitboard mask; - Bitboard magic; - Bitboard* attacks; - unsigned shift; + Bitboard mask; + Bitboard magic; + Bitboard* attacks; + unsigned shift; - // Compute the attack's index using the 'magic bitboards' approach - unsigned index(Bitboard occupied) const { + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { - if (HasPext) - return unsigned(pext(occupied, mask)); + if (HasPext) + return unsigned(pext(occupied, mask)); - if (Is64Bit) - return unsigned(((occupied & mask) * magic) >> shift); + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); - unsigned lo = unsigned(occupied) & unsigned(mask); - unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); - return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; - } + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + } }; extern Magic RookMagics[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; inline Bitboard square_bb(Square s) { - assert(is_ok(s)); - return (1ULL << s); + assert(is_ok(s)); + return (1ULL << s); } // Overloads of bitwise operators between a Bitboard and a Square for testing // whether a given bit is set in a bitboard, and for setting and clearing bits. -inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } -inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } -inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); } +inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } +inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } +inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } -inline Bitboard operator&(Square s, Bitboard b) { return b & s; } -inline Bitboard operator|(Square s, Bitboard b) { return b | s; } -inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } +inline Bitboard operator&(Square s, Bitboard b) { return b & s; } +inline Bitboard operator|(Square s, Bitboard b) { return b | s; } +inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } -inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } +inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } -constexpr bool more_than_one(Bitboard b) { - return b & (b - 1); -} +constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } // rank_bb() and file_bb() return a bitboard representing all the squares on // the given file or rank. -constexpr Bitboard rank_bb(Rank r) { - return Rank1BB << (8 * r); -} +constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); } -constexpr Bitboard rank_bb(Square s) { - return rank_bb(rank_of(s)); -} +constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); } -constexpr Bitboard file_bb(File f) { - return FileABB << f; -} +constexpr Bitboard file_bb(File f) { return FileABB << f; } -constexpr Bitboard file_bb(Square s) { - return file_bb(file_of(s)); -} +constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); } // shift() moves a bitboard one or two steps as specified by the direction D template constexpr Bitboard shift(Bitboard b) { - return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 - : D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16 - : D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1 - : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7 - : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 - : 0; + return D == NORTH ? b << 8 + : D == SOUTH ? b >> 8 + : D == NORTH + NORTH ? b << 16 + : D == SOUTH + SOUTH ? b >> 16 + : D == EAST ? (b & ~FileHBB) << 1 + : D == WEST ? (b & ~FileABB) >> 1 + : D == NORTH_EAST ? (b & ~FileHBB) << 9 + : D == NORTH_WEST ? (b & ~FileABB) << 7 + : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 + : D == SOUTH_WEST ? (b & ~FileABB) >> 9 + : 0; } @@ -153,14 +148,14 @@ constexpr Bitboard shift(Bitboard b) { template constexpr Bitboard pawn_attacks_bb(Bitboard b) { - return C == WHITE ? shift(b) | shift(b) - : shift(b) | shift(b); + return C == WHITE ? shift(b) | shift(b) + : shift(b) | shift(b); } inline Bitboard pawn_attacks_bb(Color c, Square s) { - assert(is_ok(s)); - return PawnAttacks[c][s]; + assert(is_ok(s)); + return PawnAttacks[c][s]; } // line_bb() returns a bitboard representing an entire line (from board edge @@ -170,9 +165,9 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { inline Bitboard line_bb(Square s1, Square s2) { - assert(is_ok(s1) && is_ok(s2)); + assert(is_ok(s1) && is_ok(s2)); - return LineBB[s1][s2]; + return LineBB[s1][s2]; } @@ -186,26 +181,34 @@ inline Bitboard line_bb(Square s1, Square s2) { inline Bitboard between_bb(Square s1, Square s2) { - assert(is_ok(s1) && is_ok(s2)); + assert(is_ok(s1) && is_ok(s2)); - return BetweenBB[s1][s2]; + return BetweenBB[s1][s2]; } // aligned() returns true if the squares s1, s2 and s3 are aligned either on a // straight or on a diagonal line. -inline bool aligned(Square s1, Square s2, Square s3) { - return line_bb(s1, s2) & s3; -} +inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } // distance() functions return the distance between x and y, defined as the // number of steps for a king in x to reach y. -template inline int distance(Square x, Square y); -template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } -template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } -template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } +template +inline int distance(Square x, Square y); +template<> +inline int distance(Square x, Square y) { + return std::abs(file_of(x) - file_of(y)); +} +template<> +inline int distance(Square x, Square y) { + return std::abs(rank_of(x) - rank_of(y)); +} +template<> +inline int distance(Square x, Square y) { + return SquareDistance[x][y]; +} inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } @@ -215,9 +218,9 @@ inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } template inline Bitboard attacks_bb(Square s) { - assert((Pt != PAWN) && (is_ok(s))); + assert((Pt != PAWN) && (is_ok(s))); - return PseudoAttacks[Pt][s]; + return PseudoAttacks[Pt][s]; } @@ -228,28 +231,36 @@ inline Bitboard attacks_bb(Square s) { template inline Bitboard attacks_bb(Square s, Bitboard occupied) { - assert((Pt != PAWN) && (is_ok(s))); - - switch (Pt) - { - case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; - case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)]; - case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return PseudoAttacks[Pt][s]; - } + assert((Pt != PAWN) && (is_ok(s))); + + switch (Pt) + { + case BISHOP : + return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; + case ROOK : + return RookMagics[s].attacks[RookMagics[s].index(occupied)]; + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[Pt][s]; + } } inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { - assert((pt != PAWN) && (is_ok(s))); - - switch (pt) - { - case BISHOP: return attacks_bb(s, occupied); - case ROOK : return attacks_bb< ROOK>(s, occupied); - case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return PseudoAttacks[pt][s]; - } + assert((pt != PAWN) && (is_ok(s))); + + switch (pt) + { + case BISHOP : + return attacks_bb(s, occupied); + case ROOK : + return attacks_bb(s, occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[pt][s]; + } } @@ -259,16 +270,19 @@ 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]]; + 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]]; #elif defined(_MSC_VER) - return int(_mm_popcnt_u64(b)); + return int(_mm_popcnt_u64(b)); -#else // Assumed gcc or compatible compiler +#else // Assumed gcc or compatible compiler - return __builtin_popcountll(b); + return __builtin_popcountll(b); #endif } @@ -279,66 +293,72 @@ inline int popcount(Bitboard b) { #if defined(__GNUC__) // GCC, Clang, ICX inline Square lsb(Bitboard b) { - assert(b); - return Square(__builtin_ctzll(b)); + assert(b); + return Square(__builtin_ctzll(b)); } inline Square msb(Bitboard b) { - assert(b); - return Square(63 ^ __builtin_clzll(b)); + assert(b); + return Square(63 ^ __builtin_clzll(b)); } #elif defined(_MSC_VER) // MSVC -#ifdef _WIN64 // MSVC, WIN64 + #ifdef _WIN64 // MSVC, WIN64 inline Square lsb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanForward64(&idx, b); - return (Square) idx; + assert(b); + unsigned long idx; + _BitScanForward64(&idx, b); + return (Square) idx; } inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanReverse64(&idx, b); - return (Square) idx; + assert(b); + unsigned long idx; + _BitScanReverse64(&idx, b); + return (Square) idx; } -#else // MSVC, WIN32 + #else // MSVC, WIN32 inline Square lsb(Bitboard b) { - assert(b); - unsigned long idx; - - if (b & 0xffffffff) { - _BitScanForward(&idx, int32_t(b)); - return Square(idx); - } else { - _BitScanForward(&idx, int32_t(b >> 32)); - return Square(idx + 32); - } + assert(b); + unsigned long idx; + + if (b & 0xffffffff) + { + _BitScanForward(&idx, int32_t(b)); + return Square(idx); + } + else + { + _BitScanForward(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } } inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; - - if (b >> 32) { - _BitScanReverse(&idx, int32_t(b >> 32)); - return Square(idx + 32); - } else { - _BitScanReverse(&idx, int32_t(b)); - return Square(idx); - } + assert(b); + unsigned long idx; + + if (b >> 32) + { + _BitScanReverse(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + else + { + _BitScanReverse(&idx, int32_t(b)); + return Square(idx); + } } -#endif + #endif #else // Compiler is neither GCC nor MSVC compatible -#error "Compiler not supported." + #error "Compiler not supported." #endif @@ -346,19 +366,19 @@ inline Square msb(Bitboard b) { // square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). inline Bitboard least_significant_square_bb(Bitboard b) { - assert(b); - return b & -b; + assert(b); + return b & -b; } // pop_lsb() finds and clears the least significant bit in a non-zero bitboard inline Square pop_lsb(Bitboard& b) { - assert(b); - const Square s = lsb(b); - b &= b - 1; - return s; + assert(b); + const Square s = lsb(b); + b &= b - 1; + return s; } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef BITBOARD_H_INCLUDED +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3eb7ee850a3..00498bf02f0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -43,11 +43,11 @@ // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) - INCBIN(EmbeddedNNUE, EvalFileDefaultName); +INCBIN(EmbeddedNNUE, EvalFileDefaultName); #else - const unsigned char gEmbeddedNNUEData[1] = {0x0}; - const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; - const unsigned int gEmbeddedNNUESize = 1; +const unsigned char gEmbeddedNNUEData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; +const unsigned int gEmbeddedNNUESize = 1; #endif @@ -55,27 +55,28 @@ namespace Stockfish { namespace Eval { - std::string currentEvalFileName = "None"; +std::string currentEvalFileName = "None"; - // NNUE::init() tries to load a NNUE network at startup time, or when the engine - // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - // The name of the NNUE network is always retrieved from the EvalFile option. - // We search the given network in three locations: internally (the default - // network may be embedded in the binary), in the active working directory and - // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY - // variable to have the engine search in a special directory in their distro. +// NNUE::init() tries to load a NNUE network at startup time, or when the engine +// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" +// The name of the NNUE network is always retrieved from the EvalFile option. +// We search the given network in three locations: internally (the default +// network may be embedded in the binary), in the active working directory and +// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY +// variable to have the engine search in a special directory in their distro. - void NNUE::init() { +void NNUE::init() { std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) eval_file = EvalFileDefaultName; - #if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; - #else - std::vector dirs = { "" , "" , CommandLine::binaryDirectory }; - #endif +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", CommandLine::binaryDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", CommandLine::binaryDirectory}; +#endif for (const std::string& directory : dirs) if (currentEvalFileName != eval_file) @@ -90,23 +91,28 @@ namespace Eval { if (directory == "" && eval_file == EvalFileDefaultName) { // C++ way to prepare a buffer for a memory stream - class MemoryBuffer : public std::basic_streambuf { - public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } }; - MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), - size_t(gEmbeddedNNUESize)); - (void) gEmbeddedNNUEEnd; // Silence warning on unused variable + MemoryBuffer buffer( + const_cast(reinterpret_cast(gEmbeddedNNUEData)), + size_t(gEmbeddedNNUESize)); + (void) gEmbeddedNNUEEnd; // Silence warning on unused variable std::istream stream(&buffer); if (NNUE::load_eval(eval_file, stream)) currentEvalFileName = eval_file; } } - } +} - // NNUE::verify() verifies that the last net used was loaded successfully - void NNUE::verify() { +// NNUE::verify() verifies that the last net used was loaded successfully +void NNUE::verify() { std::string eval_file = std::string(Options["EvalFile"]); if (eval_file.empty()) @@ -115,10 +121,14 @@ namespace Eval { if (currentEvalFileName != eval_file) { - std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); + std::string msg3 = + "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; + std::string msg4 = + "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + + std::string(EvalFileDefaultName); std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; @@ -131,7 +141,7 @@ namespace Eval { } sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; - } +} } @@ -140,8 +150,8 @@ namespace Eval { // an approximation of the material advantage on the board in terms of pawns. Value Eval::simple_eval(const Position& pos, Color c) { - return PawnValue * (pos.count(c) - pos.count(~c)) - + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } @@ -150,43 +160,41 @@ Value Eval::simple_eval(const Position& pos, Color c) { Value Eval::evaluate(const Position& pos) { - assert(!pos.checkers()); + assert(!pos.checkers()); - Value v; - Color stm = pos.side_to_move(); - int shuffling = pos.rule50_count(); - int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); + Value v; + Color stm = pos.side_to_move(); + int shuffling = pos.rule50_count(); + int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - bool lazy = abs(simpleEval) >= RookValue + KnightValue - + 16 * shuffling * shuffling - + abs(pos.this_thread()->bestValue) - + abs(pos.this_thread()->rootSimpleEval); + bool lazy = abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling + + abs(pos.this_thread()->bestValue) + + abs(pos.this_thread()->rootSimpleEval); - if (lazy) - v = Value(simpleEval); - else - { - int nnueComplexity; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + if (lazy) + v = Value(simpleEval); + else + { + int nnueComplexity; + Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - Value optimism = pos.this_thread()->optimism[stm]; + Value optimism = pos.this_thread()->optimism[stm]; - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; - int npm = pos.non_pawn_material() / 64; - v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm )) / 1024; - } + int npm = pos.non_pawn_material() / 64; + v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; + } - // Damp down the evaluation linearly when shuffling - v = v * (200 - shuffling) / 214; + // Damp down the evaluation linearly when shuffling + v = v * (200 - shuffling) / 214; - // Guarantee evaluation does not hit the tablebase range - v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - return v; + return v; } // trace() is like evaluate(), but instead of returning a value, it returns @@ -196,33 +204,33 @@ Value Eval::evaluate(const Position& pos) { std::string Eval::trace(Position& pos) { - if (pos.checkers()) - return "Final evaluation: none (in check)"; + if (pos.checkers()) + return "Final evaluation: none (in check)"; - // Reset any global variable used in eval - pos.this_thread()->bestValue = VALUE_ZERO; - pos.this_thread()->rootSimpleEval = VALUE_ZERO; - pos.this_thread()->optimism[WHITE] = VALUE_ZERO; - pos.this_thread()->optimism[BLACK] = VALUE_ZERO; + // Reset any global variable used in eval + pos.this_thread()->bestValue = VALUE_ZERO; + pos.this_thread()->rootSimpleEval = VALUE_ZERO; + pos.this_thread()->optimism[WHITE] = VALUE_ZERO; + pos.this_thread()->optimism[BLACK] = VALUE_ZERO; - std::stringstream ss; - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos) << '\n'; - ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; + Value v; + v = NNUE::evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; - ss << " [with scaled NNUE, ...]"; - ss << "\n"; + v = evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; + ss << " [with scaled NNUE, ...]"; + ss << "\n"; - return ss.str(); + return ss.str(); } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/evaluate.h b/src/evaluate.h index 26f2fc4f985..2ab477eced2 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,27 +29,27 @@ class Position; namespace Eval { - std::string trace(Position& pos); +std::string trace(Position& pos); - Value simple_eval(const Position& pos, Color c); - Value evaluate(const Position& pos); +Value simple_eval(const Position& pos, Color c); +Value evaluate(const Position& pos); - extern std::string currentEvalFileName; +extern std::string currentEvalFileName; - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue - // for the build process (profile-build and fishtest) to work. Do not change the - // name of the macro, as it is used in the Makefile. - #define EvalFileDefaultName "nn-0000000000a0.nnue" +// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue +// for the build process (profile-build and fishtest) to work. Do not change the +// name of the macro, as it is used in the Makefile. +#define EvalFileDefaultName "nn-0000000000a0.nnue" - namespace NNUE { +namespace NNUE { - void init(); - void verify(); +void init(); +void verify(); - } // namespace NNUE +} // namespace NNUE -} // namespace Eval +} // namespace Eval -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef EVALUATE_H_INCLUDED +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index eee149fb455..04879cc4673 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,19 +33,19 @@ using namespace Stockfish; int main(int argc, char* argv[]) { - std::cout << engine_info() << std::endl; + std::cout << engine_info() << std::endl; - CommandLine::init(argc, argv); - UCI::init(Options); - Tune::init(); - Bitboards::init(); - Position::init(); - Threads.set(size_t(Options["Threads"])); - Search::clear(); // After threads are up - Eval::NNUE::init(); + CommandLine::init(argc, argv); + UCI::init(Options); + Tune::init(); + Bitboards::init(); + Position::init(); + Threads.set(size_t(Options["Threads"])); + Search::clear(); // After threads are up + Eval::NNUE::init(); - UCI::loop(argc, argv); + UCI::loop(argc, argv); - Threads.set(0); - return 0; + Threads.set(0); + return 0; } diff --git a/src/misc.cpp b/src/misc.cpp index 5abdaf07a31..05181325ece 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -19,30 +19,31 @@ #include "misc.h" #ifdef _WIN32 -#if _WIN32_WINNT < 0x0601 -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes -#endif + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif -#ifndef NOMINMAX -#define NOMINMAX -#endif + #ifndef NOMINMAX + #define NOMINMAX + #endif -#include + #include // The needed Windows API for processor groups could be missed from old Windows // versions, so instead of calling them directly (forcing the linker to resolve // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. extern "C" { -using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP, - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); -using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY); -using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); -using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); -using fun5_t = WORD(*)(); -using fun6_t = bool(*)(HANDLE, DWORD, PHANDLE); -using fun7_t = bool(*)(LPCSTR, LPCSTR, PLUID); -using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, + PDWORD); +using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY); +using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); +using fun5_t = WORD (*)(); +using fun6_t = bool (*)(HANDLE, DWORD, PHANDLE); +using fun7_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); } #endif @@ -59,12 +60,14 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES #include "types.h" #if defined(__linux__) && !defined(__ANDROID__) -#include + #include #endif -#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__) -#define POSIXALIGNEDALLOC -#include +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ + || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ + || defined(__e2k__) + #define POSIXALIGNEDALLOC + #include #endif namespace Stockfish { @@ -80,65 +83,69 @@ constexpr std::string_view version = "dev"; // usual I/O functionality, all without changing a single line of code! // Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 -struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout +struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout - Tie(std::streambuf* b, std::streambuf* l) : buf(b), logBuf(l) {} + Tie(std::streambuf* b, std::streambuf* l) : + buf(b), + logBuf(l) {} - int sync() override { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } - int underflow() override { return buf->sgetc(); } - int uflow() override { return log(buf->sbumpc(), ">> "); } + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } - std::streambuf *buf, *logBuf; + std::streambuf *buf, *logBuf; - int log(int c, const char* prefix) { + int log(int c, const char* prefix) { - static int last = '\n'; // Single log file + static int last = '\n'; // Single log file - if (last == '\n') - logBuf->sputn(prefix, 3); + if (last == '\n') + logBuf->sputn(prefix, 3); - return last = logBuf->sputc(char(c)); - } + return last = logBuf->sputc(char(c)); + } }; class Logger { - Logger() : in(std::cin.rdbuf(), file.rdbuf()), out(std::cout.rdbuf(), file.rdbuf()) {} - ~Logger() { start(""); } + Logger() : + in(std::cin.rdbuf(), file.rdbuf()), + out(std::cout.rdbuf(), file.rdbuf()) {} + ~Logger() { start(""); } - std::ofstream file; - Tie in, out; + std::ofstream file; + Tie in, out; -public: - static void start(const std::string& fname) { - - static Logger l; - - if (l.file.is_open()) - { - std::cout.rdbuf(l.out.buf); - std::cin.rdbuf(l.in.buf); - l.file.close(); - } + public: + static void start(const std::string& fname) { - if (!fname.empty()) - { - l.file.open(fname, std::ifstream::out); + static Logger l; - if (!l.file.is_open()) + if (l.file.is_open()) { - std::cerr << "Unable to open debug log file " << fname << std::endl; - exit(EXIT_FAILURE); + std::cout.rdbuf(l.out.buf); + std::cin.rdbuf(l.in.buf); + l.file.close(); } - std::cin.rdbuf(&l.in); - std::cout.rdbuf(&l.out); + if (!fname.empty()) + { + l.file.open(fname, std::ifstream::out); + + if (!l.file.is_open()) + { + std::cerr << "Unable to open debug log file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + std::cin.rdbuf(&l.in); + std::cout.rdbuf(&l.out); + } } - } }; -} // namespace +} // namespace // engine_info() returns the full name of the current Stockfish version. @@ -152,36 +159,36 @@ class Logger { // Stockfish version std::string engine_info(bool to_uci) { - std::stringstream ss; - ss << "Stockfish " << version << std::setfill('0'); - - if constexpr (version == "dev") - { - ss << "-"; - #ifdef GIT_DATE - ss << stringify(GIT_DATE); - #else - constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); - std::string month, day, year; - std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" - - date >> month >> day >> year; - ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) << std::setw(2) << std::setfill('0') << day; - #endif - - ss << "-"; - - #ifdef GIT_SHA - ss << stringify(GIT_SHA); - #else - ss << "nogit"; - #endif - } - - ss << (to_uci ? "\nid author ": " by ") - << "the Stockfish developers (see AUTHORS file)"; - - return ss.str(); + std::stringstream ss; + ss << "Stockfish " << version << std::setfill('0'); + + if constexpr (version == "dev") + { + ss << "-"; +#ifdef GIT_DATE + ss << stringify(GIT_DATE); +#else + constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + std::string month, day, year; + std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + + date >> month >> day >> year; + ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) + << std::setw(2) << std::setfill('0') << day; +#endif + + ss << "-"; + +#ifdef GIT_SHA + ss << stringify(GIT_SHA); +#else + ss << "nogit"; +#endif + } + + ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)"; + + return ss.str(); } @@ -189,119 +196,118 @@ std::string engine_info(bool to_uci) { std::string compiler_info() { - #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) - -// Predefined macros hell: -// -// __GNUC__ Compiler is GCC, Clang or ICX -// __clang__ Compiler is Clang or ICX -// __INTEL_LLVM_COMPILER Compiler is ICX -// _MSC_VER Compiler is MSVC -// _WIN32 Building on Windows (any) -// _WIN64 Building on Windows 64 bit - - std::string compiler = "\nCompiled by : "; - - #if defined(__INTEL_LLVM_COMPILER) - compiler += "ICX "; - compiler += stringify(__INTEL_LLVM_COMPILER); - #elif defined(__clang__) - compiler += "clang++ "; - compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); - #elif _MSC_VER - compiler += "MSVC "; - compiler += "(version "; - compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); - compiler += ")"; - #elif defined(__e2k__) && defined(__LCC__) +#define make_version_string(major, minor, patch) \ + stringify(major) "." stringify(minor) "." stringify(patch) + + // Predefined macros hell: + // + // __GNUC__ Compiler is GCC, Clang or ICX + // __clang__ Compiler is Clang or ICX + // __INTEL_LLVM_COMPILER Compiler is ICX + // _MSC_VER Compiler is MSVC + // _WIN32 Building on Windows (any) + // _WIN64 Building on Windows 64 bit + + std::string compiler = "\nCompiled by : "; + +#if defined(__INTEL_LLVM_COMPILER) + compiler += "ICX "; + compiler += stringify(__INTEL_LLVM_COMPILER); +#elif defined(__clang__) + compiler += "clang++ "; + compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); +#elif _MSC_VER + compiler += "MSVC "; + compiler += "(version "; + compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); + compiler += ")"; +#elif defined(__e2k__) && defined(__LCC__) #define dot_ver2(n) \ - compiler += char('.'); \ - compiler += char('0' + (n) / 10); \ - compiler += char('0' + (n) % 10); - - compiler += "MCST LCC "; - compiler += "(version "; - compiler += std::to_string(__LCC__ / 100); - dot_ver2(__LCC__ % 100) - dot_ver2(__LCC_MINOR__) - compiler += ")"; - #elif __GNUC__ - compiler += "g++ (GNUC) "; - compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); - #else - compiler += "Unknown compiler "; - compiler += "(unknown version)"; - #endif - - #if defined(__APPLE__) - compiler += " on Apple"; - #elif defined(__CYGWIN__) - compiler += " on Cygwin"; - #elif defined(__MINGW64__) - compiler += " on MinGW64"; - #elif defined(__MINGW32__) - compiler += " on MinGW32"; - #elif defined(__ANDROID__) - compiler += " on Android"; - #elif defined(__linux__) - compiler += " on Linux"; - #elif defined(_WIN64) - compiler += " on Microsoft Windows 64-bit"; - #elif defined(_WIN32) - compiler += " on Microsoft Windows 32-bit"; - #else - compiler += " on unknown system"; - #endif - - compiler += "\nCompilation architecture : "; - #if defined(ARCH) - compiler += stringify(ARCH); - #else - compiler += "(undefined architecture)"; - #endif - - compiler += "\nCompilation settings : "; - compiler += (Is64Bit ? "64bit" : "32bit"); - #if defined(USE_VNNI) + compiler += char('.'); \ + compiler += char('0' + (n) / 10); \ + compiler += char('0' + (n) % 10); + + compiler += "MCST LCC "; + compiler += "(version "; + compiler += std::to_string(__LCC__ / 100); + dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += ")"; +#elif __GNUC__ + compiler += "g++ (GNUC) "; + compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); +#else + compiler += "Unknown compiler "; + compiler += "(unknown version)"; +#endif + +#if defined(__APPLE__) + compiler += " on Apple"; +#elif defined(__CYGWIN__) + compiler += " on Cygwin"; +#elif defined(__MINGW64__) + compiler += " on MinGW64"; +#elif defined(__MINGW32__) + compiler += " on MinGW32"; +#elif defined(__ANDROID__) + compiler += " on Android"; +#elif defined(__linux__) + compiler += " on Linux"; +#elif defined(_WIN64) + compiler += " on Microsoft Windows 64-bit"; +#elif defined(_WIN32) + compiler += " on Microsoft Windows 32-bit"; +#else + compiler += " on unknown system"; +#endif + + compiler += "\nCompilation architecture : "; +#if defined(ARCH) + compiler += stringify(ARCH); +#else + compiler += "(undefined architecture)"; +#endif + + compiler += "\nCompilation settings : "; + compiler += (Is64Bit ? "64bit" : "32bit"); +#if defined(USE_VNNI) compiler += " VNNI"; - #endif - #if defined(USE_AVX512) +#endif +#if defined(USE_AVX512) compiler += " AVX512"; - #endif - compiler += (HasPext ? " BMI2" : ""); - #if defined(USE_AVX2) +#endif + compiler += (HasPext ? " BMI2" : ""); +#if defined(USE_AVX2) compiler += " AVX2"; - #endif - #if defined(USE_SSE41) +#endif +#if defined(USE_SSE41) compiler += " SSE41"; - #endif - #if defined(USE_SSSE3) +#endif +#if defined(USE_SSSE3) compiler += " SSSE3"; - #endif - #if defined(USE_SSE2) +#endif +#if defined(USE_SSE2) compiler += " SSE2"; - #endif - compiler += (HasPopCnt ? " POPCNT" : ""); - #if defined(USE_NEON_DOTPROD) +#endif + compiler += (HasPopCnt ? " POPCNT" : ""); +#if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; - #elif defined(USE_NEON) +#elif defined(USE_NEON) compiler += " NEON"; - #endif +#endif - #if !defined(NDEBUG) +#if !defined(NDEBUG) compiler += " DEBUG"; - #endif +#endif - compiler += "\nCompiler __VERSION__ macro : "; - #ifdef __VERSION__ - compiler += __VERSION__; - #else - compiler += "(undefined macro)"; - #endif + compiler += "\nCompiler __VERSION__ macro : "; +#ifdef __VERSION__ + compiler += __VERSION__; +#else + compiler += "(undefined macro)"; +#endif - compiler += "\n"; + compiler += "\n"; - return compiler; + return compiler; } @@ -312,7 +318,7 @@ namespace { template struct DebugInfo { - std::atomic data[N] = { 0 }; + std::atomic data[N] = {0}; constexpr inline std::atomic& operator[](int index) { return data[index]; } }; @@ -357,42 +363,34 @@ void dbg_correl_of(int64_t value1, int64_t value2, int slot) { void dbg_print() { int64_t n; - auto E = [&n](int64_t x) { return double(x) / n; }; - auto sqr = [](double x) { return x * x; }; + auto E = [&n](int64_t x) { return double(x) / n; }; + auto sqr = [](double x) { return x * x; }; for (int i = 0; i < MaxDebugSlots; ++i) if ((n = hit[i][0])) - std::cerr << "Hit #" << i - << ": Total " << n << " Hits " << hit[i][1] - << " Hit Rate (%) " << 100.0 * E(hit[i][1]) - << std::endl; + std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1] + << " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl; for (int i = 0; i < MaxDebugSlots; ++i) if ((n = mean[i][0])) { - std::cerr << "Mean #" << i - << ": Total " << n << " Mean " << E(mean[i][1]) - << std::endl; + std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl; } for (int i = 0; i < MaxDebugSlots; ++i) if ((n = stdev[i][0])) { double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1]))); - std::cerr << "Stdev #" << i - << ": Total " << n << " Stdev " << r - << std::endl; + std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl; } for (int i = 0; i < MaxDebugSlots; ++i) if ((n = correl[i][0])) { double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) - / ( sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) - * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); - std::cerr << "Correl. #" << i - << ": Total " << n << " Coefficient " << r - << std::endl; + / (sqrt(E(correl[i][2]) - sqr(E(correl[i][1]))) + * sqrt(E(correl[i][4]) - sqr(E(correl[i][3])))); + std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl; } } @@ -402,15 +400,15 @@ void dbg_print() { std::ostream& operator<<(std::ostream& os, SyncCout sc) { - static std::mutex m; + static std::mutex m; - if (sc == IO_LOCK) - m.lock(); + if (sc == IO_LOCK) + m.lock(); - if (sc == IO_UNLOCK) - m.unlock(); + if (sc == IO_UNLOCK) + m.unlock(); - return os; + return os; } @@ -429,11 +427,11 @@ void prefetch(void*) {} void prefetch(void* addr) { -# if defined(_MSC_VER) - _mm_prefetch((char*)addr, _MM_HINT_T0); -# else - __builtin_prefetch(addr); -# endif + #if defined(_MSC_VER) + _mm_prefetch((char*) addr, _MM_HINT_T0); + #else + __builtin_prefetch(addr); + #endif } #endif @@ -446,27 +444,27 @@ void prefetch(void* addr) { void* std_aligned_alloc(size_t alignment, size_t size) { #if defined(POSIXALIGNEDALLOC) - void *mem; - return posix_memalign(&mem, alignment, size) ? nullptr : mem; + void* mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; #elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - return _mm_malloc(size, alignment); + return _mm_malloc(size, alignment); #elif defined(_WIN32) - return _aligned_malloc(size, alignment); + return _aligned_malloc(size, alignment); #else - return std::aligned_alloc(alignment, size); + return std::aligned_alloc(alignment, size); #endif } void std_aligned_free(void* ptr) { #if defined(POSIXALIGNEDALLOC) - free(ptr); + free(ptr); #elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - _mm_free(ptr); + _mm_free(ptr); #elif defined(_WIN32) - _aligned_free(ptr); + _aligned_free(ptr); #else - free(ptr); + free(ptr); #endif } @@ -476,104 +474,104 @@ void std_aligned_free(void* ptr) { static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { - #if !defined(_WIN64) + #if !defined(_WIN64) return nullptr; - #else - - HANDLE hProcessToken { }; - LUID luid { }; - void* mem = nullptr; - - const size_t largePageSize = GetLargePageMinimum(); - if (!largePageSize) - return nullptr; - - // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - - HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); - - if (!hAdvapi32) - hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - - auto fun6 = fun6_t((void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken")); - if (!fun6) - return nullptr; - auto fun7 = fun7_t((void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); - if (!fun7) - return nullptr; - auto fun8 = fun8_t((void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); - if (!fun8) - return nullptr; - - // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!fun6( // OpenProcessToken() - GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; - - if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) - nullptr, "SeLockMemoryPrivilege", &luid)) - { - TOKEN_PRIVILEGES tp { }; - TOKEN_PRIVILEGES prevTp { }; - DWORD prevTpLen = 0; - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, - // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (fun8( // AdjustTokenPrivileges() - hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && - GetLastError() == ERROR_SUCCESS) - { - // Round up size to full pages and allocate - allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); - mem = VirtualAlloc( - nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); - - // Privilege no longer needed, restore previous state - fun8( // AdjustTokenPrivileges () + #else + + HANDLE hProcessToken{}; + LUID luid{}; + void* mem = nullptr; + + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; + + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!fun6) + return nullptr; + auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!fun7) + return nullptr; + auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!fun8) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process + if (!fun6( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; + + if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) + nullptr, "SeLockMemoryPrivilege", &luid)) + { + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, + // we still need to query GetLastError() to ensure that the privileges were actually obtained. + if (fun8( // AdjustTokenPrivileges() + hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) + && GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); + + // Privilege no longer needed, restore previous state + fun8( // AdjustTokenPrivileges () hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); - } - } + } + } - CloseHandle(hProcessToken); + CloseHandle(hProcessToken); - return mem; + return mem; - #endif + #endif } void* aligned_large_pages_alloc(size_t allocSize) { - // Try to allocate large pages - void* mem = aligned_large_pages_alloc_windows(allocSize); + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); - // Fall back to regular, page-aligned, allocation if necessary - if (!mem) - mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + // Fall back to regular, page-aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - return mem; + return mem; } #else void* aligned_large_pages_alloc(size_t allocSize) { -#if defined(__linux__) - constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size -#else - constexpr size_t alignment = 4096; // assumed small page size -#endif - - // round up to multiples of alignment - size_t size = ((allocSize + alignment - 1) / alignment) * alignment; - void *mem = std_aligned_alloc(alignment, size); -#if defined(MADV_HUGEPAGE) - madvise(mem, size, MADV_HUGEPAGE); -#endif - return mem; + #if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size + #else + constexpr size_t alignment = 4096; // assumed small page size + #endif + + // round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void* mem = std_aligned_alloc(alignment, size); + #if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); + #endif + return mem; } #endif @@ -585,21 +583,18 @@ void* aligned_large_pages_alloc(size_t allocSize) { void aligned_large_pages_free(void* mem) { - if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) - { - DWORD err = GetLastError(); - std::cerr << "Failed to free large page memory. Error code: 0x" - << std::hex << err - << std::dec << std::endl; - exit(EXIT_FAILURE); - } + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } } #else -void aligned_large_pages_free(void *mem) { - std_aligned_free(mem); -} +void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } #endif @@ -618,69 +613,69 @@ void bindThisThread(size_t) {} static int best_node(size_t idx) { - int threads = 0; - int nodes = 0; - int cores = 0; - DWORD returnLength = 0; - DWORD byteOffset = 0; - - // Early exit if the needed API is not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); - if (!fun1) - return -1; - - // First call to GetLogicalProcessorInformationEx() to get returnLength. - // We expect the call to fail due to null buffer. - if (fun1(RelationAll, nullptr, &returnLength)) - return -1; - - // Once we know returnLength, allocate the buffer - SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; - ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength); - - // Second call to GetLogicalProcessorInformationEx(), now we expect to succeed - if (!fun1(RelationAll, buffer, &returnLength)) - { - free(buffer); - return -1; - } - - while (byteOffset < returnLength) - { - if (ptr->Relationship == RelationNumaNode) - nodes++; - - else if (ptr->Relationship == RelationProcessorCore) - { - cores++; - threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; - } - - assert(ptr->Size); - byteOffset += ptr->Size; - ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); - } - - free(buffer); - - std::vector groups; - - // Run as many threads as possible on the same node until the core limit is - // reached, then move on to filling the next node. - for (int n = 0; n < nodes; n++) - for (int i = 0; i < cores / nodes; i++) - groups.push_back(n); - - // In case a core has more than one logical processor (we assume 2) and we - // have still threads to allocate, then spread them evenly across available - // nodes. - for (int t = 0; t < threads - cores; t++) - groups.push_back(t % nodes); - - // If we still have more threads than the total number of logical processors - // then return -1 and let the OS to decide what to do. - return idx < groups.size() ? groups[idx] : -1; + int threads = 0; + int nodes = 0; + int cores = 0; + DWORD returnLength = 0; + DWORD byteOffset = 0; + + // Early exit if the needed API is not available at runtime + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto fun1 = (fun1_t) (void (*)()) GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + if (!fun1) + return -1; + + // First call to GetLogicalProcessorInformationEx() to get returnLength. + // We expect the call to fail due to null buffer. + if (fun1(RelationAll, nullptr, &returnLength)) + return -1; + + // Once we know returnLength, allocate the buffer + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; + ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(returnLength); + + // Second call to GetLogicalProcessorInformationEx(), now we expect to succeed + if (!fun1(RelationAll, buffer, &returnLength)) + { + free(buffer); + return -1; + } + + while (byteOffset < returnLength) + { + if (ptr->Relationship == RelationNumaNode) + nodes++; + + else if (ptr->Relationship == RelationProcessorCore) + { + cores++; + threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; + } + + assert(ptr->Size); + byteOffset += ptr->Size; + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((char*) ptr) + ptr->Size); + } + + free(buffer); + + std::vector groups; + + // Run as many threads as possible on the same node until the core limit is + // reached, then move on to filling the next node. + for (int n = 0; n < nodes; n++) + for (int i = 0; i < cores / nodes; i++) + groups.push_back(n); + + // In case a core has more than one logical processor (we assume 2) and we + // have still threads to allocate, then spread them evenly across available + // nodes. + for (int t = 0; t < threads - cores; t++) + groups.push_back(t % nodes); + + // If we still have more threads than the total number of logical processors + // then return -1 and let the OS to decide what to do. + return idx < groups.size() ? groups[idx] : -1; } @@ -688,58 +683,59 @@ static int best_node(size_t idx) { void bindThisThread(size_t idx) { - // Use only local variables to be thread-safe - int node = best_node(idx); - - if (node == -1) - return; - - // Early exit if the needed API are not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun2 = fun2_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); - auto fun3 = fun3_t((void(*)())GetProcAddress(k32, "SetThreadGroupAffinity")); - auto fun4 = fun4_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2")); - auto fun5 = fun5_t((void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount")); - - if (!fun2 || !fun3) - return; - - if (!fun4 || !fun5) - { - GROUP_AFFINITY affinity; - if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx - fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity - } - else - { - // If a numa node has more than one processor group, we assume they are - // sized equal and we spread threads evenly across the groups. - USHORT elements, returnedElements; - elements = fun5(); // GetMaximumProcessorGroupCount - GROUP_AFFINITY *affinity = (GROUP_AFFINITY*)malloc(elements * sizeof(GROUP_AFFINITY)); - if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2 - fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity - free(affinity); - } + // Use only local variables to be thread-safe + int node = best_node(idx); + + if (node == -1) + return; + + // Early exit if the needed API are not available at runtime + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto fun2 = fun2_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); + auto fun3 = fun3_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity")); + auto fun4 = fun4_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMask2")); + auto fun5 = fun5_t((void (*)()) GetProcAddress(k32, "GetMaximumProcessorGroupCount")); + + if (!fun2 || !fun3) + return; + + if (!fun4 || !fun5) + { + GROUP_AFFINITY affinity; + if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx + fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity + } + else + { + // If a numa node has more than one processor group, we assume they are + // sized equal and we spread threads evenly across the groups. + USHORT elements, returnedElements; + elements = fun5(); // GetMaximumProcessorGroupCount + GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY)); + if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2 + fun3(GetCurrentThread(), &affinity[idx % returnedElements], + nullptr); // SetThreadGroupAffinity + free(affinity); + } } #endif -} // namespace WinProcGroup +} // namespace WinProcGroup #ifdef _WIN32 -#include -#define GETCWD _getcwd + #include + #define GETCWD _getcwd #else -#include -#define GETCWD getcwd + #include + #define GETCWD getcwd #endif namespace CommandLine { -std::string argv0; // path+name of the executable binary, as given by argv[0] -std::string binaryDirectory; // path of the executable directory -std::string workingDirectory; // path of the working directory +std::string argv0; // path+name of the executable binary, as given by argv[0] +std::string binaryDirectory; // path of the executable directory +std::string workingDirectory; // path of the working directory void init([[maybe_unused]] int argc, char* argv[]) { std::string pathSeparator; @@ -749,27 +745,27 @@ void init([[maybe_unused]] int argc, char* argv[]) { #ifdef _WIN32 pathSeparator = "\\"; - #ifdef _MSC_VER + #ifdef _MSC_VER // Under windows argv[0] may not have the extension. Also _get_pgmptr() had // issues in some Windows 10 versions, so check returned values carefully. char* pgmptr = nullptr; if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) argv0 = pgmptr; - #endif + #endif #else pathSeparator = "/"; #endif // extract the working directory workingDirectory = ""; - char buff[40000]; + char buff[40000]; char* cwd = GETCWD(buff, 40000); if (cwd) workingDirectory = cwd; // extract the binary directory path from argv0 binaryDirectory = argv0; - size_t pos = binaryDirectory.find_last_of("\\/"); + size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) binaryDirectory = "." + pathSeparator; else @@ -781,6 +777,6 @@ void init([[maybe_unused]] int argc, char* argv[]) { } -} // namespace CommandLine +} // namespace CommandLine -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index 60602048b9c..3cd3315a8ed 100644 --- a/src/misc.h +++ b/src/misc.h @@ -33,12 +33,13 @@ namespace Stockfish { std::string engine_info(bool to_uci = false); std::string compiler_info(); -void prefetch(void* addr); -void start_logger(const std::string& fname); -void* std_aligned_alloc(size_t alignment, size_t size); -void std_aligned_free(void* ptr); -void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes -void aligned_large_pages_free(void* mem); // nop if mem == nullptr +void prefetch(void* addr); +void start_logger(const std::string& fname); +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); +void* aligned_large_pages_alloc( + size_t size); // memory aligned by page size, min alignment: 4096 bytes +void aligned_large_pages_free(void* mem); // nop if mem == nullptr void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); @@ -46,15 +47,19 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); -using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds +using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); inline TimePoint now() { - return std::chrono::duration_cast - (std::chrono::steady_clock::now().time_since_epoch()).count(); + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); } -enum SyncCout { IO_LOCK, IO_UNLOCK }; +enum SyncCout { + IO_LOCK, + IO_UNLOCK +}; std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK @@ -64,34 +69,37 @@ std::ostream& operator<<(std::ostream&, SyncCout); // align_ptr_up() : get the first aligned element of an array. // ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, // where N is the number of elements in the array. -template -T* align_ptr_up(T* ptr) -{ - static_assert(alignof(T) < Alignment); +template +T* align_ptr_up(T* ptr) { + static_assert(alignof(T) < Alignment); - const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); - return reinterpret_cast(reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast( + reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); } // IsLittleEndian : 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 union { + uint32_t i; + char c[4]; +} Le = {0x01020304}; static inline const bool IsLittleEndian = (Le.c[0] == 4); -template +template class ValueList { -public: - std::size_t size() const { return size_; } - void push_back(const T& value) { values_[size_++] = value; } - const T* begin() const { return values_; } - const T* end() const { return values_ + size_; } - const T& operator[](int index) const { return values_[index]; } + public: + std::size_t size() const { return size_; } + void push_back(const T& value) { values_[size_++] = value; } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } -private: - T values_[MaxSize]; - std::size_t size_ = 0; + private: + T values_[MaxSize]; + std::size_t size_ = 0; }; @@ -112,23 +120,31 @@ class ValueList { class PRNG { - uint64_t s; + uint64_t s; - uint64_t rand64() { + uint64_t rand64() { - s ^= s >> 12, s ^= s << 25, s ^= s >> 27; - return s * 2685821657736338717LL; - } + s ^= s >> 12, s ^= s << 25, s ^= s >> 27; + return s * 2685821657736338717LL; + } -public: - PRNG(uint64_t seed) : s(seed) { assert(seed); } + public: + PRNG(uint64_t seed) : + s(seed) { + assert(seed); + } - template T rand() { return T(rand64()); } + template + T rand() { + return T(rand64()); + } - // Special generator used to fast init magic numbers. - // Output values only have 1/8th of their bits set on average. - template T sparse_rand() - { return T(rand64() & rand64() & rand64()); } + // Special generator used to fast init magic numbers. + // Output values only have 1/8th of their bits set on average. + template + T sparse_rand() { + return T(rand64() & rand64() & rand64()); + } }; inline uint64_t mul_hi64(uint64_t a, uint64_t b) { @@ -152,16 +168,16 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { // Peter Österlund. namespace WinProcGroup { - void bindThisThread(size_t idx); +void bindThisThread(size_t idx); } namespace CommandLine { - void init(int argc, char* argv[]); +void init(int argc, char* argv[]); - extern std::string binaryDirectory; // path of the executable directory - extern std::string workingDirectory; // path of the working directory +extern std::string binaryDirectory; // path of the executable directory +extern std::string workingDirectory; // path of the working directory } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MISC_H_INCLUDED +#endif // #ifndef MISC_H_INCLUDED diff --git a/src/movegen.cpp b/src/movegen.cpp index 82ad60613c2..cf457d1176c 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -28,8 +28,8 @@ namespace Stockfish { namespace { - template - ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { +template +ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { @@ -50,33 +50,32 @@ namespace { } return moveList; - } +} - template - ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +template +ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { constexpr Color Them = ~Us; - constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); - constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); constexpr Direction Up = pawn_push(Us); constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); const Bitboard emptySquares = ~pos.pieces(); - const Bitboard enemies = Type == EVASIONS ? pos.checkers() - : pos.pieces(Them); + const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them); - Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; + Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; // Single and double pawn pushes, no promotions if constexpr (Type != CAPTURES) { - Bitboard b1 = shift(pawnsNotOn7) & emptySquares; + Bitboard b1 = shift(pawnsNotOn7) & emptySquares; Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; - if constexpr (Type == EVASIONS) // Consider only blocking squares + if constexpr (Type == EVASIONS) // Consider only blocking squares { b1 &= target; b2 &= target; @@ -87,21 +86,21 @@ namespace { // To make a quiet check, you either make a direct check by pushing a pawn // or push a blocker pawn that is not on the same file as the enemy king. // Discovered check promotion has been already generated amongst the captures. - Square ksq = pos.square(Them); + Square ksq = pos.square(Them); Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); - b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); - b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + b1 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); } while (b1) { - Square to = pop_lsb(b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - Up, to); } while (b2) { - Square to = pop_lsb(b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - Up - Up, to); } } @@ -110,8 +109,8 @@ namespace { if (pawnsOn7) { Bitboard b1 = shift(pawnsOn7) & enemies; - Bitboard b2 = shift(pawnsOn7) & enemies; - Bitboard b3 = shift(pawnsOn7) & emptySquares; + Bitboard b2 = shift(pawnsOn7) & enemies; + Bitboard b3 = shift(pawnsOn7) & emptySquares; if constexpr (Type == EVASIONS) b3 &= target; @@ -123,24 +122,24 @@ namespace { moveList = make_promotions(moveList, pop_lsb(b2)); while (b3) - moveList = make_promotions(moveList, pop_lsb(b3)); + moveList = make_promotions(moveList, pop_lsb(b3)); } // Standard and en passant captures if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; - Bitboard b2 = shift(pawnsNotOn7) & enemies; + Bitboard b2 = shift(pawnsNotOn7) & enemies; while (b1) { - Square to = pop_lsb(b1); + Square to = pop_lsb(b1); *moveList++ = make_move(to - UpRight, to); } while (b2) { - Square to = pop_lsb(b2); + Square to = pop_lsb(b2); *moveList++ = make_move(to - UpLeft, to); } @@ -162,11 +161,11 @@ namespace { } return moveList; - } +} - template - ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +template +ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); @@ -174,8 +173,8 @@ namespace { while (bb) { - Square from = pop_lsb(bb); - Bitboard b = attacks_bb(from, pos.pieces()) & target; + Square from = pop_lsb(bb); + Bitboard b = attacks_bb(from, pos.pieces()) & target; // To check, you either move freely a blocker or make a direct check. if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from))) @@ -186,31 +185,31 @@ namespace { } return moveList; - } +} - template - ExtMove* generate_all(const Position& pos, ExtMove* moveList) { +template +ExtMove* generate_all(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate_all()"); - constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations - const Square ksq = pos.square(Us); - Bitboard target; + constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations + const Square ksq = pos.square(Us); + Bitboard target; // Skip generating non-king moves when in double check if (Type != EVASIONS || !more_than_one(pos.checkers())) { - target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) - : Type == NON_EVASIONS ? ~pos.pieces( Us) - : Type == CAPTURES ? pos.pieces(~Us) - : ~pos.pieces( ); // QUIETS || QUIET_CHECKS + target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) + : Type == NON_EVASIONS ? ~pos.pieces(Us) + : Type == CAPTURES ? pos.pieces(~Us) + : ~pos.pieces(); // QUIETS || QUIET_CHECKS moveList = generate_pawn_moves(pos, moveList, target); moveList = generate_moves(pos, moveList, target); moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); } if (!Checks || pos.blockers_for_king(~Us) & ksq) @@ -223,15 +222,15 @@ namespace { *moveList++ = make_move(ksq, pop_lsb(b)); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) - for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) + for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) *moveList++ = make(ksq, pos.castling_rook_square(cr)); } return moveList; - } +} -} // namespace +} // namespace // Generates all pseudo-legal captures plus queen promotions @@ -246,13 +245,13 @@ namespace { template ExtMove* generate(const Position& pos, ExtMove* moveList) { - static_assert(Type != LEGAL, "Unsupported type in generate()"); - assert((Type == EVASIONS) == bool(pos.checkers())); + static_assert(Type != LEGAL, "Unsupported type in generate()"); + assert((Type == EVASIONS) == bool(pos.checkers())); - Color us = pos.side_to_move(); + Color us = pos.side_to_move(); - return us == WHITE ? generate_all(pos, moveList) - : generate_all(pos, moveList); + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); } // Explicit template instantiations @@ -268,21 +267,21 @@ template ExtMove* generate(const Position&, ExtMove*); template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { - Color us = pos.side_to_move(); - Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); - Square ksq = pos.square(us); - ExtMove* cur = moveList; - - moveList = pos.checkers() ? generate(pos, moveList) - : generate(pos, moveList); - while (cur != moveList) - if ( ((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) - && !pos.legal(*cur)) - *cur = (--moveList)->move; - else - ++cur; - - return moveList; + Color us = pos.side_to_move(); + Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); + Square ksq = pos.square(us); + ExtMove* cur = moveList; + + moveList = + pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); + while (cur != moveList) + if (((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) + && !pos.legal(*cur)) + *cur = (--moveList)->move; + else + ++cur; + + return moveList; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/movegen.h b/src/movegen.h index e913a13ea13..9a39d1c50ea 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -19,7 +19,7 @@ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include #include "types.h" @@ -29,29 +29,27 @@ namespace Stockfish { class Position; enum GenType { - CAPTURES, - QUIETS, - QUIET_CHECKS, - EVASIONS, - NON_EVASIONS, - LEGAL + CAPTURES, + QUIETS, + QUIET_CHECKS, + EVASIONS, + NON_EVASIONS, + LEGAL }; struct ExtMove { - Move move; - int value; + Move move; + int value; - operator Move() const { return move; } - void operator=(Move m) { move = m; } + operator Move() const { return move; } + void operator=(Move m) { move = m; } - // Inhibit unwanted implicit conversions to Move - // with an ambiguity that yields to a compile error. - operator float() const = delete; + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; }; -inline bool operator<(const ExtMove& f, const ExtMove& s) { - return f.value < s.value; -} +inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; } template ExtMove* generate(const Position& pos, ExtMove* moveList); @@ -62,18 +60,17 @@ ExtMove* generate(const Position& pos, ExtMove* moveList); template struct MoveList { - explicit MoveList(const Position& pos) : last(generate(pos, moveList)) {} - const ExtMove* begin() const { return moveList; } - const ExtMove* end() const { return last; } - size_t size() const { return last - moveList; } - bool contains(Move move) const { - return std::find(begin(), end(), move) != end(); - } + explicit MoveList(const Position& pos) : + last(generate(pos, moveList)) {} + const ExtMove* begin() const { return moveList; } + const ExtMove* end() const { return last; } + size_t size() const { return last - moveList; } + bool contains(Move move) const { return std::find(begin(), end(), move) != end(); } -private: - ExtMove moveList[MAX_MOVES], *last; + private: + ExtMove moveList[MAX_MOVES], *last; }; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MOVEGEN_H_INCLUDED +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp index 5bb0fd6c274..41ad0dd6e8d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -30,29 +30,50 @@ namespace Stockfish { namespace { - enum Stages { - MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE, - EVASION_TT, EVASION_INIT, EVASION, - PROBCUT_TT, PROBCUT_INIT, PROBCUT, - QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK - }; - - // partial_insertion_sort() sorts moves in descending order up to and including - // a given limit. The order of moves smaller than the limit is left unspecified. - void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { +enum Stages { + // generate main search moves + MAIN_TT, + CAPTURE_INIT, + GOOD_CAPTURE, + REFUTATION, + QUIET_INIT, + QUIET, + BAD_CAPTURE, + + // generate evasion moves + EVASION_TT, + EVASION_INIT, + EVASION, + + // generate probcut moves + PROBCUT_TT, + PROBCUT_INIT, + PROBCUT, + + // generate qsearch moves + QSEARCH_TT, + QCAPTURE_INIT, + QCAPTURE, + QCHECK_INIT, + QCHECK +}; + +// partial_insertion_sort() sorts moves in descending order up to and including +// a given limit. The order of moves smaller than the limit is left unspecified. +void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) if (p->value >= limit) { ExtMove tmp = *p, *q; - *p = *++sortedEnd; + *p = *++sortedEnd; for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) *q = *(q - 1); *q = tmp; } - } +} -} // namespace +} // namespace // Constructors of the MovePicker class. As arguments, we pass information @@ -62,44 +83,57 @@ namespace { // move ordering is at the current node. // MovePicker constructor for the main search -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch, - Move cm, - const Move* killers) - : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), - ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) -{ - assert(d > 0); - - stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + - !(ttm && pos.pseudo_legal(ttm)); +MovePicker::MovePicker(const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, + Move cm, + const Move* killers) : + pos(p), + mainHistory(mh), + captureHistory(cph), + continuationHistory(ch), + ttMove(ttm), + refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, + depth(d) { + assert(d > 0); + + stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); } // MovePicker constructor for quiescence search -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch, - Square rs) - : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) -{ - assert(d <= 0); - - stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + - !( ttm - && pos.pseudo_legal(ttm)); +MovePicker::MovePicker(const Position& p, + Move ttm, + Depth d, + const ButterflyHistory* mh, + const CapturePieceToHistory* cph, + const PieceToHistory** ch, + Square rs) : + pos(p), + mainHistory(mh), + captureHistory(cph), + continuationHistory(ch), + ttMove(ttm), + recaptureSquare(rs), + depth(d) { + assert(d <= 0); + + stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } // MovePicker constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) - : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) -{ - assert(!pos.checkers()); - - stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) - && pos.pseudo_legal(ttm) - && pos.see_ge(ttm, threshold)); +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : + pos(p), + captureHistory(cph), + ttMove(ttm), + threshold(th) { + assert(!pos.checkers()); + + stage = PROBCUT_TT + + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } // MovePicker::score() assigns a numerical value to each move in a list, used @@ -108,76 +142,78 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece template void MovePicker::score() { - static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - - [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces; - if constexpr (Type == QUIETS) - { - Color us = pos.side_to_move(); - - threatenedByPawn = pos.attacks_by(~us); - threatenedByMinor = pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; - threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; - - // Pieces threatened by pieces of lesser material value - threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) - | (pos.pieces(us, ROOK) & threatenedByMinor) - | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); - } - - for (auto& m : *this) - if constexpr (Type == CAPTURES) - m.value = (7 * int(PieceValue[pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16; - - else if constexpr (Type == QUIETS) - { - Piece pc = pos.moved_piece(m); - PieceType pt = type_of(pos.moved_piece(m)); - Square from = from_sq(m); - Square to = to_sq(m); - - // histories - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; - m.value += 2 * (*continuationHistory[0])[pc][to]; - m.value += (*continuationHistory[1])[pc][to]; - m.value += (*continuationHistory[2])[pc][to] / 4; - m.value += (*continuationHistory[3])[pc][to]; - m.value += (*continuationHistory[5])[pc][to]; - - // bonus for checks - m.value += bool(pos.check_squares(pt) & to) * 16384; - - // bonus for escaping from capture - m.value += threatenedPieces & from ? - (pt == QUEEN && !(to & threatenedByRook) ? 50000 - : pt == ROOK && !(to & threatenedByMinor) ? 25000 - : !(to & threatenedByPawn) ? 15000 - : 0 ) - : 0 ; - - // malus for putting piece en prise - m.value -= !(threatenedPieces & from) ? - (pt == QUEEN ? bool(to & threatenedByRook) * 50000 - + bool(to & threatenedByMinor) * 10000 - + bool(to & threatenedByPawn) * 20000 - : pt == ROOK ? bool(to & threatenedByMinor) * 25000 - + bool(to & threatenedByPawn) * 10000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 - : 0 ) - : 0 ; - } - - else // Type == EVASIONS - { - if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))) - + (1 << 28); - else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; - } + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + + [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, + threatenedPieces; + if constexpr (Type == QUIETS) + { + Color us = pos.side_to_move(); + + threatenedByPawn = pos.attacks_by(~us); + threatenedByMinor = + pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; + threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; + + // Pieces threatened by pieces of lesser material value + threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) + | (pos.pieces(us, ROOK) & threatenedByMinor) + | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); + } + + for (auto& m : *this) + if constexpr (Type == CAPTURES) + m.value = + (7 * int(PieceValue[pos.piece_on(to_sq(m))]) + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) + / 16; + + else if constexpr (Type == QUIETS) + { + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pos.moved_piece(m)); + Square from = from_sq(m); + Square to = to_sq(m); + + // histories + m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value += 2 * (*continuationHistory[0])[pc][to]; + m.value += (*continuationHistory[1])[pc][to]; + m.value += (*continuationHistory[2])[pc][to] / 4; + m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[5])[pc][to]; + + // bonus for checks + m.value += bool(pos.check_squares(pt) & to) * 16384; + + // bonus for escaping from capture + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000 + : pt == ROOK && !(to & threatenedByMinor) ? 25000 + : !(to & threatenedByPawn) ? 15000 + : 0) + : 0; + + // malus for putting piece en prise + m.value -= !(threatenedPieces & from) + ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 + + bool(to & threatenedByMinor) * 10000 + + bool(to & threatenedByPawn) * 20000 + : pt == ROOK ? bool(to & threatenedByMinor) * 25000 + + bool(to & threatenedByPawn) * 10000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0) + : 0; + } + + else // Type == EVASIONS + { + if (pos.capture_stage(m)) + m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + + (1 << 28); + else + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; + } } // MovePicker::select() returns the next move satisfying a predicate function. @@ -185,17 +221,17 @@ void MovePicker::score() { template Move MovePicker::select(Pred filter) { - while (cur < endMoves) - { - if constexpr (T == Best) - std::swap(*cur, *std::max_element(cur, endMoves)); + while (cur < endMoves) + { + if constexpr (T == Best) + std::swap(*cur, *std::max_element(cur, endMoves)); - if (*cur != ttMove && filter()) - return *cur++; + if (*cur != ttMove && filter()) + return *cur++; - cur++; - } - return MOVE_NONE; + cur++; + } + return MOVE_NONE; } // MovePicker::next_move() is the most important method of the MovePicker class. It @@ -204,122 +240,126 @@ Move MovePicker::select(Pred filter) { Move MovePicker::next_move(bool skipQuiets) { top: - switch (stage) { - - case MAIN_TT: - case EVASION_TT: - case QSEARCH_TT: - case PROBCUT_TT: - ++stage; - return ttMove; - - case CAPTURE_INIT: - case PROBCUT_INIT: - case QCAPTURE_INIT: - cur = endBadCaptures = moves; - endMoves = generate(pos, cur); - - score(); - partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); - ++stage; - goto top; - - case GOOD_CAPTURE: - if (select([&](){ - return pos.see_ge(*cur, Value(-cur->value)) ? - // Move losing capture to endBadCaptures to be tried later - true : (*endBadCaptures++ = *cur, false); })) - return *(cur - 1); - - // Prepare the pointers to loop over the refutations array - cur = std::begin(refutations); - endMoves = std::end(refutations); - - // If the countermove is the same as a killer, skip it - if ( refutations[0].move == refutations[2].move - || refutations[1].move == refutations[2].move) - --endMoves; - - ++stage; - [[fallthrough]]; - - case REFUTATION: - if (select([&](){ return *cur != MOVE_NONE - && !pos.capture_stage(*cur) - && pos.pseudo_legal(*cur); })) - return *(cur - 1); - ++stage; - [[fallthrough]]; - - case QUIET_INIT: - if (!skipQuiets) - { - cur = endBadCaptures; - endMoves = generate(pos, cur); - - score(); - partial_insertion_sort(cur, endMoves, -3000 * depth); - } - - ++stage; - [[fallthrough]]; - - case QUIET: - if ( !skipQuiets - && select([&](){return *cur != refutations[0].move - && *cur != refutations[1].move - && *cur != refutations[2].move;})) - return *(cur - 1); - - // Prepare the pointers to loop over the bad captures - cur = moves; - endMoves = endBadCaptures; - - ++stage; - [[fallthrough]]; - - case BAD_CAPTURE: - return select([](){ return true; }); - - case EVASION_INIT: - cur = moves; - endMoves = generate(pos, cur); - - score(); - ++stage; - [[fallthrough]]; - - case EVASION: - return select([](){ return true; }); - - case PROBCUT: - return select([&](){ return pos.see_ge(*cur, threshold); }); - - case QCAPTURE: - if (select([&](){ return depth > DEPTH_QS_RECAPTURES - || to_sq(*cur) == recaptureSquare; })) - return *(cur - 1); - - // If we did not find any move and we do not try checks, we have finished - if (depth != DEPTH_QS_CHECKS) - return MOVE_NONE; - - ++stage; - [[fallthrough]]; - - case QCHECK_INIT: - cur = moves; - endMoves = generate(pos, cur); - - ++stage; - [[fallthrough]]; - - case QCHECK: - return select([](){ return true; }); - } - - assert(false); - return MOVE_NONE; // Silence warning + switch (stage) + { + + case MAIN_TT : + case EVASION_TT : + case QSEARCH_TT : + case PROBCUT_TT : + ++stage; + return ttMove; + + case CAPTURE_INIT : + case PROBCUT_INIT : + case QCAPTURE_INIT : + cur = endBadCaptures = moves; + endMoves = generate(pos, cur); + + score(); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); + ++stage; + goto top; + + case GOOD_CAPTURE : + if (select([&]() { + return pos.see_ge(*cur, Value(-cur->value)) + ? + // Move losing capture to endBadCaptures to be tried later + true + : (*endBadCaptures++ = *cur, false); + })) + return *(cur - 1); + + // Prepare the pointers to loop over the refutations array + cur = std::begin(refutations); + endMoves = std::end(refutations); + + // If the countermove is the same as a killer, skip it + if (refutations[0].move == refutations[2].move + || refutations[1].move == refutations[2].move) + --endMoves; + + ++stage; + [[fallthrough]]; + + case REFUTATION : + if (select([&]() { + return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); + })) + return *(cur - 1); + ++stage; + [[fallthrough]]; + + case QUIET_INIT : + if (!skipQuiets) + { + cur = endBadCaptures; + endMoves = generate(pos, cur); + + score(); + partial_insertion_sort(cur, endMoves, -3000 * depth); + } + + ++stage; + [[fallthrough]]; + + case QUIET : + if (!skipQuiets && select([&]() { + return *cur != refutations[0].move && *cur != refutations[1].move + && *cur != refutations[2].move; + })) + return *(cur - 1); + + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; + + ++stage; + [[fallthrough]]; + + case BAD_CAPTURE : + return select([]() { return true; }); + + case EVASION_INIT : + cur = moves; + endMoves = generate(pos, cur); + + score(); + ++stage; + [[fallthrough]]; + + case EVASION : + return select([]() { return true; }); + + case PROBCUT : + return select([&]() { return pos.see_ge(*cur, threshold); }); + + case QCAPTURE : + if (select( + [&]() { return depth > DEPTH_QS_RECAPTURES || to_sq(*cur) == recaptureSquare; })) + return *(cur - 1); + + // If we did not find any move and we do not try checks, we have finished + if (depth != DEPTH_QS_CHECKS) + return MOVE_NONE; + + ++stage; + [[fallthrough]]; + + case QCHECK_INIT : + cur = moves; + endMoves = generate(pos, cur); + + ++stage; + [[fallthrough]]; + + case QCHECK : + return select([]() { return true; }); + } + + assert(false); + return MOVE_NONE; // Silence warning } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 457defa525a..65e93dda6fe 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -24,7 +24,7 @@ #include #include #include -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include "movegen.h" #include "types.h" @@ -39,22 +39,22 @@ class Position; template class StatsEntry { - T entry; + T entry; -public: - void operator=(const T& v) { entry = v; } - T* operator&() { return &entry; } - T* operator->() { return &entry; } - operator const T&() const { return entry; } + public: + void operator=(const T& v) { entry = v; } + T* operator&() { return &entry; } + T* operator->() { return &entry; } + operator const T&() const { return entry; } - void operator<<(int bonus) { - assert(abs(bonus) <= D); // Ensure range is [-D, D] - static_assert(D <= std::numeric_limits::max(), "D overflows T"); + void operator<<(int bonus) { + assert(abs(bonus) <= D); // Ensure range is [-D, D] + static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); + entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); - assert(abs(entry) <= D); - } + assert(abs(entry) <= D); + } }; // Stats is a generic N-dimensional array used to store various statistics. @@ -62,28 +62,32 @@ class StatsEntry { // template parameter D limits the range of updates in [-D, D] when we update // values with the << operator, while the last parameters (Size and Sizes) // encode the dimensions of the array. -template -struct Stats : public std::array, Size> -{ - using stats = Stats; +template +struct Stats: public std::array, Size> { + using stats = Stats; - void fill(const T& v) { + void fill(const T& v) { - // For standard-layout 'this' points to the first struct member - assert(std::is_standard_layout_v); + // For standard-layout 'this' points to the first struct member + assert(std::is_standard_layout_v); - using entry = StatsEntry; - entry* p = reinterpret_cast(this); - std::fill(p, p + sizeof(*this) / sizeof(entry), v); - } + using entry = StatsEntry; + entry* p = reinterpret_cast(this); + std::fill(p, p + sizeof(*this) / sizeof(entry), v); + } }; -template -struct Stats : public std::array, Size> {}; +template +struct Stats: public std::array, Size> {}; // In stats table, D=0 means that the template parameter is not used -enum StatsParams { NOT_USED = 0 }; -enum StatsType { NoCaptures, Captures }; +enum StatsParams { + NOT_USED = 0 +}; +enum StatsType { + NoCaptures, + Captures +}; // ButterflyHistory records how often quiet moves have been successful or // unsuccessful during the current search, and is used for reduction and move @@ -117,42 +121,53 @@ using ContinuationHistory = Stats // likely to get a cut-off first. class MovePicker { - enum PickType { Next, Best }; - -public: - MovePicker(const MovePicker&) = delete; - MovePicker& operator=(const MovePicker&) = delete; - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, - const CapturePieceToHistory*, - const PieceToHistory**, - Move, - const Move*); - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, - const CapturePieceToHistory*, - const PieceToHistory**, - Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); - Move next_move(bool skipQuiets = false); - -private: - template Move select(Pred); - template void score(); - ExtMove* begin() { return cur; } - ExtMove* end() { return endMoves; } - - const Position& pos; - const ButterflyHistory* mainHistory; - const CapturePieceToHistory* captureHistory; - const PieceToHistory** continuationHistory; - Move ttMove; - ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; - int stage; - Square recaptureSquare; - Value threshold; - Depth depth; - ExtMove moves[MAX_MOVES]; + enum PickType { + Next, + Best + }; + + public: + MovePicker(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&) = delete; + MovePicker(const Position&, + Move, + Depth, + const ButterflyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Move, + const Move*); + MovePicker(const Position&, + Move, + Depth, + const ButterflyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Square); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + Move next_move(bool skipQuiets = false); + + private: + template + Move select(Pred); + template + void score(); + ExtMove* begin() { return cur; } + ExtMove* end() { return endMoves; } + + const Position& pos; + const ButterflyHistory* mainHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** continuationHistory; + Move ttMove; + ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; + int stage; + Square recaptureSquare; + Value threshold; + Depth depth; + ExtMove moves[MAX_MOVES]; }; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef MOVEPICK_H_INCLUDED +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 1f821cf9a3b..679192d4710 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -39,136 +39,144 @@ namespace Stockfish::Eval::NNUE { - // Input feature converter - LargePagePtr featureTransformer; +// Input feature converter +LargePagePtr featureTransformer; - // Evaluation function - AlignedPtr network[LayerStacks]; +// Evaluation function +AlignedPtr network[LayerStacks]; - // Evaluation function file name - std::string fileName; - std::string netDescription; +// Evaluation function file name +std::string fileName; +std::string netDescription; - namespace Detail { +namespace Detail { - // Initialize the evaluation function parameters - template - void initialize(AlignedPtr& pointer) { +// Initialize the evaluation function parameters +template +void initialize(AlignedPtr& pointer) { pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); - } +} - template - void initialize(LargePagePtr& pointer) { +template +void initialize(LargePagePtr& pointer) { - static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); - } +} - // Read evaluation function parameters - template - bool read_parameters(std::istream& stream, T& reference) { +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { std::uint32_t header; header = read_little_endian(stream); - if (!stream || header != T::get_hash_value()) return false; + if (!stream || header != T::get_hash_value()) + return false; return reference.read_parameters(stream); - } +} - // Write evaluation function parameters - template - bool write_parameters(std::ostream& stream, const T& reference) { +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, const T& reference) { write_little_endian(stream, T::get_hash_value()); return reference.write_parameters(stream); - } +} - } // namespace Detail +} // namespace Detail - // Initialize the evaluation function parameters - static void initialize() { +// Initialize the evaluation function parameters +static void initialize() { Detail::initialize(featureTransformer); for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(network[i]); - } + Detail::initialize(network[i]); +} - // Read network header - static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) - { +// Read network header +static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { std::uint32_t version, size; - version = read_little_endian(stream); - *hashValue = read_little_endian(stream); - size = read_little_endian(stream); - if (!stream || version != Version) return false; + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; desc->resize(size); stream.read(&(*desc)[0], size); return !stream.fail(); - } +} - // Write network header - static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) - { +// Write network header +static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { write_little_endian(stream, Version); write_little_endian(stream, hashValue); - write_little_endian(stream, (std::uint32_t)desc.size()); + write_little_endian(stream, (std::uint32_t) desc.size()); stream.write(&desc[0], desc.size()); return !stream.fail(); - } +} - // Read network parameters - static bool read_parameters(std::istream& stream) { +// Read network parameters +static bool read_parameters(std::istream& stream) { std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) return false; - if (hashValue != HashValue) return false; - if (!Detail::read_parameters(stream, *featureTransformer)) return false; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != HashValue) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::read_parameters(stream, *(network[i]))) return false; + if (!Detail::read_parameters(stream, *(network[i]))) + return false; return stream && stream.peek() == std::ios::traits_type::eof(); - } +} - // Write network parameters - static bool write_parameters(std::ostream& stream) { +// Write network parameters +static bool write_parameters(std::ostream& stream) { - if (!write_header(stream, HashValue, netDescription)) return false; - if (!Detail::write_parameters(stream, *featureTransformer)) return false; + if (!write_header(stream, HashValue, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::write_parameters(stream, *(network[i]))) return false; + if (!Detail::write_parameters(stream, *(network[i]))) + return false; return bool(stream); - } +} - void hint_common_parent_position(const Position& pos) { +void hint_common_parent_position(const Position& pos) { featureTransformer->hint_common_access(pos); - } +} - // Evaluation function. Perform differential calculation. - Value evaluate(const Position& pos, bool adjusted, int* complexity) { +// Evaluation function. Perform differential calculation. +Value evaluate(const Position& pos, bool adjusted, int* complexity) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - constexpr int delta = 24; + constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned[ - FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); if (complexity) @@ -176,158 +184,164 @@ namespace Stockfish::Eval::NNUE { // Give more value to positional evaluation when adjusted flag is set if (adjusted) - return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) / (1024 * OutputScale)); + return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) + / (1024 * OutputScale)); else return static_cast((psqt + positional) / OutputScale); - } +} - struct NnueEvalTrace { +struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); - Value psqt[LayerStacks]; - Value positional[LayerStacks]; + Value psqt[LayerStacks]; + Value positional[LayerStacks]; std::size_t correctBucket; - }; +}; - static NnueEvalTrace trace_evaluate(const Position& pos) { +static NnueEvalTrace trace_evaluate(const Position& pos) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned[ - FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); NnueEvalTrace t{}; t.correctBucket = (pos.count() - 1) / 4; - for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { - const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); + const auto positional = network[bucket]->propagate(transformedFeatures); - t.psqt[bucket] = static_cast( materialist / OutputScale ); - t.positional[bucket] = static_cast( positional / OutputScale ); + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); } return t; - } +} - constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); - // format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. - // The buffer must have capacity for at least 5 chars. - static void format_cp_compact(Value v, char* buffer) { +// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +static void format_cp_compact(Value v, char* buffer) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); int cp = std::abs(UCI::to_cp(v)); if (cp >= 10000) { - buffer[1] = '0' + cp / 10000; cp %= 10000; - buffer[2] = '0' + cp / 1000; cp %= 1000; + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; buffer[3] = '0' + cp / 100; buffer[4] = ' '; } else if (cp >= 1000) { - buffer[1] = '0' + cp / 1000; cp %= 1000; - buffer[2] = '0' + cp / 100; cp %= 100; + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; buffer[3] = '.'; buffer[4] = '0' + cp / 10; } else { - buffer[1] = '0' + cp / 100; cp %= 100; + buffer[1] = '0' + cp / 100; + cp %= 100; buffer[2] = '.'; - buffer[3] = '0' + cp / 10; cp %= 10; + buffer[3] = '0' + cp / 10; + cp %= 10; buffer[4] = '0' + cp / 1; } - } +} - // format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals - static void format_cp_aligned_dot(Value v, std::stringstream &stream) { +// format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals +static void format_cp_aligned_dot(Value v, std::stringstream& stream) { const double pawns = std::abs(0.01 * UCI::to_cp(v)); - stream << (v < 0 ? '-' : v > 0 ? '+' : ' ') - << std::setiosflags(std::ios::fixed) - << std::setw(6) - << std::setprecision(2) - << pawns; - } + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} - // trace() returns a string with the value of each piece on a board, - // and a table for (PSQT, Layers) values bucket by bucket. - std::string trace(Position& pos) { +// trace() returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string trace(Position& pos) { std::stringstream ss; - char board[3*8+1][8*8+2]; + char board[3 * 8 + 1][8 * 8 + 2]; std::memset(board, ' ', sizeof(board)); - for (int row = 0; row < 3*8+1; ++row) - board[row][8*8+1] = '\0'; + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; // A lambda to output one box of the board auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - - const int x = int(file) * 8; - const int y = (7 - int(rank)) * 3; - for (int i = 1; i < 8; ++i) - board[y][x+i] = board[y+3][x+i] = '-'; - for (int i = 1; i < 3; ++i) - board[y+i][x] = board[y+i][x+8] = '|'; - board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+'; - if (pc != NO_PIECE) - board[y+1][x+4] = PieceToChar[pc]; - if (value != VALUE_NONE) - format_cp_compact(value, &board[y+2][x+2]); + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y + 2][x + 2]); }; // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. Value base = evaluate(pos); - base = pos.side_to_move() == WHITE ? base : -base; + base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) - for (Rank r = RANK_1; r <= RANK_8; ++r) - { - Square sq = make_square(f, r); - Piece pc = pos.piece_on(sq); - Value v = VALUE_NONE; - - if (pc != NO_PIECE && type_of(pc) != KING) + for (Rank r = RANK_1; r <= RANK_8; ++r) { - auto st = pos.state(); + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; - pos.remove_piece(sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); - Value eval = evaluate(pos); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; + pos.remove_piece(sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; - pos.put_piece(pc, sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - } + Value eval = evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + } - writeSquare(f, r, pc, v); - } + writeSquare(f, r, pc, v); + } ss << " NNUE derived piece values:\n"; - for (int row = 0; row < 3*8+1; ++row) + for (int row = 0; row < 3 * 8 + 1; ++row) ss << board[row] << '\n'; ss << '\n'; @@ -342,41 +356,47 @@ namespace Stockfish::Eval::NNUE { for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { - ss << "| " << bucket << " "; - ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss); ss << " " - << " | "; format_cp_aligned_dot(t.positional[bucket], ss); ss << " " - << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); ss << " " - << " |"; - if (bucket == t.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; + ss << "| " << bucket << " "; + ss << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + ss << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; } ss << "+------------+------------+------------+------------+\n"; return ss.str(); - } +} - // Load eval, from a file stream or a memory stream - bool load_eval(std::string name, std::istream& stream) { +// Load eval, from a file stream or a memory stream +bool load_eval(std::string name, std::istream& stream) { initialize(); fileName = name; return read_parameters(stream); - } +} - // Save eval, to a file stream or a memory stream - bool save_eval(std::ostream& stream) { +// Save eval, to a file stream or a memory stream +bool save_eval(std::ostream& stream) { if (fileName.empty()) - return false; + return false; return write_parameters(stream); - } +} - // Save eval, to a file given by its name - bool save_eval(const std::optional& filename) { +// Save eval, to a file given by its name +bool save_eval(const std::optional& filename) { std::string actualFilename; std::string msg; @@ -387,23 +407,23 @@ namespace Stockfish::Eval::NNUE { { if (currentEvalFileName != EvalFileDefaultName) { - msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; + msg = + "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; - sync_cout << msg << sync_endl; - return false; + sync_cout << msg << sync_endl; + return false; } actualFilename = EvalFileDefaultName; } std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream); + bool saved = save_eval(stream); - msg = saved ? "Network saved successfully to " + actualFilename - : "Failed to export a net"; + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; sync_cout << msg << sync_endl; return saved; - } +} -} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 8faec6cce43..6edc212f4d7 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -32,48 +32,48 @@ #include "nnue_feature_transformer.h" namespace Stockfish { - class Position; - enum Value : int; +class Position; +enum Value : int; } namespace Stockfish::Eval::NNUE { - // Hash value of evaluation function structure - constexpr std::uint32_t HashValue = - FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); +// Hash value of evaluation function structure +constexpr std::uint32_t HashValue = + FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); - // Deleter for automating release of memory area - template - struct AlignedDeleter { +// Deleter for automating release of memory area +template +struct AlignedDeleter { void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); + ptr->~T(); + std_aligned_free(ptr); } - }; +}; - template - struct LargePageDeleter { +template +struct LargePageDeleter { void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); + ptr->~T(); + aligned_large_pages_free(ptr); } - }; +}; - template - using AlignedPtr = std::unique_ptr>; +template +using AlignedPtr = std::unique_ptr>; - template - using LargePagePtr = std::unique_ptr>; +template +using LargePagePtr = std::unique_ptr>; - std::string trace(Position& pos); - Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); - void hint_common_parent_position(const Position& pos); +std::string trace(Position& pos); +Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); +void hint_common_parent_position(const Position& pos); - bool load_eval(std::string name, std::istream& stream); - bool save_eval(std::ostream& stream); - bool save_eval(const std::optional& filename); +bool load_eval(std::string name, std::istream& stream); +bool save_eval(std::ostream& stream); +bool save_eval(const std::optional& filename); } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED +#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 016934b8c4d..6c3fdfdb60b 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -27,61 +27,60 @@ namespace Stockfish::Eval::NNUE::Features { - // Index of a feature for a given king position and another piece on some square - template - inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + KingBuckets[Perspective][ksq]); - } - - // Get a list of indices for active features - template - void HalfKAv2_hm::append_active_indices( - const Position& pos, - IndexList& active - ) { - Square ksq = pos.square(Perspective); - Bitboard bb = pos.pieces(); +// Index of a feature for a given king position and another piece on some square +template +inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { + return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + + KingBuckets[Perspective][ksq]); +} + +// Get a list of indices for active features +template +void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { + Square ksq = pos.square(Perspective); + Bitboard bb = pos.pieces(); while (bb) { - Square s = pop_lsb(bb); - active.push_back(make_index(s, pos.piece_on(s), ksq)); + Square s = pop_lsb(bb); + active.push_back(make_index(s, pos.piece_on(s), ksq)); } - } - - // Explicit template instantiations - template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); - template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); - - // append_changed_indices() : get a list of indices for recently changed features - template - void HalfKAv2_hm::append_changed_indices( - Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added - ) { - for (int i = 0; i < dp.dirty_num; ++i) { - if (dp.from[i] != SQ_NONE) - removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); - if (dp.to[i] != SQ_NONE) - added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); +} + +// Explicit template instantiations +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); + +// append_changed_indices() : get a list of indices for recently changed features +template +void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added) { + for (int i = 0; i < dp.dirty_num; ++i) + { + if (dp.from[i] != SQ_NONE) + removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); + if (dp.to[i] != SQ_NONE) + added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); } - } +} - // Explicit template instantiations - template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); +// Explicit template instantiations +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); - int HalfKAv2_hm::update_cost(const StateInfo* st) { - return st->dirtyPiece.dirty_num; - } +int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; } - int HalfKAv2_hm::refresh_cost(const Position& pos) { - return pos.count(); - } +int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count(); } - bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { +bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { return st->dirtyPiece.piece[0] == make_piece(perspective, KING); - } +} } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 9da1cc05531..540ff895a5a 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -28,41 +28,40 @@ #include "../nnue_common.h" namespace Stockfish { - struct StateInfo; - class Position; +struct StateInfo; +class Position; } namespace Stockfish::Eval::NNUE::Features { - // Feature HalfKAv2_hm: Combination of the position of own king - // and the position of pieces. Position mirrored such that king always on e..h files. - class HalfKAv2_hm { +// Feature HalfKAv2_hm: Combination of the position of own king +// and the position of pieces. Position mirrored such that king always on e..h files. +class HalfKAv2_hm { // unique number for each piece type on each square enum { - PS_NONE = 0, - PS_W_PAWN = 0, - PS_B_PAWN = 1 * SQUARE_NB, - PS_W_KNIGHT = 2 * SQUARE_NB, - PS_B_KNIGHT = 3 * SQUARE_NB, - PS_W_BISHOP = 4 * SQUARE_NB, - PS_B_BISHOP = 5 * SQUARE_NB, - PS_W_ROOK = 6 * SQUARE_NB, - PS_B_ROOK = 7 * SQUARE_NB, - PS_W_QUEEN = 8 * SQUARE_NB, - PS_B_QUEEN = 9 * SQUARE_NB, - PS_KING = 10 * SQUARE_NB, - PS_NB = 11 * SQUARE_NB + PS_NONE = 0, + PS_W_PAWN = 0, + PS_B_PAWN = 1 * SQUARE_NB, + PS_W_KNIGHT = 2 * SQUARE_NB, + PS_B_KNIGHT = 3 * SQUARE_NB, + PS_W_BISHOP = 4 * SQUARE_NB, + PS_B_BISHOP = 5 * SQUARE_NB, + PS_W_ROOK = 6 * SQUARE_NB, + PS_B_ROOK = 7 * SQUARE_NB, + PS_W_QUEEN = 8 * SQUARE_NB, + PS_B_QUEEN = 9 * SQUARE_NB, + PS_KING = 10 * SQUARE_NB, + PS_NB = 11 * SQUARE_NB }; static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { // convention: W - us, B - them // viewed from other side, W and B are reversed - { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, - PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE }, - { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, - PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE } - }; + {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE}, + {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}}; // Index of a feature for a given king position and another piece on some square template @@ -77,9 +76,10 @@ namespace Stockfish::Eval::NNUE::Features { // Number of feature dimensions static constexpr IndexType Dimensions = - static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; + static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; #define B(v) (v * PS_NB) + // clang-format off static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), @@ -98,8 +98,9 @@ namespace Stockfish::Eval::NNUE::Features { B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } }; + // clang-format on #undef B - + // clang-format off // Orient a square according to perspective (rotates by 180 for black) static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, @@ -119,25 +120,20 @@ namespace Stockfish::Eval::NNUE::Features { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } }; + // clang-format on // Maximum number of simultaneously active features. static constexpr IndexType MaxActiveDimensions = 32; - using IndexList = ValueList; + using IndexList = ValueList; // Get a list of indices for active features template - static void append_active_indices( - const Position& pos, - IndexList& active); + static void append_active_indices(const Position& pos, IndexList& active); // Get a list of indices for recently changed features template - static void append_changed_indices( - Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added - ); + static void + append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); // Returns the cost of updating one perspective, the most costly one. // Assumes no refresh needed. @@ -147,8 +143,8 @@ namespace Stockfish::Eval::NNUE::Features { // Returns whether the change stored in this StateInfo means that // a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); - }; +}; } // namespace Stockfish::Eval::NNUE::Features -#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index fc65c34339f..3fba45ed87d 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -42,95 +42,102 @@ namespace Stockfish::Eval::NNUE::Layers { // Fallback implementation for older/other architectures. // Requires the input to be padded to at least 16 values. #if !defined(USE_SSSE3) - template - static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) - { -# if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) -# if defined(USE_SSE2) +template +static void affine_transform_non_ssse3(std::int32_t* output, + const std::int8_t* weights, + const std::int32_t* biases, + const std::uint8_t* input) { + #if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) + #if defined(USE_SSE2) // At least a multiple of 16, with SSE2. - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const __m128i Zeros = _mm_setzero_si128(); - const auto inputVector = reinterpret_cast(input); - -# elif defined(USE_NEON_DOTPROD) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const auto inputVector = reinterpret_cast(input); - -# elif defined(USE_NEON) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const auto inputVector = reinterpret_cast(input); -# endif - - for (IndexType i = 0; i < OutputDimensions; ++i) { - const IndexType offset = i * PaddedInputDimensions; - -# if defined(USE_SSE2) - __m128i sumLo = _mm_cvtsi32_si128(biases[i]); - __m128i sumHi = Zeros; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - __m128i row_j = _mm_load_si128(&row[j]); - __m128i input_j = _mm_load_si128(&inputVector[j]); - __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); - __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); - __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); - __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); - __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); - __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); - sumLo = _mm_add_epi32(sumLo, productLo); - sumHi = _mm_add_epi32(sumHi, productHi); - } - __m128i sum = _mm_add_epi32(sumLo, sumHi); - __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); - sum = _mm_add_epi32(sum, sumHigh_64); - __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); - sum = _mm_add_epi32(sum, sum_second_32); - output[i] = _mm_cvtsi128_si32(sum); - -# elif defined(USE_NEON_DOTPROD) - int32x4_t sum = {biases[i]}; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - sum = vdotq_s32(sum, inputVector[j], row[j]); - } - output[i] = vaddvq_s32(sum); - -# elif defined(USE_NEON) - int32x4_t sum = {biases[i]}; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) { - int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); - 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]; - -# endif + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const __m128i Zeros = _mm_setzero_si128(); + const auto inputVector = reinterpret_cast(input); + + #elif defined(USE_NEON_DOTPROD) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + + #elif defined(USE_NEON) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + #endif + + for (IndexType i = 0; i < OutputDimensions; ++i) + { + const IndexType offset = i * PaddedInputDimensions; + + #if defined(USE_SSE2) + __m128i sumLo = _mm_cvtsi32_si128(biases[i]); + __m128i sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m128i row_j = _mm_load_si128(&row[j]); + __m128i input_j = _mm_load_si128(&inputVector[j]); + __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); + __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); + __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); + __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); + __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_epi32(sumLo, productLo); + sumHi = _mm_add_epi32(sumHi, productHi); + } + __m128i sum = _mm_add_epi32(sumLo, sumHi); + __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sumHigh_64); + __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_second_32); + output[i] = _mm_cvtsi128_si32(sum); + + #elif defined(USE_NEON_DOTPROD) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + sum = vdotq_s32(sum, inputVector[j], row[j]); + } + output[i] = vaddvq_s32(sum); + + #elif defined(USE_NEON) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); + 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]; + + #endif } -# else - std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); - - // Traverse weights in transpose order to take advantage of input sparsity - for (IndexType i = 0; i < InputDimensions; ++i) - if (input[i]) { - const std::int8_t* w = &weights[i]; - const int in = input[i]; - for (IndexType j = 0; j < OutputDimensions; ++j) - output[j] += w[j * PaddedInputDimensions] * in; - } -# endif - } + #else + std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); + + // Traverse weights in transpose order to take advantage of input sparsity + for (IndexType i = 0; i < InputDimensions; ++i) + if (input[i]) + { + const std::int8_t* w = &weights[i]; + const int in = input[i]; + for (IndexType j = 0; j < OutputDimensions; ++j) + output[j] += w[j * PaddedInputDimensions] * in; + } + #endif +} #endif - template - class AffineTransform { +template +class AffineTransform { public: // Input/output type - using InputType = std::uint8_t; + using InputType = std::uint8_t; using OutputType = std::int32_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = OutDims; static constexpr IndexType PaddedInputDimensions = @@ -142,175 +149,168 @@ namespace Stockfish::Eval::NNUE::Layers { // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; } - static constexpr IndexType get_weight_index_scrambled(IndexType i) - { - return - (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + - i / PaddedInputDimensions * 4 + - i % 4; + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + i % 4; } - static constexpr IndexType get_weight_index(IndexType i) - { -#if defined (USE_SSSE3) - return get_weight_index_scrambled(i); + static constexpr IndexType get_weight_index(IndexType i) { +#if defined(USE_SSSE3) + return get_weight_index_scrambled(i); #else - return i; + return i; #endif } // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); + write_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); - return !stream.fail(); + return !stream.fail(); } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { - -#if defined (USE_SSSE3) - - if constexpr (OutputDimensions > 1) - { - -#if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_setzero _mm512_setzero_si512 - #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 - #define vec_hadd Simd::m512_hadd -#elif defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd -#endif - - static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); - - static_assert(OutputDimensions % OutputSimdWidth == 0); + void propagate(const InputType* input, OutputType* output) const { - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; - constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; +#if defined(USE_SSSE3) - const auto input32 = reinterpret_cast(input); - const vec_t* biasvec = reinterpret_cast(biases); - vec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasvec[k]; - - for (IndexType i = 0; i < NumChunks; i += 2) + if constexpr (OutputDimensions > 1) { - const vec_t in0 = vec_set_32(input32[i + 0]); - const vec_t in1 = vec_set_32(input32[i + 1]); - const auto col0 = reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); - const auto col1 = reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]); - } - - vec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) - outptr[k] = acc[k]; - -# undef vec_setzero -# undef vec_set_32 -# undef vec_add_dpbusd_32 -# undef vec_add_dpbusd_32x2 -# undef vec_hadd - - } - else if constexpr (OutputDimensions == 1) - { - -// We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. -#if defined (USE_AVX2) - using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 - #define vec_hadd Simd::m256_hadd -#elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 - #define vec_hadd Simd::m128_hadd -#endif - const auto inputVector = reinterpret_cast(input); - - static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); - - static_assert(PaddedInputDimensions % InputSimdWidth == 0); - - constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; - vec_t sum0 = vec_setzero(); - const auto row0 = reinterpret_cast(&weights[0]); - - for (int j = 0; j < int(NumChunks); ++j) - { - const vec_t in = inputVector[j]; - vec_add_dpbusd_32(sum0, in, row0[j]); + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 + #define vec_hadd Simd::m512_hadd + #elif defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 + #define vec_hadd Simd::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::m128_hadd + #endif + + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + + static_assert(OutputDimensions % OutputSimdWidth == 0); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + + const auto input32 = reinterpret_cast(input); + const vec_t* biasvec = reinterpret_cast(biases); + vec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType i = 0; i < NumChunks; i += 2) + { + const vec_t in0 = vec_set_32(input32[i + 0]); + const vec_t in1 = vec_set_32(input32[i + 1]); + const auto col0 = + reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); + const auto col1 = + reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]); + } + + vec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + + #undef vec_setzero + #undef vec_set_32 + #undef vec_add_dpbusd_32 + #undef vec_add_dpbusd_32x2 + #undef vec_hadd } - output[0] = vec_hadd(sum0, biases[0]); - -# undef vec_setzero -# undef vec_set_32 -# undef vec_add_dpbusd_32 -# undef vec_add_dpbusd_32x2 -# undef vec_hadd + else if constexpr (OutputDimensions == 1) + { - } + // We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. + #if defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 + #define vec_hadd Simd::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 + #define vec_hadd Simd::m128_hadd + #endif + + const auto inputVector = reinterpret_cast(input); + + static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); + + static_assert(PaddedInputDimensions % InputSimdWidth == 0); + + constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < int(NumChunks); ++j) + { + const vec_t in = inputVector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases[0]); + + #undef vec_setzero + #undef vec_set_32 + #undef vec_add_dpbusd_32 + #undef vec_add_dpbusd_32x2 + #undef vec_hadd + } #else - // Use old implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); + // Use old implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); #endif } private: - using BiasType = OutputType; + using BiasType = OutputType; using WeightType = std::int8_t; alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 1dc42109844..6cb4d1a9347 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -38,104 +38,110 @@ namespace Stockfish::Eval::NNUE::Layers { #if (USE_SSSE3 | (USE_NEON >= 8)) - alignas(CacheLineSize) static inline const std::array, 256> lookup_indices = [](){ - std::array, 256> v{}; - for (unsigned i = 0; i < 256; ++i) - { - std::uint64_t j = i, k = 0; - while(j) - v[i][k++] = pop_lsb(j); - } - return v; +alignas(CacheLineSize) static inline const + std::array, 256> lookup_indices = []() { + std::array, 256> v{}; + for (unsigned i = 0; i < 256; ++i) + { + std::uint64_t j = i, k = 0; + while (j) + v[i][k++] = pop_lsb(j); + } + return v; }(); - // Find indices of nonzero numbers in an int32_t array - template - void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { -#if defined (USE_SSSE3) - #if defined (USE_AVX512) - using vec_t = __m512i; - #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) - #elif defined (USE_AVX2) - using vec_t = __m256i; - #if defined(USE_VNNI) && !defined(USE_AVXVNNI) - #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) - #else - #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) +// Find indices of nonzero numbers in an int32_t array +template +void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { + #if defined(USE_SSSE3) + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #elif defined(USE_AVX2) + using vec_t = __m256i; + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) \ + _mm256_movemask_ps( \ + _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_nnz(a) \ + _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) #endif - #elif defined (USE_SSSE3) - using vec_t = __m128i; - #define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) - #endif using vec128_t = __m128i; - #define vec128_zero _mm_setzero_si128() - #define vec128_set_16(a) _mm_set1_epi16(a) - #define vec128_load(a) _mm_load_si128(a) - #define vec128_storeu(a, b) _mm_storeu_si128(a, b) - #define vec128_add(a, b) _mm_add_epi16(a, b) -#elif defined (USE_NEON) - using vec_t = uint32x4_t; + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #define vec128_load(a) _mm_load_si128(a) + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + #elif defined(USE_NEON) + using vec_t = uint32x4_t; static const std::uint32_t Mask[4] = {1, 2, 4, 8}; - #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) - using vec128_t = uint16x8_t; - #define vec128_zero vdupq_n_u16(0) - #define vec128_set_16(a) vdupq_n_u16(a) - #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) - #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) - #define vec128_add(a, b) vaddq_u16(a, b) -#endif + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + using vec128_t = uint16x8_t; + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) + #endif constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) - constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); - constexpr IndexType NumChunks = InputDimensions / ChunkSize; - constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; constexpr IndexType OutputsPerChunk = ChunkSize / 8; - const auto inputVector = reinterpret_cast(input); - IndexType count = 0; - vec128_t base = vec128_zero; - const vec128_t increment = vec128_set_16(8); + const auto inputVector = reinterpret_cast(input); + IndexType count = 0; + vec128_t base = vec128_zero; + const vec128_t increment = vec128_set_16(8); for (IndexType i = 0; i < NumChunks; ++i) { - // bitmask of nonzero values in this chunk - unsigned nnz = 0; - for (IndexType j = 0; j < InputsPerChunk; ++j) - { - const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; - nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); - } - for (IndexType j = 0; j < OutputsPerChunk; ++j) - { - const auto lookup = (nnz >> (j * 8)) & 0xFF; - const auto offsets = vec128_load(reinterpret_cast(&lookup_indices[lookup])); - vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); - count += popcount(lookup); - base = vec128_add(base, increment); - } + // bitmask of nonzero values in this chunk + unsigned nnz = 0; + for (IndexType j = 0; j < InputsPerChunk; ++j) + { + const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; + nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); + } + for (IndexType j = 0; j < OutputsPerChunk; ++j) + { + const auto lookup = (nnz >> (j * 8)) & 0xFF; + const auto offsets = + vec128_load(reinterpret_cast(&lookup_indices[lookup])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); + count += popcount(lookup); + base = vec128_add(base, increment); + } } count_out = count; - } -# undef vec_nnz -# undef vec128_zero -# undef vec128_set_16 -# undef vec128_load -# undef vec128_storeu -# undef vec128_add +} + #undef vec_nnz + #undef vec128_zero + #undef vec128_set_16 + #undef vec128_load + #undef vec128_storeu + #undef vec128_add #endif - // Sparse input implementation - template - class AffineTransformSparseInput { +// Sparse input implementation +template +class AffineTransformSparseInput { public: // Input/output type - using InputType = std::uint8_t; + using InputType = std::uint8_t; using OutputType = std::int32_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = OutDims; - static_assert(OutputDimensions % 16 == 0, "Only implemented for OutputDimensions divisible by 16."); + static_assert(OutputDimensions % 16 == 0, + "Only implemented for OutputDimensions divisible by 16."); static constexpr IndexType PaddedInputDimensions = ceil_to_multiple(InputDimensions, MaxSimdWidth); @@ -152,127 +158,121 @@ namespace Stockfish::Eval::NNUE::Layers { // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0xCC03DAE4u; - hashValue += OutputDimensions; - hashValue ^= prevHash >> 1; - hashValue ^= prevHash << 31; - return hashValue; + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; } - static constexpr IndexType get_weight_index_scrambled(IndexType i) - { - return - (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + - i / PaddedInputDimensions * ChunkSize + - i % ChunkSize; + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + + i / PaddedInputDimensions * ChunkSize + i % ChunkSize; } - static constexpr IndexType get_weight_index(IndexType i) - { + static constexpr IndexType get_weight_index(IndexType i) { #if (USE_SSSE3 | (USE_NEON >= 8)) - return get_weight_index_scrambled(i); + return get_weight_index_scrambled(i); #else - return i; + return i; #endif } // Read network parameters bool read_parameters(std::istream& stream) { - read_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - weights[get_weight_index(i)] = read_little_endian(stream); + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_little_endian(stream, biases, OutputDimensions); + write_little_endian(stream, biases, OutputDimensions); - for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) - write_little_endian(stream, weights[get_weight_index(i)]); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); - return !stream.fail(); + return !stream.fail(); } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { #if (USE_SSSE3 | (USE_NEON >= 8)) -#if defined (USE_AVX512) - using invec_t = __m512i; - using outvec_t = __m512i; - #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 -#elif defined (USE_AVX2) - using invec_t = __m256i; - using outvec_t = __m256i; - #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 -#elif defined (USE_SSSE3) - using invec_t = __m128i; - using outvec_t = __m128i; - #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 -#elif defined (USE_NEON_DOTPROD) - using invec_t = int8x16_t; - using outvec_t = int32x4_t; - #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) - #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 -#elif defined (USE_NEON) - using invec_t = int8x16_t; - using outvec_t = int32x4_t; - #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) - #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 -#endif - static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); - - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; - constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; - std::uint16_t nnz[NumChunks]; - IndexType count; + #if defined(USE_AVX512) + using invec_t = __m512i; + using outvec_t = __m512i; + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #elif defined(USE_AVX2) + using invec_t = __m256i; + using outvec_t = __m256i; + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #elif defined(USE_SSSE3) + using invec_t = __m128i; + using outvec_t = __m128i; + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #elif defined(USE_NEON_DOTPROD) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 + #elif defined(USE_NEON) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 + #endif + static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); - const auto input32 = reinterpret_cast(input); + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + std::uint16_t nnz[NumChunks]; + IndexType count; - // Find indices of nonzero 32bit blocks - find_nnz(input32, nnz, count); + const auto input32 = reinterpret_cast(input); - const outvec_t* biasvec = reinterpret_cast(biases); - outvec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasvec[k]; + // Find indices of nonzero 32bit blocks + find_nnz(input32, nnz, count); - for (IndexType j = 0; j < count; ++j) - { - const auto i = nnz[j]; - const invec_t in = vec_set_32(input32[i]); - const auto col = reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + const outvec_t* biasvec = reinterpret_cast(biases); + outvec_t acc[NumRegs]; for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32(acc[k], in, col[k]); - } - - outvec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) - outptr[k] = acc[k]; -# undef vec_set_32 -# undef vec_add_dpbusd_32 + acc[k] = biasvec[k]; + + for (IndexType j = 0; j < count; ++j) + { + const auto i = nnz[j]; + const invec_t in = vec_set_32(input32[i]); + const auto col = + reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in, col[k]); + } + + outvec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + #undef vec_set_32 + #undef vec_add_dpbusd_32 #else - // Use dense implementation for the other architectures. - affine_transform_non_ssse3< - InputDimensions, - PaddedInputDimensions, - OutputDimensions>(output, weights, biases, input); + // Use dense implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); #endif } private: - using BiasType = OutputType; + using BiasType = OutputType; using WeightType = std::int8_t; alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 48cd6c69345..a3a0c1ede9e 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -29,136 +29,140 @@ namespace Stockfish::Eval::NNUE::Layers { - // Clipped ReLU - template - class ClippedReLU { +// Clipped ReLU +template +class ClippedReLU { public: // Input/output type - using InputType = std::int32_t; + using InputType = std::int32_t; using OutputType = std::uint8_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = InputDimensions; static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, 32); + ceil_to_multiple(OutputDimensions, 32); using OutputBuffer = OutputType[PaddedOutputDimensions]; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0x538D24C7u; - hashValue += prevHash; - return hashValue; + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; } // Read network parameters - bool read_parameters(std::istream&) { - return true; - } + bool read_parameters(std::istream&) { return true; } // Write network parameters - bool write_parameters(std::ostream&) const { - return true; - } + bool write_parameters(std::ostream&) const { return true; } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { + void propagate(const InputType* input, OutputType* output) const { + +#if defined(USE_AVX2) + if constexpr (InputDimensions % SimdWidth == 0) + { + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m256i Zero = _mm256_setzero_si256(); + const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m256i words0 = + _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), + WeightScaleBits); + const __m256i words1 = + _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), + WeightScaleBits); + _mm256_store_si256( + &out[i], _mm256_permutevar8x32_epi32( + _mm256_max_epi8(_mm256_packs_epi16(words0, words1), Zero), Offsets)); + } + } + else + { + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const __m128i Zero = _mm_setzero_si128(); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + } + } + constexpr IndexType Start = InputDimensions % SimdWidth == 0 + ? InputDimensions / SimdWidth * SimdWidth + : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); - #if defined(USE_AVX2) - if constexpr (InputDimensions % SimdWidth == 0) { +#elif defined(USE_SSE2) constexpr IndexType NumChunks = InputDimensions / SimdWidth; - const __m256i Zero = _mm256_setzero_si256(); - const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m256i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_load_si256(&in[i * 4 + 0]), - _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits); - const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( - _mm256_load_si256(&in[i * 4 + 2]), - _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits); - _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( - _mm256_packs_epi16(words0, words1), Zero), Offsets)); - } - } else { - constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + + #ifdef USE_SSE41 const __m128i Zero = _mm_setzero_si128(); - const auto in = reinterpret_cast(input); + #else + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); - const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], + + #ifdef USE_SSE41 + _mm_max_epi8(packedbytes, Zero) + #else + _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + #endif + + ); + } + constexpr IndexType Start = NumChunks * SimdWidth; + +#elif defined(USE_NEON) + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + int16x8_t shifted; + const auto pack = reinterpret_cast(&shifted); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), Zero); + } + constexpr IndexType Start = NumChunks * (SimdWidth / 2); +#else + constexpr IndexType Start = 0; +#endif + + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast(std::clamp(input[i] >> WeightScaleBits, 0, 127)); } - } - constexpr IndexType Start = - InputDimensions % SimdWidth == 0 - ? InputDimensions / SimdWidth * SimdWidth - : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); - - #elif defined(USE_SSE2) - constexpr IndexType NumChunks = InputDimensions / SimdWidth; - - #ifdef USE_SSE41 - const __m128i Zero = _mm_setzero_si128(); - #else - const __m128i k0x80s = _mm_set1_epi8(-128); - #endif - - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); - const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, Zero) - #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) - #endif - - ); - } - constexpr IndexType Start = NumChunks * SimdWidth; - - #elif defined(USE_NEON) - constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); - const int8x8_t Zero = {0}; - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast(output); - for (IndexType i = 0; i < NumChunks; ++i) { - int16x8_t shifted; - const auto pack = reinterpret_cast(&shifted); - pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); - pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); - out[i] = vmax_s8(vqmovn_s16(shifted), Zero); - } - constexpr IndexType Start = NumChunks * (SimdWidth / 2); - #else - constexpr IndexType Start = 0; - #endif - - for (IndexType i = Start; i < InputDimensions; ++i) { - output[i] = static_cast( - std::clamp(input[i] >> WeightScaleBits, 0, 127)); - } } - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 349217edb7a..5425ca192bc 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -20,30 +20,30 @@ #define STOCKFISH_SIMD_H_INCLUDED #if defined(USE_AVX2) -# include + #include #elif defined(USE_SSE41) -# include + #include #elif defined(USE_SSSE3) -# include + #include #elif defined(USE_SSE2) -# include + #include #elif defined(USE_NEON) -# include + #include #endif namespace Stockfish::Simd { -#if defined (USE_AVX512) +#if defined(USE_AVX512) - [[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { - return _mm512_reduce_add_epi32(sum) + bias; - } +[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { + return _mm512_reduce_add_epi32(sum) + bias; +} - /* +/* Parameters: sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]] sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]] @@ -58,186 +58,164 @@ namespace Stockfish::Simd { reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3]) ] */ - [[maybe_unused]] static __m512i m512_hadd128x16_interleave( - __m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) { - - __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); - __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); - - __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); - __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); - - __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); - __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); - - __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); - __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); - - return _mm512_add_epi32(sum0123a, sum0123b); - } - - [[maybe_unused]] static void m512_add_dpbusd_epi32( - __m512i& acc, - __m512i a, - __m512i b) { - -# if defined (USE_VNNI) - acc = _mm512_dpbusd_epi32(acc, a, b); -# else - __m512i product0 = _mm512_maddubs_epi16(a, b); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, product0); -# endif - } - - [[maybe_unused]] static void m512_add_dpbusd_epi32x2( - __m512i& acc, - __m512i a0, __m512i b0, - __m512i a1, __m512i b1) { - -# if defined (USE_VNNI) - acc = _mm512_dpbusd_epi32(acc, a0, b0); - acc = _mm512_dpbusd_epi32(acc, a1, b1); -# else - __m512i product0 = _mm512_maddubs_epi16(a0, b0); - __m512i product1 = _mm512_maddubs_epi16(a1, b1); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); -# endif - } +[[maybe_unused]] static __m512i +m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) { + + __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); + __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); + + __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); + __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); + + __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); + __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); + + __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); + __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); + + return _mm512_add_epi32(sum0123a, sum0123b); +} + +[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { + + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); + #else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, product0); + #endif +} + +[[maybe_unused]] static void +m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1) { + + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a0, b0); + acc = _mm512_dpbusd_epi32(acc, a1, b1); + #else + __m512i product0 = _mm512_maddubs_epi16(a0, b0); + __m512i product1 = _mm512_maddubs_epi16(a1, b1); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); + #endif +} #endif -#if defined (USE_AVX2) - - [[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { - __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); - return _mm_cvtsi128_si32(sum128) + bias; - } - - [[maybe_unused]] static void m256_add_dpbusd_epi32( - __m256i& acc, - __m256i a, - __m256i b) { - -# if defined (USE_VNNI) - acc = _mm256_dpbusd_epi32(acc, a, b); -# else - __m256i product0 = _mm256_maddubs_epi16(a, b); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, product0); -# endif - } - - [[maybe_unused]] static void m256_add_dpbusd_epi32x2( - __m256i& acc, - __m256i a0, __m256i b0, - __m256i a1, __m256i b1) { - -# if defined (USE_VNNI) - acc = _mm256_dpbusd_epi32(acc, a0, b0); - acc = _mm256_dpbusd_epi32(acc, a1, b1); -# else - __m256i product0 = _mm256_maddubs_epi16(a0, b0); - __m256i product1 = _mm256_maddubs_epi16(a1, b1); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); -# endif - } +#if defined(USE_AVX2) + +[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + return _mm_cvtsi128_si32(sum128) + bias; +} + +[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) { + + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); + #else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, product0); + #endif +} + +[[maybe_unused]] static void +m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1) { + + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a0, b0); + acc = _mm256_dpbusd_epi32(acc, a1, b1); + #else + __m256i product0 = _mm256_maddubs_epi16(a0, b0); + __m256i product1 = _mm256_maddubs_epi16(a1, b1); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); + #endif +} #endif -#if defined (USE_SSSE3) +#if defined(USE_SSSE3) - [[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB - return _mm_cvtsi128_si32(sum) + bias; - } +[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + return _mm_cvtsi128_si32(sum) + bias; +} - [[maybe_unused]] static void m128_add_dpbusd_epi32( - __m128i& acc, - __m128i a, - __m128i b) { +[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) { - __m128i product0 = _mm_maddubs_epi16(a, b); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, product0); - } + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, product0); +} - [[maybe_unused]] static void m128_add_dpbusd_epi32x2( - __m128i& acc, - __m128i a0, __m128i b0, - __m128i a1, __m128i b1) { +[[maybe_unused]] static void +m128_add_dpbusd_epi32x2(__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1) { - __m128i product0 = _mm_maddubs_epi16(a0, b0); - __m128i product1 = _mm_maddubs_epi16(a1, b1); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); - } + __m128i product0 = _mm_maddubs_epi16(a0, b0); + __m128i product1 = _mm_maddubs_epi16(a1, b1); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); +} #endif -#if defined (USE_NEON_DOTPROD) +#if defined(USE_NEON_DOTPROD) - [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( - int32x4_t& acc, - int8x16_t a0, int8x16_t b0, - int8x16_t a1, int8x16_t b1) { +[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( + int32x4_t& acc, int8x16_t a0, int8x16_t b0, int8x16_t a1, int8x16_t b1) { - acc = vdotq_s32(acc, a0, b0); - acc = vdotq_s32(acc, a1, b1); - } + acc = vdotq_s32(acc, a0, b0); + acc = vdotq_s32(acc, a1, b1); +} - [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32( - int32x4_t& acc, - int8x16_t a, int8x16_t b) { +[[maybe_unused]] static void +dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { - acc = vdotq_s32(acc, a, b); - } + acc = vdotq_s32(acc, a, b); +} #endif -#if defined (USE_NEON) - - [[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { -# if USE_NEON >= 8 - return vaddvq_s32(s); -# else - return s[0] + s[1] + s[2] + s[3]; -# endif - } - - [[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { - return neon_m128_reduce_add_epi32(sum) + bias; - } - - [[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2( - int32x4_t& acc, - int8x8_t a0, int8x8_t b0, - int8x8_t a1, int8x8_t b1) { - - int16x8_t product = vmull_s8(a0, b0); - product = vmlal_s8(product, a1, b1); - acc = vpadalq_s16(acc, product); - } +#if defined(USE_NEON) + +[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { + #if USE_NEON >= 8 + return vaddvq_s32(s); + #else + return s[0] + s[1] + s[2] + s[3]; + #endif +} + +[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { + return neon_m128_reduce_add_epi32(sum) + bias; +} + +[[maybe_unused]] static void +neon_m128_add_dpbusd_epi32x2(int32x4_t& acc, int8x8_t a0, int8x8_t b0, int8x8_t a1, int8x8_t b1) { + + int16x8_t product = vmull_s8(a0, b0); + product = vmlal_s8(product, a1, b1); + acc = vpadalq_s16(acc, product); +} #endif #if USE_NEON >= 8 - [[maybe_unused]] static void neon_m128_add_dpbusd_epi32( - int32x4_t& acc, - int8x16_t a, int8x16_t b) { - - int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); - int16x8_t product1 = vmull_high_s8(a, b); - int16x8_t sum = vpaddq_s16(product0, product1); - acc = vpadalq_s16(acc, sum); - } +[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); +} #endif } -#endif // STOCKFISH_SIMD_H_INCLUDED +#endif // STOCKFISH_SIMD_H_INCLUDED diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index a3d2059b4de..987de892f3d 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -29,80 +29,75 @@ namespace Stockfish::Eval::NNUE::Layers { - // Clipped ReLU - template - class SqrClippedReLU { +// Clipped ReLU +template +class SqrClippedReLU { public: // Input/output type - using InputType = std::int32_t; + using InputType = std::int32_t; using OutputType = std::uint8_t; // Number of input/output dimensions - static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType InputDimensions = InDims; static constexpr IndexType OutputDimensions = InputDimensions; static constexpr IndexType PaddedOutputDimensions = - ceil_to_multiple(OutputDimensions, 32); + ceil_to_multiple(OutputDimensions, 32); using OutputBuffer = OutputType[PaddedOutputDimensions]; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { - std::uint32_t hashValue = 0x538D24C7u; - hashValue += prevHash; - return hashValue; + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; } // Read network parameters - bool read_parameters(std::istream&) { - return true; - } + bool read_parameters(std::istream&) { return true; } // Write network parameters - bool write_parameters(std::ostream&) const { - return true; - } + bool write_parameters(std::ostream&) const { return true; } // Forward propagation - void propagate( - const InputType* input, OutputType* output) const { - - #if defined(USE_SSE2) - constexpr IndexType NumChunks = InputDimensions / 16; - - static_assert(WeightScaleBits == 6); - const auto in = reinterpret_cast(input); - const auto out = reinterpret_cast<__m128i*>(output); - for (IndexType i = 0; i < NumChunks; ++i) { - __m128i words0 = _mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 0]), - _mm_load_si128(&in[i * 4 + 1])); - __m128i words1 = _mm_packs_epi32( - _mm_load_si128(&in[i * 4 + 2]), - _mm_load_si128(&in[i * 4 + 3])); - - // We shift by WeightScaleBits * 2 = 12 and divide by 128 - // which is an additional shift-right of 7, meaning 19 in total. - // MulHi strips the lower 16 bits so we need to shift out 3 more to match. - words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); - words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); - - _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); - } - constexpr IndexType Start = NumChunks * 16; - - #else - constexpr IndexType Start = 0; - #endif - - for (IndexType i = Start; i < InputDimensions; ++i) { - output[i] = static_cast( - // Really should be /127 but we need to make it fast so we right shift - // by an extra 7 bits instead. Needs to be accounted for in the trainer. - std::min(127ll, ((long long)input[i] * input[i]) >> (2 * WeightScaleBits + 7))); - } + void propagate(const InputType* input, OutputType* output) const { + +#if defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / 16; + + static_assert(WeightScaleBits == 6); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + __m128i words0 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])); + __m128i words1 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])); + + // We shift by WeightScaleBits * 2 = 12 and divide by 128 + // which is an additional shift-right of 7, meaning 19 in total. + // MulHi strips the lower 16 bits so we need to shift out 3 more to match. + words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); + words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); + + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + } + constexpr IndexType Start = NumChunks * 16; + +#else + constexpr IndexType Start = 0; +#endif + + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast( + // Really should be /127 but we need to make it fast so we right shift + // by an extra 7 bits instead. Needs to be accounted for in the trainer. + std::min(127ll, ((long long) input[i] * input[i]) >> (2 * WeightScaleBits + 7))); + } } - }; +}; } // namespace Stockfish::Eval::NNUE::Layers -#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 03fc3bd5cd8..2f1b1d35e52 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -28,13 +28,13 @@ namespace Stockfish::Eval::NNUE { - // Class that holds the result of affine transformation of input features - struct alignas(CacheLineSize) Accumulator { +// Class that holds the result of affine transformation of input features +struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[2][TransformedFeatureDimensions]; std::int32_t psqtAccumulation[2][PSQTBuckets]; - bool computed[2]; - }; + bool computed[2]; +}; } // namespace Stockfish::Eval::NNUE -#endif // NNUE_ACCUMULATOR_H_INCLUDED +#endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 2a7f064bbaa..be0138f14bd 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -39,97 +39,90 @@ using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion constexpr IndexType TransformedFeatureDimensions = 2560; -constexpr IndexType PSQTBuckets = 8; -constexpr IndexType LayerStacks = 8; - -struct Network -{ - static constexpr int FC_0_OUTPUTS = 15; - static constexpr int FC_1_OUTPUTS = 32; - - Layers::AffineTransformSparseInput fc_0; - Layers::SqrClippedReLU ac_sqr_0; - Layers::ClippedReLU ac_0; - Layers::AffineTransform fc_1; - Layers::ClippedReLU ac_1; - Layers::AffineTransform fc_2; - - // Hash value embedded in the evaluation file - static constexpr std::uint32_t get_hash_value() { - // input slice hash - std::uint32_t hashValue = 0xEC42E90Du; - hashValue ^= TransformedFeatureDimensions * 2; - - hashValue = decltype(fc_0)::get_hash_value(hashValue); - hashValue = decltype(ac_0)::get_hash_value(hashValue); - hashValue = decltype(fc_1)::get_hash_value(hashValue); - hashValue = decltype(ac_1)::get_hash_value(hashValue); - hashValue = decltype(fc_2)::get_hash_value(hashValue); - - return hashValue; - } - - // Read network parameters - bool read_parameters(std::istream& stream) { - return fc_0.read_parameters(stream) - && ac_0.read_parameters(stream) - && fc_1.read_parameters(stream) - && ac_1.read_parameters(stream) - && fc_2.read_parameters(stream); - } - - // Write network parameters - bool write_parameters(std::ostream& stream) const { - return fc_0.write_parameters(stream) - && ac_0.write_parameters(stream) - && fc_1.write_parameters(stream) - && ac_1.write_parameters(stream) - && fc_2.write_parameters(stream); - } - - std::int32_t propagate(const TransformedFeatureType* transformedFeatures) - { - struct alignas(CacheLineSize) Buffer - { - alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; - alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; - alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; - alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; - alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; - alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; - - Buffer() - { - std::memset(this, 0, sizeof(*this)); - } - }; +constexpr IndexType PSQTBuckets = 8; +constexpr IndexType LayerStacks = 8; + +struct Network { + static constexpr int FC_0_OUTPUTS = 15; + static constexpr int FC_1_OUTPUTS = 32; + + Layers::AffineTransformSparseInput fc_0; + Layers::SqrClippedReLU ac_sqr_0; + Layers::ClippedReLU ac_0; + Layers::AffineTransform fc_1; + Layers::ClippedReLU ac_1; + Layers::AffineTransform fc_2; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + // input slice hash + std::uint32_t hashValue = 0xEC42E90Du; + hashValue ^= TransformedFeatureDimensions * 2; + + hashValue = decltype(fc_0)::get_hash_value(hashValue); + hashValue = decltype(ac_0)::get_hash_value(hashValue); + hashValue = decltype(fc_1)::get_hash_value(hashValue); + hashValue = decltype(ac_1)::get_hash_value(hashValue); + hashValue = decltype(fc_2)::get_hash_value(hashValue); + + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + return fc_0.read_parameters(stream) && ac_0.read_parameters(stream) + && fc_1.read_parameters(stream) && ac_1.read_parameters(stream) + && fc_2.read_parameters(stream); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + return fc_0.write_parameters(stream) && ac_0.write_parameters(stream) + && fc_1.write_parameters(stream) && ac_1.write_parameters(stream) + && fc_2.write_parameters(stream); + } + + std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { + struct alignas(CacheLineSize) Buffer { + alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType + ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; + alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; + alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; + alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; + alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; + + Buffer() { std::memset(this, 0, sizeof(*this)); } + }; #if defined(__clang__) && (__APPLE__) - // workaround for a bug reported with xcode 12 - static thread_local auto tlsBuffer = std::make_unique(); - // Access TLS only once, cache result. - Buffer& buffer = *tlsBuffer; + // workaround for a bug reported with xcode 12 + static thread_local auto tlsBuffer = std::make_unique(); + // Access TLS only once, cache result. + Buffer& buffer = *tlsBuffer; #else - alignas(CacheLineSize) static thread_local Buffer buffer; + alignas(CacheLineSize) static thread_local Buffer buffer; #endif - fc_0.propagate(transformedFeatures, buffer.fc_0_out); - ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); - ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); - std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType)); - fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); - ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); - fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); - - // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1< + #include #elif defined(USE_SSE41) -#include + #include #elif defined(USE_SSSE3) -#include + #include #elif defined(USE_SSE2) -#include + #include #elif defined(USE_NEON) -#include + #include #endif namespace Stockfish::Eval::NNUE { - // Version of the evaluation file - constexpr std::uint32_t Version = 0x7AF32F20u; +// Version of the evaluation file +constexpr std::uint32_t Version = 0x7AF32F20u; - // Constant used in evaluation value calculation - constexpr int OutputScale = 16; - constexpr int WeightScaleBits = 6; +// Constant used in evaluation value calculation +constexpr int OutputScale = 16; +constexpr int WeightScaleBits = 6; - // Size of cache line (in bytes) - constexpr std::size_t CacheLineSize = 64; +// Size of cache line (in bytes) +constexpr std::size_t CacheLineSize = 64; - constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; - constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; - - // SIMD width (in bytes) - #if defined(USE_AVX2) - constexpr std::size_t SimdWidth = 32; - - #elif defined(USE_SSE2) - constexpr std::size_t SimdWidth = 16; - - #elif defined(USE_NEON) - constexpr std::size_t SimdWidth = 16; - #endif - - constexpr std::size_t MaxSimdWidth = 32; - - // Type of input feature after conversion - using TransformedFeatureType = std::uint8_t; - using IndexType = std::uint32_t; - - // Round n up to be a multiple of base - template - constexpr IntType ceil_to_multiple(IntType n, IntType base) { - return (n + base - 1) / base * base; - } - - - // read_little_endian() is our utility to read an integer (signed or unsigned, any size) - // from a stream in little-endian order. We swap the byte order after the read if - // necessary to return a result with the byte ordering of the compiling machine. - template - inline IntType read_little_endian(std::istream& stream) { - IntType result; - - if (IsLittleEndian) - stream.read(reinterpret_cast(&result), sizeof(IntType)); - else - { - std::uint8_t u[sizeof(IntType)]; - std::make_unsigned_t v = 0; - - stream.read(reinterpret_cast(u), sizeof(IntType)); - for (std::size_t i = 0; i < sizeof(IntType); ++i) - v = (v << 8) | u[sizeof(IntType) - i - 1]; - - std::memcpy(&result, &v, sizeof(IntType)); - } - - return result; - } +constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; +constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; +// SIMD width (in bytes) +#if defined(USE_AVX2) +constexpr std::size_t SimdWidth = 32; - // write_little_endian() is our utility to write an integer (signed or unsigned, any size) - // to a stream in little-endian order. We swap the byte order before the write if - // necessary to always write in little endian order, independently of the byte - // ordering of the compiling machine. - template - inline void write_little_endian(std::ostream& stream, IntType value) { +#elif defined(USE_SSE2) +constexpr std::size_t SimdWidth = 16; - if (IsLittleEndian) - stream.write(reinterpret_cast(&value), sizeof(IntType)); - else - { - std::uint8_t u[sizeof(IntType)]; - std::make_unsigned_t v = value; +#elif defined(USE_NEON) +constexpr std::size_t SimdWidth = 16; +#endif - std::size_t i = 0; - // if constexpr to silence the warning about shift by 8 - if constexpr (sizeof(IntType) > 1) - { +constexpr std::size_t MaxSimdWidth = 32; + +// Type of input feature after conversion +using TransformedFeatureType = std::uint8_t; +using IndexType = std::uint32_t; + +// Round n up to be a multiple of base +template +constexpr IntType ceil_to_multiple(IntType n, IntType base) { + return (n + base - 1) / base * base; +} + + +// read_little_endian() is our utility to read an integer (signed or unsigned, any size) +// from a stream in little-endian order. We swap the byte order after the read if +// necessary to return a result with the byte ordering of the compiling machine. +template +inline IntType read_little_endian(std::istream& stream) { + IntType result; + + if (IsLittleEndian) + stream.read(reinterpret_cast(&result), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = 0; + + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; + + std::memcpy(&result, &v, sizeof(IntType)); + } + + return result; +} + + +// write_little_endian() is our utility to write an integer (signed or unsigned, any size) +// to a stream in little-endian order. We swap the byte order before the write if +// necessary to always write in little endian order, independently of the byte +// ordering of the compiling machine. +template +inline void write_little_endian(std::ostream& stream, IntType value) { + + if (IsLittleEndian) + stream.write(reinterpret_cast(&value), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = value; + + std::size_t i = 0; + // if constexpr to silence the warning about shift by 8 + if constexpr (sizeof(IntType) > 1) + { for (; i + 1 < sizeof(IntType); ++i) { - u[i] = (std::uint8_t)v; + u[i] = (std::uint8_t) v; v >>= 8; } - } - u[i] = (std::uint8_t)v; - - stream.write(reinterpret_cast(u), sizeof(IntType)); - } - } - - - // read_little_endian(s, out, N) : read integers in bulk from a little indian stream. - // This reads N integers from stream s and put them in array out. - template - inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { - if (IsLittleEndian) - stream.read(reinterpret_cast(out), sizeof(IntType) * count); - else - for (std::size_t i = 0; i < count; ++i) - out[i] = read_little_endian(stream); - } - - - // write_little_endian(s, values, N) : write integers in bulk to a little indian stream. - // This takes N integers from array values and writes them on stream s. - template - inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { - if (IsLittleEndian) - stream.write(reinterpret_cast(values), sizeof(IntType) * count); - else - for (std::size_t i = 0; i < count; ++i) - write_little_endian(stream, values[i]); - } - - - // read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in - // the array out. The stream is assumed to be compressed using the signed LEB128 format. - // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. - template - inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { - - // Check the presence of our LEB128 magic string - char leb128MagicString[Leb128MagicStringSize]; - stream.read(leb128MagicString, Leb128MagicStringSize); - assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); - - static_assert(std::is_signed_v, "Not implemented for unsigned types"); - - const std::uint32_t BUF_SIZE = 4096; - std::uint8_t buf[BUF_SIZE]; - - auto bytes_left = read_little_endian(stream); - - std::uint32_t buf_pos = BUF_SIZE; - for (std::size_t i = 0; i < count; ++i) - { - IntType result = 0; - size_t shift = 0; - do - { - if (buf_pos == BUF_SIZE) - { - stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); - buf_pos = 0; - } - - std::uint8_t byte = buf[buf_pos++]; - --bytes_left; - result |= (byte & 0x7f) << shift; - shift += 7; - - if ((byte & 0x80) == 0) - { - out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) ? result - : result | ~((1 << shift) - 1); - break; - } - } - while (shift < sizeof(IntType) * 8); - } - - assert(bytes_left == 0); - } - - - // write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. - // This takes N integers from array values, compress them with the LEB128 algorithm and - // writes the result on the stream s. - // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. - template - inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { - - // Write our LEB128 magic string - stream.write(Leb128MagicString, Leb128MagicStringSize); - - static_assert(std::is_signed_v, "Not implemented for unsigned types"); - - std::uint32_t byte_count = 0; - for (std::size_t i = 0; i < count; ++i) - { - IntType value = values[i]; - std::uint8_t byte; - do - { - byte = value & 0x7f; - value >>= 7; - ++byte_count; - } - while ((byte & 0x40) == 0 ? value != 0 : value != -1); - } - - write_little_endian(stream, byte_count); - - const std::uint32_t BUF_SIZE = 4096; - std::uint8_t buf[BUF_SIZE]; - std::uint32_t buf_pos = 0; - - auto flush = [&]() { - if (buf_pos > 0) - { - stream.write(reinterpret_cast(buf), buf_pos); - buf_pos = 0; - } - }; - - auto write = [&](std::uint8_t byte) { - buf[buf_pos++] = byte; - if (buf_pos == BUF_SIZE) - flush(); - }; - - for (std::size_t i = 0; i < count; ++i) - { - IntType value = values[i]; - while (true) - { - std::uint8_t byte = value & 0x7f; - value >>= 7; - if ((byte & 0x40) == 0 ? value == 0 : value == -1) - { - write(byte); - break; - } - write(byte | 0x80); - } - } - - flush(); - } + } + u[i] = (std::uint8_t) v; + + stream.write(reinterpret_cast(u), sizeof(IntType)); + } +} + + +// read_little_endian(s, out, N) : read integers in bulk from a little indian stream. +// This reads N integers from stream s and put them in array out. +template +inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { + if (IsLittleEndian) + stream.read(reinterpret_cast(out), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + out[i] = read_little_endian(stream); +} + + +// write_little_endian(s, values, N) : write integers in bulk to a little indian stream. +// This takes N integers from array values and writes them on stream s. +template +inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { + if (IsLittleEndian) + stream.write(reinterpret_cast(values), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + write_little_endian(stream, values[i]); +} + + +// read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in +// the array out. The stream is assumed to be compressed using the signed LEB128 format. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { + + // Check the presence of our LEB128 magic string + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + + auto bytes_left = read_little_endian(stream); + + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) + { + IntType result = 0; + size_t shift = 0; + do + { + if (buf_pos == BUF_SIZE) + { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } + + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; + + if ((byte & 0x80) == 0) + { + out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) + ? result + : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); + } + + assert(bytes_left == 0); +} + + +// write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. +// This takes N integers from array values, compress them with the LEB128 algorithm and +// writes the result on the stream s. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { + + // Write our LEB128 magic string + stream.write(Leb128MagicString, Leb128MagicStringSize); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + std::uint8_t byte; + do + { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + + write_little_endian(stream, byte_count); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; + + auto flush = [&]() { + if (buf_pos > 0) + { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; + + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) + flush(); + }; + + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + while (true) + { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) + { + write(byte); + break; + } + write(byte | 0x80); + } + } + + flush(); +} } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_COMMON_H_INCLUDED +#endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 9f02830a63e..9cb14187df4 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -36,303 +36,304 @@ namespace Stockfish::Eval::NNUE { - using BiasType = std::int16_t; - using WeightType = std::int16_t; - using PSQTWeightType = std::int32_t; - - // 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. - #define VECTOR - - static_assert(PSQTBuckets % 8 == 0, - "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); - - #ifdef USE_AVX512 - using vec_t = __m512i; - using psqt_vec_t = __m256i; - #define vec_load(a) _mm512_load_si512(a) - #define vec_store(a,b) _mm512_store_si512(a,b) - #define vec_add_16(a,b) _mm512_add_epi16(a,b) - #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm512_mullo_epi16(a,b) - #define vec_zero() _mm512_setzero_epi32() - #define vec_set_16(a) _mm512_set1_epi16(a) - #define vec_max_16(a,b) _mm512_max_epi16(a,b) - #define vec_min_16(a,b) _mm512_min_epi16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a,7),_mm512_srli_epi16(b,7)); +using BiasType = std::int16_t; +using WeightType = std::int16_t; +using PSQTWeightType = std::int32_t; + +// 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. +#define VECTOR + +static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); + +#ifdef USE_AVX512 +using vec_t = __m512i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a, b) _mm512_store_si512(a, b) + #define vec_add_16(a, b) _mm512_add_epi16(a, b) + #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm512_mullo_epi16(a, b) + #define vec_zero() _mm512_setzero_epi32() + #define vec_set_16(a) _mm512_set1_epi16(a) + #define vec_max_16(a, b) _mm512_max_epi16(a, b) + #define vec_min_16(a, b) _mm512_min_epi16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)); return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted); - } - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a,b) _mm256_store_si256(a,b) - #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 16 - #define MaxChunkSize 64 - - #elif USE_AVX2 - using vec_t = __m256i; - using psqt_vec_t = __m256i; - #define vec_load(a) _mm256_load_si256(a) - #define vec_store(a,b) _mm256_store_si256(a,b) - #define vec_add_16(a,b) _mm256_add_epi16(a,b) - #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm256_mullo_epi16(a,b) - #define vec_zero() _mm256_setzero_si256() - #define vec_set_16(a) _mm256_set1_epi16(a) - #define vec_max_16(a,b) _mm256_max_epi16(a,b) - #define vec_min_16(a,b) _mm256_min_epi16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a,7), _mm256_srli_epi16(b,7)); +} + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 64 + +#elif USE_AVX2 +using vec_t = __m256i; +using psqt_vec_t = __m256i; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a, b) _mm256_store_si256(a, b) + #define vec_add_16(a, b) _mm256_add_epi16(a, b) + #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm256_mullo_epi16(a, b) + #define vec_zero() _mm256_setzero_si256() + #define vec_set_16(a) _mm256_set1_epi16(a) + #define vec_max_16(a, b) _mm256_max_epi16(a, b) + #define vec_min_16(a, b) _mm256_min_epi16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)); return _mm256_permute4x64_epi64(compacted, 0b11011000); - } - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a,b) _mm256_store_si256(a,b) - #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 16 - #define MaxChunkSize 32 - - #elif USE_SSE2 - using vec_t = __m128i; - using psqt_vec_t = __m128i; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) _mm_add_epi16(a,b) - #define vec_sub_16(a,b) _mm_sub_epi16(a,b) - #define vec_mul_16(a,b) _mm_mullo_epi16(a,b) - #define vec_zero() _mm_setzero_si128() - #define vec_set_16(a) _mm_set1_epi16(a) - #define vec_max_16(a,b) _mm_max_epi16(a,b) - #define vec_min_16(a,b) _mm_min_epi16(a,b) - #define vec_msb_pack_16(a,b) _mm_packs_epi16(_mm_srli_epi16(a,7),_mm_srli_epi16(b,7)) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) _mm_add_epi32(a,b) - #define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b) - #define vec_zero_psqt() _mm_setzero_si128() - #define NumRegistersSIMD (Is64Bit ? 16 : 8) - #define MaxChunkSize 16 - - #elif USE_NEON - using vec_t = int16x8_t; - using psqt_vec_t = int32x4_t; - #define vec_load(a) (*(a)) - #define vec_store(a,b) *(a)=(b) - #define vec_add_16(a,b) vaddq_s16(a,b) - #define vec_sub_16(a,b) vsubq_s16(a,b) - #define vec_mul_16(a,b) vmulq_s16(a,b) - #define vec_zero() vec_t{0} - #define vec_set_16(a) vdupq_n_s16(a) - #define vec_max_16(a,b) vmaxq_s16(a,b) - #define vec_min_16(a,b) vminq_s16(a,b) - inline vec_t vec_msb_pack_16(vec_t a, vec_t b){ - const int8x8_t shifta = vshrn_n_s16(a, 7); - const int8x8_t shiftb = vshrn_n_s16(b, 7); - const int8x16_t compacted = vcombine_s8(shifta,shiftb); - return *reinterpret_cast (&compacted); - } - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a,b) *(a)=(b) - #define vec_add_psqt_32(a,b) vaddq_s32(a,b) - #define vec_sub_psqt_32(a,b) vsubq_s32(a,b) - #define vec_zero_psqt() psqt_vec_t{0} - #define NumRegistersSIMD 16 - #define MaxChunkSize 16 - - #else - #undef VECTOR - - #endif - - - #ifdef VECTOR - - // Compute optimal SIMD register count for feature transformer accumulation. - - // We use __m* types as template arguments, which causes GCC to emit warnings - // about losing some attribute information. This is irrelevant to us as we - // only take their size, so the following pragma are harmless. - #if defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wignored-attributes" - #endif - - template - static constexpr int BestRegisterCount() - { - #define RegisterSize sizeof(SIMDRegisterType) - #define LaneSize sizeof(LaneType) - - static_assert(RegisterSize >= LaneSize); - static_assert(MaxRegisters <= NumRegistersSIMD); - static_assert(MaxRegisters > 0); - static_assert(NumRegistersSIMD > 0); - static_assert(RegisterSize % LaneSize == 0); - static_assert((NumLanes * LaneSize) % RegisterSize == 0); - - const int ideal = (NumLanes * LaneSize) / RegisterSize; - if (ideal <= MaxRegisters) - return ideal; - - // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters - for (int divisor = MaxRegisters; divisor > 1; --divisor) - if (ideal % divisor == 0) - return divisor; - - return 1; - } - - static constexpr int NumRegs = BestRegisterCount(); - static constexpr int NumPsqtRegs = BestRegisterCount(); - #if defined(__GNUC__) - #pragma GCC diagnostic pop - #endif - #endif - - - - // Input feature converter - class FeatureTransformer { +} + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + #define MaxChunkSize 32 + +#elif USE_SSE2 +using vec_t = __m128i; +using psqt_vec_t = __m128i; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) _mm_add_epi16(a, b) + #define vec_sub_16(a, b) _mm_sub_epi16(a, b) + #define vec_mul_16(a, b) _mm_mullo_epi16(a, b) + #define vec_zero() _mm_setzero_si128() + #define vec_set_16(a) _mm_set1_epi16(a) + #define vec_max_16(a, b) _mm_max_epi16(a, b) + #define vec_min_16(a, b) _mm_min_epi16(a, b) + #define vec_msb_pack_16(a, b) _mm_packs_epi16(_mm_srli_epi16(a, 7), _mm_srli_epi16(b, 7)) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b) + #define vec_zero_psqt() _mm_setzero_si128() + #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define MaxChunkSize 16 + +#elif USE_NEON +using vec_t = int16x8_t; +using psqt_vec_t = int32x4_t; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) vaddq_s16(a, b) + #define vec_sub_16(a, b) vsubq_s16(a, b) + #define vec_mul_16(a, b) vmulq_s16(a, b) + #define vec_zero() \ + vec_t { 0 } + #define vec_set_16(a) vdupq_n_s16(a) + #define vec_max_16(a, b) vmaxq_s16(a, b) + #define vec_min_16(a, b) vminq_s16(a, b) +inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { + const int8x8_t shifta = vshrn_n_s16(a, 7); + const int8x8_t shiftb = vshrn_n_s16(b, 7); + const int8x16_t compacted = vcombine_s8(shifta, shiftb); + return *reinterpret_cast(&compacted); +} + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) vaddq_s32(a, b) + #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) + #define vec_zero_psqt() \ + psqt_vec_t { 0 } + #define NumRegistersSIMD 16 + #define MaxChunkSize 16 + +#else + #undef VECTOR + +#endif + + +#ifdef VECTOR + + // Compute optimal SIMD register count for feature transformer accumulation. + + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + #endif + +template +static constexpr int BestRegisterCount() { + #define RegisterSize sizeof(SIMDRegisterType) + #define LaneSize sizeof(LaneType) + + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); + + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; + + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; + + return 1; +} + +static constexpr int NumRegs = + BestRegisterCount(); +static constexpr int NumPsqtRegs = + BestRegisterCount(); + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif +#endif + + +// Input feature converter +class FeatureTransformer { private: // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - #ifdef VECTOR - static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; +#ifdef VECTOR + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); - #endif +#endif public: // Output type using OutputType = TransformedFeatureType; // Number of input/output dimensions - static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType InputDimensions = FeatureSet::Dimensions; static constexpr IndexType OutputDimensions = HalfDimensions; // Size of forward propagation buffer - static constexpr std::size_t BufferSize = - OutputDimensions * sizeof(OutputType); + static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { - return FeatureSet::HashValue ^ (OutputDimensions * 2); + return FeatureSet::HashValue ^ (OutputDimensions * 2); } // Read network parameters bool read_parameters(std::istream& stream) { - read_leb_128(stream, biases , HalfDimensions ); - read_leb_128(stream, weights , HalfDimensions * InputDimensions); - read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + read_leb_128(stream, biases, HalfDimensions); + read_leb_128(stream, weights, HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - return !stream.fail(); + return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { - write_leb_128(stream, biases , HalfDimensions ); - write_leb_128(stream, weights , HalfDimensions * InputDimensions); - write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + write_leb_128(stream, biases, HalfDimensions); + write_leb_128(stream, weights, HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - return !stream.fail(); + return !stream.fail(); } // Convert input features std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { - update_accumulator(pos); - update_accumulator(pos); + update_accumulator(pos); + update_accumulator(pos); - const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& accumulation = pos.state()->accumulator.accumulation; - const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; + const auto& accumulation = pos.state()->accumulator.accumulation; + const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; - const auto psqt = ( - psqtAccumulation[perspectives[0]][bucket] - - psqtAccumulation[perspectives[1]][bucket] - ) / 2; + const auto psqt = + (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) + / 2; - for (IndexType p = 0; p < 2; ++p) - { - const IndexType offset = (HalfDimensions / 2) * p; + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = (HalfDimensions / 2) * p; #if defined(VECTOR) - constexpr IndexType OutputChunkSize = MaxChunkSize; - static_assert((HalfDimensions / 2) % OutputChunkSize == 0); - constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; + constexpr IndexType OutputChunkSize = MaxChunkSize; + static_assert((HalfDimensions / 2) % OutputChunkSize == 0); + constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - vec_t Zero = vec_zero(); - vec_t One = vec_set_16(127); + vec_t Zero = vec_zero(); + vec_t One = vec_set_16(127); - const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); - const vec_t* in1 = reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); - vec_t* out = reinterpret_cast< vec_t*>(output + offset); + const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); + const vec_t* in1 = + reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); + vec_t* out = reinterpret_cast(output + offset); - for (IndexType j = 0; j < NumOutputChunks; j += 1) - { - const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); - const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); - const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); - const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); + for (IndexType j = 0; j < NumOutputChunks; j += 1) + { + const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); + const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); + const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); + const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); - const vec_t pa = vec_mul_16(sum0a, sum1a); - const vec_t pb = vec_mul_16(sum0b, sum1b); + const vec_t pa = vec_mul_16(sum0a, sum1a); + const vec_t pb = vec_mul_16(sum0b, sum1b); - out[j] = vec_msb_pack_16(pa, pb); - } + out[j] = vec_msb_pack_16(pa, pb); + } #else - for (IndexType j = 0; j < HalfDimensions / 2; ++j) { - BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; - BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - sum0 = std::clamp(sum0, 0, 127); - sum1 = std::clamp(sum1, 0, 127); - output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); - } + for (IndexType j = 0; j < HalfDimensions / 2; ++j) + { + BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; + BiasType sum1 = + accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; + sum0 = std::clamp(sum0, 0, 127); + sum1 = std::clamp(sum1, 0, 127); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); + } #endif - } + } - return psqt; - } // end of function transform() + return psqt; + } // end of function transform() void hint_common_access(const Position& pos) const { - hint_common_access_for_perspective(pos); - hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); + hint_common_access_for_perspective(pos); } private: template - [[nodiscard]] std::pair 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(), *next = nullptr; - int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !st->accumulator.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; - next = st; - st = st->previous; - } - return { st, next }; + [[nodiscard]] std::pair + 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(), *next = nullptr; + int gain = FeatureSet::refresh_cost(pos); + while (st->previous && !st->accumulator.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; + next = st; + st = st->previous; + } + return {st, next}; } // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. @@ -340,364 +341,374 @@ namespace Stockfish::Eval::NNUE { // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr. // computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr. template - void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const { - static_assert(N > 0); - assert(states_to_update[N-1] == nullptr); + void update_accumulator_incremental(const Position& pos, + StateInfo* computed_st, + StateInfo* states_to_update[N]) const { + static_assert(N > 0); + assert(states_to_update[N - 1] == 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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif +#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[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; +#endif - if (states_to_update[0] == nullptr) - return; + if (states_to_update[0] == nullptr) + return; - // Update incrementally going back through states_to_update. + // Update incrementally going back through states_to_update. - // Gather all features to be updated. - const Square ksq = pos.square(Perspective); + // Gather all features to be updated. + 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. - FeatureSet::IndexList removed[N-1], added[N-1]; + // 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. + FeatureSet::IndexList removed[N - 1], added[N - 1]; - { - int i = N-2; // last potential state to update. Skip last element because it must be nullptr. - while (states_to_update[i] == nullptr) - --i; + { + int i = + N + - 2; // last potential state to update. Skip last element because it must be nullptr. + while (states_to_update[i] == nullptr) + --i; - StateInfo* st2 = states_to_update[i]; + StateInfo* st2 = states_to_update[i]; - for (; i >= 0; --i) - { - states_to_update[i]->accumulator.computed[Perspective] = true; + for (; i >= 0; --i) + { + states_to_update[i]->accumulator.computed[Perspective] = true; - const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - for (; st2 != end_state; st2 = st2->previous) - FeatureSet::append_changed_indices( - ksq, st2->dirtyPiece, removed[i], added[i]); + for (; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, + removed[i], added[i]); + } } - } - StateInfo* st = computed_st; + StateInfo* st = computed_st; - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - if ( states_to_update[1] == nullptr - && (removed[0].size() == 1 || removed[0].size() == 2) - && added[0].size() == 1) - { - assert(states_to_update[0]); + if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2) + && added[0].size() == 1) + { + assert(states_to_update[0]); - auto accIn = reinterpret_cast( - &st->accumulator.accumulation[Perspective][0]); - auto accOut = reinterpret_cast( + auto accIn = + reinterpret_cast(&st->accumulator.accumulation[Perspective][0]); + auto accOut = reinterpret_cast( &states_to_update[0]->accumulator.accumulation[Perspective][0]); - const IndexType offsetR0 = HalfDimensions * removed[0][0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; - auto columnA = reinterpret_cast(&weights[offsetA]); - - if (removed[0].size() == 1) - { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); - } - else - { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k) - accOut[k] = vec_sub_16( - vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); - } - - auto accPsqtIn = reinterpret_cast( + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); + + if (removed[0].size() == 1) + { + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); + + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); + } + + auto accPsqtIn = reinterpret_cast( &st->accumulator.psqtAccumulation[Perspective][0]); - auto accPsqtOut = reinterpret_cast( + auto accPsqtOut = reinterpret_cast( &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; - auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; - auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); - - if (removed[0].size() == 1) - { - for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32( - accPsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]); - } - else - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; - auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); - - for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k) - accPsqtOut[k] = vec_sub_psqt_32( - vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), - vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); - } - } - else - { - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - // Load accumulator - auto accTileIn = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; + auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); + const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; + auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); - for (IndexType i = 0; states_to_update[i]; ++i) + if (removed[0].size() == 1) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); + ++k) + accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[k], columnPsqtR0[k]), + columnPsqtA[k]); + } + else + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; + auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); + ++k) + accPsqtOut[k] = + vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), + vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); + } + } + else + { + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTileIn = reinterpret_cast( + &st->accumulator.accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - auto accTileOut = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); + } } - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - // Load accumulator - auto accTilePsqtIn = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + // Load accumulator + auto accTilePsqtIn = reinterpret_cast( + &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - // Store accumulator - auto accTilePsqtOut = reinterpret_cast( - &states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + // Store accumulator + auto accTilePsqtOut = reinterpret_cast( + &states_to_update[i] + ->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + } } - } - } + } #else - for (IndexType i = 0; states_to_update[i]; ++i) - { - std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], - st->accumulator.accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); + for (IndexType i = 0; states_to_update[i]; ++i) + { + std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], + st->accumulator.accumulation[Perspective], + HalfDimensions * sizeof(BiasType)); - for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k]; + for (std::size_t k = 0; k < PSQTBuckets; ++k) + states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = + st->accumulator.psqtAccumulation[Perspective][k]; - st = states_to_update[i]; + st = states_to_update[i]; - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index; + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; - } + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[Perspective][k] -= + psqtWeights[index * PSQTBuckets + k]; + } - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index; + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] += weights[offset + j]; + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[Perspective][j] += weights[offset + j]; - for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[Perspective][k] += + psqtWeights[index * PSQTBuckets + k]; + } } - } #endif } template void update_accumulator_refresh(const Position& pos) const { - #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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; - #endif - - // Refresh the accumulator - // Could be extracted to a separate function because it's done in 2 places, - // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->accumulator; - accumulator.computed[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); - #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast( - &biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; + // Gcc-10.2 unnecessarily spills AVX2 registers if this array + // is defined in the VECTOR code below, once in each branch + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; +#endif - for (const auto index : active) + // Refresh the accumulator + // Could be extracted to a separate function because it's done in 2 places, + // but it's unclear if compilers would correctly handle register allocation. + auto& accumulator = pos.state()->accumulator; + accumulator.computed[Perspective] = true; + FeatureSet::IndexList active; + FeatureSet::append_active_indices(pos, active); + +#ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); - auto accTile = reinterpret_cast( - &accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); + auto accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } - for (const auto index : active) + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } + for (const auto index : active) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } #else - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); + std::memcpy(accumulator.accumulation[Perspective], biases, + HalfDimensions * sizeof(BiasType)); - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] = 0; - for (const auto index : active) - { - const IndexType offset = HalfDimensions * index; + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[Perspective][j] += weights[offset + j]; - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] += + psqtWeights[index * PSQTBuckets + k]; + } #endif } template void hint_common_access_for_perspective(const Position& pos) const { - // Works like update_accumulator, but performs less work. - // Updates ONLY the accumulator for pos. - - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - // Fast early exit. - if (pos.state()->accumulator.computed[Perspective]) - return; - - auto [oldest_st, _] = try_find_computed_accumulator(pos); - - if (oldest_st->accumulator.computed[Perspective]) - { - // Only update current position accumulator to minimize work. - StateInfo* states_to_update[2] = { pos.state(), nullptr }; - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - else - { - update_accumulator_refresh(pos); - } + // Works like update_accumulator, but performs less work. + // Updates ONLY the accumulator for pos. + + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + // Fast early exit. + if (pos.state()->accumulator.computed[Perspective]) + return; + + auto [oldest_st, _] = try_find_computed_accumulator(pos); + + if (oldest_st->accumulator.computed[Perspective]) + { + // Only update current position accumulator to minimize work. + StateInfo* states_to_update[2] = {pos.state(), nullptr}; + update_accumulator_incremental(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } } template void update_accumulator(const Position& pos) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos); + auto [oldest_st, next] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) - { - if (next == nullptr) - return; - - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. - // Currently we update 2 accumulators. - // 1. for the current position - // 2. the next accumulator after the computed one - // The heuristic may change in the future. - StateInfo *states_to_update[3] = - { next, next == pos.state() ? nullptr : pos.state(), nullptr }; - - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - else - { - update_accumulator_refresh(pos); - } + if (oldest_st->accumulator.computed[Perspective]) + { + if (next == nullptr) + return; + + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Currently we update 2 accumulators. + // 1. for the current position + // 2. the next accumulator after the computed one + // The heuristic may change in the future. + StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), + nullptr}; + + update_accumulator_incremental(pos, oldest_st, states_to_update); + } + else + { + update_accumulator_refresh(pos); + } } alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; - }; +}; } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp index ada371eb951..f7354b3d77c 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -46,59 +46,57 @@ namespace Stockfish { namespace Zobrist { - Key psq[PIECE_NB][SQUARE_NB]; - Key enpassant[FILE_NB]; - Key castling[CASTLING_RIGHT_NB]; - Key side; +Key psq[PIECE_NB][SQUARE_NB]; +Key enpassant[FILE_NB]; +Key castling[CASTLING_RIGHT_NB]; +Key side; } namespace { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; -} // namespace +constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; +} // namespace // operator<<(Position) returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { - os << "\n +---+---+---+---+---+---+---+---+\n"; + os << "\n +---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; - - os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; - } + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; - os << " a b c d e f g h\n" - << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase - << std::setfill('0') << std::setw(16) << pos.key() - << std::setfill(' ') << std::dec << "\nCheckers: "; + os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; + } - for (Bitboard b = pos.checkers(); b; ) - os << UCI::square(pop_lsb(b)) << " "; + os << " a b c d e f g h\n" + << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0') + << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: "; - if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) - && !pos.can_castle(ANY_CASTLING)) - { - StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + for (Bitboard b = pos.checkers(); b;) + os << UCI::square(pop_lsb(b)) << " "; - Position p; - p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); - Tablebases::ProbeState s1, s2; - Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); - int dtz = Tablebases::probe_dtz(p, &s2); - os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" - << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; - } + if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + Position p; + p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } - return os; + return os; } @@ -112,7 +110,7 @@ inline int H1(Key h) { return h & 0x1fff; } inline int H2(Key h) { return (h >> 16) & 0x1fff; } // Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves -Key cuckoo[8192]; +Key cuckoo[8192]; Move cuckooMove[8192]; @@ -120,43 +118,43 @@ Move cuckooMove[8192]; void Position::init() { - PRNG rng(1070372); - - for (Piece pc : Pieces) - for (Square s = SQ_A1; s <= SQ_H8; ++s) - Zobrist::psq[pc][s] = rng.rand(); - - for (File f = FILE_A; f <= FILE_H; ++f) - Zobrist::enpassant[f] = rng.rand(); - - for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) - Zobrist::castling[cr] = rng.rand(); - - Zobrist::side = rng.rand(); - - // Prepare the cuckoo tables - std::memset(cuckoo, 0, sizeof(cuckoo)); - std::memset(cuckooMove, 0, sizeof(cuckooMove)); - [[maybe_unused]] int count = 0; - for (Piece pc : Pieces) - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) - if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) - { - Move move = make_move(s1, s2); - Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; - int i = H1(key); - while (true) - { - std::swap(cuckoo[i], key); - std::swap(cuckooMove[i], move); - if (move == MOVE_NONE) // Arrived at empty slot? - break; - i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot - } - count++; - } - assert(count == 3668); + PRNG rng(1070372); + + for (Piece pc : Pieces) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[pc][s] = rng.rand(); + + for (File f = FILE_A; f <= FILE_H; ++f) + Zobrist::enpassant[f] = rng.rand(); + + for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) + Zobrist::castling[cr] = rng.rand(); + + Zobrist::side = rng.rand(); + + // Prepare the cuckoo tables + std::memset(cuckoo, 0, sizeof(cuckoo)); + std::memset(cuckooMove, 0, sizeof(cuckooMove)); + [[maybe_unused]] int count = 0; + for (Piece pc : Pieces) + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) + if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) + { + Move move = make_move(s1, s2); + Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; + int i = H1(key); + while (true) + { + std::swap(cuckoo[i], key); + std::swap(cuckooMove[i], move); + if (move == MOVE_NONE) // Arrived at empty slot? + break; + i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot + } + count++; + } + assert(count == 3668); } @@ -165,7 +163,7 @@ void Position::init() { // this is assumed to be the responsibility of the GUI. Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { -/* + /* A FEN string defines a particular position using only the ASCII character set. A FEN string contains six fields separated by a space. The fields are: @@ -200,100 +198,103 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th incremented after Black's move. */ - unsigned char col, row, token; - size_t idx; - Square sq = SQ_A8; - std::istringstream ss(fenStr); - - std::memset(this, 0, sizeof(Position)); - std::memset(si, 0, sizeof(StateInfo)); - st = si; + unsigned char col, row, token; + size_t idx; + Square sq = SQ_A8; + std::istringstream ss(fenStr); - ss >> std::noskipws; + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + st = si; - // 1. Piece placement - while ((ss >> token) && !isspace(token)) - { - if (isdigit(token)) - sq += (token - '0') * EAST; // Advance the given number of files + ss >> std::noskipws; - else if (token == '/') - sq += 2 * SOUTH; - - else if ((idx = PieceToChar.find(token)) != string::npos) { - put_piece(Piece(idx), sq); - ++sq; - } - } + // 1. Piece placement + while ((ss >> token) && !isspace(token)) + { + if (isdigit(token)) + sq += (token - '0') * EAST; // Advance the given number of files - // 2. Active color - ss >> token; - sideToMove = (token == 'w' ? WHITE : BLACK); - ss >> token; + else if (token == '/') + sq += 2 * SOUTH; - // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, - // Shredder-FEN that uses the letters of the columns on which the rooks began - // the game instead of KQkq and also X-FEN standard that, in case of Chess960, - // if an inner rook is associated with the castling right, the castling tag is - // replaced by the file letter of the involved rook, as for the Shredder-FEN. - while ((ss >> token) && !isspace(token)) - { - Square rsq; - Color c = islower(token) ? BLACK : WHITE; - Piece rook = make_piece(c, ROOK); + else if ((idx = PieceToChar.find(token)) != string::npos) + { + put_piece(Piece(idx), sq); + ++sq; + } + } - token = char(toupper(token)); + // 2. Active color + ss >> token; + sideToMove = (token == 'w' ? WHITE : BLACK); + ss >> token; + + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + Piece rook = make_piece(c, ROOK); - if (token == 'K') - for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} + token = char(toupper(token)); - else if (token == 'Q') - for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} + if (token == 'K') + for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) + {} - else if (token >= 'A' && token <= 'H') - rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + else if (token == 'Q') + for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) + {} - else - continue; + else if (token >= 'A' && token <= 'H') + rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); - set_castling_right(c, rsq); - } + else + continue; - // 4. En passant square. - // Ignore if square is invalid or not on side to move relative rank 6. - bool enpassant = false; + set_castling_right(c, rsq); + } - if ( ((ss >> col) && (col >= 'a' && col <= 'h')) - && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) - { - st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + bool enpassant = false; - // En passant square will be considered only if - // a) side to move have a pawn threatening epSquare - // b) there is an enemy pawn in front of epSquare - // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); - } + if (((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + + // En passant square will be considered only if + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn in front of epSquare + // c) there is no piece on epSquare or behind epSquare + enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + } - if (!enpassant) - st->epSquare = SQ_NONE; + if (!enpassant) + st->epSquare = SQ_NONE; - // 5-6. Halfmove clock and fullmove number - ss >> std::skipws >> st->rule50 >> gamePly; + // 5-6. Halfmove clock and fullmove number + ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to gamePly starting from 0, - // handle also common incorrect FEN with fullmove = 0. - gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); - chess960 = isChess960; - thisThread = th; - set_state(); + chess960 = isChess960; + thisThread = th; + set_state(); - assert(pos_is_ok()); + assert(pos_is_ok()); - return *this; + return *this; } @@ -302,19 +303,18 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th void Position::set_castling_right(Color c, Square rfrom) { - Square kfrom = square(c); - CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE); + Square kfrom = square(c); + CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE); - st->castlingRights |= cr; - castlingRightsMask[kfrom] |= cr; - castlingRightsMask[rfrom] |= cr; - castlingRookSquare[cr] = rfrom; + st->castlingRights |= cr; + castlingRightsMask[kfrom] |= cr; + castlingRightsMask[rfrom] |= cr; + castlingRookSquare[cr] = rfrom; - Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); - Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); + Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1); + Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1); - castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) - & ~(kfrom | rfrom); + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom); } @@ -322,17 +322,17 @@ void Position::set_castling_right(Color c, Square rfrom) { void Position::set_check_info() const { - update_slider_blockers(WHITE); - update_slider_blockers(BLACK); + update_slider_blockers(WHITE); + update_slider_blockers(BLACK); - Square ksq = square(~sideToMove); + Square ksq = square(~sideToMove); - st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); - st->checkSquares[KNIGHT] = attacks_bb(ksq); - st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); - st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); - st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; - st->checkSquares[KING] = 0; + st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[KNIGHT] = attacks_bb(ksq); + st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); + st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); + st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK]; + st->checkSquares[KING] = 0; } @@ -342,33 +342,33 @@ void Position::set_check_info() const { void Position::set_state() const { - st->key = st->materialKey = 0; - st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; - st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + st->key = st->materialKey = 0; + st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; + st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); - set_check_info(); + set_check_info(); - for (Bitboard b = pieces(); b; ) - { - Square s = pop_lsb(b); - Piece pc = piece_on(s); - st->key ^= Zobrist::psq[pc][s]; + for (Bitboard b = pieces(); b;) + { + Square s = pop_lsb(b); + Piece pc = piece_on(s); + st->key ^= Zobrist::psq[pc][s]; - if (type_of(pc) != KING && type_of(pc) != PAWN) - st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; - } + if (type_of(pc) != KING && type_of(pc) != PAWN) + st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; + } - if (st->epSquare != SQ_NONE) - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + if (st->epSquare != SQ_NONE) + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; - if (sideToMove == BLACK) - st->key ^= Zobrist::side; + if (sideToMove == BLACK) + st->key ^= Zobrist::side; - st->key ^= Zobrist::castling[st->castlingRights]; + st->key ^= Zobrist::castling[st->castlingRights]; - for (Piece pc : Pieces) - for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][cnt]; + for (Piece pc : Pieces) + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + st->materialKey ^= Zobrist::psq[pc][cnt]; } @@ -378,20 +378,20 @@ void Position::set_state() const { Position& Position::set(const string& code, Color c, StateInfo* si) { - assert(code[0] == 'K'); + assert(code[0] == 'K'); - string sides[] = { code.substr(code.find('K', 1)), // Weak - code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong + string sides[] = {code.substr(code.find('K', 1)), // Weak + code.substr(0, std::min(code.find('v'), code.find('K', 1)))}; // Strong - assert(sides[0].length() > 0 && sides[0].length() < 8); - assert(sides[1].length() > 0 && sides[1].length() < 8); + assert(sides[0].length() > 0 && sides[0].length() < 8); + assert(sides[1].length() > 0 && sides[1].length() < 8); - std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); - string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" - + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; + string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1] + + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; - return set(fenStr, false, si, nullptr); + return set(fenStr, false, si, nullptr); } @@ -400,48 +400,48 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { string Position::fen() const { - int emptyCnt; - std::ostringstream ss; + int emptyCnt; + std::ostringstream ss; - for (Rank r = RANK_8; r >= RANK_1; --r) - { - for (File f = FILE_A; f <= FILE_H; ++f) - { - for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) - ++emptyCnt; + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + { + for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) + ++emptyCnt; - if (emptyCnt) - ss << emptyCnt; + if (emptyCnt) + ss << emptyCnt; - if (f <= FILE_H) - ss << PieceToChar[piece_on(make_square(f, r))]; - } + if (f <= FILE_H) + ss << PieceToChar[piece_on(make_square(f, r))]; + } - if (r > RANK_1) - ss << '/'; - } + if (r > RANK_1) + ss << '/'; + } - ss << (sideToMove == WHITE ? " w " : " b "); + ss << (sideToMove == WHITE ? " w " : " b "); - if (can_castle(WHITE_OO)) - ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K'); + if (can_castle(WHITE_OO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO))) : 'K'); - if (can_castle(WHITE_OOO)) - ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); + if (can_castle(WHITE_OOO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); - if (can_castle(BLACK_OO)) - ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k'); + if (can_castle(BLACK_OO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO))) : 'k'); - if (can_castle(BLACK_OOO)) - ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); + if (can_castle(BLACK_OOO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); - if (!can_castle(ANY_CASTLING)) - ss << '-'; + if (!can_castle(ANY_CASTLING)) + ss << '-'; - ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") - << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50 + << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; - return ss.str(); + return ss.str(); } // update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], @@ -449,28 +449,29 @@ string Position::fen() const { // and the slider pieces of color ~c pinning pieces of color c to the king. void Position::update_slider_blockers(Color c) const { - Square ksq = square(c); - - st->blockersForKing[c] = 0; - st->pinners[~c] = 0; + Square ksq = square(c); - // Snipers are sliders that attack 's' when a piece and other snipers are removed - Bitboard snipers = ( (attacks_bb< ROOK>(ksq) & pieces(QUEEN, ROOK)) - | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) & pieces(~c); - Bitboard occupancy = pieces() ^ snipers; + st->blockersForKing[c] = 0; + st->pinners[~c] = 0; - while (snipers) - { - Square sniperSq = pop_lsb(snipers); - Bitboard b = between_bb(ksq, sniperSq) & occupancy; + // Snipers are sliders that attack 's' when a piece and other snipers are removed + Bitboard snipers = ((attacks_bb(ksq) & pieces(QUEEN, ROOK)) + | (attacks_bb(ksq) & pieces(QUEEN, BISHOP))) + & pieces(~c); + Bitboard occupancy = pieces() ^ snipers; - if (b && !more_than_one(b)) + while (snipers) { - st->blockersForKing[c] |= b; - if (b & pieces(c)) - st->pinners[~c] |= sniperSq; + Square sniperSq = pop_lsb(snipers); + Bitboard b = between_bb(ksq, sniperSq) & occupancy; + + if (b && !more_than_one(b)) + { + st->blockersForKing[c] |= b; + if (b & pieces(c)) + st->pinners[~c] |= sniperSq; + } } - } } @@ -479,12 +480,12 @@ void Position::update_slider_blockers(Color c) const { Bitboard Position::attackers_to(Square s, Bitboard occupied) const { - return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) - | (attacks_bb(s) & pieces(KNIGHT)) - | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) - | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(s) & pieces(KING)); + return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) + | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s) & pieces(KNIGHT)) + | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(s) & pieces(KING)); } @@ -492,60 +493,59 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { bool Position::legal(Move m) const { - assert(is_ok(m)); + assert(is_ok(m)); - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); - assert(color_of(moved_piece(m)) == us); - assert(piece_on(square(us)) == make_piece(us, KING)); + assert(color_of(moved_piece(m)) == us); + assert(piece_on(square(us)) == make_piece(us, KING)); - // En passant captures are a tricky special case. Because they are rather - // uncommon, we do it simply by testing whether the king is attacked after - // the move is made. - if (type_of(m) == EN_PASSANT) - { - Square ksq = square(us); - Square capsq = to - pawn_push(us); - Bitboard occupied = (pieces() ^ from ^ capsq) | to; + // En passant captures are a tricky special case. Because they are rather + // uncommon, we do it simply by testing whether the king is attacked after + // the move is made. + if (type_of(m) == EN_PASSANT) + { + Square ksq = square(us); + Square capsq = to - pawn_push(us); + Bitboard occupied = (pieces() ^ from ^ capsq) | to; - assert(to == ep_square()); - assert(moved_piece(m) == make_piece(us, PAWN)); - assert(piece_on(capsq) == make_piece(~us, PAWN)); - assert(piece_on(to) == NO_PIECE); + assert(to == ep_square()); + assert(moved_piece(m) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(~us, PAWN)); + assert(piece_on(to) == NO_PIECE); - return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK)) + return !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, ROOK)) && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); - } - - // Castling moves generation does not check if the castling path is clear of - // enemy attacks, it is delayed at a later time: now! - if (type_of(m) == CASTLING) - { - // After castling, the rook and king final positions are the same in - // Chess960 as they would be in standard chess. - to = relative_square(us, to > from ? SQ_G1 : SQ_C1); - Direction step = to > from ? WEST : EAST; - - for (Square s = to; s != from; s += step) - if (attackers_to(s) & pieces(~us)) - return false; - - // In case of Chess960, verify if the Rook blocks some checks. - // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 || !(blockers_for_king(us) & to_sq(m)); - } - - // If the moving piece is a king, check whether the destination square is - // attacked by the opponent. - if (type_of(piece_on(from)) == KING) - return !(attackers_to(to, pieces() ^ from) & pieces(~us)); - - // A non-king move is legal if and only if it is not pinned or it - // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) - || aligned(from, to, square(us)); + } + + // Castling moves generation does not check if the castling path is clear of + // enemy attacks, it is delayed at a later time: now! + if (type_of(m) == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + to = relative_square(us, to > from ? SQ_G1 : SQ_C1); + Direction step = to > from ? WEST : EAST; + + for (Square s = to; s != from; s += step) + if (attackers_to(s) & pieces(~us)) + return false; + + // In case of Chess960, verify if the Rook blocks some checks. + // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. + return !chess960 || !(blockers_for_king(us) & to_sq(m)); + } + + // If the moving piece is a king, check whether the destination square is + // attacked by the opponent. + if (type_of(piece_on(from)) == KING) + return !(attackers_to(to, pieces() ^ from) & pieces(~us)); + + // A non-king move is legal if and only if it is not pinned or it + // is moving along the ray towards or away from the king. + return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); } @@ -555,70 +555,68 @@ bool Position::legal(Move m) const { bool Position::pseudo_legal(const Move m) const { - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = moved_piece(m); - - // Use a slower but simpler function for uncommon cases - // yet we skip the legality check of MoveList(). - if (type_of(m) != NORMAL) - return checkers() ? MoveList< EVASIONS>(*this).contains(m) - : MoveList(*this).contains(m); - - // Is not a promotion, so the promotion piece must be empty - assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); - - // If the 'from' square is not occupied by a piece belonging to the side to - // move, the move is obviously not legal. - if (pc == NO_PIECE || color_of(pc) != us) - return false; - - // The destination square cannot be occupied by a friendly piece - if (pieces(us) & to) - return false; - - // Handle the special case of a pawn move - if (type_of(pc) == PAWN) - { - // We have already handled promotion moves, so destination - // cannot be on the 8th/1st rank. - if ((Rank8BB | Rank1BB) & to) - return false; - - if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !( (from + 2 * pawn_push(us) == to) // Not a double push - && (relative_rank(us, from) == RANK_2) - && empty(to) - && empty(to - pawn_push(us)))) - return false; - } - else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) - return false; - - // Evasions generator already takes care to avoid some kind of illegal moves - // and legal() relies on this. We therefore have to take care that the same - // kind of moves are filtered out here. - if (checkers()) - { - if (type_of(pc) != KING) - { - // Double check? In this case, a king move is required - if (more_than_one(checkers())) - return false; - - // Our move must be a blocking interposition or a capture of the checking piece - if (!(between_bb(square(us), lsb(checkers())) & to)) - return false; - } - // In case of king moves under check we have to remove the king so as to catch - // invalid moves like b1a1 when opposite queen is on c1. - else if (attackers_to(to, pieces() ^ from) & pieces(~us)) - return false; - } - - return true; + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = moved_piece(m); + + // Use a slower but simpler function for uncommon cases + // yet we skip the legality check of MoveList(). + if (type_of(m) != NORMAL) + return checkers() ? MoveList(*this).contains(m) + : MoveList(*this).contains(m); + + // Is not a promotion, so the promotion piece must be empty + assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); + + // If the 'from' square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == NO_PIECE || color_of(pc) != us) + return false; + + // The destination square cannot be occupied by a friendly piece + if (pieces(us) & to) + return false; + + // Handle the special case of a pawn move + if (type_of(pc) == PAWN) + { + // We have already handled promotion moves, so destination + // cannot be on the 8th/1st rank. + if ((Rank8BB | Rank1BB) & to) + return false; + + if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !((from + 2 * pawn_push(us) == to) // Not a double push + && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) + return false; + } + else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) + return false; + + // Evasions generator already takes care to avoid some kind of illegal moves + // and legal() relies on this. We therefore have to take care that the same + // kind of moves are filtered out here. + if (checkers()) + { + if (type_of(pc) != KING) + { + // Double check? In this case, a king move is required + if (more_than_one(checkers())) + return false; + + // Our move must be a blocking interposition or a capture of the checking piece + if (!(between_bb(square(us), lsb(checkers())) & to)) + return false; + } + // In case of king moves under check we have to remove the king so as to catch + // invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to(to, pieces() ^ from) & pieces(~us)) + return false; + } + + return true; } @@ -626,49 +624,48 @@ bool Position::pseudo_legal(const Move m) const { bool Position::gives_check(Move m) const { - assert(is_ok(m)); - assert(color_of(moved_piece(m)) == sideToMove); - - Square from = from_sq(m); - Square to = to_sq(m); - - // Is there a direct check? - if (check_squares(type_of(piece_on(from))) & to) - return true; - - // Is there a discovered check? - if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) - || type_of(m) == CASTLING; - - switch (type_of(m)) - { - case NORMAL: - return false; - - case PROMOTION: - return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); - - // En passant capture with check? We have already handled the case - // of direct checks and ordinary discovered check, so the only case we - // need to handle is the unusual case of a discovered check through - // the captured pawn. - case EN_PASSANT: - { - Square capsq = make_square(file_of(to), rank_of(from)); - Bitboard b = (pieces() ^ from ^ capsq) | to; - - return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) - | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); - } - default: //CASTLING - { - // Castling is encoded as 'king captures the rook' - Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); - - return check_squares(ROOK) & rto; - } - } + assert(is_ok(m)); + assert(color_of(moved_piece(m)) == sideToMove); + + Square from = from_sq(m); + Square to = to_sq(m); + + // Is there a direct check? + if (check_squares(type_of(piece_on(from))) & to) + return true; + + // Is there a discovered check? + if (blockers_for_king(~sideToMove) & from) + return !aligned(from, to, square(~sideToMove)) || type_of(m) == CASTLING; + + switch (type_of(m)) + { + case NORMAL : + return false; + + case PROMOTION : + return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + + // En passant capture with check? We have already handled the case + // of direct checks and ordinary discovered check, so the only case we + // need to handle is the unusual case of a discovered check through + // the captured pawn. + case EN_PASSANT : { + Square capsq = make_square(file_of(to), rank_of(from)); + Bitboard b = (pieces() ^ from ^ capsq) | to; + + return (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) + | (attacks_bb(square(~sideToMove), b) + & pieces(sideToMove, QUEEN, BISHOP)); + } + default : //CASTLING + { + // Castling is encoded as 'king captures the rook' + Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1); + + return check_squares(ROOK) & rto; + } + } } @@ -678,195 +675,195 @@ bool Position::gives_check(Move m) const { void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { - assert(is_ok(m)); - assert(&newSt != st); - - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - Key k = st->key ^ Zobrist::side; - - // Copy some fields of the old state to our new StateInfo object except the - // ones which are going to be recalculated from scratch anyway and then switch - // our state pointer to point to the new (ready to be updated) state. - std::memcpy(&newSt, st, offsetof(StateInfo, key)); - newSt.previous = st; - st = &newSt; - - // Increment ply counters. In particular, rule50 will be reset to zero later on - // in case of a capture or a pawn move. - ++gamePly; - ++st->rule50; - ++st->pliesFromNull; - - // Used by NNUE - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - auto& dp = st->dirtyPiece; - dp.dirty_num = 1; - - Color us = sideToMove; - Color them = ~us; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(from); - Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); - - assert(color_of(pc) == us); - assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); - assert(type_of(captured) != KING); - - if (type_of(m) == CASTLING) - { - assert(pc == make_piece(us, KING)); - assert(captured == make_piece(us, ROOK)); - - Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); - - k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; - captured = NO_PIECE; - } - - if (captured) - { - Square capsq = to; - - // If the captured piece is a pawn, update pawn hash key, otherwise - // update non-pawn material. - if (type_of(captured) == PAWN) - { - if (type_of(m) == EN_PASSANT) - { - capsq -= pawn_push(us); - - assert(pc == make_piece(us, PAWN)); - assert(to == st->epSquare); - assert(relative_rank(us, to) == RANK_6); - assert(piece_on(to) == NO_PIECE); - assert(piece_on(capsq) == make_piece(them, PAWN)); - } - } - else - st->nonPawnMaterial[them] -= PieceValue[captured]; - - dp.dirty_num = 2; // 1 piece moved, 1 piece captured - dp.piece[1] = captured; - dp.from[1] = capsq; - dp.to[1] = SQ_NONE; - - // Update board and piece lists - remove_piece(capsq); - - // Update material hash key and prefetch access to materialTable - k ^= Zobrist::psq[captured][capsq]; - st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; - - // Reset rule 50 counter - st->rule50 = 0; - } - - // Update hash key - k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - - // Reset en passant square - if (st->epSquare != SQ_NONE) - { - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } - - // Update castling rights if needed - if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) - { - k ^= Zobrist::castling[st->castlingRights]; - st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); - k ^= Zobrist::castling[st->castlingRights]; - } - - // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) - { - dp.piece[0] = pc; - dp.from[0] = from; - dp.to[0] = to; - - move_piece(from, to); - } - - // If the moving piece is a pawn do some special extra work - if (type_of(pc) == PAWN) - { - // Set en passant square if the moved pawn can be captured - if ( (int(to) ^ int(from)) == 16 - && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) - { - st->epSquare = to - pawn_push(us); - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - } - - else if (type_of(m) == PROMOTION) - { - Piece promotion = make_piece(us, promotion_type(m)); - - assert(relative_rank(us, to) == RANK_8); - assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); - - remove_piece(to); - put_piece(promotion, to); - - // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE - dp.to[0] = SQ_NONE; - dp.piece[dp.dirty_num] = promotion; - dp.from[dp.dirty_num] = SQ_NONE; - dp.to[dp.dirty_num] = to; - dp.dirty_num++; - - // Update hash keys - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; - st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] - ^ Zobrist::psq[pc][pieceCount[pc]]; - - // Update material - st->nonPawnMaterial[us] += PieceValue[promotion]; - } - - // Reset rule 50 draw counter - st->rule50 = 0; - } - - // Set capture piece - st->capturedPiece = captured; - - // Update the key with the final value - st->key = k; - - // Calculate checkers bitboard (if move gives check) - st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; - - sideToMove = ~sideToMove; - - // Update king attacks used for fast check detection - set_check_info(); - - // Calculate the repetition info. It is the ply distance from the previous - // occurrence of the same position, negative in the 3-fold case, or zero - // if the position was not repeated. - st->repetition = 0; - int end = std::min(st->rule50, st->pliesFromNull); - if (end >= 4) - { - StateInfo* stp = st->previous->previous; - for (int i = 4; i <= end; i += 2) - { - stp = stp->previous->previous; - if (stp->key == st->key) - { - st->repetition = stp->repetition ? -i : i; - break; - } - } - } - - assert(pos_is_ok()); + assert(is_ok(m)); + assert(&newSt != st); + + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + Key k = st->key ^ Zobrist::side; + + // Copy some fields of the old state to our new StateInfo object except the + // ones which are going to be recalculated from scratch anyway and then switch + // our state pointer to point to the new (ready to be updated) state. + std::memcpy(&newSt, st, offsetof(StateInfo, key)); + newSt.previous = st; + st = &newSt; + + // Increment ply counters. In particular, rule50 will be reset to zero later on + // in case of a capture or a pawn move. + ++gamePly; + ++st->rule50; + ++st->pliesFromNull; + + // Used by NNUE + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; + + Color us = sideToMove; + Color them = ~us; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + + assert(color_of(pc) == us); + assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(type_of(captured) != KING); + + if (type_of(m) == CASTLING) + { + assert(pc == make_piece(us, KING)); + assert(captured == make_piece(us, ROOK)); + + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + + k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + captured = NO_PIECE; + } + + if (captured) + { + Square capsq = to; + + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (type_of(captured) == PAWN) + { + if (type_of(m) == EN_PASSANT) + { + capsq -= pawn_push(us); + + assert(pc == make_piece(us, PAWN)); + assert(to == st->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(to) == NO_PIECE); + assert(piece_on(capsq) == make_piece(them, PAWN)); + } + } + else + st->nonPawnMaterial[them] -= PieceValue[captured]; + + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; + + // Update board and piece lists + remove_piece(capsq); + + // Update material hash key and prefetch access to materialTable + k ^= Zobrist::psq[captured][capsq]; + st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; + + // Reset rule 50 counter + st->rule50 = 0; + } + + // Update hash key + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + // Reset en passant square + if (st->epSquare != SQ_NONE) + { + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } + + // Update castling rights if needed + if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + { + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + k ^= Zobrist::castling[st->castlingRights]; + } + + // Move the piece. The tricky Chess960 castling is handled earlier + if (type_of(m) != CASTLING) + { + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; + + move_piece(from, to); + } + + // If the moving piece is a pawn do some special extra work + if (type_of(pc) == PAWN) + { + // Set en passant square if the moved pawn can be captured + if ((int(to) ^ int(from)) == 16 + && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + { + st->epSquare = to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + } + + else if (type_of(m) == PROMOTION) + { + Piece promotion = make_piece(us, promotion_type(m)); + + assert(relative_rank(us, to) == RANK_8); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + + remove_piece(to); + put_piece(promotion, to); + + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; + + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; + st->materialKey ^= + Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[promotion]; + } + + // Reset rule 50 draw counter + st->rule50 = 0; + } + + // Set capture piece + st->capturedPiece = captured; + + // Update the key with the final value + st->key = k; + + // Calculate checkers bitboard (if move gives check) + st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + + sideToMove = ~sideToMove; + + // Update king attacks used for fast check detection + set_check_info(); + + // Calculate the repetition info. It is the ply distance from the previous + // occurrence of the same position, negative in the 3-fold case, or zero + // if the position was not repeated. + st->repetition = 0; + int end = std::min(st->rule50, st->pliesFromNull); + if (end >= 4) + { + StateInfo* stp = st->previous->previous; + for (int i = 4; i <= end; i += 2) + { + stp = stp->previous->previous; + if (stp->key == st->key) + { + st->repetition = stp->repetition ? -i : i; + break; + } + } + } + + assert(pos_is_ok()); } @@ -875,62 +872,62 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { void Position::undo_move(Move m) { - assert(is_ok(m)); - - sideToMove = ~sideToMove; - - Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(to); - - assert(empty(from) || type_of(m) == CASTLING); - assert(type_of(st->capturedPiece) != KING); - - if (type_of(m) == PROMOTION) - { - assert(relative_rank(us, to) == RANK_8); - assert(type_of(pc) == promotion_type(m)); - assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); - - remove_piece(to); - pc = make_piece(us, PAWN); - put_piece(pc, to); - } - - if (type_of(m) == CASTLING) - { - Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); - } - else - { - move_piece(to, from); // Put the piece back at the source square - - if (st->capturedPiece) - { - Square capsq = to; - - if (type_of(m) == EN_PASSANT) - { - capsq -= pawn_push(us); - - assert(type_of(pc) == PAWN); - assert(to == st->previous->epSquare); - assert(relative_rank(us, to) == RANK_6); - assert(piece_on(capsq) == NO_PIECE); - assert(st->capturedPiece == make_piece(~us, PAWN)); - } - - put_piece(st->capturedPiece, capsq); // Restore the captured piece - } - } - - // Finally point our state pointer back to the previous state - st = st->previous; - --gamePly; - - assert(pos_is_ok()); + assert(is_ok(m)); + + sideToMove = ~sideToMove; + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(to); + + assert(empty(from) || type_of(m) == CASTLING); + assert(type_of(st->capturedPiece) != KING); + + if (type_of(m) == PROMOTION) + { + assert(relative_rank(us, to) == RANK_8); + assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + + remove_piece(to); + pc = make_piece(us, PAWN); + put_piece(pc, to); + } + + if (type_of(m) == CASTLING) + { + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + } + else + { + move_piece(to, from); // Put the piece back at the source square + + if (st->capturedPiece) + { + Square capsq = to; + + if (type_of(m) == EN_PASSANT) + { + capsq -= pawn_push(us); + + assert(type_of(pc) == PAWN); + assert(to == st->previous->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(capsq) == NO_PIECE); + assert(st->capturedPiece == make_piece(~us, PAWN)); + } + + put_piece(st->capturedPiece, capsq); // Restore the captured piece + } + } + + // Finally point our state pointer back to the previous state + st = st->previous; + --gamePly; + + assert(pos_is_ok()); } @@ -939,29 +936,30 @@ void Position::undo_move(Move m) { template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { - bool kingSide = to > from; - rfrom = to; // Castling is encoded as "king captures friendly rook" - rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); - to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); - - if (Do) - { - auto& dp = st->dirtyPiece; - dp.piece[0] = make_piece(us, KING); - dp.from[0] = from; - dp.to[0] = to; - dp.piece[1] = make_piece(us, ROOK); - dp.from[1] = rfrom; - dp.to[1] = rto; - dp.dirty_num = 2; - } - - // Remove both pieces first since squares could overlap in Chess960 - remove_piece(Do ? from : to); - remove_piece(Do ? rfrom : rto); - board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // remove_piece does not do this for us - put_piece(make_piece(us, KING), Do ? to : from); - put_piece(make_piece(us, ROOK), Do ? rto : rfrom); + bool kingSide = to > from; + rfrom = to; // Castling is encoded as "king captures friendly rook" + rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + + if (Do) + { + auto& dp = st->dirtyPiece; + dp.piece[0] = make_piece(us, KING); + dp.from[0] = from; + dp.to[0] = to; + dp.piece[1] = make_piece(us, ROOK); + dp.from[1] = rfrom; + dp.to[1] = rto; + dp.dirty_num = 2; + } + + // Remove both pieces first since squares could overlap in Chess960 + remove_piece(Do ? from : to); + remove_piece(Do ? rfrom : rto); + board[Do ? from : to] = board[Do ? rfrom : rto] = + NO_PIECE; // remove_piece does not do this for us + put_piece(make_piece(us, KING), Do ? to : from); + put_piece(make_piece(us, ROOK), Do ? rto : rfrom); } @@ -970,38 +968,38 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ void Position::do_null_move(StateInfo& newSt) { - assert(!checkers()); - assert(&newSt != st); + assert(!checkers()); + assert(&newSt != st); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); - newSt.previous = st; - st = &newSt; + newSt.previous = st; + st = &newSt; - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; - if (st->epSquare != SQ_NONE) - { - st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; - st->epSquare = SQ_NONE; - } + if (st->epSquare != SQ_NONE) + { + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } - st->key ^= Zobrist::side; - ++st->rule50; - prefetch(TT.first_entry(key())); + st->key ^= Zobrist::side; + ++st->rule50; + prefetch(TT.first_entry(key())); - st->pliesFromNull = 0; + st->pliesFromNull = 0; - sideToMove = ~sideToMove; + sideToMove = ~sideToMove; - set_check_info(); + set_check_info(); - st->repetition = 0; + st->repetition = 0; - assert(pos_is_ok()); + assert(pos_is_ok()); } @@ -1009,10 +1007,10 @@ void Position::do_null_move(StateInfo& newSt) { void Position::undo_null_move() { - assert(!checkers()); + assert(!checkers()); - st = st->previous; - sideToMove = ~sideToMove; + st = st->previous; + sideToMove = ~sideToMove; } @@ -1022,19 +1020,18 @@ void Position::undo_null_move() { Key Position::key_after(Move m) const { - Square from = from_sq(m); - Square to = to_sq(m); - Piece pc = piece_on(from); - Piece captured = piece_on(to); - Key k = st->key ^ Zobrist::side; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = piece_on(to); + Key k = st->key ^ Zobrist::side; - if (captured) - k ^= Zobrist::psq[captured][to]; + if (captured) + k ^= Zobrist::psq[captured][to]; - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; - return (captured || type_of(pc) == PAWN) - ? k : adjust_key50(k); + return (captured || type_of(pc) == PAWN) ? k : adjust_key50(k); } @@ -1044,103 +1041,103 @@ Key Position::key_after(Move m) const { bool Position::see_ge(Move m, Value threshold) const { - assert(is_ok(m)); - - // Only deal with normal moves, assume others pass a simple SEE - if (type_of(m) != NORMAL) - return VALUE_ZERO >= threshold; - - Square from = from_sq(m), to = to_sq(m); - - int swap = PieceValue[piece_on(to)] - threshold; - if (swap < 0) - return false; - - swap = PieceValue[piece_on(from)] - swap; - if (swap <= 0) - return true; - - assert(color_of(piece_on(from)) == sideToMove); - Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic - Color stm = sideToMove; - Bitboard attackers = attackers_to(to, occupied); - Bitboard stmAttackers, bb; - int res = 1; - - while (true) - { - stm = ~stm; - attackers &= occupied; - - // If stm has no more attackers then give up: stm loses - if (!(stmAttackers = attackers & pieces(stm))) - break; - - // Don't allow pinned pieces to attack as long as there are - // pinners on their original square. - if (pinners(~stm) & occupied) - { - stmAttackers &= ~blockers_for_king(stm); - - if (!stmAttackers) - break; - } - - res ^= 1; - - // Locate and remove the next least valuable attacker, and add to - // the bitboard 'attackers' any X-ray attackers behind it. - if ((bb = stmAttackers & pieces(PAWN))) - { - if ((swap = PawnValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); - - attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); - } - - else if ((bb = stmAttackers & pieces(KNIGHT))) - { - if ((swap = KnightValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); - } - - else if ((bb = stmAttackers & pieces(BISHOP))) - { - if ((swap = BishopValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); - - attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); - } - - else if ((bb = stmAttackers & pieces(ROOK))) - { - if ((swap = RookValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); - - attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); - } - - else if ((bb = stmAttackers & pieces(QUEEN))) - { - if ((swap = QueenValue - swap) < res) - break; - occupied ^= least_significant_square_bb(bb); - - attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); - } - - else // KING - // If we "capture" with the king but the opponent still has attackers, - // reverse the result. - return (attackers & ~pieces(stm)) ? res ^ 1 : res; - } - - return bool(res); + assert(is_ok(m)); + + // Only deal with normal moves, assume others pass a simple SEE + if (type_of(m) != NORMAL) + return VALUE_ZERO >= threshold; + + Square from = from_sq(m), to = to_sq(m); + + int swap = PieceValue[piece_on(to)] - threshold; + if (swap < 0) + return false; + + swap = PieceValue[piece_on(from)] - swap; + if (swap <= 0) + return true; + + assert(color_of(piece_on(from)) == sideToMove); + Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic + Color stm = sideToMove; + Bitboard attackers = attackers_to(to, occupied); + Bitboard stmAttackers, bb; + int res = 1; + + while (true) + { + stm = ~stm; + attackers &= occupied; + + // If stm has no more attackers then give up: stm loses + if (!(stmAttackers = attackers & pieces(stm))) + break; + + // Don't allow pinned pieces to attack as long as there are + // pinners on their original square. + if (pinners(~stm) & occupied) + { + stmAttackers &= ~blockers_for_king(stm); + + if (!stmAttackers) + break; + } + + res ^= 1; + + // Locate and remove the next least valuable attacker, and add to + // the bitboard 'attackers' any X-ray attackers behind it. + if ((bb = stmAttackers & pieces(PAWN))) + { + if ((swap = PawnValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(KNIGHT))) + { + if ((swap = KnightValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + } + + else if ((bb = stmAttackers & pieces(BISHOP))) + { + if ((swap = BishopValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(ROOK))) + { + if ((swap = RookValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + + attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); + } + + else if ((bb = stmAttackers & pieces(QUEEN))) + { + if ((swap = QueenValue - swap) < res) + break; + occupied ^= least_significant_square_bb(bb); + + attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(to, occupied) & pieces(ROOK, QUEEN)); + } + + else // KING + // If we "capture" with the king but the opponent still has attackers, + // reverse the result. + return (attackers & ~pieces(stm)) ? res ^ 1 : res; + } + + return bool(res); } // Position::is_draw() tests whether the position is drawn by 50-move rule @@ -1148,12 +1145,12 @@ bool Position::see_ge(Move m, Value threshold) const { bool Position::is_draw(int ply) const { - if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) - return true; + 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 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; } @@ -1163,7 +1160,7 @@ bool Position::is_draw(int ply) const { bool Position::has_repeated() const { StateInfo* stc = st; - int end = std::min(st->rule50, st->pliesFromNull); + int end = std::min(st->rule50, st->pliesFromNull); while (end-- >= 4) { if (stc->repetition) @@ -1180,47 +1177,46 @@ bool Position::has_repeated() const { bool Position::has_game_cycle(int ply) const { - int j; + int j; + + int end = std::min(st->rule50, st->pliesFromNull); - int end = std::min(st->rule50, st->pliesFromNull); + if (end < 3) + return false; - if (end < 3) - return false; + Key originalKey = st->key; + StateInfo* stp = st->previous; - Key originalKey = st->key; - StateInfo* stp = st->previous; - - for (int i = 3; i <= end; i += 2) - { - stp = stp->previous->previous; - - Key moveKey = originalKey ^ stp->key; - if ( (j = H1(moveKey), cuckoo[j] == moveKey) - || (j = H2(moveKey), cuckoo[j] == moveKey)) - { - Move move = cuckooMove[j]; - Square s1 = from_sq(move); - Square s2 = to_sq(move); - - if (!((between_bb(s1, s2) ^ s2) & pieces())) - { - if (ply > i) - return true; - - // For nodes before or at the root, check that the move is a - // repetition rather than a move to the current position. - // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in - // the same location, so we have to select which square to check. - if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) - continue; - - // For repetitions before or at the root, require one more - if (stp->repetition) - return true; - } - } - } - return false; + for (int i = 3; i <= end; i += 2) + { + stp = stp->previous->previous; + + Key moveKey = originalKey ^ stp->key; + if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) + { + Move move = cuckooMove[j]; + Square s1 = from_sq(move); + Square s2 = to_sq(move); + + if (!((between_bb(s1, s2) ^ s2) & pieces())) + { + if (ply > i) + return true; + + // For nodes before or at the root, check that the move is a + // repetition rather than a move to the current position. + // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in + // the same location, so we have to select which square to check. + if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) + continue; + + // For repetitions before or at the root, require one more + if (stp->repetition) + return true; + } + } + } + return false; } @@ -1229,33 +1225,33 @@ bool Position::has_game_cycle(int ply) const { void Position::flip() { - string f, token; - std::stringstream ss(fen()); + string f, token; + std::stringstream ss(fen()); - for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement - { - std::getline(ss, token, r > RANK_1 ? '/' : ' '); - f.insert(0, token + (f.empty() ? " " : "/")); - } + for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement + { + std::getline(ss, token, r > RANK_1 ? '/' : ' '); + f.insert(0, token + (f.empty() ? " " : "/")); + } - ss >> token; // Active color - f += (token == "w" ? "B " : "W "); // Will be lowercased later + ss >> token; // Active color + f += (token == "w" ? "B " : "W "); // Will be lowercased later - ss >> token; // Castling availability - f += token + " "; + ss >> token; // Castling availability + f += token + " "; - std::transform(f.begin(), f.end(), f.begin(), - [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + std::transform(f.begin(), f.end(), f.begin(), + [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); - ss >> token; // En passant square - f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); + ss >> token; // En passant square + f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); - std::getline(ss, token); // Half and full moves - f += token; + std::getline(ss, token); // Half and full moves + f += token; - set(f, is_chess960(), st, this_thread()); + set(f, is_chess960(), st, this_thread()); - assert(pos_is_ok()); + assert(pos_is_ok()); } @@ -1265,58 +1261,51 @@ void Position::flip() { bool Position::pos_is_ok() const { - constexpr bool Fast = true; // Quick (default) or full check? - - if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(square(WHITE)) != W_KING - || piece_on(square(BLACK)) != B_KING - || ( ep_square() != SQ_NONE - && relative_rank(sideToMove, ep_square()) != RANK_6)) - assert(0 && "pos_is_ok: Default"); - - if (Fast) - return true; - - if ( pieceCount[W_KING] != 1 - || pieceCount[B_KING] != 1 - || attackers_to(square(~sideToMove)) & pieces(sideToMove)) - assert(0 && "pos_is_ok: Kings"); - - if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) - || pieceCount[W_PAWN] > 8 - || pieceCount[B_PAWN] > 8) - assert(0 && "pos_is_ok: Pawns"); - - if ( (pieces(WHITE) & pieces(BLACK)) - || (pieces(WHITE) | pieces(BLACK)) != pieces() - || popcount(pieces(WHITE)) > 16 - || popcount(pieces(BLACK)) > 16) - assert(0 && "pos_is_ok: Bitboards"); - - for (PieceType p1 = PAWN; p1 <= KING; ++p1) - for (PieceType p2 = PAWN; p2 <= KING; ++p2) - if (p1 != p2 && (pieces(p1) & pieces(p2))) - assert(0 && "pos_is_ok: Bitboards"); - - - for (Piece pc : Pieces) - if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) - || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) - assert(0 && "pos_is_ok: Pieces"); - - for (Color c : { WHITE, BLACK }) - for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) - { - if (!can_castle(cr)) - continue; - - if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) - || castlingRightsMask[castlingRookSquare[cr]] != cr - || (castlingRightsMask[square(c)] & cr) != cr) - assert(0 && "pos_is_ok: Castling"); - } - - return true; + constexpr bool Fast = true; // Quick (default) or full check? + + if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6)) + assert(0 && "pos_is_ok: Default"); + + if (Fast) + return true; + + if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1 + || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + assert(0 && "pos_is_ok: Kings"); + + if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8) + assert(0 && "pos_is_ok: Pawns"); + + if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16) + assert(0 && "pos_is_ok: Bitboards"); + + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); + + + for (Piece pc : Pieces) + if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); + + for (Color c : {WHITE, BLACK}) + for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) + { + if (!can_castle(cr)) + continue; + + if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[cr]] != cr + || (castlingRightsMask[square(c)] & cr) != cr) + assert(0 && "pos_is_ok: Castling"); + } + + return true; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/position.h b/src/position.h index 23fd5bf5688..2aeb8fcd575 100644 --- a/src/position.h +++ b/src/position.h @@ -37,27 +37,27 @@ namespace Stockfish { struct StateInfo { - // Copied when making a move - Key materialKey; - Value nonPawnMaterial[COLOR_NB]; - int castlingRights; - int rule50; - int pliesFromNull; - Square epSquare; - - // Not copied when making a move (will be recomputed anyhow) - Key key; - Bitboard checkersBB; - StateInfo* previous; - Bitboard blockersForKing[COLOR_NB]; - Bitboard pinners[COLOR_NB]; - Bitboard checkSquares[PIECE_TYPE_NB]; - Piece capturedPiece; - int repetition; - - // Used by NNUE - Eval::NNUE::Accumulator accumulator; - DirtyPiece dirtyPiece; + // Copied when making a move + Key materialKey; + Value nonPawnMaterial[COLOR_NB]; + int castlingRights; + int rule50; + int pliesFromNull; + Square epSquare; + + // Not copied when making a move (will be recomputed anyhow) + Key key; + Bitboard checkersBB; + StateInfo* previous; + Bitboard blockersForKing[COLOR_NB]; + Bitboard pinners[COLOR_NB]; + Bitboard checkSquares[PIECE_TYPE_NB]; + Piece capturedPiece; + int repetition; + + // Used by NNUE + Eval::NNUE::Accumulator accumulator; + DirtyPiece dirtyPiece; }; @@ -75,329 +75,290 @@ using StateListPtr = std::unique_ptr>; class Thread; class Position { -public: - static void init(); - - Position() = default; - Position(const Position&) = delete; - Position& operator=(const Position&) = delete; - - // FEN string input/output - Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); - Position& set(const std::string& code, Color c, StateInfo* si); - std::string fen() const; - - // Position representation - Bitboard pieces(PieceType pt = ALL_PIECES) const; - template Bitboard pieces(PieceType pt, PieceTypes... pts) const; - Bitboard pieces(Color c) const; - template Bitboard pieces(Color c, PieceTypes... pts) const; - Piece piece_on(Square s) const; - Square ep_square() const; - bool empty(Square s) const; - template int count(Color c) const; - template int count() const; - template Square square(Color c) const; - - // Castling - CastlingRights castling_rights(Color c) const; - bool can_castle(CastlingRights cr) const; - bool castling_impeded(CastlingRights cr) const; - Square castling_rook_square(CastlingRights cr) const; - - // Checking - Bitboard checkers() const; - Bitboard blockers_for_king(Color c) const; - Bitboard check_squares(PieceType pt) const; - Bitboard pinners(Color c) const; - - // Attacks to/from a given square - Bitboard attackers_to(Square s) const; - Bitboard attackers_to(Square s, Bitboard occupied) const; - void update_slider_blockers(Color c) const; - template Bitboard attacks_by(Color c) const; - - // Properties of moves - bool legal(Move m) const; - bool pseudo_legal(const Move m) const; - bool capture(Move m) const; - bool capture_stage(Move m) const; - bool gives_check(Move m) const; - Piece moved_piece(Move m) const; - Piece captured_piece() const; - - // Doing and undoing moves - void do_move(Move m, StateInfo& newSt); - void do_move(Move m, StateInfo& newSt, bool givesCheck); - void undo_move(Move m); - void do_null_move(StateInfo& newSt); - void undo_null_move(); - - // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; - - // Accessing hash keys - Key key() const; - Key key_after(Move m) const; - Key material_key() const; - - // Other properties of the position - Color side_to_move() const; - int game_ply() const; - bool is_chess960() const; - Thread* this_thread() const; - bool is_draw(int ply) const; - bool has_game_cycle(int ply) const; - bool has_repeated() const; - int rule50_count() const; - Value non_pawn_material(Color c) const; - Value non_pawn_material() const; - - // Position consistency check, for debugging - bool pos_is_ok() const; - void flip(); - - // Used by NNUE - StateInfo* state() const; - - void put_piece(Piece pc, Square s); - void remove_piece(Square s); - -private: - // Initialization helpers (used while setting up a position) - void set_castling_right(Color c, Square rfrom); - void set_state() const; - void set_check_info() const; - - // Other helpers - void move_piece(Square from, Square to); - template - void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); - template - Key adjust_key50(Key k) const; - - // Data members - Piece board[SQUARE_NB]; - Bitboard byTypeBB[PIECE_TYPE_NB]; - Bitboard byColorBB[COLOR_NB]; - int pieceCount[PIECE_NB]; - int castlingRightsMask[SQUARE_NB]; - Square castlingRookSquare[CASTLING_RIGHT_NB]; - Bitboard castlingPath[CASTLING_RIGHT_NB]; - Thread* thisThread; - StateInfo* st; - int gamePly; - Color sideToMove; - bool chess960; + public: + static void init(); + + Position() = default; + Position(const Position&) = delete; + Position& operator=(const Position&) = delete; + + // FEN string input/output + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const std::string& code, Color c, StateInfo* si); + std::string fen() const; + + // Position representation + Bitboard pieces(PieceType pt = ALL_PIECES) const; + template + Bitboard pieces(PieceType pt, PieceTypes... pts) const; + Bitboard pieces(Color c) const; + template + Bitboard pieces(Color c, PieceTypes... pts) const; + Piece piece_on(Square s) const; + Square ep_square() const; + bool empty(Square s) const; + template + int count(Color c) const; + template + int count() const; + template + Square square(Color c) const; + + // Castling + CastlingRights castling_rights(Color c) const; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; + + // Checking + Bitboard checkers() const; + Bitboard blockers_for_king(Color c) const; + Bitboard check_squares(PieceType pt) const; + Bitboard pinners(Color c) const; + + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Bitboard occupied) const; + void update_slider_blockers(Color c) const; + template + Bitboard attacks_by(Color c) const; + + // Properties of moves + bool legal(Move m) const; + bool pseudo_legal(const Move m) const; + bool capture(Move m) const; + bool capture_stage(Move m) const; + bool gives_check(Move m) const; + Piece moved_piece(Move m) const; + Piece captured_piece() const; + + // Doing and undoing moves + void do_move(Move m, StateInfo& newSt); + void do_move(Move m, StateInfo& newSt, bool givesCheck); + void undo_move(Move m); + void do_null_move(StateInfo& newSt); + void undo_null_move(); + + // Static Exchange Evaluation + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + + // Accessing hash keys + Key key() const; + Key key_after(Move m) const; + Key material_key() const; + + // Other properties of the position + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + Thread* this_thread() const; + bool is_draw(int ply) const; + bool has_game_cycle(int ply) const; + bool has_repeated() const; + int rule50_count() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; + + // Position consistency check, for debugging + bool pos_is_ok() const; + void flip(); + + // Used by NNUE + StateInfo* state() const; + + void put_piece(Piece pc, Square s); + void remove_piece(Square s); + + private: + // Initialization helpers (used while setting up a position) + void set_castling_right(Color c, Square rfrom); + void set_state() const; + void set_check_info() const; + + // Other helpers + void move_piece(Square from, Square to); + template + void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + template + Key adjust_key50(Key k) const; + + // Data members + Piece board[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + Thread* thisThread; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; }; std::ostream& operator<<(std::ostream& os, const Position& pos); -inline Color Position::side_to_move() const { - return sideToMove; -} +inline Color Position::side_to_move() const { return sideToMove; } inline Piece Position::piece_on(Square s) const { - assert(is_ok(s)); - return board[s]; + assert(is_ok(s)); + return board[s]; } -inline bool Position::empty(Square s) const { - return piece_on(s) == NO_PIECE; -} +inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } -inline Piece Position::moved_piece(Move m) const { - return piece_on(from_sq(m)); -} +inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); } -inline Bitboard Position::pieces(PieceType pt) const { - return byTypeBB[pt]; -} +inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } -template +template inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const { - return pieces(pt) | pieces(pts...); + return pieces(pt) | pieces(pts...); } -inline Bitboard Position::pieces(Color c) const { - return byColorBB[c]; -} +inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; } -template +template inline Bitboard Position::pieces(Color c, PieceTypes... pts) const { - return pieces(c) & pieces(pts...); + return pieces(c) & pieces(pts...); } -template inline int Position::count(Color c) const { - return pieceCount[make_piece(c, Pt)]; +template +inline int Position::count(Color c) const { + return pieceCount[make_piece(c, Pt)]; } -template inline int Position::count() const { - return count(WHITE) + count(BLACK); +template +inline int Position::count() const { + return count(WHITE) + count(BLACK); } -template inline Square Position::square(Color c) const { - assert(count(c) == 1); - return lsb(pieces(c, Pt)); +template +inline Square Position::square(Color c) const { + assert(count(c) == 1); + return lsb(pieces(c, Pt)); } -inline Square Position::ep_square() const { - return st->epSquare; -} +inline Square Position::ep_square() const { return st->epSquare; } -inline bool Position::can_castle(CastlingRights cr) const { - return st->castlingRights & cr; -} +inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; } inline CastlingRights Position::castling_rights(Color c) const { - return c & CastlingRights(st->castlingRights); + return c & CastlingRights(st->castlingRights); } inline bool Position::castling_impeded(CastlingRights cr) const { - assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return pieces() & castlingPath[cr]; + return pieces() & castlingPath[cr]; } inline Square Position::castling_rook_square(CastlingRights cr) const { - assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return castlingRookSquare[cr]; + return castlingRookSquare[cr]; } -inline Bitboard Position::attackers_to(Square s) const { - return attackers_to(s, pieces()); -} +inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); } template inline Bitboard Position::attacks_by(Color c) const { - if constexpr (Pt == PAWN) - return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) - : pawn_attacks_bb(pieces(BLACK, PAWN)); - else - { - Bitboard threats = 0; - Bitboard attackers = pieces(c, Pt); - while (attackers) - threats |= attacks_bb(pop_lsb(attackers), pieces()); - return threats; - } + if constexpr (Pt == PAWN) + return c == WHITE ? pawn_attacks_bb(pieces(WHITE, PAWN)) + : pawn_attacks_bb(pieces(BLACK, PAWN)); + else + { + Bitboard threats = 0; + Bitboard attackers = pieces(c, Pt); + while (attackers) + threats |= attacks_bb(pop_lsb(attackers), pieces()); + return threats; + } } -inline Bitboard Position::checkers() const { - return st->checkersBB; -} +inline Bitboard Position::checkers() const { return st->checkersBB; } -inline Bitboard Position::blockers_for_king(Color c) const { - return st->blockersForKing[c]; -} +inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; } -inline Bitboard Position::pinners(Color c) const { - return st->pinners[c]; -} +inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; } -inline Bitboard Position::check_squares(PieceType pt) const { - return st->checkSquares[pt]; -} +inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline Key Position::key() const { - return adjust_key50(st->key); -} +inline Key Position::key() const { return adjust_key50(st->key); } template -inline Key Position::adjust_key50(Key k) const -{ - return st->rule50 < 14 - AfterMove - ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); +inline Key Position::adjust_key50(Key k) const { + return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } -inline Key Position::material_key() const { - return st->materialKey; -} +inline Key Position::material_key() const { return st->materialKey; } -inline Value Position::non_pawn_material(Color c) const { - return st->nonPawnMaterial[c]; -} +inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } inline Value Position::non_pawn_material() const { - return non_pawn_material(WHITE) + non_pawn_material(BLACK); + return non_pawn_material(WHITE) + non_pawn_material(BLACK); } -inline int Position::game_ply() const { - return gamePly; -} +inline int Position::game_ply() const { return gamePly; } -inline int Position::rule50_count() const { - return st->rule50; -} +inline int Position::rule50_count() const { return st->rule50; } -inline bool Position::is_chess960() const { - return chess960; -} +inline bool Position::is_chess960() const { return chess960; } inline bool Position::capture(Move m) const { - assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) - || type_of(m) == EN_PASSANT; + assert(is_ok(m)); + return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; } // Returns true if a move is generated from the capture stage, having also // queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { - assert(is_ok(m)); - return capture(m) || promotion_type(m) == QUEEN; + assert(is_ok(m)); + return capture(m) || promotion_type(m) == QUEEN; } -inline Piece Position::captured_piece() const { - return st->capturedPiece; -} +inline Piece Position::captured_piece() const { return st->capturedPiece; } -inline Thread* Position::this_thread() const { - return thisThread; -} +inline Thread* Position::this_thread() const { return thisThread; } inline void Position::put_piece(Piece pc, Square s) { - board[s] = pc; - byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; - byColorBB[color_of(pc)] |= s; - pieceCount[pc]++; - pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; + board[s] = pc; + byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; + byColorBB[color_of(pc)] |= s; + pieceCount[pc]++; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; } inline void Position::remove_piece(Square s) { - Piece pc = board[s]; - byTypeBB[ALL_PIECES] ^= s; - byTypeBB[type_of(pc)] ^= s; - byColorBB[color_of(pc)] ^= s; - board[s] = NO_PIECE; - pieceCount[pc]--; - pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; + Piece pc = board[s]; + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[type_of(pc)] ^= s; + byColorBB[color_of(pc)] ^= s; + board[s] = NO_PIECE; + pieceCount[pc]--; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; } inline void Position::move_piece(Square from, Square to) { - Piece pc = board[from]; - Bitboard fromTo = from | to; - byTypeBB[ALL_PIECES] ^= fromTo; - byTypeBB[type_of(pc)] ^= fromTo; - byColorBB[color_of(pc)] ^= fromTo; - board[from] = NO_PIECE; - board[to] = pc; + Piece pc = board[from]; + Bitboard fromTo = from | to; + byTypeBB[ALL_PIECES] ^= fromTo; + byTypeBB[type_of(pc)] ^= fromTo; + byColorBB[color_of(pc)] ^= fromTo; + board[from] = NO_PIECE; + board[to] = pc; } -inline void Position::do_move(Move m, StateInfo& newSt) { - do_move(m, newSt, gives_check(m)); -} - -inline StateInfo* Position::state() const { +inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); } - return st; -} +inline StateInfo* Position::state() const { return st; } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef POSITION_H_INCLUDED +#endif // #ifndef POSITION_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index baf819687c5..43f0c8726e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -49,15 +49,15 @@ namespace Stockfish { namespace Search { - LimitsType Limits; +LimitsType Limits; } namespace Tablebases { - int Cardinality; - bool RootInTB; - bool UseRule50; - Depth ProbeDepth; +int Cardinality; +bool RootInTB; +bool UseRule50; +Depth ProbeDepth; } namespace TB = Tablebases; @@ -68,45 +68,46 @@ using namespace Search; namespace { - // Different node types, used as a template parameter - enum NodeType { NonPV, PV, Root }; +// Different node types, used as a template parameter +enum NodeType { + NonPV, + PV, + Root +}; - // Futility margin - Value futility_margin(Depth d, bool noTtCutNode, bool improving) { +// Futility margin +Value futility_margin(Depth d, bool noTtCutNode, bool improving) { return Value((126 - 42 * noTtCutNode) * (d - improving)); - } +} - // Reductions lookup table initialized at startup - int Reductions[MAX_MOVES]; // [depth or moveNumber] +// Reductions lookup table initialized at startup +int Reductions[MAX_MOVES]; // [depth or moveNumber] - Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { +Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 - + (!i && reductionScale > 791); - } - - constexpr int futility_move_count(bool improving, Depth depth) { - return improving ? (3 + depth * depth) - : (3 + depth * depth) / 2; - } - - // History and stats update bonus, based on depth - int stat_bonus(Depth d) { - return std::min(334 * d - 531, 1538); - } - - // Add a small random component to draw evaluations to avoid 3-fold blindness - Value value_draw(const Thread* thisThread) { + return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 + + (!i && reductionScale > 791); +} + +constexpr int futility_move_count(bool improving, Depth depth) { + return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; +} + +// History and stats update bonus, based on depth +int stat_bonus(Depth d) { return std::min(334 * d - 531, 1538); } + +// Add a small random component to draw evaluations to avoid 3-fold blindness +Value value_draw(const Thread* thisThread) { return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); - } - - // Skill structure is used to implement strength limit. If we have a UCI_Elo, - // we convert it to an appropriate skill level, anchored to the Stash engine. - // This method is based on a fit of the Elo results for games played between - // Stockfish at various skill levels and various versions of the Stash engine. - // Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately - // Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 - struct Skill { +} + +// Skill structure is used to implement strength limit. If we have a UCI_Elo, +// we convert it to an appropriate skill level, anchored to the Stash engine. +// This method is based on a fit of the Elo results for games played between +// Stockfish at various skill levels and various versions of the Stash engine. +// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately +// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 +struct Skill { Skill(int skill_level, int uci_elo) { if (uci_elo) { @@ -121,32 +122,41 @@ namespace { Move pick_best(size_t multiPV); double level; - Move best = MOVE_NONE; - }; - - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); - - template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); - - Value value_to_tt(Value v, int ply); - Value value_from_tt(Value v, int ply, int r50c); - void update_pv(Move* pv, Move move, const Move* childPv); - void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); - void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); - void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, - Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth); - - // perft() is our utility to verify move generation. All the leaf nodes up - // to the given depth are generated and counted, and the sum is returned. - template - uint64_t perft(Position& pos, Depth depth) { + Move best = MOVE_NONE; +}; + +template +Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + +template +Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); + +Value value_to_tt(Value v, int ply); +Value value_from_tt(Value v, int ply, int r50c); +void update_pv(Move* pv, Move move, const Move* childPv); +void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); +void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); +void update_all_stats(const Position& pos, + Stack* ss, + Move bestMove, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth); + +// perft() is our utility to verify move generation. All the leaf nodes up +// to the given depth are generated and counted, and the sum is returned. +template +uint64_t perft(Position& pos, Depth depth) { StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - uint64_t cnt, nodes = 0; + uint64_t cnt, nodes = 0; const bool leaf = (depth == 2); for (const auto& m : MoveList(pos)) @@ -164,17 +174,17 @@ namespace { sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; } return nodes; - } +} -} // namespace +} // namespace // Search::init() is called at startup to initialize various lookup tables void Search::init() { - for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); + for (int i = 1; i < MAX_MOVES; ++i) + Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); } @@ -182,12 +192,12 @@ void Search::init() { void Search::clear() { - Threads.main()->wait_for_search_finished(); + Threads.main()->wait_for_search_finished(); - Time.availableNodes = 0; - TT.clear(); - Threads.clear(); - Tablebases::init(Options["SyzygyPath"]); // Free mapped files + Time.availableNodes = 0; + TT.clear(); + Threads.clear(); + Tablebases::init(Options["SyzygyPath"]); // Free mapped files } @@ -196,75 +206,74 @@ void Search::clear() { void MainThread::search() { - if (Limits.perft) - { - nodes = perft(rootPos, Limits.perft); - sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; - return; - } - - Color us = rootPos.side_to_move(); - Time.init(Limits, us, rootPos.game_ply()); - TT.new_search(); - - Eval::NNUE::verify(); - - if (rootMoves.empty()) - { - rootMoves.emplace_back(MOVE_NONE); - sync_cout << "info depth 0 score " - << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) - << sync_endl; - } - else - { - Threads.start_searching(); // start non-main threads - Thread::search(); // main thread start searching - } - - // When we reach the maximum depth, we can arrive here without a raise of - // Threads.stop. However, if we are pondering or in an infinite search, - // the UCI protocol states that we shouldn't print the best move before the - // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here - // until the GUI sends one of those commands. - - while (!Threads.stop && (ponder || Limits.infinite)) - {} // Busy wait for a stop or a ponder reset - - // Stop the threads if not already stopped (also raise the stop if - // "ponderhit" just reset Threads.ponder). - Threads.stop = true; - - // Wait until all threads have finished - Threads.wait_for_search_finished(); - - // When playing in 'nodes as time' mode, subtract the searched nodes from - // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); - - Thread* bestThread = this; - Skill skill = Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); - - if ( int(Options["MultiPV"]) == 1 - && !Limits.depth - && !skill.enabled() - && rootMoves[0].pv[0] != MOVE_NONE) - bestThread = Threads.get_best_thread(); - - bestPreviousScore = bestThread->rootMoves[0].score; - bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; - - // Send again PV info if we have a new best thread - if (bestThread != this) - sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; - - sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); - - if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) - std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); - - std::cout << sync_endl; + if (Limits.perft) + { + nodes = perft(rootPos, Limits.perft); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return; + } + + Color us = rootPos.side_to_move(); + Time.init(Limits, us, rootPos.game_ply()); + TT.new_search(); + + Eval::NNUE::verify(); + + if (rootMoves.empty()) + { + rootMoves.emplace_back(MOVE_NONE); + sync_cout << "info depth 0 score " + << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; + } + else + { + Threads.start_searching(); // start non-main threads + Thread::search(); // main thread start searching + } + + // When we reach the maximum depth, we can arrive here without a raise of + // Threads.stop. However, if we are pondering or in an infinite search, + // the UCI protocol states that we shouldn't print the best move before the + // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here + // until the GUI sends one of those commands. + + while (!Threads.stop && (ponder || Limits.infinite)) + {} // Busy wait for a stop or a ponder reset + + // Stop the threads if not already stopped (also raise the stop if + // "ponderhit" just reset Threads.ponder). + Threads.stop = true; + + // Wait until all threads have finished + Threads.wait_for_search_finished(); + + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (Limits.npmsec) + Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + + Thread* bestThread = this; + Skill skill = + Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + + if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled() + && rootMoves[0].pv[0] != MOVE_NONE) + bestThread = Threads.get_best_thread(); + + bestPreviousScore = bestThread->rootMoves[0].score; + bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; + + // Send again PV info if we have a new best thread + if (bestThread != this) + sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; + + sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + + if (bestThread->rootMoves[0].pv.size() > 1 + || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + + std::cout << sync_endl; } @@ -274,266 +283,259 @@ void MainThread::search() { void Thread::search() { - // Allocate stack with extra size to allow access from (ss-7) to (ss+2): - // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), - // (ss+2) is needed for initialization of statScore and killers. - Stack stack[MAX_PLY+10], *ss = stack+7; - Move pv[MAX_PLY+1]; - Value alpha, beta, delta; - Move lastBestMove = MOVE_NONE; - Depth lastBestMoveDepth = 0; - MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int iterIdx = 0; - - std::memset(ss-7, 0, 10 * sizeof(Stack)); - for (int i = 7; i > 0; --i) - { - (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel - (ss-i)->staticEval = VALUE_NONE; - } - - for (int i = 0; i <= MAX_PLY + 2; ++i) - (ss+i)->ply = i; - - ss->pv = pv; - - bestValue = -VALUE_INFINITE; - - if (mainThread) - { - if (mainThread->bestPreviousScore == VALUE_INFINITE) - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = VALUE_ZERO; - else - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = mainThread->bestPreviousScore; - } - - size_t multiPV = size_t(Options["MultiPV"]); - Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); - - // When playing with strength handicap enable MultiPV search that we will - // use behind-the-scenes to retrieve a set of possible moves. - if (skill.enabled()) - multiPV = std::max(multiPV, size_t(4)); - - multiPV = std::min(multiPV, rootMoves.size()); - - int searchAgainCounter = 0; - - // Iterative deepening loop until requested to stop or the target depth is reached - while ( ++rootDepth < MAX_PLY - && !Threads.stop - && !(Limits.depth && mainThread && rootDepth > Limits.depth)) - { - // Age out PV variability metric - if (mainThread) - totBestMoveChanges /= 2; - - // Save the last iteration's scores before the first PV line is searched and - // all the move scores except the (new) PV are set to -VALUE_INFINITE. - for (RootMove& rm : rootMoves) - rm.previousScore = rm.score; - - size_t pvFirst = 0; - pvLast = 0; - - if (!Threads.increaseDepth) - searchAgainCounter++; - - // MultiPV loop. We perform a full root search for each PV line - for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) - { - if (pvIdx == pvLast) - { - pvFirst = pvLast; - for (pvLast++; pvLast < rootMoves.size(); pvLast++) - if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) - break; - } - - // Reset UCI info selDepth for each depth and each PV line - selDepth = 0; - - // Reset aspiration window starting size - Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 17470; - alpha = std::max(prev - delta,-VALUE_INFINITE); - beta = std::min(prev + delta, VALUE_INFINITE); - - // Adjust optimism based on root move's previousScore (~4 Elo) - int opt = 113 * prev / (std::abs(prev) + 109); - optimism[ us] = Value(opt); - optimism[~us] = -optimism[us]; - - // Start with a small aspiration window and, in the case of a fail - // high/low, re-search with a bigger window until we don't fail - // high/low anymore. - int failedHighCnt = 0; - while (true) - { - // Adjust the effective depth searched, but ensure at least one effective increment for every - // four searchAgain steps (see issue #2717). - Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); - - // Bring the best move to the front. It is critical that sorting - // is done with a stable algorithm because all the values but the - // first and eventually the new best one is set to -VALUE_INFINITE - // and we want to keep the same order for all the moves except the - // new PV that goes to the front. Note that in the case of MultiPV - // search the already searched PV lines are preserved. - std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); - - // If search has been stopped, we break immediately. Sorting is - // safe because RootMoves is still valid, although it refers to - // the previous iteration. - if (Threads.stop) - break; - - // When failing high/low give some update (without cluttering - // the UI) before a re-search. - if ( mainThread - && multiPV == 1 - && (bestValue <= alpha || bestValue >= beta) - && Time.elapsed() > 3000) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; - - // In case of failing low/high increase aspiration window and - // re-search, otherwise exit the loop. - if (bestValue <= alpha) - { - beta = (alpha + beta) / 2; - alpha = std::max(bestValue - delta, -VALUE_INFINITE); - - failedHighCnt = 0; - if (mainThread) - mainThread->stopOnPonderhit = false; - } - else if (bestValue >= beta) - { - beta = std::min(bestValue + delta, VALUE_INFINITE); - ++failedHighCnt; - } - else - break; - - delta += delta / 3; - - assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); - } - - // Sort the PV lines searched so far and update the GUI - std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); - - if ( mainThread - && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; - } - - if (!Threads.stop) - completedDepth = rootDepth; - - if (rootMoves[0].pv[0] != lastBestMove) - { - lastBestMove = rootMoves[0].pv[0]; - lastBestMoveDepth = rootDepth; - } - - // Have we found a "mate in x"? - if ( Limits.mate - && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * Limits.mate) - Threads.stop = true; - - if (!mainThread) - continue; - - // If the skill level is enabled and time is up, pick a sub-optimal best move - if (skill.enabled() && skill.time_to_pick(rootDepth)) - skill.pick_best(multiPV); - - // Use part of the gained time from a previous stable move for the current move - for (Thread* th : Threads) - { - totBestMoveChanges += th->bestMoveChanges; - th->bestMoveChanges = 0; - } - - // Do we have time for the next iteration? Can we stop searching now? - if ( Limits.use_time_management() - && !Threads.stop - && !mainThread->stopOnPonderhit) - { - double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 619.6; - fallingEval = std::clamp(fallingEval, 0.5, 1.5); - - // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); - double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); - - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; - - // Cap used time in case of a single legal move for a better viewer experience - if (rootMoves.size() == 1) - totalTime = std::min(500.0, totalTime); - - // Stop the search if we have exceeded the totalTime - if (Time.elapsed() > totalTime) - { - // If we are allowed to ponder do not stop the search now but - // keep pondering until the GUI sends "ponderhit" or "stop". - if (mainThread->ponder) - mainThread->stopOnPonderhit = true; - else - Threads.stop = true; - } - else if ( !mainThread->ponder - && Time.elapsed() > totalTime * 0.50) - Threads.increaseDepth = false; - else - Threads.increaseDepth = true; - } - - mainThread->iterValue[iterIdx] = bestValue; - iterIdx = (iterIdx + 1) & 3; - } - - if (!mainThread) - return; - - mainThread->previousTimeReduction = timeReduction; - - // If the skill level is enabled, swap the best PV line with the sub-optimal one - if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), - skill.best ? skill.best : skill.pick_best(multiPV))); + // Allocate stack with extra size to allow access from (ss-7) to (ss+2): + // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), + // (ss+2) is needed for initialization of statScore and killers. + Stack stack[MAX_PLY + 10], *ss = stack + 7; + Move pv[MAX_PLY + 1]; + Value alpha, beta, delta; + Move lastBestMove = MOVE_NONE; + Depth lastBestMoveDepth = 0; + MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int iterIdx = 0; + + std::memset(ss - 7, 0, 10 * sizeof(Stack)); + for (int i = 7; i > 0; --i) + { + (ss - i)->continuationHistory = + &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + (ss - i)->staticEval = VALUE_NONE; + } + + for (int i = 0; i <= MAX_PLY + 2; ++i) + (ss + i)->ply = i; + + ss->pv = pv; + + bestValue = -VALUE_INFINITE; + + if (mainThread) + { + if (mainThread->bestPreviousScore == VALUE_INFINITE) + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = VALUE_ZERO; + else + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = mainThread->bestPreviousScore; + } + + size_t multiPV = size_t(Options["MultiPV"]); + Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + + // When playing with strength handicap enable MultiPV search that we will + // use behind-the-scenes to retrieve a set of possible moves. + if (skill.enabled()) + multiPV = std::max(multiPV, size_t(4)); + + multiPV = std::min(multiPV, rootMoves.size()); + + int searchAgainCounter = 0; + + // Iterative deepening loop until requested to stop or the target depth is reached + while (++rootDepth < MAX_PLY && !Threads.stop + && !(Limits.depth && mainThread && rootDepth > Limits.depth)) + { + // Age out PV variability metric + if (mainThread) + totBestMoveChanges /= 2; + + // Save the last iteration's scores before the first PV line is searched and + // all the move scores except the (new) PV are set to -VALUE_INFINITE. + for (RootMove& rm : rootMoves) + rm.previousScore = rm.score; + + size_t pvFirst = 0; + pvLast = 0; + + if (!Threads.increaseDepth) + searchAgainCounter++; + + // MultiPV loop. We perform a full root search for each PV line + for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) + { + if (pvIdx == pvLast) + { + pvFirst = pvLast; + for (pvLast++; pvLast < rootMoves.size(); pvLast++) + if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) + break; + } + + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; + + // Reset aspiration window starting size + Value prev = rootMoves[pvIdx].averageScore; + delta = Value(10) + int(prev) * prev / 17470; + alpha = std::max(prev - delta, -VALUE_INFINITE); + beta = std::min(prev + delta, VALUE_INFINITE); + + // Adjust optimism based on root move's previousScore (~4 Elo) + int opt = 113 * prev / (std::abs(prev) + 109); + optimism[us] = Value(opt); + optimism[~us] = -optimism[us]; + + // Start with a small aspiration window and, in the case of a fail + // high/low, re-search with a bigger window until we don't fail + // high/low anymore. + int failedHighCnt = 0; + while (true) + { + // Adjust the effective depth searched, but ensure at least one effective increment for every + // four searchAgain steps (see issue #2717). + Depth adjustedDepth = + std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); + bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); + + // Bring the best move to the front. It is critical that sorting + // is done with a stable algorithm because all the values but the + // first and eventually the new best one is set to -VALUE_INFINITE + // and we want to keep the same order for all the moves except the + // new PV that goes to the front. Note that in the case of MultiPV + // search the already searched PV lines are preserved. + std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); + + // If search has been stopped, we break immediately. Sorting is + // safe because RootMoves is still valid, although it refers to + // the previous iteration. + if (Threads.stop) + break; + + // When failing high/low give some update (without cluttering + // the UI) before a re-search. + if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) + && Time.elapsed() > 3000) + sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + + // In case of failing low/high increase aspiration window and + // re-search, otherwise exit the loop. + if (bestValue <= alpha) + { + beta = (alpha + beta) / 2; + alpha = std::max(bestValue - delta, -VALUE_INFINITE); + + failedHighCnt = 0; + if (mainThread) + mainThread->stopOnPonderhit = false; + } + else if (bestValue >= beta) + { + beta = std::min(bestValue + delta, VALUE_INFINITE); + ++failedHighCnt; + } + else + break; + + delta += delta / 3; + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } + + // Sort the PV lines searched so far and update the GUI + std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); + + if (mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) + sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + } + + if (!Threads.stop) + completedDepth = rootDepth; + + if (rootMoves[0].pv[0] != lastBestMove) + { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; + } + + // Have we found a "mate in x"? + if (Limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * Limits.mate) + Threads.stop = true; + + if (!mainThread) + continue; + + // If the skill level is enabled and time is up, pick a sub-optimal best move + if (skill.enabled() && skill.time_to_pick(rootDepth)) + skill.pick_best(multiPV); + + // Use part of the gained time from a previous stable move for the current move + for (Thread* th : Threads) + { + totBestMoveChanges += th->bestMoveChanges; + th->bestMoveChanges = 0; + } + + // Do we have time for the next iteration? Can we stop searching now? + if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit) + { + double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) + / 619.6; + fallingEval = std::clamp(fallingEval, 0.5, 1.5); + + // If the bestMove is stable over several iterations, reduce time accordingly + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); + double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); + + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; + + // Cap used time in case of a single legal move for a better viewer experience + if (rootMoves.size() == 1) + totalTime = std::min(500.0, totalTime); + + // Stop the search if we have exceeded the totalTime + if (Time.elapsed() > totalTime) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (mainThread->ponder) + mainThread->stopOnPonderhit = true; + else + Threads.stop = true; + } + else if (!mainThread->ponder && Time.elapsed() > totalTime * 0.50) + Threads.increaseDepth = false; + else + Threads.increaseDepth = true; + } + + mainThread->iterValue[iterIdx] = bestValue; + iterIdx = (iterIdx + 1) & 3; + } + + if (!mainThread) + return; + + mainThread->previousTimeReduction = timeReduction; + + // If the skill level is enabled, swap the best PV line with the sub-optimal one + if (skill.enabled()) + std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(multiPV))); } namespace { - // search<>() is the main search function for both PV and non-PV nodes +// search<>() is the main search function for both PV and non-PV nodes - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { +template +Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { - constexpr bool PvNode = nodeType != NonPV; + constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch(pos, ss, alpha, beta); + return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. - if ( !rootNode - && alpha < VALUE_DRAW - && pos.has_game_cycle(ss->ply)) + if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); if (alpha >= beta) @@ -545,43 +547,41 @@ namespace { assert(0 < depth && depth < MAX_PLY); assert(!(PvNode && cutNode)); - Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[32]; + Move pv[MAX_PLY + 1], capturesSearched[32], quietsSearched[32]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; - Key posKey; - Move ttMove, move, excludedMove, bestMove; - Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture, singularQuietLMR; - bool capture, moveCountPruning, ttCapture; - Piece movedPiece; - int moveCount, captureCount, quietCount; + Key posKey; + Move ttMove, move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, ttValue, eval, maxValue, probCutBeta; + bool givesCheck, improving, priorCapture, singularQuietLMR; + bool capture, moveCountPruning, ttCapture; + Piece movedPiece; + int moveCount, captureCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); - moveCount = captureCount = quietCount = ss->moveCount = 0; - bestValue = -VALUE_INFINITE; - maxValue = VALUE_INFINITE; + moveCount = captureCount = quietCount = ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; // Check for the available remaining time if (thisThread == Threads.main()) static_cast(thisThread)->check_time(); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) - if ( PvNode - && thisThread->selDepth < ss->ply + 1) + if (PvNode && thisThread->selDepth < ss->ply + 1) thisThread->selDepth = ss->ply + 1; if (!rootNode) { // Step 2. Check for aborted search and immediate draw - if ( Threads.stop.load(std::memory_order_relaxed) - || pos.is_draw(ss->ply) + if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : value_draw(pos.this_thread()); @@ -593,7 +593,7 @@ namespace { // signs apply also in the opposite condition of being mated instead of giving // mate. In this case, return a fail-high score. alpha = std::max(mated_in(ss->ply), alpha); - beta = std::min(mate_in(ss->ply+1), beta); + beta = std::min(mate_in(ss->ply + 1), beta); if (alpha >= beta) return alpha; } @@ -602,20 +602,21 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss+1)->excludedMove = bestMove = MOVE_NONE; - (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; - (ss+2)->cutoffCnt = 0; - ss->doubleExtensions = (ss-1)->doubleExtensions; - Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - ss->statScore = 0; + (ss + 1)->excludedMove = bestMove = MOVE_NONE; + (ss + 2)->killers[0] = (ss + 2)->killers[1] = MOVE_NONE; + (ss + 2)->cutoffCnt = 0; + ss->doubleExtensions = (ss - 1)->doubleExtensions; + Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + ss->statScore = 0; // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; - posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); - ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] - : ss->ttHit ? tte->move() : MOVE_NONE; + posKey = pos.key(); + tte = TT.probe(posKey, ss->ttHit); + ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; + ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] + : ss->ttHit ? tte->move() + : MOVE_NONE; ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, @@ -624,10 +625,8 @@ namespace { ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); // At non-PV nodes we check for an early TT cutoff - if ( !PvNode - && !excludedMove - && tte->depth() > depth - && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit + if (!PvNode && !excludedMove && tte->depth() > depth + && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) @@ -640,10 +639,9 @@ namespace { update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) - if ( prevSq != SQ_NONE - && (ss-1)->moveCount <= 2 - && !priorCapture) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); + if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + -stat_bonus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) else if (!ttCapture) @@ -665,13 +663,12 @@ namespace { { int piecesCount = pos.count(); - if ( piecesCount <= TB::Cardinality - && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) - && pos.rule50_count() == 0 + if (piecesCount <= TB::Cardinality + && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) { TB::ProbeState err; - TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); // Force check of time on the next occasion if (thisThread == Threads.main()) @@ -684,19 +681,18 @@ namespace { int drawScore = TB::UseRule50 ? 1 : 0; // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score - value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 - : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 - : VALUE_DRAW + 2 * wdl * drawScore; + value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 + : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 + : VALUE_DRAW + 2 * wdl * drawScore; - Bound b = wdl < -drawScore ? BOUND_UPPER - : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER + : BOUND_EXACT; - if ( b == BOUND_EXACT - || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), - MOVE_NONE, VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), MOVE_NONE, VALUE_NONE); return value; } @@ -719,7 +715,7 @@ namespace { { // Skip early pruning when in check ss->staticEval = eval = VALUE_NONE; - improving = false; + improving = false; goto moves_loop; } else if (excludedMove) @@ -738,8 +734,7 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); // ttValue can be used as a better position evaluation (~7 Elo) - if ( ttValue != VALUE_NONE - && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) + if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttValue; } else @@ -750,12 +745,10 @@ namespace { } // Use static evaluation difference to improve quiet move ordering (~4 Elo) - if ( is_ok((ss-1)->currentMove) - && !(ss-1)->inCheck - && !priorCapture) + if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1812, 1812); - thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; + int bonus = std::clamp(-18 * int((ss - 1)->staticEval + ss->staticEval), -1812, 1812); + thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; } // Set up the improving flag, which is true if current static evaluation is @@ -763,15 +756,15 @@ namespace { // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval > (ss-2)->staticEval - : (ss-4)->staticEval != VALUE_NONE ? ss->staticEval > (ss-4)->staticEval - : true; + improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval + : (ss - 4)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 4)->staticEval + : true; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 492 - (257 - 200 * ((ss+1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 492 - (257 - 200 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -780,38 +773,31 @@ namespace { // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if ( !ss->ttPv - && depth < 9 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss-1)->statScore / 321 >= beta - && eval >= beta - && eval < 29462 // smaller than TB wins - && !( !ttCapture - && ttMove)) + if (!ss->ttPv && depth < 9 + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) + - (ss - 1)->statScore / 321 + >= beta + && eval >= beta && eval < 29462 // smaller than TB wins + && !(!ttCapture && ttMove)) return eval; // Step 9. Null move search with verification search (~35 Elo) - if ( !PvNode - && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 17257 - && eval >= beta - && eval >= ss->staticEval - && ss->staticEval >= beta - 24 * depth + 281 - && !excludedMove - && pos.non_pawn_material(us) - && ss->ply >= thisThread->nmpMinPly - && beta > VALUE_TB_LOSS_IN_MAX_PLY) + if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17257 && eval >= beta + && eval >= ss->staticEval && ss->staticEval >= beta - 24 * depth + 281 && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly + && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; - ss->currentMove = MOVE_NULL; + ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; pos.do_null_move(st); - Value nullValue = -search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); + Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, !cutNode); pos.undo_null_move(); @@ -821,13 +807,13 @@ namespace { if (thisThread->nmpMinPly || depth < 14) return nullValue; - assert(!thisThread->nmpMinPly); // Recursive verification is not allowed + assert(!thisThread->nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled // until ply exceeds nmpMinPly. - thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; + thisThread->nmpMinPly = ss->ply + 3 * (depth - R) / 4; - Value v = search(pos, ss, beta-1, beta, depth-R, false); + Value v = search(pos, ss, beta - 1, beta, depth - R, false); thisThread->nmpMinPly = 0; @@ -839,16 +825,13 @@ namespace { // Step 10. If the position doesn't have a ttMove, decrease depth by 2 // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). // Use qsearch if depth is equal or below zero (~9 Elo) - if ( PvNode - && !ttMove) + if (PvNode && !ttMove) depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); - if ( cutNode - && depth >= 8 - && !ttMove) + if (cutNode && depth >= 8 && !ttMove) depth -= 2; probCutBeta = beta + 168 - 70 * improving; @@ -856,16 +839,14 @@ namespace { // Step 11. ProbCut (~10 Elo) // 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. - if ( !PvNode - && depth > 3 - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - // If value from transposition table is lower than probCutBeta, don't attempt 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 - && !( tte->depth() >= depth - 3 - && ttValue != VALUE_NONE - && ttValue < probCutBeta)) + if ( + !PvNode && depth > 3 + && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + // If value from transposition table is lower than probCutBeta, don't attempt 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 + && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE); @@ -877,26 +858,27 @@ namespace { assert(pos.capture_stage(move)); ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [true] - [pos.moved_piece(move)] - [to_sq(move)]; + ss->continuationHistory = + &thisThread + ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][to_sq(move)]; pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds - value = -qsearch(pos, ss+1, -probCutBeta, -probCutBeta+1); + value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); // If the qsearch held, perform the regular search if (value >= probCutBeta) - value = -search(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode); + value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4, + !cutNode); pos.undo_move(move); if (value >= probCutBeta) { // Save ProbCut data into transposition table - tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); + tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, + move, ss->staticEval); return value - (probCutBeta - beta); } } @@ -904,447 +886,416 @@ namespace { Eval::NNUE::hint_common_parent_position(pos); } -moves_loop: // When in check, search starts here +moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) probCutBeta = beta + 416; - if ( ss->inCheck - && !PvNode - && ttCapture - && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 4 - && ttValue >= probCutBeta - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 4 && ttValue >= probCutBeta + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; - const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, - (ss-3)->continuationHistory, (ss-4)->continuationHistory, - nullptr , (ss-6)->continuationHistory }; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, + (ss - 2)->continuationHistory, + (ss - 3)->continuationHistory, + (ss - 4)->continuationHistory, + nullptr, + (ss - 6)->continuationHistory}; - Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; + Move countermove = + prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &captureHistory, - contHist, - countermove, - ss->killers); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, + countermove, ss->killers); - value = bestValue; + value = bestValue; moveCountPruning = singularQuietLMR = false; // Indicate PvNodes that will probably fail low if the node was searched // at a depth equal to or greater than the current depth, and the result // of this search was a fail low. - bool likelyFailLow = PvNode - && ttMove - && (tte->bound() & BOUND_UPPER) - && tte->depth() >= depth; + bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) && tte->depth() >= depth; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) { - assert(is_ok(move)); - - if (move == excludedMove) - continue; - - // Check for legality - if (!pos.legal(move)) - continue; - - // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. In MultiPV mode we also skip PV moves that have been already - // searched and those of lower "TB rank" if we are in a TB root position. - if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, - thisThread->rootMoves.begin() + thisThread->pvLast, move)) - continue; - - ss->moveCount = ++moveCount; - - if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) - sync_cout << "info depth " << depth - << " currmove " << UCI::move(move, pos.is_chess960()) - << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; - if (PvNode) - (ss+1)->pv = nullptr; - - extension = 0; - capture = pos.capture_stage(move); - movedPiece = pos.moved_piece(move); - givesCheck = pos.gives_check(move); - - // Calculate new depth for this move - newDepth = depth - 1; - - Value delta = beta - alpha; - - Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); - - // Step 14. Pruning at shallow depth (~120 Elo). - // Depth conditions are important for mate finding. - if ( !rootNode - && pos.non_pawn_material(us) - && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) - { - // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - if (!moveCountPruning) - moveCountPruning = moveCount >= futility_move_count(improving, depth); - - // Reduced depth of the next LMR search - int lmrDepth = newDepth - r; - - if ( capture - || givesCheck) - { - // Futility pruning for captures (~2 Elo) - if ( !givesCheck - && lmrDepth < 7 - && !ss->inCheck - && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha) - continue; - - // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-185) * depth)) - continue; - } - else - { - int history = (*contHist[0])[movedPiece][to_sq(move)] + assert(is_ok(move)); + + if (move == excludedMove) + continue; + + // Check for legality + if (!pos.legal(move)) + continue; + + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. In MultiPV mode we also skip PV moves that have been already + // searched and those of lower "TB rank" if we are in a TB root position. + if (rootNode + && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, + thisThread->rootMoves.begin() + thisThread->pvLast, move)) + continue; + + ss->moveCount = ++moveCount; + + if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) + sync_cout << "info depth " << depth << " currmove " + << UCI::move(move, pos.is_chess960()) << " currmovenumber " + << moveCount + thisThread->pvIdx << sync_endl; + if (PvNode) + (ss + 1)->pv = nullptr; + + extension = 0; + capture = pos.capture_stage(move); + movedPiece = pos.moved_piece(move); + givesCheck = pos.gives_check(move); + + // Calculate new depth for this move + newDepth = depth - 1; + + Value delta = beta - alpha; + + Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); + + // Step 14. Pruning at shallow depth (~120 Elo). + // Depth conditions are important for mate finding. + if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) + if (!moveCountPruning) + moveCountPruning = moveCount >= futility_move_count(improving, depth); + + // Reduced depth of the next LMR search + int lmrDepth = newDepth - r; + + if (capture || givesCheck) + { + // Futility pruning for captures (~2 Elo) + if (!givesCheck && lmrDepth < 7 && !ss->inCheck + && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] + + captureHistory[movedPiece][to_sq(move)] + [type_of(pos.piece_on(to_sq(move)))] + / 7 + < alpha) + continue; + + // SEE based pruning for captures and checks (~11 Elo) + if (!pos.see_ge(move, Value(-185) * depth)) + continue; + } + else + { + int history = (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)]; - // Continuation history based pruning (~2 Elo) - if ( lmrDepth < 6 - && history < -3232 * depth) - continue; - - history += 2 * thisThread->mainHistory[us][from_to(move)]; - - lmrDepth += history / 5793; - lmrDepth = std::max(lmrDepth, -2); - - // Futility pruning: parent node (~13 Elo) - if ( !ss->inCheck - && lmrDepth < 13 - && ss->staticEval + 115 + 122 * lmrDepth <= alpha) - continue; - - lmrDepth = std::max(lmrDepth, 0); - - // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) - continue; - } - } - - // Step 15. Extensions (~100 Elo) - // We take care to not overdo to avoid search getting stuck. - if (ss->ply < thisThread->rootDepth * 2) - { - // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the result - // is lower than ttValue minus a margin, then we will extend the ttMove. Note - // that 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 this type of time controls. - if ( !rootNode - && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) - && move == ttMove - && !excludedMove // Avoid recursive singular search - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY - && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 3) - { - Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; - Depth singularDepth = (depth - 1) / 2; - - ss->excludedMove = move; - value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = MOVE_NONE; - - if (value < singularBeta) - { - extension = 1; - singularQuietLMR = !ttCapture; - - // Avoid search explosion by limiting the number of double extensions - if ( !PvNode - && value < singularBeta - 18 - && ss->doubleExtensions <= 11) - { - extension = 2; - depth += depth < 15; - } - } - - // Multi-cut pruning - // Our ttMove is assumed to fail high, and now we failed high also on a - // reduced search without the ttMove. So we assume this expected cut-node - // is not singular, that multiple moves fail high, and we can prune the - // whole subtree by returning a softbound. - else if (singularBeta >= beta) - return singularBeta; - - // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) - else if (ttValue >= beta) - extension = -2 - !PvNode; - - // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) - else if (cutNode) - extension = depth < 19 ? -2 : -1; - - // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) - else if (ttValue <= value) - extension = -1; - } - - // Check extensions (~1 Elo) - else if ( givesCheck - && depth > 9) - extension = 1; - - // Quiet ttMove extensions (~1 Elo) - else if ( PvNode - && move == ttMove - && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) - extension = 1; - } - - // Add extension to new depth - newDepth += extension; - ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2); - - // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); - - // Update the current move (this must be done after singular extension search) - ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [movedPiece] - [to_sq(move)]; - - // Step 16. Make the move - pos.do_move(move, st, givesCheck); - - // Decrease reduction if position is or has been on the PV (~4 Elo) - if ( ss->ttPv - && !likelyFailLow) - r -= cutNode && tte->depth() >= depth ? 3 : 2; - - // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss-1)->moveCount > 7) - r--; - - // Increase reduction for cut nodes (~3 Elo) - if (cutNode) - r += 2; - - // Increase reduction if ttMove is a capture (~3 Elo) - if (ttCapture) - r++; - - // Decrease reduction for PvNodes (~2 Elo) - if (PvNode) - r--; - - // Decrease reduction if ttMove has been singularly extended (~1 Elo) - if (singularQuietLMR) - r--; - - // Increase reduction on repetition (~1 Elo) - if ( move == (ss-4)->currentMove - && pos.has_repeated()) - r += 2; - - // Increase reduction if next ply has a lot of fail high (~5 Elo) - if ((ss+1)->cutoffCnt > 3) - r++; - - // Decrease reduction for first generated move (ttMove) - else if (move == ttMove) - r--; - - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - - 3848; - - // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); - - // Step 17. Late moves reduction / extension (LMR, ~117 Elo) - // We use various heuristics for the sons of a node after the first son has - // been searched. In general, we would like to reduce them, but there are many - // cases where we extend a son if it has good chances to be "interesting". - if ( depth >= 2 - && moveCount > 1 + (PvNode && ss->ply <= 1) - && ( !ss->ttPv - || !capture - || (cutNode && (ss-1)->moveCount > 1))) - { - // In general we want to cap the LMR depth search at newDepth, but when - // reduction is negative, we allow this move a limited search extension - // beyond the first move depth. This may lead to hidden double extensions. - Depth d = std::clamp(newDepth - r, 1, newDepth + 1); - - value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); - - // Do a full-depth search when reduced LMR search fails high - if ( value > alpha - && d < newDepth) - { - // 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 + 51 + 10 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; - const bool doShallowerSearch = value < bestValue + newDepth; - - ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; - - newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; - - if (newDepth > d) - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); - - int bonus = value <= alpha ? -stat_bonus(newDepth) - : value >= beta ? stat_bonus(newDepth) - : 0; - - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); - } - } - - // Step 18. Full-depth search when LMR is skipped - else if (!PvNode || moveCount > 1) - { - // Increase reduction for cut nodes and not ttMove (~1 Elo) - if ( !ttMove - && cutNode) - r += 2; - - // Note that if expected reduction is high, we reduce search depth by 1 here - value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 3), !cutNode); - } - - // For PV nodes only, do a full PV search on the first move or after a fail high, - // otherwise let the parent node fail low with value <= alpha and try another move. - if ( PvNode - && (moveCount == 1 || value > alpha)) - { - (ss+1)->pv = pv; - (ss+1)->pv[0] = MOVE_NONE; - - value = -search(pos, ss+1, -beta, -alpha, newDepth, false); - } - - // Step 19. Undo move - pos.undo_move(move); - - assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); - - // Step 20. Check for a new best move - // Finished searching the move. If a stop occurred, the return value of - // the search cannot be trusted, and we return immediately without - // updating best move, PV and TT. - if (Threads.stop.load(std::memory_order_relaxed)) - return VALUE_ZERO; - - if (rootNode) - { - RootMove& rm = *std::find(thisThread->rootMoves.begin(), - thisThread->rootMoves.end(), move); - - rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; - - // PV move or new best move? - if (moveCount == 1 || value > alpha) - { - rm.score = rm.uciScore = value; - rm.selDepth = thisThread->selDepth; - rm.scoreLowerbound = rm.scoreUpperbound = false; - - if (value >= beta) - { - rm.scoreLowerbound = true; - rm.uciScore = beta; - } - else if (value <= alpha) - { - rm.scoreUpperbound = true; - rm.uciScore = alpha; - } - - rm.pv.resize(1); - - assert((ss+1)->pv); - - for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) - rm.pv.push_back(*m); - - // We record how often the best move has been changed in each iteration. - // This information is used for time management. In MultiPV mode, - // we must take care to only do this for the first PV line. - if ( moveCount > 1 - && !thisThread->pvIdx) - ++thisThread->bestMoveChanges; - } - else - // All other moves but the PV, are set to the lowest value: this - // is not a problem when sorting because the sort is stable and the - // move position in the list is preserved - just the PV is pushed up. - rm.score = -VALUE_INFINITE; - } - - if (value > bestValue) - { - bestValue = value; - - if (value > alpha) - { - bestMove = move; - - if (PvNode && !rootNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); - - if (value >= beta) - { - ss->cutoffCnt += 1 + !ttMove; - assert(value >= beta); // Fail high - break; - } - else - { - // Reduce other moves if we have found at least one score improvement (~2 Elo) - if ( depth > 2 - && depth < 12 - && beta < 13828 - && value > -11369) - depth -= 2; - - assert(depth > 0); - alpha = value; // Update alpha! Always alpha < beta - } - } - } - - // If the move is worse than some previously searched move, - // remember it, to update its stats later. - if (move != bestMove && moveCount <= 32) - { - if (capture) - capturesSearched[captureCount++] = move; - - else - quietsSearched[quietCount++] = move; - } + // Continuation history based pruning (~2 Elo) + if (lmrDepth < 6 && history < -3232 * depth) + continue; + + history += 2 * thisThread->mainHistory[us][from_to(move)]; + + lmrDepth += history / 5793; + lmrDepth = std::max(lmrDepth, -2); + + // Futility pruning: parent node (~13 Elo) + if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 115 + 122 * lmrDepth <= alpha) + continue; + + lmrDepth = std::max(lmrDepth, 0); + + // Prune moves with negative SEE (~4 Elo) + if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) + continue; + } + } + + // Step 15. Extensions (~100 Elo) + // We take care to not overdo to avoid search getting stuck. + if (ss->ply < thisThread->rootDepth * 2) + { + // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the result + // is lower than ttValue minus a margin, then we will extend the ttMove. Note + // that 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 this type of time controls. + if (!rootNode + && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) + && move == ttMove && !excludedMove // Avoid recursive singular search + && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3) + { + Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; + Depth singularDepth = (depth - 1) / 2; + + ss->excludedMove = move; + value = + search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = MOVE_NONE; + + if (value < singularBeta) + { + extension = 1; + singularQuietLMR = !ttCapture; + + // Avoid search explosion by limiting the number of double extensions + if (!PvNode && value < singularBeta - 18 && ss->doubleExtensions <= 11) + { + extension = 2; + depth += depth < 15; + } + } + + // Multi-cut pruning + // Our ttMove is assumed to fail high, and now we failed high also on a + // reduced search without the ttMove. So we assume this expected cut-node + // is not singular, that multiple moves fail high, and we can prune the + // whole subtree by returning a softbound. + else if (singularBeta >= beta) + return singularBeta; + + // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) + else if (ttValue >= beta) + extension = -2 - !PvNode; + + // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) + else if (cutNode) + extension = depth < 19 ? -2 : -1; + + // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) + else if (ttValue <= value) + extension = -1; + } + + // Check extensions (~1 Elo) + else if (givesCheck && depth > 9) + extension = 1; + + // Quiet ttMove extensions (~1 Elo) + else if (PvNode && move == ttMove && move == ss->killers[0] + && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) + extension = 1; + } + + // Add extension to new depth + newDepth += extension; + ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension == 2); + + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); + + // Update the current move (this must be done after singular extension search) + ss->currentMove = move; + ss->continuationHistory = + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][to_sq(move)]; + + // Step 16. Make the move + pos.do_move(move, st, givesCheck); + + // Decrease reduction if position is or has been on the PV (~4 Elo) + if (ss->ttPv && !likelyFailLow) + r -= cutNode && tte->depth() >= depth ? 3 : 2; + + // Decrease reduction if opponent's move count is high (~1 Elo) + if ((ss - 1)->moveCount > 7) + r--; + + // Increase reduction for cut nodes (~3 Elo) + if (cutNode) + r += 2; + + // Increase reduction if ttMove is a capture (~3 Elo) + if (ttCapture) + r++; + + // Decrease reduction for PvNodes (~2 Elo) + if (PvNode) + r--; + + // Decrease reduction if ttMove has been singularly extended (~1 Elo) + if (singularQuietLMR) + r--; + + // Increase reduction on repetition (~1 Elo) + if (move == (ss - 4)->currentMove && pos.has_repeated()) + r += 2; + + // Increase reduction if next ply has a lot of fail high (~5 Elo) + if ((ss + 1)->cutoffCnt > 3) + r++; + + // Decrease reduction for first generated move (ttMove) + else if (move == ttMove) + r--; + + ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + + (*contHist[0])[movedPiece][to_sq(move)] + + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] - 3848; + + // Decrease/increase reduction for moves with a good/bad history (~25 Elo) + r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); + + // Step 17. Late moves reduction / extension (LMR, ~117 Elo) + // We use various heuristics for the sons of a node after the first son has + // been searched. In general, we would like to reduce them, but there are many + // cases where we extend a son if it has good chances to be "interesting". + if (depth >= 2 && moveCount > 1 + (PvNode && ss->ply <= 1) + && (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1))) + { + // In general we want to cap the LMR depth search at newDepth, but when + // reduction is negative, we allow this move a limited search extension + // beyond the first move depth. This may lead to hidden double extensions. + Depth d = std::clamp(newDepth - r, 1, newDepth + 1); + + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + + // Do a full-depth search when reduced LMR search fails high + if (value > alpha && d < newDepth) + { + // 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 + 51 + 10 * (newDepth - d)); + const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; + const bool doShallowerSearch = value < bestValue + newDepth; + + ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; + + newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; + + if (newDepth > d) + value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); + + int bonus = value <= alpha ? -stat_bonus(newDepth) + : value >= beta ? stat_bonus(newDepth) + : 0; + + update_continuation_histories(ss, movedPiece, to_sq(move), bonus); + } + } + + // Step 18. Full-depth search when LMR is skipped + else if (!PvNode || moveCount > 1) + { + // Increase reduction for cut nodes and not ttMove (~1 Elo) + if (!ttMove && cutNode) + r += 2; + + // Note that if expected reduction is high, we reduce search depth by 1 here + value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3), !cutNode); + } + + // For PV nodes only, do a full PV search on the first move or after a fail high, + // otherwise let the parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || value > alpha)) + { + (ss + 1)->pv = pv; + (ss + 1)->pv[0] = MOVE_NONE; + + value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); + } + + // Step 19. Undo move + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Step 20. Check for a new best move + // Finished searching the move. If a stop occurred, the return value of + // the search cannot be trusted, and we return immediately without + // updating best move, PV and TT. + if (Threads.stop.load(std::memory_order_relaxed)) + return VALUE_ZERO; + + if (rootNode) + { + RootMove& rm = + *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); + + rm.averageScore = + rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; + + // PV move or new best move? + if (moveCount == 1 || value > alpha) + { + rm.score = rm.uciScore = value; + rm.selDepth = thisThread->selDepth; + rm.scoreLowerbound = rm.scoreUpperbound = false; + + if (value >= beta) + { + rm.scoreLowerbound = true; + rm.uciScore = beta; + } + else if (value <= alpha) + { + rm.scoreUpperbound = true; + rm.uciScore = alpha; + } + + rm.pv.resize(1); + + assert((ss + 1)->pv); + + for (Move* m = (ss + 1)->pv; *m != MOVE_NONE; ++m) + rm.pv.push_back(*m); + + // We record how often the best move has been changed in each iteration. + // This information is used for time management. In MultiPV mode, + // we must take care to only do this for the first PV line. + if (moveCount > 1 && !thisThread->pvIdx) + ++thisThread->bestMoveChanges; + } + else + // All other moves but the PV, are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the + // move position in the list is preserved - just the PV is pushed up. + rm.score = -VALUE_INFINITE; + } + + if (value > bestValue) + { + bestValue = value; + + if (value > alpha) + { + bestMove = move; + + if (PvNode && !rootNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); + + if (value >= beta) + { + ss->cutoffCnt += 1 + !ttMove; + assert(value >= beta); // Fail high + break; + } + else + { + // Reduce other moves if we have found at least one score improvement (~2 Elo) + if (depth > 2 && depth < 12 && beta < 13828 && value > -11369) + depth -= 2; + + assert(depth > 0); + alpha = value; // Update alpha! Always alpha < beta + } + } + } + + // If the move is worse than some previously searched move, + // remember it, to update its stats later. + if (move != bestMove && moveCount <= 32) + { + if (capture) + capturesSearched[captureCount++] = move; + + else + quietsSearched[quietCount++] = move; + } } // Step 21. Check for mate and stalemate @@ -1355,21 +1306,22 @@ namespace { assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); if (!moveCount) - bestValue = excludedMove ? alpha : - ss->inCheck ? mated_in(ss->ply) - : VALUE_DRAW; + bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; // If there is a move that produces search value greater than alpha we update the stats of searched moves else if (bestMove) - update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, - quietsSearched, quietCount, capturesSearched, captureCount, depth); + update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, + capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) + ((ss-1)->moveCount > 11); - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus / 2; + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) + + ((ss - 1)->moveCount > 11); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] + << stat_bonus(depth) * bonus / 2; } if (PvNode) @@ -1378,26 +1330,27 @@ namespace { // 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) if (bestValue <= alpha) - ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); + ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); // Write gathered information in transposition table if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, - bestValue >= beta ? BOUND_LOWER : - PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + bestValue >= beta ? BOUND_LOWER + : PvNode && bestMove ? BOUND_EXACT + : BOUND_UPPER, depth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; - } +} - // qsearch() is the quiescence search function, which is called by the main search - // function with zero depth, or recursively with further decreasing depth per call. - // (~155 Elo) - template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { +// qsearch() is the quiescence search function, which is called by the main search +// function with zero depth, or recursively with further decreasing depth per call. +// (~155 Elo) +template +Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { static_assert(nodeType != Root); constexpr bool PvNode = nodeType == PV; @@ -1408,42 +1361,40 @@ namespace { // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. - if ( alpha < VALUE_DRAW - && pos.has_game_cycle(ss->ply)) + if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); if (alpha >= beta) return alpha; } - Move pv[MAX_PLY+1]; + Move pv[MAX_PLY + 1]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; - Key posKey; - Move ttMove, move, bestMove; - Depth ttDepth; - Value bestValue, value, ttValue, futilityValue, futilityBase; - bool pvHit, givesCheck, capture; - int moveCount; - Color us = pos.side_to_move(); + Key posKey; + Move ttMove, move, bestMove; + Depth ttDepth; + Value bestValue, value, ttValue, futilityValue, futilityBase; + bool pvHit, givesCheck, capture; + int moveCount; + Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) { - (ss+1)->pv = pv; - ss->pv[0] = MOVE_NONE; + (ss + 1)->pv = pv; + ss->pv[0] = MOVE_NONE; } Thread* thisThread = pos.this_thread(); - bestMove = MOVE_NONE; - ss->inCheck = pos.checkers(); - moveCount = 0; + bestMove = MOVE_NONE; + ss->inCheck = pos.checkers(); + moveCount = 0; // Step 2. Check for an immediate draw or maximum ply reached - if ( pos.is_draw(ss->ply) - || ss->ply >= MAX_PLY) + if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1451,20 +1402,18 @@ namespace { // Decide whether or not to include checks: this fixes also the type of // TT entry depth that we are going to use. Note that in qsearch we use // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. - ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS - : DEPTH_QS_NO_CHECKS; + ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Step 3. Transposition table lookup - posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); + posKey = pos.key(); + tte = TT.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = ss->ttHit ? tte->move() : MOVE_NONE; - pvHit = ss->ttHit && tte->is_pv(); + ttMove = ss->ttHit ? tte->move() : MOVE_NONE; + pvHit = ss->ttHit && tte->is_pv(); // At non-PV nodes we check for an early TT cutoff - if ( !PvNode - && tte->depth() >= ttDepth - && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit + if (!PvNode && tte->depth() >= ttDepth + && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; @@ -1480,21 +1429,21 @@ namespace { ss->staticEval = bestValue = evaluate(pos); // ttValue can be used as a better position evaluation (~13 Elo) - if ( ttValue != VALUE_NONE + if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttValue; } else // In case of null move search use previous static eval with a different sign - ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) - : -(ss-1)->staticEval; + ss->staticEval = bestValue = + (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, - DEPTH_NONE, MOVE_NONE, ss->staticEval); + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, + MOVE_NONE, ss->staticEval); return bestValue; } @@ -1505,17 +1454,16 @@ namespace { futilityBase = std::min(ss->staticEval, bestValue) + 200; } - const PieceToHistory* contHist[] = {(ss-1)->continuationHistory, (ss-2)->continuationHistory}; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, + (ss - 2)->continuationHistory}; // Initialize a MovePicker object for the current position, and prepare // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, - &thisThread->captureHistory, - contHist, - prevSq); + Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + contHist, prevSq); int quietCheckEvasions = 0; @@ -1530,19 +1478,16 @@ namespace { continue; givesCheck = pos.gives_check(move); - capture = pos.capture_stage(move); + capture = pos.capture_stage(move); moveCount++; // Step 6. Pruning - if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY - && pos.non_pawn_material(us)) + if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if ( !givesCheck - && to_sq(move) != prevSq - && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY - && type_of(move) != PROMOTION) + if (!givesCheck && to_sq(move) != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + && type_of(move) != PROMOTION) { if (moveCount > 2) continue; @@ -1559,8 +1504,7 @@ namespace { // If static eval is much lower than alpha and move is not winning material // we can prune this move. - if ( futilityBase <= alpha - && !pos.see_ge(move, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1582,8 +1526,7 @@ namespace { break; // Continuation history based pruning (~3 Elo) - if ( !capture - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 + if (!capture && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) continue; @@ -1597,16 +1540,15 @@ namespace { // Update the current move ss->currentMove = move; - ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] - [capture] - [pos.moved_piece(move)] - [to_sq(move)]; + ss->continuationHistory = + &thisThread + ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][to_sq(move)]; quietCheckEvasions += !capture && ss->inCheck; // Step 7. Make and search the move pos.do_move(move, st, givesCheck); - value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); + value = -qsearch(pos, ss + 1, -beta, -alpha, depth - 1); pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1620,13 +1562,13 @@ namespace { { bestMove = move; - if (PvNode) // Update pv even in fail-high case - update_pv(ss->pv, move, (ss+1)->pv); + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss + 1)->pv); - if (value < beta) // Update alpha here! + if (value < beta) // Update alpha here! alpha = value; else - break; // Fail high + break; // Fail high } } } @@ -1638,40 +1580,38 @@ namespace { { assert(!MoveList(pos).size()); - return mated_in(ss->ply); // Plies to mate from the root + return mated_in(ss->ply); // Plies to mate from the root } // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, - bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, - ttDepth, bestMove, ss->staticEval); + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; - } +} - // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" - // to "plies to mate from the current position". Standard scores are unchanged. - // The function is called before storing a value in the transposition table. +// value_to_tt() adjusts a mate or TB score from "plies to mate from the root" +// to "plies to mate from the current position". Standard scores are unchanged. +// The function is called before storing a value in the transposition table. - Value value_to_tt(Value v, int ply) { +Value value_to_tt(Value v, int ply) { assert(v != VALUE_NONE); - return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply - : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; - } + return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; +} - // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score - // from the transposition table (which refers to the plies to mate/be mated from - // current position) to "plies to mate/be mated (TB win/loss) from the root". - // However, to avoid potentially false mate scores related to the 50 moves rule - // and the graph history interaction problem, we return an optimal TB score instead. +// value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score +// from the transposition table (which refers to the plies to mate/be mated from +// current position) to "plies to mate/be mated (TB win/loss) from the root". +// However, to avoid potentially false mate scores related to the 50 moves rule +// and the graph history interaction problem, we return an optimal TB score instead. - Value value_from_tt(Value v, int ply, int r50c) { +Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) return VALUE_NONE; @@ -1679,50 +1619,59 @@ namespace { if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better { if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) - return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score + return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score return v - ply; } - if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse + if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse { if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) - return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score + return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score return v + ply; } return v; - } +} - // update_pv() adds current move and appends child pv[] +// update_pv() adds current move and appends child pv[] - void update_pv(Move* pv, Move move, const Move* childPv) { +void update_pv(Move* pv, Move move, const Move* childPv) { - for (*pv++ = move; childPv && *childPv != MOVE_NONE; ) + for (*pv++ = move; childPv && *childPv != MOVE_NONE;) *pv++ = *childPv++; *pv = MOVE_NONE; - } +} - // update_all_stats() updates stats at the end of search() when a bestMove is found +// update_all_stats() updates stats at the end of search() when a bestMove is found - void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, - Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) { +void update_all_stats(const Position& pos, + Stack* ss, + Move bestMove, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth) { - Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); + Color us = pos.side_to_move(); + Thread* thisThread = pos.this_thread(); CapturePieceToHistory& captureHistory = thisThread->captureHistory; - Piece moved_piece = pos.moved_piece(bestMove); - PieceType captured; + Piece moved_piece = pos.moved_piece(bestMove); + PieceType captured; int quietMoveBonus = stat_bonus(depth + 1); if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus - : stat_bonus(depth); // smaller bonus + int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus + : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bestMoveBonus); @@ -1731,7 +1680,8 @@ namespace { for (int i = 0; i < quietCount; ++i) { thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bestMoveBonus); + update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), + to_sq(quietsSearched[i]), -bestMoveBonus); } } else @@ -1743,40 +1693,41 @@ namespace { // Extra penalty for a quiet early move that was not a TT move or // main killer move in previous ply when it gets refuted. - if ( prevSq != SQ_NONE - && ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) + if (prevSq != SQ_NONE + && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit + || ((ss - 1)->currentMove == (ss - 1)->killers[0])) && !pos.captured_piece()) - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); - captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); + captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveBonus; } - } +} - // update_continuation_histories() updates histories of the move pairs formed - // by moves at ply -1, -2, -4, and -6 with current move. +// update_continuation_histories() updates histories of the move pairs formed +// by moves at ply -1, -2, -4, and -6 with current move. - void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { +void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { for (int i : {1, 2, 3, 4, 6}) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; - if (is_ok((ss-i)->currentMove)) - (*(ss-i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); + if (is_ok((ss - i)->currentMove)) + (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } - } +} - // update_quiet_stats() updates move sorting heuristics +// update_quiet_stats() updates move sorting heuristics - void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { +void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { // Update killers if (ss->killers[0] != move) @@ -1785,31 +1736,31 @@ namespace { ss->killers[0] = move; } - Color us = pos.side_to_move(); + Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); thisThread->mainHistory[us][from_to(move)] << bonus; update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); // Update countermove history - if (is_ok((ss-1)->currentMove)) + if (is_ok((ss - 1)->currentMove)) { - Square prevSq = to_sq((ss-1)->currentMove); + Square prevSq = to_sq((ss - 1)->currentMove); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } - } +} - // When playing with strength handicap, choose the best move among a set of RootMoves - // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. +// When playing with strength handicap, choose the best move among a set of RootMoves +// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - Move Skill::pick_best(size_t multiPV) { +Move Skill::pick_best(size_t multiPV) { const RootMoves& rootMoves = Threads.main()->rootMoves; - static PRNG rng(now()); // PRNG sequence should be non-deterministic + static PRNG rng(now()); // PRNG sequence should be non-deterministic // RootMoves are already sorted by score in descending order - Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); - int maxScore = -VALUE_INFINITE; + Value topScore = rootMoves[0].score; + int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); + int maxScore = -VALUE_INFINITE; double weakness = 120 - 2 * level; // Choose best move. For each move score we add two terms, both dependent on @@ -1818,20 +1769,21 @@ namespace { for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = int(( weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) / 128); + int push = int((weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) + / 128); if (rootMoves[i].score + push >= maxScore) { maxScore = rootMoves[i].score + push; - best = rootMoves[i].pv[0]; + best = rootMoves[i].pv[0]; } } return best; - } +} -} // namespace +} // namespace // MainThread::check_time() is used to print debug info and, more importantly, @@ -1839,31 +1791,31 @@ namespace { void MainThread::check_time() { - if (--callsCnt > 0) - return; + if (--callsCnt > 0) + return; - // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; + // When using nodes, ensure checking rate is not lower than 0.1% of nodes + callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; - static TimePoint lastInfoTime = now(); + static TimePoint lastInfoTime = now(); - TimePoint elapsed = Time.elapsed(); - TimePoint tick = Limits.startTime + elapsed; + TimePoint elapsed = Time.elapsed(); + TimePoint tick = Limits.startTime + elapsed; - if (tick - lastInfoTime >= 1000) - { - lastInfoTime = tick; - dbg_print(); - } + if (tick - lastInfoTime >= 1000) + { + lastInfoTime = tick; + dbg_print(); + } - // We should not stop pondering until told so by the GUI - if (ponder) - return; + // We should not stop pondering until told so by the GUI + if (ponder) + return; - if ( (Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) - || (Limits.movetime && elapsed >= Limits.movetime) - || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) - Threads.stop = true; + if ((Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) + || (Limits.movetime && elapsed >= Limits.movetime) + || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) + Threads.stop = true; } @@ -1872,57 +1824,53 @@ void MainThread::check_time() { string UCI::pv(const Position& pos, Depth depth) { - std::stringstream ss; - TimePoint elapsed = Time.elapsed() + 1; - const RootMoves& rootMoves = pos.this_thread()->rootMoves; - size_t pvIdx = pos.this_thread()->pvIdx; - size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); - uint64_t nodesSearched = Threads.nodes_searched(); - uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + std::stringstream ss; + TimePoint elapsed = Time.elapsed() + 1; + const RootMoves& rootMoves = pos.this_thread()->rootMoves; + size_t pvIdx = pos.this_thread()->pvIdx; + size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); + uint64_t nodesSearched = Threads.nodes_searched(); + uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = rootMoves[i].score != -VALUE_INFINITE; + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; - if (depth == 1 && !updated && i > 0) - continue; + if (depth == 1 && !updated && i > 0) + continue; - Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; - if (v == -VALUE_INFINITE) - v = VALUE_ZERO; + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; - bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; - v = tb ? rootMoves[i].tbScore : v; + bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; + v = tb ? rootMoves[i].tbScore : v; - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; - ss << "info" - << " depth " << d - << " seldepth " << rootMoves[i].selDepth - << " multipv " << i + 1 - << " score " << UCI::value(v); + ss << "info" + << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 + << " score " << UCI::value(v); - if (Options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos.game_ply()); + if (Options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound ? " lowerbound" : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound + ? " lowerbound" + : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); - ss << " nodes " << nodesSearched - << " nps " << nodesSearched * 1000 / elapsed - << " hashfull " << TT.hashfull() - << " tbhits " << tbHits - << " time " << elapsed - << " pv"; + ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / elapsed + << " hashfull " << TT.hashfull() << " tbhits " << tbHits << " time " << elapsed << " pv"; - for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); - } + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(m, pos.is_chess960()); + } - return ss.str(); + return ss.str(); } @@ -1948,7 +1896,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { if (ttHit) { - Move m = tte->move(); // Local copy to be SMP safe + Move m = tte->move(); // Local copy to be SMP safe if (MoveList(pos).contains(m)) pv.push_back(m); } @@ -1959,10 +1907,10 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { - RootInTB = false; - UseRule50 = bool(Options["Syzygy50MoveRule"]); - ProbeDepth = int(Options["SyzygyProbeDepth"]); - Cardinality = int(Options["SyzygyProbeLimit"]); + RootInTB = false; + UseRule50 = bool(Options["Syzygy50MoveRule"]); + ProbeDepth = int(Options["SyzygyProbeDepth"]); + Cardinality = int(Options["SyzygyProbeLimit"]); bool dtz_available = true; // Tables with fewer pieces than SyzygyProbeLimit are searched with @@ -1970,7 +1918,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { if (Cardinality > MaxCardinality) { Cardinality = MaxCardinality; - ProbeDepth = 0; + ProbeDepth = 0; } if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) @@ -1982,7 +1930,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; - RootInTB = root_probe_wdl(pos, rootMoves); + RootInTB = root_probe_wdl(pos, rootMoves); } } @@ -1990,7 +1938,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { { // Sort moves according to TB rank std::stable_sort(rootMoves.begin(), rootMoves.end(), - [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } ); + [](const RootMove& a, const RootMove& b) { return a.tbRank > b.tbRank; }); // Probe during search only if DTZ is not available and we are winning if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) @@ -2004,4 +1952,4 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { } } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/search.h b/src/search.h index c434ba752d6..37cd5e5a686 100644 --- a/src/search.h +++ b/src/search.h @@ -38,20 +38,20 @@ namespace Search { // its own array of Stack objects, indexed by the current ply. struct Stack { - Move* pv; - PieceToHistory* continuationHistory; - int ply; - Move currentMove; - Move excludedMove; - Move killers[2]; - Value staticEval; - int statScore; - int moveCount; - bool inCheck; - bool ttPv; - bool ttHit; - int doubleExtensions; - int cutoffCnt; + Move* pv; + PieceToHistory* continuationHistory; + int ply; + Move currentMove; + Move excludedMove; + Move killers[2]; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; + int doubleExtensions; + int cutoffCnt; }; @@ -61,24 +61,24 @@ struct Stack { struct RootMove { - explicit RootMove(Move m) : pv(1, m) {} - bool extract_ponder_from_tt(Position& pos); - bool operator==(const Move& m) const { return pv[0] == m; } - bool operator<(const RootMove& m) const { // Sort in descending order - return m.score != score ? m.score < score - : m.previousScore < previousScore; - } - - Value score = -VALUE_INFINITE; - Value previousScore = -VALUE_INFINITE; - Value averageScore = -VALUE_INFINITE; - Value uciScore = -VALUE_INFINITE; - bool scoreLowerbound = false; - bool scoreUpperbound = false; - int selDepth = 0; - int tbRank = 0; - Value tbScore; - std::vector pv; + explicit RootMove(Move m) : + pv(1, m) {} + bool extract_ponder_from_tt(Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + bool operator<(const RootMove& m) const { // Sort in descending order + return m.score != score ? m.score < score : m.previousScore < previousScore; + } + + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + Value averageScore = -VALUE_INFINITE; + Value uciScore = -VALUE_INFINITE; + bool scoreLowerbound = false; + bool scoreUpperbound = false; + int selDepth = 0; + int tbRank = 0; + Value tbScore; + std::vector pv; }; using RootMoves = std::vector; @@ -89,20 +89,18 @@ using RootMoves = std::vector; struct LimitsType { - LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC - time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); - movestogo = depth = mate = perft = infinite = 0; - nodes = 0; - } + LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC + time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); + movestogo = depth = mate = perft = infinite = 0; + nodes = 0; + } - bool use_time_management() const { - return time[WHITE] || time[BLACK]; - } + bool use_time_management() const { return time[WHITE] || time[BLACK]; } - std::vector searchmoves; - TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; - int movestogo, depth, mate, perft, infinite; - int64_t nodes; + std::vector searchmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + int64_t nodes; }; extern LimitsType Limits; @@ -110,8 +108,8 @@ extern LimitsType Limits; void init(); void clear(); -} // namespace Search +} // namespace Search -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef SEARCH_H_INCLUDED +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 4114db605d2..c8e60ab6c02 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -45,15 +45,15 @@ #include "../uci.h" #ifndef _WIN32 -#include -#include -#include + #include + #include + #include #else -#define WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX -# define NOMINMAX // Disable macros min() and max() -#endif -#include + #define WIN32_LEAN_AND_MEAN + #ifndef NOMINMAX + #define NOMINMAX // Disable macros min() and max() + #endif + #include #endif using namespace Stockfish::Tablebases; @@ -64,60 +64,69 @@ namespace Stockfish { namespace { -constexpr int TBPIECES = 7; // Max number of supported pieces -constexpr int MAX_DTZ = 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. +constexpr int TBPIECES = 7; // Max number of supported pieces +constexpr int MAX_DTZ = + 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. -enum { BigEndian, LittleEndian }; -enum TBType { WDL, DTZ }; // Used as template parameter +enum { + BigEndian, + LittleEndian +}; +enum TBType { + WDL, + DTZ +}; // Used as template parameter // Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables -enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 }; +enum TBFlag { + STM = 1, + Mapped = 2, + WinPlies = 4, + LossPlies = 8, + Wide = 16, + SingleValue = 128 +}; inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } -inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } constexpr std::string_view PieceToChar = " PNBRQK pnbrqk"; int MapPawns[SQUARE_NB]; int MapB1H1H7[SQUARE_NB]; int MapA1D1D4[SQUARE_NB]; -int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] -int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements -int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] -int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] // Comparison function to sort leading pawns in ascending MapPawns[] order bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } -int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } - -constexpr Value WDL_to_value[] = { - -VALUE_MATE + MAX_PLY + 1, - VALUE_DRAW - 2, - VALUE_DRAW, - VALUE_DRAW + 2, - VALUE_MATE - MAX_PLY - 1 -}; +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } + +constexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW, + VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1}; template -inline void swap_endian(T& x) -{ +inline void swap_endian(T& x) { static_assert(std::is_unsigned_v, "Argument of swap_endian not unsigned"); - uint8_t tmp, *c = (uint8_t*)&x; + uint8_t tmp, *c = (uint8_t*) &x; for (int i = 0; i < Half; ++i) tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; } -template<> inline void swap_endian(uint8_t&) {} +template<> +inline void swap_endian(uint8_t&) {} -template T number(void* addr) -{ +template +T number(void* addr) { T v; - if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) + if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare) std::memcpy(&v, addr, sizeof(T)); else - v = *((T*)addr); + v = *((T*) addr); if (LE != IsLittleEndian) swap_endian(v); @@ -128,14 +137,16 @@ template T number(void* addr) // like captures and pawn moves but we can easily recover the correct dtz of the // previous move if we know the position's WDL score. int dtz_before_zeroing(WDLScore wdl) { - return wdl == WDLWin ? 1 : - wdl == WDLCursedWin ? 101 : - wdl == WDLBlessedLoss ? -101 : - wdl == WDLLoss ? -1 : 0; + return wdl == WDLWin ? 1 + : wdl == WDLCursedWin ? 101 + : wdl == WDLBlessedLoss ? -101 + : wdl == WDLLoss ? -1 + : 0; } // Return the sign of a number (-1, 0, 1) -template int sign_of(T val) { +template +int sign_of(T val) { return (T(0) < val) - (val < T(0)); } @@ -147,18 +158,22 @@ struct SparseEntry { static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); -using Sym = uint16_t; // Huffman symbol +using Sym = uint16_t; // Huffman symbol struct LR { - enum Side { Left, Right }; + enum Side { + Left, + Right + }; - uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 - // bits is the right-hand symbol. If the symbol has length 1, - // then the left-hand symbol is the stored value. + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If the symbol has length 1, + // then the left-hand symbol is the stored value. template Sym get() { - return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] : - S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1)); + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] + : S == Right ? (lr[2] << 4) | (lr[1] >> 4) + : (assert(false), Sym(-1)); } }; @@ -173,11 +188,11 @@ static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); // class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are // memory mapped for best performance. Files are mapped at first access: at init // time only existence of the file is checked. -class TBFile : public std::ifstream { +class TBFile: public std::ifstream { std::string fname; -public: + public: // Look for and open the file among the Paths directories where the .rtbw // and .rtbz files can be found. Multiple directories are separated by ";" // on Windows and by ":" on Unix-based operating systems. @@ -194,7 +209,7 @@ class TBFile : public std::ifstream { constexpr char SepChar = ';'; #endif std::stringstream ss(Paths); - std::string path; + std::string path; while (std::getline(ss, path, SepChar)) { @@ -208,11 +223,11 @@ class TBFile : public std::ifstream { // Memory map the file and check it. uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) { if (is_open()) - close(); // Need to re-open to get native file descriptor + close(); // Need to re-open to get native file descriptor #ifndef _WIN32 struct stat statbuf; - int fd = ::open(fname.c_str(), O_RDONLY); + int fd = ::open(fname.c_str(), O_RDONLY); if (fd == -1) return *baseAddress = nullptr, nullptr; @@ -225,11 +240,11 @@ class TBFile : public std::ifstream { exit(EXIT_FAILURE); } - *mapping = statbuf.st_size; + *mapping = statbuf.st_size; *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); -#if defined(MADV_RANDOM) + #if defined(MADV_RANDOM) madvise(*baseAddress, statbuf.st_size, MADV_RANDOM); -#endif + #endif ::close(fd); if (*baseAddress == MAP_FAILED) @@ -240,7 +255,7 @@ class TBFile : public std::ifstream { #else // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, - OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); + OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); if (fd == INVALID_HANDLE_VALUE) return *baseAddress = nullptr, nullptr; @@ -263,7 +278,7 @@ class TBFile : public std::ifstream { exit(EXIT_FAILURE); } - *mapping = uint64_t(mmap); + *mapping = uint64_t(mmap); *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); if (!*baseAddress) @@ -273,10 +288,9 @@ class TBFile : public std::ifstream { exit(EXIT_FAILURE); } #endif - uint8_t* data = (uint8_t*)*baseAddress; + uint8_t* data = (uint8_t*) *baseAddress; - constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 }, - { 0x71, 0xE8, 0x23, 0x5D } }; + constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}}; if (memcmp(data, Magics[type == WDL], 4)) { @@ -285,7 +299,7 @@ class TBFile : public std::ifstream { return *baseAddress = nullptr, nullptr; } - return data + 4; // Skip Magics's header + return data + 4; // Skip Magics's header } static void unmap(void* baseAddress, uint64_t mapping) { @@ -294,7 +308,7 @@ class TBFile : public std::ifstream { munmap(baseAddress, mapping); #else UnmapViewOfFile(baseAddress); - CloseHandle((HANDLE)mapping); + CloseHandle((HANDLE) mapping); #endif } }; @@ -305,25 +319,27 @@ std::string TBFile::Paths; // There are 8, 4, or 2 PairsData records for each TBTable, according to the type // of table and if positions have pawns or not. It is populated at first access. struct PairsData { - uint8_t flags; // Table flags, see enum TBFlag - uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols - uint8_t minSymLen; // Minimum length in bits of the Huffman symbols - uint32_t blocksNum; // Number of blocks in the TB file - size_t sizeofBlock; // Block size in bytes - size_t span; // About every span values there is a SparseIndex[] entry - Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value - LR* btree; // btree[sym] stores the left and right symbols that expand sym - uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 - uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum - SparseEntry* sparseIndex; // Partial indices into blockLength[] - size_t sparseIndexSize; // Size of SparseIndex[] table - uint8_t* data; // Start of Huffman compressed data - std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l - std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 - Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups - uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces - int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) - uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) + uint8_t flags; // Table flags, see enum TBFlag + uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols + uint8_t minSymLen; // Minimum length in bits of the Huffman symbols + uint32_t blocksNum; // Number of blocks in the TB file + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector + base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector + symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1) + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) }; // struct TBTable contains indexing information to access the corresponding TBFile. @@ -337,22 +353,22 @@ struct TBTable { static constexpr int Sides = Type == WDL ? 2 : 1; std::atomic_bool ready; - void* baseAddress; - uint8_t* map; - uint64_t mapping; - Key key; - Key key2; - int pieceCount; - bool hasPawns; - bool hasUniquePieces; - uint8_t pawnCount[2]; // [Lead color / other color] - PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] - - PairsData* get(int stm, int f) { - return &items[stm % Sides][hasPawns ? f : 0]; - } - - TBTable() : ready(false), baseAddress(nullptr) {} + void* baseAddress; + uint8_t* map; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; + uint8_t pawnCount[2]; // [Lead color / other color] + PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] + + PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; } + + TBTable() : + ready(false), + baseAddress(nullptr) {} explicit TBTable(const std::string& code); explicit TBTable(const TBTable& wdl); @@ -363,26 +379,26 @@ struct TBTable { }; template<> -TBTable::TBTable(const std::string& code) : TBTable() { +TBTable::TBTable(const std::string& code) : + TBTable() { StateInfo st; - Position pos; + Position pos; - key = pos.set(code, WHITE, &st).material_key(); + key = pos.set(code, WHITE, &st).material_key(); pieceCount = pos.count(); - hasPawns = pos.pieces(PAWN); + hasPawns = pos.pieces(PAWN); hasUniquePieces = false; - for (Color c : { WHITE, BLACK }) + for (Color c : {WHITE, BLACK}) for (PieceType pt = PAWN; pt < KING; ++pt) if (popcount(pos.pieces(c, pt)) == 1) hasUniquePieces = true; // Set the leading color. In case both sides have pawns the leading color // is the side with fewer pawns because this leads to better compression. - bool c = !pos.count(BLACK) - || ( pos.count(WHITE) - && pos.count(BLACK) >= pos.count(WHITE)); + bool c = !pos.count(BLACK) + || (pos.count(WHITE) && pos.count(BLACK) >= pos.count(WHITE)); pawnCount[0] = pos.count(c ? WHITE : BLACK); pawnCount[1] = pos.count(c ? BLACK : WHITE); @@ -391,16 +407,17 @@ TBTable::TBTable(const std::string& code) : TBTable() { } template<> -TBTable::TBTable(const TBTable& wdl) : TBTable() { +TBTable::TBTable(const TBTable& wdl) : + TBTable() { // Use the corresponding WDL table to avoid recalculating all from scratch - key = wdl.key; - key2 = wdl.key2; - pieceCount = wdl.pieceCount; - hasPawns = wdl.hasPawns; + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; hasUniquePieces = wdl.hasUniquePieces; - pawnCount[0] = wdl.pawnCount[0]; - pawnCount[1] = wdl.pawnCount[1]; + pawnCount[0] = wdl.pawnCount[0]; + pawnCount[1] = wdl.pawnCount[1]; } // class TBTables creates and keeps ownership of the TBTable objects, one for @@ -408,19 +425,18 @@ TBTable::TBTable(const TBTable& wdl) : TBTable() { // at init time, accessed at probe time. class TBTables { - struct Entry - { - Key key; + struct Entry { + Key key; TBTable* wdl; TBTable* dtz; - template + template TBTable* get() const { - return (TBTable*)(Type == WDL ? (void*)wdl : (void*)dtz); + return (TBTable*) (Type == WDL ? (void*) wdl : (void*) dtz); } }; - static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb + static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket Entry hashTable[Size + Overflow]; @@ -430,12 +446,14 @@ class TBTables { void insert(Key key, TBTable* wdl, TBTable* dtz) { uint32_t homeBucket = uint32_t(key) & (Size - 1); - Entry entry{ key, wdl, dtz }; + Entry entry{key, wdl, dtz}; // Ensure last element is empty to avoid overflow when looking up - for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) { + for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) + { Key otherKey = hashTable[bucket].key; - if (otherKey == key || !hashTable[bucket].get()) { + if (otherKey == key || !hashTable[bucket].get()) + { hashTable[bucket] = entry; return; } @@ -443,9 +461,10 @@ class TBTables { // Robin Hood hashing: If we've probed for longer than this element, // insert here and search for a new spot for the other element instead. uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1); - if (otherHomeBucket > homeBucket) { + if (otherHomeBucket > homeBucket) + { std::swap(entry, hashTable[bucket]); - key = otherKey; + key = otherKey; homeBucket = otherHomeBucket; } } @@ -453,10 +472,11 @@ class TBTables { exit(EXIT_FAILURE); } -public: + public: template TBTable* get(Key key) { - for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)]; ; ++entry) { + for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry) + { if (entry->key == key || !entry->get()) return entry->get(); } @@ -468,7 +488,7 @@ class TBTables { dtzTable.clear(); } size_t size() const { return wdlTable.size(); } - void add(const std::vector& pieces); + void add(const std::vector& pieces); }; TBTables TBTables; @@ -482,9 +502,9 @@ void TBTables::add(const std::vector& pieces) { for (PieceType pt : pieces) code += PieceToChar[pt]; - TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK - if (!file.is_open()) // Only WDL file is checked + if (!file.is_open()) // Only WDL file is checked return; file.close(); @@ -495,7 +515,7 @@ void TBTables::add(const std::vector& pieces) { dtzTable.emplace_back(wdlTable.back()); // Insert into the hash keys for both colors: KRvK with KR white and black - insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back()); insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); } @@ -538,8 +558,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { uint32_t k = uint32_t(idx / d->span); // Then we read the corresponding SparseIndex[] entry - uint32_t block = number(&d->sparseIndex[k].block); - int offset = number(&d->sparseIndex[k].offset); + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); // Now compute the difference idx - I(k). From the definition of k, we know that // @@ -560,18 +580,19 @@ int decompress_pairs(PairsData* d, uint64_t idx) { offset -= d->blockLength[block++] + 1; // Finally, we find the start address of our block of canonical Huffman symbols - uint32_t* ptr = (uint32_t*)(d->data + (uint64_t(block) * d->sizeofBlock)); + uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock)); // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one // is at the beginning of this 64-bit sequence. - uint64_t buf64 = number(ptr); ptr += 2; + uint64_t buf64 = number(ptr); + ptr += 2; int buf64Size = 64; Sym sym; while (true) { - int len = 0; // This is the symbol length - d->min_sym_len + int len = 0; // This is the symbol length - d->min_sym_len // Now get the symbol length. For any symbol s64 of length l right-padded // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we @@ -594,11 +615,12 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // ...otherwise update the offset and continue to iterate offset -= d->symlen[sym] + 1; - len += d->minSymLen; // Get the real length - buf64 <<= len; // Consume the just processed symbol + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol buf64Size -= len; - if (buf64Size <= 32) { // Refill the buffer + if (buf64Size <= 32) + { // Refill the buffer buf64Size += 32; buf64 |= uint64_t(number(ptr++)) << (64 - buf64Size); } @@ -618,7 +640,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // the left side because in Recursive Pairing child symbols are adjacent. if (offset < d->symlen[left] + 1) sym = left; - else { + else + { offset -= d->symlen[left] + 1; sym = d->btree[sym].get(); } @@ -632,8 +655,7 @@ bool check_dtz_stm(TBTable*, int, File) { return true; } bool check_dtz_stm(TBTable* entry, int stm, File f) { auto flags = entry->get(stm, f)->flags; - return (flags & TBFlag::STM) == stm - || ((entry->key == entry->key2) && !entry->hasPawns); + return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns); } // DTZ scores are sorted by frequency of occurrence and then assigned the @@ -644,25 +666,25 @@ WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(v int map_score(TBTable* entry, File f, int value, WDLScore wdl) { - constexpr int WDLMap[] = { 1, 3, 0, 2, 0 }; + constexpr int WDLMap[] = {1, 3, 0, 2, 0}; auto flags = entry->get(0, f)->flags; - uint8_t* map = entry->map; + uint8_t* map = entry->map; uint16_t* idx = entry->get(0, f)->map_idx; - if (flags & TBFlag::Mapped) { + if (flags & TBFlag::Mapped) + { if (flags & TBFlag::Wide) - value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value]; + value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value]; else value = map[idx[WDLMap[wdl + 2]] + value]; } // DTZ tables store distance to zero in number of moves or plies. We // want to return plies, so we have to convert to plies when needed. - if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) - || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) - || wdl == WDLCursedWin - || wdl == WDLBlessedLoss) + if ((wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) value *= 2; return value + 1; @@ -677,13 +699,13 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { template Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { - Square squares[TBPIECES]; - Piece pieces[TBPIECES]; - uint64_t idx; - int next = 0, size = 0, leadPawnsCnt = 0; + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; PairsData* d; - Bitboard b, leadPawns = 0; - File tbFile = FILE_A; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. // If both sides have the same pieces keys are equal. In this case TB tables @@ -704,7 +726,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // For pawns, TB files store 4 separate tables according if leading pawn is on // file a, b, c or d after reordering. The leading pawn is the one with maximum // MapPawns[] value, that is the one most toward the edges and with lowest rank. - if (entry->hasPawns) { + if (entry->hasPawns) + { // In all the 4 tables, pawns are at the beginning of the piece sequence and // their color is the reference one. So we just pick the first one. @@ -733,9 +756,10 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Now we are ready to get all the position pieces (but the lead pawns) and // directly map them to the correct color and square. b = pos.pieces() ^ leadPawns; - do { - Square s = pop_lsb(b); - squares[size] = s ^ flipSquares; + do + { + Square s = pop_lsb(b); + squares[size] = s ^ flipSquares; pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); } while (b); @@ -762,7 +786,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Encode leading pawns starting with the one with minimum MapPawns[] and // proceeding in ascending order. - if (entry->hasPawns) { + if (entry->hasPawns) + { idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp); @@ -770,7 +795,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu for (int i = 1; i < leadPawnsCnt; ++i) idx += Binomial[i][MapPawns[squares[i]]]; - goto encode_remaining; // With pawns we have finished special treatments + goto encode_remaining; // With pawns we have finished special treatments } // In positions without pawns, we further flip the squares to ensure leading @@ -781,11 +806,12 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // Look for the first piece of the leading group not on the A1-D4 diagonal // and ensure it is mapped below the diagonal. - for (int i = 0; i < d->groupLen[0]; ++i) { + for (int i = 0; i < d->groupLen[0]; ++i) + { if (!off_A1H8(squares[i])) continue; - if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 for (int j = i; j < size; ++j) squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); break; @@ -818,41 +844,36 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // // In case we have at least 3 unique pieces (including kings) we encode them // together. - if (entry->hasUniquePieces) { + if (entry->hasUniquePieces) + { - int adjust1 = squares[1] > squares[0]; + int adjust1 = squares[1] > squares[0]; int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 // triangle to 0...5. There are 63 squares for second piece and and 62 // (mapped to 0...61) for the third. if (off_A1H8(squares[0])) - idx = ( MapA1D1D4[squares[0]] * 63 - + (squares[1] - adjust1)) * 62 - + squares[2] - adjust2; + idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2; // First piece is on a1-h8 diagonal, second below: map this occurrence to // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. else if (off_A1H8(squares[1])) - idx = ( 6 * 63 + rank_of(squares[0]) * 28 - + MapB1H1H7[squares[1]]) * 62 - + squares[2] - adjust2; + idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2] + - adjust2; // First two pieces are on a1-h8 diagonal, third below else if (off_A1H8(squares[2])) - idx = 6 * 63 * 62 + 4 * 28 * 62 - + rank_of(squares[0]) * 7 * 28 - + (rank_of(squares[1]) - adjust1) * 28 - + MapB1H1H7[squares[2]]; + idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]]; // All 3 pieces on the diagonal a1-h8 else - idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 - + rank_of(squares[0]) * 7 * 6 - + (rank_of(squares[1]) - adjust1) * 6 - + (rank_of(squares[2]) - adjust2); - } else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2); + } + else // We don't have at least 3 unique pieces, like in KRRvKBB, just map // the kings. idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; @@ -873,7 +894,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu // groups (similar to what was done earlier for leading group pieces). for (int i = 0; i < d->groupLen[next]; ++i) { - auto f = [&](Square s) { return groupSq[i] > s; }; + auto f = [&](Square s) { return groupSq[i] > s; }; auto adjust = std::count_if(squares, groupSq, f); n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; } @@ -911,7 +932,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) { else d->groupLen[++n] = 1; - d->groupLen[++n] = 0; // Zero-terminated + d->groupLen[++n] = 0; // Zero-terminated // The sequence in pieces[] defines the groups, but not the order in which // they are encoded. If the pieces in a group g can be combined on the board @@ -924,24 +945,23 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // pawns/pieces -> remaining pawns -> remaining pieces. In particular the // first group is at order[0] position and the remaining pawns, when present, // are at order[1] position. - bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides - int next = pp ? 2 : 1; - int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); - uint64_t idx = 1; + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) - if (k == order[0]) // Leading pawns or pieces + if (k == order[0]) // Leading pawns or pieces { d->groupIdx[0] = idx; - idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] - : e.hasUniquePieces ? 31332 : 462; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462; } - else if (k == order[1]) // Remaining pawns + else if (k == order[1]) // Remaining pawns { d->groupIdx[1] = idx; idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; } - else // Remaining pieces + else // Remaining pieces { d->groupIdx[next] = idx; idx *= Binomial[d->groupLen[next]][freeSquares]; @@ -956,8 +976,8 @@ void set_groups(T& e, PairsData* d, int order[], File f) { // symbol until reaching the leaves that represent the symbol value. uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { - visited[s] = true; // We can set it now because tree is acyclic - Sym sr = d->btree[s].get(); + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); if (sr == 0xFFF) return 0; @@ -977,10 +997,11 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { d->flags = *data++; - if (d->flags & TBFlag::SingleValue) { + if (d->flags & TBFlag::SingleValue) + { d->blocksNum = d->blockLengthSize = 0; - d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init - d->minSymLen = *data++; // Here we store the single value + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value return data; } @@ -988,16 +1009,17 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // element stores the biggest index that is the tb size. uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; - d->sizeofBlock = 1ULL << *data++; - d->span = 1ULL << *data++; - d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up - auto padding = number(data++); - d->blocksNum = number(data); data += sizeof(uint32_t); - d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] - // does not point out of range. + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up + auto padding = number(data++); + d->blocksNum = number(data); + data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. d->maxSymLen = *data++; d->minSymLen = *data++; - d->lowestSym = (Sym*)data; + d->lowestSym = (Sym*) data; d->base64.resize(d->maxSymLen - d->minSymLen + 1); // See https://en.wikipedia.org/wiki/Huffman_coding @@ -1012,11 +1034,13 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // avoiding unsigned overflow warnings. int base64_size = static_cast(d->base64.size()); - for (int i = base64_size - 2; i >= 0; --i) { + for (int i = base64_size - 2; i >= 0; --i) + { d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - - number(&d->lowestSym[i + 1])) / 2; + - number(&d->lowestSym[i + 1])) + / 2; - assert(d->base64[i] * 2 >= d->base64[i+1]); + assert(d->base64[i] * 2 >= d->base64[i + 1]); } // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more @@ -1024,11 +1048,12 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. for (int i = 0; i < base64_size; ++i) - d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits data += base64_size * sizeof(Sym); - d->symlen.resize(number(data)); data += sizeof(uint16_t); - d->btree = (LR*)data; + d->symlen.resize(number(data)); + data += sizeof(uint16_t); + d->btree = (LR*) data; // The compression scheme used is "Recursive Pairing", that replaces the most // frequent adjacent pair of symbols in the source message by a new symbol, @@ -1050,18 +1075,24 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { e.map = data; - for (File f = FILE_A; f <= maxFile; ++f) { + for (File f = FILE_A; f <= maxFile; ++f) + { auto flags = e.get(0, f)->flags; - if (flags & TBFlag::Mapped) { - if (flags & TBFlag::Wide) { + if (flags & TBFlag::Mapped) + { + if (flags & TBFlag::Wide) + { data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table - for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x - e.get(0, f)->map_idx[i] = uint16_t((uint16_t*)data - (uint16_t*)e.map + 1); + for (int i = 0; i < 4; ++i) + { // Sequence like 3,x,x,x,1,x,0,2,x,x + e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1); data += 2 * number(data) + 2; } } - else { - for (int i = 0; i < 4; ++i) { + else + { + for (int i = 0; i < 4; ++i) + { e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1); data += *data + 1; } @@ -1069,7 +1100,7 @@ uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { } } - return data += uintptr_t(data) & 1; // Word alignment + return data += uintptr_t(data) & 1; // Word alignment } // Populate entry's PairsData records with data from the just memory-mapped file. @@ -1079,38 +1110,42 @@ void set(T& e, uint8_t* data) { PairsData* d; - enum { Split = 1, HasPawns = 2 }; + enum { + Split = 1, + HasPawns = 2 + }; - assert(e.hasPawns == bool(*data & HasPawns)); + assert(e.hasPawns == bool(*data & HasPawns)); assert((e.key != e.key2) == bool(*data & Split)); - data++; // First byte stores flags + data++; // First byte stores flags - const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; + const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; const File maxFile = e.hasPawns ? FILE_D : FILE_A; - bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides assert(!pp || e.pawnCount[0]); - for (File f = FILE_A; f <= maxFile; ++f) { + for (File f = FILE_A; f <= maxFile; ++f) + { for (int i = 0; i < sides; i++) *e.get(i, f) = PairsData(); - int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, - { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; + int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF}, + {*data >> 4, pp ? *(data + 1) >> 4 : 0xF}}; data += 1 + pp; for (int k = 0; k < e.pieceCount; ++k, ++data) for (int i = 0; i < sides; i++) - e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); + e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); for (int i = 0; i < sides; ++i) set_groups(e, e.get(i, f), order[i], f); } - data += uintptr_t(data) & 1; // Word alignment + data += uintptr_t(data) & 1; // Word alignment for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) @@ -1119,20 +1154,23 @@ void set(T& e, uint8_t* data) { data = set_dtz_map(e, data, maxFile); for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - (d = e.get(i, f))->sparseIndex = (SparseEntry*)data; + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->sparseIndex = (SparseEntry*) data; data += d->sparseIndexSize * sizeof(SparseEntry); } for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - (d = e.get(i, f))->blockLength = (uint16_t*)data; + for (int i = 0; i < sides; i++) + { + (d = e.get(i, f))->blockLength = (uint16_t*) data; data += d->blockLengthSize * sizeof(uint16_t); } for (File f = FILE_A; f <= maxFile; ++f) - for (int i = 0; i < sides; i++) { - data = (uint8_t*)((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment + for (int i = 0; i < sides; i++) + { + data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment (d = e.get(i, f))->data = data; data += d->blocksNum * d->sizeofBlock; } @@ -1150,22 +1188,23 @@ void* mapped(TBTable& e, const Position& pos) { // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). if (e.ready.load(std::memory_order_acquire)) - return e.baseAddress; // Could be nullptr if file does not exist + return e.baseAddress; // Could be nullptr if file does not exist std::scoped_lock lk(mutex); - if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock return e.baseAddress; // Pieces strings in decreasing order for each color, like ("KPP","KR") std::string fname, w, b; - for (PieceType pt = KING; pt >= PAWN; --pt) { + for (PieceType pt = KING; pt >= PAWN; --pt) + { w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); } - fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) - + (Type == WDL ? ".rtbw" : ".rtbz"); + fname = + (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz"); uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type); @@ -1179,7 +1218,7 @@ void* mapped(TBTable& e, const Position& pos) { template::Ret> Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { - if (pos.count() == 2) // KvK + if (pos.count() == 2) // KvK return Ret(WDLDraw); TBTable* entry = TBTables.get(pos.material_key()); @@ -1206,16 +1245,15 @@ Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) template WDLScore search(Position& pos, ProbeState* result) { - WDLScore value, bestValue = WDLLoss; + WDLScore value, bestValue = WDLLoss; StateInfo st; - auto moveList = MoveList(pos); + auto moveList = MoveList(pos); size_t totalCount = moveList.size(), moveCount = 0; for (const Move move : moveList) { - if ( !pos.capture(move) - && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; moveCount++; @@ -1233,7 +1271,7 @@ WDLScore search(Position& pos, ProbeState* result) { if (value >= WDLWin) { - *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move return value; } } @@ -1259,13 +1297,12 @@ WDLScore search(Position& pos, ProbeState* result) { // DTZ stores a "don't care" value if bestValue is a win if (bestValue >= value) - return *result = ( bestValue > WDLDraw - || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; return *result = OK, value; } -} // namespace +} // namespace // Tablebases::init() is called at startup and after every change to @@ -1275,7 +1312,7 @@ void Tablebases::init(const std::string& paths) { TBTables.clear(); MaxCardinality = 0; - TBFile::Paths = paths; + TBFile::Paths = paths; if (paths.empty() || paths == "") return; @@ -1307,14 +1344,14 @@ void Tablebases::init(const std::string& paths) { code = 0; for (int idx = 0; idx < 10; idx++) for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) - if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 { for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) if ((PseudoAttacks[KING][s1] | s1) & s2) - continue; // Illegal position + continue; // Illegal position else if (!off_A1H8(s1) && off_A1H8(s2) > 0) - continue; // First on diagonal, second above + continue; // First on diagonal, second above else if (!off_A1H8(s1) && !off_A1H8(s2)) bothOnDiagonal.emplace_back(idx, s2); @@ -1331,16 +1368,16 @@ void Tablebases::init(const std::string& paths) { // are Binomial[k][n] ways to choose k elements from a set of n elements. Binomial[0][0] = 1; - for (int n = 1; n < 64; n++) // Squares - for (int k = 0; k < 6 && k <= n; ++k) // Pieces - Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0) - + (k < n ? Binomial[k ][n - 1] : 0); + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = + (k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0); // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible // available squares when the leading one is in 's'. Moreover the pawn with // highest MapPawns[] is the leading pawn, the one nearest the edge, and // among pawns with the same file, the one with the lowest rank. - int availableSquares = 47; // Available squares when lead pawn is in a2 + int availableSquares = 47; // Available squares when lead pawn is in a2 // Init the tables for the encoding of leading pawns group: with 7-men TB we // can have up to 5 leading pawns (KPPPPPK). @@ -1364,7 +1401,7 @@ void Tablebases::init(const std::string& paths) { // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 if (leadPawnsCnt == 1) { - MapPawns[sq] = availableSquares--; + MapPawns[sq] = availableSquares--; MapPawns[flip_file(sq)] = availableSquares--; } LeadPawnIdx[leadPawnsCnt][sq] = idx; @@ -1375,20 +1412,24 @@ void Tablebases::init(const std::string& paths) { } // Add entries in TB tables if the corresponding ".rtbw" file exists - for (PieceType p1 = PAWN; p1 < KING; ++p1) { + for (PieceType p1 = PAWN; p1 < KING; ++p1) + { TBTables.add({KING, p1, KING}); - for (PieceType p2 = PAWN; p2 <= p1; ++p2) { + for (PieceType p2 = PAWN; p2 <= p1; ++p2) + { TBTables.add({KING, p1, p2, KING}); TBTables.add({KING, p1, KING, p2}); for (PieceType p3 = PAWN; p3 < KING; ++p3) TBTables.add({KING, p1, p2, KING, p3}); - for (PieceType p3 = PAWN; p3 <= p2; ++p3) { + for (PieceType p3 = PAWN; p3 <= p2; ++p3) + { TBTables.add({KING, p1, p2, p3, KING}); - for (PieceType p4 = PAWN; p4 <= p3; ++p4) { + for (PieceType p4 = PAWN; p4 <= p3; ++p4) + { TBTables.add({KING, p1, p2, p3, p4, KING}); for (PieceType p5 = PAWN; p5 <= p4; ++p5) @@ -1398,7 +1439,8 @@ void Tablebases::init(const std::string& paths) { TBTables.add({KING, p1, p2, p3, p4, KING, p5}); } - for (PieceType p4 = PAWN; p4 < KING; ++p4) { + for (PieceType p4 = PAWN; p4 < KING; ++p4) + { TBTables.add({KING, p1, p2, p3, KING, p4}); for (PieceType p5 = PAWN; p5 <= p4; ++p5) @@ -1457,10 +1499,10 @@ WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { // then do not accept moves leading to dtz + 50-move-counter == 100. int Tablebases::probe_dtz(Position& pos, ProbeState* result) { - *result = OK; + *result = OK; WDLScore wdl = search(pos, result); - if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws return 0; // DTZ stores a 'don't care value in this case, or even a plain wrong @@ -1479,7 +1521,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // DTZ stores results for the other side, so we need to do a 1-ply search and // find the winning move that minimizes DTZ. StateInfo st; - int minDTZ = 0xFFFF; + int minDTZ = 0xFFFF; for (const Move move : MoveList(pos)) { @@ -1491,8 +1533,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // otherwise we will get the dtz of the next move sequence. Search the // position after the move to get the score sign (because even in a // winning position we could make a losing capture or go for a draw). - dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) - : -probe_dtz(pos, result); + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) : -probe_dtz(pos, result); // If the move mates, force minDTZ to 1 if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0) @@ -1524,7 +1565,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { ProbeState result = OK; - StateInfo st; + StateInfo st; // Obtain 50-move counter for the root position int cnt50 = pos.rule50_count(); @@ -1544,7 +1585,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { { // In case of a zeroing move, dtz is one of -101/-1/0/1/101 WDLScore wdl = -probe_wdl(pos, &result); - dtz = dtz_before_zeroing(wdl); + dtz = dtz_before_zeroing(wdl); } else if (pos.is_draw(1)) { @@ -1557,14 +1598,11 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { { // Otherwise, take dtz for the new position and correct by 1 ply dtz = -probe_dtz(pos, &result); - dtz = dtz > 0 ? dtz + 1 - : dtz < 0 ? dtz - 1 : dtz; + dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz; } // Make sure that a mating move is assigned a dtz value of 1 - if ( pos.checkers() - && dtz == 2 - && MoveList(pos).size() == 0) + if (pos.checkers() && dtz == 2 && MoveList(pos).size() == 0) dtz = 1; pos.undo_move(m.pv[0]); @@ -1574,19 +1612,19 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Better moves are ranked higher. Certain wins are ranked equally. // Losing moves are ranked equally unless a 50-move draw is in sight. - int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) - : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) - : 0; + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) + : 0; m.tbRank = r; // Determine the score to be displayed for this move. Assign at least // 1 cp to cursed wins and let it grow to 49 cp as the positions gets // closer to a real win. - m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 - : r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) - : r == 0 ? VALUE_DRAW - : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) - : -VALUE_MATE + MAX_PLY + 1; + m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 + : r > 0 ? Value((std::max(3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) + : r == 0 ? VALUE_DRAW + : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) + : -VALUE_MATE + MAX_PLY + 1; } return true; @@ -1599,11 +1637,11 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // A return value false indicates that not all probes were successful. bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { - static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ }; + static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ}; ProbeState result = OK; - StateInfo st; - WDLScore wdl; + StateInfo st; + WDLScore wdl; bool rule50 = Options["Syzygy50MoveRule"]; @@ -1625,12 +1663,11 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { m.tbRank = WDL_to_rank[wdl + 2]; if (!rule50) - wdl = wdl > WDLDraw ? WDLWin - : wdl < WDLDraw ? WDLLoss : WDLDraw; + wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw; m.tbScore = WDL_to_value[wdl + 2]; } return true; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index b2ba35ff4b0..3b7c8aa70fd 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -30,30 +30,30 @@ class Position; namespace Stockfish::Tablebases { enum WDLScore { - WDLLoss = -2, // Loss - WDLBlessedLoss = -1, // Loss, but draw under 50-move rule - WDLDraw = 0, // Draw - WDLCursedWin = 1, // Win, but draw under 50-move rule - WDLWin = 2, // Win + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win }; // Possible states after a probing operation enum ProbeState { - FAIL = 0, // Probe failed (missing file table) - OK = 1, // Probe successful - CHANGE_STM = -1, // DTZ should check the other side - ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe successful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) }; extern int MaxCardinality; -void init(const std::string& paths); +void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); -int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves); -bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); -void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); +int probe_dtz(Position& pos, ProbeState* result); +bool root_probe(Position& pos, Search::RootMoves& rootMoves); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); +void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); -} // namespace Stockfish::Tablebases +} // namespace Stockfish::Tablebases #endif diff --git a/src/thread.cpp b/src/thread.cpp index c752e7326cd..9f8a63bdc01 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -37,15 +37,17 @@ namespace Stockfish { -ThreadPool Threads; // Global object +ThreadPool Threads; // Global object // Thread constructor launches the thread and waits until it goes to sleep // in idle_loop(). Note that 'searching' and 'exit' should be already set. -Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { +Thread::Thread(size_t n) : + idx(n), + stdThread(&Thread::idle_loop, this) { - wait_for_search_finished(); + wait_for_search_finished(); } @@ -54,11 +56,11 @@ Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { Thread::~Thread() { - assert(!searching); + assert(!searching); - exit = true; - start_searching(); - stdThread.join(); + exit = true; + start_searching(); + stdThread.join(); } @@ -66,25 +68,25 @@ Thread::~Thread() { void Thread::clear() { - counterMoves.fill(MOVE_NONE); - mainHistory.fill(0); - captureHistory.fill(0); + counterMoves.fill(MOVE_NONE); + mainHistory.fill(0); + captureHistory.fill(0); - for (bool inCheck : { false, true }) - for (StatsType c : { NoCaptures, Captures }) - for (auto& to : continuationHistory[inCheck][c]) - for (auto& h : to) - h->fill(-71); + for (bool inCheck : {false, true}) + for (StatsType c : {NoCaptures, Captures}) + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h->fill(-71); } // Thread::start_searching() wakes up the thread that will start the search void Thread::start_searching() { - mutex.lock(); - searching = true; - mutex.unlock(); // Unlock before notifying saves a few CPU-cycles - cv.notify_one(); // Wake up the thread in idle_loop() + mutex.lock(); + searching = true; + mutex.unlock(); // Unlock before notifying saves a few CPU-cycles + cv.notify_one(); // Wake up the thread in idle_loop() } @@ -93,8 +95,8 @@ void Thread::start_searching() { void Thread::wait_for_search_finished() { - std::unique_lock lk(mutex); - cv.wait(lk, [&]{ return !searching; }); + std::unique_lock lk(mutex); + cv.wait(lk, [&] { return !searching; }); } @@ -103,28 +105,28 @@ void Thread::wait_for_search_finished() { void Thread::idle_loop() { - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case, all this - // NUMA machinery is not needed. - if (Options["Threads"] > 8) - WinProcGroup::bindThisThread(idx); + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case, all this + // NUMA machinery is not needed. + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); - while (true) - { - std::unique_lock lk(mutex); - searching = false; - cv.notify_one(); // Wake up anyone waiting for search finished - cv.wait(lk, [&]{ return searching; }); + while (true) + { + std::unique_lock lk(mutex); + searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + cv.wait(lk, [&] { return searching; }); - if (exit) - return; + if (exit) + return; - lk.unlock(); + lk.unlock(); - search(); - } + search(); + } } // ThreadPool::set() creates/destroys threads to match the requested number. @@ -133,28 +135,28 @@ void Thread::idle_loop() { void ThreadPool::set(size_t requested) { - if (threads.size() > 0) // destroy any existing thread(s) - { - main()->wait_for_search_finished(); + if (threads.size() > 0) // destroy any existing thread(s) + { + main()->wait_for_search_finished(); - while (threads.size() > 0) - delete threads.back(), threads.pop_back(); - } + while (threads.size() > 0) + delete threads.back(), threads.pop_back(); + } - if (requested > 0) // create new thread(s) - { - threads.push_back(new MainThread(0)); + if (requested > 0) // create new thread(s) + { + threads.push_back(new MainThread(0)); - while (threads.size() < requested) - threads.push_back(new Thread(threads.size())); - clear(); + while (threads.size() < requested) + threads.push_back(new Thread(threads.size())); + clear(); - // Reallocate the hash with the new threadpool size - TT.resize(size_t(Options["Hash"])); + // Reallocate the hash with the new threadpool size + TT.resize(size_t(Options["Hash"])); - // Init thread number dependent search params. - Search::init(); - } + // Init thread number dependent search params. + Search::init(); + } } @@ -162,77 +164,79 @@ void ThreadPool::set(size_t requested) { void ThreadPool::clear() { - for (Thread* th : threads) - th->clear(); + for (Thread* th : threads) + th->clear(); - main()->callsCnt = 0; - main()->bestPreviousScore = VALUE_INFINITE; - main()->bestPreviousAverageScore = VALUE_INFINITE; - main()->previousTimeReduction = 1.0; + main()->callsCnt = 0; + main()->bestPreviousScore = VALUE_INFINITE; + main()->bestPreviousAverageScore = VALUE_INFINITE; + main()->previousTimeReduction = 1.0; } // ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. -void ThreadPool::start_thinking(Position& pos, StateListPtr& states, - const Search::LimitsType& limits, bool ponderMode) { - - main()->wait_for_search_finished(); - - main()->stopOnPonderhit = stop = false; - increaseDepth = true; - main()->ponder = ponderMode; - Search::Limits = limits; - Search::RootMoves rootMoves; - - for (const auto& m : MoveList(pos)) - if ( limits.searchmoves.empty() - || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) - rootMoves.emplace_back(m); - - if (!rootMoves.empty()) - Tablebases::rank_root_moves(pos, rootMoves); - - // After ownership transfer 'states' becomes empty, so if we stop the search - // and call 'go' again without setting a new position states.get() == nullptr. - assert(states.get() || setupStates.get()); - - if (states.get()) - setupStates = std::move(states); // Ownership transfer, states is now empty - - // We use Position::set() to set root position across threads. But there are - // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot - // be deduced from a fen string, so set() clears them and they are set from - // setupStates->back() later. The rootState is per thread, earlier states are shared - // since they are read-only. - for (Thread* th : threads) - { - th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; - th->rootDepth = th->completedDepth = 0; - th->rootMoves = rootMoves; - th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); - th->rootState = setupStates->back(); - th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); - } - - main()->start_searching(); +void ThreadPool::start_thinking(Position& pos, + StateListPtr& states, + const Search::LimitsType& limits, + bool ponderMode) { + + main()->wait_for_search_finished(); + + main()->stopOnPonderhit = stop = false; + increaseDepth = true; + main()->ponder = ponderMode; + Search::Limits = limits; + Search::RootMoves rootMoves; + + for (const auto& m : MoveList(pos)) + if (limits.searchmoves.empty() + || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + rootMoves.emplace_back(m); + + if (!rootMoves.empty()) + Tablebases::rank_root_moves(pos, rootMoves); + + // After ownership transfer 'states' becomes empty, so if we stop the search + // and call 'go' again without setting a new position states.get() == nullptr. + assert(states.get() || setupStates.get()); + + if (states.get()) + setupStates = std::move(states); // Ownership transfer, states is now empty + + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and they are set from + // setupStates->back() later. The rootState is per thread, earlier states are shared + // since they are read-only. + for (Thread* th : threads) + { + th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; + th->rootDepth = th->completedDepth = 0; + th->rootMoves = rootMoves; + th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); + th->rootState = setupStates->back(); + th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); + } + + main()->start_searching(); } Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); + Thread* bestThread = threads.front(); std::map votes; - Value minScore = VALUE_NONE; + Value minScore = VALUE_NONE; // Find the minimum score of all threads - for (Thread* th: threads) + for (Thread* th : threads) minScore = std::min(minScore, th->rootMoves[0].score); // Vote according to score and depth, and select the best thread auto thread_value = [minScore](Thread* th) { - return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); - }; + return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + }; for (Thread* th : threads) votes[th->rootMoves[0].pv[0]] += thread_value(th); @@ -244,12 +248,13 @@ Thread* ThreadPool::get_best_thread() const { if (th->rootMoves[0].score > bestThread->rootMoves[0].score) bestThread = th; } - else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] - || ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] - && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) - > thread_value(bestThread) * int(bestThread->rootMoves[0].pv.size() > 2))))) + else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY + && (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] + || (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] + && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) + > thread_value(bestThread) + * int(bestThread->rootMoves[0].pv.size() > 2))))) bestThread = th; return bestThread; @@ -275,4 +280,4 @@ void ThreadPool::wait_for_search_finished() const { th->wait_for_search_finished(); } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/thread.h b/src/thread.h index 44cc56729e3..4a077962661 100644 --- a/src/thread.h +++ b/src/thread.h @@ -41,56 +41,56 @@ namespace Stockfish { class Thread { - std::mutex mutex; - std::condition_variable cv; - size_t idx; - bool exit = false, searching = true; // Set before starting std::thread - NativeThread stdThread; - -public: - explicit Thread(size_t); - virtual ~Thread(); - virtual void search(); - void clear(); - void idle_loop(); - void start_searching(); - void wait_for_search_finished(); - size_t id() const { return idx; } - - size_t pvIdx, pvLast; - std::atomic nodes, tbHits, bestMoveChanges; - int selDepth, nmpMinPly; - Value bestValue, optimism[COLOR_NB]; - - Position rootPos; - StateInfo rootState; - Search::RootMoves rootMoves; - Depth rootDepth, completedDepth; - Value rootDelta; - Value rootSimpleEval; - CounterMoveHistory counterMoves; - ButterflyHistory mainHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; + std::mutex mutex; + std::condition_variable cv; + size_t idx; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; + + public: + explicit Thread(size_t); + virtual ~Thread(); + virtual void search(); + void clear(); + void idle_loop(); + void start_searching(); + void wait_for_search_finished(); + size_t id() const { return idx; } + + size_t pvIdx, pvLast; + std::atomic nodes, tbHits, bestMoveChanges; + int selDepth, nmpMinPly; + Value bestValue, optimism[COLOR_NB]; + + Position rootPos; + StateInfo rootState; + Search::RootMoves rootMoves; + Depth rootDepth, completedDepth; + Value rootDelta; + Value rootSimpleEval; + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; }; // MainThread is a derived class specific for main thread -struct MainThread : public Thread { +struct MainThread: public Thread { - using Thread::Thread; + using Thread::Thread; - void search() override; - void check_time(); + void search() override; + void check_time(); - double previousTimeReduction; - Value bestPreviousScore; - Value bestPreviousAverageScore; - Value iterValue[4]; - int callsCnt; - bool stopOnPonderhit; - std::atomic_bool ponder; + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + Value iterValue[4]; + int callsCnt; + bool stopOnPonderhit; + std::atomic_bool ponder; }; @@ -100,41 +100,41 @@ struct MainThread : public Thread { struct ThreadPool { - void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); - void clear(); - void set(size_t); - - MainThread* main() const { return static_cast(threads.front()); } - uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } - uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; - - std::atomic_bool stop, increaseDepth; - - auto cbegin() const noexcept { return threads.cbegin(); } - auto begin() noexcept { return threads.begin(); } - auto end() noexcept { return threads.end(); } - auto cend() const noexcept { return threads.cend(); } - auto size() const noexcept { return threads.size(); } - auto empty() const noexcept { return threads.empty(); } - -private: - StateListPtr setupStates; - std::vector threads; - - uint64_t accumulate(std::atomic Thread::* member) const { - - uint64_t sum = 0; - for (Thread* th : threads) - sum += (th->*member).load(std::memory_order_relaxed); - return sum; - } + void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); + void set(size_t); + + MainThread* main() const { return static_cast(threads.front()); } + uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } + uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; + + std::atomic_bool stop, increaseDepth; + + auto cbegin() const noexcept { return threads.cbegin(); } + auto begin() noexcept { return threads.begin(); } + auto end() noexcept { return threads.end(); } + auto cend() const noexcept { return threads.cend(); } + auto size() const noexcept { return threads.size(); } + auto empty() const noexcept { return threads.empty(); } + + private: + StateListPtr setupStates; + std::vector threads; + + uint64_t accumulate(std::atomic Thread::*member) const { + + uint64_t sum = 0; + for (Thread* th : threads) + sum += (th->*member).load(std::memory_order_relaxed); + return sum; + } }; extern ThreadPool Threads; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef THREAD_H_INCLUDED +#endif // #ifndef THREAD_H_INCLUDED diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 77352aa087a..248e4a67450 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -29,46 +29,45 @@ #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) -#include + #include namespace Stockfish { static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; -template > -void* start_routine(void* ptr) -{ - P* p = reinterpret_cast(ptr); - (p->first->*(p->second))(); // Call member function pointer - delete p; - return nullptr; +template> +void* start_routine(void* ptr) { + P* p = reinterpret_cast(ptr); + (p->first->*(p->second))(); // Call member function pointer + delete p; + return nullptr; } class NativeThread { - pthread_t thread; - -public: - template> - explicit NativeThread(void(T::*fun)(), T* obj) { - pthread_attr_t attr_storage, *attr = &attr_storage; - pthread_attr_init(attr); - pthread_attr_setstacksize(attr, TH_STACK_SIZE); - pthread_create(&thread, attr, start_routine, new P(obj, fun)); - } - void join() { pthread_join(thread, nullptr); } + pthread_t thread; + + public: + template> + explicit NativeThread(void (T::*fun)(), T* obj) { + pthread_attr_t attr_storage, *attr = &attr_storage; + pthread_attr_init(attr); + pthread_attr_setstacksize(attr, TH_STACK_SIZE); + pthread_create(&thread, attr, start_routine, new P(obj, fun)); + } + void join() { pthread_join(thread, nullptr); } }; -} // namespace Stockfish +} // namespace Stockfish -#else // Default case: use STL classes +#else // Default case: use STL classes namespace Stockfish { using NativeThread = std::thread; -} // namespace Stockfish +} // namespace Stockfish #endif -#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED +#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/src/timeman.cpp b/src/timeman.cpp index 74f59d90574..cf0e08ed789 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -26,7 +26,7 @@ namespace Stockfish { -TimeManagement Time; // Our global time management object +TimeManagement Time; // Our global time management object // TimeManagement::init() is called at the beginning of the search and calculates @@ -36,74 +36,74 @@ TimeManagement Time; // Our global time management object void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - // If we have no time, no need to initialize TM, except for the start time, - // which is used by movetime. - startTime = limits.startTime; - if (limits.time[us] == 0) - return; - - TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); - TimePoint slowMover = TimePoint(Options["Slow Mover"]); - TimePoint npmsec = TimePoint(Options["nodestime"]); - - // optScale is a percentage of available time to use for the current move. - // maxScale is a multiplier applied to optimumTime. - double optScale, maxScale; - - // If we have to play in 'nodes as time' mode, then convert from time - // to nodes, and use resulting values in time management formulas. - // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) - // must be much lower than the real engine speed. - if (npmsec) - { - if (!availableNodes) // Only once at game start - availableNodes = npmsec * limits.time[us]; // Time is in msec - - // Convert from milliseconds to nodes - limits.time[us] = TimePoint(availableNodes); - limits.inc[us] *= npmsec; - limits.npmsec = npmsec; - } - - // Maximum move horizon of 50 moves - int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; - - // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = std::max(TimePoint(1), - limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); - - // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12); - - // A user may scale time usage by setting UCI option "Slow Mover" - // Default is 100 and changing this value will probably lose elo. - timeLeft = slowMover * timeLeft / 100; - - // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed actual available - // game time for the current move, so also cap to 20% of available game time. - if (limits.movestogo == 0) - { - optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, - 0.2 * limits.time[us] / double(timeLeft)) + // If we have no time, no need to initialize TM, except for the start time, + // which is used by movetime. + startTime = limits.startTime; + if (limits.time[us] == 0) + return; + + TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); + TimePoint slowMover = TimePoint(Options["Slow Mover"]); + TimePoint npmsec = TimePoint(Options["nodestime"]); + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // If we have to play in 'nodes as time' mode, then convert from time + // to nodes, and use resulting values in time management formulas. + // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) + // must be much lower than the real engine speed. + if (npmsec) + { + if (!availableNodes) // Only once at game start + availableNodes = npmsec * limits.time[us]; // Time is in msec + + // Convert from milliseconds to nodes + limits.time[us] = TimePoint(availableNodes); + limits.inc[us] *= npmsec; + limits.npmsec = npmsec; + } + + // Maximum move horizon of 50 moves + int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) + - moveOverhead * (2 + mtg)); + + // Use extra time with larger increments + double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12); + + // A user may scale time usage by setting UCI option "Slow Mover" + // Default is 100 and changing this value will probably lose elo. + timeLeft = slowMover * timeLeft / 100; + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed actual available + // game time for the current move, so also cap to 20% of available game time. + if (limits.movestogo == 0) + { + optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, + 0.2 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(7.0, 4.0 + ply / 12.0); - } - - // x moves in y seconds (+ z increment) - else - { - optScale = std::min((0.88 + ply / 116.4) / mtg, - 0.88 * limits.time[us] / double(timeLeft)); - maxScale = std::min(6.3, 1.5 + 0.11 * mtg); - } - - // Never use more than 80% of the available time for this move - optimumTime = TimePoint(optScale * timeLeft); - maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; - - if (Options["Ponder"]) - optimumTime += optimumTime / 4; + maxScale = std::min(7.0, 4.0 + ply / 12.0); + } + + // x moves in y seconds (+ z increment) + else + { + optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft)); + maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + } + + // Never use more than 80% of the available time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = + TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/timeman.h b/src/timeman.h index 6acdf0ac6db..4b9b62bd2a9 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -32,23 +32,24 @@ namespace Stockfish { // the maximum available time, the game move number, and other parameters. class TimeManagement { -public: - void init(Search::LimitsType& limits, Color us, int ply); - TimePoint optimum() const { return optimumTime; } - TimePoint maximum() const { return maximumTime; } - TimePoint elapsed() const { return Search::Limits.npmsec ? - TimePoint(Threads.nodes_searched()) : now() - startTime; } - - int64_t availableNodes; // When in 'nodes as time' mode - -private: - TimePoint startTime; - TimePoint optimumTime; - TimePoint maximumTime; + public: + void init(Search::LimitsType& limits, Color us, int ply); + TimePoint optimum() const { return optimumTime; } + TimePoint maximum() const { return maximumTime; } + TimePoint elapsed() const { + return Search::Limits.npmsec ? TimePoint(Threads.nodes_searched()) : now() - startTime; + } + + int64_t availableNodes; // When in 'nodes as time' mode + + private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; }; extern TimeManagement Time; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TIMEMAN_H_INCLUDED +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/src/tt.cpp b/src/tt.cpp index c3aec8d3e7c..a3ad0a78d4f 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -31,31 +31,29 @@ namespace Stockfish { -TranspositionTable TT; // Our global transposition table +TranspositionTable TT; // Our global transposition table // TTEntry::save() populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { - // Preserve any existing move for the same position - if (m || uint16_t(k) != key16) - move16 = uint16_t(m); - - // Overwrite less valuable entries (cheapest checks first) - if ( b == BOUND_EXACT - || uint16_t(k) != key16 - || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) - { - assert(d > DEPTH_OFFSET); - assert(d < 256 + DEPTH_OFFSET); - - key16 = uint16_t(k); - depth8 = uint8_t(d - DEPTH_OFFSET); - genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); - value16 = int16_t(v); - eval16 = int16_t(ev); - } + // Preserve any existing move for the same position + if (m || uint16_t(k) != key16) + move16 = uint16_t(m); + + // Overwrite less valuable entries (cheapest checks first) + if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) + { + assert(d > DEPTH_OFFSET); + assert(d < 256 + DEPTH_OFFSET); + + key16 = uint16_t(k); + depth8 = uint8_t(d - DEPTH_OFFSET); + genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); + value16 = int16_t(v); + eval16 = int16_t(ev); + } } @@ -65,21 +63,20 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) void TranspositionTable::resize(size_t mbSize) { - Threads.main()->wait_for_search_finished(); + Threads.main()->wait_for_search_finished(); - aligned_large_pages_free(table); + aligned_large_pages_free(table); - clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); + clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); - table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); - if (!table) - { - std::cerr << "Failed to allocate " << mbSize - << "MB for transposition table." << std::endl; - exit(EXIT_FAILURE); - } + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); + if (!table) + { + std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } - clear(); + clear(); } @@ -88,28 +85,27 @@ void TranspositionTable::resize(size_t mbSize) { void TranspositionTable::clear() { - std::vector threads; + std::vector threads; - for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) - { - threads.emplace_back([this, idx]() { + for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) + { + threads.emplace_back([this, idx]() { + // Thread binding gives faster search on systems with a first-touch policy + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); - // Thread binding gives faster search on systems with a first-touch policy - if (Options["Threads"] > 8) - WinProcGroup::bindThisThread(idx); + // Each thread will zero its part of the hash table + const size_t stride = size_t(clusterCount / Options["Threads"]), + start = size_t(stride * idx), + len = + idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start; - // Each thread will zero its part of the hash table - const size_t stride = size_t(clusterCount / Options["Threads"]), - start = size_t(stride * idx), - len = idx != size_t(Options["Threads"]) - 1 ? - stride : clusterCount - start; + std::memset(&table[start], 0, len * sizeof(Cluster)); + }); + } - std::memset(&table[start], 0, len * sizeof(Cluster)); - }); - } - - for (std::thread& th : threads) - th.join(); + for (std::thread& th : threads) + th.join(); } @@ -122,30 +118,33 @@ void TranspositionTable::clear() { TTEntry* TranspositionTable::probe(const Key key, bool& found) const { - TTEntry* const tte = first_entry(key); - const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster - - for (int i = 0; i < ClusterSize; ++i) - if (tte[i].key16 == key16 || !tte[i].depth8) - { - tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh - - return found = bool(tte[i].depth8), &tte[i]; - } - - // Find an entry to be replaced according to the replacement strategy - TTEntry* replace = tte; - for (int i = 1; i < ClusterSize; ++i) - // Due to our packed storage format for generation and its cyclic - // nature we add GENERATION_CYCLE (256 is the modulus, plus what - // is needed to keep the unrelated lowest n bits from affecting - // the result) to calculate the entry age correctly even after - // generation8 overflows into the next cycle. - if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) - > tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) - replace = &tte[i]; - - return found = false, replace; + TTEntry* const tte = first_entry(key); + const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster + + for (int i = 0; i < ClusterSize; ++i) + if (tte[i].key16 == key16 || !tte[i].depth8) + { + tte[i].genBound8 = + uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh + + return found = bool(tte[i].depth8), &tte[i]; + } + + // Find an entry to be replaced according to the replacement strategy + TTEntry* replace = tte; + for (int i = 1; i < ClusterSize; ++i) + // Due to our packed storage format for generation and its cyclic + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + if (replace->depth8 + - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) + > tte[i].depth8 + - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) + replace = &tte[i]; + + return found = false, replace; } @@ -154,12 +153,13 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { int TranspositionTable::hashfull() const { - int cnt = 0; - for (int i = 0; i < 1000; ++i) - for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; + int cnt = 0; + for (int i = 0; i < 1000; ++i) + for (int j = 0; j < ClusterSize; ++j) + cnt += table[i].entry[j].depth8 + && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; - return cnt / ClusterSize; + return cnt / ClusterSize; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/tt.h b/src/tt.h index fdea4933507..628dfba28f7 100644 --- a/src/tt.h +++ b/src/tt.h @@ -40,23 +40,23 @@ namespace Stockfish { struct TTEntry { - Move move() const { return Move (move16); } - Value value() const { return Value(value16); } - Value eval() const { return Value(eval16); } - Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } - bool is_pv() const { return bool (genBound8 & 0x4); } - Bound bound() const { return Bound(genBound8 & 0x3); } - void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); - -private: - friend class TranspositionTable; - - uint16_t key16; - uint8_t depth8; - uint8_t genBound8; - uint16_t move16; - int16_t value16; - int16_t eval16; + Move move() const { return Move(move16); } + Value value() const { return Value(value16); } + Value eval() const { return Value(eval16); } + Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } + bool is_pv() const { return bool(genBound8 & 0x4); } + Bound bound() const { return Bound(genBound8 & 0x3); } + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); + + private: + friend class TranspositionTable; + + uint16_t key16; + uint8_t depth8; + uint8_t genBound8; + uint16_t move16; + int16_t value16; + int16_t eval16; }; @@ -68,43 +68,45 @@ struct TTEntry { class TranspositionTable { - static constexpr int ClusterSize = 3; - - struct Cluster { - TTEntry entry[ClusterSize]; - char padding[2]; // Pad to 32 bytes - }; - - static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); - - // Constants used to refresh the hash table periodically - static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things - static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field - static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length - static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number - -public: - ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things - TTEntry* probe(const Key key, bool& found) const; - int hashfull() const; - void resize(size_t mbSize); - void clear(); - - TTEntry* first_entry(const Key key) const { - return &table[mul_hi64(key, clusterCount)].entry[0]; - } - -private: - friend struct TTEntry; - - size_t clusterCount; - Cluster* table; - uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 + static constexpr int ClusterSize = 3; + + struct Cluster { + TTEntry entry[ClusterSize]; + char padding[2]; // Pad to 32 bytes + }; + + static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); + + // Constants used to refresh the hash table periodically + static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things + static constexpr int GENERATION_DELTA = + (1 << GENERATION_BITS); // increment for generation field + static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length + static constexpr int GENERATION_MASK = + (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + + public: + ~TranspositionTable() { aligned_large_pages_free(table); } + void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things + TTEntry* probe(const Key key, bool& found) const; + int hashfull() const; + void resize(size_t mbSize); + void clear(); + + TTEntry* first_entry(const Key key) const { + return &table[mul_hi64(key, clusterCount)].entry[0]; + } + + private: + friend struct TTEntry; + + size_t clusterCount; + Cluster* table; + uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 }; extern TranspositionTable TT; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TT_H_INCLUDED +#endif // #ifndef TT_H_INCLUDED diff --git a/src/tune.cpp b/src/tune.cpp index 97baeb784e9..cf80b9d7b70 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -34,75 +34,84 @@ using std::string; namespace Stockfish { -bool Tune::update_on_last; -const UCI::Option* LastOption = nullptr; +bool Tune::update_on_last; +const UCI::Option* LastOption = nullptr; static std::map TuneResults; string Tune::next(string& names, bool pop) { - string name; + string name; - do { - string token = names.substr(0, names.find(',')); + do + { + string token = names.substr(0, names.find(',')); - if (pop) - names.erase(0, token.size() + 1); + if (pop) + names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace - } while ( std::count(name.begin(), name.end(), '(') - - std::count(name.begin(), name.end(), ')')); + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); - return name; + return name; } static void on_tune(const UCI::Option& o) { - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); } static void make_option(const string& n, int v, const SetRange& r) { - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; - if (TuneResults.count(n)) - v = TuneResults[n]; + if (TuneResults.count(n)) + v = TuneResults[n]; - Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); - LastOption = &Options[n]; + Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); + LastOption = &Options[n]; - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," - << v << "," - << r(v).first << "," << r(v).second << "," - << (r(v).second - r(v).first) / 20.0 << "," - << "0.0020" - << std::endl; + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," + << (r(v).second - r(v).first) / 20.0 << "," + << "0.0020" << std::endl; } -template<> void Tune::Entry::init_option() { make_option(name, value, range); } +template<> +void Tune::Entry::init_option() { + make_option(name, value, range); +} -template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = int(Options[name]); +template<> +void Tune::Entry::read_option() { + if (Options.count(name)) + value = int(Options[name]); } -template<> void Tune::Entry::init_option() { make_option(name, value, range); } +template<> +void Tune::Entry::init_option() { + make_option(name, value, range); +} -template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = Value(int(Options[name])); +template<> +void Tune::Entry::read_option() { + if (Options.count(name)) + value = Value(int(Options[name])); } // Instead of a variable here we have a PostUpdate function: just call it -template<> void Tune::Entry::init_option() {} -template<> void Tune::Entry::read_option() { value(); } +template<> +void Tune::Entry::init_option() {} +template<> +void Tune::Entry::read_option() { + value(); +} -} // namespace Stockfish +} // namespace Stockfish // Init options with tuning session results instead of default values. Useful to @@ -117,9 +126,7 @@ template<> void Tune::Entry::read_option() { value(); } namespace Stockfish { -void Tune::read_results() { - - /* ...insert your values here... */ +void Tune::read_results() { /* ...insert your values here... */ } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/tune.h b/src/tune.h index a9a7331e566..480aea165b5 100644 --- a/src/tune.h +++ b/src/tune.h @@ -22,28 +22,29 @@ #include #include #include -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include #include namespace Stockfish { enum Value : int; -using Range = std::pair; // Option's min-max values -using RangeFun = Range (int); +using Range = std::pair; // Option's min-max values +using RangeFun = Range(int); // Default Range function, to calculate Option's min-max values -inline Range default_range(int v) { - return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); -} +inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } struct SetRange { - explicit SetRange(RangeFun f) : fun(f) {} - SetRange(int min, int max) : fun(nullptr), range(min, max) {} - Range operator()(int v) const { return fun ? fun(v) : range; } - - RangeFun* fun; - Range range; + explicit SetRange(RangeFun f) : + fun(f) {} + SetRange(int min, int max) : + fun(nullptr), + range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } + + RangeFun* fun; + Range range; }; #define SetDefaultRange SetRange(default_range) @@ -76,88 +77,102 @@ struct SetRange { class Tune { - using PostUpdate = void (); // Post-update function - - Tune() { read_results(); } - Tune(const Tune&) = delete; - void operator=(const Tune&) = delete; - void read_results(); - - static Tune& instance() { static Tune t; return t; } // Singleton - - // Use polymorphism to accommodate Entry of different types in the same vector - struct EntryBase { - virtual ~EntryBase() = default; - virtual void init_option() = 0; - virtual void read_option() = 0; - }; - - template - struct Entry : public EntryBase { - - static_assert(!std::is_const_v, "Parameter cannot be const!"); - - static_assert( std::is_same_v - || std::is_same_v - || std::is_same_v, "Parameter type not supported!"); - - Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} - void operator=(const Entry&) = delete; // Because 'value' is a reference - void init_option() override; - void read_option() override; - - std::string name; - T& value; - SetRange range; - }; - - // Our facility to fill the container, each Entry corresponds to a parameter - // to tune. We use variadic templates to deal with an unspecified number of - // entries, each one of a possible different type. - static std::string next(std::string& names, bool pop = true); - - int add(const SetRange&, std::string&&) { return 0; } - - template - int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { - list.push_back(std::unique_ptr(new Entry(next(names), value, range))); - return add(range, std::move(names), args...); - } - - // Template specialization for arrays: recursively handle multi-dimensional arrays - template - int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { - for (size_t i = 0; i < N; i++) - add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); - return add(range, std::move(names), args...); - } - - // Template specialization for SetRange - template - int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { - return add(value, (next(names), std::move(names)), args...); - } - - std::vector> list; - -public: - template - static int add(const std::string& names, Args&&... args) { - return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis - } - static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access - static void read_options() { for (auto& e : instance().list) e->read_option(); } - static bool update_on_last; + using PostUpdate = void(); // Post-update function + + Tune() { read_results(); } + Tune(const Tune&) = delete; + void operator=(const Tune&) = delete; + void read_results(); + + static Tune& instance() { + static Tune t; + return t; + } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template + struct Entry: public EntryBase { + + static_assert(!std::is_const_v, "Parameter cannot be const!"); + + static_assert(std::is_same_v || std::is_same_v + || std::is_same_v, + "Parameter type not supported!"); + + Entry(const std::string& n, T& v, const SetRange& r) : + name(n), + value(v), + range(r) {} + void operator=(const Entry&) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T& value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string& names, bool pop = true); + + int add(const SetRange&, std::string&&) { return 0; } + + template + int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional arrays + template + int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template + int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { + return add(value, (next(names), std::move(names)), args...); + } + + std::vector> list; + + public: + template + static int add(const std::string& names, Args&&... args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init() { + for (auto& e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCI::Options access + static void read_options() { + for (auto& e : instance().list) + e->read_option(); + } + static bool update_on_last; }; // Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() #define STRINGIFY(x) #x -#define UNIQUE2(x, y) x ## y -#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define UNIQUE2(x, y) x##y +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ #define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TUNE_H_INCLUDED +#endif // #ifndef TUNE_H_INCLUDED diff --git a/src/types.h b/src/types.h index 1fc4d33a95f..c76efd077d1 100644 --- a/src/types.h +++ b/src/types.h @@ -17,7 +17,7 @@ */ #ifndef TYPES_H_INCLUDED -#define TYPES_H_INCLUDED + #define TYPES_H_INCLUDED // When compiling with provided Makefile (e.g. for Linux and OSX), configuration // is done automatically. To get started type 'make help'. @@ -36,15 +36,15 @@ // -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works // | only in 64-bit mode and requires hardware with pext support. -#include -#include + #include + #include -#if defined(_MSC_VER) -// Disable some silly and noisy warnings from MSVC compiler -#pragma warning(disable: 4127) // Conditional expression is constant -#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type -#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' -#endif + #if defined(_MSC_VER) + // Disable some silly and noisy warnings from MSVC compiler + #pragma warning(disable: 4127) // Conditional expression is constant + #pragma warning(disable: 4146) // Unary minus operator applied to unsigned type + #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' + #endif // Predefined macros hell: // @@ -55,53 +55,54 @@ // _WIN32 Building on Windows (any) // _WIN64 Building on Windows 64 bit -#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) -#define ALIGNAS_ON_STACK_VARIABLES_BROKEN -#endif + #if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \ + && defined(_WIN32) && !defined(__clang__) + #define ALIGNAS_ON_STACK_VARIABLES_BROKEN + #endif -#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) -#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used -# include // Microsoft header for _BitScanForward64() -# define IS_64BIT -#endif + #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used + #include // Microsoft header for _BitScanForward64() + #define IS_64BIT + #endif -#if defined(USE_POPCNT) && defined(_MSC_VER) -# include // Microsoft header for _mm_popcnt_u64() -#endif + #if defined(USE_POPCNT) && defined(_MSC_VER) + #include // Microsoft header for _mm_popcnt_u64() + #endif -#if !defined(NO_PREFETCH) && defined(_MSC_VER) -# include // Microsoft header for _mm_prefetch() -#endif + #if !defined(NO_PREFETCH) && defined(_MSC_VER) + #include // Microsoft header for _mm_prefetch() + #endif -#if defined(USE_PEXT) -# include // Header for _pext_u64() intrinsic -# define pext(b, m) _pext_u64(b, m) -#else -# define pext(b, m) 0 -#endif + #if defined(USE_PEXT) + #include // Header for _pext_u64() intrinsic + #define pext(b, m) _pext_u64(b, m) + #else + #define pext(b, m) 0 + #endif namespace Stockfish { -#ifdef USE_POPCNT + #ifdef USE_POPCNT constexpr bool HasPopCnt = true; -#else + #else constexpr bool HasPopCnt = false; -#endif + #endif -#ifdef USE_PEXT + #ifdef USE_PEXT constexpr bool HasPext = true; -#else + #else constexpr bool HasPext = false; -#endif + #endif -#ifdef IS_64BIT + #ifdef IS_64BIT constexpr bool Is64Bit = true; -#else + #else constexpr bool Is64Bit = false; -#endif + #endif -using Key = uint64_t; +using Key = uint64_t; using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; @@ -120,164 +121,187 @@ constexpr int MAX_PLY = 246; // while MOVE_NONE and MOVE_NULL have the same origin and destination square. enum Move : int { - MOVE_NONE, - MOVE_NULL = 65 + MOVE_NONE, + MOVE_NULL = 65 }; enum MoveType { - NORMAL, - PROMOTION = 1 << 14, - EN_PASSANT = 2 << 14, - CASTLING = 3 << 14 + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 }; enum Color { - WHITE, BLACK, COLOR_NB = 2 + WHITE, + BLACK, + COLOR_NB = 2 }; enum CastlingRights { - NO_CASTLING, - WHITE_OO, - WHITE_OOO = WHITE_OO << 1, - BLACK_OO = WHITE_OO << 2, - BLACK_OOO = WHITE_OO << 3, - - KING_SIDE = WHITE_OO | BLACK_OO, - QUEEN_SIDE = WHITE_OOO | BLACK_OOO, - WHITE_CASTLING = WHITE_OO | WHITE_OOO, - BLACK_CASTLING = BLACK_OO | BLACK_OOO, - ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, - - CASTLING_RIGHT_NB = 16 + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, + + KING_SIDE = WHITE_OO | BLACK_OO, + QUEEN_SIDE = WHITE_OOO | BLACK_OOO, + WHITE_CASTLING = WHITE_OO | WHITE_OOO, + BLACK_CASTLING = BLACK_OO | BLACK_OOO, + ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, + + CASTLING_RIGHT_NB = 16 }; enum Bound { - BOUND_NONE, - BOUND_UPPER, - BOUND_LOWER, - BOUND_EXACT = BOUND_UPPER | BOUND_LOWER + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; enum Value : int { - VALUE_ZERO = 0, - VALUE_DRAW = 0, - VALUE_MATE = 32000, - VALUE_INFINITE = 32001, - VALUE_NONE = 32002, - - VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, - VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, - VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, - VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, - - // In the code, we make the assumption that these values - // are such that non_pawn_material() can be used to uniquely - // identify the material on the board. - PawnValue = 208, - KnightValue = 781, - BishopValue = 825, - RookValue = 1276, - QueenValue = 2538, + VALUE_ZERO = 0, + VALUE_DRAW = 0, + VALUE_MATE = 32000, + VALUE_INFINITE = 32001, + VALUE_NONE = 32002, + + VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, + VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, + VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, + + // In the code, we make the assumption that these values + // are such that non_pawn_material() can be used to uniquely + // identify the material on the board. + PawnValue = 208, + KnightValue = 781, + BishopValue = 825, + RookValue = 1276, + QueenValue = 2538, }; +// clang-format off enum PieceType { - NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, - ALL_PIECES = 0, - PIECE_TYPE_NB = 8 + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + ALL_PIECES = 0, + PIECE_TYPE_NB = 8 }; enum Piece { - NO_PIECE, - W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, - PIECE_NB = 16 + NO_PIECE, + W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 }; +// clang-format on -constexpr Value PieceValue[PIECE_NB] = { VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, - VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO }; +constexpr Value PieceValue[PIECE_NB] = { + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO}; using Depth = int; enum : int { - DEPTH_QS_CHECKS = 0, - DEPTH_QS_NO_CHECKS = -1, - DEPTH_QS_RECAPTURES = -5, + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NO_CHECKS = -1, + DEPTH_QS_RECAPTURES = -5, - DEPTH_NONE = -6, + DEPTH_NONE = -6, - DEPTH_OFFSET = -7 // value used only for TT entry occupancy check + DEPTH_OFFSET = -7 // value used only for TT entry occupancy check }; +// clang-format off enum Square : int { - SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, - SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, - SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, - SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, - SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, - SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, - SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, - SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, - SQ_NONE, - - SQUARE_ZERO = 0, - SQUARE_NB = 64 + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, + + SQUARE_ZERO = 0, + SQUARE_NB = 64 }; +// clang-format on enum Direction : int { - NORTH = 8, - EAST = 1, - SOUTH = -NORTH, - WEST = -EAST, - - NORTH_EAST = NORTH + EAST, - SOUTH_EAST = SOUTH + EAST, - SOUTH_WEST = SOUTH + WEST, - NORTH_WEST = NORTH + WEST + NORTH = 8, + EAST = 1, + SOUTH = -NORTH, + WEST = -EAST, + + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST }; enum File : int { - FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB + FILE_A, + FILE_B, + FILE_C, + FILE_D, + FILE_E, + FILE_F, + FILE_G, + FILE_H, + FILE_NB }; enum Rank : int { - RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB + RANK_1, + RANK_2, + RANK_3, + RANK_4, + RANK_5, + RANK_6, + RANK_7, + RANK_8, + RANK_NB }; // Keep track of what a move changes on the board (used by NNUE) struct DirtyPiece { - // Number of changed pieces - int dirty_num; + // Number of changed pieces + int dirty_num; - // Max 3 pieces can change in one move. A promotion with capture moves - // both the pawn and the captured piece to SQ_NONE and the piece promoted - // to from SQ_NONE to the capture square. - Piece piece[3]; + // Max 3 pieces can change in one move. A promotion with capture moves + // both the pawn and the captured piece to SQ_NONE and the piece promoted + // to from SQ_NONE to the capture square. + Piece piece[3]; - // From and to squares, which may be SQ_NONE - Square from[3]; - Square to[3]; + // From and to squares, which may be SQ_NONE + Square from[3]; + Square to[3]; }; -#define ENABLE_BASE_OPERATORS_ON(T) \ -constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ -constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ -constexpr T operator-(T d) { return T(-int(d)); } \ -inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ -inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } - -#define ENABLE_INCR_OPERATORS_ON(T) \ -inline T& operator++(T& d) { return d = T(int(d) + 1); } \ -inline T& operator--(T& d) { return d = T(int(d) - 1); } - -#define ENABLE_FULL_OPERATORS_ON(T) \ -ENABLE_BASE_OPERATORS_ON(T) \ -constexpr T operator*(int i, T d) { return T(i * int(d)); } \ -constexpr T operator*(T d, int i) { return T(int(d) * i); } \ -constexpr T operator/(T d, int i) { return T(int(d) / i); } \ -constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ -inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ -inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } + #define ENABLE_BASE_OPERATORS_ON(T) \ + constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ + constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ + constexpr T operator-(T d) { return T(-int(d)); } \ + inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ + inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } + + #define ENABLE_INCR_OPERATORS_ON(T) \ + inline T& operator++(T& d) { return d = T(int(d) + 1); } \ + inline T& operator--(T& d) { return d = T(int(d) - 1); } + + #define ENABLE_FULL_OPERATORS_ON(T) \ + ENABLE_BASE_OPERATORS_ON(T) \ + constexpr T operator*(int i, T d) { return T(i * int(d)); } \ + constexpr T operator*(T d, int i) { return T(int(d) * i); } \ + constexpr T operator/(T d, int i) { return T(int(d) / i); } \ + constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ + inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ + inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(Direction) @@ -287,131 +311,97 @@ ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(Rank) -#undef ENABLE_FULL_OPERATORS_ON -#undef ENABLE_INCR_OPERATORS_ON -#undef ENABLE_BASE_OPERATORS_ON + #undef ENABLE_FULL_OPERATORS_ON + #undef ENABLE_INCR_OPERATORS_ON + #undef ENABLE_BASE_OPERATORS_ON // Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } -inline Square& operator+=(Square& s, Direction d) { return s = s + d; } -inline Square& operator-=(Square& s, Direction d) { return s = s - d; } +inline Square& operator+=(Square& s, Direction d) { return s = s + d; } +inline Square& operator-=(Square& s, Direction d) { return s = s - d; } constexpr Color operator~(Color c) { - return Color(c ^ BLACK); // Toggle color + return Color(c ^ BLACK); // Toggle color } -constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 - return Square(s ^ SQ_A8); +constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 + return Square(s ^ SQ_A8); } -constexpr Square flip_file(Square s) { // Swap A1 <-> H1 - return Square(s ^ SQ_H1); +constexpr Square flip_file(Square s) { // Swap A1 <-> H1 + return Square(s ^ SQ_H1); } constexpr Piece operator~(Piece pc) { - return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT + return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT } constexpr CastlingRights operator&(Color c, CastlingRights cr) { - return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); + return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); } -constexpr Value mate_in(int ply) { - return VALUE_MATE - ply; -} +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } -constexpr Value mated_in(int ply) { - return -VALUE_MATE + ply; -} +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } -constexpr Square make_square(File f, Rank r) { - return Square((r << 3) + f); -} +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } -constexpr Piece make_piece(Color c, PieceType pt) { - return Piece((c << 3) + pt); -} +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } -constexpr PieceType type_of(Piece pc) { - return PieceType(pc & 7); -} +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } inline Color color_of(Piece pc) { - assert(pc != NO_PIECE); - return Color(pc >> 3); + assert(pc != NO_PIECE); + return Color(pc >> 3); } -constexpr bool is_ok(Move m) { - return m != MOVE_NONE && m != MOVE_NULL; -} +constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; } -constexpr bool is_ok(Square s) { - return s >= SQ_A1 && s <= SQ_H8; -} +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } -constexpr File file_of(Square s) { - return File(s & 7); -} +constexpr File file_of(Square s) { return File(s & 7); } -constexpr Rank rank_of(Square s) { - return Rank(s >> 3); -} +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } -constexpr Square relative_square(Color c, Square s) { - return Square(s ^ (c * 56)); -} +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } -constexpr Rank relative_rank(Color c, Rank r) { - return Rank(r ^ (c * 7)); -} +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } -constexpr Rank relative_rank(Color c, Square s) { - return relative_rank(c, rank_of(s)); -} +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } -constexpr Direction pawn_push(Color c) { - return c == WHITE ? NORTH : SOUTH; -} +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } constexpr Square from_sq(Move m) { - assert(is_ok(m)); - return Square((m >> 6) & 0x3F); + assert(is_ok(m)); + return Square((m >> 6) & 0x3F); } constexpr Square to_sq(Move m) { - assert(is_ok(m)); - return Square(m & 0x3F); + assert(is_ok(m)); + return Square(m & 0x3F); } -constexpr int from_to(Move m) { - return m & 0xFFF; -} +constexpr int from_to(Move m) { return m & 0xFFF; } -constexpr MoveType type_of(Move m) { - return MoveType(m & (3 << 14)); -} +constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } -constexpr PieceType promotion_type(Move m) { - return PieceType(((m >> 12) & 3) + KNIGHT); -} +constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } -constexpr Move make_move(Square from, Square to) { - return Move((from << 6) + to); -} +constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); } template constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { - return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } // Based on a congruential pseudo-random number generator constexpr Key make_key(uint64_t seed) { - return seed * 6364136223846793005ULL + 1442695040888963407ULL; + return seed * 6364136223846793005ULL + 1442695040888963407ULL; } -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef TYPES_H_INCLUDED +#endif // #ifndef TYPES_H_INCLUDED -#include "tune.h" // Global visibility to tuning setup +#include "tune.h" // Global visibility to tuning setup diff --git a/src/uci.cpp b/src/uci.cpp index 81bf7aff768..0671cb5ff65 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -45,18 +45,18 @@ namespace Stockfish { namespace { - // FEN string for the initial position in standard chess - const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +// FEN string for the initial position in standard chess +const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - // position() is called when the engine receives the "position" UCI command. - // It sets up the position that is described in the given FEN string ("fen") or - // the initial position ("startpos") and then makes the moves given in the following - // move list ("moves"). +// position() is called when the engine receives the "position" UCI command. +// It sets up the position that is described in the given FEN string ("fen") or +// the initial position ("startpos") and then makes the moves given in the following +// move list ("moves"). - void position(Position& pos, std::istringstream& is, StateListPtr& states) { +void position(Position& pos, std::istringstream& is, StateListPtr& states) { - Move m; + Move m; std::string token, fen; is >> token; @@ -64,7 +64,7 @@ namespace { if (token == "startpos") { fen = StartFEN; - is >> token; // Consume the "moves" token, if any + is >> token; // Consume the "moves" token, if any } else if (token == "fen") while (is >> token && token != "moves") @@ -72,7 +72,7 @@ namespace { else return; - states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one + states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse the move list, if any @@ -81,33 +81,33 @@ namespace { states->emplace_back(); pos.do_move(m, states->back()); } - } +} - // trace_eval() prints the evaluation of the current position, consistent with - // the UCI options set so far. +// trace_eval() prints the evaluation of the current position, consistent with +// the UCI options set so far. - void trace_eval(Position& pos) { +void trace_eval(Position& pos) { StateListPtr states(new std::deque(1)); - Position p; + Position p; p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); Eval::NNUE::verify(); sync_cout << "\n" << Eval::trace(p) << sync_endl; - } +} - // setoption() is called when the engine receives the "setoption" UCI command. - // The function updates the UCI option ("name") to the given value ("value"). +// setoption() is called when the engine receives the "setoption" UCI command. +// The function updates the UCI option ("name") to the given value ("value"). - void setoption(std::istringstream& is) { +void setoption(std::istringstream& is) { Threads.main()->wait_for_search_finished(); std::string token, name, value; - is >> token; // Consume the "name" token + is >> token; // Consume the "name" token // Read the option name (can contain spaces) while (is >> token && token != "value") @@ -121,54 +121,67 @@ namespace { Options[name] = value; else sync_cout << "No such option: " << name << sync_endl; - } +} - // go() is called when the engine receives the "go" UCI command. The function - // sets the thinking time and other parameters from the input string, then starts - // with a search. +// go() is called when the engine receives the "go" UCI command. The function +// sets the thinking time and other parameters from the input string, then starts +// with a search. - void go(Position& pos, std::istringstream& is, StateListPtr& states) { +void go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits; - std::string token; - bool ponderMode = false; + std::string token; + bool ponderMode = false; - limits.startTime = now(); // The search starts as early as possible + limits.startTime = now(); // The search starts as early as possible while (is >> token) - if (token == "searchmoves") // Needs to be the last command on the line + if (token == "searchmoves") // Needs to be the last command on the line while (is >> token) limits.searchmoves.push_back(UCI::to_move(pos, token)); - else if (token == "wtime") is >> limits.time[WHITE]; - else if (token == "btime") is >> limits.time[BLACK]; - else if (token == "winc") is >> limits.inc[WHITE]; - else if (token == "binc") is >> limits.inc[BLACK]; - else if (token == "movestogo") is >> limits.movestogo; - else if (token == "depth") is >> limits.depth; - else if (token == "nodes") is >> limits.nodes; - else if (token == "movetime") is >> limits.movetime; - else if (token == "mate") is >> limits.mate; - else if (token == "perft") is >> limits.perft; - else if (token == "infinite") limits.infinite = 1; - else if (token == "ponder") ponderMode = true; + else if (token == "wtime") + is >> limits.time[WHITE]; + else if (token == "btime") + is >> limits.time[BLACK]; + else if (token == "winc") + is >> limits.inc[WHITE]; + else if (token == "binc") + is >> limits.inc[BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") + ponderMode = true; Threads.start_thinking(pos, states, limits, ponderMode); - } +} - // bench() is called when the engine receives the "bench" command. - // First, a list of UCI commands is set up according to the bench - // parameters, then it is run one by one, printing a summary at the end. +// bench() is called when the engine receives the "bench" command. +// First, a list of UCI commands is set up according to the bench +// parameters, then it is run one by one, printing a summary at the end. - void bench(Position& pos, std::istream& args, StateListPtr& states) { +void bench(Position& pos, std::istream& args, StateListPtr& states) { std::string token; - uint64_t num, nodes = 0, cnt = 1; + uint64_t num, nodes = 0, cnt = 1; std::vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + num = count_if(list.begin(), list.end(), + [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); TimePoint elapsed = now(); @@ -179,58 +192,64 @@ namespace { if (token == "go" || token == "eval") { - std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << std::endl; + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" + << std::endl; if (token == "go") { - go(pos, is, states); - Threads.main()->wait_for_search_finished(); - nodes += Threads.nodes_searched(); + go(pos, is, states); + Threads.main()->wait_for_search_finished(); + nodes += Threads.nodes_searched(); } else - trace_eval(pos); + trace_eval(pos); } - else if (token == "setoption") setoption(is); - else if (token == "position") position(pos, is, states); - else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take a while + else if (token == "setoption") + setoption(is); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + { + Search::clear(); + elapsed = now(); + } // Search::clear() may take a while } - elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' dbg_print(); std::cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes + << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; - } +} - // The win rate model returns the probability of winning (in per mille units) given an - // eval and a game ply. It fits the LTC fishtest statistics rather accurately. - int win_rate_model(Value v, int ply) { +// The win rate model returns the probability of winning (in per mille units) given an +// eval and a game ply. It fits the LTC fishtest statistics rather accurately. +int win_rate_model(Value v, int ply) { - // The model only captures up to 240 plies, so limit the input and then rescale - double m = std::min(240, ply) / 64.0; + // The model only captures up to 240 plies, so limit the input and then rescale + double m = std::min(240, ply) / 64.0; - // The coefficients of a third-order polynomial fit is based on the fishtest data - // for two parameters that need to transform eval to the argument of a logistic - // function. - constexpr double as[] = { 0.38036525, -2.82015070, 23.17882135, 307.36768407}; - constexpr double bs[] = { -2.29434733, 13.27689788, -14.26828904, 63.45318330 }; + // The coefficients of a third-order polynomial fit is based on the fishtest data + // for two parameters that need to transform eval to the argument of a logistic + // function. + constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407}; + constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330}; - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 - static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); + // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 + static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); - double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; - double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - // Transform the eval to centipawns with limited range - double x = std::clamp(double(v), -4000.0, 4000.0); + // Transform the eval to centipawns with limited range + double x = std::clamp(double(v), -4000.0, 4000.0); - // Return the win rate in per mille units, rounded to the nearest integer - return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); - } + // Return the win rate in per mille units, rounded to the nearest integer + return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); +} -} // namespace +} // namespace // UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate @@ -241,81 +260,91 @@ namespace { void UCI::loop(int argc, char* argv[]) { - Position pos; - std::string token, cmd; - StateListPtr states(new std::deque(1)); - - pos.set(StartFEN, false, &states->back(), Threads.main()); - - for (int i = 1; i < argc; ++i) - cmd += std::string(argv[i]) + " "; - - do { - if (argc == 1 && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication - cmd = "quit"; - - std::istringstream is(cmd); - - token.clear(); // Avoid a stale if getline() returns nothing or a blank line - is >> std::skipws >> token; - - if ( token == "quit" - || token == "stop") - Threads.stop = true; - - // The GUI sends 'ponderhit' to tell that the user has played the expected move. - // So, 'ponderhit' is sent if pondering was done on the same move that the user - // has played. The search should continue, but should also switch from pondering - // to the normal search. - else if (token == "ponderhit") - Threads.main()->ponder = false; // Switch to the normal search - - else if (token == "uci") - sync_cout << "id name " << engine_info(true) - << "\n" << Options - << "\nuciok" << sync_endl; - - else if (token == "setoption") setoption(is); - else if (token == "go") go(pos, is, states); - else if (token == "position") position(pos, is, states); - else if (token == "ucinewgame") Search::clear(); - else if (token == "isready") sync_cout << "readyok" << sync_endl; - - // Add custom non-UCI commands, mainly for debugging purposes. - // These commands must not be used during a search! - else if (token == "flip") pos.flip(); - else if (token == "bench") bench(pos, is, states); - else if (token == "d") sync_cout << pos << sync_endl; - else if (token == "eval") trace_eval(pos); - else if (token == "compiler") sync_cout << compiler_info() << sync_endl; - else if (token == "export_net") - { - std::optional filename; - std::string f; - if (is >> std::skipws >> f) - filename = f; - Eval::NNUE::save_eval(filename); - } - else if (token == "--help" || token == "help" || token == "--license" || token == "license") - sync_cout << "\nStockfish is a powerful chess engine for playing and analyzing." - "\nIt is released as free software licensed under the GNU GPLv3 License." - "\nStockfish is normally used with a graphical user interface (GUI) and implements" - "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." - "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" - "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" << sync_endl; - else if (!token.empty() && token[0] != '#') - sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl; - - } while (token != "quit" && argc == 1); // The command-line arguments are one-shot + Position pos; + std::string token, cmd; + StateListPtr states(new std::deque(1)); + + pos.set(StartFEN, false, &states->back(), Threads.main()); + + for (int i = 1; i < argc; ++i) + cmd += std::string(argv[i]) + " "; + + do + { + if (argc == 1 + && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + cmd = "quit"; + + std::istringstream is(cmd); + + token.clear(); // Avoid a stale if getline() returns nothing or a blank line + is >> std::skipws >> token; + + if (token == "quit" || token == "stop") + Threads.stop = true; + + // The GUI sends 'ponderhit' to tell that the user has played the expected move. + // So, 'ponderhit' is sent if pondering was done on the same move that the user + // has played. The search should continue, but should also switch from pondering + // to the normal search. + else if (token == "ponderhit") + Threads.main()->ponder = false; // Switch to the normal search + + else if (token == "uci") + sync_cout << "id name " << engine_info(true) << "\n" + << Options << "\nuciok" << sync_endl; + + else if (token == "setoption") + setoption(is); + else if (token == "go") + go(pos, is, states); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + Search::clear(); + else if (token == "isready") + sync_cout << "readyok" << sync_endl; + + // Add custom non-UCI commands, mainly for debugging purposes. + // These commands must not be used during a search! + else if (token == "flip") + pos.flip(); + else if (token == "bench") + bench(pos, is, states); + else if (token == "d") + sync_cout << pos << sync_endl; + else if (token == "eval") + trace_eval(pos); + else if (token == "compiler") + sync_cout << compiler_info() << sync_endl; + else if (token == "export_net") + { + std::optional filename; + std::string f; + if (is >> std::skipws >> f) + filename = f; + Eval::NNUE::save_eval(filename); + } + else if (token == "--help" || token == "help" || token == "--license" || token == "license") + sync_cout + << "\nStockfish is a powerful chess engine for playing and analyzing." + "\nIt is released as free software licensed under the GNU GPLv3 License." + "\nStockfish is normally used with a graphical user interface (GUI) and implements" + "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." + "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" + "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" + << sync_endl; + else if (!token.empty() && token[0] != '#') + sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." + << sync_endl; + + } while (token != "quit" && argc == 1); // The command-line arguments are one-shot } // Turns a Value to an integer centipawn number, // without treatment of mate and similar special scores. -int UCI::to_cp(Value v) { - - return 100 * v / UCI::NormalizeToPawnValue; -} +int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } // UCI::value() converts a Value to a string by adhering to the UCI protocol specification: // @@ -325,21 +354,21 @@ int UCI::to_cp(Value v) { std::string UCI::value(Value v) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - std::stringstream ss; + std::stringstream ss; - if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << UCI::to_cp(v); - else if (abs(v) < VALUE_MATE_IN_MAX_PLY) - { - const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply - ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); - } - else - ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; + if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + ss << "cp " << UCI::to_cp(v); + else if (abs(v) < VALUE_MATE_IN_MAX_PLY) + { + const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply + ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); + } + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - return ss.str(); + return ss.str(); } @@ -348,21 +377,21 @@ std::string UCI::value(Value v) { std::string UCI::wdl(Value v, int ply) { - std::stringstream ss; + std::stringstream ss; - int wdl_w = win_rate_model( v, ply); - int wdl_l = win_rate_model(-v, ply); - int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + int wdl_w = win_rate_model(v, ply); + int wdl_l = win_rate_model(-v, ply); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; - return ss.str(); + return ss.str(); } // UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { - return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; + return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } @@ -373,24 +402,24 @@ std::string UCI::square(Square s) { std::string UCI::move(Move m, bool chess960) { - if (m == MOVE_NONE) - return "(none)"; + if (m == MOVE_NONE) + return "(none)"; - if (m == MOVE_NULL) - return "0000"; + if (m == MOVE_NULL) + return "0000"; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = from_sq(m); + Square to = to_sq(m); - if (type_of(m) == CASTLING && !chess960) - to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); + if (type_of(m) == CASTLING && !chess960) + to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - std::string move = UCI::square(from) + UCI::square(to); + std::string move = UCI::square(from) + UCI::square(to); - if (type_of(m) == PROMOTION) - move += " pnbrqk"[promotion_type(m)]; + if (type_of(m) == PROMOTION) + move += " pnbrqk"[promotion_type(m)]; - return move; + return move; } @@ -399,14 +428,14 @@ std::string UCI::move(Move m, bool chess960) { Move UCI::to_move(const Position& pos, std::string& str) { - if (str.length() == 5) - str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased + if (str.length() == 5) + str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased - for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) - return m; + for (const auto& m : MoveList(pos)) + if (str == UCI::move(m, pos.is_chess960())) + return m; - return MOVE_NONE; + return MOVE_NONE; } -} // namespace Stockfish +} // namespace Stockfish diff --git a/src/uci.h b/src/uci.h index 048f8c1166e..be5c70c54ce 100644 --- a/src/uci.h +++ b/src/uci.h @@ -43,7 +43,7 @@ class Option; // Define a custom comparator, because the UCI options should be case-insensitive struct CaseInsensitiveLess { - bool operator() (const std::string&, const std::string&) const; + bool operator()(const std::string&, const std::string&) const; }; // The options container is defined as a std::map @@ -52,44 +52,44 @@ using OptionsMap = std::map; // The Option class implements each option as specified by the UCI protocol class Option { - using OnChange = void (*)(const Option&); + using OnChange = void (*)(const Option&); -public: - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char* v, OnChange = nullptr); - Option(double v, int minv, int maxv, OnChange = nullptr); - Option(const char* v, const char* cur, OnChange = nullptr); + public: + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); - Option& operator=(const std::string&); - void operator<<(const Option&); - operator int() const; - operator std::string() const; - bool operator==(const char*) const; + Option& operator=(const std::string&); + void operator<<(const Option&); + operator int() const; + operator std::string() const; + bool operator==(const char*) const; -private: - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + private: + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; }; -void init(OptionsMap&); -void loop(int argc, char* argv[]); -int to_cp(Value v); +void init(OptionsMap&); +void loop(int argc, char* argv[]); +int to_cp(Value v); std::string value(Value v); std::string square(Square s); std::string move(Move m, bool chess960); std::string pv(const Position& pos, Depth depth); std::string wdl(Value v, int ply); -Move to_move(const Position& pos, std::string& str); +Move to_move(const Position& pos, std::string& str); -} // namespace UCI +} // namespace UCI extern UCI::OptionsMap Options; -} // namespace Stockfish +} // namespace Stockfish -#endif // #ifndef UCI_H_INCLUDED +#endif // #ifndef UCI_H_INCLUDED diff --git a/src/ucioption.cpp b/src/ucioption.cpp index b822ccf936f..8db4233af5f 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -40,7 +40,7 @@ using std::string; namespace Stockfish { -UCI::OptionsMap Options; // Global object +UCI::OptionsMap Options; // Global object namespace UCI { @@ -53,10 +53,10 @@ static void on_tb_path(const Option& o) { Tablebases::init(o); } static void on_eval_file(const Option&) { Eval::NNUE::init(); } // Our case insensitive less() function as required by UCI protocol -bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { +bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const { - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return tolower(c1) < tolower(c2); }); + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return tolower(c1) < tolower(c2); }); } @@ -64,28 +64,28 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const void init(OptionsMap& o) { - constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; - - o["Debug Log File"] << Option("", on_logger); - o["Threads"] << Option(1, 1, 1024, on_threads); - o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); - o["Clear Hash"] << Option(on_clear_hash); - o["Ponder"] << Option(false); - o["MultiPV"] << Option(1, 1, 500); - o["Skill Level"] << Option(20, 0, 20); - o["Move Overhead"] << Option(10, 0, 5000); - o["Slow Mover"] << Option(100, 10, 1000); - o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); - o["UCI_AnalyseMode"] << Option(false); - o["UCI_LimitStrength"] << Option(false); - o["UCI_Elo"] << Option(1320, 1320, 3190); - o["UCI_ShowWDL"] << Option(false); - o["SyzygyPath"] << Option("", on_tb_path); - o["SyzygyProbeDepth"] << Option(1, 1, 100); - o["Syzygy50MoveRule"] << Option(true); - o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); + constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + + o["Debug Log File"] << Option("", on_logger); + o["Threads"] << Option(1, 1, 1024, on_threads); + o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); + o["Clear Hash"] << Option(on_clear_hash); + o["Ponder"] << Option(false); + o["MultiPV"] << Option(1, 1, 500); + o["Skill Level"] << Option(20, 0, 20); + o["Move Overhead"] << Option(10, 0, 5000); + o["Slow Mover"] << Option(100, 10, 1000); + o["nodestime"] << Option(0, 0, 10000); + o["UCI_Chess960"] << Option(false); + o["UCI_AnalyseMode"] << Option(false); + o["UCI_LimitStrength"] << Option(false); + o["UCI_Elo"] << Option(1320, 1320, 3190); + o["UCI_ShowWDL"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); + o["SyzygyProbeDepth"] << Option(1, 1, 100); + o["Syzygy50MoveRule"] << Option(true); + o["SyzygyProbeLimit"] << Option(7, 0, 7); + o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); } @@ -94,59 +94,81 @@ void init(OptionsMap& o) { std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { - for (size_t idx = 0; idx < om.size(); ++idx) - for (const auto& it : om) - if (it.second.idx == idx) - { - const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; + for (size_t idx = 0; idx < om.size(); ++idx) + for (const auto& it : om) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; - if (o.type == "string" || o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; + if (o.type == "string" || o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; - if (o.type == "spin") - os << " default " << int(stof(o.defaultValue)) - << " min " << o.min - << " max " << o.max; + if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + << o.max; - break; - } + break; + } - return os; + return os; } // Option class constructors and conversion operators -Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) -{ defaultValue = currentValue = v; } - -Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f) -{ defaultValue = currentValue = (v ? "true" : "false"); } +Option::Option(const char* v, OnChange f) : + type("string"), + min(0), + max(0), + on_change(f) { + defaultValue = currentValue = v; +} -Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) -{} +Option::Option(bool v, OnChange f) : + type("check"), + min(0), + max(0), + on_change(f) { + defaultValue = currentValue = (v ? "true" : "false"); +} -Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) -{ defaultValue = currentValue = std::to_string(v); } +Option::Option(OnChange f) : + type("button"), + min(0), + max(0), + on_change(f) {} + +Option::Option(double v, int minv, int maxv, OnChange f) : + type("spin"), + min(minv), + max(maxv), + on_change(f) { + defaultValue = currentValue = std::to_string(v); +} -Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) -{ defaultValue = v; currentValue = cur; } +Option::Option(const char* v, const char* cur, OnChange f) : + type("combo"), + min(0), + max(0), + on_change(f) { + defaultValue = v; + currentValue = cur; +} Option::operator int() const { - assert(type == "check" || type == "spin"); - return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { - assert(type == "string"); - return currentValue; + assert(type == "string"); + return currentValue; } bool Option::operator==(const char* s) const { - assert(type == "combo"); - return !CaseInsensitiveLess()(currentValue, s) - && !CaseInsensitiveLess()(s, currentValue); + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); } @@ -154,10 +176,10 @@ bool Option::operator==(const char* s) const { void Option::operator<<(const Option& o) { - static size_t insert_order = 0; + static size_t insert_order = 0; - *this = o; - idx = insert_order++; + *this = o; + idx = insert_order++; } @@ -167,33 +189,33 @@ void Option::operator<<(const Option& o) { Option& Option::operator=(const string& v) { - assert(!type.empty()); + assert(!type.empty()); - if ( (type != "button" && type != "string" && v.empty()) - || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (stof(v) < min || stof(v) > max))) - return *this; + if ((type != "button" && type != "string" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "spin" && (stof(v) < min || stof(v) > max))) + return *this; - if (type == "combo") - { - OptionsMap comboMap; // To have case insensitive compare - string token; - std::istringstream ss(defaultValue); - while (ss >> token) - comboMap[token] << Option(); - if (!comboMap.count(v) || v == "var") - return *this; - } + if (type == "combo") + { + OptionsMap comboMap; // To have case insensitive compare + string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap[token] << Option(); + if (!comboMap.count(v) || v == "var") + return *this; + } - if (type != "button") - currentValue = v; + if (type != "button") + currentValue = v; - if (on_change) - on_change(*this); + if (on_change) + on_change(*this); - return *this; + return *this; } -} // namespace UCI +} // namespace UCI -} // namespace Stockfish +} // namespace Stockfish From b7b7800e2b752c93131e03c31d7456f18b392a7c Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Sun, 22 Oct 2023 08:36:43 +0200 Subject: [PATCH 0253/1309] Simplify futilityBase formula This patch replaces std::min(ss->staticEval, bestValue) with ss->staticEval in the futilityBase formula. Original idea by Vizvezdenec: https://tests.stockfishchess.org/tests/view/64ce66795b17f7c21c0d85f3 Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 116928 W: 29925 L: 29793 D: 57210 Ptnml(0-2): 399, 13558, 30446, 13634, 427 https://tests.stockfishchess.org/tests/view/653285aade6d262d08d385dd Passed LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 50868 W: 12947 L: 12757 D: 25164 Ptnml(0-2): 30, 5414, 14355, 5606, 29 https://tests.stockfishchess.org/tests/view/65336ffbde6d262d08d39ba0 closes https://github.com/official-stockfish/Stockfish/pull/4837 bench: 1241996 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 43f0c8726e3..43d78892f60 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1451,7 +1451,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (bestValue > alpha) alpha = bestValue; - futilityBase = std::min(ss->staticEval, bestValue) + 200; + futilityBase = ss->staticEval + 200; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, From 40c6a84434ea4600b5ebdd1020b34516317be1a9 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 18 Oct 2023 04:03:39 +0900 Subject: [PATCH 0254/1309] Fix a compiler bug on Clang 15+ with AVX-512 fixes https://github.com/official-stockfish/Stockfish/issues/4450 closes https://github.com/official-stockfish/Stockfish/pull/4830 No functional change. --- src/syzygy/tbprobe.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index c8e60ab6c02..31597f835a8 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -690,6 +690,17 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { return value + 1; } +// A temporary fix for the compiler bug with AVX-512. (#4450) +#ifdef USE_AVX512 + #if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15 + #define CLANG_AVX512_BUG_FIX __attribute__((optnone)) + #endif +#endif + +#ifndef CLANG_AVX512_BUG_FIX + #define CLANG_AVX512_BUG_FIX +#endif + // Compute a unique index out of a position and use it to probe the TB file. To // encode k pieces of the same type and color, first sort the pieces by square in // ascending order s1 <= s2 <= ... <= sk then compute the unique index as: @@ -697,7 +708,8 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] // template -Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { +CLANG_AVX512_BUG_FIX Ret +do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { Square squares[TBPIECES]; Piece pieces[TBPIECES]; From b1876222335df6581777baadc68fb5b17e5fe656 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 22 Oct 2023 16:43:33 +0200 Subject: [PATCH 0255/1309] use expanded variables for shell commands Performance improvement for the shell commands in the Makefile. By using expanded variables, the shell commands are only evaluated once, instead of every time they are used. closes https://github.com/official-stockfish/Stockfish/pull/4838 No functional change --- src/Makefile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Makefile b/src/Makefile index 7b7ee41b654..76ef6fdeb8d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,9 +20,9 @@ ### ========================================================================== ### Establish the operating system name -KERNEL = $(shell uname -s) +KERNEL := $(shell uname -s) ifeq ($(KERNEL),Linux) - OS = $(shell uname -o) + OS := $(shell uname -o) endif ### Target Windows OS @@ -33,7 +33,7 @@ ifeq ($(OS),Windows_NT) else ifeq ($(COMP),mingw) target_windows = yes ifeq ($(WINE_PATH),) - WINE_PATH = $(shell which wine) + WINE_PATH := $(shell which wine) endif endif @@ -116,7 +116,7 @@ ifeq ($(ARCH),) endif ifeq ($(ARCH), native) - override ARCH = $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) + override ARCH := $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) endif # explicitly check for the list of supported architectures (as listed with make help), @@ -542,8 +542,8 @@ endif ### Sometimes gcc is really clang ifeq ($(COMP),gcc) - gccversion = $(shell $(CXX) --version 2>/dev/null) - gccisclang = $(findstring clang,$(gccversion)) + gccversion := $(shell $(CXX) --version 2>/dev/null) + gccisclang := $(findstring clang,$(gccversion)) ifneq ($(gccisclang),) profile_make = clang-profile-make profile_use = clang-profile-use @@ -601,7 +601,7 @@ ifeq ($(optimize),yes) endif ifeq ($(comp),clang) - clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + clangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) ifeq ($(shell expr $(clangmajorversion) \< 16),1) CXXFLAGS += -fexperimental-new-pass-manager endif @@ -717,13 +717,13 @@ ifeq ($(pext),yes) endif ### 3.8.1 Try to include git commit sha for versioning -GIT_SHA = $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) +GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) ifneq ($(GIT_SHA), ) CXXFLAGS += -DGIT_SHA=$(GIT_SHA) endif ### 3.8.2 Try to include git commit date for versioning -GIT_DATE = $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) +GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) ifneq ($(GIT_DATE), ) CXXFLAGS += -DGIT_DATE=$(GIT_DATE) endif From a105978bbde04508389abad03bd121f817f91646 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 22 Oct 2023 20:20:53 +0200 Subject: [PATCH 0256/1309] remove blank line between function and it's description - remove the blank line between the declaration of the function and it's comment, leads to better IDE support when hovering over a function to see it's description - remove the unnecessary duplication of the function name in the functions description - slightly refactored code for lsb, msb in bitboard.h There are still a few things we can be improved later on, move the description of a function where it was declared (instead of implemented) and add descriptions to functions which are behind macros ifdefs closes https://github.com/official-stockfish/Stockfish/pull/4840 No functional change --- src/benchmark.cpp | 3 +- src/bitboard.cpp | 12 ++-- src/bitboard.h | 90 +++++++++++++---------------- src/evaluate.cpp | 14 ++--- src/misc.cpp | 19 ++---- src/misc.h | 25 ++++---- src/movegen.cpp | 1 - src/movepick.cpp | 12 ++-- src/nnue/evaluate_nnue.cpp | 6 +- src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/nnue_common.h | 12 ++-- src/position.cpp | 75 +++++++++--------------- src/search.cpp | 47 +++++---------- src/search.h | 8 +-- src/syzygy/tbprobe.cpp | 2 +- src/thread.cpp | 26 +++------ src/thread.h | 3 - src/timeman.cpp | 3 +- src/timeman.h | 1 - src/tt.cpp | 16 ++--- src/tt.h | 2 - src/types.h | 20 +++---- src/uci.cpp | 30 ++++------ src/ucioption.cpp | 11 ++-- 24 files changed, 172 insertions(+), 268 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 63598e750e8..2270dcc3c83 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -97,7 +97,7 @@ const std::vector Defaults = { namespace Stockfish { -// setup_bench() builds a list of UCI commands to be run by bench. There +// Builds a list of UCI commands to be run by bench. There // are five parameters: TT size in MB, number of search threads that // should be used, the limit value spent for each position, a file name // where to look for positions in FEN format, and the type of the limit: @@ -108,7 +108,6 @@ namespace Stockfish { // bench 64 1 100000 default nodes : search default positions for 100K nodes each // bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec // bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" - std::vector setup_bench(const Position& current, std::istream& is) { std::vector fens, list; diff --git a/src/bitboard.cpp b/src/bitboard.cpp index fff7eba9e6f..a8a10cbb874 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -46,18 +46,16 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]); } -// safe_destination() returns the bitboard of target square for the given step +// Returns the bitboard of target square for the given step // from the given square. If the step is off the board, returns empty bitboard. - inline Bitboard safe_destination(Square s, int step) { Square to = Square(s + step); return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); } -// Bitboards::pretty() returns an ASCII representation of a bitboard suitable +// Returns an ASCII representation of a bitboard suitable // to be printed to standard output. Useful for debugging. - std::string Bitboards::pretty(Bitboard b) { std::string s = "+---+---+---+---+---+---+---+---+\n"; @@ -75,9 +73,8 @@ std::string Bitboards::pretty(Bitboard b) { } -// Bitboards::init() initializes various bitboard tables. It is called at +// Initializes various bitboard tables. It is called at // startup and relies on global objects to be already zero-initialized. - void Bitboards::init() { for (unsigned i = 0; i < (1 << 16); ++i) @@ -137,11 +134,10 @@ Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { } -// init_magics() computes all rook and bishop attacks at startup. Magic +// Computes all rook and bishop attacks at startup. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so // called "fancy" approach. - void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { // Optimal PRNG seeds to pick the correct magics in the shortest time diff --git a/src/bitboard.h b/src/bitboard.h index 03a511361f4..24f6deca840 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -125,8 +125,7 @@ constexpr Bitboard file_bb(File f) { return FileABB << f; } constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); } -// shift() moves a bitboard one or two steps as specified by the direction D - +// Moves a bitboard one or two steps as specified by the direction D template constexpr Bitboard shift(Bitboard b) { return D == NORTH ? b << 8 @@ -143,9 +142,8 @@ constexpr Bitboard shift(Bitboard b) { } -// pawn_attacks_bb() returns the squares attacked by pawns of the given color +// Returns the squares attacked by pawns of the given color // from the squares in the given bitboard. - template constexpr Bitboard pawn_attacks_bb(Bitboard b) { return C == WHITE ? shift(b) | shift(b) @@ -158,11 +156,10 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { return PawnAttacks[c][s]; } -// line_bb() returns a bitboard representing an entire line (from board edge +// Returns a bitboard representing an entire line (from board edge // to board edge) that intersects the two given squares. If the given squares // are not on a same file/rank/diagonal, the function returns 0. For instance, // line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. - inline Bitboard line_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); @@ -171,14 +168,13 @@ inline Bitboard line_bb(Square s1, Square s2) { } -// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open +// Returns a bitboard representing the squares in the semi-open // segment between the squares s1 and s2 (excluding s1 but including s2). If the // given squares are not on a same file/rank/diagonal, it returns s2. For instance, // between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but // between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick // allows to generate non-king evasion moves faster: the defending piece must either // interpose itself to cover the check or capture the checking piece. - inline Bitboard between_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); @@ -186,9 +182,8 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } -// aligned() returns true if the squares s1, s2 and s3 are aligned either on a +// Returns true if the squares s1, s2 and s3 are aligned either on a // straight or on a diagonal line. - inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } @@ -197,14 +192,17 @@ inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & template inline int distance(Square x, Square y); + template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } + template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } + template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; @@ -212,9 +210,8 @@ inline int distance(Square x, Square y) { inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -// attacks_bb(Square) returns the pseudo attacks of the given piece type +// Returns the pseudo attacks of the given piece type // assuming an empty board. - template inline Bitboard attacks_bb(Square s) { @@ -224,10 +221,9 @@ inline Bitboard attacks_bb(Square s) { } -// attacks_bb(Square, Bitboard) returns the attacks by the given piece +// Returns the attacks by the given piece // assuming the board is occupied according to the passed Bitboard. // Sliding piece attacks do not continue passed an occupied square. - template inline Bitboard attacks_bb(Square s, Bitboard occupied) { @@ -246,6 +242,9 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) { } } +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { assert((pt != PAWN) && (is_ok(s))); @@ -264,8 +263,7 @@ inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { } -// popcount() counts the number of non-zero bits in a bitboard - +// Counts the number of non-zero bits in a bitboard. inline int popcount(Bitboard b) { #ifndef USE_POPCNT @@ -287,43 +285,22 @@ inline int popcount(Bitboard b) { #endif } - -// lsb() and msb() return the least/most significant bit in a non-zero bitboard - -#if defined(__GNUC__) // GCC, Clang, ICX - +// Returns the least significant bit in a non-zero bitboard. inline Square lsb(Bitboard b) { assert(b); - return Square(__builtin_ctzll(b)); -} -inline Square msb(Bitboard b) { - assert(b); - return Square(63 ^ __builtin_clzll(b)); -} +#if defined(__GNUC__) // GCC, Clang, ICX -#elif defined(_MSC_VER) // MSVC + return Square(__builtin_ctzll(b)); +#elif defined(_MSC_VER) #ifdef _WIN64 // MSVC, WIN64 -inline Square lsb(Bitboard b) { - assert(b); unsigned long idx; _BitScanForward64(&idx, b); return (Square) idx; -} - -inline Square msb(Bitboard b) { - assert(b); - unsigned long idx; - _BitScanReverse64(&idx, b); - return (Square) idx; -} #else // MSVC, WIN32 - -inline Square lsb(Bitboard b) { - assert(b); unsigned long idx; if (b & 0xffffffff) @@ -336,10 +313,29 @@ inline Square lsb(Bitboard b) { _BitScanForward(&idx, int32_t(b >> 32)); return Square(idx + 32); } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif } +// Returns the most significant bit in a non-zero bitboard. inline Square msb(Bitboard b) { assert(b); + +#if defined(__GNUC__) // GCC, Clang, ICX + + return Square(63 ^ __builtin_clzll(b)); + +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 + + unsigned long idx; + _BitScanReverse64(&idx, b); + return (Square) idx; + + #else // MSVC, WIN32 + unsigned long idx; if (b >> 32) @@ -352,26 +348,20 @@ inline Square msb(Bitboard b) { _BitScanReverse(&idx, int32_t(b)); return Square(idx); } -} - #endif - #else // Compiler is neither GCC nor MSVC compatible - #error "Compiler not supported." - #endif +} -// least_significant_square_bb() returns the bitboard of the least significant +// Returns the bitboard of the least significant // square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). - inline Bitboard least_significant_square_bb(Bitboard b) { assert(b); return b & -b; } -// pop_lsb() finds and clears the least significant bit in a non-zero bitboard - +// Finds and clears the least significant bit in a non-zero bitboard. inline Square pop_lsb(Bitboard& b) { assert(b); const Square s = lsb(b); diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 00498bf02f0..4ee3e6fd8b4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -57,14 +57,13 @@ namespace Eval { std::string currentEvalFileName = "None"; -// NNUE::init() tries to load a NNUE network at startup time, or when the engine +// Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" // The name of the NNUE network is always retrieved from the EvalFile option. // We search the given network in three locations: internally (the default // network may be embedded in the binary), in the active working directory and // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY // variable to have the engine search in a special directory in their distro. - void NNUE::init() { std::string eval_file = std::string(Options["EvalFile"]); @@ -111,7 +110,7 @@ void NNUE::init() { } } -// NNUE::verify() verifies that the last net used was loaded successfully +// Verifies that the last net used was loaded successfully void NNUE::verify() { std::string eval_file = std::string(Options["EvalFile"]); @@ -145,19 +144,17 @@ void NNUE::verify() { } -// simple_eval() returns a static, purely materialistic evaluation of the position +// Returns a static, purely materialistic evaluation of the position // from the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. - Value Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } -// evaluate() is the evaluator for the outer world. It returns a static evaluation +// Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. - Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); @@ -197,11 +194,10 @@ Value Eval::evaluate(const Position& pos) { return v; } -// trace() is like evaluate(), but instead of returning a value, it returns +// Like evaluate(), but instead of returning a value, it returns // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view - std::string Eval::trace(Position& pos) { if (pos.checkers()) diff --git a/src/misc.cpp b/src/misc.cpp index 05181325ece..3e9006156c6 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -148,7 +148,7 @@ class Logger { } // namespace -// engine_info() returns the full name of the current Stockfish version. +// Returns the full name of the current Stockfish version. // For local dev compiles we try to append the commit sha and commit date // from git if that fails only the local compilation date is set and "nogit" is specified: // Stockfish dev-YYYYMMDD-SHA @@ -157,7 +157,6 @@ class Logger { // // For releases (non-dev builds) we only include the version number: // Stockfish version - std::string engine_info(bool to_uci) { std::stringstream ss; ss << "Stockfish " << version << std::setfill('0'); @@ -192,8 +191,7 @@ std::string engine_info(bool to_uci) { } -// compiler_info() returns a string trying to describe the compiler we use - +// Returns a string trying to describe the compiler we use std::string compiler_info() { #define make_version_string(major, minor, patch) \ @@ -397,7 +395,6 @@ void dbg_print() { // Used to serialize access to std::cout to avoid multiple threads writing at // the same time. - std::ostream& operator<<(std::ostream& os, SyncCout sc) { static std::mutex m; @@ -416,9 +413,6 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) { void start_logger(const std::string& fname) { Logger::start(fname); } -// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking -// function that doesn't stall the CPU waiting for data to be loaded from memory, -// which can be quite slow. #ifdef NO_PREFETCH void prefetch(void*) {} @@ -437,10 +431,9 @@ void prefetch(void* addr) { #endif -// std_aligned_alloc() is our wrapper for systems where the c++17 implementation +// Wrapper for systems where the c++17 implementation // does not guarantee the availability of aligned_alloc(). Memory allocated with // std_aligned_alloc() must be freed with std_aligned_free(). - void* std_aligned_alloc(size_t alignment, size_t size) { #if defined(POSIXALIGNEDALLOC) @@ -607,10 +600,9 @@ void bindThisThread(size_t) {} #else -// best_node() retrieves logical processor information using Windows specific +// Retrieves logical processor information using Windows specific // API and returns the best node id for the thread with index idx. Original // code from Texel by Peter Österlund. - static int best_node(size_t idx) { int threads = 0; @@ -679,8 +671,7 @@ static int best_node(size_t idx) { } -// bindThisThread() sets the group affinity of the current thread - +// Sets the group affinity of the current thread void bindThisThread(size_t idx) { // Use only local variables to be thread-safe diff --git a/src/misc.h b/src/misc.h index 3cd3315a8ed..91fdb72f1b1 100644 --- a/src/misc.h +++ b/src/misc.h @@ -33,13 +33,19 @@ namespace Stockfish { std::string engine_info(bool to_uci = false); std::string compiler_info(); -void prefetch(void* addr); -void start_logger(const std::string& fname); -void* std_aligned_alloc(size_t alignment, size_t size); -void std_aligned_free(void* ptr); -void* aligned_large_pages_alloc( - size_t size); // memory aligned by page size, min alignment: 4096 bytes -void aligned_large_pages_free(void* mem); // nop if mem == nullptr + +// Preloads the given address in L1/L2 cache. This is a non-blocking +// function that doesn't stall the CPU waiting for data to be loaded from memory, +// which can be quite slow. +void prefetch(void* addr); + +void start_logger(const std::string& fname); +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); +// memory aligned by page size, min alignment: 4096 bytes +void* aligned_large_pages_alloc(size_t size); +// nop if mem == nullptr +void aligned_large_pages_free(void* mem); void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); @@ -66,7 +72,7 @@ std::ostream& operator<<(std::ostream&, SyncCout); #define sync_endl std::endl << IO_UNLOCK -// align_ptr_up() : get the first aligned element of an array. +// Get the first aligned element of an array. // ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, // where N is the number of elements in the array. template @@ -79,7 +85,7 @@ T* align_ptr_up(T* ptr) { } -// IsLittleEndian : true if and only if the binary is compiled on a little-endian machine +// True if and only if the binary is compiled on a little-endian machine static inline const union { uint32_t i; char c[4]; @@ -166,7 +172,6 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { // cores. To overcome this, some special platform-specific API should be // called to set group affinity for each thread. Original code from Texel by // Peter Österlund. - namespace WinProcGroup { void bindThisThread(size_t idx); } diff --git a/src/movegen.cpp b/src/movegen.cpp index cf457d1176c..16da659d5e3 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -241,7 +241,6 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { // except castling and promotions // // Returns a pointer to the end of the move list. - template ExtMove* generate(const Position& pos, ExtMove* moveList) { diff --git a/src/movepick.cpp b/src/movepick.cpp index 41ad0dd6e8d..ff282262a5b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -58,7 +58,7 @@ enum Stages { QCHECK }; -// partial_insertion_sort() sorts moves in descending order up to and including +// Sort moves in descending order up to and including // a given limit. The order of moves smaller than the limit is left unspecified. void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { @@ -103,7 +103,7 @@ MovePicker::MovePicker(const Position& p, stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); } -// MovePicker constructor for quiescence search +// Constructor for quiescence search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, @@ -123,7 +123,7 @@ MovePicker::MovePicker(const Position& p, stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } -// MovePicker constructor for ProbCut: we generate captures with SEE greater +// Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : pos(p), @@ -136,7 +136,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePiece + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } -// MovePicker::score() assigns a numerical value to each move in a list, used +// Assigns a numerical value to each move in a list, used // for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring // captures with a good history. Quiets moves are ordered using the history tables. template @@ -216,7 +216,7 @@ void MovePicker::score() { } } -// MovePicker::select() returns the next move satisfying a predicate function. +// Returns the next move satisfying a predicate function. // It never returns the TT move. template Move MovePicker::select(Pred filter) { @@ -234,7 +234,7 @@ Move MovePicker::select(Pred filter) { return MOVE_NONE; } -// MovePicker::next_move() is the most important method of the MovePicker class. It +// Most important method of the MovePicker class. It // returns a new pseudo-legal move every time it is called until there are no more // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 679192d4710..ea53a5102fa 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -233,7 +233,7 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer. +// Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. static void format_cp_compact(Value v, char* buffer) { @@ -270,7 +270,7 @@ static void format_cp_compact(Value v, char* buffer) { } -// format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals +// Converts a Value into pawns, always keeping two decimals static void format_cp_aligned_dot(Value v, std::stringstream& stream) { const double pawns = std::abs(0.01 * UCI::to_cp(v)); @@ -282,7 +282,7 @@ static void format_cp_aligned_dot(Value v, std::stringstream& stream) { } -// trace() returns a string with the value of each piece on a board, +// Returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. std::string trace(Position& pos) { diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 6c3fdfdb60b..6d1b60ce43d 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -50,7 +50,7 @@ void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -// append_changed_indices() : get a list of indices for recently changed features +// Get a list of indices for recently changed features template void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f4c55e001e2..cf90850126b 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -85,7 +85,7 @@ constexpr IntType ceil_to_multiple(IntType n, IntType base) { } -// read_little_endian() is our utility to read an integer (signed or unsigned, any size) +// Utility to read an integer (signed or unsigned, any size) // from a stream in little-endian order. We swap the byte order after the read if // necessary to return a result with the byte ordering of the compiling machine. template @@ -110,7 +110,7 @@ inline IntType read_little_endian(std::istream& stream) { } -// write_little_endian() is our utility to write an integer (signed or unsigned, any size) +// Utility to write an integer (signed or unsigned, any size) // to a stream in little-endian order. We swap the byte order before the write if // necessary to always write in little endian order, independently of the byte // ordering of the compiling machine. @@ -141,7 +141,7 @@ inline void write_little_endian(std::ostream& stream, IntType value) { } -// read_little_endian(s, out, N) : read integers in bulk from a little indian stream. +// Read integers in bulk from a little indian stream. // This reads N integers from stream s and put them in array out. template inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { @@ -153,7 +153,7 @@ inline void read_little_endian(std::istream& stream, IntType* out, std::size_t c } -// write_little_endian(s, values, N) : write integers in bulk to a little indian stream. +// Write integers in bulk to a little indian stream. // This takes N integers from array values and writes them on stream s. template inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { @@ -165,7 +165,7 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std } -// read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in +// Read N signed integers from the stream s, putting them in // the array out. The stream is assumed to be compressed using the signed LEB128 format. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template @@ -215,7 +215,7 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) } -// write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression. +// Write signed integers to a stream with LEB128 compression. // This takes N integers from array values, compress them with the LEB128 algorithm and // writes the result on the stream s. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. diff --git a/src/position.cpp b/src/position.cpp index f7354b3d77c..37c586abbaa 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -61,8 +61,7 @@ constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, } // namespace -// operator<<(Position) returns an ASCII representation of the position - +// Returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { os << "\n +---+---+---+---+---+---+---+---+\n"; @@ -114,8 +113,7 @@ Key cuckoo[8192]; Move cuckooMove[8192]; -// Position::init() initializes at startup the various arrays used to compute hash keys - +// Initializes at startup the various arrays used to compute hash keys void Position::init() { PRNG rng(1070372); @@ -158,10 +156,9 @@ void Position::init() { } -// Position::set() initializes the position object with the given FEN string. +// Initializes the position object with the given FEN string. // This function is not very robust - make sure that input FENs are correct, // this is assumed to be the responsibility of the GUI. - Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { /* A FEN string defines a particular position using only the ASCII character set. @@ -298,9 +295,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th } -// Position::set_castling_right() is a helper function used to set castling +// Helper function used to set castling // rights given the corresponding color and the rook starting square. - void Position::set_castling_right(Color c, Square rfrom) { Square kfrom = square(c); @@ -318,8 +314,7 @@ void Position::set_castling_right(Color c, Square rfrom) { } -// Position::set_check_info() sets king attacks to detect if a move gives check - +// Sets king attacks to detect if a move gives check void Position::set_check_info() const { update_slider_blockers(WHITE); @@ -336,10 +331,9 @@ void Position::set_check_info() const { } -// Position::set_state() computes the hash keys of the position, and other +// Computes the hash keys of the position, and other // data that once computed is updated incrementally as moves are made. // The function is only used when a new position is set up - void Position::set_state() const { st->key = st->materialKey = 0; @@ -372,10 +366,9 @@ void Position::set_state() const { } -// Position::set() is an overload to initialize the position object with +// Overload to initialize the position object with // the given endgame code string like "KBPKN". It is mainly a helper to // get the material key out of an endgame code. - Position& Position::set(const string& code, Color c, StateInfo* si) { assert(code[0] == 'K'); @@ -395,9 +388,8 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { } -// Position::fen() returns a FEN representation of the position. In case of +// Returns a FEN representation of the position. In case of // Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. - string Position::fen() const { int emptyCnt; @@ -444,7 +436,7 @@ string Position::fen() const { return ss.str(); } -// update_slider_blockers() calculates st->blockersForKing[c] and st->pinners[~c], +// Calculates st->blockersForKing[c] and st->pinners[~c], // which store respectively the pieces preventing king of color c from being in check // and the slider pieces of color ~c pinning pieces of color c to the king. void Position::update_slider_blockers(Color c) const { @@ -475,9 +467,8 @@ void Position::update_slider_blockers(Color c) const { } -// Position::attackers_to() computes a bitboard of all pieces which attack a +// Computes a bitboard of all pieces which attack a // given square. Slider attacks use the occupied bitboard to indicate occupancy. - Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) @@ -489,8 +480,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { } -// Position::legal() tests whether a pseudo-legal move is legal - +// Tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { assert(is_ok(m)); @@ -549,10 +539,9 @@ bool Position::legal(Move m) const { } -// Position::pseudo_legal() takes a random move and tests whether the move is +// Takes a random move and tests whether the move is // pseudo-legal. It is used to validate moves from TT that can be corrupted // due to SMP concurrent access or hash position key aliasing. - bool Position::pseudo_legal(const Move m) const { Color us = sideToMove; @@ -620,8 +609,7 @@ bool Position::pseudo_legal(const Move m) const { } -// Position::gives_check() tests whether a pseudo-legal move gives a check - +// Tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { assert(is_ok(m)); @@ -669,10 +657,9 @@ bool Position::gives_check(Move m) const { } -// Position::do_move() makes a move, and saves all information necessary +// Makes a move, and saves all information necessary // to a StateInfo object. The move is assumed to be legal. Pseudo-legal // moves should be filtered out before this function is called. - void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(is_ok(m)); @@ -867,9 +854,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } -// Position::undo_move() unmakes a move. When it returns, the position should +// Unmakes a move. When it returns, the position should // be restored to exactly the same state as before the move was made. - void Position::undo_move(Move m) { assert(is_ok(m)); @@ -931,7 +917,7 @@ void Position::undo_move(Move m) { } -// Position::do_castling() is a helper used to do/undo a castling move. This +// Helper used to do/undo a castling move. This // is a bit tricky in Chess960 where from/to squares can overlap. template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { @@ -963,9 +949,8 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ } -// Position::do_null_move() is used to do a "null move": it flips +// Used to do a "null move": it flips // the side to move without executing any move on the board. - void Position::do_null_move(StateInfo& newSt) { assert(!checkers()); @@ -1003,8 +988,7 @@ void Position::do_null_move(StateInfo& newSt) { } -// Position::undo_null_move() must be used to undo a "null move" - +// Must be used to undo a "null move" void Position::undo_null_move() { assert(!checkers()); @@ -1014,10 +998,9 @@ void Position::undo_null_move() { } -// Position::key_after() computes the new hash key after the given move. Needed +// Computes the new hash key after the given move. Needed // for speculative prefetch. It doesn't recognize special moves like castling, // en passant and promotions. - Key Position::key_after(Move m) const { Square from = from_sq(m); @@ -1035,10 +1018,9 @@ Key Position::key_after(Move m) const { } -// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the -// SEE value of move is greater or equal to the given threshold. We'll use an +// Tests if the SEE (Static Exchange Evaluation) +// value of move is greater or equal to the given threshold. We'll use an // algorithm similar to alpha-beta pruning with a null window. - bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); @@ -1140,9 +1122,8 @@ bool Position::see_ge(Move m, Value threshold) const { return bool(res); } -// Position::is_draw() tests whether the position is drawn by 50-move rule +// Tests whether the position is drawn by 50-move rule // or by repetition. It does not detect stalemates. - bool Position::is_draw(int ply) const { if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) @@ -1154,9 +1135,8 @@ bool Position::is_draw(int ply) const { } -// Position::has_repeated() tests whether there has been at least one repetition +// Tests whether there has been at least one repetition // of positions since the last capture or pawn move. - bool Position::has_repeated() const { StateInfo* stc = st; @@ -1172,9 +1152,8 @@ bool Position::has_repeated() const { } -// Position::has_game_cycle() tests if the position has a move which draws by repetition, +// Tests if the position has a move which draws by repetition, // or an earlier position has a move that directly reaches the current position. - bool Position::has_game_cycle(int ply) const { int j; @@ -1220,9 +1199,8 @@ bool Position::has_game_cycle(int ply) const { } -// Position::flip() flips position with the white and black sides reversed. This +// Flips position with the white and black sides reversed. This // is only useful for debugging e.g. for finding evaluation symmetry bugs. - void Position::flip() { string f, token; @@ -1255,10 +1233,9 @@ void Position::flip() { } -// Position::pos_is_ok() performs some consistency checks for the +// Performs some consistency checks for the // position object and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. - bool Position::pos_is_ok() const { constexpr bool Fast = true; // Quick (default) or full check? diff --git a/src/search.cpp b/src/search.cpp index 43d78892f60..933cd154e0d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -148,7 +148,7 @@ void update_all_stats(const Position& pos, int captureCount, Depth depth); -// perft() is our utility to verify move generation. All the leaf nodes up +// Utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. template uint64_t perft(Position& pos, Depth depth) { @@ -179,8 +179,7 @@ uint64_t perft(Position& pos, Depth depth) { } // namespace -// Search::init() is called at startup to initialize various lookup tables - +// Called at startup to initialize various lookup tables void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) @@ -188,8 +187,7 @@ void Search::init() { } -// Search::clear() resets search state to its initial value - +// Resets search state to its initial value void Search::clear() { Threads.main()->wait_for_search_finished(); @@ -201,9 +199,8 @@ void Search::clear() { } -// MainThread::search() is started when the program receives the UCI 'go' +// Called when the program receives the UCI 'go' // command. It searches from the root position and outputs the "bestmove". - void MainThread::search() { if (Limits.perft) @@ -277,10 +274,9 @@ void MainThread::search() { } -// Thread::search() is the main iterative deepening loop. It calls search() +// Main iterative deepening loop. It calls search() // repeatedly with increasing depth until the allocated thinking time has been // consumed, the user stops the search, or the maximum search depth is reached. - void Thread::search() { // Allocate stack with extra size to allow access from (ss-7) to (ss+2): @@ -521,8 +517,7 @@ void Thread::search() { namespace { -// search<>() is the main search function for both PV and non-PV nodes - +// Main search function for both PV and non-PV nodes template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { @@ -1346,7 +1341,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } -// qsearch() is the quiescence search function, which is called by the main search +// Quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. // (~155 Elo) template @@ -1593,10 +1588,9 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { } -// value_to_tt() adjusts a mate or TB score from "plies to mate from the root" +// Adjusts a mate or TB score from "plies to mate from the root" // to "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. - Value value_to_tt(Value v, int ply) { assert(v != VALUE_NONE); @@ -1605,12 +1599,11 @@ Value value_to_tt(Value v, int ply) { } -// value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score +// Inverse of value_to_tt(): it adjusts a mate or TB score // from the transposition table (which refers to the plies to mate/be mated from // current position) to "plies to mate/be mated (TB win/loss) from the root". // However, to avoid potentially false mate scores related to the 50 moves rule // and the graph history interaction problem, we return an optimal TB score instead. - Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) @@ -1636,8 +1629,7 @@ Value value_from_tt(Value v, int ply, int r50c) { } -// update_pv() adds current move and appends child pv[] - +// Adds current move and appends child pv[] void update_pv(Move* pv, Move move, const Move* childPv) { for (*pv++ = move; childPv && *childPv != MOVE_NONE;) @@ -1646,8 +1638,7 @@ void update_pv(Move* pv, Move move, const Move* childPv) { } -// update_all_stats() updates stats at the end of search() when a bestMove is found - +// Updates stats at the end of search() when a bestMove is found void update_all_stats(const Position& pos, Stack* ss, Move bestMove, @@ -1709,9 +1700,8 @@ void update_all_stats(const Position& pos, } -// update_continuation_histories() updates histories of the move pairs formed +// Updates histories of the move pairs formed // by moves at ply -1, -2, -4, and -6 with current move. - void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { for (int i : {1, 2, 3, 4, 6}) @@ -1725,8 +1715,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } -// update_quiet_stats() updates move sorting heuristics - +// Updates move sorting heuristics void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { // Update killers @@ -1751,7 +1740,6 @@ void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { // When playing with strength handicap, choose the best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - Move Skill::pick_best(size_t multiPV) { const RootMoves& rootMoves = Threads.main()->rootMoves; @@ -1786,9 +1774,8 @@ Move Skill::pick_best(size_t multiPV) { } // namespace -// MainThread::check_time() is used to print debug info and, more importantly, +// Used to print debug info and, more importantly, // to detect when we are out of available time and thus stop the search. - void MainThread::check_time() { if (--callsCnt > 0) @@ -1819,9 +1806,8 @@ void MainThread::check_time() { } -// UCI::pv() formats PV information according to the UCI protocol. UCI requires +// Formats PV information according to the UCI protocol. UCI requires // that all (if any) unsearched PV lines are sent using a previous search score. - string UCI::pv(const Position& pos, Depth depth) { std::stringstream ss; @@ -1874,11 +1860,10 @@ string UCI::pv(const Position& pos, Depth depth) { } -// RootMove::extract_ponder_from_tt() is called in case we have no ponder move +// Called in case we have no ponder move // before exiting the search, for instance, in case we stop the search during a // fail high at root. We try hard to have a ponder move to return to the GUI, // otherwise in case of 'ponder on' we have nothing to think about. - bool RootMove::extract_ponder_from_tt(Position& pos) { StateInfo st; diff --git a/src/search.h b/src/search.h index 37cd5e5a686..b2d22e612c4 100644 --- a/src/search.h +++ b/src/search.h @@ -36,7 +36,6 @@ namespace Search { // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has // its own array of Stack objects, indexed by the current ply. - struct Stack { Move* pv; PieceToHistory* continuationHistory; @@ -58,14 +57,14 @@ struct Stack { // RootMove struct is used for moves at the root of the tree. For each root move // we store a score and a PV (really a refutation in the case of moves which // fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. - struct RootMove { explicit RootMove(Move m) : pv(1, m) {} bool extract_ponder_from_tt(Position& pos); bool operator==(const Move& m) const { return pv[0] == m; } - bool operator<(const RootMove& m) const { // Sort in descending order + // Sort in descending order + bool operator<(const RootMove& m) const { return m.score != score ? m.score < score : m.previousScore < previousScore; } @@ -89,7 +88,8 @@ using RootMoves = std::vector; struct LimitsType { - LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); movestogo = depth = mate = perft = infinite = 0; nodes = 0; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 31597f835a8..e23631575e6 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1317,7 +1317,7 @@ WDLScore search(Position& pos, ProbeState* result) { } // namespace -// Tablebases::init() is called at startup and after every change to +// Called at startup and after every change to // "SyzygyPath" UCI option to (re)create the various tables. It is not thread // safe, nor it needs to be. void Tablebases::init(const std::string& paths) { diff --git a/src/thread.cpp b/src/thread.cpp index 9f8a63bdc01..fdf89095b5e 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -40,9 +40,8 @@ namespace Stockfish { ThreadPool Threads; // Global object -// Thread constructor launches the thread and waits until it goes to sleep +// Constructor launches the thread and waits until it goes to sleep // in idle_loop(). Note that 'searching' and 'exit' should be already set. - Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { @@ -51,9 +50,8 @@ Thread::Thread(size_t n) : } -// Thread destructor wakes up the thread in idle_loop() and waits +// Destructor wakes up the thread in idle_loop() and waits // for its termination. Thread should be already waiting. - Thread::~Thread() { assert(!searching); @@ -64,8 +62,7 @@ Thread::~Thread() { } -// Thread::clear() reset histories, usually before a new game - +// Reset histories, usually before a new game void Thread::clear() { counterMoves.fill(MOVE_NONE); @@ -80,8 +77,7 @@ void Thread::clear() { } -// Thread::start_searching() wakes up the thread that will start the search - +// Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); searching = true; @@ -90,9 +86,8 @@ void Thread::start_searching() { } -// Thread::wait_for_search_finished() blocks on the condition variable +// Blocks on the condition variable // until the thread has finished searching. - void Thread::wait_for_search_finished() { std::unique_lock lk(mutex); @@ -100,7 +95,7 @@ void Thread::wait_for_search_finished() { } -// Thread::idle_loop() is where the thread is parked, blocked on the +// Thread gets parked here, blocked on the // condition variable, when it has no work to do. void Thread::idle_loop() { @@ -129,10 +124,9 @@ void Thread::idle_loop() { } } -// ThreadPool::set() creates/destroys threads to match the requested number. +// Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. - void ThreadPool::set(size_t requested) { if (threads.size() > 0) // destroy any existing thread(s) @@ -160,8 +154,7 @@ void ThreadPool::set(size_t requested) { } -// ThreadPool::clear() sets threadPool data to initial values - +// Sets threadPool data to initial values void ThreadPool::clear() { for (Thread* th : threads) @@ -174,9 +167,8 @@ void ThreadPool::clear() { } -// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +// Wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. - void ThreadPool::start_thinking(Position& pos, StateListPtr& states, const Search::LimitsType& limits, diff --git a/src/thread.h b/src/thread.h index 4a077962661..5f33b7369d3 100644 --- a/src/thread.h +++ b/src/thread.h @@ -38,7 +38,6 @@ namespace Stockfish { // per-thread pawn and material hash tables so that once we get a // pointer to an entry its lifetime is unlimited and we don't have // to care about someone changing the entry under our feet. - class Thread { std::mutex mutex; @@ -76,7 +75,6 @@ class Thread { // MainThread is a derived class specific for main thread - struct MainThread: public Thread { using Thread::Thread; @@ -97,7 +95,6 @@ struct MainThread: public Thread { // ThreadPool struct handles all the threads-related stuff like init, starting, // parking and, most importantly, launching a thread. All the access to threads // is done through this class. - struct ThreadPool { void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); diff --git a/src/timeman.cpp b/src/timeman.cpp index cf0e08ed789..7e77a4add05 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -29,11 +29,10 @@ namespace Stockfish { TimeManagement Time; // Our global time management object -// TimeManagement::init() is called at the beginning of the search and calculates +// Called at the beginning of the search and calculates // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) - void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // If we have no time, no need to initialize TM, except for the start time, diff --git a/src/timeman.h b/src/timeman.h index 4b9b62bd2a9..6c56d506b3f 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -30,7 +30,6 @@ namespace Stockfish { // The TimeManagement class computes the optimal time to think depending on // the maximum available time, the game move number, and other parameters. - class TimeManagement { public: void init(Search::LimitsType& limits, Color us, int ply); diff --git a/src/tt.cpp b/src/tt.cpp index a3ad0a78d4f..816d43f8603 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -33,9 +33,8 @@ namespace Stockfish { TranspositionTable TT; // Our global transposition table -// TTEntry::save() populates the TTEntry with a new node's data, possibly +// Populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. - void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { // Preserve any existing move for the same position @@ -57,10 +56,9 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) } -// TranspositionTable::resize() sets the size of the transposition table, +// Sets the size of the transposition table, // measured in megabytes. Transposition table consists of a power of 2 number // of clusters and each cluster consists of ClusterSize number of TTEntry. - void TranspositionTable::resize(size_t mbSize) { Threads.main()->wait_for_search_finished(); @@ -80,9 +78,8 @@ void TranspositionTable::resize(size_t mbSize) { } -// TranspositionTable::clear() initializes the entire transposition table to zero, -// in a multi-threaded way. - +// Initializes the entire transposition table to zero, +// in a multi-threaded way. void TranspositionTable::clear() { std::vector threads; @@ -109,13 +106,12 @@ void TranspositionTable::clear() { } -// TranspositionTable::probe() looks up the current position in the transposition +// Looks up the current position in the transposition // table. It returns true and a pointer to the TTEntry if the position is found. // Otherwise, it returns false and a pointer to an empty or least valuable TTEntry // to be replaced later. The replace value of an entry is calculated as its depth // minus 8 times its relative age. TTEntry t1 is considered more valuable than // TTEntry t2 if its replace value is greater than that of t2. - TTEntry* TranspositionTable::probe(const Key key, bool& found) const { TTEntry* const tte = first_entry(key); @@ -148,7 +144,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { } -// TranspositionTable::hashfull() returns an approximation of the hashtable +// Returns an approximation of the hashtable // occupation during a search. The hash is x permill full, as per UCI protocol. int TranspositionTable::hashfull() const { diff --git a/src/tt.h b/src/tt.h index 628dfba28f7..12fedd2d42f 100644 --- a/src/tt.h +++ b/src/tt.h @@ -37,7 +37,6 @@ namespace Stockfish { // move 16 bit // value 16 bit // eval value 16 bit - struct TTEntry { Move move() const { return Move(move16); } @@ -65,7 +64,6 @@ struct TTEntry { // contains information on exactly one position. The size of a Cluster should // divide the size of a cache line for best performance, as the cacheline is // prefetched when possible. - class TranspositionTable { static constexpr int ClusterSize = 3; diff --git a/src/types.h b/src/types.h index c76efd077d1..7ac2f84951a 100644 --- a/src/types.h +++ b/src/types.h @@ -321,21 +321,17 @@ constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d inline Square& operator+=(Square& s, Direction d) { return s = s + d; } inline Square& operator-=(Square& s, Direction d) { return s = s - d; } -constexpr Color operator~(Color c) { - return Color(c ^ BLACK); // Toggle color -} +// Toggle color +constexpr Color operator~(Color c) { return Color(c ^ BLACK); } -constexpr Square flip_rank(Square s) { // Swap A1 <-> A8 - return Square(s ^ SQ_A8); -} +// Swap A1 <-> A8 +constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); } -constexpr Square flip_file(Square s) { // Swap A1 <-> H1 - return Square(s ^ SQ_H1); -} +// Swap A1 <-> H1 +constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); } -constexpr Piece operator~(Piece pc) { - return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT -} +// Swap color of piece B_KNIGHT <-> W_KNIGHT +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); } constexpr CastlingRights operator&(Color c, CastlingRights cr) { return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); diff --git a/src/uci.cpp b/src/uci.cpp index 0671cb5ff65..1d8f5bdc05c 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -49,11 +49,10 @@ namespace { const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -// position() is called when the engine receives the "position" UCI command. +// Called when the engine receives the "position" UCI command. // It sets up the position that is described in the given FEN string ("fen") or // the initial position ("startpos") and then makes the moves given in the following // move list ("moves"). - void position(Position& pos, std::istringstream& is, StateListPtr& states) { Move m; @@ -83,9 +82,8 @@ void position(Position& pos, std::istringstream& is, StateListPtr& states) { } } -// trace_eval() prints the evaluation of the current position, consistent with +// Prints the evaluation of the current position, consistent with // the UCI options set so far. - void trace_eval(Position& pos) { StateListPtr states(new std::deque(1)); @@ -98,7 +96,7 @@ void trace_eval(Position& pos) { } -// setoption() is called when the engine receives the "setoption" UCI command. +// Called when the engine receives the "setoption" UCI command. // The function updates the UCI option ("name") to the given value ("value"). void setoption(std::istringstream& is) { @@ -124,7 +122,7 @@ void setoption(std::istringstream& is) { } -// go() is called when the engine receives the "go" UCI command. The function +// Called when the engine receives the "go" UCI command. The function // sets the thinking time and other parameters from the input string, then starts // with a search. @@ -170,7 +168,7 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) { } -// bench() is called when the engine receives the "bench" command. +// Called when the engine receives the "bench" command. // First, a list of UCI commands is set up according to the bench // parameters, then it is run one by one, printing a summary at the end. @@ -252,12 +250,11 @@ int win_rate_model(Value v, int ply) { } // namespace -// UCI::loop() waits for a command from the stdin, parses it, and then calls the appropriate +// Waits for a command from the stdin, parses it, and then calls the appropriate // function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a // graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, // like running 'bench', the function returns immediately after the command is executed. // In addition to the UCI ones, some additional debug commands are also supported. - void UCI::loop(int argc, char* argv[]) { Position pos; @@ -346,12 +343,11 @@ void UCI::loop(int argc, char* argv[]) { // without treatment of mate and similar special scores. int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } -// UCI::value() converts a Value to a string by adhering to the UCI protocol specification: +// Converts a Value to a string by adhering to the UCI protocol specification: // // cp The score from the engine's point of view in centipawns. // mate Mate in 'y' moves (not plies). If the engine is getting mated, // uses negative values for 'y'. - std::string UCI::value(Value v) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); @@ -372,9 +368,8 @@ std::string UCI::value(Value v) { } -// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation +// Reports the win-draw-loss (WDL) statistics given an evaluation // and a game ply based on the data gathered for fishtest LTC games. - std::string UCI::wdl(Value v, int ply) { std::stringstream ss; @@ -388,18 +383,16 @@ std::string UCI::wdl(Value v, int ply) { } -// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) - +// Converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } -// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). +// Converts a Move to a string in coordinate notation (g1f3, a7a8q). // The only special case is castling where the e1g1 notation is printed in // standard chess mode and in e1h1 notation it is printed in Chess960 mode. // Internally, all castling moves are always encoded as 'king captures rook'. - std::string UCI::move(Move m, bool chess960) { if (m == MOVE_NONE) @@ -423,9 +416,8 @@ std::string UCI::move(Move m, bool chess960) { } -// UCI::to_move() converts a string representing a move in coordinate notation +// Converts a string representing a move in coordinate notation // (g1f3, a7a8q) to the corresponding legal Move, if any. - Move UCI::to_move(const Position& pos, std::string& str) { if (str.length() == 5) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 8db4233af5f..d0db1c76dd2 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -60,8 +60,7 @@ bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const { } -// UCI::init() initializes the UCI options to their hard-coded default values - +// Initializes the UCI options to their hard-coded default values void init(OptionsMap& o) { constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; @@ -89,9 +88,8 @@ void init(OptionsMap& o) { } -// operator<<() is used to print all the options default values in chronological +// Used to print all the options default values in chronological // insertion order (the idx field) and in the format defined by the UCI protocol. - std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { for (size_t idx = 0; idx < om.size(); ++idx) @@ -172,7 +170,7 @@ bool Option::operator==(const char* s) const { } -// operator<<() inits options and assigns idx in the correct printing order +// Inits options and assigns idx in the correct printing order void Option::operator<<(const Option& o) { @@ -183,10 +181,9 @@ void Option::operator<<(const Option& o) { } -// operator=() updates currentValue and triggers on_change() action. It's up to +// 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. - Option& Option::operator=(const string& v) { assert(!type.empty()); From cf3dbcb5acd66efaaa84fa1e24ce7afb062fba08 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 21 Oct 2023 17:01:45 +0800 Subject: [PATCH 0257/1309] Time management improvements 1. Tune time management parameters. 2. Scale the optimum time and maximum time parameters based on the amount of time left, using a logarithmic scale. Many acknowledgements to @FauziAkram for tuning the parameters and for the original idea (see https://tests.stockfishchess.org/tests/view/652f0356de6d262d08d333c5). STC: https://tests.stockfishchess.org/tests/view/6533938fde6d262d08d39e4d LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 44320 W: 11301 L: 10982 D: 22037 Ptnml(0-2): 146, 4810, 11920, 5147, 137 LTC: https://tests.stockfishchess.org/tests/view/653477e4de6d262d08d3ae06 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 146442 W: 37338 L: 36811 D: 72293 Ptnml(0-2): 60, 14975, 42645, 15460, 81 Verification runs: 3+0.03: https://tests.stockfishchess.org/tests/view/65364e7ef127f3553505178a 10+0: https://tests.stockfishchess.org/tests/view/65364e9ff127f3553505178f 180+1.8: https://tests.stockfishchess.org/tests/view/65364ec3f127f35535051794 closes https://github.com/official-stockfish/Stockfish/pull/4843 No functional change. Co-Authored-By: FauziAkram <11150271+FauziAkram@users.noreply.github.com> --- src/search.cpp | 10 +++++----- src/timeman.cpp | 14 +++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 933cd154e0d..d5416e40759 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -467,15 +467,15 @@ void Thread::search() { // Do we have time for the next iteration? Can we stop searching now? if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (69 + 13 * (mainThread->bestPreviousAverageScore - bestValue) + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + 6 * (mainThread->iterValue[iterIdx] - bestValue)) - / 619.6; + / 583.0; fallingEval = std::clamp(fallingEval, 0.5, 1.5); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.57 : 0.65; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction); - double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.03 * timeReduction); + double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size(); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; diff --git a/src/timeman.cpp b/src/timeman.cpp index 7e77a4add05..1253d434672 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -72,7 +72,11 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - moveOverhead * (2 + mtg)); // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12); + double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.12); + + // Calculate time constants based on current time left. + double optConstant = std::min(0.00335 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0048); + double maxConstant = std::max(3.6 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.7); // A user may scale time usage by setting UCI option "Slow Mover" // Default is 100 and changing this value will probably lose elo. @@ -83,10 +87,10 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { - optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039, + optScale = std::min(0.0120 + std::pow(ply + 3.3, 0.44) * optConstant, 0.2 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(7.0, 4.0 + ply / 12.0); + maxScale = std::min(6.8, maxConstant + ply / 12.2); } // x moves in y seconds (+ z increment) @@ -96,10 +100,10 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { maxScale = std::min(6.3, 1.5 + 0.11 * mtg); } - // Never use more than 80% of the available time for this move + // Limit the maximum possible time for this move optimumTime = TimePoint(optScale * timeLeft); maximumTime = - TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (Options["Ponder"]) optimumTime += optimumTime / 4; From 49ece9f791b84a261f2a8865d2de51c20a520bc6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 23 Oct 2023 19:42:08 +0200 Subject: [PATCH 0258/1309] Follow up Makefile changes for clang-format as reported on various OS, these changes help portability closes https://github.com/official-stockfish/Stockfish/pull/4844 No functional change. --- src/Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Makefile b/src/Makefile index 76ef6fdeb8d..59ea7bfe7b5 100644 --- a/src/Makefile +++ b/src/Makefile @@ -153,7 +153,7 @@ dotprod = no arm_version = 0 STRIP = strip -ifneq ($(shell command -v clang-format-17),) +ifneq ($(shell which clang-format-17 2> /dev/null),) CLANG-FORMAT = clang-format-17 else CLANG-FORMAT = clang-format @@ -854,7 +854,8 @@ endif objclean profileclean config-sanity \ icx-profile-use icx-profile-make \ gcc-profile-use gcc-profile-make \ - clang-profile-use clang-profile-make FORCE + clang-profile-use clang-profile-make FORCE \ + format analyze analyze: net config-sanity objclean $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS) @@ -951,7 +952,7 @@ net: netvariables fi; \ format: - $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file:../.clang-format + $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file # default target default: From d6a5c2b0852e0fe7efff1ef7e8bd6d94f962bf8e Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 24 Oct 2023 07:43:49 +0800 Subject: [PATCH 0259/1309] Small formatting improvements Changes some C style casts to C++ style, and fixes some incorrect comments and variable names. closes #4845 No functional change --- src/bitboard.h | 4 ++-- src/nnue/evaluate_nnue.cpp | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/nnue_common.h | 4 ++-- src/search.cpp | 20 ++++++++++---------- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 24f6deca840..7dbd5329be8 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -298,7 +298,7 @@ inline Square lsb(Bitboard b) { unsigned long idx; _BitScanForward64(&idx, b); - return (Square) idx; + return Square(idx); #else // MSVC, WIN32 unsigned long idx; @@ -332,7 +332,7 @@ inline Square msb(Bitboard b) { unsigned long idx; _BitScanReverse64(&idx, b); - return (Square) idx; + return Square(idx); #else // MSVC, WIN32 diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index ea53a5102fa..e29d3c17b17 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -116,7 +116,7 @@ static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::str static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { write_little_endian(stream, Version); write_little_endian(stream, hashValue); - write_little_endian(stream, (std::uint32_t) desc.size()); + write_little_endian(stream, std::uint32_t(desc.size())); stream.write(&desc[0], desc.size()); return !stream.fail(); } diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 987de892f3d..f8e2d497ac0 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -93,7 +93,7 @@ class SqrClippedReLU { output[i] = static_cast( // Really should be /127 but we need to make it fast so we right shift // by an extra 7 bits instead. Needs to be accounted for in the trainer. - std::min(127ll, ((long long) input[i] * input[i]) >> (2 * WeightScaleBits + 7))); + std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7))); } } }; diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index cf90850126b..f9cd7fbb597 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -130,11 +130,11 @@ inline void write_little_endian(std::ostream& stream, IntType value) { { for (; i + 1 < sizeof(IntType); ++i) { - u[i] = (std::uint8_t) v; + u[i] = std::uint8_t(v); v >>= 8; } } - u[i] = (std::uint8_t) v; + u[i] = std::uint8_t(v); stream.write(reinterpret_cast(u), sizeof(IntType)); } diff --git a/src/search.cpp b/src/search.cpp index d5416e40759..4b390713d8d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -279,9 +279,9 @@ void MainThread::search() { // consumed, the user stops the search, or the maximum search depth is reached. void Thread::search() { - // Allocate stack with extra size to allow access from (ss-7) to (ss+2): - // (ss-7) is needed for update_continuation_histories(ss-1) which accesses (ss-6), - // (ss+2) is needed for initialization of statScore and killers. + // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): + // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), + // (ss + 2) is needed for initialization of cutOffCnt and killers. Stack stack[MAX_PLY + 10], *ss = stack + 7; Move pv[MAX_PLY + 1]; Value alpha, beta, delta; @@ -363,13 +363,13 @@ void Thread::search() { selDepth = 0; // Reset aspiration window starting size - Value prev = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(prev) * prev / 17470; - alpha = std::max(prev - delta, -VALUE_INFINITE); - beta = std::min(prev + delta, VALUE_INFINITE); + Value avg = rootMoves[pvIdx].averageScore; + delta = Value(10) + int(avg) * avg / 17470; + alpha = std::max(avg - delta, -VALUE_INFINITE); + beta = std::min(avg + delta, VALUE_INFINITE); - // Adjust optimism based on root move's previousScore (~4 Elo) - int opt = 113 * prev / (std::abs(prev) + 109); + // Adjust optimism based on root move's averageScore (~4 Elo) + int opt = 113 * avg / (std::abs(avg) + 109); optimism[us] = Value(opt); optimism[~us] = -optimism[us]; @@ -582,7 +582,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo : value_draw(pos.this_thread()); // Step 3. Mate distance pruning. Even if we mate at the next move our score - // would be at best mate_in(ss->ply+1), but if alpha is already bigger because + // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because // a shorter mate was found upward in the tree then there is no need to search // because we will never beat the current alpha. Same logic but with reversed // signs apply also in the opposite condition of being mated instead of giving From ec02714b6262e26d6f96c45c4e2527f3d382a9f8 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 23 Oct 2023 22:49:37 +0200 Subject: [PATCH 0260/1309] Cleanup comments and some code reorg. passed STC: https://tests.stockfishchess.org/tests/view/6536dc7dcc309ae83955b04d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 58048 W: 14693 L: 14501 D: 28854 Ptnml(0-2): 200, 6399, 15595, 6669, 161 closes https://github.com/official-stockfish/Stockfish/pull/4846 No functional change --- src/evaluate.cpp | 10 ++++---- src/movepick.cpp | 8 +++--- src/nnue/evaluate_nnue.cpp | 4 +-- src/nnue/layers/affine_transform.h | 3 ++- src/nnue/nnue_architecture.h | 4 +-- src/nnue/nnue_feature_transformer.h | 6 +++-- src/search.cpp | 39 ++++++++++++++++------------- src/uci.cpp | 5 ++-- 8 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4ee3e6fd8b4..c405cfb5538 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -123,11 +123,11 @@ void NNUE::verify() { std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; - std::string msg3 = - "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - std::string msg4 = - "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" - + std::string(EvalFileDefaultName); + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + std::string(EvalFileDefaultName); std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; diff --git a/src/movepick.cpp b/src/movepick.cpp index ff282262a5b..d2a49706fc2 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -263,11 +263,9 @@ Move MovePicker::next_move(bool skipQuiets) { case GOOD_CAPTURE : if (select([&]() { - return pos.see_ge(*cur, Value(-cur->value)) - ? - // Move losing capture to endBadCaptures to be tried later - true - : (*endBadCaptures++ = *cur, false); + // Move losing capture to endBadCaptures to be tried later + return pos.see_ge(*cur, Value(-cur->value)) ? true + : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index e29d3c17b17..ef6b7e91a60 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -407,8 +407,8 @@ bool save_eval(const std::optional& filename) { { if (currentEvalFileName != EvalFileDefaultName) { - msg = - "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; sync_cout << msg << sync_endl; return false; diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 3fba45ed87d..44fa5d00a43 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -256,7 +256,8 @@ class AffineTransform { else if constexpr (OutputDimensions == 1) { - // We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements. + // We cannot use AVX512 for the last layer because there are only 32 inputs + // and the buffer is not padded to 64 elements. #if defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero _mm256_setzero_si256 diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index be0138f14bd..e4c308cb267 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -113,8 +113,8 @@ struct Network { ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); - // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<previous from states_to_update[i+1] or states_to_update[i] == nullptr. - // computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr. + // by repeatedly applying ->previous from states_to_update[i+1] or + // states_to_update[i] == nullptr. + // computed_st must be reachable by repeatedly applying ->previous on + // states_to_update[0], if not nullptr. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, diff --git a/src/search.cpp b/src/search.cpp index 4b390713d8d..ad4b458e180 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -379,8 +379,8 @@ void Thread::search() { int failedHighCnt = 0; while (true) { - // Adjust the effective depth searched, but ensure at least one effective increment for every - // four searchAgain steps (see issue #2717). + // Adjust the effective depth searched, but ensure at least one effective increment + // for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); @@ -633,7 +633,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (!ttCapture) update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); - // Extra penalty for early quiet moves of the previous ply (~0 Elo on STC, ~2 Elo on LTC) + // Extra penalty for early quiet moves of + // the previous ply (~0 Elo on STC, ~2 Elo on LTC). if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); @@ -715,7 +716,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } else if (excludedMove) { - // Providing the hint that this node's accumulator will be used often brings significant Elo gain (~13 Elo) + // 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); eval = ss->staticEval; } @@ -817,8 +819,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } } - // Step 10. If the position doesn't have a ttMove, decrease depth by 2 - // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth). + // Step 10. If the position doesn't have a ttMove, decrease depth by 2, + // or by 4 if the TT entry for the current position was hit and + // the stored depth is greater than or equal to the current depth. // Use qsearch if depth is equal or below zero (~9 Elo) if (PvNode && !ttMove) depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); @@ -967,13 +970,15 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (capture || givesCheck) { // Futility pruning for captures (~2 Elo) - if (!givesCheck && lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 188 + 206 * lmrDepth + PieceValue[pos.piece_on(to_sq(move))] - + captureHistory[movedPiece][to_sq(move)] - [type_of(pos.piece_on(to_sq(move)))] - / 7 - < alpha) - continue; + if (!givesCheck && lmrDepth < 7 && !ss->inCheck) + { + Piece capturedPiece = pos.piece_on(to_sq(move)); + int futilityEval = + ss->staticEval + 188 + 206 * lmrDepth + PieceValue[capturedPiece] + + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; + if (futilityEval < alpha) + continue; + } // SEE based pruning for captures and checks (~11 Elo) if (!pos.see_ge(move, Value(-185) * depth)) @@ -1018,9 +1023,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // that 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 this type of time controls. - if (!rootNode + // Recursive singular search is avoided. + if (!rootNode && move == ttMove && !excludedMove && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) - && move == ttMove && !excludedMove // Avoid recursive singular search && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { @@ -1053,7 +1058,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else if (singularBeta >= beta) return singularBeta; - // If the eval of ttMove is greater than beta, we reduce it (negative extension) (~7 Elo) + // If the eval of ttMove is greater than beta, reduce it (negative extension) (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; @@ -1061,7 +1066,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else if (cutNode) extension = depth < 19 ? -2 : -1; - // If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo) + // If the eval of ttMove is less than value, reduce it (negative extension) (~1 Elo) else if (ttValue <= value) extension = -1; } diff --git a/src/uci.cpp b/src/uci.cpp index 1d8f5bdc05c..8139fae4fd8 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -178,8 +178,9 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { uint64_t num, nodes = 0, cnt = 1; std::vector list = setup_bench(pos, args); - num = count_if(list.begin(), list.end(), - [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + + num = count_if(list.begin(), list.end(), + [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); TimePoint elapsed = now(); From 0024133b08777b578c1036eb1e4a36343b7fa1bb Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 22 Oct 2023 12:59:21 -0400 Subject: [PATCH 0261/1309] Update 5 search params for pruning at shallow depth Found by spsa tuning at 45+0.45 with: ``` int fpcEvalOffset = 188; int fpcLmrDepthMult = 206; int histDepthMult = -3232; int histDenom = 5793; int fpEvalOffset = 115; int negSeeDepthMultSq = -27; TUNE(SetRange(0, 394), fpcEvalOffset); TUNE(SetRange(0, 412), fpcLmrDepthMult); TUNE(SetRange(-6464, -1616), histDepthMult); TUNE(SetRange(2896, 11586), histDenom); TUNE(SetRange(0, 230), fpEvalOffset); TUNE(SetRange(-54, 0), negSeeDepthMultSq); ``` Passed STC: https://tests.stockfishchess.org/tests/view/6535551de746e058e6c0165d LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 109056 W: 28025 L: 27599 D: 53432 Ptnml(0-2): 357, 12669, 28038, 13119, 345 Passed LTC: https://tests.stockfishchess.org/tests/view/65364c6ff127f3553505175d LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 61290 W: 15316 L: 14941 D: 31033 Ptnml(0-2): 34, 6849, 16498, 7236, 28 closes https://github.com/official-stockfish/Stockfish/pull/4847 bench 1167412 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ad4b458e180..9cef7e5f558 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -974,7 +974,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { Piece capturedPiece = pos.piece_on(to_sq(move)); int futilityEval = - ss->staticEval + 188 + 206 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 239 + 291 * lmrDepth + PieceValue[capturedPiece] + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; if (futilityEval < alpha) continue; @@ -991,16 +991,16 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo + (*contHist[3])[movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3232 * depth) + if (lmrDepth < 6 && history < -3498 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 5793; + lmrDepth += history / 7815; lmrDepth = std::max(lmrDepth, -2); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 115 + 122 * lmrDepth <= alpha) + if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 80 + 122 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); From 871ab55f01f844bd24ed768c5e734ed2c956ef78 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Wed, 25 Oct 2023 16:00:58 +0200 Subject: [PATCH 0262/1309] Simplify futility pruning formula closes https://github.com/official-stockfish/Stockfish/pull/4848 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9cef7e5f558..24f0d9946f7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -775,7 +775,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo - (ss - 1)->statScore / 321 >= beta && eval >= beta && eval < 29462 // smaller than TB wins - && !(!ttCapture && ttMove)) + && (!ttMove || ttCapture)) return eval; // Step 9. Null move search with verification search (~35 Elo) From b0658f09b93185e2b43d4b2d6f0daa30c36ebcc2 Mon Sep 17 00:00:00 2001 From: Michael Chaly <26898827+Vizvezdenec@users.noreply.github.com> Date: Fri, 27 Oct 2023 17:19:31 +0200 Subject: [PATCH 0263/1309] Introduce pawn structure based history Original idea by Seer chess engine https://github.com/connormcmonigle/seer-nnue, coding done by @Disservin, code refactoring done by @locutus2 to match the style of other histories. This patch introduces pawn structure based history, which assings moves values based on last digits of pawn structure hash and piece type of moved piece and landing square of the move. Idea is that good places for pieces are quite often determined by pawn structure of position. Used in 3 different places - sorting of quiet moves, sorting of quiet check evasions and in history based pruning in search. Passed STC: https://tests.stockfishchess.org/tests/view/65391d08cc309ae83955dbaf LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 155488 W: 39408 L: 38913 D: 77167 Ptnml(0-2): 500, 18427, 39408, 18896, 513 Passed LTC: https://tests.stockfishchess.org/tests/view/653a36a2cc309ae83955f181 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 70110 W: 17548 L: 17155 D: 35407 Ptnml(0-2): 33, 7859, 18889, 8230, 44 closes https://github.com/official-stockfish/Stockfish/pull/4849 Bench: 1257882 Co-Authored-By: Disservin Co-Authored-By: Stefan Geschwentner --- src/movepick.cpp | 13 +++++++++++-- src/movepick.h | 13 +++++++++++-- src/position.cpp | 17 ++++++++++++++--- src/position.h | 4 ++++ src/search.cpp | 15 +++++++++++---- src/thread.cpp | 1 + src/thread.h | 1 + 7 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index d2a49706fc2..444477cf78e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -89,12 +89,14 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, + const PawnHistory& ph, Move cm, const Move* killers) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), + pawnHistory(ph), ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) { @@ -110,11 +112,13 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, + const PawnHistory& ph, Square rs) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), + pawnHistory(ph), ttMove(ttm), recaptureSquare(rs), depth(d) { @@ -125,9 +129,11 @@ MovePicker::MovePicker(const Position& p, // Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : +MovePicker::MovePicker( + const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph, const PawnHistory& ph) : pos(p), captureHistory(cph), + pawnHistory(ph), ttMove(ttm), threshold(th) { assert(!pos.checkers()); @@ -203,6 +209,8 @@ void MovePicker::score() { : pt != PAWN ? bool(to & threatenedByPawn) * 15000 : 0) : 0; + + m.value += pawnHistory[pawn_structure(pos)][pc][to]; } else // Type == EVASIONS @@ -212,7 +220,8 @@ void MovePicker::score() { + (1 << 28); else m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]; + + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] + + pawnHistory[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; } } diff --git a/src/movepick.h b/src/movepick.h index 65e93dda6fe..f210f5387fc 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -28,9 +28,13 @@ #include "movegen.h" #include "types.h" +#include "position.h" namespace Stockfish { -class Position; + +constexpr int PAWN_HISTORY_SIZE = 512; + +inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } // StatsEntry stores the stat table value. It is usually a number but could // be a move or even a nested history. We use a class instead of a naked value @@ -112,6 +116,8 @@ using PieceToHistory = Stats; // (~63 elo) using ContinuationHistory = Stats; +// PawnStructureHistory is addressed by the pawn structure and a move's [piece][to] +using PawnHistory = Stats; // MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which returns a @@ -135,6 +141,7 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, + const PawnHistory&, Move, const Move*); MovePicker(const Position&, @@ -143,8 +150,9 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, + const PawnHistory&, Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*, const PawnHistory&); Move next_move(bool skipQuiets = false); private: @@ -159,6 +167,7 @@ class MovePicker { const ButterflyHistory* mainHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; + const PawnHistory& pawnHistory; Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; diff --git a/src/position.cpp b/src/position.cpp index 37c586abbaa..2bb47871555 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -49,7 +49,7 @@ namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; -Key side; +Key side, noPawns; } namespace { @@ -128,7 +128,8 @@ void Position::init() { for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) Zobrist::castling[cr] = rng.rand(); - Zobrist::side = rng.rand(); + Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); // Prepare the cuckoo tables std::memset(cuckoo, 0, sizeof(cuckoo)); @@ -337,6 +338,7 @@ void Position::set_check_info() const { void Position::set_state() const { st->key = st->materialKey = 0; + st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); @@ -348,7 +350,10 @@ void Position::set_state() const { Piece pc = piece_on(s); st->key ^= Zobrist::psq[pc][s]; - if (type_of(pc) != KING && type_of(pc) != PAWN) + if (type_of(pc) == PAWN) + st->pawnKey ^= Zobrist::psq[pc][s]; + + else if (type_of(pc) != KING) st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; } @@ -728,6 +733,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); } + + st->pawnKey ^= Zobrist::psq[captured][capsq]; } else st->nonPawnMaterial[them] -= PieceValue[captured]; @@ -806,6 +813,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // 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]]; @@ -813,6 +821,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[us] += PieceValue[promotion]; } + // Update pawn hash key + st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + // Reset rule 50 draw counter st->rule50 = 0; } diff --git a/src/position.h b/src/position.h index 2aeb8fcd575..ce03c34f332 100644 --- a/src/position.h +++ b/src/position.h @@ -39,6 +39,7 @@ struct StateInfo { // Copied when making a move Key materialKey; + Key pawnKey; Value nonPawnMaterial[COLOR_NB]; int castlingRights; int rule50; @@ -146,6 +147,7 @@ class Position { Key key() const; Key key_after(Move m) const; Key material_key() const; + Key pawn_key() const; // Other properties of the position Color side_to_move() const; @@ -293,6 +295,8 @@ inline Key Position::adjust_key50(Key k) const { return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); } +inline Key Position::pawn_key() const { return st->pawnKey; } + inline Key Position::material_key() const { return st->materialKey; } inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } diff --git a/src/search.cpp b/src/search.cpp index 24f0d9946f7..0ffca247853 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -848,7 +848,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(probCutBeta < VALUE_INFINITE); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory, + thisThread->pawnHistory); while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) @@ -904,7 +905,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, - countermove, ss->killers); + thisThread->pawnHistory, countermove, ss->killers); value = bestValue; moveCountPruning = singularQuietLMR = false; @@ -988,7 +989,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { int history = (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)]; + + (*contHist[3])[movedPiece][to_sq(move)] + + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) if (lmrDepth < 6 && history < -3498 * depth) @@ -1463,7 +1465,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // will be generated. Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, prevSq); + contHist, thisThread->pawnHistory, prevSq); int quietCheckEvasions = 0; @@ -1671,10 +1673,15 @@ void update_all_stats(const Position& pos, // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bestMoveBonus); + thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] + << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { + thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] + [to_sq(quietsSearched[i])] + << -bestMoveBonus; thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bestMoveBonus); diff --git a/src/thread.cpp b/src/thread.cpp index fdf89095b5e..bc884dedf01 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -68,6 +68,7 @@ void Thread::clear() { counterMoves.fill(MOVE_NONE); mainHistory.fill(0); captureHistory.fill(0); + pawnHistory.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) diff --git a/src/thread.h b/src/thread.h index 5f33b7369d3..37a4a6ca2bc 100644 --- a/src/thread.h +++ b/src/thread.h @@ -71,6 +71,7 @@ class Thread { ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; }; From d30af4f669fa9a47e26a54967c571ffa7987d660 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 27 Oct 2023 13:28:55 +0300 Subject: [PATCH 0264/1309] Rewarding Quiet Moves that Enable Razoring The main idea of the patch comes from @peregrineshahin : https://tests.stockfishchess.org/tests/view/65205363ac57711436728781 Another small idea (tuning) comes from @Vizvezdenec https://tests.stockfishchess.org/tests/view/652e071ade6d262d08d318f4 And a long phases of tuning and tests was done by me in order to make the patch be able to pass both tests. The idea, as mentioned by Peregrine is that in our standard code, if no best move found after searching all moves, we give a bonus to the previous move that caused the fail high. So in razoring we assume no bestmove will be found so we might as well do the same. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 82336 W: 20997 L: 20610 D: 40729 Ptnml(0-2): 288, 9710, 20753, 10161, 256 https://tests.stockfishchess.org/tests/view/6538fafbcc309ae83955d8f0 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 46584 W: 11753 L: 11411 D: 23420 Ptnml(0-2): 21, 5133, 12642, 5475, 21 https://tests.stockfishchess.org/tests/view/653a3f2ccc309ae83955f223 closes https://github.com/official-stockfish/Stockfish/pull/4850 Bench: 1258079 Co-Authored-By: Shahin M. Shahin <41402573+peregrineshahin@users.noreply.github.com> --- src/evaluate.cpp | 4 ++-- src/search.cpp | 34 +++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c405cfb5538..9c39d4c07fb 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -144,8 +144,8 @@ void NNUE::verify() { } -// Returns a static, purely materialistic evaluation of the position -// from the point of view of the given color. It can be divided by PawnValue to get +// Returns a static, purely materialistic evaluation of the position from +// the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. Value Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) diff --git a/src/search.cpp b/src/search.cpp index 0ffca247853..65b27d16d4f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,7 +77,7 @@ enum NodeType { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((126 - 42 * noTtCutNode) * (d - improving)); + return Value((125 - 43 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -85,8 +85,8 @@ int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1560 - int(delta) * 945 / int(rootDelta)) / 1024 - + (!i && reductionScale > 791); + return (reductionScale + 1487 - int(delta) * 976 / int(rootDelta)) / 1024 + + (!i && reductionScale > 808); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -94,7 +94,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(334 * d - 531, 1538); } +int stat_bonus(Depth d) { return std::min(357 * d - 483, 1511); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -761,11 +761,19 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 492 - (257 - 200 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 474 - (270 - 174 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) + { + if (!priorCapture && prevSq != SQ_NONE) + { + int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) + ((ss-1)->moveCount > 11); + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus * 57 / 100; + } return value; + } } // Step 8. Futility pruning: child node (~40 Elo) @@ -993,22 +1001,22 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3498 * depth) + if (lmrDepth < 6 && history < -3645 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7815; - lmrDepth = std::max(lmrDepth, -2); + lmrDepth += history / 7836; + lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 80 + 122 * lmrDepth <= alpha) + if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 77 + 124 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth))) + if (!pos.see_ge(move, Value(-26 * lmrDepth * lmrDepth))) continue; } } @@ -1318,12 +1326,12 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 653) - + ((ss - 1)->moveCount > 11); + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 657) + + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus / 2; + << stat_bonus(depth) * bonus * 61 / 100; } if (PvNode) From 08ed4c90db31959521b7ef3186c026edd1e90307 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 27 Oct 2023 17:33:41 +0200 Subject: [PATCH 0265/1309] Format Code No functional change --- src/search.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 65b27d16d4f..b7b51e0b3de 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -768,9 +768,12 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) + ((ss-1)->moveCount > 11); - update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << stat_bonus(depth) * bonus * 57 / 100; + int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) + + ((ss - 1)->moveCount > 11); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + stat_bonus(depth) * bonus); + thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] + << stat_bonus(depth) * bonus * 57 / 100; } return value; } From 347d613b0e2c47f90cbf1c5a5affe97303f1ac3d Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 27 Oct 2023 18:35:52 +0200 Subject: [PATCH 0266/1309] remove outdated comment Bench: 1258079 --- src/thread.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/thread.h b/src/thread.h index 37a4a6ca2bc..cb2f6db1d41 100644 --- a/src/thread.h +++ b/src/thread.h @@ -34,10 +34,7 @@ namespace Stockfish { -// Thread class keeps together all the thread-related stuff. We use -// per-thread pawn and material hash tables so that once we get a -// pointer to an entry its lifetime is unlimited and we don't have -// to care about someone changing the entry under our feet. +// Thread class keeps together all the thread-related stuff. class Thread { std::mutex mutex; From 38aa70adcfa767387da91c8df1180fb11ad89ac7 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 29 Oct 2023 09:18:10 +0800 Subject: [PATCH 0267/1309] Small cleanups Corrects some incorrect or outdated comments. Credit is shared with yaneurao (see 38e830a#commitcomment-131131500) and locutus2 closes #4852 No functional change. --- src/movepick.h | 5 ++++- src/search.cpp | 15 +++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index f210f5387fc..dc171c9ff5b 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -32,7 +32,10 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 + +static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, + "PAWN_HISTORY_SIZE has to be a power of 2"); inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } diff --git a/src/search.cpp b/src/search.cpp index b7b51e0b3de..ef98d862e6a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -830,16 +830,19 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } } - // Step 10. If the position doesn't have a ttMove, decrease depth by 2, - // or by 4 if the TT entry for the current position was hit and + // Step 10. Internal iterative reductions (~9 Elo) + // For PV nodes without a ttMove, we decrease depth by 2, + // or by 4 if the current position is present in the TT and // the stored depth is greater than or equal to the current depth. - // Use qsearch if depth is equal or below zero (~9 Elo) + // Use qsearch if depth <= 0. if (PvNode && !ttMove) depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); if (depth <= 0) return qsearch(pos, ss, alpha, beta); + // For cutNodes without a ttMove, we decrease depth by 2 + // if current depth >= 8. if (cutNode && depth >= 8 && !ttMove) depth -= 2; @@ -1129,7 +1132,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (PvNode) r--; - // Decrease reduction if ttMove has been singularly extended (~1 Elo) + // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; @@ -1194,7 +1197,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { - // Increase reduction for cut nodes and not ttMove (~1 Elo) + // Increase reduction for cut nodes without ttMove (~1 Elo) if (!ttMove && cutNode) r += 2; @@ -1724,7 +1727,7 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed -// by moves at ply -1, -2, -4, and -6 with current move. +// by moves at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { for (int i : {1, 2, 3, 4, 6}) From 908811c24ab53d2cb1bebc1138427e21fefa8054 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 28 Oct 2023 16:04:15 +0800 Subject: [PATCH 0268/1309] Introduce asymmetric optimism Introduce asymmetric optimism for both side to move and opponent. Parameter tuning was done with 200k LTC games. STC: https://tests.stockfishchess.org/tests/view/653cc08fcc309ae8395622b3 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 98336 W: 25074 L: 24661 D: 48601 Ptnml(0-2): 339, 11612, 24890, 11951, 376 LTC: https://tests.stockfishchess.org/tests/view/653db595cc309ae839563140 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 83244 W: 20760 L: 20339 D: 42145 Ptnml(0-2): 51, 9306, 22498, 9705, 62 closes https://github.com/official-stockfish/Stockfish/pull/4853 Bench: 1371690 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ef98d862e6a..daab1eb1770 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -364,14 +364,13 @@ void Thread::search() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(avg) * avg / 17470; + delta = Value(10) + int(avg) * avg / 15335; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - int opt = 113 * avg / (std::abs(avg) + 109); - optimism[us] = Value(opt); - optimism[~us] = -optimism[us]; + optimism[us] = 103 * (avg + 33) / (std::abs(avg + 34) + 119); + optimism[~us] = -116 * (avg + 40) / (std::abs(avg + 12) + 123); // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From e277dda7166992536124891e212d6d6a866f8a12 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 31 Oct 2023 08:25:06 +0800 Subject: [PATCH 0269/1309] Prefetch TT entries in probcut Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 101344 W: 25893 L: 25491 D: 49960 Ptnml(0-2): 303, 11350, 26991, 11698, 330 https://tests.stockfishchess.org/tests/view/6540daa6cc309ae83956669b slight speedup: ``` Result of 100 runs ================== base (./stockfish.master ) = 1170705 +/- 3133 test (./stockfish.patch ) = 1174545 +/- 2895 diff = +3841 +/- 3196 speedup = +0.0033 P(speedup > 0) = 0.9907 ``` closes https://github.com/official-stockfish/Stockfish/pull/4856 No functional change --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index daab1eb1770..6e719be82f4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -869,6 +869,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(pos.capture_stage(move)); + // Prefetch the TT entry for the resulting position + prefetch(TT.first_entry(pos.key_after(move))); + ss->currentMove = move; ss->continuationHistory = &thisThread From 101d2bb8eae2c93c94013f56eae3af4faf8886f9 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 1 Nov 2023 13:42:06 +0300 Subject: [PATCH 0270/1309] Simplifying two formulas by eliminating two multiplication operations. Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60000 W: 15193 L: 14996 D: 29811 Ptnml(0-2): 199, 7100, 15215, 7277, 209 https://tests.stockfishchess.org/tests/view/653beb69cc309ae83956129d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 122910 W: 30471 L: 30353 D: 62086 Ptnml(0-2): 68, 13961, 33271, 14095, 60 https://tests.stockfishchess.org/tests/view/653c5848cc309ae839561ae7 closes https://github.com/official-stockfish/Stockfish/pull/4857 bench: 1216779 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6e719be82f4..3cd3b555c4a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -772,7 +772,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus * 57 / 100; + << stat_bonus(depth) * bonus / 2; } return value; } @@ -1339,7 +1339,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus * 61 / 100; + << stat_bonus(depth) * bonus / 2; } if (PvNode) From 1cb9afbdc04fbb864ee48babe56c1e88867d11e9 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 28 Oct 2023 07:43:12 +0200 Subject: [PATCH 0271/1309] Remove razoring history update. The recently commit 'Rewarding Quiet Moves that Enable Razoring' add a history update if razoring. But its contains also many tuned values all over the search. Following tests shows that the tuned values and not the added history update is responsible for the elo gain. So remove later. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 29376 W: 7641 L: 7410 D: 14325 Ptnml(0-2): 100, 3411, 7451, 3610, 116 https://tests.stockfishchess.org/tests/view/653c9fe1cc309ae839562070 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 242922 W: 59879 L: 59885 D: 123158 Ptnml(0-2): 129, 27764, 65688, 27744, 136 https://tests.stockfishchess.org/tests/view/653d06cbcc309ae839562735 closes https://github.com/official-stockfish/Stockfish/pull/4858 Bench: 1286104 --- src/search.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3cd3b555c4a..aaf545d2658 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -764,18 +764,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) - { - if (!priorCapture && prevSq != SQ_NONE) - { - int bonus = (depth > 6) + (PvNode || cutNode) + (value < alpha - 658) - + ((ss - 1)->moveCount > 11); - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] - << stat_bonus(depth) * bonus / 2; - } return value; - } } // Step 8. Futility pruning: child node (~40 Elo) From b4b704e6866bde21c69cd53457a6a91a15182b36 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 3 Nov 2023 14:03:12 +0300 Subject: [PATCH 0272/1309] Update pawn history based on static eval difference Use the same logic as in main history but with 1/4 multiplier. Passed STC: https://tests.stockfishchess.org/tests/view/653c1282cc309ae8395615bf LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 306624 W: 77811 L: 77094 D: 151719 Ptnml(0-2): 975, 36411, 77830, 37114, 982 Passed LTC: https://tests.stockfishchess.org/tests/view/654258e2cc309ae83956818d LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 99150 W: 24681 L: 24228 D: 50241 Ptnml(0-2): 56, 11107, 26792, 11568, 52 closes https://github.com/official-stockfish/Stockfish/pull/4859 bench 1330590 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index aaf545d2658..55d11003354 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -745,6 +745,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { int bonus = std::clamp(-18 * int((ss - 1)->staticEval + ss->staticEval), -1812, 1812); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; + if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) + thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; } // Set up the improving flag, which is true if current static evaluation is From 7f97ba775ece910402108d7a7b11ce645185d300 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 4 Nov 2023 17:19:08 +0300 Subject: [PATCH 0273/1309] Tweaking the futility pruning formula Huge credit goes also to candirufish, as the idea was first tried by him, and then tuned by me at multiple phases. Tweaking the futility pruning formula to be a bit more selective about when pruning is applied. Adjust the value added to the static eval based on the bestValue relative to ss->staticEval. If bestValue is significantly lower, we add a larger value. Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 37120 W: 9590 L: 9266 D: 18264 Ptnml(0-2): 130, 4301, 9385, 4603, 141 https://tests.stockfishchess.org/tests/view/6544cf90136acbc573523247 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 49632 W: 12381 L: 12033 D: 25218 Ptnml(0-2): 30, 5429, 13549, 5779, 29 https://tests.stockfishchess.org/tests/view/65453bc1136acbc573523a3c closes https://github.com/official-stockfish/Stockfish/pull/4861 bench: 1107118 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 55d11003354..27f0f987085 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1009,7 +1009,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 && ss->staticEval + 77 + 124 * lmrDepth <= alpha) + if (!ss->inCheck && lmrDepth < 13 + && ss->staticEval + (bestValue < ss->staticEval - 62 ? 123 : 77) + + 127 * lmrDepth + <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); From 791419aab529e714271ebc03d1d84ad4e7e9879a Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sun, 5 Nov 2023 12:01:06 +0300 Subject: [PATCH 0274/1309] Enhance some comments This documents some code and makes some hard code easier to understand for new developers. closes https://github.com/official-stockfish/Stockfish/pull/4862 No functional change --- src/movepick.h | 2 +- src/search.cpp | 31 ++++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index dc171c9ff5b..f058ff0ee88 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -119,7 +119,7 @@ using PieceToHistory = Stats; // (~63 elo) using ContinuationHistory = Stats; -// PawnStructureHistory is addressed by the pawn structure and a move's [piece][to] +// PawnHistory is addressed by the pawn structure and a move's [piece][to] using PawnHistory = Stats; // MovePicker class is used to pick one pseudo-legal move at a time from the diff --git a/src/search.cpp b/src/search.cpp index 27f0f987085..483412c598a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1030,9 +1030,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Singular extension search (~94 Elo). 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 all the other moves but the ttMove and if the result - // is lower than ttValue minus a margin, then we will extend the ttMove. Note - // that depth margin and singularBeta margin are known for having non-linear + // 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. + + // 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 this type of time controls. // Recursive singular search is avoided. @@ -1063,22 +1064,28 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } // Multi-cut pruning - // Our ttMove is assumed to fail high, and now we failed high also on a - // reduced search without the ttMove. So we assume this expected cut-node - // is not singular, that multiple moves fail high, and we can prune the - // whole subtree by returning a softbound. + // Our ttMove is assumed to fail high based on the bound of the TT entry, + // and if after excluding the ttMove with a reduced search we fail high over the original beta, + // we assume this expected cut-node is not singular (multiple moves fail high), + // and we can prune the whole subtree by returning a softbound. else if (singularBeta >= beta) return singularBeta; - // If the eval of ttMove is greater than beta, reduce it (negative extension) (~7 Elo) + // Negative extensions + // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, + // but we cannot do multi-cut because (ttValue - margin) is lower than the original beta, + // we do not know 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 currnet beta (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; - // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo) + // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) extension = depth < 19 ? -2 : -1; - // If the eval of ttMove is less than value, reduce it (negative extension) (~1 Elo) + // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) else if (ttValue <= value) extension = -1; } @@ -1411,9 +1418,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { assert(0 <= ss->ply && ss->ply < MAX_PLY); - // Decide whether or not to include checks: this fixes also the type of - // TT entry depth that we are going to use. Note that in qsearch we use - // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. + // Decide the replacement and cutoff priority of the qsearch TT entries ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; // Step 3. Transposition table lookup From d4b46ea6db7caf71cad3c36d0ef8c0162a743aba Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 5 Nov 2023 13:52:20 +0300 Subject: [PATCH 0275/1309] Set reduction to 0 if the move is a TT move The reduction formula currently decreases by 1 if the move is a TT move. This changes this by just setting the reduction to 0 instead. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 83136 W: 21145 L: 20758 D: 41233 Ptnml(0-2): 279, 9772, 21090, 10137, 290 https://tests.stockfishchess.org/tests/view/653c0fbacc309ae839561584 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 273150 W: 67987 L: 67171 D: 137992 Ptnml(0-2): 155, 30730, 73966, 31592, 132 https://tests.stockfishchess.org/tests/view/653d9d02cc309ae839562fdf closes https://github.com/official-stockfish/Stockfish/pull/4863 bench: 1110556 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 483412c598a..8b2cc572fe7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1147,9 +1147,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if ((ss + 1)->cutoffCnt > 3) r++; - // Decrease reduction for first generated move (ttMove) + // Set reduction to 0 for first generated move (ttMove) + // Nullifies all previous reduction adjustments to ttMove and leaves only history to do them else if (move == ttMove) - r--; + r = 0; ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] From 442c294a07836e9e32ad8b3bad49a853cc6f47de Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Sun, 5 Nov 2023 16:01:52 +0100 Subject: [PATCH 0276/1309] Use stat_malus when decreasing stats This patch applies stat_bonus when increasing and stat_malus when decreasing stats. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 133792 W: 34221 L: 33758 D: 65813 Ptnml(0-2): 477, 15764, 33959, 16211, 485 https://tests.stockfishchess.org/tests/view/654699f3136acbc5735256b2 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 67374 W: 16912 L: 16523 D: 33939 Ptnml(0-2): 42, 7528, 18171, 7891, 55 https://tests.stockfishchess.org/tests/view/65474558136acbc5735263ab closes https://github.com/official-stockfish/Stockfish/pull/4864 bench: 1114417 --- src/search.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8b2cc572fe7..a8f178a31ca 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,7 +94,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(357 * d - 483, 1511); } +int stat_bonus(Depth d) { return std::min(364 * d - 438, 1501); } + +// History and stats update malus, based on depth +int stat_malus(Depth d) { return std::min(452 * d - 452, 1478); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -636,12 +639,12 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // the previous ply (~0 Elo on STC, ~2 Elo on LTC). if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_bonus(depth + 1)); + -stat_malus(depth + 1)); } // Penalty for a quiet ttMove that fails low (~1 Elo) else if (!ttCapture) { - int penalty = -stat_bonus(depth); + int penalty = -stat_malus(depth); thisThread->mainHistory[us][from_to(ttMove)] << penalty; update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); } @@ -1190,7 +1193,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); - int bonus = value <= alpha ? -stat_bonus(newDepth) + int bonus = value <= alpha ? -stat_malus(newDepth) : value >= beta ? stat_bonus(newDepth) : 0; @@ -1681,6 +1684,7 @@ void update_all_stats(const Position& pos, PieceType captured; int quietMoveBonus = stat_bonus(depth + 1); + int quietMoveMalus = stat_malus(depth + 1); if (!pos.capture_stage(bestMove)) { @@ -1692,15 +1696,18 @@ void update_all_stats(const Position& pos, thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] << quietMoveBonus; + int moveMalus = bestValue > beta + 168 ? quietMoveMalus // larger malus + : stat_malus(depth); // smaller malus + // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] [to_sq(quietsSearched[i])] - << -bestMoveBonus; - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bestMoveBonus; + << -moveMalus; + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -moveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - to_sq(quietsSearched[i]), -bestMoveBonus); + to_sq(quietsSearched[i]), -moveMalus); } } else @@ -1716,14 +1723,14 @@ void update_all_stats(const Position& pos, && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit || ((ss - 1)->currentMove == (ss - 1)->killers[0])) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveBonus); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveBonus; + captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveMalus; } } From d0e87104aa782176735442e1f6668f91014f07eb Mon Sep 17 00:00:00 2001 From: Taras Vuk Date: Mon, 6 Nov 2023 15:00:06 +0100 Subject: [PATCH 0277/1309] Remove pawn history from ProbCut constructor use same style as other history tables Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 184672 W: 46953 L: 46896 D: 90823 Ptnml(0-2): 604, 21095, 48887, 21140, 610 https://tests.stockfishchess.org/tests/view/654766b4136acbc573526602 closes https://github.com/official-stockfish/Stockfish/pull/4865 No functional change --- src/movepick.cpp | 13 +++++-------- src/movepick.h | 8 ++++---- src/search.cpp | 7 +++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 444477cf78e..0aba9a55fe7 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -89,7 +89,7 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory& ph, + const PawnHistory* ph, Move cm, const Move* killers) : pos(p), @@ -112,7 +112,7 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory& ph, + const PawnHistory* ph, Square rs) : pos(p), mainHistory(mh), @@ -129,11 +129,9 @@ MovePicker::MovePicker(const Position& p, // Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker( - const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph, const PawnHistory& ph) : +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), - pawnHistory(ph), ttMove(ttm), threshold(th) { assert(!pos.checkers()); @@ -188,6 +186,7 @@ void MovePicker::score() { m.value += (*continuationHistory[2])[pc][to] / 4; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; + m.value += (*pawnHistory)[pawn_structure(pos)][pc][to]; // bonus for checks m.value += bool(pos.check_squares(pt) & to) * 16384; @@ -209,8 +208,6 @@ void MovePicker::score() { : pt != PAWN ? bool(to & threatenedByPawn) * 15000 : 0) : 0; - - m.value += pawnHistory[pawn_structure(pos)][pc][to]; } else // Type == EVASIONS @@ -221,7 +218,7 @@ void MovePicker::score() { else m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + pawnHistory[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; + + (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; } } diff --git a/src/movepick.h b/src/movepick.h index f058ff0ee88..9f18997406f 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -144,7 +144,7 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory&, + const PawnHistory*, Move, const Move*); MovePicker(const Position&, @@ -153,9 +153,9 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory&, + const PawnHistory*, Square); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*, const PawnHistory&); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); private: @@ -170,7 +170,7 @@ class MovePicker { const ButterflyHistory* mainHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; - const PawnHistory& pawnHistory; + const PawnHistory* pawnHistory; Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; diff --git a/src/search.cpp b/src/search.cpp index a8f178a31ca..b947fc5f3e8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -855,8 +855,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(probCutBeta < VALUE_INFINITE); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory, - thisThread->pawnHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); while ((move = mp.next_move()) != MOVE_NONE) if (move != excludedMove && pos.legal(move)) @@ -915,7 +914,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, - thisThread->pawnHistory, countermove, ss->killers); + &thisThread->pawnHistory, countermove, ss->killers); value = bestValue; moveCountPruning = singularQuietLMR = false; @@ -1484,7 +1483,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // will be generated. Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, thisThread->pawnHistory, prevSq); + contHist, &thisThread->pawnHistory, prevSq); int quietCheckEvasions = 0; From 80b0e3754303c44bdcc53c01339a955d5677cd64 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Mon, 6 Nov 2023 16:12:30 +0100 Subject: [PATCH 0278/1309] Double weight of pawn history for quiet move ordering. I measured on my 1000 position bench the average additional added pawn history per depth. This shows on average negative value with even smaller values with increaing depth. A linear regression against depth get following formula: -1960 - 130 * depth For compensation add this to the used sort limit to maintain roughly the same proportion of sorted quiet moves. Remarks: 1. using no compensation failed here https://tests.stockfishchess.org/tests/view/6547664f136acbc5735265f0 2. using only the compensation failed at LTC: passed STC: https://tests.stockfishchess.org/tests/view/65477457136acbc5735266f8 failed LTC: https://tests.stockfishchess.org/tests/view/65487fc8136acbc573527d1c STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 98528 W: 25109 L: 24699 D: 48720 Ptnml(0-2): 334, 11586, 25009, 12006, 329 https://tests.stockfishchess.org/tests/view/65475873136acbc5735264f7 LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 69726 W: 17467 L: 17073 D: 35186 Ptnml(0-2): 39, 7814, 18769, 8196, 45 https://tests.stockfishchess.org/tests/view/6547e759136acbc573527071 closes https://github.com/official-stockfish/Stockfish/pull/4866 Bench: 1379422 --- src/movepick.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0aba9a55fe7..798f51ddb6e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -181,12 +181,12 @@ void MovePicker::score() { // histories m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value += 2 * (*pawnHistory)[pawn_structure(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to] / 4; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; - m.value += (*pawnHistory)[pawn_structure(pos)][pc][to]; // bonus for checks m.value += bool(pos.check_squares(pt) & to) * 16384; @@ -302,7 +302,7 @@ Move MovePicker::next_move(bool skipQuiets) { endMoves = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -3000 * depth); + partial_insertion_sort(cur, endMoves, -1960 - 3130 * depth); } ++stage; From fbc6b275051773b491c1c180fd7ff331194ca0f1 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 7 Nov 2023 13:03:05 -0500 Subject: [PATCH 0279/1309] Simplify away optimism average score offset params Passed non-regression STC: https://tests.stockfishchess.org/tests/view/654abf6b136acbc57352ac4b LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 49664 W: 12687 L: 12477 D: 24500 Ptnml(0-2): 138, 5840, 12703, 5976, 175 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/654b638e136acbc57352b961 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 347166 W: 85561 L: 85676 D: 175929 Ptnml(0-2): 206, 39569, 94150, 39450, 208 closes https://github.com/official-stockfish/Stockfish/pull/4871 bench 1257641 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b947fc5f3e8..3ce74126aa7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -372,8 +372,8 @@ void Thread::search() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 103 * (avg + 33) / (std::abs(avg + 34) + 119); - optimism[~us] = -116 * (avg + 40) / (std::abs(avg + 12) + 123); + optimism[us] = 103 * avg / (std::abs(avg) + 119); + optimism[~us] = -116 * avg / (std::abs(avg) + 123); // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From 863a1f2b4cb233be3126b244cbd8f6c8b9b4d13c Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:45:36 +0100 Subject: [PATCH 0280/1309] Introduce recapture extensions When in a PV-node this patch extends ttMove if it is a recapture and has a good history. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 83840 W: 21560 L: 21166 D: 41114 Ptnml(0-2): 343, 9905, 21027, 10305, 340 https://tests.stockfishchess.org/tests/view/654f4b02136acbc5735308ab Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 165318 W: 41068 L: 40476 D: 83774 Ptnml(0-2): 98, 18670, 44517, 19290, 84 https://tests.stockfishchess.org/tests/view/654fde04136acbc5735314e0 closes https://github.com/official-stockfish/Stockfish/pull/4872 Bench: 1393911 --- src/search.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 3ce74126aa7..5c53c0dae8b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1100,6 +1100,12 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else if (PvNode && move == ttMove && move == ss->killers[0] && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) extension = 1; + + // Recapture extensions (~1 Elo) + else if (PvNode && move == ttMove && to_sq(move) == prevSq + && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] + > 4000) + extension = 1; } // Add extension to new depth From f9d8717844643e4ea3723f5ea240bf5d22800df7 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 12 Nov 2023 15:51:38 +0100 Subject: [PATCH 0281/1309] Symmetrize optimism Removes some additional parameters, making the term more logical at the same time. Passed STC: https://tests.stockfishchess.org/tests/view/6550e896136acbc5735328ed LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 271104 W: 68441 L: 68480 D: 134183 Ptnml(0-2): 827, 32590, 68816, 32433, 886 Passed LTC: https://tests.stockfishchess.org/tests/view/65523858136acbc5735344f7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 198954 W: 49250 L: 49211 D: 100493 Ptnml(0-2): 93, 22565, 54117, 22614, 88 closes https://github.com/official-stockfish/Stockfish/pull/4874 Bench: 1334248 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5c53c0dae8b..5bea5945c24 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -372,8 +372,8 @@ void Thread::search() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 103 * avg / (std::abs(avg) + 119); - optimism[~us] = -116 * avg / (std::abs(avg) + 123); + optimism[us] = 110 * avg / (std::abs(avg) + 121); + optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail // high/low, re-search with a bigger window until we don't fail From d89217766b748eccd08f58f35209d762d8bf0600 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 15 Nov 2023 21:00:37 +0100 Subject: [PATCH 0282/1309] CI updates - updates the SDE action to v2.2 - removes the linux x86-32 builds, which were almost unused, and the build process under SDE started failing recently, possibly related to glibc update (The futex facility returned an unexpected error code.) closes https://github.com/official-stockfish/Stockfish/pull/4875 No functional change --- .github/workflows/stockfish_binaries.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index fadfbcfcd04..6da576e40a0 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -13,6 +13,7 @@ jobs: NAME: ${{ matrix.config.simple_name }} BINARY: ${{ matrix.binaries }} strategy: + fail-fast: false matrix: config: - name: Ubuntu 20.04 GCC @@ -42,7 +43,6 @@ jobs: sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future -- archive_ext: zip binaries: - - x86-32 - x86-64 - x86-64-sse41-popcnt - x86-64-avx2 @@ -54,10 +54,6 @@ jobs: exclude: - binaries: x86-64-avxvnni config: { ubuntu-20.04 } - - binaries: x86-32 - config: { os: windows-2022} - - binaries: x86-32 - config: { os: macos-13 } - binaries: x86-64-avxvnni config: { os: macos-13 } - binaries: x86-64-avx512 @@ -75,12 +71,6 @@ jobs: with: fetch-depth: 0 - - name: Download required Linux packages - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install g++-multilib g++-11-multilib - - name: Download required macOS packages if: runner.os == 'macOS' run: brew install coreutils @@ -100,7 +90,7 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@v2.1 + uses: petarpetrovt/setup-sde@6f4926100f31791716b11d25c0f3f35809d44f84 with: environmentVariableName: SDE_DIR sdeVersion: 9.14.0 From 7970236e9ea64796d5c7597cb1aedde737751f07 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 16 Nov 2023 08:40:25 +0100 Subject: [PATCH 0283/1309] Fix undefined behavior in search. We use following line to clamp the search depth in some range: Depth d = std::clamp(newDepth - r, 1, newDepth + 1); Through negative extension its possible that the maximum value becomes smaller than the minimum value but then the behavior is undefined (see https://en.cppreference.com/w/cpp/algorithm/clamp). So replace this line with a safe implementation. Remark: We have in recent master already one line where up to 3 negative extensions are possible which could trigger this undefined behavior but this can only be happen for completed depth > 24 so its not discovered by our default bench. Recent negative extension tests by @fauzi shows then this undefined behavior with wrong bench numbers. closes https://github.com/official-stockfish/Stockfish/pull/4877 No functional change --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5bea5945c24..fa479c4b827 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1178,7 +1178,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension // beyond the first move depth. This may lead to hidden double extensions. - Depth d = std::clamp(newDepth - r, 1, newDepth + 1); + // To prevent problems when the max value is less than the min value, + // std::clamp has been replaced by a more robust implementation. + Depth d = std::max(1, std::min(newDepth - r, newDepth + 1)); value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From 504bf0e8b8cf2dd818deb623c5ad7e428e504cd8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 20 Nov 2023 18:56:34 +0100 Subject: [PATCH 0284/1309] Change depth - 1 to newDepth Replacing 'depth - 1' with 'newDepth' in the singularbeta formula utilizes existing variables more succinctly. closes https://github.com/official-stockfish/Stockfish/pull/4876 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fa479c4b827..7d567b8ab71 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1045,7 +1045,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo && tte->depth() >= depth - 3) { Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; - Depth singularDepth = (depth - 1) / 2; + Depth singularDepth = newDepth / 2; ss->excludedMove = move; value = From b59786e750a59d3d7cff2630cf284553f607ed29 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 16 Nov 2023 14:00:11 +0300 Subject: [PATCH 0285/1309] Remove doEvenDeeperSearch Passed STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 51040 W: 13014 L: 12804 D: 25222 Ptnml(0-2): 166, 6032, 12917, 6236, 169 https://tests.stockfishchess.org/tests/view/65525aa1136acbc573534801 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 165168 W: 40863 L: 40789 D: 83516 Ptnml(0-2): 73, 18783, 44792, 18869, 67 https://tests.stockfishchess.org/tests/view/65535af5136acbc573535c84 closes https://github.com/official-stockfish/Stockfish/pull/4880 Bench: 1477007 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7d567b8ab71..ae83ab34758 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1190,12 +1190,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // 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 + 51 + 10 * (newDepth - d)); - const bool doEvenDeeperSearch = value > alpha + 700 && ss->doubleExtensions <= 6; const bool doShallowerSearch = value < bestValue + newDepth; - ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch; - - newDepth += doDeeperSearch - doShallowerSearch + doEvenDeeperSearch; + newDepth += doDeeperSearch - doShallowerSearch; if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); From b4e9ee72e36aadd0e653ac4ab5c07a9e3d639aca Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 20 Nov 2023 19:09:48 +0100 Subject: [PATCH 0286/1309] Reformat some comments Tests used to derive some Elo worth comments: https://tests.stockfishchess.org/tests/view/653cf6b7cc309ae83956263a https://tests.stockfishchess.org/tests/view/655250b7136acbc573534711 https://tests.stockfishchess.org/tests/view/65525767136acbc5735347b9 https://tests.stockfishchess.org/tests/view/65525aa1136acbc573534801 closes https://github.com/official-stockfish/Stockfish/pull/4879 No functional change --- src/misc.cpp | 19 +++++++++---------- src/movepick.h | 9 ++++----- src/position.cpp | 27 ++++++++++++--------------- src/search.cpp | 16 ++++++++-------- src/uci.cpp | 9 ++++----- src/uci.h | 2 +- 6 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 3e9006156c6..4193f8d2c7d 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -393,8 +393,8 @@ void dbg_print() { } -// Used to serialize access to std::cout to avoid multiple threads writing at -// the same time. +// Used to serialize access to std::cout +// to avoid multiple threads writing at the same time. std::ostream& operator<<(std::ostream& os, SyncCout sc) { static std::mutex m; @@ -558,7 +558,7 @@ void* aligned_large_pages_alloc(size_t allocSize) { constexpr size_t alignment = 4096; // assumed small page size #endif - // round up to multiples of alignment + // Round up to multiples of alignment size_t size = ((allocSize + alignment - 1) / alignment) * alignment; void* mem = std_aligned_alloc(alignment, size); #if defined(MADV_HUGEPAGE) @@ -600,7 +600,7 @@ void bindThisThread(size_t) {} #else -// Retrieves logical processor information using Windows specific +// Retrieves logical processor information using Windows-specific // API and returns the best node id for the thread with index idx. Original // code from Texel by Peter Österlund. static int best_node(size_t idx) { @@ -660,8 +660,7 @@ static int best_node(size_t idx) { groups.push_back(n); // In case a core has more than one logical processor (we assume 2) and we - // have still threads to allocate, then spread them evenly across available - // nodes. + // still have threads to allocate, spread them evenly across available nodes. for (int t = 0; t < threads - cores; t++) groups.push_back(t % nodes); @@ -731,7 +730,7 @@ std::string workingDirectory; // path of the working directory void init([[maybe_unused]] int argc, char* argv[]) { std::string pathSeparator; - // extract the path+name of the executable binary + // Extract the path+name of the executable binary argv0 = argv[0]; #ifdef _WIN32 @@ -747,14 +746,14 @@ void init([[maybe_unused]] int argc, char* argv[]) { pathSeparator = "/"; #endif - // extract the working directory + // Extract the working directory workingDirectory = ""; char buff[40000]; char* cwd = GETCWD(buff, 40000); if (cwd) workingDirectory = cwd; - // extract the binary directory path from argv0 + // Extract the binary directory path from argv0 binaryDirectory = argv0; size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) @@ -762,7 +761,7 @@ void init([[maybe_unused]] int argc, char* argv[]) { else binaryDirectory.resize(pos + 1); - // pattern replacement: "./" at the start of path is replaced by the working directory + // Pattern replacement: "./" at the start of path is replaced by the working directory if (binaryDirectory.find("." + pathSeparator) == 0) binaryDirectory.replace(0, 1, workingDirectory); } diff --git a/src/movepick.h b/src/movepick.h index 9f18997406f..299925a582e 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -96,11 +96,10 @@ enum StatsType { Captures }; -// ButterflyHistory records how often quiet moves have been successful or -// unsuccessful during the current search, and is used for reduction and move -// ordering decisions. It uses 2 tables (one for each color) indexed by -// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards -// (~11 elo) +// ButterflyHistory records how often quiet moves have been successful or unsuccessful +// during the current search, and is used for reduction and move ordering decisions. +// It uses 2 tables (one for each color) indexed by the move's from and to squares, +// see www.chessprogramming.org/Butterfly_Boards (~11 elo) using ButterflyHistory = Stats; // CounterMoveHistory stores counter moves indexed by [piece][to] of the previous diff --git a/src/position.cpp b/src/position.cpp index 2bb47871555..c45dd7b2e22 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -371,9 +371,8 @@ void Position::set_state() const { } -// Overload to initialize the position object with -// the given endgame code string like "KBPKN". It is mainly a helper to -// get the material key out of an endgame code. +// Overload to initialize the position object with the given endgame code string +// like "KBPKN". It's mainly a helper to get the material key out of an endgame code. Position& Position::set(const string& code, Color c, StateInfo* si) { assert(code[0] == 'K'); @@ -472,8 +471,8 @@ void Position::update_slider_blockers(Color c) const { } -// Computes a bitboard of all pieces which attack a -// given square. Slider attacks use the occupied bitboard to indicate occupancy. +// Computes a bitboard of all pieces which attack a given square. +// Slider attacks use the occupied bitboard to indicate occupancy. Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) @@ -575,8 +574,7 @@ bool Position::pseudo_legal(const Move m) const { // Handle the special case of a pawn move if (type_of(pc) == PAWN) { - // We have already handled promotion moves, so destination - // cannot be on the 8th/1st rank. + // We have already handled promotion moves, so destination cannot be on the 8th/1st rank if ((Rank8BB | Rank1BB) & to) return false; @@ -639,10 +637,9 @@ bool Position::gives_check(Move m) const { case PROMOTION : return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); - // En passant capture with check? We have already handled the case - // of direct checks and ordinary discovered check, so the only case we - // need to handle is the unusual case of a discovered check through - // the captured pawn. + // En passant capture with check? We have already handled the case of direct + // checks and ordinary discovered check, so the only case we need to handle + // is the unusual case of a discovered check through the captured pawn. case EN_PASSANT : { Square capsq = make_square(file_of(to), rank_of(from)); Bitboard b = (pieces() ^ from ^ capsq) | to; @@ -928,8 +925,8 @@ void Position::undo_move(Move m) { } -// Helper used to do/undo a castling move. This -// is a bit tricky in Chess960 where from/to squares can overlap. +// Helper used to do/undo a castling move. This is a bit +// tricky in Chess960 where from/to squares can overlap. template void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { @@ -1244,8 +1241,8 @@ void Position::flip() { } -// Performs some consistency checks for the -// position object and raise an assert if something wrong is detected. +// Performs some consistency checks for the position object +// and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. bool Position::pos_is_ok() const { diff --git a/src/search.cpp b/src/search.cpp index ae83ab34758..c878b0aba1d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -834,8 +834,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (depth <= 0) return qsearch(pos, ss, alpha, beta); - // For cutNodes without a ttMove, we decrease depth by 2 - // if current depth >= 8. + // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough. if (cutNode && depth >= 8 && !ttMove) depth -= 2; @@ -1037,7 +1036,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // 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 this type of time controls. + // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) @@ -1079,7 +1078,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // we do not know 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 currnet beta (~7 Elo) + // If the ttMove is assumed to fail high over current beta (~7 Elo) else if (ttValue >= beta) extension = -2 - !PvNode; @@ -1155,7 +1154,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if ((ss + 1)->cutoffCnt > 3) r++; - // Set reduction to 0 for first generated move (ttMove) + // Set reduction to 0 for first picked move (ttMove) (~2 Elo) // Nullifies all previous reduction adjustments to ttMove and leaves only history to do them else if (move == ttMove) r = 0; @@ -1189,8 +1188,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // 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 + 51 + 10 * (newDepth - d)); - const bool doShallowerSearch = value < bestValue + newDepth; + const bool doDeeperSearch = + value > (bestValue + 51 + 10 * (newDepth - d)); // (~1 Elo) + const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1459,7 +1459,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { bestValue = ttValue; } else - // In case of null move search use previous static eval with a different sign + // In case of null move search, use previous static eval with a different sign ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; diff --git a/src/uci.cpp b/src/uci.cpp index 8139fae4fd8..95f6f349dd3 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -82,8 +82,8 @@ void position(Position& pos, std::istringstream& is, StateListPtr& states) { } } -// Prints the evaluation of the current position, consistent with -// the UCI options set so far. +// Prints the evaluation of the current position, +// consistent with the UCI options set so far. void trace_eval(Position& pos) { StateListPtr states(new std::deque(1)); @@ -122,9 +122,8 @@ void setoption(std::istringstream& is) { } -// Called when the engine receives the "go" UCI command. The function -// sets the thinking time and other parameters from the input string, then starts -// with a search. +// Called when the engine receives the "go" UCI command. The function sets the +// thinking time and other parameters from the input string then stars with a search void go(Position& pos, std::istringstream& is, StateListPtr& states) { diff --git a/src/uci.h b/src/uci.h index be5c70c54ce..55fb47c29ef 100644 --- a/src/uci.h +++ b/src/uci.h @@ -36,7 +36,7 @@ namespace UCI { // to the UCI centipawn result used in output. This value is derived from // the win_rate_model() such that Stockfish outputs an advantage of // "100 centipawns" for a position if the engine has a 50% probability to win -// from this position in selfplay at fishtest LTC time control. +// from this position in self-play at fishtest LTC time control. const int NormalizeToPawnValue = 328; class Option; From 13426a93c187c4953388a4484b8da69ee6f26fa3 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 23 Nov 2023 22:13:11 +0100 Subject: [PATCH 0287/1309] Simplify history update. Removal of the slowdown factor from the history update formula with corresponding adjustment of the stat bonus used in the search. Passed STC: https://tests.stockfishchess.org/tests/view/655e1079136acbc573544744 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 128096 W: 32355 L: 32235 D: 63506 Ptnml(0-2): 466, 15187, 32573, 15405, 417 Passed LTC: https://tests.stockfishchess.org/tests/view/655f4e60136acbc573546266 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 50652 W: 12653 L: 12459 D: 25540 Ptnml(0-2): 28, 5666, 13751, 5846, 35 closes https://github.com/official-stockfish/Stockfish/pull/4883 Bench: 1303857 --- src/movepick.h | 2 +- src/search.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 299925a582e..e032b0c7c79 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -58,7 +58,7 @@ class StatsEntry { assert(abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += (bonus * D - entry * abs(bonus)) / (D * 5 / 4); + entry += bonus - entry * abs(bonus) / D; assert(abs(entry) <= D); } diff --git a/src/search.cpp b/src/search.cpp index c878b0aba1d..55be43b30f5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,10 +94,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(364 * d - 438, 1501); } +int stat_bonus(Depth d) { return std::min(291 * d - 350, 1200); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(452 * d - 452, 1478); } +int stat_malus(Depth d) { return std::min(361 * d - 361, 1182); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -746,7 +746,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-18 * int((ss - 1)->staticEval + ss->staticEval), -1812, 1812); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1449, 1449); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; From 757ae2ff53714b975066cd9eb3b518611bc06b11 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:16:56 +0800 Subject: [PATCH 0288/1309] Simplify move history reduction Recent VLTC search tuning has suggested that the depth limit can be increased by a lot. This patch simplifies away the depth-based bonus from statScore reduction, making the divisor a constant. Passed STC: https://tests.stockfishchess.org/tests/view/656201f5136acbc573549791 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 91520 W: 23130 L: 22967 D: 45423 Ptnml(0-2): 282, 10947, 23141, 11106, 284 Passed LTC: https://tests.stockfishchess.org/tests/view/6562b43a136acbc57354a581 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 352902 W: 86796 L: 86917 D: 179189 Ptnml(0-2): 190, 40227, 95741, 40100, 193 closes https://github.com/official-stockfish/Stockfish/pull/4886 Bench: 1297179 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 55be43b30f5..6580f520130 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1165,7 +1165,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo + (*contHist[3])[movedPiece][to_sq(move)] - 3848; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / (10216 + 3855 * (depth > 5 && depth < 23)); + r -= ss->statScore / 14200; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has From f17db4641e0ec3a3d633cff6abc83e980a04ac4c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 2 Dec 2023 11:33:11 +0100 Subject: [PATCH 0289/1309] Simplify doDeeperSearch Removing dependence on d simplifies the doDeeperSearch formula and eliminates a variable that is not necessary in this context. Passed STC: https://tests.stockfishchess.org/tests/view/65647980136acbc57354c9f6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 37440 W: 9558 L: 9334 D: 18548 Ptnml(0-2): 127, 4439, 9375, 4641, 138 Passed LTC: https://tests.stockfishchess.org/tests/view/6564c3f0136acbc57354d126 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 113946 W: 27993 L: 27864 D: 58089 Ptnml(0-2): 67, 12975, 30783, 13058, 90 closes https://github.com/official-stockfish/Stockfish/pull/4888 Bench: 1427733 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6580f520130..0a12c85b721 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1188,9 +1188,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // 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 + 51 + 10 * (newDepth - d)); // (~1 Elo) - const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) + const bool doDeeperSearch = value > (bestValue + 50 + 2 * newDepth); // (~1 Elo) + const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; From 883163395ed464d17c6732e227a2d2c3c0b26f1e Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sat, 2 Dec 2023 11:37:52 +0100 Subject: [PATCH 0290/1309] Simplify promotion move generation closes https://github.com/official-stockfish/Stockfish/pull/4892 No functional change --- src/movegen.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 16da659d5e3..7d6856bb036 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -31,18 +31,12 @@ namespace { template ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { - if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) - { + constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; + + if constexpr (Type == CAPTURES || all) *moveList++ = make(to - D, to, QUEEN); - if constexpr (Enemy && Type == CAPTURES) - { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); - } - } - if constexpr ((Type == QUIETS && !Enemy) || Type == EVASIONS || Type == NON_EVASIONS) + if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all) { *moveList++ = make(to - D, to, ROOK); *moveList++ = make(to - D, to, BISHOP); From 7dc40ac6437ec96d312e387b76573e5c496bd0b6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 29 Nov 2023 13:47:25 +0300 Subject: [PATCH 0291/1309] Simplify quietMoveMalus malus Use a simple depth instead of depth + 1 in the quietMoveMalus formula. Passed STC: https://tests.stockfishchess.org/tests/view/65636bf0136acbc57354b662 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 105248 W: 26680 L: 26532 D: 52036 Ptnml(0-2): 409, 12590, 26481, 12732, 412 Passed LTC: https://tests.stockfishchess.org/tests/view/6563b5db136acbc57354bcab LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 204204 W: 50200 L: 50166 D: 103838 Ptnml(0-2): 123, 23331, 55145, 23395, 108 closes https://github.com/official-stockfish/Stockfish/pull/4893 Bench: 1717495 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0a12c85b721..786d25c615b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1687,7 +1687,7 @@ void update_all_stats(const Position& pos, PieceType captured; int quietMoveBonus = stat_bonus(depth + 1); - int quietMoveMalus = stat_malus(depth + 1); + int quietMoveMalus = stat_malus(depth); if (!pos.capture_stage(bestMove)) { @@ -1898,9 +1898,9 @@ string UCI::pv(const Position& pos, Depth depth) { } -// Called in case we have no ponder move -// before exiting the search, for instance, in case we stop the search during a -// fail high at root. We try hard to have a ponder move to return to the GUI, +// Called in case we have no ponder move before exiting the search, +// for instance, in case we stop the search during a fail high at root. +// We try hard to have a ponder move to return to the GUI, // otherwise in case of 'ponder on' we have nothing to think about. bool RootMove::extract_ponder_from_tt(Position& pos) { From 85403a89bac9fe3538ae410fe651364abf78c504 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:20:43 +0100 Subject: [PATCH 0292/1309] Skip LMR for 2nd move at the root only This patch reverts commit by Vizvezdenec: https://github.com/official-stockfish/Stockfish/commit/27139dedac14af400f5b18e2ab50aca3f8cf0e33 Passed STC: https://tests.stockfishchess.org/tests/view/65660b4a136acbc57354f13d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 301952 W: 76271 L: 76344 D: 149337 Ptnml(0-2): 1053, 36293, 76348, 36238, 1044 Passed LTC: https://tests.stockfishchess.org/tests/view/656738ab136acbc573550e39 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 25050 W: 6283 L: 6063 D: 12704 Ptnml(0-2): 10, 2756, 6775, 2972, 12 closes https://github.com/official-stockfish/Stockfish/pull/4895 Bench: 1722961 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 786d25c615b..409a3c1d429 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1171,7 +1171,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // We use various heuristics for the sons of a node after the first son has // been searched. In general, we would like to reduce them, but there are many // cases where we extend a son if it has good chances to be "interesting". - if (depth >= 2 && moveCount > 1 + (PvNode && ss->ply <= 1) + if (depth >= 2 && moveCount > 1 + rootNode && (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1))) { // In general we want to cap the LMR depth search at newDepth, but when From 15d47a2b3821b92c4d048f39f7f43c301299d365 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 29 Nov 2023 17:25:01 +0800 Subject: [PATCH 0293/1309] Remove recaptures stage in qsearch Simplify an old commit https://github.com/official-stockfish/Stockfish/commit/72760c05c64d1fb2bb71c2ac54acfbeecf513b87. Search is not stuck on the test position given r1n1n1b1/1P1P1P1P/1N1N1N2/2RnQrRq/2pKp3/3BNQbQ/k7/4Bq2 w - - 0 1 Passed STC: https://tests.stockfishchess.org/tests/view/6567050d136acbc573550919 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 236160 W: 59475 L: 59475 D: 117210 Ptnml(0-2): 841, 28266, 59816, 28366, 791 Passed LTC: https://tests.stockfishchess.org/tests/view/6567d133136acbc573551c78 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 201690 W: 49630 L: 49593 D: 102467 Ptnml(0-2): 128, 23214, 54122, 23255, 126 closes https://github.com/official-stockfish/Stockfish/pull/4896 Bench: 1604361 --- src/movepick.cpp | 7 ++----- src/movepick.h | 4 +--- src/search.cpp | 2 +- src/types.h | 5 ++--- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 798f51ddb6e..0267a8e2e6f 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -112,15 +112,13 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory* ph, - Square rs) : + const PawnHistory* ph) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), pawnHistory(ph), ttMove(ttm), - recaptureSquare(rs), depth(d) { assert(d <= 0); @@ -340,8 +338,7 @@ Move MovePicker::next_move(bool skipQuiets) { return select([&]() { return pos.see_ge(*cur, threshold); }); case QCAPTURE : - if (select( - [&]() { return depth > DEPTH_QS_RECAPTURES || to_sq(*cur) == recaptureSquare; })) + if (select([]() { return true; })) return *(cur - 1); // If we did not find any move and we do not try checks, we have finished diff --git a/src/movepick.h b/src/movepick.h index e032b0c7c79..7828fa19d97 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -152,8 +152,7 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory*, - Square); + const PawnHistory*); MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); @@ -173,7 +172,6 @@ class MovePicker { Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; - Square recaptureSquare; Value threshold; Depth depth; ExtMove moves[MAX_MOVES]; diff --git a/src/search.cpp b/src/search.cpp index 409a3c1d429..16003138483 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1487,7 +1487,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // will be generated. Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, &thisThread->pawnHistory, prevSq); + contHist, &thisThread->pawnHistory); int quietCheckEvasions = 0; diff --git a/src/types.h b/src/types.h index 7ac2f84951a..0575f1d453c 100644 --- a/src/types.h +++ b/src/types.h @@ -205,9 +205,8 @@ constexpr Value PieceValue[PIECE_NB] = { using Depth = int; enum : int { - DEPTH_QS_CHECKS = 0, - DEPTH_QS_NO_CHECKS = -1, - DEPTH_QS_RECAPTURES = -5, + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NO_CHECKS = -1, DEPTH_NONE = -6, From 08cdbca56fac98513481683a92eb1ecdc00d3f6e Mon Sep 17 00:00:00 2001 From: lonfom169 <50217346+lonfom169@users.noreply.github.com> Date: Thu, 30 Nov 2023 01:12:19 -0300 Subject: [PATCH 0294/1309] Tweak return value in futility pruning In futility pruning, return the average between eval and beta. Passed STC: https://tests.stockfishchess.org/tests/view/65680bb6136acbc5735521d7 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 15200 W: 3926 L: 3642 D: 7632 Ptnml(0-2): 36, 1699, 3867, 1941, 57 Passed LTC: https://tests.stockfishchess.org/tests/view/656817fc136acbc573552304 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 200376 W: 49700 L: 49036 D: 101640 Ptnml(0-2): 110, 22584, 54137, 23246, 111 closes https://github.com/official-stockfish/Stockfish/pull/4897 Bench: 1403703 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 16003138483..ba5ea2e54f9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -780,7 +780,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo >= beta && eval >= beta && eval < 29462 // smaller than TB wins && (!ttMove || ttCapture)) - return eval; + return (eval + beta) / 2; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17257 && eval >= beta From 8f65953583a2abc34041b087120a378e22d0509d Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 3 Dec 2023 13:37:59 +0100 Subject: [PATCH 0295/1309] Temporarily disable CI include checks The include checks currently fail because of broken LLVM nightly packages: https://github.com/llvm/llvm-project/issues/73402. closes https://github.com/official-stockfish/Stockfish/pull/4899 No functional change --- .github/workflows/stockfish.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 1ed4b92d4ca..83dd1b9c86c 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -33,8 +33,9 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - Analyzers: - uses: ./.github/workflows/stockfish_analyzers.yml + # The include checks currently fail because of broken LLVM nightly packages: https://github.com/llvm/llvm-project/issues/73402 + #Analyzers: + # uses: ./.github/workflows/stockfish_analyzers.yml Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: From 0ff2ea654971445f4e8955840bcb974dc62e5106 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Sun, 3 Dec 2023 13:39:52 +0100 Subject: [PATCH 0296/1309] Update GitHub workflows - Use the latest version of the actions - Use commit hash for actions from little providers - Update Intel SDE to 9.27 closes https://github.com/official-stockfish/Stockfish/pull/4900 No functional change --- .github/workflows/codeql.yml | 2 +- .github/workflows/stockfish.yml | 2 +- .github/workflows/stockfish_analyzers.yml | 4 ++-- .github/workflows/stockfish_arm_binaries.yml | 6 +++--- .github/workflows/stockfish_binaries.yml | 16 ++++++++-------- .github/workflows/stockfish_compile_test.yml | 2 +- .github/workflows/stockfish_format_check.yml | 8 ++++---- .github/workflows/stockfish_sanitizers.yml | 2 +- .github/workflows/stockfish_test.yml | 6 +++--- 9 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 863f219ca7b..054be90040c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 83dd1b9c86c..6d71fef5aef 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -26,7 +26,7 @@ jobs: echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV # delete old previous pre-release and tag - - uses: dev-drprasad/delete-tag-and-release@v0.2.1 + - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 if: env.COMMIT_SHA != 'null' with: tag_name: ${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_analyzers.yml b/.github/workflows/stockfish_analyzers.yml index 5f985cc859f..f54cdd7ca6b 100644 --- a/.github/workflows/stockfish_analyzers.yml +++ b/.github/workflows/stockfish_analyzers.yml @@ -11,12 +11,12 @@ jobs: shell: bash steps: - name: Checkout Stockfish - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: Stockfish - name: Checkout include-what-you-use - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: include-what-you-use/include-what-you-use ref: f25caa280dc3277c4086ec345ad279a2463fea0f diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml index 4d7f3d55c1a..203c00f2ce2 100644 --- a/.github/workflows/stockfish_arm_binaries.yml +++ b/.github/workflows/stockfish_arm_binaries.yml @@ -46,7 +46,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -139,7 +139,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: files: stockfish-android-${{ matrix.binaries }}.tar @@ -162,7 +162,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 6da576e40a0..5b3a522625c 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -23,7 +23,7 @@ jobs: comp: gcc shell: bash archive_ext: tar - sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-lin/sde -future -- + sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future -- - name: MacOS 13 Apple Clang os: macos-13 simple_name: macos @@ -40,7 +40,7 @@ jobs: msys_env: x86_64-gcc shell: msys2 {0} ext: .exe - sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.14.0-2022-10-25-win/sde.exe -future -- + sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future -- archive_ext: zip binaries: - x86-64 @@ -67,7 +67,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -77,7 +77,7 @@ jobs: - name: Install fixed GCC on Linux if: runner.os == 'Linux' - uses: egor-tensin/setup-gcc@v1 + uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 with: version: 11 @@ -90,10 +90,10 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@6f4926100f31791716b11d25c0f3f35809d44f84 + uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 with: environmentVariableName: SDE_DIR - sdeVersion: 9.14.0 + sdeVersion: 9.27.0 - name: Download the used network from the fishtest framework run: make net @@ -183,7 +183,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} @@ -206,7 +206,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 808fcb55f7b..1adc3e34b7c 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -51,7 +51,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup msys and install required packages if: runner.os == 'Windows' diff --git a/.github/workflows/stockfish_format_check.yml b/.github/workflows/stockfish_format_check.yml index cb16b327871..7a47ab6f406 100644 --- a/.github/workflows/stockfish_format_check.yml +++ b/.github/workflows/stockfish_format_check.yml @@ -16,12 +16,12 @@ jobs: name: clang-format check runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - name: Run clang-format style check - uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e + uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 id: clang-format continue-on-error: true with: @@ -30,7 +30,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome == 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | clang-format 17 needs to be run on this PR. @@ -42,7 +42,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome != 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/stockfish_sanitizers.yml index b137f50eb06..e3f046178f5 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/stockfish_sanitizers.yml @@ -35,7 +35,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download required linux packages run: | diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index 72f0c22e136..cff3ef1b184 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -95,7 +95,7 @@ jobs: working-directory: src shell: ${{ matrix.config.shell }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -121,11 +121,11 @@ jobs: - name: Set up QEMU if: matrix.config.base_image - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx if: matrix.config.base_image - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Build Docker container if: matrix.config.base_image From 7a8bcfc229ca6e4e44c0b284b7609a3aa26fa1ee Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 4 Dec 2023 11:31:16 +0100 Subject: [PATCH 0297/1309] Remove cutNode condition cutNode condition seems to be irrelevant. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 24224 W: 6206 L: 5970 D: 12048 Ptnml(0-2): 69, 2818, 6122, 3014, 89 https://tests.stockfishchess.org/tests/view/65686910136acbc5735529ec Passed LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 236538 W: 58624 L: 58622 D: 119292 Ptnml(0-2): 136, 26955, 64091, 26945, 142 https://tests.stockfishchess.org/tests/view/6568925a136acbc573552d8f closes https://github.com/official-stockfish/Stockfish/pull/4901 Bench: 1244386 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ba5ea2e54f9..b3ca8c9afe5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1207,8 +1207,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { - // Increase reduction for cut nodes without ttMove (~1 Elo) - if (!ttMove && cutNode) + // Increase reduction if ttMove is not present (~1 Elo) + if (!ttMove) r += 2; // Note that if expected reduction is high, we reduce search depth by 1 here From dadff4698651336ebd07c414f74c1e707cd9bd15 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 5 Dec 2023 11:49:12 +0100 Subject: [PATCH 0298/1309] Revert "Temporarily disable CI include checks" This reverts commit 8f65953583a2abc34041b087120a378e22d0509d. closes https://github.com/official-stockfish/Stockfish/pull/4904 No functional change --- .github/workflows/stockfish.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 6d71fef5aef..7bbb53d5c62 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -33,9 +33,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # The include checks currently fail because of broken LLVM nightly packages: https://github.com/llvm/llvm-project/issues/73402 - #Analyzers: - # uses: ./.github/workflows/stockfish_analyzers.yml + Analyzers: + uses: ./.github/workflows/stockfish_analyzers.yml Sanitizers: uses: ./.github/workflows/stockfish_sanitizers.yml Tests: From 53ad6d23b0e7ec2814579d4acba7c02c2b12008f Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Tue, 5 Dec 2023 15:41:10 +0100 Subject: [PATCH 0299/1309] Remove moveMalus Passed STC: https://tests.stockfishchess.org/tests/view/656e0bb86980e15f69c763fa LLR: 3.15 (-2.94,2.94) <-1.75,0.25> Total: 123008 W: 30973 L: 30831 D: 61204 Ptnml(0-2): 368, 14032, 32568, 14162, 374 closes https://github.com/official-stockfish/Stockfish/pull/4905 No functional change --- src/search.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b3ca8c9afe5..39711f3c611 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1699,18 +1699,15 @@ void update_all_stats(const Position& pos, thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] << quietMoveBonus; - int moveMalus = bestValue > beta + 168 ? quietMoveMalus // larger malus - : stat_malus(depth); // smaller malus - // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] [to_sq(quietsSearched[i])] - << -moveMalus; - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -moveMalus; + << -quietMoveMalus; + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - to_sq(quietsSearched[i]), -moveMalus); + to_sq(quietsSearched[i]), -quietMoveMalus); } } else From 8724503d9c8f15f9185bd4394e425e809f3992c1 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Wed, 6 Dec 2023 18:01:58 +0100 Subject: [PATCH 0300/1309] Simplify the code to get the native flags closes https://github.com/official-stockfish/Stockfish/pull/4908 No functional change --- scripts/get_native_properties.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index cffb0ce2731..ae23c3bb4ac 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -14,10 +14,9 @@ check_flags() { } # Set the CPU flags list +# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 get_flags() { - flags="$(awk '/^flags[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo) $(awk '/^Features[ \t]*:/{gsub(/^Features[ \t]*:[ \t]*/, ""); line=$0} END{print line}' /proc/cpuinfo)" - # remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 - flags=$(printf '%s' "$flags" | sed "s/[_.]//g") + flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo) } # Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid @@ -55,7 +54,7 @@ case $uname_s in file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2 ;; 'x86_64') - flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | sed "s/[_.]//g") + flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') set_arch_x86_64 if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then file_arch='x86-64-bmi2' From 36db936e769a2e7a95fc4032eec3b79251bbaef5 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:39:57 +0800 Subject: [PATCH 0301/1309] VLTC Search parameters tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SPSA tuning was done for 44k games at 120+1.2. https://tests.stockfishchess.org/tests/view/656ee2a76980e15f69c7767f. Note that the tune was originally done in combination with the recent dual NNUE idea (see #4910). VLTC: https://tests.stockfishchess.org/tests/view/65731ccbf09ce1261f12246e LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 52806 W: 13069 L: 12760 D: 26977 Ptnml(0-2): 19, 5498, 15056, 5815, 15 VLTC SMP: https://tests.stockfishchess.org/tests/view/65740ffaf09ce1261f1239ba LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 27630 W: 6934 L: 6651 D: 14045 Ptnml(0-2): 1, 2643, 8243, 2928, 0 Estimated close to neutral at LTC: https://tests.stockfishchess.org/tests/view/6575485a8ec68176cf7d9423 Elo: -0.59 ± 1.8 (95%) LOS: 26.6% Total: 32060 W: 7859 L: 7913 D: 16288 Ptnml(0-2): 20, 3679, 8676, 3645, 10 nElo: -1.21 ± 3.8 (95%) PairsRatio: 0.99 closes https://github.com/official-stockfish/Stockfish/pull/4912 Bench: 1283323 --- src/search.cpp | 76 +++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 39711f3c611..f398074065d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,7 +77,7 @@ enum NodeType { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((125 - 43 * noTtCutNode) * (d - improving)); + return Value((116 - 44 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup @@ -85,8 +85,8 @@ int Reductions[MAX_MOVES]; // [depth or moveNumber] Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1487 - int(delta) * 976 / int(rootDelta)) / 1024 - + (!i && reductionScale > 808); + return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 + + (!i && reductionScale > 880); } constexpr int futility_move_count(bool improving, Depth depth) { @@ -94,10 +94,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(291 * d - 350, 1200); } +int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(361 * d - 361, 1182); } +int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(const Thread* thisThread) { @@ -367,12 +367,12 @@ void Thread::search() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(10) + int(avg) * avg / 15335; + delta = Value(9) + int(avg) * avg / 14847; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 110 * avg / (std::abs(avg) + 121); + optimism[us] = 121 * avg / (std::abs(avg) + 109); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -746,7 +746,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1449, 1449); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1555, 1452); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; @@ -765,7 +765,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 474 - (270 - 174 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 472 - (284 - 165 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -776,22 +776,22 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // The depth condition is important for mate finding. if (!ss->ttPv && depth < 9 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - - (ss - 1)->statScore / 321 + - (ss - 1)->statScore / 337 >= beta - && eval >= beta && eval < 29462 // smaller than TB wins + && eval >= beta && eval < 29008 // smaller than TB wins && (!ttMove || ttCapture)) return (eval + beta) / 2; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17257 && eval >= beta - && eval >= ss->staticEval && ss->staticEval >= beta - 24 * depth + 281 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta + && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -805,7 +805,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Do not return unproven mate or TB scores if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { - if (thisThread->nmpMinPly || depth < 14) + if (thisThread->nmpMinPly || depth < 15) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed @@ -838,7 +838,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 168 - 70 * improving; + probCutBeta = beta + 163 - 67 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -896,7 +896,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 416; + probCutBeta = beta + 425; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -983,14 +983,14 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { Piece capturedPiece = pos.piece_on(to_sq(move)); int futilityEval = - ss->staticEval + 239 + 291 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; if (futilityEval < alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-185) * depth)) + if (!pos.see_ge(move, Value(-187) * depth)) continue; } else @@ -1001,18 +1001,18 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3645 * depth) + if (lmrDepth < 6 && history < -3752 * depth) continue; history += 2 * thisThread->mainHistory[us][from_to(move)]; - lmrDepth += history / 7836; + lmrDepth += history / 7838; lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 - && ss->staticEval + (bestValue < ss->staticEval - 62 ? 123 : 77) - + 127 * lmrDepth + if (!ss->inCheck && lmrDepth < 14 + && ss->staticEval + (bestValue < ss->staticEval - 57 ? 124 : 71) + + 118 * lmrDepth <= alpha) continue; @@ -1039,11 +1039,11 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 24) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 27) + 2 * (PvNode && tte->is_pv()) && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (64 + 57 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (66 + 58 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1057,7 +1057,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 18 && ss->doubleExtensions <= 11) + if (!PvNode && value < singularBeta - 17 && ss->doubleExtensions <= 11) { extension = 2; depth += depth < 15; @@ -1092,18 +1092,18 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } // Check extensions (~1 Elo) - else if (givesCheck && depth > 9) + else if (givesCheck && depth > 10) extension = 1; // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 4194) + && (*contHist[0])[movedPiece][to_sq(move)] >= 4325) extension = 1; // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && to_sq(move) == prevSq && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] - > 4000) + > 4146) extension = 1; } @@ -1162,10 +1162,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - 3848; + + (*contHist[3])[movedPiece][to_sq(move)] - 3817; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / 14200; + r -= ss->statScore / 14767; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1188,7 +1188,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // 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 + 50 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 53 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1303,7 +1303,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13828 && value > -11369) + if (depth > 2 && depth < 12 && beta < 13782 && value > -11541) depth -= 2; assert(depth > 0); @@ -1342,7 +1342,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 657) + int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 656) + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1475,7 +1475,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 200; + futilityBase = ss->staticEval + 182; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1555,7 +1555,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-90))) + if (!pos.see_ge(move, Value(-77))) continue; } @@ -1691,7 +1691,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 282e15bf75bd1142de96306b22424f0cd2bb8dfa Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Fri, 17 Nov 2023 18:25:50 +0300 Subject: [PATCH 0302/1309] Fix TB score output in UCI without using TB This is a rewrite of the fix introduced for https://github.com/official-stockfish/Stockfish/issues/4413 in https://github.com/official-stockfish/Stockfish/pull/4591 by @windfishballad it targets only the relevant part of this issue that returns TB scores (CP 20000) without using TB due to the downgrading of potentially false mates from the TT to an optimal TB score. the difference is that it is a much clearer code that introduces a separate TB_VALUE constant to account for a correct distance from the TB_VALUE with MAX_PLY. the originally posted position in the issue does not trigger the problem anymore, so here is a new position to test: ``` position fen 3k4/8/8/8/8/8/3BN3/3K4 w - - 0 1 go infinite ``` Passed non-regression STC: https://tests.stockfishchess.org/tests/view/65578994136acbc57353b258 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 119264 W: 29993 L: 29863 D: 59408 Ptnml(0-2): 372, 13692, 31379, 13812, 377 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6558323f136acbc57353c1ca LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 237834 W: 58791 L: 58792 D: 120251 Ptnml(0-2): 193, 26200, 66111, 26241, 172 fixes https://github.com/official-stockfish/Stockfish/issues/4413 closes https://github.com/official-stockfish/Stockfish/pull/4591 closes https://github.com/official-stockfish/Stockfish/pull/4882 Bench: 1305821 --- src/search.cpp | 39 +++++++++++++++++++++++++++------------ src/types.h | 12 +++++++----- src/uci.cpp | 4 ++-- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f398074065d..89879374c1a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -678,9 +678,11 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo int drawScore = TB::UseRule50 ? 1 : 0; - // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score - value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 - : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 + Value tbValue = VALUE_TB - ss->ply; + + // use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score + value = wdl < -drawScore ? -tbValue + : wdl > drawScore ? tbValue : VALUE_DRAW + 2 * wdl * drawScore; Bound b = wdl < -drawScore ? BOUND_UPPER @@ -1631,25 +1633,38 @@ Value value_to_tt(Value v, int ply) { // Inverse of value_to_tt(): it adjusts a mate or TB score // from the transposition table (which refers to the plies to mate/be mated from // current position) to "plies to mate/be mated (TB win/loss) from the root". -// However, to avoid potentially false mate scores related to the 50 moves rule -// and the graph history interaction problem, we return an optimal TB score instead. +// However, to avoid potentially false mate or TB scores related to the 50 moves rule +// and the graph history interaction, we return highest non-TB score instead. + Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) return VALUE_NONE; - if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better + // handle TB win or better + if (v >= VALUE_TB_WIN_IN_MAX_PLY) { - if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) - return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score + // Downgrade a potentially false mate score + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; return v - ply; } - if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse + // handle TB loss or worse + if (v <= VALUE_TB_LOSS_IN_MAX_PLY) { - if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) - return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score + // Downgrade a potentially false mate score. + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + + // Downgrade a potentially false TB score. + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; return v + ply; } @@ -1866,7 +1881,7 @@ string UCI::pv(const Position& pos, Depth depth) { if (v == -VALUE_INFINITE) v = VALUE_ZERO; - bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; + bool tb = TB::RootInTB && abs(v) <= VALUE_TB; v = tb ? rootMoves[i].tbScore : v; if (ss.rdbuf()->in_avail()) // Not at first line diff --git a/src/types.h b/src/types.h index 0575f1d453c..3e00d68d19d 100644 --- a/src/types.h +++ b/src/types.h @@ -164,14 +164,16 @@ enum Bound { enum Value : int { VALUE_ZERO = 0, VALUE_DRAW = 0, - VALUE_MATE = 32000, - VALUE_INFINITE = 32001, VALUE_NONE = 32002, + VALUE_INFINITE = 32001, + + VALUE_MATE = 32000, + VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, - VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, + VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1, + VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY, VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, - VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, - VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, // In the code, we make the assumption that these values // are such that non_pawn_material() can be used to uniquely diff --git a/src/uci.cpp b/src/uci.cpp index 95f6f349dd3..d0341bd7168 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -356,9 +356,9 @@ std::string UCI::value(Value v) { if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) ss << "cp " << UCI::to_cp(v); - else if (abs(v) < VALUE_MATE_IN_MAX_PLY) + else if (abs(v) <= VALUE_TB) { - const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply + const int ply = VALUE_TB - std::abs(v); // recompute ss->ply ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); } else From 7885fa5bd3c8aae1e992ec80cbaaab1177502426 Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Mon, 4 Dec 2023 00:39:41 +0300 Subject: [PATCH 0303/1309] Track seldepth in qsearch too Sometimes if we count the reported PV length, it turns out to be longer than the selective depth reported. This fixes this behavior by applying the selective depth to qsearch since we do report PVs from it as well. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/656cf5b66980e15f69c7499d LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 223648 W: 56372 L: 56356 D: 110920 Ptnml(0-2): 710, 25580, 59231, 25590, 713 closes https://github.com/official-stockfish/Stockfish/pull/4903 No functional change --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 89879374c1a..10a36cbf261 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1421,6 +1421,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->inCheck = pos.checkers(); moveCount = 0; + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; + // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; From cdfafb3426cdf3a6c60fe2e20eb52c72d2777e51 Mon Sep 17 00:00:00 2001 From: WangXiang Date: Sun, 10 Dec 2023 23:04:32 +0800 Subject: [PATCH 0304/1309] Add loongarch64 support Adding support for LoongArch64 architecture. Tested on Loongson 3A6000 EVB Board. Since Loongson's SIMD extended instruction set ([LSX](https://gcc.gnu.org/onlinedocs/gcc/LoongArch-SX-Vector-Intrinsics.html), [LASX](https://gcc.gnu.org/onlinedocs/gcc/LoongArch-ASX-Vector-Intrinsics.html)) is already supported by GCC, more optimizations are being developed. Here's the benchmark result for Loongson 3A6000 (4c8t, 2.5Ghz) without SIMD optimizations. ``` Total time (ms) : 17903 Nodes searched : 1244386 Nodes/second : 69507 ``` closes https://github.com/official-stockfish/Stockfish/pull/4913 No functional change --- AUTHORS | 1 + src/Makefile | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index d7b64b62e8c..f6a10288476 100644 --- a/AUTHORS +++ b/AUTHORS @@ -227,6 +227,7 @@ Vince Negri (cuddlestmonkey) Viren windfishballad xefoci7612 +Xiang Wang (KatyushaScarlet) zz4032 # Additionally, we acknowledge the authors and maintainers of fishtest, diff --git a/src/Makefile b/src/Makefile index 59ea7bfe7b5..761b40869ee 100644 --- a/src/Makefile +++ b/src/Makefile @@ -125,7 +125,7 @@ ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ - armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64)) + armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 loongarch64)) SUPPORTED_ARCH=true else SUPPORTED_ARCH=false @@ -369,6 +369,10 @@ endif ifeq ($(ARCH),riscv64) arch = riscv64 endif + +ifeq ($(ARCH),loongarch64) + arch = loongarch64 +endif endif @@ -404,6 +408,8 @@ ifeq ($(COMP),gcc) ifeq ($(ARCH),riscv64) CXXFLAGS += -latomic endif + else ifeq ($(ARCH),loongarch64) + CXXFLAGS += -latomic else CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) @@ -474,6 +480,8 @@ ifeq ($(COMP),clang) ifeq ($(ARCH),riscv64) CXXFLAGS += -latomic endif + else ifeq ($(ARCH),loongarch64) + CXXFLAGS += -latomic else CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) @@ -823,6 +831,7 @@ help: @echo "general-64 > unspecified 64-bit" @echo "general-32 > unspecified 32-bit" @echo "riscv64 > RISC-V 64-bit" + @echo "loongarch64 > LoongArch 64-bit" @echo "" @echo "Supported compilers:" @echo "" @@ -1004,7 +1013,7 @@ config-sanity: net @test "$(SUPPORTED_ARCH)" = "true" @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ - test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64" @test "$(bits)" = "32" || test "$(bits)" = "64" @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" From 9fc064e872e772f941e6fb5d303d827174003ce7 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 10 Dec 2023 23:40:45 +0100 Subject: [PATCH 0305/1309] Fix action deprecation warning for dev-drprasad closes https://github.com/official-stockfish/Stockfish/pull/4914 No functional change --- .github/workflows/stockfish.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 7bbb53d5c62..e8db52351dd 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -30,8 +30,7 @@ jobs: if: env.COMMIT_SHA != 'null' with: tag_name: ${{ env.COMMIT_SHA }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} Analyzers: uses: ./.github/workflows/stockfish_analyzers.yml From 536d692a3082cc86afcf1a48b0cf25ac73fa7074 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 12 Dec 2023 16:38:24 +0300 Subject: [PATCH 0306/1309] Remove SlowMover Option The SlowMover option allows users to modify the timeLeft variant, impacting the engine's time management. However, this feature, while theoretically flexible, doesn't offer substantial benefits. Instead, it introduces the risk of non-experienced users altering values without a clear understanding of the effects, potentially leading to a weaker engine. The vast majority of SF users don't use it anyway, and based on tests conducted by fauzi several months ago suggest that changing it would only lose Elo. Examples: https://tests.stockfishchess.org/tests/view/651f309bac57711436726bba https://tests.stockfishchess.org/tests/view/651fea29ac57711436727d85 https://tests.stockfishchess.org/tests/view/65257c343125598fc7eb68a1 https://tests.stockfishchess.org/tests/view/652296c83125598fc7eb2ad7 Tune: https://tests.stockfishchess.org/tests/view/652a70313125598fc7ebd706 (keeping the value at 100, zz2) closes https://github.com/official-stockfish/Stockfish/pull/4917 No functional change --- src/timeman.cpp | 5 ----- src/ucioption.cpp | 1 - 2 files changed, 6 deletions(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 1253d434672..9ff422fe966 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -42,7 +42,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { return; TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); - TimePoint slowMover = TimePoint(Options["Slow Mover"]); TimePoint npmsec = TimePoint(Options["nodestime"]); // optScale is a percentage of available time to use for the current move. @@ -78,10 +77,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { double optConstant = std::min(0.00335 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0048); double maxConstant = std::max(3.6 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.7); - // A user may scale time usage by setting UCI option "Slow Mover" - // Default is 100 and changing this value will probably lose elo. - timeLeft = slowMover * timeLeft / 100; - // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed actual available // game time for the current move, so also cap to 20% of available game time. diff --git a/src/ucioption.cpp b/src/ucioption.cpp index d0db1c76dd2..233602ca153 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -73,7 +73,6 @@ void init(OptionsMap& o) { o["MultiPV"] << Option(1, 1, 500); o["Skill Level"] << Option(20, 0, 20); o["Move Overhead"] << Option(10, 0, 5000); - o["Slow Mover"] << Option(100, 10, 1000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); o["UCI_AnalyseMode"] << Option(false); From c53d2ec253557d8a679197becb7ba9a6aa393ecc Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 13 Dec 2023 19:05:37 +0800 Subject: [PATCH 0307/1309] Remove UCI_AnalyseMode Option Simplify away the useless option, as documented: "An option handled by your GUI. This currently doesn't do anything." The option was originally added with the introduction of contempt (https://github.com/official-stockfish/Stockfish/commit/e9aeaad05266ca557a9496b5a17b4c5f82f0e946), but it is now no longer used. closes https://github.com/official-stockfish/Stockfish/pull/4918 No functional change --- src/ucioption.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 233602ca153..1dc9b89baed 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -75,7 +75,6 @@ void init(OptionsMap& o) { o["Move Overhead"] << Option(10, 0, 5000); o["nodestime"] << Option(0, 0, 10000); o["UCI_Chess960"] << Option(false); - o["UCI_AnalyseMode"] << Option(false); o["UCI_LimitStrength"] << Option(false); o["UCI_Elo"] << Option(1320, 1320, 3190); o["UCI_ShowWDL"] << Option(false); From d9ec82e7438716671168d78eee26fae327249e8c Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 15 Dec 2023 01:51:52 +0300 Subject: [PATCH 0308/1309] Adjust stand pat in qsearch on pv nodes Instead of immediately returning a fail high do this only at non-pv nodes, for pv nodes adjust bestValue to value between alpha and beta and continue searching. Idea is to do it the same way as it's done in search where we don't return positive beta cutoffs after ttHits / zero window search at PvNodes and instead fully search lines. Passed STC: https://tests.stockfishchess.org/tests/view/65739b0af09ce1261f122f33 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 189216 W: 48142 L: 47598 D: 93476 Ptnml(0-2): 584, 22463, 48051, 22845, 665 Passed LTC: https://tests.stockfishchess.org/tests/view/657701214d789acf40aac194 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 82506 W: 20689 L: 20269 D: 41548 Ptnml(0-2): 56, 9236, 22268, 9618, 75 Two issues had to be resolved: - in rare cases it set alpha to the same value as beta and thus broke some asserts; - messed up with returning tb win values. Fix passed non-regression LTC vs this patch: https://tests.stockfishchess.org/tests/view/6578113b4d789acf40aad544 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 277308 W: 68839 L: 68880 D: 139589 Ptnml(0-2): 167, 31580, 75212, 31517, 178 closes https://github.com/official-stockfish/Stockfish/pull/4922 Bench: 1069503 Co-Authored-By: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Co-Authored-By: Shahin M. Shahin <41402573+peregrineshahin@users.noreply.github.com> Co-Authored-By: fffelix-huang <72808219+fffelix-huang@users.noreply.github.com> --- src/search.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 10a36cbf261..27c2c84e9dd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1468,14 +1468,19 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; - // Stand pat. Return immediately if static value is at least beta + // Stand pat. Return immediately if bestValue is at least beta at non-Pv nodes. + // At PvNodes set bestValue between alpha and beta instead if (bestValue >= beta) { - if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - MOVE_NONE, ss->staticEval); + if (!PvNode || abs(bestValue) >= VALUE_TB_WIN_IN_MAX_PLY) + { + if (!ss->ttHit) + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, + DEPTH_NONE, MOVE_NONE, ss->staticEval); - return bestValue; + return bestValue; + } + bestValue = std::min((alpha + beta) / 2, beta - 1); } if (bestValue > alpha) From 07a2619b62a25910a32ad8a4e9912f748338580f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 15 Dec 2023 14:29:44 +0300 Subject: [PATCH 0309/1309] Improvement of Time Management Parameters Passed STC: https://tests.stockfishchess.org/tests/view/6579c5574d789acf40aaf914 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 44672 W: 11354 L: 11030 D: 22288 Ptnml(0-2): 140, 5033, 11685, 5319, 159 Passed LTC: https://tests.stockfishchess.org/tests/view/657ad7f44d789acf40ab105e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 40932 W: 10275 L: 9950 D: 20707 Ptnml(0-2): 21, 4316, 11473, 4629, 27 Passed non-regression Sudden death 10+0: https://tests.stockfishchess.org/tests/view/657b9b9e393ac02e7911f1a8 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 21384 W: 5171 L: 4925 D: 11288 Ptnml(0-2): 112, 2420, 5409, 2612, 139 closes https://github.com/official-stockfish/Stockfish/pull/4923 No functional change --- src/search.cpp | 6 +++--- src/timeman.cpp | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 27c2c84e9dd..ce71c7883ca 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -471,12 +471,12 @@ void Thread::search() { { double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + 6 * (mainThread->iterValue[iterIdx] - bestValue)) - / 583.0; - fallingEval = std::clamp(fallingEval, 0.5, 1.5); + / 616.6; + fallingEval = std::clamp(fallingEval, 0.51, 1.51); // If the bestMove is stable over several iterations, reduce time accordingly timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.03 * timeReduction); + double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size(); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; diff --git a/src/timeman.cpp b/src/timeman.cpp index 9ff422fe966..f404ee0c353 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -71,21 +71,21 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - moveOverhead * (2 + mtg)); // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.12); + double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); // Calculate time constants based on current time left. - double optConstant = std::min(0.00335 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0048); - double maxConstant = std::max(3.6 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.7); + double optConstant = std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); + double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed actual available // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { - optScale = std::min(0.0120 + std::pow(ply + 3.3, 0.44) * optConstant, - 0.2 * limits.time[us] / double(timeLeft)) + optScale = std::min(0.0120 + std::pow(ply + 3.1, 0.44) * optConstant, + 0.21 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(6.8, maxConstant + ply / 12.2); + maxScale = std::min(6.9, maxConstant + ply / 12.2); } // x moves in y seconds (+ z increment) From a069a1bbbfb60abddbe3fe5276b06f35f783f41c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 18 Dec 2023 16:20:41 +0300 Subject: [PATCH 0310/1309] Use std::abs over abs closes https://github.com/official-stockfish/Stockfish/pull/4926 closes https://github.com/official-stockfish/Stockfish/pull/4909 No functional change Co-Authored-By: fffelix-huang <72808219+fffelix-huang@users.noreply.github.com> --- AUTHORS | 1 + src/evaluate.cpp | 11 ++++++----- src/movepick.h | 7 ++++--- src/nnue/evaluate_nnue.cpp | 2 +- src/search.cpp | 8 ++++---- src/thread.cpp | 3 ++- src/uci.cpp | 4 ++-- 7 files changed, 20 insertions(+), 16 deletions(-) diff --git a/AUTHORS b/AUTHORS index f6a10288476..cedee2f3536 100644 --- a/AUTHORS +++ b/AUTHORS @@ -214,6 +214,7 @@ Taras Vuk (TarasVuk) Thanar2 thaspel theo77186 +Ting-Hsuan Huang (fffelix-huang) Tomasz Sobczyk (Sopel97) Tom Truscott Tom Vijlbrief (tomtor) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9c39d4c07fb..586cadc0ec5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -164,9 +165,9 @@ Value Eval::evaluate(const Position& pos) { int shuffling = pos.rule50_count(); int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - bool lazy = abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling - + abs(pos.this_thread()->bestValue) - + abs(pos.this_thread()->rootSimpleEval); + bool lazy = std::abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling + + std::abs(pos.this_thread()->bestValue) + + std::abs(pos.this_thread()->rootSimpleEval); if (lazy) v = Value(simpleEval); @@ -178,8 +179,8 @@ Value Eval::evaluate(const Position& pos) { Value optimism = pos.this_thread()->optimism[stm]; // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; int npm = pos.non_pawn_material() / 64; v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; diff --git a/src/movepick.h b/src/movepick.h index 7828fa19d97..5077f4e3b72 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -55,12 +56,12 @@ class StatsEntry { operator const T&() const { return entry; } void operator<<(int bonus) { - assert(abs(bonus) <= D); // Ensure range is [-D, D] + assert(std::abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += bonus - entry * abs(bonus) / D; + entry += bonus - entry * std::abs(bonus) / D; - assert(abs(entry) <= D); + assert(std::abs(entry) <= D); } }; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index ef6b7e91a60..e7339c10ae9 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -180,7 +180,7 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { const auto positional = network[bucket]->propagate(transformedFeatures); if (complexity) - *complexity = abs(psqt - positional) / OutputScale; + *complexity = std::abs(psqt - positional) / OutputScale; // Give more value to positional evaluation when adjusted flag is set if (adjusted) diff --git a/src/search.cpp b/src/search.cpp index ce71c7883ca..bd3da5a23b1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -847,7 +847,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // much above beta, we can (almost) safely prune the previous move. if ( !PvNode && depth > 3 - && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY // If value from transposition table is lower than probCutBeta, don't attempt 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 @@ -901,7 +901,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo probCutBeta = beta + 425; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1042,7 +1042,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove && depth >= 4 - (thisThread->completedDepth > 27) + 2 * (PvNode && tte->is_pv()) - && abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) + && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { Value singularBeta = ttValue - (66 + 58 * (ss->ttPv && !PvNode)) * depth / 64; @@ -1890,7 +1890,7 @@ string UCI::pv(const Position& pos, Depth depth) { if (v == -VALUE_INFINITE) v = VALUE_ZERO; - bool tb = TB::RootInTB && abs(v) <= VALUE_TB; + bool tb = TB::RootInTB && std::abs(v) <= VALUE_TB; v = tb ? rootMoves[i].tbScore : v; if (ss.rdbuf()->in_avail()) // Not at first line diff --git a/src/thread.cpp b/src/thread.cpp index bc884dedf01..de8de87d8a2 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -235,7 +236,7 @@ Thread* ThreadPool::get_best_thread() const { votes[th->rootMoves[0].pv[0]] += thread_value(th); for (Thread* th : threads) - if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) { // Make sure we pick the shortest mate / TB conversion or stave off mate the longest if (th->rootMoves[0].score > bestThread->rootMoves[0].score) diff --git a/src/uci.cpp b/src/uci.cpp index d0341bd7168..5f250a3617a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -354,9 +354,9 @@ std::string UCI::value(Value v) { std::stringstream ss; - if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) ss << "cp " << UCI::to_cp(v); - else if (abs(v) <= VALUE_TB) + else if (std::abs(v) <= VALUE_TB) { const int ply = VALUE_TB - std::abs(v); // recompute ss->ply ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); From 9be0360aa414556f231873ce2348f9c1f00d1713 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 18 Dec 2023 20:34:35 +0300 Subject: [PATCH 0311/1309] Adjust return value in qsearch after fail high Instead of returning strict fail soft fail high return value between value from search and beta (somewhat by analogy to futility pruning and probcut). This seems to be somewhat depth sensitive heuristic which performed much worse at LTC while performing much better at STC if it is more aggressive, passed version is the least aggressive one. Passed STC: https://tests.stockfishchess.org/tests/view/657b06414d789acf40ab1475 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 212352 W: 53900 L: 53315 D: 105137 Ptnml(0-2): 809, 25236, 53520, 25783, 828 Passed LTC: https://tests.stockfishchess.org/tests/view/657ce36f393ac02e79120a7c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 319362 W: 79541 L: 78630 D: 161191 Ptnml(0-2): 202, 35839, 86709, 36708, 223 closes https://github.com/official-stockfish/Stockfish/pull/4928 Bench: 974739 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index bd3da5a23b1..fad43b624dc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1618,6 +1618,9 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { return mated_in(ss->ply); // Plies to mate from the root } + if (abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) + bestValue = bestValue >= beta ? (3 * bestValue + beta) / 4 : bestValue; + // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); From 358a85379094cbffa9d80b443ba63f3066c4cd33 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 22 Dec 2023 11:46:28 +0100 Subject: [PATCH 0312/1309] Revert "Adjust stand pat in qsearch on pv nodes" This reverts commit d9ec82e7438716671168d78eee26fae327249e8c. Bench: 1249544 --- src/search.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fad43b624dc..235b35c19d8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1468,19 +1468,14 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; - // Stand pat. Return immediately if bestValue is at least beta at non-Pv nodes. - // At PvNodes set bestValue between alpha and beta instead + // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - if (!PvNode || abs(bestValue) >= VALUE_TB_WIN_IN_MAX_PLY) - { - if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, - DEPTH_NONE, MOVE_NONE, ss->staticEval); + if (!ss->ttHit) + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, + MOVE_NONE, ss->staticEval); - return bestValue; - } - bestValue = std::min((alpha + beta) / 2, beta - 1); + return bestValue; } if (bestValue > alpha) From fbdf5d94a9a42acb92720a5896b16c92931ec3de Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 20 Dec 2023 14:26:11 +0300 Subject: [PATCH 0313/1309] Tweak quiet move bonus Improving quiet move bonus by replacing bestvalue and alpha comparison, with checking the statScore of the previous search step instead. Inspired by @locutus2 Passed STC: https://tests.stockfishchess.org/tests/view/657f22fb893104ee25b614e8 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 51296 W: 13121 L: 12774 D: 25401 Ptnml(0-2): 225, 5986, 12868, 6355, 214 Passed LTC: https://tests.stockfishchess.org/tests/view/658024a2893104ee25b62587 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 82758 W: 20606 L: 20189 D: 41963 Ptnml(0-2): 51, 9149, 22555, 9580, 44 closes https://github.com/official-stockfish/Stockfish/pull/4930 Bench: 1312822 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 235b35c19d8..3c61ea2f395 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -748,7 +748,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Use static evaluation difference to improve quiet move ordering (~4 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1555, 1452); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; @@ -1344,7 +1344,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + (bestValue < alpha - 656) + int bonus = (depth > 6) + (PvNode || cutNode) + ((ss - 1)->statScore < -18782) + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); From 3f5adc037e14e40d1e0e1380e3e7c5884ca528ed Mon Sep 17 00:00:00 2001 From: peregrineshahin Date: Thu, 21 Dec 2023 07:44:32 +0300 Subject: [PATCH 0314/1309] Fix wrong mate/tb scores from probCut This fixes returning wrong mated-in scores, or losing a proven mate-in score from probCut after recent tweaks. The issue reported by @cj5716 on discord. Passed non-reg STC: https://tests.stockfishchess.org/tests/view/6583c36b5457644dc9843afe LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 295936 W: 75011 L: 75075 D: 145850 Ptnml(0-2): 978, 33947, 78146, 33955, 942 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/658513075457644dc98451cd LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 55932 W: 13970 L: 13786 D: 28176 Ptnml(0-2): 33, 5933, 15837, 6143, 20 closes https://github.com/official-stockfish/Stockfish/pull/4933 Bench: 1308739 --- src/search.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3c61ea2f395..25fc30ba191 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -854,7 +854,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // So effective depth is equal to depth - 3 && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) { - assert(probCutBeta < VALUE_INFINITE); + assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); @@ -888,7 +888,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, ss->staticEval); - return value - (probCutBeta - beta); + return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) + : value; } } @@ -1613,7 +1614,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { return mated_in(ss->ply); // Plies to mate from the root } - if (abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) bestValue = bestValue >= beta ? (3 * bestValue + beta) / 4 : bestValue; // Save gathered info in transposition table From f388e4180950833a1f79e023c88ff2521c9583b2 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 24 Dec 2023 20:36:52 +0300 Subject: [PATCH 0315/1309] Adjust value returned after TT cutoff Instead of returning value from TT in case of a fail high return mix between it and beta. Passed STC: https://tests.stockfishchess.org/tests/view/658465395457644dc98446c7 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 220704 W: 56404 L: 55811 D: 108489 Ptnml(0-2): 750, 26214, 55921, 26627, 840 Passed LTC: https://tests.stockfishchess.org/tests/view/6585c3f55457644dc9845db9 LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 124980 W: 31169 L: 30658 D: 63153 Ptnml(0-2): 57, 14147, 33603, 14594, 89 closes https://github.com/official-stockfish/Stockfish/pull/4934 Bench: 1191093 --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 25fc30ba191..bc196ec4f50 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -653,7 +653,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) - return ttValue; + return ttValue >= beta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY + ? (ttValue * 3 + beta) / 4 + : ttValue; } // Step 5. Tablebases probe From bab1cc300cbf1929fe42bdbce786a22dd97c8e1b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 26 Dec 2023 20:27:45 +0300 Subject: [PATCH 0316/1309] Refactor bestvalue adjustment in qsearch closes https://github.com/official-stockfish/Stockfish/pull/4935 No functional change --- AUTHORS | 2 +- src/search.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index cedee2f3536..28586eec974 100644 --- a/AUTHORS +++ b/AUTHORS @@ -72,7 +72,7 @@ Fabian Beuke (madnight) Fabian Fichter (ianfab) Fanael Linithien (Fanael) fanon -Fauzi Akram Dabat (FauziAkram) +Fauzi Akram Dabat (fauzi2) Felix Wittmann gamander Gabriele Lombardo (gabe) diff --git a/src/search.cpp b/src/search.cpp index bc196ec4f50..7709af35a02 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1616,8 +1616,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { return mated_in(ss->ply); // Plies to mate from the root } - if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) - bestValue = bestValue >= beta ? (3 * bestValue + beta) / 4 : bestValue; + if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && bestValue >= beta) + bestValue = (3 * bestValue + beta) / 4; // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, From f12035c88c58a5fd568d26cde9868f73a8d7b839 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 25 Dec 2023 10:14:15 -0500 Subject: [PATCH 0317/1309] Update default net to nn-b1e55edbea57.nnue Created by retraining the master big net `nn-0000000000a0.nnue` on the same dataset with the ranger21 optimizer and more WDL skipping at training time. More WDL skipping is meant to increase lambda accuracy and train on fewer misevaluated positions where position scores are unlikely to correlate with game outcomes. Inspired by: - repeated reports in discord #events-discuss about SF misplaying due to wrong endgame evals, possibly due to Leela's endgame weaknesses reflected in training data - an attempt to reduce the skewed dataset piece count distribution where there are much more positions with less than 16 pieces, since the target piece count distribution in the trainer is symmetric around 16 The faster convergence seen with ranger21 is meant to: - prune experiment ideas more quickly since fewer epochs are needed to reach elo maxima - research faster potential trainings by shortening each run ```yaml experiment-name: 2560-S7-Re-514G-ranger21-more-wdl-skip training-dataset: /data/S6-514G.binpack early-fen-skipping: 28 start-from-engine-test-net: True nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip num-epochs: 1200 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` Experiment yaml configs converted to easy_train.sh commands with: https://github.com/linrock/nnue-tools/blob/4339954/yaml_easy_train.py Implementations based off of Sopel's NNUE training & experimentation log: https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY - Experiment 336 - ranger21 https://github.com/Sopel97/nnue-pytorch/tree/experiment_336 - Experiment 351 - more WDL skipping The version of the ranger21 optimizer used is: https://github.com/lessw2020/Ranger21/blob/b507df6/ranger21/ranger21.py The dataset is the exact same as in: https://github.com/official-stockfish/Stockfish/pull/4782 Local elo at 25k nodes per move: nn-epoch619.nnue : 6.2 +/- 4.2 Passed STC: https://tests.stockfishchess.org/tests/view/658a029779aa8af82b94fbe6 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 46528 W: 11985 L: 11650 D: 22893 Ptnml(0-2): 154, 5489, 11688, 5734, 199 Passed LTC: https://tests.stockfishchess.org/tests/view/658a448979aa8af82b95010f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 265326 W: 66378 L: 65574 D: 133374 Ptnml(0-2): 153, 30175, 71254, 30877, 204 This was additionally tested with the latest DualNNUE and passed SPRTs: Passed STC vs. https://github.com/official-stockfish/Stockfish/pull/4919 https://tests.stockfishchess.org/tests/view/658bcd5c79aa8af82b951846 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 296128 W: 76273 L: 75554 D: 144301 Ptnml(0-2): 1223, 35768, 73617, 35979, 1477 Passed LTC vs. https://github.com/official-stockfish/Stockfish/pull/4919 https://tests.stockfishchess.org/tests/view/658c988d79aa8af82b95240f LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 75618 W: 19085 L: 18680 D: 37853 Ptnml(0-2): 45, 8420, 20497, 8779, 68 closes https://github.com/official-stockfish/Stockfish/pull/4942 Bench: 1304666 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 2ab477eced2..33df13089fb 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ extern std::string currentEvalFileName; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultName "nn-0000000000a0.nnue" +#define EvalFileDefaultName "nn-b1e55edbea57.nnue" namespace NNUE { From 1a69efbb404fd4389651ab9f45127fb012c0cf94 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Sat, 30 Dec 2023 00:28:13 +0300 Subject: [PATCH 0318/1309] Fix scores from reverse futility pruning This fixes futility pruning return values after recent tweaks, `eval` is guaranteed to be less than the mate-in range but it can be as low value such that the average between eval and beta can still fall in the mated-in range when beta is as low in mated range. i.e. (eval + beta) / 2 being at mated-range which can break mates. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/658f3eed79aa8af82b955139 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 117408 W: 29891 L: 29761 D: 57756 Ptnml(0-2): 386, 13355, 31120, 13429, 414 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/658f8b7a79aa8af82b9557bd LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60240 W: 14962 L: 14786 D: 30492 Ptnml(0-2): 22, 6257, 17390, 6425, 26 changes signature at higher depth e.g. `128 1 15` closes https://github.com/official-stockfish/Stockfish/pull/4944 Bench: 1304666 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 7709af35a02..4e12a6c925a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -784,7 +784,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo >= beta && eval >= beta && eval < 29008 // smaller than TB wins && (!ttMove || ttCapture)) - return (eval + beta) / 2; + return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta From 4f99dfcae2dd8e9a4b163ade623084888655ed46 Mon Sep 17 00:00:00 2001 From: Tobias Steinmann Date: Mon, 18 Dec 2023 15:07:14 +0100 Subject: [PATCH 0319/1309] Update Makefile for android x86-64 builds For developing an Android GUI it can be helpful to use the Emulator on Windows. Therefor an android_x86-64 library of Stockfish is needed. It would be nice to compile it "out-of-the-box". This change is originally suggested by Craftyawesome closes https://github.com/official-stockfish/Stockfish/pull/4927 No functional change --- AUTHORS | 1 + src/Makefile | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/AUTHORS b/AUTHORS index 28586eec974..6f518ec24a8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -215,6 +215,7 @@ Thanar2 thaspel theo77186 Ting-Hsuan Huang (fffelix-huang) +Tobias Steinmann Tomasz Sobczyk (Sopel97) Tom Truscott Tom Vijlbrief (tomtor) diff --git a/src/Makefile b/src/Makefile index 761b40869ee..ac354c7b99c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -521,6 +521,14 @@ ifeq ($(COMP),ndk) STRIP=llvm-strip endif endif + ifeq ($(arch),x86_64) + CXX=x86_64-linux-android21-clang++ + ifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),) + STRIP=x86_64-linux-android-strip + else + STRIP=llvm-strip + endif + endif LDFLAGS += -static-libstdc++ -pie -lm -latomic endif From 833a2e2bc09e3640440766683043134d72bffd51 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 30 Dec 2023 15:22:17 +0300 Subject: [PATCH 0320/1309] Cleanup comments Tests used to derive some Elo worth comments: https://tests.stockfishchess.org/tests/view/656a7f4e136acbc573555a31 https://tests.stockfishchess.org/tests/view/6585fb455457644dc984620f closes https://github.com/official-stockfish/Stockfish/pull/4945 No functional change --- .github/ISSUE_TEMPLATE/config.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/stockfish_binaries.yml | 6 +++--- src/incbin/incbin.h | 10 +++++----- src/nnue/features/half_ka_v2_hm.h | 14 +++++++------- src/nnue/layers/affine_transform_sparse_input.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/nnue_common.h | 16 ++++++++-------- src/nnue/nnue_feature_transformer.h | 6 +++--- src/search.cpp | 8 ++++---- tests/instrumented.sh | 6 +++--- 11 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1f8694d2e6f..0666eb32fb0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Discord server url: https://discord.gg/GWDRS3kU6R - about: Feel free to ask for support or have a chat with us in our Discord server! + about: Feel free to ask for support or have a chat with us on our Discord server! - name: Discussions, Q&A, ideas, show us something... url: https://github.com/official-stockfish/Stockfish/discussions/new about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it! diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 054be90040c..d6da8a1c288 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,7 +23,7 @@ jobs: matrix: language: [ 'cpp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'java' to analyze code written in Java, Kotlin, or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index 5b3a522625c..eff2c2c9471 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -172,8 +172,8 @@ jobs: name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar - # Artifacts automatically get zipped - # to avoid double zipping, we use the unzipped directory + # Artifacts automatically get zipped. + # To avoid double-zipping, we use the unzipped directory - name: Upload binaries if: runner.os == 'Windows' uses: actions/upload-artifact@v3 @@ -195,7 +195,7 @@ jobs: id: commit_date run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV - # Make sure that an old ci which still runs on master doesn't recreate a prerelease + # Make sure that an old ci that still runs on master doesn't recreate a prerelease - name: Check Pullable Commits id: check_commits run: | diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h index c19684d7242..18718b95fae 100644 --- a/src/incbin/incbin.h +++ b/src/incbin/incbin.h @@ -3,8 +3,8 @@ * @author Dale Weiler * @brief Utility for including binary files * - * Facilities for including binary files into the current translation unit and - * making use from them externally in other translation units. + * Facilities for including binary files into the current translation unit + * and making use of them externally in other translation units. */ #ifndef INCBIN_HDR #define INCBIN_HDR @@ -139,7 +139,7 @@ #endif #if defined(__APPLE__) -/* The directives are different for Apple branded compilers */ +/* The directives are different for Apple-branded compilers */ # define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" # define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" # define INCBIN_INT ".long " @@ -261,8 +261,8 @@ INCBIN_STRINGIZE( \ INCBIN_STYLE_IDENT(TYPE)) \ -/* Generate the global labels by indirectly invoking the macro with our style - * type and concatenating the name against them. */ +/* Generate the global labels by indirectly invoking the macro + * with our style type and concatenate the name against them. */ #define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ INCBIN_INVOKE( \ INCBIN_GLOBAL, \ diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 540ff895a5a..c208e38dbad 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -34,11 +34,11 @@ class Position; namespace Stockfish::Eval::NNUE::Features { -// Feature HalfKAv2_hm: Combination of the position of own king -// and the position of pieces. Position mirrored such that king always on e..h files. +// Feature HalfKAv2_hm: Combination of the position of own king and the +// position of pieces. Position mirrored such that king is always on e..h files. class HalfKAv2_hm { - // unique number for each piece type on each square + // Unique number for each piece type on each square enum { PS_NONE = 0, PS_W_PAWN = 0, @@ -56,8 +56,8 @@ class HalfKAv2_hm { }; static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { - // convention: W - us, B - them - // viewed from other side, W and B are reversed + // Convention: W - us, B - them + // Viewed from other side, W and B are reversed {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE}, {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, @@ -140,8 +140,8 @@ class HalfKAv2_hm { static int update_cost(const StateInfo* st); static int refresh_cost(const Position& pos); - // Returns whether the change stored in this StateInfo means that - // a full accumulator refresh is required. + // Returns whether the change stored in this StateInfo means + // that a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); }; diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 6cb4d1a9347..70dbd790469 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -235,7 +235,7 @@ class AffineTransformSparseInput { const auto input32 = reinterpret_cast(input); - // Find indices of nonzero 32bit blocks + // Find indices of nonzero 32-bit blocks find_nnz(input32, nnz, count); const outvec_t* biasvec = reinterpret_cast(biases); diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index f8e2d497ac0..b9d8f030a24 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -91,7 +91,7 @@ class SqrClippedReLU { for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( - // Really should be /127 but we need to make it fast so we right shift + // Really should be /127 but we need to make it fast so we right-shift // by an extra 7 bits instead. Needs to be accounted for in the trainer. std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7))); } diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f9cd7fbb597..d4bd0028969 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -112,7 +112,7 @@ inline IntType read_little_endian(std::istream& stream) { // Utility to write an integer (signed or unsigned, any size) // to a stream in little-endian order. We swap the byte order before the write if -// necessary to always write in little endian order, independently of the byte +// necessary to always write in little-endian order, independently of the byte // ordering of the compiling machine. template inline void write_little_endian(std::ostream& stream, IntType value) { @@ -141,8 +141,8 @@ inline void write_little_endian(std::ostream& stream, IntType value) { } -// Read integers in bulk from a little indian stream. -// This reads N integers from stream s and put them in array out. +// Read integers in bulk from a little-endian stream. +// This reads N integers from stream s and puts them in array out. template inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { if (IsLittleEndian) @@ -153,7 +153,7 @@ inline void read_little_endian(std::istream& stream, IntType* out, std::size_t c } -// Write integers in bulk to a little indian stream. +// Write integers in bulk to a little-endian stream. // This takes N integers from array values and writes them on stream s. template inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { @@ -165,8 +165,8 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std } -// Read N signed integers from the stream s, putting them in -// the array out. The stream is assumed to be compressed using the signed LEB128 format. +// Read N signed integers from the stream s, putting them in the array out. +// The stream is assumed to be compressed using the signed LEB128 format. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { @@ -216,8 +216,8 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) // Write signed integers to a stream with LEB128 compression. -// This takes N integers from array values, compress them with the LEB128 algorithm and -// writes the result on the stream s. +// This takes N integers from array values, compresses them with +// the LEB128 algorithm and writes the result on the stream s. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. template inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 2af80f07792..a83a77c9d71 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -366,14 +366,14 @@ class FeatureTransformer { // 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. + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. FeatureSet::IndexList removed[N - 1], added[N - 1]; { int i = N - - 2; // last potential state to update. Skip last element because it must be nullptr. + - 2; // Last potential state to update. Skip last element because it must be nullptr. while (states_to_update[i] == nullptr) --i; diff --git a/src/search.cpp b/src/search.cpp index 4e12a6c925a..eb63ec90762 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -747,7 +747,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } - // Use static evaluation difference to improve quiet move ordering (~4 Elo) + // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); @@ -1201,6 +1201,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); + // Post LMR continuation history updates (~1 Elo) int bonus = value <= alpha ? -stat_malus(newDepth) : value >= beta ? stat_bonus(newDepth) : 0; @@ -1216,7 +1217,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (!ttMove) r += 2; - // Note that if expected reduction is high, we reduce search depth by 1 here + // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3), !cutNode); } @@ -1644,8 +1645,7 @@ Value value_to_tt(Value v, int ply) { // from the transposition table (which refers to the plies to mate/be mated from // current position) to "plies to mate/be mated (TB win/loss) from the root". // However, to avoid potentially false mate or TB scores related to the 50 moves rule -// and the graph history interaction, we return highest non-TB score instead. - +// and the graph history interaction, we return the highest non-TB score instead. Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 637d19f9d63..2a3eadc074e 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -1,5 +1,5 @@ #!/bin/bash -# check for errors under valgrind or sanitizers. +# check for errors under Valgrind or sanitizers. error() { @@ -151,7 +151,7 @@ cat << EOF > game.exp send "quit\n" expect eof - # return error code of the spawned program, useful for valgrind + # return error code of the spawned program, useful for Valgrind lassign [wait] pid spawnid os_error_flag value exit \$value EOF @@ -179,7 +179,7 @@ cat << EOF > syzygy.exp send "quit\n" expect eof - # return error code of the spawned program, useful for valgrind + # return error code of the spawned program, useful for Valgrind lassign [wait] pid spawnid os_error_flag value exit \$value EOF From 1fe562fdf32c153f82929660197f8b97469f76b4 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 30 Dec 2023 19:26:41 +0300 Subject: [PATCH 0321/1309] Simplify the improving flag calculation Passed STC: https://tests.stockfishchess.org/tests/view/658ec29979aa8af82b9547f6 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 93408 W: 23747 L: 23587 D: 46074 Ptnml(0-2): 340, 11178, 23527, 11300, 359 Passed LTC: https://tests.stockfishchess.org/tests/view/658f73e479aa8af82b9555b6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 64026 W: 15984 L: 15806 D: 32236 Ptnml(0-2): 31, 7113, 17552, 7281, 36 closes https://github.com/official-stockfish/Stockfish/pull/4948 Bench: 1143749 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index eb63ec90762..cb6b450de1c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -761,9 +761,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval - : (ss - 4)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 4)->staticEval - : true; + improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval + : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, From 4ff297a6dfae199571a4f24631a8e970924c8d63 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 31 Dec 2023 03:43:19 +0300 Subject: [PATCH 0322/1309] Mark square_bb() as constexpr closes https://github.com/official-stockfish/Stockfish/pull/4949 No functional change --- src/bitboard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitboard.h b/src/bitboard.h index 7dbd5329be8..8b9c291807a 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -89,7 +89,7 @@ struct Magic { extern Magic RookMagics[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; -inline Bitboard square_bb(Square s) { +constexpr Bitboard square_bb(Square s) { assert(is_ok(s)); return (1ULL << s); } From b4d995d0d910044cf4ea2ad3ee30fd1d21070cd8 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 31 Dec 2023 10:13:03 +0300 Subject: [PATCH 0323/1309] Introduce static evaluation correction history Idea from Caissa (https://github.com/Witek902/Caissa) chess engine. With given pawn structure collect data with how often search result and by how much it was better / worse than static evalution of position and use it to adjust static evaluation of positions with given pawn structure. Details: 1. excludes positions with fail highs and moves producing it being a capture; 2. update value is function of not only difference between best value and static evaluation but also is multiplied by linear function of depth; 3. maximum update value is maximum value of correction history divided by 2; 4. correction history itself is divided by 32 when applied so maximum value of static evaluation adjustment is 32 internal units. Passed STC: https://tests.stockfishchess.org/tests/view/658fc7b679aa8af82b955cac LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 128672 W: 32757 L: 32299 D: 63616 Ptnml(0-2): 441, 15241, 32543, 15641, 470 Passed LTC: https://tests.stockfishchess.org/tests/view/65903f6979aa8af82b9566f1 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 97422 W: 24626 L: 24178 D: 48618 Ptnml(0-2): 41, 10837, 26527, 11245, 61 closes https://github.com/official-stockfish/Stockfish/pull/4950 Bench: 1157852 --- src/movepick.cpp | 4 +- src/movepick.h | 21 ++++++++++- src/search.cpp | 97 +++++++++++++++++++++++++++++++++++++----------- src/thread.cpp | 1 + src/thread.h | 1 + 5 files changed, 98 insertions(+), 26 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0267a8e2e6f..ab37ff68e12 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -179,7 +179,7 @@ void MovePicker::score() { // histories m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; - m.value += 2 * (*pawnHistory)[pawn_structure(pos)][pc][to]; + m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to] / 4; @@ -216,7 +216,7 @@ void MovePicker::score() { else m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)]; + + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][to_sq(m)]; } } diff --git a/src/movepick.h b/src/movepick.h index 5077f4e3b72..eefc0d5037b 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -33,12 +33,25 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } +static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, + "CORRECTION_HISTORY_SIZE has to be a power of 2"); + +enum PawnHistoryType { + Normal, + Correction +}; + +template +inline int pawn_structure_index(const Position& pos) { + return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); +} // StatsEntry stores the stat table value. It is usually a number but could // be a move or even a nested history. We use a class instead of a naked value @@ -122,6 +135,10 @@ using ContinuationHistory = Stats // PawnHistory is addressed by the pawn structure and a move's [piece][to] using PawnHistory = Stats; +// CorrectionHistory is addressed by color and pawn structure +using CorrectionHistory = + Stats; + // MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which returns a // new pseudo-legal move each time it is called, until there are no moves left, diff --git a/src/search.cpp b/src/search.cpp index cb6b450de1c..1ec77bcd93b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -93,6 +93,11 @@ constexpr int futility_move_count(bool improving, Depth depth) { return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; } +// Guarantee evaluation does not hit the tablebase range +constexpr Value to_static_eval(const Value v) { + return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); +} + // History and stats update bonus, based on depth int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); } @@ -712,6 +717,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo CapturePieceToHistory& captureHistory = thisThread->captureHistory; + Value unadjustedStaticEval = VALUE_NONE; + // Step 6. Static evaluation of the position if (ss->inCheck) { @@ -725,26 +732,40 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // 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); - eval = ss->staticEval; + unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) { // Never assume anything about values stored in TT - ss->staticEval = eval = tte->eval(); + unadjustedStaticEval = ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - ss->staticEval = eval = evaluate(pos); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = eval = to_static_eval(newEval); + // ttValue can be used as a better position evaluation (~7 Elo) if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttValue; } else { - ss->staticEval = eval = evaluate(pos); - // Save static evaluation into the transposition table - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); + + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = eval = to_static_eval(newEval); + + // Static evaluation is saved as it was before adjustment by correction history + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, + unadjustedStaticEval); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) @@ -753,7 +774,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) - thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4; + thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] + << bonus / 4; } // Set up the improving flag, which is true if current static evaluation is @@ -888,7 +910,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, - move, ss->staticEval); + move, unadjustedStaticEval); return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) : value; } @@ -999,10 +1021,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } else { - int history = (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)]; + int history = + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] + + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][to_sq(move)]; // Continuation history based pruning (~2 Elo) if (lmrDepth < 6 && history < -3752 * depth) @@ -1364,12 +1386,23 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); // Write gathered information in transposition table + // Static evaluation is saved as it was before correction history if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, ss->staticEval); + depth, bestMove, unadjustedStaticEval); + + // Adjust correction history + if (!ss->inCheck && (!bestMove || !pos.capture(bestMove)) + && !(bestValue >= beta && bestValue <= ss->staticEval) + && !(!bestMove && bestValue >= ss->staticEval)) + { + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + thisThread->correctionHistory[us][pawn_structure_index(pos)] << bonus; + } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1450,6 +1483,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; + Value unadjustedStaticEval = VALUE_NONE; + // Step 4. Static evaluation of the position if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; @@ -1458,8 +1493,14 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (ss->ttHit) { // Never assume anything about values stored in TT - if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - ss->staticEval = bestValue = evaluate(pos); + if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) + unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos); + + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = bestValue = to_static_eval(newEval); // ttValue can be used as a better position evaluation (~13 Elo) if (ttValue != VALUE_NONE @@ -1467,16 +1508,24 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { bestValue = ttValue; } else + { // In case of null move search, use previous static eval with a different sign - ss->staticEval = bestValue = + unadjustedStaticEval = ss->staticEval = bestValue = (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; + Value newEval = + ss->staticEval + + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + ss->staticEval = bestValue = to_static_eval(newEval); + } + // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - MOVE_NONE, ss->staticEval); + MOVE_NONE, unadjustedStaticEval); return bestValue; } @@ -1620,8 +1669,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { bestValue = (3 * bestValue + beta) / 4; // Save gathered info in transposition table + // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, - bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, + unadjustedStaticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1720,15 +1771,17 @@ void update_all_stats(const Position& pos, // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bestMoveBonus); - thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)] - << quietMoveBonus; + + int pIndex = pawn_structure_index(pos); + thisThread->pawnHistory[pIndex][moved_piece][to_sq(bestMove)] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])] - [to_sq(quietsSearched[i])] + thisThread + ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][to_sq(quietsSearched[i])] << -quietMoveMalus; + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -quietMoveMalus); diff --git a/src/thread.cpp b/src/thread.cpp index de8de87d8a2..eeab18821ba 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -70,6 +70,7 @@ void Thread::clear() { mainHistory.fill(0); captureHistory.fill(0); pawnHistory.fill(0); + correctionHistory.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) diff --git a/src/thread.h b/src/thread.h index cb2f6db1d41..1edc9cc9e31 100644 --- a/src/thread.h +++ b/src/thread.h @@ -69,6 +69,7 @@ class Thread { CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; PawnHistory pawnHistory; + CorrectionHistory correctionHistory; }; From 3cfaef74311e943298a9a82bce5717d272338e66 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 31 Dec 2023 18:45:48 +0100 Subject: [PATCH 0324/1309] Tweak static eval history update Modify the applied static eval bonus for main and pawn history with different factors for positive and negative values. Passed STC: https://tests.stockfishchess.org/tests/view/659132e179aa8af82b957bb0 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 12512 W: 3308 L: 3027 D: 6177 Ptnml(0-2): 32, 1372, 3189, 1609, 54 Passed LTC: https://tests.stockfishchess.org/tests/view/65913e3d79aa8af82b957cd2 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 35946 W: 9128 L: 8809 D: 18009 Ptnml(0-2): 19, 3879, 9862, 4190, 23 closes https://github.com/official-stockfish/Stockfish/pull/4952 Bench: 1392883 --- src/search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/search.cpp b/src/search.cpp index 1ec77bcd93b..8e48b1647ba 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -772,6 +772,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); + bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] From 0fca5605fa2e5e7240fde5e1aae50952b2612231 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 02:29:28 +0100 Subject: [PATCH 0325/1309] Fix formatting in search.cpp fixes the formatting for 1fe562fdf32c153f82929660197f8b97469f76b4 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8e48b1647ba..c45e9f20c01 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -784,8 +784,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // check at our previous move we look at static evaluation at move prior to it // and if we were in check at move prior to it flag is set to true) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval - : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; + improving = (ss - 2)->staticEval != VALUE_NONE + ? ss->staticEval > (ss - 2)->staticEval + : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, From 154abb337e8737aedd6def4e7c0ca18bd4737252 Mon Sep 17 00:00:00 2001 From: Joseph Huang Date: Sun, 31 Dec 2023 03:11:04 -0500 Subject: [PATCH 0326/1309] Lower MultiPV max to MAX_MOVES Link max value of MultiPV to that of MAX_MOVES which is 256 closes https://github.com/official-stockfish/Stockfish/pull/4951 No functional change --- src/ucioption.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 1dc9b89baed..43392e9a19d 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -70,7 +70,7 @@ void init(OptionsMap& o) { o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Clear Hash"] << Option(on_clear_hash); o["Ponder"] << Option(false); - o["MultiPV"] << Option(1, 1, 500); + o["MultiPV"] << Option(1, 1, MAX_MOVES); o["Skill Level"] << Option(20, 0, 20); o["Move Overhead"] << Option(10, 0, 5000); o["nodestime"] << Option(0, 0, 10000); From a25f48a23671b0e18d1eae58e95d1a28fc389221 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 02:19:23 +0100 Subject: [PATCH 0327/1309] Silence security alert warning about possible infinite loop As some have noticed, a security alert has been complaining about a for loop in our TB code for quite some now. Though it was never a real issue, so not of high importance. A few lines earlier the symlen vector is resized `d->symlen.resize(number(data));` while this code seems odd at first, it resizes the array to at most (2 << 16) - 1 elements, basically making the infinite loop issue impossible to occur. closes https://github.com/official-stockfish/Stockfish/pull/4953 No functional change --- src/syzygy/tbprobe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index e23631575e6..5fe28fd2966 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1074,7 +1074,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf std::vector visited(d->symlen.size()); - for (Sym sym = 0; sym < d->symlen.size(); ++sym) + for (std::size_t sym = 0; sym < d->symlen.size(); ++sym) if (!visited[sym]) d->symlen[sym] = set_symlen(d, sym, visited); From 444f03ee95fcde4cf9014d82cae72c644357a31d Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 12:41:20 +0100 Subject: [PATCH 0328/1309] Update copyright year closes https://github.com/official-stockfish/Stockfish/pull/4954 No functional change --- src/Makefile | 2 +- src/benchmark.cpp | 2 +- src/benchmark.h | 2 +- src/bitboard.cpp | 2 +- src/bitboard.h | 2 +- src/evaluate.cpp | 2 +- src/evaluate.h | 2 +- src/main.cpp | 2 +- src/misc.cpp | 2 +- src/misc.h | 2 +- src/movegen.cpp | 2 +- src/movegen.h | 2 +- src/movepick.cpp | 2 +- src/movepick.h | 2 +- src/nnue/evaluate_nnue.cpp | 2 +- src/nnue/evaluate_nnue.h | 2 +- src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/features/half_ka_v2_hm.h | 2 +- src/nnue/layers/affine_transform.h | 2 +- src/nnue/layers/affine_transform_sparse_input.h | 2 +- src/nnue/layers/clipped_relu.h | 2 +- src/nnue/layers/simd.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_architecture.h | 2 +- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 2 +- src/position.cpp | 2 +- src/position.h | 2 +- src/search.cpp | 2 +- src/search.h | 2 +- src/syzygy/tbprobe.cpp | 2 +- src/syzygy/tbprobe.h | 2 +- src/thread.cpp | 2 +- src/thread.h | 2 +- src/thread_win32_osx.h | 2 +- src/timeman.cpp | 2 +- src/timeman.h | 2 +- src/tt.cpp | 2 +- src/tt.h | 2 +- src/tune.cpp | 2 +- src/tune.h | 2 +- src/types.h | 2 +- src/uci.cpp | 2 +- src/uci.h | 2 +- src/ucioption.cpp | 2 +- 46 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Makefile b/src/Makefile index ac354c7b99c..660b41e7edb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) +# Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 2270dcc3c83..50f8612d912 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.h b/src/benchmark.h index e6206d19349..86f8a0ad50b 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.cpp b/src/bitboard.cpp index a8a10cbb874..72afabb6554 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.h b/src/bitboard.h index 8b9c291807a..d028be02906 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 586cadc0ec5..b6342f189b8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.h b/src/evaluate.h index 33df13089fb..c2b08aaf1b0 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index 04879cc4673..78b3f54d919 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.cpp b/src/misc.cpp index 4193f8d2c7d..9350a4830df 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.h b/src/misc.h index 91fdb72f1b1..ca6cc16639f 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.cpp b/src/movegen.cpp index 7d6856bb036..750a07e85b7 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.h b/src/movegen.h index 9a39d1c50ea..3ae84c4c0cf 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.cpp b/src/movepick.cpp index ab37ff68e12..aa577541d66 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.h b/src/movepick.h index eefc0d5037b..242524338f3 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index e7339c10ae9..14e2fec15a4 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 6edc212f4d7..05c98bc5380 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 6d1b60ce43d..5789db4844a 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index c208e38dbad..8363184f430 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 44fa5d00a43..e6852236108 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 70dbd790469..0ac557abac2 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index a3a0c1ede9e..813234c59cd 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 5425ca192bc..6f4c9d206ed 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index b9d8f030a24..9c20df9d6f5 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 2f1b1d35e52..f6d705243ef 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index e4c308cb267..6c0e52b738e 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index d4bd0028969..4bc3408f18a 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index a83a77c9d71..2008cf25f1d 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.cpp b/src/position.cpp index c45dd7b2e22..32823bd05f2 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.h b/src/position.h index ce03c34f332..46956afc6e9 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.cpp b/src/search.cpp index c45e9f20c01..3553065fd99 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.h b/src/search.h index b2d22e612c4..72e275d36f7 100644 --- a/src/search.h +++ b/src/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 5fe28fd2966..c1275cf5755 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 3b7c8aa70fd..cc8eb0d4d70 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.cpp b/src/thread.cpp index eeab18821ba..e900a9ac88f 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.h b/src/thread.h index 1edc9cc9e31..22fe32c3a12 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 248e4a67450..4bc62d678ad 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.cpp b/src/timeman.cpp index f404ee0c353..77db2f621df 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.h b/src/timeman.h index 6c56d506b3f..0509158c3f4 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.cpp b/src/tt.cpp index 816d43f8603..5c4e6d537fc 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.h b/src/tt.h index 12fedd2d42f..82a66863b9d 100644 --- a/src/tt.h +++ b/src/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.cpp b/src/tune.cpp index cf80b9d7b70..44bfa682a7d 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.h b/src/tune.h index 480aea165b5..3d45e51c19e 100644 --- a/src/tune.h +++ b/src/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/types.h b/src/types.h index 3e00d68d19d..dde1a52c2a0 100644 --- a/src/types.h +++ b/src/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.cpp b/src/uci.cpp index 5f250a3617a..5dc9b2b0690 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.h b/src/uci.h index 55fb47c29ef..d249da7442b 100644 --- a/src/uci.h +++ b/src/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 43392e9a19d..087882f11b8 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 5546bc0a260d9bd01b1ef9d5b6b10fbbcb3a24b0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 1 Jan 2024 14:52:05 +0300 Subject: [PATCH 0329/1309] Simplification of partial_insertion_sort formula. Passed STC: https://tests.stockfishchess.org/tests/view/6590110879aa8af82b9562e9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134880 W: 34468 L: 34355 D: 66057 Ptnml(0-2): 476, 16060, 34220, 16243, 441 Passed LTC: https://tests.stockfishchess.org/tests/view/659156ca79aa8af82b957f07 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60780 W: 15179 L: 14996 D: 30605 Ptnml(0-2): 27, 6847, 16464, 7020, 32 closes https://github.com/official-stockfish/Stockfish/pull/4955 Bench: 1338331 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index aa577541d66..f33839cd04b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -300,7 +300,7 @@ Move MovePicker::next_move(bool skipQuiets) { endMoves = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -1960 - 3130 * depth); + partial_insertion_sort(cur, endMoves, -3330 * depth); } ++stage; From 28f8663f3947e716fefe392a463060dc12e39849 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Tue, 2 Jan 2024 02:54:45 +0000 Subject: [PATCH 0330/1309] Modify ttPV reduction This patch modifies ttPV reduction by reducing 1 more unless ttValue is above alpha. Inspired from @pb00068 https://tests.stockfishchess.org/tests/view/658060796a3b4f6202215f1f Passed STC: https://tests.stockfishchess.org/tests/view/6591867679aa8af82b958328 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 37856 W: 9727 L: 9407 D: 18722 Ptnml(0-2): 99, 4444, 9568, 4672, 145 Passed LTC: https://tests.stockfishchess.org/tests/view/6591d9b679aa8af82b958a6c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 128256 W: 32152 L: 31639 D: 64465 Ptnml(0-2): 64, 14364, 34772, 14851, 77 closes https://github.com/official-stockfish/Stockfish/pull/4957 Bench: 1176235 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 3553065fd99..aae3625a6da 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1152,7 +1152,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Decrease reduction if position is or has been on the PV (~4 Elo) if (ss->ttPv && !likelyFailLow) - r -= cutNode && tte->depth() >= depth ? 3 : 2; + r -= 1 + (cutNode && tte->depth() >= depth) + (ttValue > alpha); // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss - 1)->moveCount > 7) From cafbe8e8e8c26594dd7040788e6f72bc4bc8cfd9 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 23:13:18 +0100 Subject: [PATCH 0331/1309] Change the Move enum to a class This changes the Move enum to a class, this way all move related functions can be moved into the class and be more self contained. closes https://github.com/official-stockfish/Stockfish/pull/4958 No functional change --- src/movegen.cpp | 28 ++++----- src/movegen.h | 8 +-- src/movepick.cpp | 32 +++++----- src/movepick.h | 2 +- src/position.cpp | 86 +++++++++++++-------------- src/position.h | 10 ++-- src/search.cpp | 148 ++++++++++++++++++++++++----------------------- src/thread.cpp | 10 ++-- src/tt.cpp | 2 +- src/tt.h | 2 +- src/types.h | 111 +++++++++++++++++++++-------------- src/uci.cpp | 18 +++--- 12 files changed, 238 insertions(+), 219 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index 750a07e85b7..e6923067f88 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -34,13 +34,13 @@ ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; if constexpr (Type == CAPTURES || all) - *moveList++ = make(to - D, to, QUEEN); + *moveList++ = Move::make(to - D, to, QUEEN); if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all) { - *moveList++ = make(to - D, to, ROOK); - *moveList++ = make(to - D, to, BISHOP); - *moveList++ = make(to - D, to, KNIGHT); + *moveList++ = Move::make(to - D, to, ROOK); + *moveList++ = Move::make(to - D, to, BISHOP); + *moveList++ = Move::make(to - D, to, KNIGHT); } return moveList; @@ -89,13 +89,13 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta while (b1) { Square to = pop_lsb(b1); - *moveList++ = make_move(to - Up, to); + *moveList++ = Move(to - Up, to); } while (b2) { Square to = pop_lsb(b2); - *moveList++ = make_move(to - Up - Up, to); + *moveList++ = Move(to - Up - Up, to); } } @@ -128,13 +128,13 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta while (b1) { Square to = pop_lsb(b1); - *moveList++ = make_move(to - UpRight, to); + *moveList++ = Move(to - UpRight, to); } while (b2) { Square to = pop_lsb(b2); - *moveList++ = make_move(to - UpLeft, to); + *moveList++ = Move(to - UpLeft, to); } if (pos.ep_square() != SQ_NONE) @@ -150,7 +150,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta assert(b1); while (b1) - *moveList++ = make(pop_lsb(b1), pos.ep_square()); + *moveList++ = Move::make(pop_lsb(b1), pos.ep_square()); } } @@ -175,7 +175,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) b &= pos.check_squares(Pt); while (b) - *moveList++ = make_move(from, pop_lsb(b)); + *moveList++ = Move(from, pop_lsb(b)); } return moveList; @@ -213,12 +213,12 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { b &= ~attacks_bb(pos.square(~Us)); while (b) - *moveList++ = make_move(ksq, pop_lsb(b)); + *moveList++ = Move(ksq, pop_lsb(b)); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) - *moveList++ = make(ksq, pos.castling_rook_square(cr)); + *moveList++ = Move::make(ksq, pos.castling_rook_square(cr)); } return moveList; @@ -268,9 +268,9 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if (((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT) + if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT) && !pos.legal(*cur)) - *cur = (--moveList)->move; + *cur = *(--moveList); else ++cur; diff --git a/src/movegen.h b/src/movegen.h index 3ae84c4c0cf..5f650d2e36d 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -37,12 +37,10 @@ enum GenType { LEGAL }; -struct ExtMove { - Move move; - int value; +struct ExtMove: public Move { + int value; - operator Move() const { return move; } - void operator=(Move m) { move = m; } + void operator=(Move m) { data = m.raw(); } // Inhibit unwanted implicit conversions to Move // with an ambiguity that yields to a compile error. diff --git a/src/movepick.cpp b/src/movepick.cpp index f33839cd04b..cae018915f0 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -166,19 +166,19 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = - (7 * int(PieceValue[pos.piece_on(to_sq(m))]) - + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) + (7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]) / 16; else if constexpr (Type == QUIETS) { Piece pc = pos.moved_piece(m); PieceType pt = type_of(pos.moved_piece(m)); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); // histories - m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]; + m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -211,12 +211,12 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m))) + m.value = PieceValue[pos.piece_on(m.to_sq())] - Value(type_of(pos.moved_piece(m))) + (1 << 28); else - m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)] - + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][to_sq(m)]; + m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] + + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()]; } } @@ -235,7 +235,7 @@ Move MovePicker::select(Pred filter) { cur++; } - return MOVE_NONE; + return Move::none(); } // Most important method of the MovePicker class. It @@ -278,8 +278,7 @@ Move MovePicker::next_move(bool skipQuiets) { endMoves = std::end(refutations); // If the countermove is the same as a killer, skip it - if (refutations[0].move == refutations[2].move - || refutations[1].move == refutations[2].move) + if (refutations[0] == refutations[2] || refutations[1] == refutations[2]) --endMoves; ++stage; @@ -287,7 +286,7 @@ Move MovePicker::next_move(bool skipQuiets) { case REFUTATION : if (select([&]() { - return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); + return *cur != Move::none() && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); })) return *(cur - 1); ++stage; @@ -308,8 +307,7 @@ Move MovePicker::next_move(bool skipQuiets) { case QUIET : if (!skipQuiets && select([&]() { - return *cur != refutations[0].move && *cur != refutations[1].move - && *cur != refutations[2].move; + return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) return *(cur - 1); @@ -343,7 +341,7 @@ Move MovePicker::next_move(bool skipQuiets) { // If we did not find any move and we do not try checks, we have finished if (depth != DEPTH_QS_CHECKS) - return MOVE_NONE; + return Move::none(); ++stage; [[fallthrough]]; @@ -360,7 +358,7 @@ Move MovePicker::next_move(bool skipQuiets) { } assert(false); - return MOVE_NONE; // Silence warning + return Move::none(); // Silence warning } } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 242524338f3..ad4be8e9a9c 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -142,7 +142,7 @@ using CorrectionHistory = // MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which returns a // new pseudo-legal move each time it is called, until there are no moves left, -// when MOVE_NONE is returned. In order to improve the efficiency of the +// when Move::none() is returned. In order to improve the efficiency of the // alpha-beta algorithm, MovePicker attempts to return the moves which are most // likely to get a cut-off first. class MovePicker { diff --git a/src/position.cpp b/src/position.cpp index 32823bd05f2..810bba57d6f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -140,14 +140,14 @@ void Position::init() { for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2) if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2)) { - Move move = make_move(s1, s2); + Move move = Move(s1, s2); Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; int i = H1(key); while (true) { std::swap(cuckoo[i], key); std::swap(cuckooMove[i], move); - if (move == MOVE_NONE) // Arrived at empty slot? + if (move == Move::none()) // Arrived at empty slot? break; i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot } @@ -487,11 +487,11 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { // Tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { - assert(is_ok(m)); + assert(m.is_ok()); Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); assert(color_of(moved_piece(m)) == us); assert(piece_on(square(us)) == make_piece(us, KING)); @@ -499,7 +499,7 @@ bool Position::legal(Move m) const { // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { Square ksq = square(us); Square capsq = to - pawn_push(us); @@ -516,7 +516,7 @@ bool Position::legal(Move m) const { // Castling moves generation does not check if the castling path is clear of // enemy attacks, it is delayed at a later time: now! - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { // After castling, the rook and king final positions are the same in // Chess960 as they would be in standard chess. @@ -529,7 +529,7 @@ bool Position::legal(Move m) const { // In case of Chess960, verify if the Rook blocks some checks. // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. - return !chess960 || !(blockers_for_king(us) & to_sq(m)); + return !chess960 || !(blockers_for_king(us) & m.to_sq()); } // If the moving piece is a king, check whether the destination square is @@ -549,18 +549,18 @@ bool Position::legal(Move m) const { bool Position::pseudo_legal(const Move m) const { Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = moved_piece(m); // Use a slower but simpler function for uncommon cases // yet we skip the legality check of MoveList(). - if (type_of(m) != NORMAL) + if (m.type_of() != NORMAL) return checkers() ? MoveList(*this).contains(m) : MoveList(*this).contains(m); // Is not a promotion, so the promotion piece must be empty - assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE); + assert(m.promotion_type() - KNIGHT == NO_PIECE_TYPE); // If the 'from' square is not occupied by a piece belonging to the side to // move, the move is obviously not legal. @@ -615,11 +615,11 @@ bool Position::pseudo_legal(const Move m) const { // Tests whether a pseudo-legal move gives a check bool Position::gives_check(Move m) const { - assert(is_ok(m)); + assert(m.is_ok()); assert(color_of(moved_piece(m)) == sideToMove); - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); // Is there a direct check? if (check_squares(type_of(piece_on(from))) & to) @@ -627,15 +627,15 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || type_of(m) == CASTLING; + return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; - switch (type_of(m)) + switch (m.type_of()) { case NORMAL : return false; case PROMOTION : - return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle @@ -664,7 +664,7 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { - assert(is_ok(m)); + assert(m.is_ok()); assert(&newSt != st); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); @@ -691,16 +691,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Color us = sideToMove; Color them = ~us; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(from); - Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); assert(color_of(pc) == us); - assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us)); assert(type_of(captured) != KING); - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { assert(pc == make_piece(us, KING)); assert(captured == make_piece(us, ROOK)); @@ -720,7 +720,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // update non-pawn material. if (type_of(captured) == PAWN) { - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { capsq -= pawn_push(us); @@ -771,7 +771,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { } // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) + if (m.type_of() != CASTLING) { dp.piece[0] = pc; dp.from[0] = from; @@ -791,9 +791,9 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { k ^= Zobrist::enpassant[file_of(st->epSquare)]; } - else if (type_of(m) == PROMOTION) + else if (m.type_of() == PROMOTION) { - Piece promotion = make_piece(us, promotion_type(m)); + Piece promotion = make_piece(us, m.promotion_type()); assert(relative_rank(us, to) == RANK_8); assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); @@ -866,22 +866,22 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // be restored to exactly the same state as before the move was made. void Position::undo_move(Move m) { - assert(is_ok(m)); + assert(m.is_ok()); sideToMove = ~sideToMove; Color us = sideToMove; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(to); - assert(empty(from) || type_of(m) == CASTLING); + assert(empty(from) || m.type_of() == CASTLING); assert(type_of(st->capturedPiece) != KING); - if (type_of(m) == PROMOTION) + if (m.type_of() == PROMOTION) { assert(relative_rank(us, to) == RANK_8); - assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) == m.promotion_type()); assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); remove_piece(to); @@ -889,7 +889,7 @@ void Position::undo_move(Move m) { put_piece(pc, to); } - if (type_of(m) == CASTLING) + if (m.type_of() == CASTLING) { Square rfrom, rto; do_castling(us, from, to, rfrom, rto); @@ -902,7 +902,7 @@ void Position::undo_move(Move m) { { Square capsq = to; - if (type_of(m) == EN_PASSANT) + if (m.type_of() == EN_PASSANT) { capsq -= pawn_push(us); @@ -1011,8 +1011,8 @@ void Position::undo_null_move() { // en passant and promotions. Key Position::key_after(Move m) const { - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); Piece pc = piece_on(from); Piece captured = piece_on(to); Key k = st->key ^ Zobrist::side; @@ -1031,13 +1031,13 @@ Key Position::key_after(Move m) const { // algorithm similar to alpha-beta pruning with a null window. bool Position::see_ge(Move m, Value threshold) const { - assert(is_ok(m)); + assert(m.is_ok()); // Only deal with normal moves, assume others pass a simple SEE - if (type_of(m) != NORMAL) + if (m.type_of() != NORMAL) return VALUE_ZERO >= threshold; - Square from = from_sq(m), to = to_sq(m); + Square from = m.from_sq(), to = m.to_sq(); int swap = PieceValue[piece_on(to)] - threshold; if (swap < 0) @@ -1182,8 +1182,8 @@ bool Position::has_game_cycle(int ply) const { if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) { Move move = cuckooMove[j]; - Square s1 = from_sq(move); - Square s2 = to_sq(move); + Square s1 = move.from_sq(); + Square s2 = move.to_sq(); if (!((between_bb(s1, s2) ^ s2) & pieces())) { diff --git a/src/position.h b/src/position.h index 46956afc6e9..3e9327592fa 100644 --- a/src/position.h +++ b/src/position.h @@ -210,7 +210,7 @@ inline Piece Position::piece_on(Square s) const { inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } -inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); } +inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } @@ -312,16 +312,16 @@ inline int Position::rule50_count() const { return st->rule50; } inline bool Position::is_chess960() const { return chess960; } inline bool Position::capture(Move m) const { - assert(is_ok(m)); - return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT; + assert(m.is_ok()); + return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT; } // Returns true if a move is generated from the capture stage, having also // queen promotions covered, i.e. consistency with the capture stage move generation // is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { - assert(is_ok(m)); - return capture(m) || promotion_type(m) == QUEEN; + assert(m.is_ok()); + return capture(m) || m.promotion_type() == QUEEN; } inline Piece Position::captured_piece() const { return st->capturedPiece; } diff --git a/src/search.cpp b/src/search.cpp index aae3625a6da..0d41f48d981 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -130,7 +130,7 @@ struct Skill { Move pick_best(size_t multiPV); double level; - Move best = MOVE_NONE; + Move best = Move::none(); }; template @@ -226,7 +226,7 @@ void MainThread::search() { if (rootMoves.empty()) { - rootMoves.emplace_back(MOVE_NONE); + rootMoves.emplace_back(Move::none()); sync_cout << "info depth 0 score " << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; } @@ -262,7 +262,7 @@ void MainThread::search() { Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled() - && rootMoves[0].pv[0] != MOVE_NONE) + && rootMoves[0].pv[0] != Move::none()) bestThread = Threads.get_best_thread(); bestPreviousScore = bestThread->rootMoves[0].score; @@ -293,7 +293,7 @@ void Thread::search() { Stack stack[MAX_PLY + 10], *ss = stack + 7; Move pv[MAX_PLY + 1]; Value alpha, beta, delta; - Move lastBestMove = MOVE_NONE; + Move lastBestMove = Move::none(); Depth lastBestMoveDepth = 0; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); double timeReduction = 1, totBestMoveChanges = 0; @@ -604,11 +604,11 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss + 1)->excludedMove = bestMove = MOVE_NONE; - (ss + 2)->killers[0] = (ss + 2)->killers[1] = MOVE_NONE; + (ss + 1)->excludedMove = bestMove = Move::none(); + (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; ss->doubleExtensions = (ss - 1)->doubleExtensions; - Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; // Step 4. Transposition table lookup. @@ -618,7 +618,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() - : MOVE_NONE; + : Move::none(); ttCapture = ttMove && pos.capture_stage(ttMove); // At this point, if excluded, skip straight to step 6, static eval. However, @@ -650,8 +650,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else if (!ttCapture) { int penalty = -stat_malus(depth); - thisThread->mainHistory[us][from_to(ttMove)] << penalty; - update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); + thisThread->mainHistory[us][ttMove.from_to()] << penalty; + update_continuation_histories(ss, pos.moved_piece(ttMove), ttMove.to_sq(), penalty); } } @@ -699,7 +699,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), MOVE_NONE, VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE); return value; } @@ -764,17 +764,17 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->staticEval = eval = to_static_eval(newEval); // Static evaluation is saved as it was before adjustment by correction history - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), unadjustedStaticEval); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) - if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); bonus = bonus > 0 ? 2 * bonus : bonus / 2; - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus; - if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION) + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; + 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 / 4; } @@ -810,9 +810,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta - && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 && !excludedMove - && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17496 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 + && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); @@ -820,7 +820,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Null move dynamic reduction based on depth and eval Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; - ss->currentMove = MOVE_NULL; + ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; pos.do_null_move(st); @@ -883,7 +883,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) { assert(pos.capture_stage(move)); @@ -894,7 +894,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->currentMove = move; ss->continuationHistory = &thisThread - ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][to_sq(move)]; + ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; pos.do_move(move, st); @@ -938,7 +938,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo (ss - 6)->continuationHistory}; Move countermove = - prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE; + prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, &thisThread->pawnHistory, countermove, ss->killers); @@ -953,9 +953,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) + while ((move = mp.next_move(moveCountPruning)) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); if (move == excludedMove) continue; @@ -1009,10 +1009,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Piece capturedPiece = pos.piece_on(to_sq(move)); + Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] - + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7; + + captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) continue; } @@ -1024,15 +1024,16 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else { int history = - (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][to_sq(move)]; + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) if (lmrDepth < 6 && history < -3752 * depth) continue; - history += 2 * thisThread->mainHistory[us][from_to(move)]; + history += 2 * thisThread->mainHistory[us][move.from_to()]; lmrDepth += history / 7838; lmrDepth = std::max(lmrDepth, -1); @@ -1077,7 +1078,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->excludedMove = move; value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = MOVE_NONE; + ss->excludedMove = Move::none(); if (value < singularBeta) { @@ -1125,12 +1126,13 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][to_sq(move)] >= 4325) + && (*contHist[0])[movedPiece][move.to_sq()] >= 4325) extension = 1; // Recapture extensions (~1 Elo) - else if (PvNode && move == ttMove && to_sq(move) == prevSq - && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] + else if (PvNode && move == ttMove && move.to_sq() == prevSq + && captureHistory[movedPiece][move.to_sq()] + [type_of(pos.piece_on(move.to_sq()))] > 4146) extension = 1; } @@ -1145,7 +1147,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = - &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][to_sq(move)]; + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; // Step 16. Make the move pos.do_move(move, st, givesCheck); @@ -1187,10 +1189,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo else if (move == ttMove) r = 0; - ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - 3817; + ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] - 3817; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) r -= ss->statScore / 14767; @@ -1229,7 +1231,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo : value >= beta ? stat_bonus(newDepth) : 0; - update_continuation_histories(ss, movedPiece, to_sq(move), bonus); + update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } @@ -1249,7 +1251,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (PvNode && (moveCount == 1 || value > alpha)) { (ss + 1)->pv = pv; - (ss + 1)->pv[0] = MOVE_NONE; + (ss + 1)->pv[0] = Move::none(); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); } @@ -1296,7 +1298,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo assert((ss + 1)->pv); - for (Move* m = (ss + 1)->pv; *m != MOVE_NONE; ++m) + for (Move* m = (ss + 1)->pv; *m != Move::none(); ++m) rm.pv.push_back(*m); // We record how often the best move has been changed in each iteration. @@ -1375,7 +1377,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); - thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << stat_bonus(depth) * bonus / 2; } @@ -1451,11 +1453,11 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (PvNode) { (ss + 1)->pv = pv; - ss->pv[0] = MOVE_NONE; + ss->pv[0] = Move::none(); } Thread* thisThread = pos.this_thread(); - bestMove = MOVE_NONE; + bestMove = Move::none(); ss->inCheck = pos.checkers(); moveCount = 0; @@ -1476,7 +1478,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { posKey = pos.key(); tte = TT.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = ss->ttHit ? tte->move() : MOVE_NONE; + ttMove = ss->ttHit ? tte->move() : Move::none(); pvHit = ss->ttHit && tte->is_pv(); // At non-PV nodes we check for an early TT cutoff @@ -1513,7 +1515,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval; + (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; Value newEval = ss->staticEval @@ -1527,7 +1529,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - MOVE_NONE, unadjustedStaticEval); + Move::none(), unadjustedStaticEval); return bestValue; } @@ -1545,7 +1547,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) // will be generated. - Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); @@ -1553,9 +1555,9 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move()) != Move::none()) { - assert(is_ok(move)); + assert(move.is_ok()); // Check for legality if (!pos.legal(move)) @@ -1570,13 +1572,13 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if (!givesCheck && to_sq(move) != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY - && type_of(move) != PROMOTION) + if (!givesCheck && move.to_sq() != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + && move.type_of() != PROMOTION) { if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))]; + 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. @@ -1610,8 +1612,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { break; // Continuation history based pruning (~3 Elo) - if (!capture && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0 - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0) + if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] < 0 + && (*contHist[1])[pos.moved_piece(move)][move.to_sq()] < 0) continue; // Do not search moves with bad enough SEE values (~5 Elo) @@ -1626,7 +1628,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->currentMove = move; ss->continuationHistory = &thisThread - ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][to_sq(move)]; + ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; quietCheckEvasions += !capture && ss->inCheck; @@ -1738,9 +1740,9 @@ Value value_from_tt(Value v, int ply, int r50c) { // Adds current move and appends child pv[] void update_pv(Move* pv, Move move, const Move* childPv) { - for (*pv++ = move; childPv && *childPv != MOVE_NONE;) + for (*pv++ = move; childPv && *childPv != Move::none();) *pv++ = *childPv++; - *pv = MOVE_NONE; + *pv = Move::none(); } @@ -1775,25 +1777,25 @@ void update_all_stats(const Position& pos, update_quiet_stats(pos, ss, bestMove, bestMoveBonus); int pIndex = pawn_structure_index(pos); - thisThread->pawnHistory[pIndex][moved_piece][to_sq(bestMove)] << quietMoveBonus; + thisThread->pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread - ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][to_sq(quietsSearched[i])] + ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] << -quietMoveMalus; - thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus; + thisThread->mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - to_sq(quietsSearched[i]), -quietMoveMalus); + quietsSearched[i].to_sq(), -quietMoveMalus); } } else { // Increase stats for the best move in case it was a capture move - captured = type_of(pos.piece_on(to_sq(bestMove))); - captureHistory[moved_piece][to_sq(bestMove)][captured] << quietMoveBonus; + captured = type_of(pos.piece_on(bestMove.to_sq())); + captureHistory[moved_piece][bestMove.to_sq()][captured] << quietMoveBonus; } // Extra penalty for a quiet early move that was not a TT move or @@ -1808,8 +1810,8 @@ void update_all_stats(const Position& pos, for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); - captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); - captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveMalus; + captured = type_of(pos.piece_on(capturesSearched[i].to_sq())); + captureHistory[moved_piece][capturesSearched[i].to_sq()][captured] << -quietMoveMalus; } } @@ -1823,7 +1825,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; - if (is_ok((ss - i)->currentMove)) + if (((ss - i)->currentMove).is_ok()) (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); } } @@ -1841,13 +1843,13 @@ void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { Color us = pos.side_to_move(); Thread* thisThread = pos.this_thread(); - thisThread->mainHistory[us][from_to(move)] << bonus; - update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + thisThread->mainHistory[us][move.from_to()] << bonus; + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); // Update countermove history - if (is_ok((ss - 1)->currentMove)) + if (((ss - 1)->currentMove).is_ok()) { - Square prevSq = to_sq((ss - 1)->currentMove); + Square prevSq = ((ss - 1)->currentMove).to_sq(); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } @@ -1987,7 +1989,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { assert(pv.size() == 1); - if (pv[0] == MOVE_NONE) + if (pv[0] == Move::none()) return false; pos.do_move(pv[0], st); diff --git a/src/thread.cpp b/src/thread.cpp index e900a9ac88f..01ccd4fc565 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include @@ -66,7 +66,7 @@ Thread::~Thread() { // Reset histories, usually before a new game void Thread::clear() { - counterMoves.fill(MOVE_NONE); + counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(0); pawnHistory.fill(0); @@ -220,9 +220,9 @@ void ThreadPool::start_thinking(Position& pos, Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); - std::map votes; - Value minScore = VALUE_NONE; + Thread* bestThread = threads.front(); + std::unordered_map votes; + Value minScore = VALUE_NONE; // Find the minimum score of all threads for (Thread* th : threads) diff --git a/src/tt.cpp b/src/tt.cpp index 5c4e6d537fc..2e3f7d32835 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -39,7 +39,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) // Preserve any existing move for the same position if (m || uint16_t(k) != key16) - move16 = uint16_t(m); + move16 = m; // Overwrite less valuable entries (cheapest checks first) if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) diff --git a/src/tt.h b/src/tt.h index 82a66863b9d..61e854c1af2 100644 --- a/src/tt.h +++ b/src/tt.h @@ -53,7 +53,7 @@ struct TTEntry { uint16_t key16; uint8_t depth8; uint8_t genBound8; - uint16_t move16; + Move move16; int16_t value16; int16_t eval16; }; diff --git a/src/types.h b/src/types.h index dde1a52c2a0..2970d1e00ce 100644 --- a/src/types.h +++ b/src/types.h @@ -108,30 +108,6 @@ using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -// A move needs 16 bits to be stored -// -// bit 0- 5: destination square (from 0 to 63) -// bit 6-11: origin square (from 0 to 63) -// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) -// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) -// NOTE: en passant bit is set only when a pawn can be captured -// -// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in -// any normal move destination square is always different from origin square -// while MOVE_NONE and MOVE_NULL have the same origin and destination square. - -enum Move : int { - MOVE_NONE, - MOVE_NULL = 65 -}; - -enum MoveType { - NORMAL, - PROMOTION = 1 << 14, - EN_PASSANT = 2 << 14, - CASTLING = 3 << 14 -}; - enum Color { WHITE, BLACK, @@ -353,8 +329,6 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } -constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; } - constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } constexpr File file_of(Square s) { return File(s & 7); } @@ -369,33 +343,80 @@ constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_o constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -constexpr Square from_sq(Move m) { - assert(is_ok(m)); - return Square((m >> 6) & 0x3F); -} -constexpr Square to_sq(Move m) { - assert(is_ok(m)); - return Square(m & 0x3F); +// Based on a congruential pseudo-random number generator +constexpr Key make_key(uint64_t seed) { + return seed * 6364136223846793005ULL + 1442695040888963407ULL; } -constexpr int from_to(Move m) { return m & 0xFFF; } -constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 +}; + +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are Move::none() and Move::null(). We can sneak these in because in +// any normal move destination square is always different from origin square +// while Move::none() and Move::null() have the same origin and destination square. +class Move { + public: + Move() = default; + constexpr explicit Move(std::uint16_t d) : + data(d) {} + + constexpr Move(Square from, Square to) : + data((from << 6) + to) {} -constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } + template + static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + } -constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); } + constexpr Square from_sq() const { + assert(is_ok()); + return Square((data >> 6) & 0x3F); + } -template -constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { - return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); -} + constexpr Square to_sq() const { + assert(is_ok()); + return Square(data & 0x3F); + } -// Based on a congruential pseudo-random number generator -constexpr Key make_key(uint64_t seed) { - return seed * 6364136223846793005ULL + 1442695040888963407ULL; -} + constexpr int from_to() const { return data & 0xFFF; } + + constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } + + constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } + + constexpr bool is_ok() const { return none().data != data && null().data != data; } + + static constexpr Move null() { return Move(65); } + static constexpr Move none() { return Move(0); } + + constexpr bool operator==(const Move& m) const { return data == m.data; } + constexpr bool operator!=(const Move& m) const { return data != m.data; } + + constexpr explicit operator bool() const { return data != 0; } + + constexpr std::uint16_t raw() const { return data; } + + struct MoveHash { + std::size_t operator()(const Move& m) const { return m.data; } + }; + + protected: + std::uint16_t data; +}; } // namespace Stockfish diff --git a/src/uci.cpp b/src/uci.cpp index 5dc9b2b0690..8e93eee6dc5 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -75,7 +75,7 @@ void position(Position& pos, std::istringstream& is, StateListPtr& states) { pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse the move list, if any - while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + while (is >> token && (m = UCI::to_move(pos, token)) != Move::none()) { states->emplace_back(); pos.do_move(m, states->back()); @@ -395,22 +395,22 @@ std::string UCI::square(Square s) { // Internally, all castling moves are always encoded as 'king captures rook'. std::string UCI::move(Move m, bool chess960) { - if (m == MOVE_NONE) + if (m == Move::none()) return "(none)"; - if (m == MOVE_NULL) + if (m == Move::null()) return "0000"; - Square from = from_sq(m); - Square to = to_sq(m); + Square from = m.from_sq(); + Square to = m.to_sq(); - if (type_of(m) == CASTLING && !chess960) + if (m.type_of() == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); std::string move = UCI::square(from) + UCI::square(to); - if (type_of(m) == PROMOTION) - move += " pnbrqk"[promotion_type(m)]; + if (m.type_of() == PROMOTION) + move += " pnbrqk"[m.promotion_type()]; return move; } @@ -427,7 +427,7 @@ Move UCI::to_move(const Position& pos, std::string& str) { if (str == UCI::move(m, pos.is_chess960())) return m; - return MOVE_NONE; + return Move::none(); } } // namespace Stockfish From 49308929852731e118b2c6d5d4232e930182cc11 Mon Sep 17 00:00:00 2001 From: RainRat Date: Tue, 2 Jan 2024 23:01:24 -0800 Subject: [PATCH 0332/1309] Fix typo in tbprobe.cpp closes https://github.com/official-stockfish/Stockfish/pull/4959 No functional change --- src/syzygy/tbprobe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index c1275cf5755..91013dcac28 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -863,7 +863,7 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 - // triangle to 0...5. There are 63 squares for second piece and and 62 + // triangle to 0...5. There are 63 squares for second piece and 62 // (mapped to 0...61) for the third. if (off_A1H8(squares[0])) idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2; From b987d4f0332f57a58157641bf3a6e437133e7879 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 1 Jan 2024 20:45:14 +0100 Subject: [PATCH 0333/1309] Use type aliases instead of enums for Value types The primary rationale behind this lies in the fact that enums were not originally designed to be employed in the manner we currently utilize them. The Value enum was used like a type alias throughout the code and was often misused. Furthermore, changing the underlying size of the enum to int16_t broke everything, mostly because of the operator overloads for the Value enum, were causing data to be truncated. Since Value is now a type alias, the operator overloads are no longer required. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/6593b8bb79aa8af82b95b401 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235296 W: 59919 L: 59917 D: 115460 Ptnml(0-2): 743, 27085, 62054, 26959, 807 closes https://github.com/official-stockfish/Stockfish/pull/4960 No functional change --- src/evaluate.cpp | 10 ++++---- src/evaluate.h | 2 +- src/movepick.cpp | 9 ++++--- src/movepick.h | 4 ++-- src/nnue/evaluate_nnue.h | 2 +- src/position.cpp | 2 +- src/position.h | 2 +- src/search.cpp | 26 ++++++++++---------- src/thread.h | 6 +++-- src/tune.cpp | 15 ------------ src/tune.h | 4 +--- src/types.h | 51 +++++++++++++++++++++------------------- 12 files changed, 60 insertions(+), 73 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index b6342f189b8..bda7132a1ff 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -148,7 +148,7 @@ void NNUE::verify() { // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. -Value Eval::simple_eval(const Position& pos, Color c) { +int Eval::simple_eval(const Position& pos, Color c) { return PawnValue * (pos.count(c) - pos.count(~c)) + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } @@ -160,7 +160,7 @@ Value Eval::evaluate(const Position& pos) { assert(!pos.checkers()); - Value v; + int v; Color stm = pos.side_to_move(); int shuffling = pos.rule50_count(); int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); @@ -170,13 +170,13 @@ Value Eval::evaluate(const Position& pos) { + std::abs(pos.this_thread()->rootSimpleEval); if (lazy) - v = Value(simpleEval); + v = simpleEval; else { int nnueComplexity; Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); - Value optimism = pos.this_thread()->optimism[stm]; + int optimism = pos.this_thread()->optimism[stm]; // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; @@ -190,7 +190,7 @@ Value Eval::evaluate(const Position& pos) { v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range - v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + v = std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); return v; } diff --git a/src/evaluate.h b/src/evaluate.h index c2b08aaf1b0..0a7ec61a3cf 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -31,7 +31,7 @@ namespace Eval { std::string trace(Position& pos); -Value simple_eval(const Position& pos, Color c); +int simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); extern std::string currentEvalFileName; diff --git a/src/movepick.cpp b/src/movepick.cpp index cae018915f0..14b6c87a744 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -127,7 +127,7 @@ MovePicker::MovePicker(const Position& p, // Constructor for ProbCut: we generate captures with SEE greater // than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) : +MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), ttMove(ttm), @@ -211,8 +211,8 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(m.to_sq())] - Value(type_of(pos.moved_piece(m))) - + (1 << 28); + m.value = + PieceValue[pos.piece_on(m.to_sq())] - type_of(pos.moved_piece(m)) + (1 << 28); else m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] @@ -268,8 +268,7 @@ Move MovePicker::next_move(bool skipQuiets) { case GOOD_CAPTURE : if (select([&]() { // Move losing capture to endBadCaptures to be tried later - return pos.see_ge(*cur, Value(-cur->value)) ? true - : (*endBadCaptures++ = *cur, false); + return pos.see_ge(*cur, -cur->value) ? true : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); diff --git a/src/movepick.h b/src/movepick.h index ad4be8e9a9c..c429f8aec9a 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -171,7 +171,7 @@ class MovePicker { const CapturePieceToHistory*, const PieceToHistory**, const PawnHistory*); - MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); private: @@ -190,7 +190,7 @@ class MovePicker { Move ttMove; ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; int stage; - Value threshold; + int threshold; Depth depth; ExtMove moves[MAX_MOVES]; }; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index 05c98bc5380..f80aa398bba 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -30,10 +30,10 @@ #include "../misc.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" +#include "../types.h" namespace Stockfish { class Position; -enum Value : int; } namespace Stockfish::Eval::NNUE { diff --git a/src/position.cpp b/src/position.cpp index 810bba57d6f..4fba3c234b7 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1029,7 +1029,7 @@ Key Position::key_after(Move m) const { // Tests if the SEE (Static Exchange Evaluation) // value of move is greater or equal to the given threshold. We'll use an // algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value threshold) const { +bool Position::see_ge(Move m, int threshold) const { assert(m.is_ok()); diff --git a/src/position.h b/src/position.h index 3e9327592fa..7e0c3eefd77 100644 --- a/src/position.h +++ b/src/position.h @@ -141,7 +141,7 @@ class Position { void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + bool see_ge(Move m, int threshold = 0) const; // Accessing hash keys Key key() const; diff --git a/src/search.cpp b/src/search.cpp index 0d41f48d981..9dc4ee988dd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,13 +77,13 @@ enum NodeType { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return Value((116 - 44 * noTtCutNode) * (d - improving)); + return ((116 - 44 * noTtCutNode) * (d - improving)); } // Reductions lookup table initialized at startup int Reductions[MAX_MOVES]; // [depth or moveNumber] -Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) { +Depth reduction(bool i, Depth d, int mn, int delta, int rootDelta) { int reductionScale = Reductions[d] * Reductions[mn]; return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 + (!i && reductionScale > 880); @@ -95,7 +95,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Guarantee evaluation does not hit the tablebase range constexpr Value to_static_eval(const Value v) { - return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth @@ -292,13 +292,13 @@ void Thread::search() { // (ss + 2) is needed for initialization of cutOffCnt and killers. Stack stack[MAX_PLY + 10], *ss = stack + 7; Move pv[MAX_PLY + 1]; - Value alpha, beta, delta; + Value alpha, beta; Move lastBestMove = Move::none(); Depth lastBestMoveDepth = 0; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int iterIdx = 0; + Color us = rootPos.side_to_move(); + int delta, iterIdx = 0; std::memset(ss - 7, 0, 10 * sizeof(Stack)); for (int i = 7; i > 0; --i) @@ -374,7 +374,7 @@ void Thread::search() { Value avg = rootMoves[pvIdx].averageScore; delta = Value(9) + int(avg) * avg / 14847; alpha = std::max(avg - delta, -VALUE_INFINITE); - beta = std::min(avg + delta, VALUE_INFINITE); + beta = std::min(avg + delta, int(VALUE_INFINITE)); // Adjust optimism based on root move's averageScore (~4 Elo) optimism[us] = 121 * avg / (std::abs(avg) + 109); @@ -425,7 +425,7 @@ void Thread::search() { } else if (bestValue >= beta) { - beta = std::min(bestValue + delta, VALUE_INFINITE); + beta = std::min(bestValue + delta, int(VALUE_INFINITE)); ++failedHighCnt; } else @@ -989,7 +989,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Calculate new depth for this move newDepth = depth - 1; - Value delta = beta - alpha; + int delta = beta - alpha; Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); @@ -1018,7 +1018,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, Value(-187) * depth)) + if (!pos.see_ge(move, -187 * depth)) continue; } else @@ -1048,7 +1048,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, Value(-26 * lmrDepth * lmrDepth))) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1617,7 +1617,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, Value(-77))) + if (!pos.see_ge(move, -77)) continue; } @@ -1863,7 +1863,7 @@ Move Skill::pick_best(size_t multiPV) { // RootMoves are already sorted by score in descending order Value topScore = rootMoves[0].score; - int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue); + int delta = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue)); int maxScore = -VALUE_INFINITE; double weakness = 120 - 2 * level; diff --git a/src/thread.h b/src/thread.h index 22fe32c3a12..7db7c15905b 100644 --- a/src/thread.h +++ b/src/thread.h @@ -56,13 +56,15 @@ class Thread { size_t pvIdx, pvLast; std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; - Value bestValue, optimism[COLOR_NB]; + Value bestValue; + + int optimism[COLOR_NB]; Position rootPos; StateInfo rootState; Search::RootMoves rootMoves; Depth rootDepth, completedDepth; - Value rootDelta; + int rootDelta; Value rootSimpleEval; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; diff --git a/src/tune.cpp b/src/tune.cpp index 44bfa682a7d..1dddca0c37d 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -26,10 +26,6 @@ #include "uci.h" -namespace Stockfish { -enum Value : int; -} - using std::string; namespace Stockfish { @@ -92,17 +88,6 @@ void Tune::Entry::read_option() { value = int(Options[name]); } -template<> -void Tune::Entry::init_option() { - make_option(name, value, range); -} - -template<> -void Tune::Entry::read_option() { - if (Options.count(name)) - value = Value(int(Options[name])); -} - // Instead of a variable here we have a PostUpdate function: just call it template<> void Tune::Entry::init_option() {} diff --git a/src/tune.h b/src/tune.h index 3d45e51c19e..17057001225 100644 --- a/src/tune.h +++ b/src/tune.h @@ -27,7 +27,6 @@ #include namespace Stockfish { -enum Value : int; using Range = std::pair; // Option's min-max values using RangeFun = Range(int); @@ -101,8 +100,7 @@ class Tune { static_assert(!std::is_const_v, "Parameter cannot be const!"); - static_assert(std::is_same_v || std::is_same_v - || std::is_same_v, + static_assert(std::is_same_v || std::is_same_v, "Parameter type not supported!"); Entry(const std::string& n, T& v, const SetRange& r) : diff --git a/src/types.h b/src/types.h index 2970d1e00ce..ca9ef6152ba 100644 --- a/src/types.h +++ b/src/types.h @@ -137,29 +137,33 @@ enum Bound { BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; -enum Value : int { - VALUE_ZERO = 0, - VALUE_DRAW = 0, - VALUE_NONE = 32002, - VALUE_INFINITE = 32001, - - VALUE_MATE = 32000, - VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, - VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, - - VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1, - VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY, - VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, - - // In the code, we make the assumption that these values - // are such that non_pawn_material() can be used to uniquely - // identify the material on the board. - PawnValue = 208, - KnightValue = 781, - BishopValue = 825, - RookValue = 1276, - QueenValue = 2538, -}; +// Value is used as an alias for int16_t, this is done to differentiate between +// a search value and any other integer value. The values used in search are always +// supposed to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range. +using Value = int; + +constexpr Value VALUE_ZERO = 0; +constexpr Value VALUE_DRAW = 0; +constexpr Value VALUE_NONE = 32002; +constexpr Value VALUE_INFINITE = 32001; + +constexpr Value VALUE_MATE = 32000; +constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; +constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; + +constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; +constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; +constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; + +// In the code, we make the assumption that these values +// are such that non_pawn_material() can be used to uniquely +// identify the material on the board. +constexpr Value PawnValue = 208; +constexpr Value KnightValue = 781; +constexpr Value BishopValue = 825; +constexpr Value RookValue = 1276; +constexpr Value QueenValue = 2538; + // clang-format off enum PieceType { @@ -280,7 +284,6 @@ struct DirtyPiece { inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } -ENABLE_FULL_OPERATORS_ON(Value) ENABLE_FULL_OPERATORS_ON(Direction) ENABLE_INCR_OPERATORS_ON(PieceType) From 8b4583bce76da7d27aaa565e6302d2e540cd496a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 3 Jan 2024 17:32:57 +0300 Subject: [PATCH 0334/1309] Remove redundant int cast Remove a redundant int cast in the calculation of fwdOut. The variable OutputType is already defined as std::int32_t, which is an integer type, making the cast unnecessary. closes https://github.com/official-stockfish/Stockfish/pull/4961 No functional change --- src/nnue/nnue_architecture.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 6c0e52b738e..92445704296 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -116,7 +116,7 @@ struct Network { // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1< Date: Fri, 5 Jan 2024 21:03:19 +0800 Subject: [PATCH 0335/1309] Remove unneeded operator overload macros Only Direction type is using two of the enable overload macros. Aside from this, only two of the overloads are even being used. Therefore, we can just define the needed overloads and remove the macros. closes https://github.com/official-stockfish/Stockfish/pull/4966 No functional change. --- src/types.h | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/types.h b/src/types.h index ca9ef6152ba..e83b306dc73 100644 --- a/src/types.h +++ b/src/types.h @@ -264,36 +264,19 @@ struct DirtyPiece { Square to[3]; }; - #define ENABLE_BASE_OPERATORS_ON(T) \ - constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ - constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ - constexpr T operator-(T d) { return T(-int(d)); } \ - inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ - inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } - #define ENABLE_INCR_OPERATORS_ON(T) \ inline T& operator++(T& d) { return d = T(int(d) + 1); } \ inline T& operator--(T& d) { return d = T(int(d) - 1); } - #define ENABLE_FULL_OPERATORS_ON(T) \ - ENABLE_BASE_OPERATORS_ON(T) \ - constexpr T operator*(int i, T d) { return T(i * int(d)); } \ - constexpr T operator*(T d, int i) { return T(int(d) * i); } \ - constexpr T operator/(T d, int i) { return T(int(d) / i); } \ - constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ - inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ - inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } - -ENABLE_FULL_OPERATORS_ON(Direction) - ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) ENABLE_INCR_OPERATORS_ON(File) ENABLE_INCR_OPERATORS_ON(Rank) - #undef ENABLE_FULL_OPERATORS_ON #undef ENABLE_INCR_OPERATORS_ON - #undef ENABLE_BASE_OPERATORS_ON + +constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int(d1) + int(d2)); } +constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } // Additional operators to add a Direction to a Square constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } From 6f9071c64354a34970e7b5669701d0ad15b7a694 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 6 Jan 2024 07:42:53 +0300 Subject: [PATCH 0336/1309] Tweak usage of correction history Instead of using linear formula use quadratic one. Maximum impact of correction history is doubled this way, it breaks even with previous formula on half of maximum value. Passed STC: https://tests.stockfishchess.org/tests/view/659591e579aa8af82b95d7e8 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 225216 W: 57616 L: 57019 D: 110581 Ptnml(0-2): 747, 26677, 57201, 27198, 785 Passed LTC: https://tests.stockfishchess.org/tests/view/6596ee0b79aa8af82b95f08a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 73314 W: 18524 L: 18125 D: 36665 Ptnml(0-2): 41, 8159, 19875, 8524, 58 closes https://github.com/official-stockfish/Stockfish/pull/4967 Bench: 1464785 --- src/search.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9dc4ee988dd..e93b12d1598 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -745,7 +745,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = eval = to_static_eval(newEval); @@ -759,7 +761,9 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = eval = to_static_eval(newEval); @@ -1502,7 +1506,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs( + thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = bestValue = to_static_eval(newEval); @@ -1519,7 +1526,10 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { Value newEval = ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] / 32; + + thisThread->correctionHistory[us][pawn_structure_index(pos)] + * std::abs( + thisThread->correctionHistory[us][pawn_structure_index(pos)]) + / 16384; ss->staticEval = bestValue = to_static_eval(newEval); } From 19f9a197be95395f761304310f9792d40b05307c Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 6 Jan 2024 19:36:17 +0100 Subject: [PATCH 0337/1309] Add .git-blame-ignore-revs Add a `.git-blame-ignore-revs` file which can be used to skip specified commits when blaming, this is useful to ignore formatting commits, like clang-format #4790. Github blame automatically supports this file format, as well as other third party tools. Git itself needs to be told about the file name to work, the following command will add it to the current git repo. `git config blame.ignoreRevsFile .git-blame-ignore-revs`, alternatively one has to specify it with every blame. `git blame --ignore-revs-file .git-blame-ignore-revs search.cpp` Supported since git 2.23. closes https://github.com/official-stockfish/Stockfish/pull/4969 No functional change --- .git-blame-ignore-revs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000000..d2d6cfe67ba --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,7 @@ +# .git-blame-ignore-revs +# Ignore commit which added clang-format +2d0237db3f0e596fb06e3ffbadba84dcc4e018f6 + +# Post commit formatting fixes +0fca5605fa2e5e7240fde5e1aae50952b2612231 +08ed4c90db31959521b7ef3186c026edd1e90307 \ No newline at end of file From a5a76a63704009d35997725558dfafd90f5d616f Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 6 Jan 2024 19:29:27 -0800 Subject: [PATCH 0338/1309] Introduce BAD_QUIET movepicker stage Split quiets into good and bad as we do with captures. When we find the first quiet move below a certain threshold that has been sorted we consider all subsequent quiets bad. Inspired by @locutus2 idea to skip bad captures. Passed STC: https://tests.stockfishchess.org/tests/view/6597759f79aa8af82b95fa17 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 138688 W: 35566 L: 35096 D: 68026 Ptnml(0-2): 476, 16367, 35183, 16847, 471 Passed LTC: https://tests.stockfishchess.org/tests/view/6598583c79aa8af82b960ad0 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 84108 W: 21468 L: 21048 D: 41592 Ptnml(0-2): 38, 9355, 22858, 9755, 48 closes https://github.com/official-stockfish/Stockfish/pull/4970 Bench: 1336907 --- src/movepick.cpp | 45 +++++++++++++++++++++++++++++++++++++++------ src/movepick.h | 10 +++++----- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 14b6c87a744..6a5629961e3 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -37,8 +37,9 @@ enum Stages { GOOD_CAPTURE, REFUTATION, QUIET_INIT, - QUIET, + GOOD_QUIET, BAD_CAPTURE, + BAD_QUIET, // generate evasion moves EVASION_TT, @@ -243,6 +244,8 @@ Move MovePicker::select(Pred filter) { // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { + auto quiet_threshold = [](Depth d) { return -3330 * d; }; + top: switch (stage) { @@ -295,20 +298,34 @@ Move MovePicker::next_move(bool skipQuiets) { if (!skipQuiets) { cur = endBadCaptures; - endMoves = generate(pos, cur); + endMoves = beginBadQuiets = endBadQuiets = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -3330 * depth); + partial_insertion_sort(cur, endMoves, quiet_threshold(depth)); } ++stage; [[fallthrough]]; - case QUIET : + case GOOD_QUIET : if (!skipQuiets && select([&]() { return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) - return *(cur - 1); + { + Move tmp = *(cur - 1); + if ((cur - 1)->value < -7500 && (cur - 1)->value > quiet_threshold(depth)) + { + // Remaining quiets are bad + beginBadQuiets = cur; + + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; + + ++stage; + } + return tmp; + } // Prepare the pointers to loop over the bad captures cur = moves; @@ -318,7 +335,23 @@ Move MovePicker::next_move(bool skipQuiets) { [[fallthrough]]; case BAD_CAPTURE : - return select([]() { return true; }); + if (select([]() { return true; })) + return *(cur - 1); + + // Prepare the pointers to loop over the bad quiets + cur = beginBadQuiets; + endMoves = endBadQuiets; + + ++stage; + [[fallthrough]]; + + case BAD_QUIET : + if (!skipQuiets) + return select([&]() { + return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; + }); + + return Move::none(); case EVASION_INIT : cur = moves; diff --git a/src/movepick.h b/src/movepick.h index c429f8aec9a..357918a90f2 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -188,11 +188,11 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; - int stage; - int threshold; - Depth depth; - ExtMove moves[MAX_MOVES]; + ExtMove refutations[3], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + int stage; + int threshold; + Depth depth; + ExtMove moves[MAX_MOVES]; }; } // namespace Stockfish From 584d9efedcde330eeb96a99215552ddfb06f52ba Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 2 Dec 2023 17:50:32 -0800 Subject: [PATCH 0339/1309] Dual NNUE with L1-128 smallnet Credit goes to @mstembera for: - writing the code enabling dual NNUE: https://github.com/official-stockfish/Stockfish/pull/4898 - the idea of trying L1-128 trained exclusively on high simple eval positions The L1-128 smallnet is: - epoch 399 of a single-stage training from scratch - trained only on positions from filtered data with high material difference - defined by abs(simple_eval) > 1000 ```yaml experiment-name: 128--S1-only-hse-v2 training-dataset: - /data/hse/S3/dfrc99-16tb7p-eval-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/leela96-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/test80-apr2022-16tb7p.min.high-simple-eval-1k.binpack - /data/hse/S7/test60-2020-2tb7p.v6-3072.high-simple-eval-1k.binpack - /data/hse/S7/test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test77-nov2021-2tb7p.v6-3072.min.high-simple-eval-1k.binpack - /data/hse/S7/test77-dec2021-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test77-jan2022-2tb7p.high-simple-eval-1k.binpack - /data/hse/S7/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test79-apr2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test79-may2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack # T80 2022 - /data/hse/S7/test80-may2022-16tb7p.high-simple-eval-1k.binpack - /data/hse/S7/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-jul2022-16tb7p.v6-dd.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-aug2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-oct2022-16tb7p.v6-dd.high-simple-eval-1k.binpack - /data/hse/S7/test80-nov2022-16tb7p-v6-dd.min.high-simple-eval-1k.binpack # T80 2023 - /data/hse/S7/test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.binpack - /data/hse/S7/test80-mar2023-2tb7p.v6-sk16.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-apr2023-2tb7p-filter-v6-sk16.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-may2023-2tb7p.v6.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-jun2023-2tb7p.v6-3072.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-jul2023-2tb7p.v6-3072.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-aug2023-2tb7p.v6.min.high-simple-eval-1k.binpack - /data/hse/S7/test80-sep2023-2tb7p.high-simple-eval-1k.binpack - /data/hse/S7/test80-oct2023-2tb7p.high-simple-eval-1k.binpack start-from-engine-test-net: False nnue-pytorch-branch: linrock/nnue-pytorch/L1-128 engine-test-branch: linrock/Stockfish/L1-128-nolazy engine-base-branch: linrock/Stockfish/L1-128 num-epochs: 500 lambda: 1.0 ``` Experiment yaml configs converted to easy_train.sh commands with: https://github.com/linrock/nnue-tools/blob/4339954/yaml_easy_train.py Binpacks interleaved at training time with: https://github.com/official-stockfish/nnue-pytorch/pull/259 Data filtered for high simple eval positions with: https://github.com/linrock/nnue-data/blob/32d6a68/filter_high_simple_eval_plain.py https://github.com/linrock/Stockfish/blob/61dbfe/src/tools/transform.cpp#L626-L655 Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move of L1-128 smallnet (nnue-only eval) vs. L1-128 trained on standard S1 data: nn-epoch399.nnue : -318.1 +/- 2.1 Passed STC: https://tests.stockfishchess.org/tests/view/6574cb9d95ea6ba1fcd49e3b LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 62432 W: 15875 L: 15521 D: 31036 Ptnml(0-2): 177, 7331, 15872, 7633, 203 Passed LTC: https://tests.stockfishchess.org/tests/view/6575da2d4d789acf40aaac6e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 64830 W: 16118 L: 15738 D: 32974 Ptnml(0-2): 43, 7129, 17697, 7497, 49 closes https://github.com/official-stockfish/Stockfish/pulls Bench: 1330050 Co-Authored-By: mstembera <5421953+mstembera@users.noreply.github.com> --- src/Makefile | 32 ++++-- src/evaluate.cpp | 163 ++++++++++++++++------------ src/evaluate.h | 5 +- src/nnue/evaluate_nnue.cpp | 139 ++++++++++++++++-------- src/nnue/evaluate_nnue.h | 19 ++-- src/nnue/nnue_accumulator.h | 3 +- src/nnue/nnue_architecture.h | 38 +++++-- src/nnue/nnue_feature_transformer.h | 65 +++++------ src/position.cpp | 18 +-- src/position.h | 6 +- src/uci.cpp | 3 +- src/ucioption.cpp | 4 +- 12 files changed, 297 insertions(+), 198 deletions(-) diff --git a/src/Makefile b/src/Makefile index 660b41e7edb..e6de514e568 100644 --- a/src/Makefile +++ b/src/Makefile @@ -806,7 +806,7 @@ help: @echo "help > Display architecture details" @echo "profile-build > standard build with profile-guided optimization" @echo "build > skip profile-guided optimization" - @echo "net > Download the default nnue net" + @echo "net > Download the default nnue nets" @echo "strip > Strip executable" @echo "install > Install executable" @echo "clean > Clean up" @@ -922,16 +922,7 @@ profileclean: @rm -f stockfish.res @rm -f ./-lstdc++.res -# set up shell variables for the net stuff -netvariables: - $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) - $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) - $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) - $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) - $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) - -# evaluation network (nnue) -net: netvariables +define fetch_network @echo "Default net: $(nnuenet)" @if [ "x$(curl_or_wget)" = "x" ]; then \ echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ @@ -966,7 +957,24 @@ net: netvariables if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ echo "Network validated"; break; \ fi; \ - fi; \ + fi; +endef + +# set up shell variables for the net stuff +define netvariables +$(eval nnuenet := $(shell grep $(1) evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) +$(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) +$(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) +$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) +$(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) +endef + +# evaluation network (nnue) +net: + $(call netvariables, EvalFileDefaultNameBig) + $(call fetch_network) + $(call netvariables, EvalFileDefaultNameSmall) + $(call fetch_network) format: $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bda7132a1ff..deeb9e673d0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include "incbin/incbin.h" #include "misc.h" #include "nnue/evaluate_nnue.h" +#include "nnue/nnue_architecture.h" #include "position.h" #include "thread.h" #include "types.h" @@ -44,11 +46,15 @@ // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) -INCBIN(EmbeddedNNUE, EvalFileDefaultName); +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); #else -const unsigned char gEmbeddedNNUEData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; -const unsigned int gEmbeddedNNUESize = 1; +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; #endif @@ -56,7 +62,9 @@ namespace Stockfish { namespace Eval { -std::string currentEvalFileName = "None"; +std::string currentEvalFileName[2] = {"None", "None"}; +const std::string EvFiles[2] = {"EvalFile", "EvalFileSmall"}; +const std::string EvFileNames[2] = {EvalFileDefaultNameBig, EvalFileDefaultNameSmall}; // Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -67,84 +75,96 @@ std::string currentEvalFileName = "None"; // variable to have the engine search in a special directory in their distro. void NNUE::init() { - std::string eval_file = std::string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; + for (NetSize netSize : {Big, Small}) + { + // change after fishtest supports EvalFileSmall + std::string eval_file = + std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); + if (eval_file.empty()) + eval_file = EvFileNames[netSize]; #if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = {"", "", CommandLine::binaryDirectory, - stringify(DEFAULT_NNUE_DIRECTORY)}; + std::vector dirs = {"", "", CommandLine::binaryDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; #else - std::vector dirs = {"", "", CommandLine::binaryDirectory}; + std::vector dirs = {"", "", CommandLine::binaryDirectory}; #endif - for (const std::string& directory : dirs) - if (currentEvalFileName != eval_file) + for (const std::string& directory : dirs) { - if (directory != "") - { - std::ifstream stream(directory + eval_file, std::ios::binary); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; - } - - if (directory == "" && eval_file == EvalFileDefaultName) + if (currentEvalFileName[netSize] != eval_file) { - // C++ way to prepare a buffer for a memory stream - class MemoryBuffer: public std::basic_streambuf { - public: - MemoryBuffer(char* p, size_t n) { - setg(p, p, p + n); - setp(p, p + n); - } - }; - - MemoryBuffer buffer( - const_cast(reinterpret_cast(gEmbeddedNNUEData)), - size_t(gEmbeddedNNUESize)); - (void) gEmbeddedNNUEEnd; // Silence warning on unused variable - - std::istream stream(&buffer); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; + if (directory != "") + { + std::ifstream stream(directory + eval_file, std::ios::binary); + if (NNUE::load_eval(eval_file, stream, netSize)) + currentEvalFileName[netSize] = eval_file; + } + + if (directory == "" && eval_file == EvFileNames[netSize]) + { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; + + MemoryBuffer buffer( + const_cast(reinterpret_cast( + netSize == Small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)), + size_t(netSize == Small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize)); + (void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable + (void) gEmbeddedNNUESmallEnd; + + std::istream stream(&buffer); + if (NNUE::load_eval(eval_file, stream, netSize)) + currentEvalFileName[netSize] = eval_file; + } } } + } } // Verifies that the last net used was loaded successfully void NNUE::verify() { - std::string eval_file = std::string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; - - if (currentEvalFileName != eval_file) + for (NetSize netSize : {Big, Small}) { + // change after fishtest supports EvalFileSmall + std::string eval_file = + std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); + if (eval_file.empty()) + eval_file = EvFileNames[netSize]; - std::string msg1 = - "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, " - "including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: " - "https://tests.stockfishchess.org/api/nn/" - + std::string(EvalFileDefaultName); - std::string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - exit(EXIT_FAILURE); - } + if (currentEvalFileName[netSize] != eval_file) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + std::string(EvFileNames[netSize]); + std::string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + + exit(EXIT_FAILURE); + } - sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; + sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; + } } } - // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. @@ -163,18 +183,19 @@ Value Eval::evaluate(const Position& pos) { int v; Color stm = pos.side_to_move(); int shuffling = pos.rule50_count(); - int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3); - - bool lazy = std::abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling - + std::abs(pos.this_thread()->bestValue) - + std::abs(pos.this_thread()->rootSimpleEval); + int simpleEval = simple_eval(pos, stm); + bool lazy = std::abs(simpleEval) > 2300; if (lazy) v = simpleEval; else { - int nnueComplexity; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + bool smallNet = std::abs(simpleEval) > 1100; + + int nnueComplexity; + + Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) + : NNUE::evaluate(pos, true, &nnueComplexity); int optimism = pos.this_thread()->optimism[stm]; @@ -217,7 +238,7 @@ std::string Eval::trace(Position& pos) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); Value v; - v = NNUE::evaluate(pos, false); + v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; diff --git a/src/evaluate.h b/src/evaluate.h index 0a7ec61a3cf..3ead6b763dc 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -34,12 +34,13 @@ std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); -extern std::string currentEvalFileName; +extern std::string currentEvalFileName[2]; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultName "nn-b1e55edbea57.nnue" +#define EvalFileDefaultNameBig "nn-b1e55edbea57.nnue" +#define EvalFileDefaultNameSmall "nn-c01dc0ffeede.nnue" namespace NNUE { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 14e2fec15a4..004e28dfb41 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -40,14 +40,18 @@ namespace Stockfish::Eval::NNUE { // Input feature converter -LargePagePtr featureTransformer; +LargePagePtr> + featureTransformerBig; +LargePagePtr> + featureTransformerSmall; // Evaluation function -AlignedPtr network[LayerStacks]; +AlignedPtr> networkBig[LayerStacks]; +AlignedPtr> networkSmall[LayerStacks]; -// Evaluation function file name -std::string fileName; -std::string netDescription; +// Evaluation function file names +std::string fileName[2]; +std::string netDescription[2]; namespace Detail { @@ -91,11 +95,20 @@ bool write_parameters(std::ostream& stream, const T& reference) { // Initialize the evaluation function parameters -static void initialize() { +static void initialize(NetSize netSize) { - Detail::initialize(featureTransformer); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(network[i]); + if (netSize == Small) + { + Detail::initialize(featureTransformerSmall); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(networkSmall[i]); + } + else + { + Detail::initialize(featureTransformerBig); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(networkBig[i]); + } } // Read network header @@ -122,39 +135,57 @@ static bool write_header(std::ostream& stream, std::uint32_t hashValue, const st } // Read network parameters -static bool read_parameters(std::istream& stream) { +static bool read_parameters(std::istream& stream, NetSize netSize) { std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) + if (!read_header(stream, &hashValue, &netDescription[netSize])) + return false; + if (hashValue != HashValue[netSize]) return false; - if (hashValue != HashValue) + if (netSize == Big && !Detail::read_parameters(stream, *featureTransformerBig)) return false; - if (!Detail::read_parameters(stream, *featureTransformer)) + if (netSize == Small && !Detail::read_parameters(stream, *featureTransformerSmall)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::read_parameters(stream, *(network[i]))) + { + if (netSize == Big && !Detail::read_parameters(stream, *(networkBig[i]))) return false; + if (netSize == Small && !Detail::read_parameters(stream, *(networkSmall[i]))) + return false; + } return stream && stream.peek() == std::ios::traits_type::eof(); } // Write network parameters -static bool write_parameters(std::ostream& stream) { +static bool write_parameters(std::ostream& stream, NetSize netSize) { - if (!write_header(stream, HashValue, netDescription)) + if (!write_header(stream, HashValue[netSize], netDescription[netSize])) return false; - if (!Detail::write_parameters(stream, *featureTransformer)) + if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) + return false; + if (netSize == Small && !Detail::write_parameters(stream, *featureTransformerSmall)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::write_parameters(stream, *(network[i]))) + { + if (netSize == Big && !Detail::write_parameters(stream, *(networkBig[i]))) + return false; + if (netSize == Small && !Detail::write_parameters(stream, *(networkSmall[i]))) return false; + } return bool(stream); } void hint_common_parent_position(const Position& pos) { - featureTransformer->hint_common_access(pos); + + int simpleEval = simple_eval(pos, pos.side_to_move()); + if (abs(simpleEval) > 1100) + featureTransformerSmall->hint_common_access(pos); + else + featureTransformerBig->hint_common_access(pos); } // Evaluation function. Perform differential calculation. +template Value evaluate(const Position& pos, bool adjusted, int* complexity) { // We manually align the arrays on the stack because with gcc < 9.3 @@ -165,19 +196,28 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + transformedFeaturesUnaligned[FeatureTransformer < Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr + > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr > ::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + const auto psqt = Net_Size == Small + ? featureTransformerSmall->transform(pos, transformedFeatures, bucket) + : featureTransformerBig->transform(pos, transformedFeatures, bucket); + const auto positional = Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) + : networkBig[bucket]->propagate(transformedFeatures); if (complexity) *complexity = std::abs(psqt - positional) / OutputScale; @@ -190,6 +230,9 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { return static_cast((psqt + positional) / OutputScale); } +template Value evaluate(const Position& pos, bool adjusted, int* complexity); +template Value evaluate(const Position& pos, bool adjusted, int* complexity); + struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); @@ -205,13 +248,14 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); @@ -220,8 +264,8 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { t.correctBucket = (pos.count() - 1) / 4; for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { - const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + const auto materialist = featureTransformerBig->transform(pos, transformedFeatures, bucket); + const auto positional = networkBig[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); t.positional[bucket] = static_cast(positional / OutputScale); @@ -310,7 +354,7 @@ std::string trace(Position& pos) { // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - Value base = evaluate(pos); + Value base = evaluate(pos); base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) @@ -325,16 +369,16 @@ std::string trace(Position& pos) { auto st = pos.state(); pos.remove_piece(sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = false; + st->accumulatorBig.computed[BLACK] = false; - Value eval = evaluate(pos); + Value eval = evaluate(pos); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = false; + st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); @@ -379,24 +423,24 @@ std::string trace(Position& pos) { // Load eval, from a file stream or a memory stream -bool load_eval(std::string name, std::istream& stream) { +bool load_eval(const std::string name, std::istream& stream, NetSize netSize) { - initialize(); - fileName = name; - return read_parameters(stream); + initialize(netSize); + fileName[netSize] = name; + return read_parameters(stream, netSize); } // Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream) { +bool save_eval(std::ostream& stream, NetSize netSize) { - if (fileName.empty()) + if (fileName[netSize].empty()) return false; - return write_parameters(stream); + return write_parameters(stream, netSize); } // Save eval, to a file given by its name -bool save_eval(const std::optional& filename) { +bool save_eval(const std::optional& filename, NetSize netSize) { std::string actualFilename; std::string msg; @@ -405,7 +449,8 @@ bool save_eval(const std::optional& filename) { actualFilename = filename.value(); else { - if (currentEvalFileName != EvalFileDefaultName) + if (currentEvalFileName[netSize] + != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) { msg = "Failed to export a net. " "A non-embedded net can only be saved if the filename is specified"; @@ -413,11 +458,11 @@ bool save_eval(const std::optional& filename) { sync_cout << msg << sync_endl; return false; } - actualFilename = EvalFileDefaultName; + actualFilename = (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig); } std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream); + bool saved = save_eval(stream, netSize); msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index f80aa398bba..fabfb5693f9 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -39,9 +39,11 @@ class Position; namespace Stockfish::Eval::NNUE { // Hash value of evaluation function structure -constexpr std::uint32_t HashValue = - FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); - +constexpr std::uint32_t HashValue[2] = { + FeatureTransformer::get_hash_value() + ^ Network::get_hash_value(), + FeatureTransformer::get_hash_value() + ^ Network::get_hash_value()}; // Deleter for automating release of memory area template @@ -67,12 +69,13 @@ template using LargePagePtr = std::unique_ptr>; std::string trace(Position& pos); -Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); -void hint_common_parent_position(const Position& pos); +template +Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); +void hint_common_parent_position(const Position& pos); -bool load_eval(std::string name, std::istream& stream); -bool save_eval(std::ostream& stream); -bool save_eval(const std::optional& filename); +bool load_eval(const std::string name, std::istream& stream, NetSize netSize); +bool save_eval(std::ostream& stream, NetSize netSize); +bool save_eval(const std::optional& filename, NetSize netSize); } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index f6d705243ef..0b05d00da28 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -29,8 +29,9 @@ namespace Stockfish::Eval::NNUE { // Class that holds the result of affine transformation of input features +template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[2][TransformedFeatureDimensions]; + std::int16_t accumulation[2][Size]; std::int32_t psqtAccumulation[2][PSQTBuckets]; bool computed[2]; }; diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 92445704296..949f2d8687f 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -37,14 +37,28 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; +enum NetSize { + Big, + Small +}; + // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensions = 2560; -constexpr IndexType PSQTBuckets = 8; -constexpr IndexType LayerStacks = 8; +constexpr IndexType TransformedFeatureDimensionsBig = 2560; +constexpr int L2Big = 15; +constexpr int L3Big = 32; + +constexpr IndexType TransformedFeatureDimensionsSmall = 128; +constexpr int L2Small = 15; +constexpr int L3Small = 32; + +constexpr IndexType PSQTBuckets = 8; +constexpr IndexType LayerStacks = 8; +template struct Network { - static constexpr int FC_0_OUTPUTS = 15; - static constexpr int FC_1_OUTPUTS = 32; + static constexpr IndexType TransformedFeatureDimensions = L1; + static constexpr int FC_0_OUTPUTS = L2; + static constexpr int FC_1_OUTPUTS = L3; Layers::AffineTransformSparseInput fc_0; Layers::SqrClippedReLU ac_sqr_0; @@ -84,13 +98,13 @@ struct Network { std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { struct alignas(CacheLineSize) Buffer { - alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out; - alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType + alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; - alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out; - alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out; - alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out; - alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out; + alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out; + alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out; + alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out; + alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out; Buffer() { std::memset(this, 0, sizeof(*this)); } }; @@ -108,7 +122,7 @@ struct Network { ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, - FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType)); + FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType)); fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 2008cf25f1d..9a162ac9853 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -186,11 +186,6 @@ static constexpr int BestRegisterCount() { return 1; } - -static constexpr int NumRegs = - BestRegisterCount(); -static constexpr int NumPsqtRegs = - BestRegisterCount(); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif @@ -198,6 +193,8 @@ static constexpr int NumPsqtRegs = // Input feature converter +template StateInfo::*accPtr> class FeatureTransformer { private: @@ -205,6 +202,11 @@ class FeatureTransformer { static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; #ifdef VECTOR + static constexpr int NumRegs = + BestRegisterCount(); + static constexpr int NumPsqtRegs = + BestRegisterCount(); + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); @@ -253,8 +255,8 @@ class FeatureTransformer { update_accumulator(pos); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& accumulation = pos.state()->accumulator.accumulation; - const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; + const auto& accumulation = (pos.state()->*accPtr).accumulation; + const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) @@ -323,7 +325,7 @@ class FeatureTransformer { // of the estimated gain in terms of features to be added/subtracted. StateInfo *st = pos.state(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !st->accumulator.computed[Perspective]) + 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. @@ -381,7 +383,7 @@ class FeatureTransformer { for (; i >= 0; --i) { - states_to_update[i]->accumulator.computed[Perspective] = true; + (states_to_update[i]->*accPtr).computed[Perspective] = true; const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; @@ -402,9 +404,9 @@ class FeatureTransformer { assert(states_to_update[0]); auto accIn = - reinterpret_cast(&st->accumulator.accumulation[Perspective][0]); + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); auto accOut = reinterpret_cast( - &states_to_update[0]->accumulator.accumulation[Perspective][0]); + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); const IndexType offsetR0 = HalfDimensions * removed[0][0]; auto columnR0 = reinterpret_cast(&weights[offsetR0]); @@ -428,10 +430,10 @@ class FeatureTransformer { vec_add_16(columnR0[k], columnR1[k])); } - auto accPsqtIn = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][0]); + auto accPsqtIn = + reinterpret_cast(&(st->*accPtr).psqtAccumulation[Perspective][0]); auto accPsqtOut = reinterpret_cast( - &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]); + &(states_to_update[0]->*accPtr).psqtAccumulation[Perspective][0]); const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); @@ -463,7 +465,7 @@ class FeatureTransformer { { // Load accumulator auto accTileIn = reinterpret_cast( - &st->accumulator.accumulation[Perspective][j * TileHeight]); + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_load(&accTileIn[k]); @@ -489,7 +491,7 @@ class FeatureTransformer { // Store accumulator auto accTileOut = reinterpret_cast( - &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]); + &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) vec_store(&accTileOut[k], acc[k]); } @@ -499,7 +501,7 @@ class FeatureTransformer { { // Load accumulator auto accTilePsqtIn = reinterpret_cast( - &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + &(st->*accPtr).psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); @@ -525,8 +527,8 @@ class FeatureTransformer { // Store accumulator auto accTilePsqtOut = reinterpret_cast( - &states_to_update[i] - ->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + &(states_to_update[i]->*accPtr) + .psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqtOut[k], psqt[k]); } @@ -535,13 +537,12 @@ class FeatureTransformer { #else for (IndexType i = 0; states_to_update[i]; ++i) { - std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective], - st->accumulator.accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) - states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = - st->accumulator.psqtAccumulation[Perspective][k]; + (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = + (st->*accPtr).psqtAccumulation[Perspective][k]; st = states_to_update[i]; @@ -551,10 +552,10 @@ class FeatureTransformer { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] -= weights[offset + j]; + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] -= + (st->*accPtr).psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; } @@ -564,10 +565,10 @@ class FeatureTransformer { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - st->accumulator.accumulation[Perspective][j] += weights[offset + j]; + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - st->accumulator.psqtAccumulation[Perspective][k] += + (st->*accPtr).psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } } @@ -586,7 +587,7 @@ class FeatureTransformer { // Refresh the accumulator // Could be extracted to a separate function because it's done in 2 places, // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->accumulator; + auto& accumulator = pos.state()->*accPtr; accumulator.computed[Perspective] = true; FeatureSet::IndexList active; FeatureSet::append_active_indices(pos, active); @@ -663,12 +664,12 @@ class FeatureTransformer { // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. // Fast early exit. - if (pos.state()->accumulator.computed[Perspective]) + if ((pos.state()->*accPtr).computed[Perspective]) return; auto [oldest_st, _] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective]) { // Only update current position accumulator to minimize work. StateInfo* states_to_update[2] = {pos.state(), nullptr}; @@ -685,7 +686,7 @@ class FeatureTransformer { auto [oldest_st, next] = try_find_computed_accumulator(pos); - if (oldest_st->accumulator.computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective]) { if (next == nullptr) return; diff --git a/src/position.cpp b/src/position.cpp index 4fba3c234b7..ddc31888422 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -684,10 +684,10 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - auto& dp = st->dirtyPiece; - dp.dirty_num = 1; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; Color us = sideToMove; Color them = ~us; @@ -964,15 +964,15 @@ void Position::do_null_move(StateInfo& newSt) { assert(!checkers()); assert(&newSt != st); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + std::memcpy(&newSt, st, offsetof(StateInfo, accumulatorBig)); newSt.previous = st; st = &newSt; - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; + 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) { diff --git a/src/position.h b/src/position.h index 7e0c3eefd77..34b53f4a558 100644 --- a/src/position.h +++ b/src/position.h @@ -27,6 +27,7 @@ #include "bitboard.h" #include "nnue/nnue_accumulator.h" +#include "nnue/nnue_architecture.h" #include "types.h" namespace Stockfish { @@ -57,8 +58,9 @@ struct StateInfo { int repetition; // Used by NNUE - Eval::NNUE::Accumulator accumulator; - DirtyPiece dirtyPiece; + Eval::NNUE::Accumulator accumulatorBig; + Eval::NNUE::Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; }; diff --git a/src/uci.cpp b/src/uci.cpp index 8e93eee6dc5..be902277984 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -37,6 +37,7 @@ #include "misc.h" #include "movegen.h" #include "nnue/evaluate_nnue.h" +#include "nnue/nnue_architecture.h" #include "position.h" #include "search.h" #include "thread.h" @@ -320,7 +321,7 @@ void UCI::loop(int argc, char* argv[]) { std::string f; if (is >> std::skipws >> f) filename = f; - Eval::NNUE::save_eval(filename); + Eval::NNUE::save_eval(filename, Eval::NNUE::Big); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 087882f11b8..f8cbcc53077 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -82,7 +82,9 @@ void init(OptionsMap& o) { o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); + o["EvalFile"] << Option(EvalFileDefaultNameBig, on_eval_file); + // Enable this after fishtest workers support EvalFileSmall + // o["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, on_eval_file); } From f09adaa4a4c3cbb44e1ca8cc687a08dc3d58076e Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 13 Dec 2023 13:07:36 -0500 Subject: [PATCH 0340/1309] Update smallnet to nn-baff1ede1f90.nnue with wider eval range Created by training an L1-128 net from scratch with a wider range of evals in the training data and wld-fen-skipping disabled during training. The differences in this training data compared to the first dual nnue PR are: - removal of all positions with 3 pieces - when piece count >= 16, keep positions with simple eval above 750 - when piece count < 16, remove positions with simple eval above 3000 The asymmetric data filtering was meant to flatten the training data piece count distribution, which was previously heavily skewed towards positions with low piece counts. Additionally, the simple eval range where the smallnet is used was widened to cover more positions previously evaluated by the big net and simple eval. ```yaml experiment-name: 128--S1-hse-S7-v4-S3-v1-no-wld-skip training-dataset: - /data/hse/S3/leela96-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/dfrc99-16tb7p-eval-filt-v2.min.high-simple-eval-1k.binpack - /data/hse/S3/test80-apr2022-16tb7p.min.high-simple-eval-1k.binpack - /data/hse/S7/test60-2020-2tb7p.v6-3072.high-simple-eval-v4.binpack - /data/hse/S7/test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test77-nov2021-2tb7p.v6-3072.min.high-simple-eval-v4.binpack - /data/hse/S7/test77-dec2021-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test77-jan2022-2tb7p.high-simple-eval-v4.binpack - /data/hse/S7/test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test79-apr2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test79-may2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-may2022-16tb7p.high-simple-eval-v4.binpack - /data/hse/S7/test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-jul2022-16tb7p.v6-dd.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-aug2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-oct2022-16tb7p.v6-dd.high-simple-eval-v4.binpack - /data/hse/S7/test80-nov2022-16tb7p-v6-dd.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-jan2023-3of3-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-feb2023-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-v4.binpack - /data/hse/S7/test80-mar2023-2tb7p.v6-sk16.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-apr2023-2tb7p-filter-v6-sk16.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-may2023-2tb7p.v6.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-jun2023-2tb7p.v6-3072.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-jul2023-2tb7p.v6-3072.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-aug2023-2tb7p.v6.min.high-simple-eval-v4.binpack - /data/hse/S7/test80-sep2023-2tb7p.high-simple-eval-v4.binpack - /data/hse/S7/test80-oct2023-2tb7p.high-simple-eval-v4.binpack wld-fen-skipping: False start-from-engine-test-net: False nnue-pytorch-branch: linrock/nnue-pytorch/L1-128 engine-test-branch: linrock/Stockfish/L1-128-nolazy engine-base-branch: linrock/Stockfish/L1-128 num-epochs: 500 start-lambda: 1.0 end-lambda: 1.0 ``` Experiment yaml configs converted to easy_train.sh commands with: https://github.com/linrock/nnue-tools/blob/4339954/yaml_easy_train.py Binpacks interleaved at training time with: https://github.com/official-stockfish/nnue-pytorch/pull/259 FT weights permuted with 10k positions from fishpack32.binpack with: https://github.com/official-stockfish/nnue-pytorch/pull/254 Data filtered for high simple eval positions (v4) with: https://github.com/linrock/Stockfish/blob/b9c8440/src/tools/transform.cpp#L640-L675 Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move of L1-128 smallnet (nnue-only eval) vs. L1-128 trained on standard S1 data: nn-epoch319.nnue : -241.7 +/- 3.2 Passed STC vs. 36db936: https://tests.stockfishchess.org/tests/view/6576b3484d789acf40aabbfe LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 21920 W: 5680 L: 5381 D: 10859 Ptnml(0-2): 82, 2488, 5520, 2789, 81 Passed LTC vs. DualNNUE #4915: https://tests.stockfishchess.org/tests/view/65775c034d789acf40aac7e3 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 147606 W: 36619 L: 36063 D: 74924 Ptnml(0-2): 98, 16591, 39891, 17103, 120 closes https://github.com/official-stockfish/Stockfish/pull/4919 Bench: 1438336 --- src/evaluate.cpp | 4 ++-- src/evaluate.h | 2 +- src/nnue/evaluate_nnue.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index deeb9e673d0..e3f60f9cd72 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -185,12 +185,12 @@ Value Eval::evaluate(const Position& pos) { int shuffling = pos.rule50_count(); int simpleEval = simple_eval(pos, stm); - bool lazy = std::abs(simpleEval) > 2300; + bool lazy = std::abs(simpleEval) > 2550; if (lazy) v = simpleEval; else { - bool smallNet = std::abs(simpleEval) > 1100; + bool smallNet = std::abs(simpleEval) > 1050; int nnueComplexity; diff --git a/src/evaluate.h b/src/evaluate.h index 3ead6b763dc..ce608735b51 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -40,7 +40,7 @@ extern std::string currentEvalFileName[2]; // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. #define EvalFileDefaultNameBig "nn-b1e55edbea57.nnue" -#define EvalFileDefaultNameSmall "nn-c01dc0ffeede.nnue" +#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 004e28dfb41..7566d84981d 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -178,7 +178,7 @@ static bool write_parameters(std::ostream& stream, NetSize netSize) { void hint_common_parent_position(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - if (abs(simpleEval) > 1100) + if (abs(simpleEval) > 1050) featureTransformerSmall->hint_common_access(pos); else featureTransformerBig->hint_common_access(pos); From 7c5e3f28655607288a980645e6b2ce600a627b11 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 7 Jan 2024 21:41:52 +0100 Subject: [PATCH 0341/1309] Prefix abs with std:: --- src/nnue/evaluate_nnue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 7566d84981d..7a3f68774c4 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -178,7 +178,7 @@ static bool write_parameters(std::ostream& stream, NetSize netSize) { void hint_common_parent_position(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - if (abs(simpleEval) > 1050) + if (std::abs(simpleEval) > 1050) featureTransformerSmall->hint_common_access(pos); else featureTransformerBig->hint_common_access(pos); From 99cdb920fcee4cb09cbe273eef9deb85b5a1af2c Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 7 Jan 2024 23:08:33 +0100 Subject: [PATCH 0342/1309] Cleanup Evalfile handling This cleans up the EvalFile handling after the merge of #4915, which has become a bit confusing on what it is actually doing. closes https://github.com/official-stockfish/Stockfish/pull/4971 No functional change --- src/evaluate.cpp | 63 ++++++++++++++++++++---------------- src/evaluate.h | 13 ++++++-- src/nnue/evaluate_nnue.cpp | 3 +- src/nnue/nnue_architecture.h | 2 +- 4 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e3f60f9cd72..e220b92a7fc 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -23,10 +23,10 @@ #include #include #include -#include #include #include #include +#include #include #include "incbin/incbin.h" @@ -62,9 +62,10 @@ namespace Stockfish { namespace Eval { -std::string currentEvalFileName[2] = {"None", "None"}; -const std::string EvFiles[2] = {"EvalFile", "EvalFileSmall"}; -const std::string EvFileNames[2] = {EvalFileDefaultNameBig, EvalFileDefaultNameSmall}; +std::unordered_map EvalFiles = { + {NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None"}}, + {NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None"}}}; + // Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -75,13 +76,16 @@ const std::string EvFileNames[2] = {EvalFileDefaultNameBig, EvalFileDefa // variable to have the engine search in a special directory in their distro. void NNUE::init() { - for (NetSize netSize : {Big, Small}) + for (auto& [netSize, evalFile] : EvalFiles) { - // change after fishtest supports EvalFileSmall - std::string eval_file = - std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); - if (eval_file.empty()) - eval_file = EvFileNames[netSize]; + // Replace with + // Options[evalFile.option_name] + // once fishtest supports the uci option EvalFileSmall + std::string user_eval_file = + netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + + if (user_eval_file.empty()) + user_eval_file = evalFile.default_name; #if defined(DEFAULT_NNUE_DIRECTORY) std::vector dirs = {"", "", CommandLine::binaryDirectory, @@ -92,16 +96,16 @@ void NNUE::init() { for (const std::string& directory : dirs) { - if (currentEvalFileName[netSize] != eval_file) + if (evalFile.selected_name != user_eval_file) { if (directory != "") { - std::ifstream stream(directory + eval_file, std::ios::binary); - if (NNUE::load_eval(eval_file, stream, netSize)) - currentEvalFileName[netSize] = eval_file; + std::ifstream stream(directory + user_eval_file, std::ios::binary); + if (NNUE::load_eval(user_eval_file, stream, netSize)) + evalFile.selected_name = user_eval_file; } - if (directory == "" && eval_file == EvFileNames[netSize]) + if (directory == "" && user_eval_file == evalFile.default_name) { // C++ way to prepare a buffer for a memory stream class MemoryBuffer: public std::basic_streambuf { @@ -120,8 +124,8 @@ void NNUE::init() { (void) gEmbeddedNNUESmallEnd; std::istream stream(&buffer); - if (NNUE::load_eval(eval_file, stream, netSize)) - currentEvalFileName[netSize] = eval_file; + if (NNUE::load_eval(user_eval_file, stream, netSize)) + evalFile.selected_name = user_eval_file; } } } @@ -131,24 +135,27 @@ void NNUE::init() { // Verifies that the last net used was loaded successfully void NNUE::verify() { - for (NetSize netSize : {Big, Small}) + for (const auto& [netSize, evalFile] : EvalFiles) { - // change after fishtest supports EvalFileSmall - std::string eval_file = - std::string(netSize == Small ? EvalFileDefaultNameSmall : Options[EvFiles[netSize]]); - if (eval_file.empty()) - eval_file = EvFileNames[netSize]; - - if (currentEvalFileName[netSize] != eval_file) + // Replace with + // Options[evalFile.option_name] + // once fishtest supports the uci option EvalFileSmall + std::string user_eval_file = + netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + if (user_eval_file.empty()) + user_eval_file = evalFile.default_name; + + if (evalFile.selected_name != user_eval_file) { std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The network file " + eval_file + " was not loaded successfully."; + std::string msg2 = + "The network file " + user_eval_file + " was not loaded successfully."; std::string msg3 = "The UCI option EvalFile might need to specify the full path, " "including the directory name, to the network file."; std::string msg4 = "The default net can be downloaded from: " "https://tests.stockfishchess.org/api/nn/" - + std::string(EvFileNames[netSize]); + + evalFile.default_name; std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; @@ -160,7 +167,7 @@ void NNUE::verify() { exit(EXIT_FAILURE); } - sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; + sync_cout << "info string NNUE evaluation using " << user_eval_file << sync_endl; } } } diff --git a/src/evaluate.h b/src/evaluate.h index ce608735b51..f712d8e6a0f 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -20,6 +20,7 @@ #define EVALUATE_H_INCLUDED #include +#include #include "types.h" @@ -34,8 +35,6 @@ std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); Value evaluate(const Position& pos); -extern std::string currentEvalFileName[2]; - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. @@ -44,11 +43,21 @@ extern std::string currentEvalFileName[2]; namespace NNUE { +enum NetSize : int; + void init(); void verify(); } // namespace NNUE +struct EvalFile { + std::string option_name; + std::string default_name; + std::string selected_name; +}; + +extern std::unordered_map EvalFiles; + } // namespace Eval } // namespace Stockfish diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 7a3f68774c4..86fe523050a 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "../evaluate.h" #include "../misc.h" @@ -449,7 +450,7 @@ bool save_eval(const std::optional& filename, NetSize netSize) { actualFilename = filename.value(); else { - if (currentEvalFileName[netSize] + if (EvalFiles.at(netSize).selected_name != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) { msg = "Failed to export a net. " diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 949f2d8687f..b222ab997db 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -37,7 +37,7 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; -enum NetSize { +enum NetSize : int { Big, Small }; From 6deb88728fb141e853243c2873ad0cda4dd19320 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 3 Jan 2024 00:58:16 -0500 Subject: [PATCH 0343/1309] Update default main net to nn-baff1edbea57.nnue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created by retraining the previous main net nn-b1e55edbea57.nnue with: - some of the same options as before: ranger21 optimizer, more WDL skipping - adding T80 aug filter-v6, sep, and oct 2023 data to the previous best dataset - increasing training loss for positions where predicted win rates were higher than estimated match results from training data position scores ```yaml experiment-name: 2560--S8-r21-more-wdl-skip-10p-more-loss-high-q-sk28 training-dataset: # https://github.com/official-stockfish/Stockfish/pull/4782 - /data/S6-1ee1aba5ed.binpack - /data/test80-aug2023-2tb7p.v6.min.binpack - /data/test80-sep2023-2tb7p.binpack - /data/test80-oct2023-2tb7p.binpack early-fen-skipping: 28 start-from-engine-test-net: True nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip-10p-more-loss-high-q num-epochs: 1000 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` Training data can be found at: https://robotmoon.com/nnue-training-data/ Training loss was increased by 10% for positions where predicted win rates were higher than suggested by the win rate model based on the training data, by multiplying with: ((qf > pt) * 0.1 + 1). This was a variant of experiments from Sopel's NNUE training & experimentation log: https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY Experiment 302 - increase loss when prediction too high, vondele’s idea Experiment 309 - increase loss when prediction too high, normalize in a batch Passed STC: https://tests.stockfishchess.org/tests/view/6597a21c79aa8af82b95fd5c LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 148320 W: 37960 L: 37475 D: 72885 Ptnml(0-2): 542, 17565, 37383, 18206, 464 Passed LTC: https://tests.stockfishchess.org/tests/view/659834a679aa8af82b960845 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 55188 W: 13955 L: 13592 D: 27641 Ptnml(0-2): 34, 6162, 14834, 6535, 29 closes https://github.com/official-stockfish/Stockfish/pull/4972 Bench: 1219824 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index f712d8e6a0f..79b77192a24 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -38,7 +38,7 @@ Value evaluate(const Position& pos); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultNameBig "nn-b1e55edbea57.nnue" +#define EvalFileDefaultNameBig "nn-baff1edbea57.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { From a10791095150bf7c020b92be0f55566fe34e9bf2 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 8 Jan 2024 19:48:46 +0100 Subject: [PATCH 0344/1309] Refactor global variables This aims to remove some of the annoying global structure which Stockfish has. Overall there is no major elo regression to be expected. Non regression SMP STC (paused, early version): https://tests.stockfishchess.org/tests/view/65983d7979aa8af82b9608f1 LLR: 0.23 (-2.94,2.94) <-1.75,0.25> Total: 76232 W: 19035 L: 19096 D: 38101 Ptnml(0-2): 92, 8735, 20515, 8690, 84 Non regression STC (early version): https://tests.stockfishchess.org/tests/view/6595b3a479aa8af82b95da7f LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 185344 W: 47027 L: 46972 D: 91345 Ptnml(0-2): 571, 21285, 48943, 21264, 609 Non regression SMP STC: https://tests.stockfishchess.org/tests/view/65a0715c79aa8af82b96b7e4 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142936 W: 35761 L: 35662 D: 71513 Ptnml(0-2): 209, 16400, 38135, 16531, 193 These global structures/variables add hidden dependencies and allow data to be mutable from where it shouldn't it be (i.e. options). They also prevent Stockfish from internal selfplay, which would be a nice thing to be able to do, i.e. instantiate two Stockfish instances and let them play against each other. It will also allow us to make Stockfish a library, which can be easier used on other platforms. For consistency with the old search code, `thisThread` has been kept, even though it is not strictly necessary anymore. This the first major refactor of this kind (in recent time), and future changes are required, to achieve the previously described goals. This includes cleaning up the dependencies, transforming the network to be self contained and coming up with a plan to deal with proper tablebase memory management (see comments for more information on this). The removal of these global structures has been discussed in parts with Vondele and Sopel. closes https://github.com/official-stockfish/Stockfish/pull/4968 No functional change --- src/Makefile | 2 +- src/evaluate.cpp | 79 +++--- src/evaluate.h | 34 ++- src/main.cpp | 19 +- src/misc.cpp | 15 +- src/misc.h | 15 +- src/nnue/evaluate_nnue.cpp | 38 +-- src/nnue/evaluate_nnue.h | 19 +- src/position.cpp | 18 +- src/position.h | 30 +-- src/search.cpp | 502 +++++++++++++++++-------------------- src/search.h | 155 +++++++++++- src/syzygy/tbprobe.cpp | 8 +- src/syzygy/tbprobe.h | 8 +- src/thread.cpp | 134 +++++----- src/thread.h | 115 ++++----- src/thread_win32_osx.h | 26 +- src/timeman.cpp | 33 ++- src/timeman.h | 28 ++- src/tt.cpp | 31 +-- src/tt.h | 14 +- src/tune.cpp | 19 +- src/tune.h | 9 +- src/uci.cpp | 485 ++++++++++++++++++----------------- src/uci.h | 101 ++++---- src/ucioption.cpp | 133 ++++------ src/ucioption.h | 81 ++++++ 27 files changed, 1175 insertions(+), 976 deletions(-) create mode 100644 src/ucioption.h diff --git a/src/Makefile b/src/Makefile index e6de514e568..9680ca7feff 100644 --- a/src/Makefile +++ b/src/Makefile @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h + tt.h tune.h types.h uci.h ucioption.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e220b92a7fc..3e067e4c447 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -34,9 +35,10 @@ #include "nnue/evaluate_nnue.h" #include "nnue/nnue_architecture.h" #include "position.h" -#include "thread.h" +#include "search.h" #include "types.h" #include "uci.h" +#include "ucioption.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). @@ -62,10 +64,6 @@ namespace Stockfish { namespace Eval { -std::unordered_map EvalFiles = { - {NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None"}}, - {NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None"}}}; - // Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" @@ -74,38 +72,45 @@ std::unordered_map EvalFiles = { // network may be embedded in the binary), in the active working directory and // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY // variable to have the engine search in a special directory in their distro. -void NNUE::init() { +NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, + const OptionsMap& options, + NNUE::EvalFiles evalFiles) { - for (auto& [netSize, evalFile] : EvalFiles) + for (auto& [netSize, evalFile] : evalFiles) { // Replace with - // Options[evalFile.option_name] + // options[evalFile.optionName] // once fishtest supports the uci option EvalFileSmall std::string user_eval_file = - netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; if (user_eval_file.empty()) - user_eval_file = evalFile.default_name; + user_eval_file = evalFile.defaultName; #if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = {"", "", CommandLine::binaryDirectory, + std::vector dirs = {"", "", rootDirectory, stringify(DEFAULT_NNUE_DIRECTORY)}; #else - std::vector dirs = {"", "", CommandLine::binaryDirectory}; + std::vector dirs = {"", "", rootDirectory}; #endif for (const std::string& directory : dirs) { - if (evalFile.selected_name != user_eval_file) + if (evalFile.current != user_eval_file) { if (directory != "") { std::ifstream stream(directory + user_eval_file, std::ios::binary); - if (NNUE::load_eval(user_eval_file, stream, netSize)) - evalFile.selected_name = user_eval_file; + auto description = NNUE::load_eval(stream, netSize); + + if (description.has_value()) + { + evalFile.current = user_eval_file; + evalFile.netDescription = description.value(); + } } - if (directory == "" && user_eval_file == evalFile.default_name) + if (directory == "" && user_eval_file == evalFile.defaultName) { // C++ way to prepare a buffer for a memory stream class MemoryBuffer: public std::basic_streambuf { @@ -124,28 +129,36 @@ void NNUE::init() { (void) gEmbeddedNNUESmallEnd; std::istream stream(&buffer); - if (NNUE::load_eval(user_eval_file, stream, netSize)) - evalFile.selected_name = user_eval_file; + auto description = NNUE::load_eval(stream, netSize); + + if (description.has_value()) + { + evalFile.current = user_eval_file; + evalFile.netDescription = description.value(); + } } } } } + + return evalFiles; } // Verifies that the last net used was loaded successfully -void NNUE::verify() { +void NNUE::verify(const OptionsMap& options, + const std::unordered_map& evalFiles) { - for (const auto& [netSize, evalFile] : EvalFiles) + for (const auto& [netSize, evalFile] : evalFiles) { // Replace with - // Options[evalFile.option_name] + // options[evalFile.optionName] // once fishtest supports the uci option EvalFileSmall std::string user_eval_file = - netSize == Small ? evalFile.default_name : Options[evalFile.option_name]; + netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; if (user_eval_file.empty()) - user_eval_file = evalFile.default_name; + user_eval_file = evalFile.defaultName; - if (evalFile.selected_name != user_eval_file) + if (evalFile.current != user_eval_file) { std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; @@ -155,7 +168,7 @@ void NNUE::verify() { "including the directory name, to the network file."; std::string msg4 = "The default net can be downloaded from: " "https://tests.stockfishchess.org/api/nn/" - + evalFile.default_name; + + evalFile.defaultName; std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; @@ -183,7 +196,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos) { +Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { assert(!pos.checkers()); @@ -204,7 +217,7 @@ Value Eval::evaluate(const Position& pos) { Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) : NNUE::evaluate(pos, true, &nnueComplexity); - int optimism = pos.this_thread()->optimism[stm]; + int optimism = workerThread.optimism[stm]; // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; @@ -227,16 +240,16 @@ Value Eval::evaluate(const Position& pos) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos) { +std::string Eval::trace(Position& pos, Search::Worker& workerThread) { if (pos.checkers()) return "Final evaluation: none (in check)"; // Reset any global variable used in eval - pos.this_thread()->bestValue = VALUE_ZERO; - pos.this_thread()->rootSimpleEval = VALUE_ZERO; - pos.this_thread()->optimism[WHITE] = VALUE_ZERO; - pos.this_thread()->optimism[BLACK] = VALUE_ZERO; + workerThread.iterBestValue = VALUE_ZERO; + workerThread.rootSimpleEval = VALUE_ZERO; + workerThread.optimism[WHITE] = VALUE_ZERO; + workerThread.optimism[BLACK] = VALUE_ZERO; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); @@ -249,7 +262,7 @@ std::string Eval::trace(Position& pos) { v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos); + v = evaluate(pos, workerThread); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index 79b77192a24..8a9d6fc7c87 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -27,13 +27,18 @@ namespace Stockfish { class Position; +class OptionsMap; + +namespace Search { +class Worker; +} namespace Eval { -std::string trace(Position& pos); +std::string trace(Position& pos, Search::Worker& workerThread); int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos); +Value evaluate(const Position& pos, const Search::Worker& workerThread); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the @@ -41,22 +46,27 @@ Value evaluate(const Position& pos); #define EvalFileDefaultNameBig "nn-baff1edbea57.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" +struct EvalFile { + // UCI option name + std::string optionName; + // Default net name, will use one of the macros above + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; + namespace NNUE { enum NetSize : int; -void init(); -void verify(); +using EvalFiles = std::unordered_map; -} // namespace NNUE +EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles); +void verify(const OptionsMap&, const EvalFiles&); -struct EvalFile { - std::string option_name; - std::string default_name; - std::string selected_name; -}; - -extern std::unordered_map EvalFiles; +} // namespace NNUE } // namespace Eval diff --git a/src/main.cpp b/src/main.cpp index 78b3f54d919..de07d6a8738 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,15 +16,13 @@ along with this program. If not, see . */ -#include #include +#include #include "bitboard.h" #include "evaluate.h" #include "misc.h" #include "position.h" -#include "search.h" -#include "thread.h" #include "tune.h" #include "types.h" #include "uci.h" @@ -35,17 +33,16 @@ int main(int argc, char* argv[]) { std::cout << engine_info() << std::endl; - CommandLine::init(argc, argv); - UCI::init(Options); - Tune::init(); Bitboards::init(); Position::init(); - Threads.set(size_t(Options["Threads"])); - Search::clear(); // After threads are up - Eval::NNUE::init(); - UCI::loop(argc, argv); + UCI uci(argc, argv); + + Tune::init(uci.options); + + uci.evalFiles = Eval::NNUE::load_networks(uci.workingDirectory(), uci.options, uci.evalFiles); + + uci.loop(); - Threads.set(0); return 0; } diff --git a/src/misc.cpp b/src/misc.cpp index 9350a4830df..4885a5cd35c 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -721,17 +721,13 @@ void bindThisThread(size_t idx) { #define GETCWD getcwd #endif -namespace CommandLine { - -std::string argv0; // path+name of the executable binary, as given by argv[0] -std::string binaryDirectory; // path of the executable directory -std::string workingDirectory; // path of the working directory - -void init([[maybe_unused]] int argc, char* argv[]) { +CommandLine::CommandLine(int _argc, char** _argv) : + argc(_argc), + argv(_argv) { std::string pathSeparator; // Extract the path+name of the executable binary - argv0 = argv[0]; + std::string argv0 = argv[0]; #ifdef _WIN32 pathSeparator = "\\"; @@ -766,7 +762,4 @@ void init([[maybe_unused]] int argc, char* argv[]) { binaryDirectory.replace(0, 1, workingDirectory); } - -} // namespace CommandLine - } // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index ca6cc16639f..994f551d5ff 100644 --- a/src/misc.h +++ b/src/misc.h @@ -176,12 +176,17 @@ namespace WinProcGroup { void bindThisThread(size_t idx); } -namespace CommandLine { -void init(int argc, char* argv[]); -extern std::string binaryDirectory; // path of the executable directory -extern std::string workingDirectory; // path of the working directory -} +struct CommandLine { + public: + CommandLine(int, char**); + + int argc; + char** argv; + + std::string binaryDirectory; // path of the executable directory + std::string workingDirectory; // path of the working directory +}; } // namespace Stockfish diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 86fe523050a..d4a4dbe4c45 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -26,8 +26,10 @@ #include #include #include +#include #include #include +#include #include #include "../evaluate.h" @@ -51,8 +53,6 @@ AlignedPtr> network AlignedPtr> networkSmall[LayerStacks]; // Evaluation function file names -std::string fileName[2]; -std::string netDescription[2]; namespace Detail { @@ -136,10 +136,10 @@ static bool write_header(std::ostream& stream, std::uint32_t hashValue, const st } // Read network parameters -static bool read_parameters(std::istream& stream, NetSize netSize) { +static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) { std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription[netSize])) + if (!read_header(stream, &hashValue, &netDescription)) return false; if (hashValue != HashValue[netSize]) return false; @@ -158,9 +158,10 @@ static bool read_parameters(std::istream& stream, NetSize netSize) { } // Write network parameters -static bool write_parameters(std::ostream& stream, NetSize netSize) { +static bool +write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) { - if (!write_header(stream, HashValue[netSize], netDescription[netSize])) + if (!write_header(stream, HashValue[netSize], netDescription)) return false; if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) return false; @@ -424,24 +425,30 @@ std::string trace(Position& pos) { // Load eval, from a file stream or a memory stream -bool load_eval(const std::string name, std::istream& stream, NetSize netSize) { +std::optional load_eval(std::istream& stream, NetSize netSize) { initialize(netSize); - fileName[netSize] = name; - return read_parameters(stream, netSize); + std::string netDescription; + return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription) + : std::nullopt; } // Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream, NetSize netSize) { +bool save_eval(std::ostream& stream, + NetSize netSize, + const std::string& name, + const std::string& netDescription) { - if (fileName[netSize].empty()) + if (name.empty() || name == "None") return false; - return write_parameters(stream, netSize); + return write_parameters(stream, netSize, netDescription); } // Save eval, to a file given by its name -bool save_eval(const std::optional& filename, NetSize netSize) { +bool save_eval(const std::optional& filename, + NetSize netSize, + const std::unordered_map& evalFiles) { std::string actualFilename; std::string msg; @@ -450,7 +457,7 @@ bool save_eval(const std::optional& filename, NetSize netSize) { actualFilename = filename.value(); else { - if (EvalFiles.at(netSize).selected_name + if (evalFiles.at(netSize).current != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) { msg = "Failed to export a net. " @@ -463,7 +470,8 @@ bool save_eval(const std::optional& filename, NetSize netSize) { } std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream, netSize); + bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current, + evalFiles.at(netSize).netDescription); msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index fabfb5693f9..ea88f890227 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -26,14 +26,20 @@ #include #include #include +#include #include "../misc.h" +#include "../types.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" -#include "../types.h" namespace Stockfish { class Position; + +namespace Eval { +struct EvalFile; +} + } namespace Stockfish::Eval::NNUE { @@ -73,9 +79,14 @@ template Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); void hint_common_parent_position(const Position& pos); -bool load_eval(const std::string name, std::istream& stream, NetSize netSize); -bool save_eval(std::ostream& stream, NetSize netSize); -bool save_eval(const std::optional& filename, NetSize netSize); +std::optional load_eval(std::istream& stream, NetSize netSize); +bool save_eval(std::ostream& stream, + NetSize netSize, + const std::string& name, + const std::string& netDescription); +bool save_eval(const std::optional& filename, + NetSize netSize, + const std::unordered_map&); } // namespace Stockfish::Eval::NNUE diff --git a/src/position.cpp b/src/position.cpp index ddc31888422..6202381d072 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -19,7 +19,6 @@ #include "position.h" #include -#include #include #include #include @@ -36,7 +35,6 @@ #include "movegen.h" #include "nnue/nnue_common.h" #include "syzygy/tbprobe.h" -#include "thread.h" #include "tt.h" #include "uci.h" @@ -87,7 +85,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Position p; - p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + p.set(pos.fen(), pos.is_chess960(), &st); Tablebases::ProbeState s1, s2; Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); int dtz = Tablebases::probe_dtz(p, &s2); @@ -160,7 +158,7 @@ void Position::init() { // Initializes the position object with the given FEN string. // This function is not very robust - make sure that input FENs are correct, // this is assumed to be the responsibility of the GUI. -Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { +Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { /* A FEN string defines a particular position using only the ASCII character set. @@ -286,8 +284,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); - chess960 = isChess960; - thisThread = th; + chess960 = isChess960; set_state(); assert(pos_is_ok()); @@ -388,7 +385,7 @@ Position& Position::set(const string& code, Color c, StateInfo* si) { string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; - return set(fenStr, false, si, nullptr); + return set(fenStr, false, si); } @@ -667,7 +664,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(m.is_ok()); assert(&newSt != st); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); Key k = st->key ^ Zobrist::side; // Copy some fields of the old state to our new StateInfo object except the @@ -959,7 +955,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ // Used to do a "null move": it flips // the side to move without executing any move on the board. -void Position::do_null_move(StateInfo& newSt) { +void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { assert(!checkers()); assert(&newSt != st); @@ -982,7 +978,7 @@ void Position::do_null_move(StateInfo& newSt) { st->key ^= Zobrist::side; ++st->rule50; - prefetch(TT.first_entry(key())); + prefetch(tt.first_entry(key())); st->pliesFromNull = 0; @@ -1235,7 +1231,7 @@ void Position::flip() { std::getline(ss, token); // Half and full moves f += token; - set(f, is_chess960(), st, this_thread()); + set(f, is_chess960(), st); assert(pos_is_ok()); } diff --git a/src/position.h b/src/position.h index 34b53f4a558..7ce3556f0e9 100644 --- a/src/position.h +++ b/src/position.h @@ -32,6 +32,8 @@ namespace Stockfish { +class TranspositionTable; + // StateInfo struct stores information needed to restore a Position object to // its previous state when we retract a move. Whenever a move is made on the // board (by calling Position::do_move), a StateInfo object must be passed. @@ -75,8 +77,6 @@ using StateListPtr = std::unique_ptr>; // pieces, side to move, hash keys, castling info, etc. Important methods are // do_move() and undo_move(), used by the search to update node info when // traversing the search tree. -class Thread; - class Position { public: static void init(); @@ -86,7 +86,7 @@ class Position { Position& operator=(const Position&) = delete; // FEN string input/output - Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const std::string& fenStr, bool isChess960, StateInfo* si); Position& set(const std::string& code, Color c, StateInfo* si); std::string fen() const; @@ -139,7 +139,7 @@ class Position { void do_move(Move m, StateInfo& newSt); void do_move(Move m, StateInfo& newSt, bool givesCheck); void undo_move(Move m); - void do_null_move(StateInfo& newSt); + void do_null_move(StateInfo& newSt, TranspositionTable& tt); void undo_null_move(); // Static Exchange Evaluation @@ -152,16 +152,15 @@ class Position { Key pawn_key() const; // Other properties of the position - Color side_to_move() const; - int game_ply() const; - bool is_chess960() const; - Thread* this_thread() const; - bool is_draw(int ply) const; - bool has_game_cycle(int ply) const; - bool has_repeated() const; - int rule50_count() const; - Value non_pawn_material(Color c) const; - Value non_pawn_material() const; + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + bool is_draw(int ply) const; + bool has_game_cycle(int ply) const; + bool has_repeated() const; + int rule50_count() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; // Position consistency check, for debugging bool pos_is_ok() const; @@ -194,7 +193,6 @@ class Position { int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; - Thread* thisThread; StateInfo* st; int gamePly; Color sideToMove; @@ -328,8 +326,6 @@ inline bool Position::capture_stage(Move m) const { inline Piece Position::captured_piece() const { return st->capturedPiece; } -inline Thread* Position::this_thread() const { return thisThread; } - inline void Position::put_piece(Piece pc, Square s) { board[s] = pc; diff --git a/src/search.cpp b/src/search.cpp index e93b12d1598..5530d125f15 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -27,8 +27,6 @@ #include #include #include -#include -#include #include #include "bitboard.h" @@ -44,14 +42,10 @@ #include "timeman.h" #include "tt.h" #include "uci.h" +#include "ucioption.h" namespace Stockfish { -namespace Search { - -LimitsType Limits; -} - namespace Tablebases { int Cardinality; @@ -62,33 +56,17 @@ Depth ProbeDepth; namespace TB = Tablebases; -using std::string; using Eval::evaluate; using namespace Search; namespace { -// Different node types, used as a template parameter -enum NodeType { - NonPV, - PV, - Root -}; // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { return ((116 - 44 * noTtCutNode) * (d - improving)); } -// Reductions lookup table initialized at startup -int Reductions[MAX_MOVES]; // [depth or moveNumber] - -Depth reduction(bool i, Depth d, int mn, int delta, int rootDelta) { - int reductionScale = Reductions[d] * Reductions[mn]; - return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 - + (!i && reductionScale > 880); -} - constexpr int futility_move_count(bool improving, Depth depth) { return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; } @@ -105,9 +83,7 @@ int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); } int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); } // Add a small random component to draw evaluations to avoid 3-fold blindness -Value value_draw(const Thread* thisThread) { - return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2); -} +Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } // Skill structure is used to implement strength limit. If we have a UCI_Elo, // we convert it to an appropriate skill level, anchored to the Stash engine. @@ -127,34 +103,30 @@ struct Skill { } bool enabled() const { return level < 20.0; } bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } - Move pick_best(size_t multiPV); + Move pick_best(const RootMoves&, size_t multiPV); double level; Move best = Move::none(); }; -template -Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); - -template -Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); - Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus); -void update_all_stats(const Position& pos, - Stack* ss, - Move bestMove, - Value bestValue, - Value beta, - Square prevSq, - Move* quietsSearched, - int quietCount, - Move* capturesSearched, - int captureCount, - Depth depth); +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Value bestValue, + Value beta, + Square prevSq, + Move* quietsSearched, + int quietCount, + Move* capturesSearched, + int captureCount, + Depth depth); // Utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. @@ -187,42 +159,35 @@ uint64_t perft(Position& pos, Depth depth) { } // namespace -// Called at startup to initialize various lookup tables -void Search::init() { - - for (int i = 1; i < MAX_MOVES; ++i) - Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i)); +Search::Worker::Worker(SharedState& sharedState, + std::unique_ptr sm, + size_t thread_id) : + // Unpack the SharedState struct into member variables + thread_idx(thread_id), + manager(std::move(sm)), + options(sharedState.options), + threads(sharedState.threads), + tt(sharedState.tt) { + clear(); } +void Search::Worker::start_searching() { + // Non-main threads go directly to iterative_deepening() + if (!is_mainthread()) + { + iterative_deepening(); + return; + } -// Resets search state to its initial value -void Search::clear() { - - Threads.main()->wait_for_search_finished(); - - Time.availableNodes = 0; - TT.clear(); - Threads.clear(); - Tablebases::init(Options["SyzygyPath"]); // Free mapped files -} - - -// Called when the program receives the UCI 'go' -// command. It searches from the root position and outputs the "bestmove". -void MainThread::search() { - - if (Limits.perft) + if (limits.perft) { - nodes = perft(rootPos, Limits.perft); + nodes = perft(rootPos, limits.perft); sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; return; } - Color us = rootPos.side_to_move(); - Time.init(Limits, us, rootPos.game_ply()); - TT.new_search(); - - Eval::NNUE::verify(); + main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options); + tt.new_search(); if (rootMoves.empty()) { @@ -232,73 +197,75 @@ void MainThread::search() { } else { - Threads.start_searching(); // start non-main threads - Thread::search(); // main thread start searching + threads.start_searching(); // start non-main threads + iterative_deepening(); // main thread start searching } // When we reach the maximum depth, we can arrive here without a raise of - // Threads.stop. However, if we are pondering or in an infinite search, + // threads.stop. However, if we are pondering or in an infinite search, // the UCI protocol states that we shouldn't print the best move before the // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here // until the GUI sends one of those commands. - - while (!Threads.stop && (ponder || Limits.infinite)) + while (!threads.stop && (main_manager()->ponder || limits.infinite)) {} // Busy wait for a stop or a ponder reset // Stop the threads if not already stopped (also raise the stop if - // "ponderhit" just reset Threads.ponder). - Threads.stop = true; + // "ponderhit" just reset threads.ponder). + threads.stop = true; // Wait until all threads have finished - Threads.wait_for_search_finished(); + threads.wait_for_search_finished(); // When playing in 'nodes as time' mode, subtract the searched nodes from // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + if (limits.npmsec) + main_manager()->tm.advance_nodes_time(limits.inc[rootPos.side_to_move()] + - threads.nodes_searched()); - Thread* bestThread = this; + Worker* bestThread = this; Skill skill = - Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); - if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled() + if (int(options["MultiPV"]) == 1 && !limits.depth && !skill.enabled() && rootMoves[0].pv[0] != Move::none()) - bestThread = Threads.get_best_thread(); + bestThread = threads.get_best_thread()->worker.get(); - bestPreviousScore = bestThread->rootMoves[0].score; - bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; + main_manager()->bestPreviousScore = bestThread->rootMoves[0].score; + main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; // Send again PV info if we have a new best thread if (bestThread != this) - sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; + sync_cout << UCI::pv(*bestThread, main_manager()->tm.elapsed(threads.nodes_searched()), + threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), + TB::RootInTB) + << sync_endl; sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); if (bestThread->rootMoves[0].pv.size() > 1 - || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); std::cout << sync_endl; } - // Main iterative deepening loop. It calls search() // repeatedly with increasing depth until the allocated thinking time has been // consumed, the user stops the search, or the maximum search depth is reached. -void Thread::search() { +void Search::Worker::iterative_deepening() { // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), // (ss + 2) is needed for initialization of cutOffCnt and killers. - Stack stack[MAX_PLY + 10], *ss = stack + 7; - Move pv[MAX_PLY + 1]; - Value alpha, beta; - Move lastBestMove = Move::none(); - Depth lastBestMoveDepth = 0; - MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int delta, iterIdx = 0; + Stack stack[MAX_PLY + 10], *ss = stack + 7; + Move pv[MAX_PLY + 1]; + Value alpha, beta; + Move lastBestMove = Move::none(); + Depth lastBestMoveDepth = 0; + SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int delta, iterIdx = 0; std::memset(ss - 7, 0, 10 * sizeof(Stack)); for (int i = 7; i > 0; --i) @@ -313,7 +280,7 @@ void Thread::search() { ss->pv = pv; - bestValue = -VALUE_INFINITE; + iterBestValue = -VALUE_INFINITE; if (mainThread) { @@ -325,8 +292,8 @@ void Thread::search() { mainThread->iterValue[i] = mainThread->bestPreviousScore; } - size_t multiPV = size_t(Options["MultiPV"]); - Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0); + size_t multiPV = size_t(options["MultiPV"]); + Skill skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); // When playing with strength handicap enable MultiPV search that we will // use behind-the-scenes to retrieve a set of possible moves. @@ -338,8 +305,8 @@ void Thread::search() { int searchAgainCounter = 0; // Iterative deepening loop until requested to stop or the target depth is reached - while (++rootDepth < MAX_PLY && !Threads.stop - && !(Limits.depth && mainThread && rootDepth > Limits.depth)) + while (++rootDepth < MAX_PLY && !threads.stop + && !(limits.depth && mainThread && rootDepth > limits.depth)) { // Age out PV variability metric if (mainThread) @@ -353,11 +320,11 @@ void Thread::search() { size_t pvFirst = 0; pvLast = 0; - if (!Threads.increaseDepth) + if (!threads.increaseDepth) searchAgainCounter++; // MultiPV loop. We perform a full root search for each PV line - for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) + for (pvIdx = 0; pvIdx < multiPV && !threads.stop; ++pvIdx) { if (pvIdx == pvLast) { @@ -390,7 +357,7 @@ void Thread::search() { // for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); + iterBestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -403,29 +370,32 @@ void Thread::search() { // If search has been stopped, we break immediately. Sorting is // safe because RootMoves is still valid, although it refers to // the previous iteration. - if (Threads.stop) + if (threads.stop) break; // When failing high/low give some update (without cluttering // the UI) before a re-search. - if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && Time.elapsed() > 3000) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + if (mainThread && multiPV == 1 && (iterBestValue <= alpha || iterBestValue >= beta) + && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), + threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), + TB::RootInTB) + << sync_endl; // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. - if (bestValue <= alpha) + if (iterBestValue <= alpha) { beta = (alpha + beta) / 2; - alpha = std::max(bestValue - delta, -VALUE_INFINITE); + alpha = std::max(iterBestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; if (mainThread) mainThread->stopOnPonderhit = false; } - else if (bestValue >= beta) + else if (iterBestValue >= beta) { - beta = std::min(bestValue + delta, int(VALUE_INFINITE)); + beta = std::min(iterBestValue + delta, int(VALUE_INFINITE)); ++failedHighCnt; } else @@ -439,11 +409,16 @@ void Thread::search() { // Sort the PV lines searched so far and update the GUI std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); - if (mainThread && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) - sync_cout << UCI::pv(rootPos, rootDepth) << sync_endl; + if (mainThread + && (threads.stop || pvIdx + 1 == multiPV + || mainThread->tm.elapsed(threads.nodes_searched()) > 3000)) + sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), + threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), + TB::RootInTB) + << sync_endl; } - if (!Threads.stop) + if (!threads.stop) completedDepth = rootDepth; if (rootMoves[0].pv[0] != lastBestMove) @@ -453,60 +428,62 @@ void Thread::search() { } // Have we found a "mate in x"? - if (Limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * Limits.mate) - Threads.stop = true; + if (limits.mate && iterBestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - iterBestValue <= 2 * limits.mate) + threads.stop = true; if (!mainThread) continue; // If the skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) - skill.pick_best(multiPV); + skill.pick_best(rootMoves, multiPV); // Use part of the gained time from a previous stable move for the current move - for (Thread* th : Threads) + for (Thread* th : threads) { - totBestMoveChanges += th->bestMoveChanges; - th->bestMoveChanges = 0; + totBestMoveChanges += th->worker->bestMoveChanges; + th->worker->bestMoveChanges = 0; } // Do we have time for the next iteration? Can we stop searching now? - if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit) + if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - iterBestValue) + + 6 * (mainThread->iterValue[iterIdx] - iterBestValue)) / 616.6; fallingEval = std::clamp(fallingEval, 0.51, 1.51); // If the bestMove is stable over several iterations, reduce time accordingly timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction); - double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / Threads.size(); + double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / threads.size(); - double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; + double totalTime = + mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); // Stop the search if we have exceeded the totalTime - if (Time.elapsed() > totalTime) + if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". if (mainThread->ponder) mainThread->stopOnPonderhit = true; else - Threads.stop = true; + threads.stop = true; } - else if (!mainThread->ponder && Time.elapsed() > totalTime * 0.50) - Threads.increaseDepth = false; + else if (!mainThread->ponder + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.50) + threads.increaseDepth = false; else - Threads.increaseDepth = true; + threads.increaseDepth = true; } - mainThread->iterValue[iterIdx] = bestValue; + mainThread->iterValue[iterIdx] = iterBestValue; iterIdx = (iterIdx + 1) & 3; } @@ -517,16 +494,34 @@ void Thread::search() { // If the skill level is enabled, swap the best PV line with the sub-optimal one if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), - skill.best ? skill.best : skill.pick_best(multiPV))); + std::swap(rootMoves[0], + *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); } +void Search::Worker::clear() { + counterMoves.fill(Move::none()); + mainHistory.fill(0); + captureHistory.fill(0); + pawnHistory.fill(0); + correctionHistory.fill(0); + + for (bool inCheck : {false, true}) + for (StatsType c : {NoCaptures, Captures}) + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h->fill(-71); -namespace { -// Main search function for both PV and non-PV nodes + for (int i = 1; i < MAX_MOVES; ++i) + reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); +} + + +// Main search function for both PV and non-PV nodes. template -Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { +Value Search::Worker::search( + Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; @@ -539,7 +534,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // if the opponent had an alternative move earlier to this position. if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { - alpha = value_draw(pos.this_thread()); + alpha = value_draw(this->nodes); if (alpha >= beta) return alpha; } @@ -564,7 +559,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo int moveCount, captureCount, quietCount; // Step 1. Initialize node - Thread* thisThread = pos.this_thread(); + Worker* thisThread = this; ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); @@ -573,8 +568,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo maxValue = VALUE_INFINITE; // Check for the available remaining time - if (thisThread == Threads.main()) - static_cast(thisThread)->check_time(); + if (is_mainthread()) + main_manager()->check_time(*this); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) if (PvNode && thisThread->selDepth < ss->ply + 1) @@ -583,10 +578,10 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (!rootNode) { // Step 2. Check for aborted search and immediate draw - if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) + if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) - : value_draw(pos.this_thread()); + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -614,7 +609,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Step 4. Transposition table lookup. excludedMove = ss->excludedMove; posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); + tte = tt.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() @@ -638,7 +633,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_stats(pos, ss, ttMove, stat_bonus(depth)); + update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of // the previous ply (~0 Elo on STC, ~2 Elo on LTC). @@ -676,8 +671,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); // Force check of time on the next occasion - if (thisThread == Threads.main()) - static_cast(thisThread)->callsCnt = 0; + if (is_mainthread()) + main_manager()->callsCnt = 0; if (err != TB::ProbeState::FAIL) { @@ -699,7 +694,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE); + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE, + tt.generation()); return value; } @@ -715,7 +711,6 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } } - CapturePieceToHistory& captureHistory = thisThread->captureHistory; Value unadjustedStaticEval = VALUE_NONE; @@ -739,7 +734,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Never assume anything about values stored in TT unadjustedStaticEval = ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); @@ -757,7 +752,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo } else { - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); Value newEval = ss->staticEval @@ -769,7 +764,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), - unadjustedStaticEval); + unadjustedStaticEval, tt.generation()); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) @@ -827,7 +822,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; - pos.do_null_move(st); + pos.do_null_move(st, tt); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, !cutNode); @@ -885,7 +880,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->captureHistory); while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) @@ -893,13 +888,14 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo assert(pos.capture_stage(move)); // Prefetch the TT entry for the resulting position - prefetch(TT.first_entry(pos.key_after(move))); + prefetch(tt.first_entry(pos.key_after(move))); ss->currentMove = move; ss->continuationHistory = - &thisThread + &this ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds @@ -916,7 +912,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo { // Save ProbCut data into transposition table tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, - move, unadjustedStaticEval); + move, unadjustedStaticEval, tt.generation()); return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) : value; } @@ -944,8 +940,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist, - &thisThread->pawnHistory, countermove, ss->killers); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + contHist, &thisThread->pawnHistory, countermove, ss->killers); value = bestValue; moveCountPruning = singularQuietLMR = false; @@ -978,7 +974,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->moveCount = ++moveCount; - if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) + if (rootNode && is_mainthread() + && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << "info depth " << depth << " currmove " << UCI::move(move, pos.is_chess960()) << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; @@ -995,7 +992,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo int delta = beta - alpha; - Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta); + Depth r = reduction(improving, depth, moveCount, delta); // Step 14. Pruning at shallow depth (~120 Elo). // Depth conditions are important for mate finding. @@ -1016,7 +1013,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] - + captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; + + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] + / 7; if (futilityEval < alpha) continue; } @@ -1135,8 +1133,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq - && captureHistory[movedPiece][move.to_sq()] - [type_of(pos.piece_on(move.to_sq()))] + && thisThread->captureHistory[movedPiece][move.to_sq()] + [type_of(pos.piece_on(move.to_sq()))] > 4146) extension = 1; } @@ -1146,7 +1144,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension == 2); // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + prefetch(tt.first_entry(pos.key_after(move))); // Update the current move (this must be done after singular extension search) ss->currentMove = move; @@ -1154,6 +1152,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; // Step 16. Make the move + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); // Decrease reduction if position is or has been on the PV (~4 Elo) @@ -1269,7 +1268,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // Finished searching the move. If a stop occurred, the return value of // the search cannot be trusted, and we return immediately without // updating best move, PV and TT. - if (Threads.stop.load(std::memory_order_relaxed)) + if (threads.stop.load(std::memory_order_relaxed)) return VALUE_ZERO; if (rootNode) @@ -1371,8 +1370,8 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // If there is a move that produces search value greater than alpha we update the stats of searched moves else if (bestMove) - update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, - capturesSearched, captureCount, depth); + update_all_stats(pos, ss, *this, bestMove, bestValue, beta, prevSq, quietsSearched, + quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1400,7 +1399,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, unadjustedStaticEval); + depth, bestMove, unadjustedStaticEval, tt.generation()); // Adjust correction history if (!ss->inCheck && (!bestMove || !pos.capture(bestMove)) @@ -1422,7 +1421,7 @@ Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, boo // function with zero depth, or recursively with further decreasing depth per call. // (~155 Elo) template -Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { +Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { static_assert(nodeType != Root); constexpr bool PvNode = nodeType == PV; @@ -1435,7 +1434,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // if the opponent had an alternative move earlier to this position. if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { - alpha = value_draw(pos.this_thread()); + alpha = value_draw(this->nodes); if (alpha >= beta) return alpha; } @@ -1460,7 +1459,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { ss->pv[0] = Move::none(); } - Thread* thisThread = pos.this_thread(); + Worker* thisThread = this; bestMove = Move::none(); ss->inCheck = pos.checkers(); moveCount = 0; @@ -1471,7 +1470,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1480,7 +1479,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Step 3. Transposition table lookup posKey = pos.key(); - tte = TT.probe(posKey, ss->ttHit); + tte = tt.probe(posKey, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = ss->ttHit ? tte->move() : Move::none(); pvHit = ss->ttHit && tte->is_pv(); @@ -1502,7 +1501,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { // Never assume anything about values stored in TT if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos); + unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos, *thisThread); Value newEval = ss->staticEval @@ -1522,7 +1521,8 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; + (ss - 1)->currentMove != Move::null() ? evaluate(pos, *thisThread) + : -(ss - 1)->staticEval; Value newEval = ss->staticEval @@ -1539,7 +1539,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { { if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - Move::none(), unadjustedStaticEval); + Move::none(), unadjustedStaticEval, tt.generation()); return bestValue; } @@ -1632,7 +1632,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { } // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + prefetch(tt.first_entry(pos.key_after(move))); // Update the current move ss->currentMove = move; @@ -1643,6 +1643,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { quietCheckEvasions += !capture && ss->inCheck; // Step 7. Make and search the move + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); value = -qsearch(pos, ss + 1, -beta, -alpha, depth - 1); pos.undo_move(move); @@ -1686,7 +1687,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, - unadjustedStaticEval); + unadjustedStaticEval, tt.generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1694,6 +1695,7 @@ Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { } +namespace { // Adjusts a mate or TB score from "plies to mate from the root" // to "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. @@ -1759,6 +1761,7 @@ void update_pv(Move* pv, Move move, const Move* childPv) { // Updates stats at the end of search() when a bestMove is found void update_all_stats(const Position& pos, Stack* ss, + Search::Worker& workerThread, Move bestMove, Value bestValue, Value beta, @@ -1770,8 +1773,7 @@ void update_all_stats(const Position& pos, Depth depth) { Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); - CapturePieceToHistory& captureHistory = thisThread->captureHistory; + CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; @@ -1784,19 +1786,19 @@ void update_all_stats(const Position& pos, : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move - update_quiet_stats(pos, ss, bestMove, bestMoveBonus); + update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); int pIndex = pawn_structure_index(pos); - thisThread->pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; + workerThread.pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { - thisThread - ->pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] + workerThread + .pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] << -quietMoveMalus; - thisThread->mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; + workerThread.mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), quietsSearched[i].to_sq(), -quietMoveMalus); } @@ -1842,7 +1844,8 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Updates move sorting heuristics -void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { // Update killers if (ss->killers[0] != move) @@ -1851,25 +1854,23 @@ void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) { ss->killers[0] = move; } - Color us = pos.side_to_move(); - Thread* thisThread = pos.this_thread(); - thisThread->mainHistory[us][move.from_to()] << bonus; + Color us = pos.side_to_move(); + workerThread.mainHistory[us][move.from_to()] << bonus; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); // Update countermove history if (((ss - 1)->currentMove).is_ok()) { - Square prevSq = ((ss - 1)->currentMove).to_sq(); - thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; + Square prevSq = ((ss - 1)->currentMove).to_sq(); + workerThread.counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } +} // When playing with strength handicap, choose the best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. -Move Skill::pick_best(size_t multiPV) { - - const RootMoves& rootMoves = Threads.main()->rootMoves; - static PRNG rng(now()); // PRNG sequence should be non-deterministic +Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { + static PRNG rng(now()); // PRNG sequence should be non-deterministic // RootMoves are already sorted by score in descending order Value topScore = rootMoves[0].score; @@ -1897,23 +1898,20 @@ Move Skill::pick_best(size_t multiPV) { return best; } -} // namespace - // Used to print debug info and, more importantly, // to detect when we are out of available time and thus stop the search. -void MainThread::check_time() { - +void SearchManager::check_time(Search::Worker& worker) { if (--callsCnt > 0) return; // When using nodes, ensure checking rate is not lower than 0.1% of nodes - callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512; + callsCnt = worker.limits.nodes ? std::min(512, int(worker.limits.nodes / 1024)) : 512; static TimePoint lastInfoTime = now(); - TimePoint elapsed = Time.elapsed(); - TimePoint tick = Limits.startTime + elapsed; + TimePoint elapsed = tm.elapsed(worker.threads.nodes_searched()); + TimePoint tick = worker.limits.startTime + elapsed; if (tick - lastInfoTime >= 1000) { @@ -1925,72 +1923,18 @@ void MainThread::check_time() { if (ponder) return; - if ((Limits.use_time_management() && (elapsed > Time.maximum() || stopOnPonderhit)) - || (Limits.movetime && elapsed >= Limits.movetime) - || (Limits.nodes && Threads.nodes_searched() >= uint64_t(Limits.nodes))) - Threads.stop = true; + if ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) + || (worker.limits.movetime && elapsed >= worker.limits.movetime) + || (worker.limits.nodes + && worker.threads.nodes_searched() >= uint64_t(worker.limits.nodes))) + worker.threads.stop = true; } - -// Formats PV information according to the UCI protocol. UCI requires -// that all (if any) unsearched PV lines are sent using a previous search score. -string UCI::pv(const Position& pos, Depth depth) { - - std::stringstream ss; - TimePoint elapsed = Time.elapsed() + 1; - const RootMoves& rootMoves = pos.this_thread()->rootMoves; - size_t pvIdx = pos.this_thread()->pvIdx; - size_t multiPV = std::min(size_t(Options["MultiPV"]), rootMoves.size()); - uint64_t nodesSearched = Threads.nodes_searched(); - uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); - - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = rootMoves[i].score != -VALUE_INFINITE; - - if (depth == 1 && !updated && i > 0) - continue; - - Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; - - if (v == -VALUE_INFINITE) - v = VALUE_ZERO; - - bool tb = TB::RootInTB && std::abs(v) <= VALUE_TB; - v = tb ? rootMoves[i].tbScore : v; - - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; - - ss << "info" - << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCI::value(v); - - if (Options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos.game_ply()); - - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound - ? " lowerbound" - : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); - - ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / elapsed - << " hashfull " << TT.hashfull() << " tbhits " << tbHits << " time " << elapsed << " pv"; - - for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); - } - - return ss.str(); -} - - // Called in case we have no ponder move before exiting the search, // for instance, in case we stop the search during a fail high at root. // We try hard to have a ponder move to return to the GUI, // otherwise in case of 'ponder on' we have nothing to think about. -bool RootMove::extract_ponder_from_tt(Position& pos) { +bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) { StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); @@ -2003,7 +1947,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { return false; pos.do_move(pv[0], st); - TTEntry* tte = TT.probe(pos.key(), ttHit); + TTEntry* tte = tt.probe(pos.key(), ttHit); if (ttHit) { @@ -2016,12 +1960,14 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { return pv.size() > 1; } -void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { +void Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves) { RootInTB = false; - UseRule50 = bool(Options["Syzygy50MoveRule"]); - ProbeDepth = int(Options["SyzygyProbeDepth"]); - Cardinality = int(Options["SyzygyProbeLimit"]); + UseRule50 = bool(options["Syzygy50MoveRule"]); + ProbeDepth = int(options["SyzygyProbeDepth"]); + Cardinality = int(options["SyzygyProbeLimit"]); bool dtz_available = true; // Tables with fewer pieces than SyzygyProbeLimit are searched with @@ -2035,13 +1981,13 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { // Rank moves using DTZ tables - RootInTB = root_probe(pos, rootMoves); + RootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); if (!RootInTB) { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; - RootInTB = root_probe_wdl(pos, rootMoves); + RootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); } } diff --git a/src/search.h b/src/search.h index 72e275d36f7..48a5630cddb 100644 --- a/src/search.h +++ b/src/search.h @@ -19,19 +19,37 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include +#include +#include #include +#include #include #include "misc.h" #include "movepick.h" +#include "position.h" +#include "timeman.h" #include "types.h" namespace Stockfish { -class Position; +// Different node types, used as a template parameter +enum NodeType { + NonPV, + PV, + Root +}; + +class TranspositionTable; +class ThreadPool; +class OptionsMap; +class UCI; namespace Search { +// Called at startup to initialize various lookup tables, after program startup +void init(int); // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has @@ -61,7 +79,7 @@ struct RootMove { explicit RootMove(Move m) : pv(1, m) {} - bool extract_ponder_from_tt(Position& pos); + bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos); bool operator==(const Move& m) const { return pv[0] == m; } // Sort in descending order bool operator<(const RootMove& m) const { @@ -85,7 +103,6 @@ using RootMoves = std::vector; // LimitsType struct stores information sent by GUI about available time to // search the current move, maximum depth/time, or if we are in analysis mode. - struct LimitsType { // Init explicitly due to broken value-initialization of non POD in MSVC @@ -103,10 +120,136 @@ struct LimitsType { int64_t nodes; }; -extern LimitsType Limits; -void init(); -void clear(); +// The UCI stores the uci options, thread pool, and transposition table. +// This struct is used to easily forward data to the Search::Worker class. +struct SharedState { + SharedState(const OptionsMap& o, ThreadPool& tp, TranspositionTable& t) : + options(o), + threads(tp), + tt(t) {} + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; +}; + +class Worker; + +// Null Object Pattern, implement a common interface +// for the SearchManagers. A Null Object will be given to +// non-mainthread workers. +class ISearchManager { + public: + virtual ~ISearchManager() {} + virtual void check_time(Search::Worker&) = 0; +}; + +// SearchManager manages the search from the main thread. It is responsible for +// keeping track of the time, and storing data strictly related to the main thread. +class SearchManager: public ISearchManager { + public: + void check_time(Search::Worker& worker) override; + + Stockfish::TimeManagement tm; + int callsCnt; + std::atomic_bool ponder; + + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + Value iterValue[4]; + bool stopOnPonderhit; + + size_t id; +}; + +class NullSearchManager: public ISearchManager { + public: + void check_time(Search::Worker&) override {} +}; + +// Search::Worker is the class that does the actual search. +// It is instantiated once per thread, and it is responsible for keeping track +// of the search history, and storing data required for the search. +class Worker { + public: + Worker(SharedState&, std::unique_ptr, size_t); + + // Reset histories, usually before a new game + void clear(); + + // Called when the program receives the UCI 'go' + // command. It searches from the root position and outputs the "bestmove". + void start_searching(); + + bool is_mainthread() const { return thread_idx == 0; } + + // Public because evaluate uses this + Value iterBestValue, optimism[COLOR_NB]; + Value rootSimpleEval; + + // Public because they need to be updatable by the stats + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; + CorrectionHistory correctionHistory; + + private: + void iterative_deepening(); + + // Main search function for both PV and non-PV nodes + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + + // Quiescence search function, which is called by the main search + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); + + Depth reduction(bool i, Depth d, int mn, int delta) { + int reductionScale = reductions[d] * reductions[mn]; + return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 + + (!i && reductionScale > 880); + } + + // Get a pointer to the search manager, only allowed to be called by the + // main thread. + SearchManager* main_manager() const { + assert(thread_idx == 0); + return static_cast(manager.get()); + } + + LimitsType limits; + + size_t pvIdx, pvLast; + std::atomic nodes, tbHits, bestMoveChanges; + int selDepth, nmpMinPly; + + Position rootPos; + StateInfo rootState; + RootMoves rootMoves; + Depth rootDepth, completedDepth; + Value rootDelta; + + size_t thread_idx; + + // Reductions lookup table initialized at startup + int reductions[MAX_MOVES]; // [depth or moveNumber] + + // The main thread has a SearchManager, the others have a NullSearchManager + std::unique_ptr manager; + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + + friend class Stockfish::ThreadPool; + friend class Stockfish::UCI; + friend class SearchManager; +}; + } // namespace Search diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 91013dcac28..6f30bf6b1f7 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -42,7 +42,6 @@ #include "../position.h" #include "../search.h" #include "../types.h" -#include "../uci.h" #ifndef _WIN32 #include @@ -1574,7 +1573,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { +bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50) { ProbeState result = OK; StateInfo st; @@ -1585,7 +1584,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // Check whether a position was repeated since the last zeroing move. bool rep = pos.has_repeated(); - int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1; + int dtz, bound = rule50 ? (MAX_DTZ - 100) : 1; // Probe and rank each move for (auto& m : rootMoves) @@ -1647,7 +1646,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { // This is a fallback for the case that some or all DTZ tables are missing. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) { static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ}; @@ -1655,7 +1654,6 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { StateInfo st; WDLScore wdl; - bool rule50 = Options["Syzygy50MoveRule"]; // Probe and rank each move for (auto& m : rootMoves) diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index cc8eb0d4d70..d7b412a10e5 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -25,6 +25,7 @@ namespace Stockfish { class Position; +class OptionsMap; } namespace Stockfish::Tablebases { @@ -47,12 +48,13 @@ enum ProbeState { extern int MaxCardinality; + void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves); -bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); -void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); +bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); +void rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); } // namespace Stockfish::Tablebases diff --git a/src/thread.cpp b/src/thread.cpp index 01ccd4fc565..a512c0a52b8 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -23,9 +23,8 @@ #include #include #include -#include -#include #include +#include #include #include "evaluate.h" @@ -33,18 +32,21 @@ #include "movegen.h" #include "search.h" #include "syzygy/tbprobe.h" +#include "timeman.h" #include "tt.h" -#include "uci.h" +#include "types.h" +#include "ucioption.h" namespace Stockfish { -ThreadPool Threads; // Global object - - // Constructor launches the thread and waits until it goes to sleep // in idle_loop(). Note that 'searching' and 'exit' should be already set. -Thread::Thread(size_t n) : +Thread::Thread(Search::SharedState& sharedState, + std::unique_ptr sm, + size_t n) : + worker(std::make_unique(sharedState, std::move(sm), n)), idx(n), + nthreads(sharedState.options["Threads"]), stdThread(&Thread::idle_loop, this) { wait_for_search_finished(); @@ -62,24 +64,6 @@ Thread::~Thread() { stdThread.join(); } - -// Reset histories, usually before a new game -void Thread::clear() { - - counterMoves.fill(Move::none()); - mainHistory.fill(0); - captureHistory.fill(0); - pawnHistory.fill(0); - correctionHistory.fill(0); - - for (bool inCheck : {false, true}) - for (StatsType c : {NoCaptures, Captures}) - for (auto& to : continuationHistory[inCheck][c]) - for (auto& h : to) - h->fill(-71); -} - - // Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -108,7 +92,7 @@ void Thread::idle_loop() { // some Windows NUMA hardware, for instance in fishtest. To make it simple, // just check if running threads are below a threshold, in this case, all this // NUMA machinery is not needed. - if (Options["Threads"] > 8) + if (nthreads > 8) WinProcGroup::bindThisThread(idx); while (true) @@ -123,36 +107,41 @@ void Thread::idle_loop() { lk.unlock(); - search(); + worker->start_searching(); } } // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. -void ThreadPool::set(size_t requested) { +void ThreadPool::set(Search::SharedState sharedState) { if (threads.size() > 0) // destroy any existing thread(s) { - main()->wait_for_search_finished(); + main_thread()->wait_for_search_finished(); while (threads.size() > 0) delete threads.back(), threads.pop_back(); } + const size_t requested = sharedState.options["Threads"]; + if (requested > 0) // create new thread(s) { - threads.push_back(new MainThread(0)); + threads.push_back(new Thread( + sharedState, std::unique_ptr(new Search::SearchManager()), 0)); + while (threads.size() < requested) - threads.push_back(new Thread(threads.size())); + threads.push_back(new Thread( + sharedState, std::unique_ptr(new Search::NullSearchManager()), + threads.size())); clear(); - // Reallocate the hash with the new threadpool size - TT.resize(size_t(Options["Hash"])); + main_thread()->wait_for_search_finished(); - // Init thread number dependent search params. - Search::init(); + // Reallocate the hash with the new threadpool size + sharedState.tt.resize(sharedState.options["Hash"], requested); } } @@ -161,28 +150,31 @@ void ThreadPool::set(size_t requested) { void ThreadPool::clear() { for (Thread* th : threads) - th->clear(); + th->worker->clear(); - main()->callsCnt = 0; - main()->bestPreviousScore = VALUE_INFINITE; - main()->bestPreviousAverageScore = VALUE_INFINITE; - main()->previousTimeReduction = 1.0; + main_manager()->callsCnt = 0; + main_manager()->bestPreviousScore = VALUE_INFINITE; + main_manager()->bestPreviousAverageScore = VALUE_INFINITE; + main_manager()->previousTimeReduction = 1.0; + main_manager()->tm.clear(); } // Wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. -void ThreadPool::start_thinking(Position& pos, - StateListPtr& states, - const Search::LimitsType& limits, - bool ponderMode) { +void ThreadPool::start_thinking(const OptionsMap& options, + Position& pos, + StateListPtr& states, + Search::LimitsType limits, + bool ponderMode) { + + main_thread()->wait_for_search_finished(); - main()->wait_for_search_finished(); + main_manager()->stopOnPonderhit = stop = false; + main_manager()->ponder = ponderMode; + + increaseDepth = true; - main()->stopOnPonderhit = stop = false; - increaseDepth = true; - main()->ponder = ponderMode; - Search::Limits = limits; Search::RootMoves rootMoves; for (const auto& m : MoveList(pos)) @@ -191,7 +183,7 @@ void ThreadPool::start_thinking(Position& pos, rootMoves.emplace_back(m); if (!rootMoves.empty()) - Tablebases::rank_root_moves(pos, rootMoves); + Tablebases::rank_root_moves(options, pos, rootMoves); // After ownership transfer 'states' becomes empty, so if we stop the search // and call 'go' again without setting a new position states.get() == nullptr. @@ -207,15 +199,17 @@ void ThreadPool::start_thinking(Position& pos, // since they are read-only. for (Thread* th : threads) { - th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; - th->rootDepth = th->completedDepth = 0; - th->rootMoves = rootMoves; - th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); - th->rootState = setupStates->back(); - th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); + th->worker->limits = limits; + th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly = + th->worker->bestMoveChanges = 0; + th->worker->rootDepth = th->worker->completedDepth = 0; + th->worker->rootMoves = rootMoves; + th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); + th->worker->rootState = setupStates->back(); + th->worker->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); } - main()->start_searching(); + main_thread()->start_searching(); } Thread* ThreadPool::get_best_thread() const { @@ -226,30 +220,32 @@ Thread* ThreadPool::get_best_thread() const { // Find the minimum score of all threads for (Thread* th : threads) - minScore = std::min(minScore, th->rootMoves[0].score); + minScore = std::min(minScore, th->worker->rootMoves[0].score); // Vote according to score and depth, and select the best thread auto thread_value = [minScore](Thread* th) { - return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth); }; for (Thread* th : threads) - votes[th->rootMoves[0].pv[0]] += thread_value(th); + votes[th->worker->rootMoves[0].pv[0]] += thread_value(th); for (Thread* th : threads) - if (std::abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + if (std::abs(bestThread->worker->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) { // Make sure we pick the shortest mate / TB conversion or stave off mate the longest - if (th->rootMoves[0].score > bestThread->rootMoves[0].score) + if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score) bestThread = th; } - else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]] - || (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]] - && thread_value(th) * int(th->rootMoves[0].pv.size() > 2) + else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY + && (votes[th->worker->rootMoves[0].pv[0]] + > votes[bestThread->worker->rootMoves[0].pv[0]] + || (votes[th->worker->rootMoves[0].pv[0]] + == votes[bestThread->worker->rootMoves[0].pv[0]] + && thread_value(th) * int(th->worker->rootMoves[0].pv.size() > 2) > thread_value(bestThread) - * int(bestThread->rootMoves[0].pv.size() > 2))))) + * int(bestThread->worker->rootMoves[0].pv.size() > 2))))) bestThread = th; return bestThread; @@ -257,7 +253,7 @@ Thread* ThreadPool::get_best_thread() const { // Start non-main threads - +// Will be invoked by main thread after it has started searching void ThreadPool::start_searching() { for (Thread* th : threads) diff --git a/src/thread.h b/src/thread.h index 7db7c15905b..6575b14e638 100644 --- a/src/thread.h +++ b/src/thread.h @@ -23,91 +23,76 @@ #include #include #include +#include #include #include -#include "movepick.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" -#include "types.h" namespace Stockfish { -// Thread class keeps together all the thread-related stuff. -class Thread { - - std::mutex mutex; - std::condition_variable cv; - size_t idx; - bool exit = false, searching = true; // Set before starting std::thread - NativeThread stdThread; +class OptionsMap; +using Value = int; +// Abstraction of a thread. It contains a pointer to the worker and a native thread. +// After construction, the native thread is started with idle_loop() +// waiting for a signal to start searching. +// When the signal is received, the thread starts searching and when +// the search is finished, it goes back to idle_loop() waiting for a new signal. +class Thread { public: - explicit Thread(size_t); + Thread(Search::SharedState&, std::unique_ptr, size_t); virtual ~Thread(); - virtual void search(); - void clear(); - void idle_loop(); - void start_searching(); - void wait_for_search_finished(); - size_t id() const { return idx; } - - size_t pvIdx, pvLast; - std::atomic nodes, tbHits, bestMoveChanges; - int selDepth, nmpMinPly; - Value bestValue; - - int optimism[COLOR_NB]; - - Position rootPos; - StateInfo rootState; - Search::RootMoves rootMoves; - Depth rootDepth, completedDepth; - int rootDelta; - Value rootSimpleEval; - CounterMoveHistory counterMoves; - ButterflyHistory mainHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; - PawnHistory pawnHistory; - CorrectionHistory correctionHistory; -}; - - -// MainThread is a derived class specific for main thread -struct MainThread: public Thread { - using Thread::Thread; + void idle_loop(); + void start_searching(); + void wait_for_search_finished(); + size_t id() const { return idx; } - void search() override; - void check_time(); + std::unique_ptr worker; - double previousTimeReduction; - Value bestPreviousScore; - Value bestPreviousAverageScore; - Value iterValue[4]; - int callsCnt; - bool stopOnPonderhit; - std::atomic_bool ponder; + private: + std::mutex mutex; + std::condition_variable cv; + size_t idx, nthreads; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; }; // ThreadPool struct handles all the threads-related stuff like init, starting, // parking and, most importantly, launching a thread. All the access to threads // is done through this class. -struct ThreadPool { +class ThreadPool { - void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); - void clear(); - void set(size_t); + public: + ~ThreadPool() { + // destroy any existing thread(s) + if (threads.size() > 0) + { + main_thread()->wait_for_search_finished(); + + while (threads.size() > 0) + delete threads.back(), threads.pop_back(); + } + } - MainThread* main() const { return static_cast(threads.front()); } - uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } - uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; + void + start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType, bool = false); + void clear(); + void set(Search::SharedState); + + Search::SearchManager* main_manager() const { + return static_cast(main_thread()->worker.get()->manager.get()); + }; + Thread* main_thread() const { return threads.front(); } + uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); } + uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); } + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; std::atomic_bool stop, increaseDepth; @@ -122,17 +107,15 @@ struct ThreadPool { StateListPtr setupStates; std::vector threads; - uint64_t accumulate(std::atomic Thread::*member) const { + uint64_t accumulate(std::atomic Search::Worker::*member) const { uint64_t sum = 0; for (Thread* th : threads) - sum += (th->*member).load(std::memory_order_relaxed); + sum += (th->worker.get()->*member).load(std::memory_order_relaxed); return sum; } }; -extern ThreadPool Threads; - } // namespace Stockfish #endif // #ifndef THREAD_H_INCLUDED diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 4bc62d678ad..8d424b72a50 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -30,31 +30,35 @@ #if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) #include + #include namespace Stockfish { -static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; - -template> -void* start_routine(void* ptr) { - P* p = reinterpret_cast(ptr); - (p->first->*(p->second))(); // Call member function pointer - delete p; +// free function to be passed to pthread_create() +inline void* start_routine(void* ptr) { + auto func = reinterpret_cast*>(ptr); + (*func)(); // Call the function + delete func; return nullptr; } class NativeThread { - pthread_t thread; + static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024; + public: - template> - explicit NativeThread(void (T::*fun)(), T* obj) { + template + explicit NativeThread(Function&& fun, Args&&... args) { + auto func = new std::function( + std::bind(std::forward(fun), std::forward(args)...)); + pthread_attr_t attr_storage, *attr = &attr_storage; pthread_attr_init(attr); pthread_attr_setstacksize(attr, TH_STACK_SIZE); - pthread_create(&thread, attr, start_routine, new P(obj, fun)); + pthread_create(&thread, attr, start_routine, func); } + void join() { pthread_join(thread, nullptr); } }; diff --git a/src/timeman.cpp b/src/timeman.cpp index 77db2f621df..121f8edb985 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -19,30 +19,47 @@ #include "timeman.h" #include +#include #include +#include #include "search.h" -#include "uci.h" +#include "ucioption.h" namespace Stockfish { -TimeManagement Time; // Our global time management object +TimePoint TimeManagement::optimum() const { return optimumTime; } +TimePoint TimeManagement::maximum() const { return maximumTime; } +TimePoint TimeManagement::elapsed(size_t nodes) const { + return useNodesTime ? TimePoint(nodes) : now() - startTime; +} + +void TimeManagement::clear() { + availableNodes = 0; // When in 'nodes as time' mode +} + +void TimeManagement::advance_nodes_time(std::int64_t nodes) { + assert(useNodesTime); + availableNodes += nodes; +} // Called at the beginning of the search and calculates // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) -void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { - +void TimeManagement::init(Search::LimitsType& limits, + Color us, + int ply, + const OptionsMap& options) { // If we have no time, no need to initialize TM, except for the start time, // which is used by movetime. startTime = limits.startTime; if (limits.time[us] == 0) return; - TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); - TimePoint npmsec = TimePoint(Options["nodestime"]); + TimePoint moveOverhead = TimePoint(options["Move Overhead"]); + TimePoint npmsec = TimePoint(options["nodestime"]); // optScale is a percentage of available time to use for the current move. // maxScale is a multiplier applied to optimumTime. @@ -54,6 +71,8 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { // must be much lower than the real engine speed. if (npmsec) { + useNodesTime = true; + if (!availableNodes) // Only once at game start availableNodes = npmsec * limits.time[us]; // Time is in msec @@ -100,7 +119,7 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { maximumTime = TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; - if (Options["Ponder"]) + if (options["Ponder"]) optimumTime += optimumTime / 4; } diff --git a/src/timeman.h b/src/timeman.h index 0509158c3f4..b07712a25c2 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -19,35 +19,41 @@ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED +#include #include #include "misc.h" -#include "search.h" -#include "thread.h" #include "types.h" namespace Stockfish { +class OptionsMap; + +namespace Search { +struct LimitsType; +} + // The TimeManagement class computes the optimal time to think depending on // the maximum available time, the game move number, and other parameters. class TimeManagement { public: - void init(Search::LimitsType& limits, Color us, int ply); - TimePoint optimum() const { return optimumTime; } - TimePoint maximum() const { return maximumTime; } - TimePoint elapsed() const { - return Search::Limits.npmsec ? TimePoint(Threads.nodes_searched()) : now() - startTime; - } + void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options); + + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed(std::size_t nodes) const; - int64_t availableNodes; // When in 'nodes as time' mode + void clear(); + void advance_nodes_time(std::int64_t nodes); private: TimePoint startTime; TimePoint optimumTime; TimePoint maximumTime; -}; -extern TimeManagement Time; + std::int64_t availableNodes = 0; // When in 'nodes as time' mode + bool useNodesTime = false; // True if we are in 'nodes as time' mode +}; } // namespace Stockfish diff --git a/src/tt.cpp b/src/tt.cpp index 2e3f7d32835..f3f58979d1b 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -26,16 +26,13 @@ #include #include "misc.h" -#include "thread.h" -#include "uci.h" namespace Stockfish { -TranspositionTable TT; // Our global transposition table - // Populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. -void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { +void TTEntry::save( + Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) { // Preserve any existing move for the same position if (m || uint16_t(k) != key16) @@ -49,7 +46,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) key16 = uint16_t(k); depth8 = uint8_t(d - DEPTH_OFFSET); - genBound8 = uint8_t(TT.generation8 | uint8_t(pv) << 2 | b); + genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b); value16 = int16_t(v); eval16 = int16_t(ev); } @@ -59,10 +56,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) // Sets the size of the transposition table, // measured in megabytes. Transposition table consists of a power of 2 number // of clusters and each cluster consists of ClusterSize number of TTEntry. -void TranspositionTable::resize(size_t mbSize) { - - Threads.main()->wait_for_search_finished(); - +void TranspositionTable::resize(size_t mbSize, int threadCount) { aligned_large_pages_free(table); clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); @@ -74,28 +68,25 @@ void TranspositionTable::resize(size_t mbSize) { exit(EXIT_FAILURE); } - clear(); + clear(threadCount); } // Initializes the entire transposition table to zero, // in a multi-threaded way. -void TranspositionTable::clear() { - +void TranspositionTable::clear(size_t threadCount) { std::vector threads; - for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx) + for (size_t idx = 0; idx < size_t(threadCount); ++idx) { - threads.emplace_back([this, idx]() { + threads.emplace_back([this, idx, threadCount]() { // Thread binding gives faster search on systems with a first-touch policy - if (Options["Threads"] > 8) + if (threadCount > 8) WinProcGroup::bindThisThread(idx); // Each thread will zero its part of the hash table - const size_t stride = size_t(clusterCount / Options["Threads"]), - start = size_t(stride * idx), - len = - idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start; + const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx), + len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start; std::memset(&table[start], 0, len * sizeof(Cluster)); }); diff --git a/src/tt.h b/src/tt.h index 61e854c1af2..4115ee7ae51 100644 --- a/src/tt.h +++ b/src/tt.h @@ -45,7 +45,7 @@ struct TTEntry { Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } bool is_pv() const { return bool(genBound8 & 0x4); } Bound bound() const { return Bound(genBound8 & 0x3); } - void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); private: friend class TranspositionTable; @@ -88,23 +88,23 @@ class TranspositionTable { void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things TTEntry* probe(const Key key, bool& found) const; int hashfull() const; - void resize(size_t mbSize); - void clear(); + void resize(size_t mbSize, int threadCount); + void clear(size_t threadCount); TTEntry* first_entry(const Key key) const { return &table[mul_hi64(key, clusterCount)].entry[0]; } + uint8_t generation() const { return generation8; } + private: friend struct TTEntry; size_t clusterCount; - Cluster* table; - uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 + Cluster* table = nullptr; + uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 }; -extern TranspositionTable TT; - } // namespace Stockfish #endif // #ifndef TT_H_INCLUDED diff --git a/src/tune.cpp b/src/tune.cpp index 1dddca0c37d..88b3b7912d5 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -24,14 +24,15 @@ #include #include -#include "uci.h" +#include "ucioption.h" using std::string; namespace Stockfish { bool Tune::update_on_last; -const UCI::Option* LastOption = nullptr; +const Option* LastOption = nullptr; +OptionsMap* Tune::options; static std::map TuneResults; string Tune::next(string& names, bool pop) { @@ -53,13 +54,13 @@ string Tune::next(string& names, bool pop) { return name; } -static void on_tune(const UCI::Option& o) { +static void on_tune(const Option& o) { if (!Tune::update_on_last || LastOption == &o) Tune::read_options(); } -static void make_option(const string& n, int v, const SetRange& r) { +static void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { // Do not generate option when there is nothing to tune (ie. min = max) if (r(v).first == r(v).second) @@ -68,8 +69,8 @@ static void make_option(const string& n, int v, const SetRange& r) { if (TuneResults.count(n)) v = TuneResults[n]; - Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); - LastOption = &Options[n]; + (*options)[n] << Option(v, r(v).first, r(v).second, on_tune); + LastOption = &((*options)[n]); // Print formatted parameters, ready to be copy-pasted in Fishtest std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," @@ -79,13 +80,13 @@ static void make_option(const string& n, int v, const SetRange& r) { template<> void Tune::Entry::init_option() { - make_option(name, value, range); + make_option(options, name, value, range); } template<> void Tune::Entry::read_option() { - if (Options.count(name)) - value = int(Options[name]); + if (options->count(name)) + value = int((*options)[name]); } // Instead of a variable here we have a PostUpdate function: just call it diff --git a/src/tune.h b/src/tune.h index 17057001225..b88c085fd4b 100644 --- a/src/tune.h +++ b/src/tune.h @@ -28,6 +28,8 @@ namespace Stockfish { +class OptionsMap; + using Range = std::pair; // Option's min-max values using RangeFun = Range(int); @@ -151,7 +153,8 @@ class Tune { return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis } - static void init() { + static void init(OptionsMap& o) { + options = &o; for (auto& e : instance().list) e->init_option(); read_options(); @@ -160,7 +163,9 @@ class Tune { for (auto& e : instance().list) e->read_option(); } - static bool update_on_last; + + static bool update_on_last; + static OptionsMap* options; }; // Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() diff --git a/src/uci.cpp b/src/uci.cpp index be902277984..82fb25c1d27 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,111 +22,156 @@ #include #include #include -#include #include #include -#include #include #include #include -#include #include #include "benchmark.h" #include "evaluate.h" -#include "misc.h" #include "movegen.h" #include "nnue/evaluate_nnue.h" #include "nnue/nnue_architecture.h" #include "position.h" #include "search.h" -#include "thread.h" +#include "syzygy/tbprobe.h" +#include "types.h" +#include "ucioption.h" namespace Stockfish { -namespace { - -// FEN string for the initial position in standard chess -const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - - -// Called when the engine receives the "position" UCI command. -// It sets up the position that is described in the given FEN string ("fen") or -// the initial position ("startpos") and then makes the moves given in the following -// move list ("moves"). -void position(Position& pos, std::istringstream& is, StateListPtr& states) { - - Move m; - std::string token, fen; - - is >> token; - - if (token == "startpos") - { - fen = StartFEN; - is >> token; // Consume the "moves" token, if any - } - else if (token == "fen") - while (is >> token && token != "moves") - fen += token + " "; - else - return; - - states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one - pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); - - // Parse the move list, if any - while (is >> token && (m = UCI::to_move(pos, token)) != Move::none()) - { - states->emplace_back(); - pos.do_move(m, states->back()); - } +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int NormalizeToPawnValue = 328; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + +UCI::UCI(int argc, char** argv) : + cli(argc, argv) { + + evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}}, + {Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}}; + + + options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); + + options["Threads"] << Option(1, 1, 1024, [this](const Option&) { + threads.set({options, threads, tt}); + }); + + options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { + threads.main_thread()->wait_for_search_finished(); + tt.resize(o, options["Threads"]); + }); + + options["Clear Hash"] << Option(true, [this](const Option&) { search_clear(); }); + options["Ponder"] << Option(false); + options["MultiPV"] << Option(1, 1, 500); + 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(1320, 1320, 3190); + options["UCI_ShowWDL"] << Option(false); + options["SyzygyPath"] << Option("", [](const Option& o) { Tablebases::init(o); }); + options["SyzygyProbeDepth"] << Option(1, 1, 100); + options["Syzygy50MoveRule"] << Option(true); + options["SyzygyProbeLimit"] << Option(7, 0, 7); + options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { + evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + }); + + threads.set({options, threads, tt}); + + search_clear(); // After threads are up } -// Prints the evaluation of the current position, -// consistent with the UCI options set so far. -void trace_eval(Position& pos) { +void UCI::loop() { + Position pos; + std::string token, cmd; StateListPtr states(new std::deque(1)); - Position p; - p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); - Eval::NNUE::verify(); + pos.set(StartFEN, false, &states->back()); - sync_cout << "\n" << Eval::trace(p) << sync_endl; -} + for (int i = 1; i < cli.argc; ++i) + cmd += std::string(cli.argv[i]) + " "; + do + { + if (cli.argc == 1 + && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication + cmd = "quit"; -// Called when the engine receives the "setoption" UCI command. -// The function updates the UCI option ("name") to the given value ("value"). + std::istringstream is(cmd); -void setoption(std::istringstream& is) { + token.clear(); // Avoid a stale if getline() returns nothing or a blank line + is >> std::skipws >> token; - Threads.main()->wait_for_search_finished(); + if (token == "quit" || token == "stop") + threads.stop = true; - std::string token, name, value; + // The GUI sends 'ponderhit' to tell that the user has played the expected move. + // So, 'ponderhit' is sent if pondering was done on the same move that the user + // has played. The search should continue, but should also switch from pondering + // to the normal search. + else if (token == "ponderhit") + threads.main_manager()->ponder = false; // Switch to the normal search - is >> token; // Consume the "name" token + else if (token == "uci") + sync_cout << "id name " << engine_info(true) << "\n" + << options << "\nuciok" << sync_endl; - // Read the option name (can contain spaces) - while (is >> token && token != "value") - name += (name.empty() ? "" : " ") + token; + else if (token == "setoption") + setoption(is); + else if (token == "go") + go(pos, is, states); + else if (token == "position") + position(pos, is, states); + else if (token == "ucinewgame") + search_clear(); + else if (token == "isready") + sync_cout << "readyok" << sync_endl; - // Read the option value (can contain spaces) - while (is >> token) - value += (value.empty() ? "" : " ") + token; + // Add custom non-UCI commands, mainly for debugging purposes. + // These commands must not be used during a search! + else if (token == "flip") + pos.flip(); + else if (token == "bench") + bench(pos, is, states); + else if (token == "d") + sync_cout << pos << sync_endl; + else if (token == "eval") + trace_eval(pos); + else if (token == "compiler") + sync_cout << compiler_info() << sync_endl; + else if (token == "export_net") + { + std::optional filename; + std::string f; + if (is >> std::skipws >> f) + filename = f; + Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles); + } + else if (token == "--help" || token == "help" || token == "--license" || token == "license") + sync_cout + << "\nStockfish is a powerful chess engine for playing and analyzing." + "\nIt is released as free software licensed under the GNU GPLv3 License." + "\nStockfish is normally used with a graphical user interface (GUI) and implements" + "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." + "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" + "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" + << sync_endl; + else if (!token.empty() && token[0] != '#') + sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." + << sync_endl; - if (Options.count(name)) - Options[name] = value; - else - sync_cout << "No such option: " << name << sync_endl; + } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } +void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { -// Called when the engine receives the "go" UCI command. The function sets the -// thinking time and other parameters from the input string then stars with a search - -void go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits; std::string token; @@ -137,7 +182,7 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) { while (is >> token) if (token == "searchmoves") // Needs to be the last command on the line while (is >> token) - limits.searchmoves.push_back(UCI::to_move(pos, token)); + limits.searchmoves.push_back(to_move(pos, token)); else if (token == "wtime") is >> limits.time[WHITE]; @@ -164,16 +209,12 @@ void go(Position& pos, std::istringstream& is, StateListPtr& states) { else if (token == "ponder") ponderMode = true; - Threads.start_thinking(pos, states, limits, ponderMode); -} - - -// Called when the engine receives the "bench" command. -// First, a list of UCI commands is set up according to the bench -// parameters, then it is run one by one, printing a summary at the end. + Eval::NNUE::verify(options, evalFiles); -void bench(Position& pos, std::istream& args, StateListPtr& states) { + threads.start_thinking(options, pos, states, limits, ponderMode); +} +void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { std::string token; uint64_t num, nodes = 0, cnt = 1; @@ -196,8 +237,8 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { if (token == "go") { go(pos, is, states); - Threads.main()->wait_for_search_finished(); - nodes += Threads.nodes_searched(); + threads.main_thread()->wait_for_search_finished(); + nodes += threads.nodes_searched(); } else trace_eval(pos); @@ -208,9 +249,9 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { position(pos, is, states); else if (token == "ucinewgame") { - Search::clear(); + search_clear(); // Search::clear() may take a while elapsed = now(); - } // Search::clear() may take a while + } } elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' @@ -222,141 +263,66 @@ void bench(Position& pos, std::istream& args, StateListPtr& states) { << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; } -// The win rate model returns the probability of winning (in per mille units) given an -// eval and a game ply. It fits the LTC fishtest statistics rather accurately. -int win_rate_model(Value v, int ply) { - - // The model only captures up to 240 plies, so limit the input and then rescale - double m = std::min(240, ply) / 64.0; - - // The coefficients of a third-order polynomial fit is based on the fishtest data - // for two parameters that need to transform eval to the argument of a logistic - // function. - constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407}; - constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330}; - - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 - static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); +void UCI::trace_eval(Position& pos) { + StateListPtr states(new std::deque(1)); + Position p; + p.set(pos.fen(), options["UCI_Chess960"], &states->back()); - double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; - double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + Eval::NNUE::verify(options, evalFiles); - // Transform the eval to centipawns with limited range - double x = std::clamp(double(v), -4000.0, 4000.0); - - // Return the win rate in per mille units, rounded to the nearest integer - return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); + sync_cout << "\n" << Eval::trace(p, *threads.main_thread()->worker.get()) << sync_endl; } -} // namespace +void UCI::search_clear() { + threads.main_thread()->wait_for_search_finished(); + tt.clear(options["Threads"]); + threads.clear(); + Tablebases::init(options["SyzygyPath"]); // Free mapped files +} -// Waits for a command from the stdin, parses it, and then calls the appropriate -// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a -// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, -// like running 'bench', the function returns immediately after the command is executed. -// In addition to the UCI ones, some additional debug commands are also supported. -void UCI::loop(int argc, char* argv[]) { - - Position pos; - std::string token, cmd; - StateListPtr states(new std::deque(1)); +void UCI::setoption(std::istringstream& is) { + threads.main_thread()->wait_for_search_finished(); + options.setoption(is); +} - pos.set(StartFEN, false, &states->back(), Threads.main()); +void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) { + Move m; + std::string token, fen; - for (int i = 1; i < argc; ++i) - cmd += std::string(argv[i]) + " "; + is >> token; - do + if (token == "startpos") { - if (argc == 1 - && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication - cmd = "quit"; - - std::istringstream is(cmd); - - token.clear(); // Avoid a stale if getline() returns nothing or a blank line - is >> std::skipws >> token; - - if (token == "quit" || token == "stop") - Threads.stop = true; - - // The GUI sends 'ponderhit' to tell that the user has played the expected move. - // So, 'ponderhit' is sent if pondering was done on the same move that the user - // has played. The search should continue, but should also switch from pondering - // to the normal search. - else if (token == "ponderhit") - Threads.main()->ponder = false; // Switch to the normal search - - else if (token == "uci") - sync_cout << "id name " << engine_info(true) << "\n" - << Options << "\nuciok" << sync_endl; - - else if (token == "setoption") - setoption(is); - else if (token == "go") - go(pos, is, states); - else if (token == "position") - position(pos, is, states); - else if (token == "ucinewgame") - Search::clear(); - else if (token == "isready") - sync_cout << "readyok" << sync_endl; + fen = StartFEN; + is >> token; // Consume the "moves" token, if any + } + else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; - // Add custom non-UCI commands, mainly for debugging purposes. - // These commands must not be used during a search! - else if (token == "flip") - pos.flip(); - else if (token == "bench") - bench(pos, is, states); - else if (token == "d") - sync_cout << pos << sync_endl; - else if (token == "eval") - trace_eval(pos); - else if (token == "compiler") - sync_cout << compiler_info() << sync_endl; - else if (token == "export_net") - { - std::optional filename; - std::string f; - if (is >> std::skipws >> f) - filename = f; - Eval::NNUE::save_eval(filename, Eval::NNUE::Big); - } - else if (token == "--help" || token == "help" || token == "--license" || token == "license") - sync_cout - << "\nStockfish is a powerful chess engine for playing and analyzing." - "\nIt is released as free software licensed under the GNU GPLv3 License." - "\nStockfish is normally used with a graphical user interface (GUI) and implements" - "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc." - "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme" - "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" - << sync_endl; - else if (!token.empty() && token[0] != '#') - sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." - << sync_endl; + states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one + pos.set(fen, options["UCI_Chess960"], &states->back()); - } while (token != "quit" && argc == 1); // The command-line arguments are one-shot + // Parse the move list, if any + while (is >> token && (m = to_move(pos, token)) != Move::none()) + { + states->emplace_back(); + pos.do_move(m, states->back()); + } } +int UCI::to_cp(Value v) { return 100 * v / NormalizeToPawnValue; } -// Turns a Value to an integer centipawn number, -// without treatment of mate and similar special scores. -int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; } - -// Converts a Value to a string by adhering to the UCI protocol specification: -// -// cp The score from the engine's point of view in centipawns. -// mate Mate in 'y' moves (not plies). If the engine is getting mated, -// uses negative values for 'y'. std::string UCI::value(Value v) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); std::stringstream ss; if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << UCI::to_cp(v); + ss << "cp " << to_cp(v); else if (std::abs(v) <= VALUE_TB) { const int ply = VALUE_TB - std::abs(v); // recompute ss->ply @@ -368,34 +334,11 @@ std::string UCI::value(Value v) { return ss.str(); } - -// Reports the win-draw-loss (WDL) statistics given an evaluation -// and a game ply based on the data gathered for fishtest LTC games. -std::string UCI::wdl(Value v, int ply) { - - std::stringstream ss; - - int wdl_w = win_rate_model(v, ply); - int wdl_l = win_rate_model(-v, ply); - int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; - - return ss.str(); -} - - -// Converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } - -// Converts a Move to a string in coordinate notation (g1f3, a7a8q). -// The only special case is castling where the e1g1 notation is printed in -// standard chess mode and in e1h1 notation it is printed in Chess960 mode. -// Internally, all castling moves are always encoded as 'king captures rook'. std::string UCI::move(Move m, bool chess960) { - if (m == Move::none()) return "(none)"; @@ -408,7 +351,7 @@ std::string UCI::move(Move m, bool chess960) { if (m.type_of() == CASTLING && !chess960) to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); - std::string move = UCI::square(from) + UCI::square(to); + std::string move = square(from) + square(to); if (m.type_of() == PROMOTION) move += " pnbrqk"[m.promotion_type()]; @@ -416,16 +359,108 @@ std::string UCI::move(Move m, bool chess960) { return move; } +std::string UCI::pv(const Search::Worker& workerThread, + TimePoint elapsed, + uint64_t nodesSearched, + uint64_t tb_hits, + int hashfull, + bool rootInTB) { + std::stringstream ss; + TimePoint time = elapsed + 1; + const auto& rootMoves = workerThread.rootMoves; + const auto& depth = workerThread.completedDepth; + const auto& pos = workerThread.rootPos; + size_t pvIdx = workerThread.pvIdx; + size_t multiPV = std::min(size_t(workerThread.options["MultiPV"]), rootMoves.size()); + uint64_t tbHits = tb_hits + (rootInTB ? rootMoves.size() : 0); -// Converts a string representing a move in coordinate notation -// (g1f3, a7a8q) to the corresponding legal Move, if any. -Move UCI::to_move(const Position& pos, std::string& str) { + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = rootInTB && std::abs(v) <= VALUE_TB; + v = tb ? rootMoves[i].tbScore : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info" + << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 + << " score " << value(v); + + if (workerThread.options["UCI_ShowWDL"]) + ss << wdl(v, pos.game_ply()); + + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound + ? " lowerbound" + : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + + ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / time << " hashfull " + << hashfull << " tbhits " << tbHits << " time " << time << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << move(m, pos.is_chess960()); + } + + return ss.str(); +} + +namespace { +// The win rate model returns the probability of winning (in per mille units) given an +// eval and a game ply. It fits the LTC fishtest statistics rather accurately. +int win_rate_model(Value v, int ply) { + + // The model only captures up to 240 plies, so limit the input and then rescale + double m = std::min(240, ply) / 64.0; + + // The coefficients of a third-order polynomial fit is based on the fishtest data + // for two parameters that need to transform eval to the argument of a logistic + // function. + constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407}; + constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330}; + + // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 + static_assert(NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); + + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + + // Transform the eval to centipawns with limited range + double x = std::clamp(double(v), -4000.0, 4000.0); + + // Return the win rate in per mille units, rounded to the nearest integer + return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); +} +} + +std::string UCI::wdl(Value v, int ply) { + std::stringstream ss; + + int wdl_w = win_rate_model(v, ply); + int wdl_l = win_rate_model(-v, ply); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + +Move UCI::to_move(const Position& pos, std::string& str) { if (str.length() == 5) str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) + if (str == move(m, pos.is_chess960())) return m; return Move::none(); diff --git a/src/uci.h b/src/uci.h index d249da7442b..cd113b1ad29 100644 --- a/src/uci.h +++ b/src/uci.h @@ -19,77 +19,70 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED -#include -#include -#include +#include +#include #include +#include -#include "types.h" +#include "evaluate.h" +#include "misc.h" +#include "position.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" namespace Stockfish { -class Position; +namespace Eval::NNUE { +enum NetSize : int; +} -namespace UCI { +namespace Search { +class Worker; +} -// Normalizes the internal value as reported by evaluate or search -// to the UCI centipawn result used in output. This value is derived from -// the win_rate_model() such that Stockfish outputs an advantage of -// "100 centipawns" for a position if the engine has a 50% probability to win -// from this position in self-play at fishtest LTC time control. -const int NormalizeToPawnValue = 328; +class Move; +enum Square : int; +using Value = int; -class Option; +class UCI { + public: + UCI(int argc, char** argv); -// Define a custom comparator, because the UCI options should be case-insensitive -struct CaseInsensitiveLess { - bool operator()(const std::string&, const std::string&) const; -}; + void loop(); -// The options container is defined as a std::map -using OptionsMap = std::map; + static int to_cp(Value v); + static std::string value(Value v); + static std::string square(Square s); + static std::string move(Move m, bool chess960); + static std::string pv(const Search::Worker& workerThread, + TimePoint elapsed, + uint64_t nodesSearched, + uint64_t tb_hits, + int hashfull, + bool rootInTB); + static std::string wdl(Value v, int ply); + static Move to_move(const Position& pos, std::string& str); -// The Option class implements each option as specified by the UCI protocol -class Option { + const std::string& workingDirectory() const { return cli.workingDirectory; } - using OnChange = void (*)(const Option&); + OptionsMap options; - public: - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char* v, OnChange = nullptr); - Option(double v, int minv, int maxv, OnChange = nullptr); - Option(const char* v, const char* cur, OnChange = nullptr); - - Option& operator=(const std::string&); - void operator<<(const Option&); - operator int() const; - operator std::string() const; - bool operator==(const char*) const; + std::unordered_map evalFiles; private: - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); - - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; + TranspositionTable tt; + ThreadPool threads; + CommandLine cli; + + void go(Position& pos, std::istringstream& is, StateListPtr& states); + void bench(Position& pos, std::istream& args, StateListPtr& states); + void position(Position& pos, std::istringstream& is, StateListPtr& states); + void trace_eval(Position& pos); + void search_clear(); + void setoption(std::istringstream& is); }; -void init(OptionsMap&); -void loop(int argc, char* argv[]); -int to_cp(Value v); -std::string value(Value v); -std::string square(Square s); -std::string move(Move m, bool chess960); -std::string pv(const Position& pos, Depth depth); -std::string wdl(Value v, int ply); -Move to_move(const Position& pos, std::string& str); - -} // namespace UCI - -extern UCI::OptionsMap Options; - } // namespace Stockfish #endif // #ifndef UCI_H_INCLUDED diff --git a/src/ucioption.cpp b/src/ucioption.cpp index f8cbcc53077..c7de7e3f7f2 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -16,104 +16,53 @@ along with this program. If not, see . */ +#include "ucioption.h" + #include #include #include -#include -#include -#include -#include -#include +#include #include -#include +#include -#include "evaluate.h" #include "misc.h" -#include "search.h" -#include "syzygy/tbprobe.h" -#include "thread.h" -#include "tt.h" -#include "types.h" -#include "uci.h" - -using std::string; namespace Stockfish { -UCI::OptionsMap Options; // Global object - -namespace UCI { +bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const { -// 'On change' actions, triggered by an option's value change -static void on_clear_hash(const Option&) { Search::clear(); } -static void on_hash_size(const Option& o) { TT.resize(size_t(o)); } -static void on_logger(const Option& o) { start_logger(o); } -static void on_threads(const Option& o) { Threads.set(size_t(o)); } -static void on_tb_path(const Option& o) { Tablebases::init(o); } -static void on_eval_file(const Option&) { Eval::NNUE::init(); } - -// Our case insensitive less() function as required by UCI protocol -bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const { - - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return tolower(c1) < tolower(c2); }); + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); } +void OptionsMap::setoption(std::istringstream& is) { + std::string token, name, value; -// Initializes the UCI options to their hard-coded default values -void init(OptionsMap& o) { - - constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; - - o["Debug Log File"] << Option("", on_logger); - o["Threads"] << Option(1, 1, 1024, on_threads); - o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); - o["Clear Hash"] << Option(on_clear_hash); - o["Ponder"] << Option(false); - o["MultiPV"] << Option(1, 1, MAX_MOVES); - o["Skill Level"] << Option(20, 0, 20); - o["Move Overhead"] << Option(10, 0, 5000); - o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); - o["UCI_LimitStrength"] << Option(false); - o["UCI_Elo"] << Option(1320, 1320, 3190); - o["UCI_ShowWDL"] << Option(false); - o["SyzygyPath"] << Option("", on_tb_path); - o["SyzygyProbeDepth"] << Option(1, 1, 100); - o["Syzygy50MoveRule"] << Option(true); - o["SyzygyProbeLimit"] << Option(7, 0, 7); - o["EvalFile"] << Option(EvalFileDefaultNameBig, on_eval_file); - // Enable this after fishtest workers support EvalFileSmall - // o["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, on_eval_file); -} + is >> token; // Consume the "name" token + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; -// Used to print all the options default values in chronological -// insertion order (the idx field) and in the format defined by the UCI protocol. -std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; - for (size_t idx = 0; idx < om.size(); ++idx) - for (const auto& it : om) - if (it.second.idx == idx) - { - const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; - - if (o.type == "string" || o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; - - if (o.type == "spin") - os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " - << o.max; - - break; - } + if (options_map.count(name)) + options_map[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; +} - return os; +Option OptionsMap::operator[](const std::string& name) const { + auto it = options_map.find(name); + return it != options_map.end() ? it->second : Option(); } +Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; } -// Option class constructors and conversion operators +std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } Option::Option(const char* v, OnChange f) : type("string"), @@ -184,19 +133,19 @@ void Option::operator<<(const Option& o) { // 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. -Option& Option::operator=(const string& v) { +Option& Option::operator=(const std::string& v) { assert(!type.empty()); if ((type != "button" && type != "string" && v.empty()) || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (stof(v) < min || stof(v) > max))) + || (type == "spin" && (std::stof(v) < min || std::stof(v) > max))) return *this; if (type == "combo") { OptionsMap comboMap; // To have case insensitive compare - string token; + std::string token; std::istringstream ss(defaultValue); while (ss >> token) comboMap[token] << Option(); @@ -213,6 +162,24 @@ Option& Option::operator=(const string& v) { return *this; } -} // namespace UCI +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto& it : om.options_map) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; + + if (o.type == "string" || o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; + + if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + << o.max; + + break; + } -} // namespace Stockfish + return os; +} +} diff --git a/src/ucioption.h b/src/ucioption.h new file mode 100644 index 00000000000..b575d1646e6 --- /dev/null +++ b/src/ucioption.h @@ -0,0 +1,81 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCIOPTION_H_INCLUDED +#define UCIOPTION_H_INCLUDED + +#include +#include +#include +#include +#include + +namespace Stockfish { +// Define a custom comparator, because the UCI options should be case-insensitive +struct CaseInsensitiveLess { + bool operator()(const std::string&, const std::string&) const; +}; + +class Option; + +class OptionsMap { + public: + void setoption(std::istringstream&); + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + Option operator[](const std::string&) const; + Option& operator[](const std::string&); + + std::size_t count(const std::string&) const; + + private: + // The options container is defined as a std::map + using OptionsStore = std::map; + + OptionsStore options_map; +}; + +// The Option class implements each option as specified by the UCI protocol +class Option { + public: + using OnChange = std::function; + + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); + + Option& operator=(const std::string&); + void operator<<(const Option&); + operator int() const; + operator std::string() const; + bool operator==(const char*) const; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + private: + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; +}; + +} +#endif // #ifndef UCIOPTION_H_INCLUDED From 3372ee9c2671dedcbf70ad6a744ef0825eaaba94 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 11 Jan 2024 13:25:01 +0300 Subject: [PATCH 0345/1309] Remove threatenedByPawn term for queen threats Passed STC: https://tests.stockfishchess.org/tests/view/659d614c79aa8af82b9677d0 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 151776 W: 38690 L: 38597 D: 74489 Ptnml(0-2): 522, 17841, 39015, 18042, 468 Passed LTC: https://tests.stockfishchess.org/tests/view/659d94d379aa8af82b967cb2 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 91908 W: 23075 L: 22924 D: 45909 Ptnml(0-2): 70, 10311, 25037, 10470, 66 closes https://github.com/official-stockfish/Stockfish/pull/4977 Bench: 1266493 --- src/movepick.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6a5629961e3..c6532520d63 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -198,15 +198,15 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= !(threatenedPieces & from) - ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 - + bool(to & threatenedByMinor) * 10000 - + bool(to & threatenedByPawn) * 20000 - : pt == ROOK ? bool(to & threatenedByMinor) * 25000 - + bool(to & threatenedByPawn) * 10000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 - : 0) - : 0; + m.value -= + !(threatenedPieces & from) + ? (pt == QUEEN + ? bool(to & threatenedByRook) * 50000 + bool(to & threatenedByMinor) * 10000 + : pt == ROOK + ? bool(to & threatenedByMinor) * 25000 + bool(to & threatenedByPawn) * 10000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0) + : 0; } else // Type == EVASIONS From eec361f64c7d28ff3a5add64bddc5a96800f7fba Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 8 Jan 2024 01:03:38 -0800 Subject: [PATCH 0346/1309] Simplify bad quiets The main difference is that instead of returning the first bad quiet as a good one we fall through. This is actually more correct and simpler to implement. Non regression STC: https://tests.stockfishchess.org/tests/view/659bbb3479aa8af82b964ec7 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 150944 W: 38399 L: 38305 D: 74240 Ptnml(0-2): 485, 18042, 38298, 18188, 459 Non regression LTC: https://tests.stockfishchess.org/tests/view/659c6e6279aa8af82b9660eb LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 192060 W: 47871 L: 47823 D: 96366 Ptnml(0-2): 144, 21912, 51845, 22010, 119 The cutoff is now -8K instead of -7.5K. -7.5K failed. https://tests.stockfishchess.org/tests/view/659a1f4b79aa8af82b962a0e This was likely a false negative. closes https://github.com/official-stockfish/Stockfish/pull/4975 Bench: 1308279 --- src/movepick.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index c6532520d63..e521689ef73 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -312,19 +312,11 @@ Move MovePicker::next_move(bool skipQuiets) { return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) { - Move tmp = *(cur - 1); - if ((cur - 1)->value < -7500 && (cur - 1)->value > quiet_threshold(depth)) - { - // Remaining quiets are bad - beginBadQuiets = cur; - - // Prepare the pointers to loop over the bad captures - cur = moves; - endMoves = endBadCaptures; - - ++stage; - } - return tmp; + if ((cur - 1)->value > -8000 || (cur - 1)->value <= quiet_threshold(depth)) + return *(cur - 1); + + // Remaining quiets are bad + beginBadQuiets = cur - 1; } // Prepare the pointers to loop over the bad captures From cf5b070913755e2aac2a6e827e7abaa91c0b1d35 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 00:30:03 +0100 Subject: [PATCH 0347/1309] Remove unused method init() is no longer used, and was previously replaced by the clear function. fixes https://github.com/official-stockfish/Stockfish/issues/4981 No functional change --- src/search.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.h b/src/search.h index 48a5630cddb..90ed82b923c 100644 --- a/src/search.h +++ b/src/search.h @@ -48,8 +48,6 @@ class UCI; namespace Search { -// Called at startup to initialize various lookup tables, after program startup -void init(int); // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has @@ -176,6 +174,7 @@ class Worker { public: Worker(SharedState&, std::unique_ptr, size_t); + // Called at instantiation to initialize Reductions tables // Reset histories, usually before a new game void clear(); From 12e97701b299a2abfaa86d67b670411a39e5ccce Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 10:39:57 +0100 Subject: [PATCH 0348/1309] Fix UCI options Fixes the type for 'Clear Hash' and uses MAX_MOVES for 'MultiPV' as we had before. No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 82fb25c1d27..aa493c63487 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -64,9 +64,9 @@ UCI::UCI(int argc, char** argv) : tt.resize(o, options["Threads"]); }); - options["Clear Hash"] << Option(true, [this](const Option&) { search_clear(); }); + options["Clear Hash"] << Option([this](const Option&) { search_clear(); }); options["Ponder"] << Option(false); - options["MultiPV"] << Option(1, 1, 500); + 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); From 88331add0d1220068f1bc1c0e1db88598425dafc Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 13 Jan 2024 20:19:33 +0100 Subject: [PATCH 0349/1309] Remove the dependency on a Worker from evaluate Also remove dead code, `rootSimpleEval` is no longer used since the introduction of dual net. `iterBestValue` is also no longer used in evaluate and can be reduced to a local variable. closes https://github.com/official-stockfish/Stockfish/pull/4979 No functional change --- src/evaluate.cpp | 15 +++------------ src/evaluate.h | 8 ++------ src/search.cpp | 38 ++++++++++++++++++++------------------ src/search.h | 6 ++---- src/thread.cpp | 4 +--- src/uci.cpp | 2 +- 6 files changed, 29 insertions(+), 44 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3e067e4c447..45658798c52 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -35,7 +35,6 @@ #include "nnue/evaluate_nnue.h" #include "nnue/nnue_architecture.h" #include "position.h" -#include "search.h" #include "types.h" #include "uci.h" #include "ucioption.h" @@ -196,7 +195,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { +Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); @@ -217,8 +216,6 @@ Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) : NNUE::evaluate(pos, true, &nnueComplexity); - int optimism = workerThread.optimism[stm]; - // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; @@ -240,17 +237,11 @@ Value Eval::evaluate(const Position& pos, const Search::Worker& workerThread) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos, Search::Worker& workerThread) { +std::string Eval::trace(Position& pos) { if (pos.checkers()) return "Final evaluation: none (in check)"; - // Reset any global variable used in eval - workerThread.iterBestValue = VALUE_ZERO; - workerThread.rootSimpleEval = VALUE_ZERO; - workerThread.optimism[WHITE] = VALUE_ZERO; - workerThread.optimism[BLACK] = VALUE_ZERO; - std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); ss << '\n' << NNUE::trace(pos) << '\n'; @@ -262,7 +253,7 @@ std::string Eval::trace(Position& pos, Search::Worker& workerThread) { v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos, workerThread); + v = evaluate(pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index 8a9d6fc7c87..729baa6bcb8 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,16 +29,12 @@ namespace Stockfish { class Position; class OptionsMap; -namespace Search { -class Worker; -} - namespace Eval { -std::string trace(Position& pos, Search::Worker& workerThread); +std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos, const Search::Worker& workerThread); +Value evaluate(const Position& pos, int optimism); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/search.cpp b/src/search.cpp index 5530d125f15..b23740d831b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -280,7 +280,7 @@ void Search::Worker::iterative_deepening() { ss->pv = pv; - iterBestValue = -VALUE_INFINITE; + Value bestValue = -VALUE_INFINITE; if (mainThread) { @@ -357,7 +357,7 @@ void Search::Worker::iterative_deepening() { // for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - iterBestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); + bestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -375,7 +375,7 @@ void Search::Worker::iterative_deepening() { // When failing high/low give some update (without cluttering // the UI) before a re-search. - if (mainThread && multiPV == 1 && (iterBestValue <= alpha || iterBestValue >= beta) + if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), @@ -384,18 +384,18 @@ void Search::Worker::iterative_deepening() { // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. - if (iterBestValue <= alpha) + if (bestValue <= alpha) { beta = (alpha + beta) / 2; - alpha = std::max(iterBestValue - delta, -VALUE_INFINITE); + alpha = std::max(bestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; if (mainThread) mainThread->stopOnPonderhit = false; } - else if (iterBestValue >= beta) + else if (bestValue >= beta) { - beta = std::min(iterBestValue + delta, int(VALUE_INFINITE)); + beta = std::min(bestValue + delta, int(VALUE_INFINITE)); ++failedHighCnt; } else @@ -428,8 +428,8 @@ void Search::Worker::iterative_deepening() { } // Have we found a "mate in x"? - if (limits.mate && iterBestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - iterBestValue <= 2 * limits.mate) + if (limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * limits.mate) threads.stop = true; if (!mainThread) @@ -449,8 +449,8 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - iterBestValue) - + 6 * (mainThread->iterValue[iterIdx] - iterBestValue)) + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 616.6; fallingEval = std::clamp(fallingEval, 0.51, 1.51); @@ -483,7 +483,7 @@ void Search::Worker::iterative_deepening() { threads.increaseDepth = true; } - mainThread->iterValue[iterIdx] = iterBestValue; + mainThread->iterValue[iterIdx] = bestValue; iterIdx = (iterIdx + 1) & 3; } @@ -580,7 +580,7 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score @@ -734,7 +734,7 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); @@ -752,7 +752,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, *thisThread); + unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); Value newEval = ss->staticEval @@ -1470,7 +1470,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, *thisThread) : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) + : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1501,7 +1502,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // Never assume anything about values stored in TT if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos, *thisThread); + unadjustedStaticEval = ss->staticEval = bestValue = + evaluate(pos, thisThread->optimism[us]); Value newEval = ss->staticEval @@ -1521,7 +1523,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != Move::null() ? evaluate(pos, *thisThread) + (ss - 1)->currentMove != Move::null() ? evaluate(pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; Value newEval = diff --git a/src/search.h b/src/search.h index 90ed82b923c..68c9f2aa7e0 100644 --- a/src/search.h +++ b/src/search.h @@ -184,10 +184,6 @@ class Worker { bool is_mainthread() const { return thread_idx == 0; } - // Public because evaluate uses this - Value iterBestValue, optimism[COLOR_NB]; - Value rootSimpleEval; - // Public because they need to be updatable by the stats CounterMoveHistory counterMoves; ButterflyHistory mainHistory; @@ -226,6 +222,8 @@ class Worker { std::atomic nodes, tbHits, bestMoveChanges; int selDepth, nmpMinPly; + Value optimism[COLOR_NB]; + Position rootPos; StateInfo rootState; RootMoves rootMoves; diff --git a/src/thread.cpp b/src/thread.cpp index a512c0a52b8..9dc4446f1cc 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -27,7 +27,6 @@ #include #include -#include "evaluate.h" #include "misc.h" #include "movegen.h" #include "search.h" @@ -205,8 +204,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootDepth = th->worker->completedDepth = 0; th->worker->rootMoves = rootMoves; th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); - th->worker->rootState = setupStates->back(); - th->worker->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move()); + th->worker->rootState = setupStates->back(); } main_thread()->start_searching(); diff --git a/src/uci.cpp b/src/uci.cpp index aa493c63487..789f345481d 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -270,7 +270,7 @@ void UCI::trace_eval(Position& pos) { Eval::NNUE::verify(options, evalFiles); - sync_cout << "\n" << Eval::trace(p, *threads.main_thread()->worker.get()) << sync_endl; + sync_cout << "\n" << Eval::trace(p) << sync_endl; } void UCI::search_clear() { From b5e8169a85f6937d7d9d90612863fe5eec72d6ca Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 13 Jan 2024 21:35:02 +0100 Subject: [PATCH 0350/1309] Add ignoreRevsFile to CONTRIBUTING.md closes https://github.com/official-stockfish/Stockfish/pull/4980 No functional change --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e72e1db6bc..e589b4fcc82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -61,6 +61,16 @@ Changes to Stockfish C++ code should respect our coding style defined by [.clang-format](.clang-format). You can format your changes by running `make format`. This requires clang-format version 17 to be installed on your system. +## Navigate + +For experienced Git users who frequently use git blame, it is recommended to +configure the blame.ignoreRevsFile setting. +This setting is useful for excluding noisy formatting commits. + +```bash +git config blame.ignoreRevsFile .git-blame-ignore-revs +``` + ## Community and Communication - Join the [Stockfish discord][discord-link] to discuss ideas, issues, and From 32e46fc47f521d2b93f96c63267fc0bda26332dc Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 8 Jan 2024 23:20:23 -0800 Subject: [PATCH 0351/1309] Remove some outdated SIMD functions Since https://github.com/official-stockfish/Stockfish/pull/4391 the x2 SIMD functions no longer serve any useful purpose. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/659cf42579aa8af82b966d55 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67392 W: 17222 L: 17037 D: 33133 Ptnml(0-2): 207, 7668, 17762, 7851, 208 closes https://github.com/official-stockfish/Stockfish/pull/4974 No functional change --- src/nnue/layers/affine_transform.h | 19 +++-------- src/nnue/layers/simd.h | 54 ------------------------------ 2 files changed, 5 insertions(+), 68 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index e6852236108..ad9167c05c4 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -200,21 +200,18 @@ class AffineTransform { #define vec_setzero _mm512_setzero_si512 #define vec_set_32 _mm512_set1_epi32 #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2 #define vec_hadd Simd::m512_hadd #elif defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 #define vec_hadd Simd::m128_hadd #endif @@ -231,16 +228,14 @@ class AffineTransform { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasvec[k]; - for (IndexType i = 0; i < NumChunks; i += 2) + for (IndexType i = 0; i < NumChunks; ++i) { - const vec_t in0 = vec_set_32(input32[i + 0]); - const vec_t in1 = vec_set_32(input32[i + 1]); + const vec_t in0 = vec_set_32(input32[i]); const auto col0 = - reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); - const auto col1 = - reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); + reinterpret_cast(&weights[i * OutputDimensions * 4]); + for (IndexType k = 0; k < NumRegs; ++k) - vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]); + vec_add_dpbusd_32(acc[k], in0, col0[k]); } vec_t* outptr = reinterpret_cast(output); @@ -250,7 +245,6 @@ class AffineTransform { #undef vec_setzero #undef vec_set_32 #undef vec_add_dpbusd_32 - #undef vec_add_dpbusd_32x2 #undef vec_hadd } else if constexpr (OutputDimensions == 1) @@ -263,14 +257,12 @@ class AffineTransform { #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2 #define vec_hadd Simd::m128_hadd #endif @@ -294,7 +286,6 @@ class AffineTransform { #undef vec_setzero #undef vec_set_32 #undef vec_add_dpbusd_32 - #undef vec_add_dpbusd_32x2 #undef vec_hadd } #else diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 6f4c9d206ed..cec41474bb0 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -87,21 +87,6 @@ m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum #endif } -[[maybe_unused]] static void -m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1) { - - #if defined(USE_VNNI) - acc = _mm512_dpbusd_epi32(acc, a0, b0); - acc = _mm512_dpbusd_epi32(acc, a1, b1); - #else - __m512i product0 = _mm512_maddubs_epi16(a0, b0); - __m512i product1 = _mm512_maddubs_epi16(a1, b1); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1)); - #endif -} - #endif #if defined(USE_AVX2) @@ -124,21 +109,6 @@ m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512 #endif } -[[maybe_unused]] static void -m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1) { - - #if defined(USE_VNNI) - acc = _mm256_dpbusd_epi32(acc, a0, b0); - acc = _mm256_dpbusd_epi32(acc, a1, b1); - #else - __m256i product0 = _mm256_maddubs_epi16(a0, b0); - __m256i product1 = _mm256_maddubs_epi16(a1, b1); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1)); - #endif -} - #endif #if defined(USE_SSSE3) @@ -156,27 +126,10 @@ m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256 acc = _mm_add_epi32(acc, product0); } -[[maybe_unused]] static void -m128_add_dpbusd_epi32x2(__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1) { - - __m128i product0 = _mm_maddubs_epi16(a0, b0); - __m128i product1 = _mm_maddubs_epi16(a1, b1); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1)); -} - #endif #if defined(USE_NEON_DOTPROD) -[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2( - int32x4_t& acc, int8x16_t a0, int8x16_t b0, int8x16_t a1, int8x16_t b1) { - - acc = vdotq_s32(acc, a0, b0); - acc = vdotq_s32(acc, a1, b1); -} - [[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { @@ -198,13 +151,6 @@ dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { return neon_m128_reduce_add_epi32(sum) + bias; } -[[maybe_unused]] static void -neon_m128_add_dpbusd_epi32x2(int32x4_t& acc, int8x8_t a0, int8x8_t b0, int8x8_t a1, int8x8_t b1) { - - int16x8_t product = vmull_s8(a0, b0); - product = vmlal_s8(product, a1, b1); - acc = vpadalq_s16(acc, product); -} #endif #if USE_NEON >= 8 From a5675f19d87a15a5e599a108dfaa2d08ac50d6a5 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 14:29:12 +0100 Subject: [PATCH 0352/1309] Remove global TB variables from search.cpp Follow up cleanup of #4968, removes the global variables from search and instead uses a dedicated tb config struct. closes https://github.com/official-stockfish/Stockfish/pull/4982 No functional change --- src/bitboard.cpp | 6 ++-- src/search.cpp | 73 +++++------------------------------------- src/search.h | 4 ++- src/syzygy/tbprobe.cpp | 59 +++++++++++++++++++++++++++++++++- src/syzygy/tbprobe.h | 18 +++++++++-- src/thread.cpp | 4 +-- 6 files changed, 89 insertions(+), 75 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 72afabb6554..32c626d4773 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -44,15 +44,13 @@ Bitboard BishopTable[0x1480]; // To store bishop attacks void init_magics(PieceType pt, Bitboard table[], Magic magics[]); -} - // Returns the bitboard of target square for the given step // from the given square. If the step is off the board, returns empty bitboard. -inline Bitboard safe_destination(Square s, int step) { +Bitboard safe_destination(Square s, int step) { Square to = Square(s + step); return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); } - +} // Returns an ASCII representation of a bitboard suitable // to be printed to standard output. Useful for debugging. diff --git a/src/search.cpp b/src/search.cpp index b23740d831b..8901dfd1c1e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -29,7 +29,6 @@ #include #include -#include "bitboard.h" #include "evaluate.h" #include "misc.h" #include "movegen.h" @@ -46,14 +45,6 @@ namespace Stockfish { -namespace Tablebases { - -int Cardinality; -bool RootInTB; -bool UseRule50; -Depth ProbeDepth; -} - namespace TB = Tablebases; using Eval::evaluate; @@ -237,7 +228,7 @@ void Search::Worker::start_searching() { if (bestThread != this) sync_cout << UCI::pv(*bestThread, main_manager()->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - TB::RootInTB) + tbConfig.rootInTB) << sync_endl; sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); @@ -379,7 +370,7 @@ void Search::Worker::iterative_deepening() { && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - TB::RootInTB) + tbConfig.rootInTB) << sync_endl; // In case of failing low/high increase aspiration window and @@ -414,7 +405,7 @@ void Search::Worker::iterative_deepening() { || mainThread->tm.elapsed(threads.nodes_searched()) > 3000)) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - TB::RootInTB) + tbConfig.rootInTB) << sync_endl; } @@ -659,13 +650,13 @@ Value Search::Worker::search( } // Step 5. Tablebases probe - if (!rootNode && !excludedMove && TB::Cardinality) + if (!rootNode && !excludedMove && tbConfig.cardinality) { int piecesCount = pos.count(); - if (piecesCount <= TB::Cardinality - && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0 - && !pos.can_castle(ANY_CASTLING)) + if (piecesCount <= tbConfig.cardinality + && (piecesCount < tbConfig.cardinality || depth >= tbConfig.probeDepth) + && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) { TB::ProbeState err; TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); @@ -678,7 +669,7 @@ Value Search::Worker::search( { thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); - int drawScore = TB::UseRule50 ? 1 : 0; + int drawScore = tbConfig.useRule50 ? 1 : 0; Value tbValue = VALUE_TB - ss->ply; @@ -1962,53 +1953,5 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po return pv.size() > 1; } -void Tablebases::rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves) { - - RootInTB = false; - UseRule50 = bool(options["Syzygy50MoveRule"]); - ProbeDepth = int(options["SyzygyProbeDepth"]); - Cardinality = int(options["SyzygyProbeLimit"]); - bool dtz_available = true; - - // Tables with fewer pieces than SyzygyProbeLimit are searched with - // ProbeDepth == DEPTH_ZERO - if (Cardinality > MaxCardinality) - { - Cardinality = MaxCardinality; - ProbeDepth = 0; - } - - if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) - { - // Rank moves using DTZ tables - RootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); - - if (!RootInTB) - { - // DTZ tables are missing; try to rank moves using WDL tables - dtz_available = false; - RootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); - } - } - - if (RootInTB) - { - // Sort moves according to TB rank - std::stable_sort(rootMoves.begin(), rootMoves.end(), - [](const RootMove& a, const RootMove& b) { return a.tbRank > b.tbRank; }); - - // Probe during search only if DTZ is not available and we are winning - if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) - Cardinality = 0; - } - else - { - // Clean up if root_probe() and root_probe_wdl() have failed - for (auto& m : rootMoves) - m.tbRank = 0; - } -} } // namespace Stockfish diff --git a/src/search.h b/src/search.h index 68c9f2aa7e0..daf0ff85489 100644 --- a/src/search.h +++ b/src/search.h @@ -29,6 +29,7 @@ #include "misc.h" #include "movepick.h" #include "position.h" +#include "syzygy/tbprobe.h" #include "timeman.h" #include "types.h" @@ -48,7 +49,6 @@ class UCI; namespace Search { - // Stack struct keeps track of the information we need to remember from nodes // shallower and deeper in the tree during the search. Each search thread has // its own array of Stack objects, indexed by the current ply. @@ -238,6 +238,8 @@ class Worker { // The main thread has a SearchManager, the others have a NullSearchManager std::unique_ptr manager; + Tablebases::Config tbConfig; + const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 6f30bf6b1f7..722dc9d3e8e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -18,7 +18,6 @@ #include "tbprobe.h" -#include #include #include #include @@ -32,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +42,7 @@ #include "../position.h" #include "../search.h" #include "../types.h" +#include "../ucioption.h" #ifndef _WIN32 #include @@ -1680,4 +1681,60 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo return true; } +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves) { + Config config; + + if (rootMoves.empty()) + return config; + + config.rootInTB = false; + config.useRule50 = bool(options["Syzygy50MoveRule"]); + config.probeDepth = int(options["SyzygyProbeDepth"]); + config.cardinality = int(options["SyzygyProbeLimit"]); + + bool dtz_available = true; + + // Tables with fewer pieces than SyzygyProbeLimit are searched with + // probeDepth == DEPTH_ZERO + if (config.cardinality > MaxCardinality) + { + config.cardinality = MaxCardinality; + config.probeDepth = 0; + } + + if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + // Rank moves using DTZ tables + config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); + + if (!config.rootInTB) + { + // DTZ tables are missing; try to rank moves using WDL tables + dtz_available = false; + config.rootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]); + } + } + + if (config.rootInTB) + { + // Sort moves according to TB rank + std::stable_sort( + rootMoves.begin(), rootMoves.end(), + [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); + + // Probe during search only if DTZ is not available and we are winning + if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) + config.cardinality = 0; + } + else + { + // Clean up if root_probe() and root_probe_wdl() have failed + for (auto& m : rootMoves) + m.tbRank = 0; + } + + return config; +} } // namespace Stockfish diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index d7b412a10e5..e10950f4e6b 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -20,16 +20,30 @@ #define TBPROBE_H #include +#include -#include "../search.h" namespace Stockfish { class Position; class OptionsMap; + +using Depth = int; + +namespace Search { +struct RootMove; +using RootMoves = std::vector; +} } namespace Stockfish::Tablebases { +struct Config { + int cardinality = 0; + bool rootInTB = false; + bool useRule50 = false; + Depth probeDepth = 0; +}; + enum WDLScore { WDLLoss = -2, // Loss WDLBlessedLoss = -1, // Loss, but draw under 50-move rule @@ -54,7 +68,7 @@ WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); -void rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); +Config rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); } // namespace Stockfish::Tablebases diff --git a/src/thread.cpp b/src/thread.cpp index 9dc4446f1cc..a4bc3d6795b 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -181,8 +181,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) rootMoves.emplace_back(m); - if (!rootMoves.empty()) - Tablebases::rank_root_moves(options, pos, rootMoves); + Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves); // After ownership transfer 'states' becomes empty, so if we stop the search // and call 'go' again without setting a new position states.get() == nullptr. @@ -205,6 +204,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootMoves = rootMoves; th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); th->worker->rootState = setupStates->back(); + th->worker->tbConfig = tbConfig; } main_thread()->start_searching(); From f15e4f50aae3bea3991da4f72a9ff124e4db644b Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 20:44:38 +0100 Subject: [PATCH 0353/1309] Update installation guide links in CONTRIBUTING.md Link to more user friendly installation guides, these are shorter and easier to follow. closes https://github.com/official-stockfish/Stockfish/pull/4985 No functional change --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e589b4fcc82..cf9cecda2b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,7 +19,7 @@ making contributions to Stockfish. In case you do not have a C++ compiler installed, you can follow the instructions from our wiki. -- [Linux][linux-compiling-link] +- [Ubuntu][ubuntu-compiling-link] - [Windows][windows-compiling-link] - [macOS][macos-compiling-link] @@ -92,6 +92,6 @@ Thank you for contributing to Stockfish and helping us make it even better! [discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new [creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test [issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues -[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux -[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#windows -[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos +[ubuntu-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-1 +[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler +[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-2 From 0c7f56dea6ee9a46a4be8481b2316711cb356f02 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Mon, 8 Jan 2024 10:31:23 +0300 Subject: [PATCH 0354/1309] Fix mated-in behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This addresses the issue where Stockfish may output non-proven checkmate scores if the search is prematurely halted, either due to a time control or node limit, before it explores other possibilities where the checkmate score could have been delayed or refuted. The fix also replaces staving off from proven mated scores in a multithread environment making use of the threads instead of a negative effect with multithreads (1t was better in proving mated in scores than more threads). Issue reported on mate tracker repo by and this PR is co-authored with @robertnurnberg Special thanks to @AndyGrant for outlining that a fix is eventually possible. Passed Adj off SMP STC: https://tests.stockfishchess.org/tests/view/65a125d779aa8af82b96c3eb LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 303256 W: 75823 L: 75892 D: 151541 Ptnml(0-2): 406, 35269, 80395, 35104, 454 Passed Adj off SMP LTC: https://tests.stockfishchess.org/tests/view/65a37add79aa8af82b96f0f7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 56056 W: 13951 L: 13770 D: 28335 Ptnml(0-2): 11, 5910, 16002, 6097, 8 Passed all tests in matetrack without any better mate for opponent found in 1t and multithreads. Fixed bugs in https://github.com/official-stockfish/Stockfish/pull/4976 closes https://github.com/official-stockfish/Stockfish/pull/4990 Bench: 1308279 Co-Authored-By: Robert Nürnberg <28635489+robertnurnberg@users.noreply.github.com> --- src/misc.h | 15 ++++++++++++++ src/search.cpp | 55 ++++++++++++++++++++++++++++++++++---------------- src/search.h | 2 +- src/thread.cpp | 20 ++++++++++++------ src/thread.h | 2 +- 5 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/misc.h b/src/misc.h index 994f551d5ff..f73e7889a8d 100644 --- a/src/misc.h +++ b/src/misc.h @@ -19,12 +19,14 @@ #ifndef MISC_H_INCLUDED #define MISC_H_INCLUDED +#include #include #include #include #include #include #include +#include #define stringify2(x) #x #define stringify(x) stringify2(x) @@ -188,6 +190,19 @@ struct CommandLine { std::string workingDirectory; // path of the working directory }; +namespace Utility { + +template +void move_to_front(std::vector& vec, Predicate pred) { + auto it = std::find_if(vec.begin(), vec.end(), pred); + + if (it != vec.end()) + { + std::rotate(vec.begin(), it, it + 1); + } +} +} + } // namespace Stockfish #endif // #ifndef MISC_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 8901dfd1c1e..8363f2215b1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -248,15 +248,16 @@ void Search::Worker::iterative_deepening() { // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), // (ss + 2) is needed for initialization of cutOffCnt and killers. - Stack stack[MAX_PLY + 10], *ss = stack + 7; - Move pv[MAX_PLY + 1]; - Value alpha, beta; - Move lastBestMove = Move::none(); - Depth lastBestMoveDepth = 0; - SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int delta, iterIdx = 0; + Stack stack[MAX_PLY + 10], *ss = stack + 7; + Move pv[MAX_PLY + 1]; + Value alpha, beta; + Value lastBestScore = -VALUE_INFINITE; + std::vector lastBestPV = {Move::none()}; + Depth lastBestMoveDepth = 0; + SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int delta, iterIdx = 0; std::memset(ss - 7, 0, 10 * sizeof(Stack)); for (int i = 7; i > 0; --i) @@ -402,7 +403,12 @@ void Search::Worker::iterative_deepening() { if (mainThread && (threads.stop || pvIdx + 1 == multiPV - || mainThread->tm.elapsed(threads.nodes_searched()) > 3000)) + || mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + // A thread that aborted search can have mated-in/TB-loss PV and score + // that cannot be trusted, i.e. it can be delayed or refuted if we would have + // had time to fully search other root-moves. Thus we suppress this output and + // below pick a proven score/PV for this thread (from the previous iteration). + && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), tbConfig.rootInTB) @@ -412,9 +418,21 @@ void Search::Worker::iterative_deepening() { if (!threads.stop) completedDepth = rootDepth; - if (rootMoves[0].pv[0] != lastBestMove) + // We make sure not to pick an unproven mated-in score, + // in case this thread prematurely stopped search (aborted-search). + if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE + && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) { - lastBestMove = rootMoves[0].pv[0]; + // Bring the last best move to the front for best thread selection. + Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)]( + const auto& rm) { return rm == lastBestPV[0]; }); + rootMoves[0].pv = lastBestPV; + rootMoves[0].score = rootMoves[0].uciScore = lastBestScore; + } + else if (rootMoves[0].pv[0] != lastBestPV[0]) + { + lastBestPV = rootMoves[0].pv; + lastBestScore = rootMoves[0].score; lastBestMoveDepth = rootDepth; } @@ -1916,11 +1934,14 @@ void SearchManager::check_time(Search::Worker& worker) { if (ponder) return; - if ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) - || (worker.limits.movetime && elapsed >= worker.limits.movetime) - || (worker.limits.nodes - && worker.threads.nodes_searched() >= uint64_t(worker.limits.nodes))) - worker.threads.stop = true; + if ( + // Later we rely on the fact that we can at least use the mainthread previous + // root-search score and PV in a multithreaded environment to prove mated-in scores. + worker.completedDepth >= 1 + && ((worker.limits.use_time_management() && (elapsed > tm.maximum() || stopOnPonderhit)) + || (worker.limits.movetime && elapsed >= worker.limits.movetime) + || (worker.limits.nodes && worker.threads.nodes_searched() >= worker.limits.nodes))) + worker.threads.stop = worker.threads.abortedSearch = true; } // Called in case we have no ponder move before exiting the search, diff --git a/src/search.h b/src/search.h index daf0ff85489..4bd013ad670 100644 --- a/src/search.h +++ b/src/search.h @@ -115,7 +115,7 @@ struct LimitsType { std::vector searchmoves; TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; int movestogo, depth, mate, perft, infinite; - int64_t nodes; + uint64_t nodes; }; diff --git a/src/thread.cpp b/src/thread.cpp index a4bc3d6795b..4c1d01f46da 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -20,8 +20,6 @@ #include #include -#include -#include #include #include #include @@ -169,8 +167,8 @@ void ThreadPool::start_thinking(const OptionsMap& options, main_thread()->wait_for_search_finished(); - main_manager()->stopOnPonderhit = stop = false; - main_manager()->ponder = ponderMode; + main_manager()->stopOnPonderhit = stop = abortedSearch = false; + main_manager()->ponder = ponderMode; increaseDepth = true; @@ -229,13 +227,23 @@ Thread* ThreadPool::get_best_thread() const { votes[th->worker->rootMoves[0].pv[0]] += thread_value(th); for (Thread* th : threads) - if (std::abs(bestThread->worker->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + if (bestThread->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY) { - // Make sure we pick the shortest mate / TB conversion or stave off mate the longest + // Make sure we pick the shortest mate / TB conversion if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score) bestThread = th; } + else if (bestThread->worker->rootMoves[0].score != -VALUE_INFINITE + && bestThread->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) + { + // Make sure we pick the shortest mated / TB conversion + if (th->worker->rootMoves[0].score != -VALUE_INFINITE + && th->worker->rootMoves[0].score < bestThread->worker->rootMoves[0].score) + bestThread = th; + } else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || (th->worker->rootMoves[0].score != -VALUE_INFINITE + && th->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) || (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY && (votes[th->worker->rootMoves[0].pv[0]] > votes[bestThread->worker->rootMoves[0].pv[0]] diff --git a/src/thread.h b/src/thread.h index 6575b14e638..a2a1d18c454 100644 --- a/src/thread.h +++ b/src/thread.h @@ -94,7 +94,7 @@ class ThreadPool { void start_searching(); void wait_for_search_finished() const; - std::atomic_bool stop, increaseDepth; + std::atomic_bool stop, abortedSearch, increaseDepth; auto cbegin() const noexcept { return threads.cbegin(); } auto begin() noexcept { return threads.begin(); } From 6c02329860d49198ff6a20b8469fd50dd1aa2eab Mon Sep 17 00:00:00 2001 From: Torsten Hellwig Date: Mon, 15 Jan 2024 13:13:53 +0100 Subject: [PATCH 0355/1309] Fix dotprod detection This fixes the detection of dotprod capable CPUs. Previously it looked for the `dotprod` flag, but this does not exist (https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm64/kernel/cpuinfo.c#n50). The correct flag that specifies the dotprod capability is the `asimddp` flag. fixes #4931 closes https://github.com/official-stockfish/Stockfish/pull/4991 No functional change --- scripts/get_native_properties.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index ae23c3bb4ac..fb124021a31 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -79,7 +79,7 @@ case $uname_s in 'aarch64') file_os='android' true_arch='armv8' - if check_flags 'dotprod'; then + if check_flags 'asimddp'; then true_arch="$true_arch-dotprod" fi ;; From 0fbad56c50edab533e5a2f0319016d14e6a9f99c Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 15 Jan 2024 15:32:06 +0100 Subject: [PATCH 0356/1309] Refactor code for correcting unadjustedStaticEval Passed non-regression STC: https://tests.stockfishchess.org/tests/live_elo/65a4df6a79aa8af82b970ca0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 43328 W: 11103 L: 10892 D: 21333 Ptnml(0-2): 120, 4920, 11407, 5063, 154 https://github.com/official-stockfish/Stockfish/pull/4992 No functional change --- src/search.cpp | 45 ++++++++++----------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8363f2215b1..ffa37ab623a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -62,8 +62,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; } -// Guarantee evaluation does not hit the tablebase range -constexpr Value to_static_eval(const Value v) { +// Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range +Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { + auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; + v += cv * std::abs(cv) / 16384; return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -747,13 +749,7 @@ Value Search::Worker::search( else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = eval = to_static_eval(newEval); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~7 Elo) if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) @@ -762,14 +758,7 @@ Value Search::Worker::search( else { unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); - - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs(thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = eval = to_static_eval(newEval); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), @@ -1513,15 +1502,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) unadjustedStaticEval = ss->staticEval = bestValue = evaluate(pos, thisThread->optimism[us]); - - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs( - thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = bestValue = to_static_eval(newEval); + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~13 Elo) if (ttValue != VALUE_NONE @@ -1534,15 +1516,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, unadjustedStaticEval = ss->staticEval = bestValue = (ss - 1)->currentMove != Move::null() ? evaluate(pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; - - Value newEval = - ss->staticEval - + thisThread->correctionHistory[us][pawn_structure_index(pos)] - * std::abs( - thisThread->correctionHistory[us][pawn_structure_index(pos)]) - / 16384; - - ss->staticEval = bestValue = to_static_eval(newEval); + ss->staticEval = bestValue = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); } // Stand pat. Return immediately if static value is at least beta From 9a9702d668af807b9044fef5a83f6ed0854ce44f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 16 Jan 2024 13:23:49 +0300 Subject: [PATCH 0357/1309] Remove threatenedByPawn from rook threat Can be simplified away. Passed STC: https://tests.stockfishchess.org/tests/view/65a3fa4179aa8af82b96face LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 30592 W: 7903 L: 7674 D: 15015 Ptnml(0-2): 96, 3590, 7711, 3787, 112 Passed LTC: https://tests.stockfishchess.org/tests/view/65a42b9a79aa8af82b96fe88 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 73656 W: 18382 L: 18212 D: 37062 Ptnml(0-2): 47, 8287, 19981, 8475, 38 closes https://github.com/official-stockfish/Stockfish/pull/4993 Bench: 1430061 --- src/movepick.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index e521689ef73..b2638350de1 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -198,15 +198,13 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= - !(threatenedPieces & from) - ? (pt == QUEEN - ? bool(to & threatenedByRook) * 50000 + bool(to & threatenedByMinor) * 10000 - : pt == ROOK - ? bool(to & threatenedByMinor) * 25000 + bool(to & threatenedByPawn) * 10000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 - : 0) - : 0; + m.value -= !(threatenedPieces & from) + ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 + + bool(to & threatenedByMinor) * 10000 + : pt == ROOK ? bool(to & threatenedByMinor) * 25000 + : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + : 0) + : 0; } else // Type == EVASIONS From c8bc2ce4fae2bc9a5dd9b5121274c47d5c9262c0 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:45:59 +0000 Subject: [PATCH 0358/1309] Improve ttPv reduction This patch allows a partial reduction decrease when a node is likely to fail low, and increases the reduction decrease when a node has failed high. Passed STC: https://tests.stockfishchess.org/tests/view/65a626e779aa8af82b9722bc LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 157824 W: 40332 L: 39835 D: 77657 Ptnml(0-2): 543, 18617, 40098, 19108, 546 Passed LTC: https://tests.stockfishchess.org/tests/view/65a7290279aa8af82b97328a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 57228 W: 14475 L: 14111 D: 28642 Ptnml(0-2): 34, 6278, 15633, 6628, 41 closes https://github.com/official-stockfish/Stockfish/pull/4994 Bench: 1364759 --- src/search.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ffa37ab623a..3c630db0b8a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -944,11 +944,6 @@ Value Search::Worker::search( value = bestValue; moveCountPruning = singularQuietLMR = false; - // Indicate PvNodes that will probably fail low if the node was searched - // at a depth equal to or greater than the current depth, and the result - // of this search was a fail low. - bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) && tte->depth() >= depth; - // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. while ((move = mp.next_move(moveCountPruning)) != Move::none()) @@ -1153,9 +1148,10 @@ Value Search::Worker::search( thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV (~4 Elo) - if (ss->ttPv && !likelyFailLow) - r -= 1 + (cutNode && tte->depth() >= depth) + (ttValue > alpha); + // Decrease reduction if position is or has been on the PV (~7 Elo) + if (ss->ttPv) + r -= !(tte->bound() == BOUND_UPPER && PvNode) + (cutNode && tte->depth() >= depth) + + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss - 1)->moveCount > 7) From 856e60d12f11cd250cf00cdd336ed87c25bd0677 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 17 Jan 2024 22:58:46 +0100 Subject: [PATCH 0359/1309] Refactor NativeThread start_routine Removes the free function and fixes the formatting for the function call. closes https://github.com/official-stockfish/Stockfish/pull/4995 No functional change --- src/thread_win32_osx.h | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 8d424b72a50..1d9a834f600 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -34,14 +34,6 @@ namespace Stockfish { -// free function to be passed to pthread_create() -inline void* start_routine(void* ptr) { - auto func = reinterpret_cast*>(ptr); - (*func)(); // Call the function - delete func; - return nullptr; -} - class NativeThread { pthread_t thread; @@ -56,6 +48,15 @@ class NativeThread { pthread_attr_t attr_storage, *attr = &attr_storage; pthread_attr_init(attr); pthread_attr_setstacksize(attr, TH_STACK_SIZE); + + auto start_routine = [](void* ptr) -> void* { + auto f = reinterpret_cast*>(ptr); + // Call the function + (*f)(); + delete f; + return nullptr; + }; + pthread_create(&thread, attr, start_routine, func); } From aa15a9179b8cc5598a6bd04b7c2cfeb81282a0c7 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Fri, 19 Jan 2024 04:51:02 +0000 Subject: [PATCH 0360/1309] Refactor ttPv reduction conditions closes https://github.com/official-stockfish/Stockfish/pull/4999 No functional change --- src/search.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3c630db0b8a..57e87fb2484 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1148,25 +1148,24 @@ Value Search::Worker::search( thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV (~7 Elo) + // Decrease reduction if position is or has been on the PV (~5 Elo) if (ss->ttPv) - r -= !(tte->bound() == BOUND_UPPER && PvNode) + (cutNode && tte->depth() >= depth) - + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); + r -= 1 + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss - 1)->moveCount > 7) r--; - // Increase reduction for cut nodes (~3 Elo) + // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2; + r += 2 - (tte->depth() >= depth && ss->ttPv); // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) r++; - // Decrease reduction for PvNodes (~2 Elo) - if (PvNode) + // Decrease reduction for PvNodes (~3 Elo) + if (PvNode && tte->bound() != BOUND_UPPER) r--; // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo) From e860f620aa3eb42f8a6be78c030c75855d0c9ff0 Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Fri, 19 Jan 2024 07:32:56 +0100 Subject: [PATCH 0361/1309] Reduce futility_margin further when improving The idea of this is to unroll the futility_margin calculation to allow for the improving flag to have a greater effect on the futility margin. The current factor is 1.5 instead of the previous 1 resulting in a deduction of an extra margin/2 from futilit_margin if improving. The chosen value was not tuned, meaning that there is room for tweaking it. This patch is partially inspired by @Vizvezdenec, who, although quite different in execution, tested another idea where the futility_margin is lowered further when improving [1]. [1]: (first take) https://tests.stockfishchess.org/tests/view/65a56b1879aa8af82b97164b Passed STC: https://tests.stockfishchess.org/tests/live_elo/65a8bfc179aa8af82b974e3c LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 161152 W: 41321 L: 40816 D: 79015 Ptnml(0-2): 559, 19030, 40921, 19479, 587 Passed rebased LTC: https://tests.stockfishchess.org/tests/live_elo/65a8b9ef79aa8af82b974dc0 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 96024 W: 24172 L: 23728 D: 48124 Ptnml(0-2): 56, 10598, 26275, 11012, 71 closes https://github.com/official-stockfish/Stockfish/pull/5000 Bench: 1281703 --- AUTHORS | 2 +- src/search.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6f518ec24a8..a179d273d04 100644 --- a/AUTHORS +++ b/AUTHORS @@ -46,6 +46,7 @@ candirufish Chess13234 Chris Cain (ceebo) clefrks +Clemens L. (rn5f107s2) Cody Ho (aesrentai) Dale Weiler (graphitemaster) Daniel Axtens (daxtens) @@ -183,7 +184,6 @@ Raminder Singh renouve Reuven Peleg (R-Peleg) Richard Lloyd (Richard-Lloyd) -rn5f107s2 Robert Nürnberg (robertnurnberg) Rodrigo Exterckötter Tjäder Rodrigo Roim (roim) diff --git a/src/search.cpp b/src/search.cpp index 57e87fb2484..c244207d004 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,8 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - return ((116 - 44 * noTtCutNode) * (d - improving)); + Value futilityMult = 116 - 44 * noTtCutNode; + return (futilityMult * d - 3 * futilityMult / 2 * improving); } constexpr int futility_move_count(bool improving, Depth depth) { From ad9fcbc4961229286e5043d984dc172d1b0de052 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Thu, 18 Jan 2024 07:31:59 +0300 Subject: [PATCH 0362/1309] Refactor get_best_thread Make get_best_thread function easier to understand. Passed non-reg SMP STC: https://tests.stockfishchess.org/tests/view/65a91c6679aa8af82b975500 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 186000 W: 46379 L: 46325 D: 93296 Ptnml(0-2): 269, 21374, 49634, 21480, 243 closes https://github.com/official-stockfish/Stockfish/pull/5001 No functional change --- src/thread.cpp | 59 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/src/thread.cpp b/src/thread.cpp index 4c1d01f46da..3cce7c56295 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -210,49 +210,66 @@ void ThreadPool::start_thinking(const OptionsMap& options, Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); std::unordered_map votes; - Value minScore = VALUE_NONE; + + Thread* bestThread = threads.front(); + Value minScore = VALUE_NONE; // Find the minimum score of all threads for (Thread* th : threads) minScore = std::min(minScore, th->worker->rootMoves[0].score); // Vote according to score and depth, and select the best thread - auto thread_value = [minScore](Thread* th) { + auto thread_voting_value = [minScore](Thread* th) { return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth); }; for (Thread* th : threads) - votes[th->worker->rootMoves[0].pv[0]] += thread_value(th); + votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th); for (Thread* th : threads) - if (bestThread->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY) + { + const auto bestThreadScore = bestThread->worker->rootMoves[0].score; + const auto newThreadScore = th->worker->rootMoves[0].score; + + const auto bestThreadPV = bestThread->worker->rootMoves[0].pv; + const auto newThreadPV = th->worker->rootMoves[0].pv; + + const auto bestThreadMoveVote = votes[bestThreadPV[0]]; + const auto newThreadMoveVote = votes[newThreadPV[0]]; + + + const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + + const bool bestThreadInProvenLoss = + bestThreadScore != -VALUE_INFINITE && bestThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + const bool newThreadInProvenLoss = + newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + + // Note that we make sure not to pick a thread with truncated-PV for better viewer experience. + const bool betterVotingValue = + thread_voting_value(th) * int(newThreadPV.size() > 2) + > thread_voting_value(bestThread) * int(bestThreadPV.size() > 2); + + if (bestThreadInProvenWin) { // Make sure we pick the shortest mate / TB conversion - if (th->worker->rootMoves[0].score > bestThread->worker->rootMoves[0].score) + if (newThreadScore > bestThreadScore) bestThread = th; } - else if (bestThread->worker->rootMoves[0].score != -VALUE_INFINITE - && bestThread->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) + else if (bestThreadInProvenLoss) { // Make sure we pick the shortest mated / TB conversion - if (th->worker->rootMoves[0].score != -VALUE_INFINITE - && th->worker->rootMoves[0].score < bestThread->worker->rootMoves[0].score) + if (newThreadInProvenLoss && newThreadScore < bestThreadScore) bestThread = th; } - else if (th->worker->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY - || (th->worker->rootMoves[0].score != -VALUE_INFINITE - && th->worker->rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) - || (th->worker->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY - && (votes[th->worker->rootMoves[0].pv[0]] - > votes[bestThread->worker->rootMoves[0].pv[0]] - || (votes[th->worker->rootMoves[0].pv[0]] - == votes[bestThread->worker->rootMoves[0].pv[0]] - && thread_value(th) * int(th->worker->rootMoves[0].pv.size() > 2) - > thread_value(bestThread) - * int(bestThread->worker->rootMoves[0].pv.size() > 2))))) + else if (newThreadInProvenWin || newThreadInProvenLoss + || (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY + && (newThreadMoveVote > bestThreadMoveVote + || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue)))) bestThread = th; + } return bestThread; } From a901474bf9579ba259179eb09618d8401a156f64 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 20 Jan 2024 19:15:20 +0100 Subject: [PATCH 0363/1309] Update the WDL model Update the internal WDL model. After the dual net merge, the internal evaluations have drifted upwards a bit. With this PR `NormalizeToPawnValue` changes from `328` to `345`. The new model was fitted based on about 200M positions extracted from 3.4M fishtest LTC games from the last two weeks, involving SF versions from 6deb88728fb141e853243c2873ad0cda4dd19320 to current master. Apart from the WDL model parameter update, this PR implements the following changes: WDL Model: - an incorrect 8-move shift in master's WDL model has been fixed - the polynomials `p_a` and `p_b` are fitted over the move range [8, 120] - the coefficients for `p_a` and `p_b` are optimized by maximizing the probability of predicting the observed outcome (credits to @vondele) SF code: - for wdl values, move will be clamped to `max(8, min(120, move))` - no longer clamp the internal eval to [-4000,4000] - compute `NormalizeToPawnValue` with `round`, not `trunc` The PR only affects displayed `cp` and `wdl` values. closes https://github.com/official-stockfish/Stockfish/pull/5002 No functional change --- src/uci.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 789f345481d..2a55fbfab0a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -43,7 +43,7 @@ namespace Stockfish { constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int NormalizeToPawnValue = 328; +constexpr int NormalizeToPawnValue = 345; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; UCI::UCI(int argc, char** argv) : @@ -421,26 +421,23 @@ namespace { // eval and a game ply. It fits the LTC fishtest statistics rather accurately. int win_rate_model(Value v, int ply) { - // The model only captures up to 240 plies, so limit the input and then rescale - double m = std::min(240, ply) / 64.0; + // The fitted model only uses data for moves in [8, 120], and is anchored at move 32. + double m = std::clamp(ply / 2 + 1, 8, 120) / 32.0; // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407}; - constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330}; + constexpr double as[] = {-2.00568292, 10.45906746, 1.67438883, 334.45864705}; + constexpr double bs[] = {-4.97134419, 36.15096345, -82.25513499, 117.35186805}; - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64 - static_assert(NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3])); + // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at move 32. + static_assert(NormalizeToPawnValue == int(0.5 + as[0] + as[1] + as[2] + as[3])); double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - // Transform the eval to centipawns with limited range - double x = std::clamp(double(v), -4000.0, 4000.0); - - // Return the win rate in per mille units, rounded to the nearest integer - return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); + // Return the win rate in per mille units, rounded to the nearest integer. + return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); } } From a6fd17f27d7675332166e9e6ea8210237281fc77 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 17 Jan 2024 18:10:38 +0800 Subject: [PATCH 0364/1309] VLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search parameters were tuned using 152k games at 180+1.8. Passed VLTC: https://tests.stockfishchess.org/tests/view/65a7a81979aa8af82b973a20 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 117338 W: 29244 L: 28848 D: 59246 Ptnml(0-2): 24, 12474, 33267, 12890, 14 Passed VVLTC: https://tests.stockfishchess.org/tests/view/65ab246679aa8af82b977982 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 28164 W: 7239 L: 6957 D: 13968 Ptnml(0-2): 3, 2651, 8490, 2937, 1 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65ac7c0979aa8af82b9792a6 Elo: -0.53 ± 2.0 (95%) LOS: 30.4% Total: 30000 W: 7688 L: 7734 D: 14578 Ptnml(0-2): 102, 3617, 7614, 3559, 108 nElo: -1.03 ± 3.9 (95%) PairsRatio: 0.99 closes https://github.com/official-stockfish/Stockfish/pull/5003 Bench: 1235377 --- src/search.cpp | 74 +++++++++++++++++++++++++------------------------- src/search.h | 4 +-- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c244207d004..a22df12c2c6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 116 - 44 * noTtCutNode; + Value futilityMult = 114 - 47 * noTtCutNode; return (futilityMult * d - 3 * futilityMult / 2 * improving); } @@ -66,15 +66,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 16384; + v += cv * std::abs(cv) / 14095; return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); } +int stat_bonus(Depth d) { return std::min(265 * d - 349, 1112); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); } +int stat_malus(Depth d) { return std::min(482 * d - 326, 1172); } // 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); } @@ -334,12 +334,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(9) + int(avg) * avg / 14847; + delta = Value(9) + int(avg) * avg / 13181; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, int(VALUE_INFINITE)); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 121 * avg / (std::abs(avg) + 109); + optimism[us] = 132 * avg / (std::abs(avg) + 98); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -769,7 +769,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1680, 1406); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -790,7 +790,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 472 - (284 - 165 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 435 - (327 - 167 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -799,17 +799,17 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 9 + if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - - (ss - 1)->statScore / 337 + - (ss - 1)->statScore / 327 >= beta - && eval >= beta && eval < 29008 // smaller than TB wins + && eval >= beta && eval < 27734 // smaller than TB wins && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17496 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17787 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 22 * depth + 313 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { @@ -863,7 +863,7 @@ Value Search::Worker::search( if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 163 - 67 * improving; + probCutBeta = beta + 173 - 73 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -923,7 +923,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 425; + probCutBeta = beta + 427; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -1006,7 +1006,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 277 + 298 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -1014,7 +1014,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -187 * depth)) + if (!pos.see_ge(move, -203 * depth)) continue; } else @@ -1026,18 +1026,18 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3752 * depth) + if (lmrDepth < 6 && history < -4195 * depth) continue; - history += 2 * thisThread->mainHistory[us][move.from_to()]; + history += 69 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 7838; + lmrDepth += history / 6992; lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 14 - && ss->staticEval + (bestValue < ss->staticEval - 57 ? 124 : 71) - + 118 * lmrDepth + if (!ss->inCheck && lmrDepth < 15 + && ss->staticEval + (bestValue < ss->staticEval - 63 ? 137 : 64) + + 111 * lmrDepth <= alpha) continue; @@ -1064,11 +1064,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 27) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 31) + 2 * (PvNode && tte->is_pv()) && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (66 + 58 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (58 + 52 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1082,7 +1082,7 @@ Value Search::Worker::search( singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 17 && ss->doubleExtensions <= 11) + if (!PvNode && value < singularBeta - 16 && ss->doubleExtensions <= 12) { extension = 2; depth += depth < 15; @@ -1109,7 +1109,7 @@ Value Search::Worker::search( // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) - extension = depth < 19 ? -2 : -1; + extension = depth < 20 ? -2 : -1; // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) else if (ttValue <= value) @@ -1122,14 +1122,14 @@ Value Search::Worker::search( // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][move.to_sq()] >= 4325) + && (*contHist[0])[movedPiece][move.to_sq()] >= 4111) extension = 1; // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4146) + > 4484) extension = 1; } @@ -1189,10 +1189,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 3817; + + (*contHist[3])[movedPiece][move.to_sq()] - 4119; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / 14767; + r -= ss->statScore / 15373; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) // We use various heuristics for the sons of a node after the first son has @@ -1215,7 +1215,7 @@ Value Search::Worker::search( { // 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 + 53 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 51 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1331,7 +1331,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13782 && value > -11541) + if (depth > 2 && depth < 12 && beta < 13195 && value > -12346) depth -= 2; assert(depth > 0); @@ -1370,7 +1370,7 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 6) + (PvNode || cutNode) + ((ss - 1)->statScore < -18782) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -16797) + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1529,7 +1529,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 182; + futilityBase = ss->staticEval + 186; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1609,7 +1609,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -77)) + if (!pos.see_ge(move, -76)) continue; } @@ -1764,7 +1764,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 177 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move diff --git a/src/search.h b/src/search.h index 4bd013ad670..3a099c5dddf 100644 --- a/src/search.h +++ b/src/search.h @@ -205,8 +205,8 @@ class Worker { Depth reduction(bool i, Depth d, int mn, int delta) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024 - + (!i && reductionScale > 880); + return (reductionScale + 1177 - int(delta) * 776 / int(rootDelta)) / 1024 + + (!i && reductionScale > 842); } // Get a pointer to the search manager, only allowed to be called by the From 2b62c4452db5a02c5ed7d4a2b4c4cb1529fb82b7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 23 Jan 2024 13:39:06 +0300 Subject: [PATCH 0365/1309] Remove redundant max operation on lmrDepth Removed a restriction that prohibited history heuristics sum in futility pruning to exceed some negative value. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 279040 W: 71095 L: 71143 D: 136802 Ptnml(0-2): 949, 33574, 70474, 33622, 901 https://tests.stockfishchess.org/tests/view/65aaef4c79aa8af82b977631 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 75156 W: 18884 L: 18715 D: 37557 Ptnml(0-2): 52, 8445, 20408, 8628, 45 https://tests.stockfishchess.org/tests/view/65ae7ef3c865510db026abf5 closes https://github.com/official-stockfish/Stockfish/pull/5004 Bench: 1566543 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a22df12c2c6..f5395f98376 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1032,7 +1032,6 @@ Value Search::Worker::search( history += 69 * thisThread->mainHistory[us][move.from_to()] / 32; lmrDepth += history / 6992; - lmrDepth = std::max(lmrDepth, -1); // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 From 3d49a99aaf75f6f44ef6ec5a22b0acd191b8d01e Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 24 Jan 2024 14:38:59 +0300 Subject: [PATCH 0366/1309] Refactor history score calculation Passed STC: https://tests.stockfishchess.org/tests/view/65ad08b179aa8af82b979dd1 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 161376 W: 41582 L: 41498 D: 78296 Ptnml(0-2): 633, 19354, 40611, 19476, 614 Passed LTC: https://tests.stockfishchess.org/tests/view/65af966fc865510db026c0f0 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 116526 W: 29269 L: 29143 D: 58114 Ptnml(0-2): 71, 13252, 31509, 13342, 89 closes https://github.com/official-stockfish/Stockfish/pull/5006 Bench: 1317504 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f5395f98376..f4b372536c9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1029,7 +1029,7 @@ Value Search::Worker::search( if (lmrDepth < 6 && history < -4195 * depth) continue; - history += 69 * thisThread->mainHistory[us][move.from_to()] / 32; + history += 2 * thisThread->mainHistory[us][move.from_to()]; lmrDepth += history / 6992; From 1dfbde2d1056b49442aab9bf5145ad30d0e87dd1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 14 Jan 2024 00:21:46 +0100 Subject: [PATCH 0367/1309] Move perft out of search This splits the logic of search and perft. Before, threads were started, which then constructed a search object, which then started perft and returned immediately. All of this is unnecessary, instead uci should start perft right away. closes https://github.com/official-stockfish/Stockfish/pull/5008 No functional change --- src/Makefile | 2 +- src/perft.h | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/search.cpp | 36 -------------------------- src/uci.cpp | 8 +++++- 4 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 src/perft.h diff --git a/src/Makefile b/src/Makefile index 9680ca7feff..907b6155028 100644 --- a/src/Makefile +++ b/src/Makefile @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h + tt.h tune.h types.h uci.h ucioption.h perft.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/perft.h b/src/perft.h new file mode 100644 index 00000000000..2edc3ad0a6e --- /dev/null +++ b/src/perft.h @@ -0,0 +1,69 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PERFT_H_INCLUDED +#define PERFT_H_INCLUDED + +#include + +#include "movegen.h" +#include "position.h" +#include "types.h" +#include "uci.h" + +namespace Stockfish { + +// Utility to verify move generation. All the leaf nodes up +// to the given depth are generated and counted, and the sum is returned. +template +uint64_t perft(Position& pos, Depth depth) { + + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= 1) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; +} + +inline void perft(const std::string& fen, Depth depth, bool isChess960) { + StateListPtr states(new std::deque(1)); + Position p; + p.set(fen, isChess960, &states->back()); + + uint64_t nodes = perft(p, depth); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; +} +} + +#endif // PERFT_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index f4b372536c9..086bff34029 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -122,37 +122,8 @@ void update_all_stats(const Position& pos, int captureCount, Depth depth); -// Utility to verify move generation. All the leaf nodes up -// to the given depth are generated and counted, and the sum is returned. -template -uint64_t perft(Position& pos, Depth depth) { - - StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - - uint64_t cnt, nodes = 0; - const bool leaf = (depth == 2); - - for (const auto& m : MoveList(pos)) - { - if (Root && depth <= 1) - cnt = 1, nodes++; - else - { - pos.do_move(m, st); - cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); - nodes += cnt; - pos.undo_move(m); - } - if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; - } - return nodes; -} - } // namespace - Search::Worker::Worker(SharedState& sharedState, std::unique_ptr sm, size_t thread_id) : @@ -173,13 +144,6 @@ void Search::Worker::start_searching() { return; } - if (limits.perft) - { - nodes = perft(rootPos, limits.perft); - sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; - return; - } - main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options); tt.new_search(); diff --git a/src/uci.cpp b/src/uci.cpp index 2a55fbfab0a..e6107d4713a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -39,6 +39,7 @@ #include "syzygy/tbprobe.h" #include "types.h" #include "ucioption.h" +#include "perft.h" namespace Stockfish { @@ -172,7 +173,6 @@ void UCI::loop() { void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { - Search::LimitsType limits; std::string token; bool ponderMode = false; @@ -211,6 +211,12 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { Eval::NNUE::verify(options, evalFiles); + if (limits.perft) + { + perft(pos.fen(), limits.perft, options["UCI_Chess960"]); + return; + } + threads.start_thinking(options, pos, states, limits, ponderMode); } From 37bd1e774ee4eb03e558062284da1e72cbce5a95 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 25 Jan 2024 23:22:07 +0300 Subject: [PATCH 0368/1309] Do more double extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parameter tweak from Black Marlin chess engine. Choose a significantly lower value that triggers in 95% of cases, compared to the usual 84% in standard benchmark runs. Since the introduction by https://github.com/official-stockfish/Stockfish/commit/33a858eaa1f792b3413384a3d0993dba36aca92e this constant has only decreased in value over time. 2-16-17-18-21-22-25-26-52-71-75-93-140 Failed STC really fast: https://tests.stockfishchess.org/tests/view/65b11d05c865510db026df7b LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 13216 W: 3242 L: 3485 D: 6489 Ptnml(0-2): 50, 1682, 3371, 1471, 34 Was reasonable at LTC: https://tests.stockfishchess.org/tests/view/65b13e20c865510db026e210 Elo: 1.18 ± 1.5 (95%) LOS: 94.3% Total: 50000 W: 12517 L: 12347 D: 25136 Ptnml(0-2): 31, 5598, 13579, 5754, 38 nElo: 2.45 ± 3.0 (95%) PairsRatio: 1.03 Passed VLTC with STC bounds: https://tests.stockfishchess.org/tests/view/65b18870c865510db026e769 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 30456 W: 7726 L: 7448 D: 15282 Ptnml(0-2): 6, 3111, 8717, 3387, 7 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/65b20b95c865510db026eef0 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 36134 W: 9158 L: 8859 D: 18117 Ptnml(0-2): 3, 3455, 10850, 3758, 1 closes https://github.com/official-stockfish/Stockfish/pull/5013 Bench: 1503692 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 086bff34029..9b74141bcfa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1045,7 +1045,7 @@ Value Search::Worker::search( singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 16 && ss->doubleExtensions <= 12) + if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 12) { extension = 2; depth += depth < 15; From c17ec9524d57c2fdaba1fd7f16ce30744780b6aa Mon Sep 17 00:00:00 2001 From: Ahmed Kerimov Date: Fri, 26 Jan 2024 13:52:56 +0300 Subject: [PATCH 0369/1309] Move OnChange callback in Option ctors Parameter 'f' is passed by value and only copied once. Moving it to avoid unnecessary copies. closes https://github.com/official-stockfish/Stockfish/pull/5014 No functional change --- AUTHORS | 1 + src/ucioption.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index a179d273d04..cc8edafa4aa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,6 +12,7 @@ Hisayori Noda (nodchip) # All other authors of Stockfish code (in alphabetical order) Aditya (absimaldata) Adrian Petrescu (apetresc) +Ahmed Kerimov (wcdbmv) Ajith Chandy Jose (ajithcj) Alain Savard (Rocky640) Alayan Feh (Alayan-stk-2) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index c7de7e3f7f2..e1ffe546525 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -68,7 +68,7 @@ Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), - on_change(f) { + on_change(std::move(f)) { defaultValue = currentValue = v; } @@ -76,7 +76,7 @@ Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), - on_change(f) { + on_change(std::move(f)) { defaultValue = currentValue = (v ? "true" : "false"); } @@ -84,13 +84,13 @@ Option::Option(OnChange f) : type("button"), min(0), max(0), - on_change(f) {} + on_change(std::move(f)) {} Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), - on_change(f) { + on_change(std::move(f)) { defaultValue = currentValue = std::to_string(v); } @@ -98,7 +98,7 @@ Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), - on_change(f) { + on_change(std::move(f)) { defaultValue = v; currentValue = cur; } From fcbb02ffdeebb65c970ecf5aebaa3078cdf8f374 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:27:49 +0000 Subject: [PATCH 0370/1309] Use ttPv in depth condition of singular extensions This replaces the PvNode condition and tte Pv call previously with using the precomputed ttPv, and also removes the multiplier of 2. This new depth condition occurs with approximately equal frequency (47%) to the old depth condition (measured when the other conditions in the if are true), so non-linear scaling behaviour isn't expected. Passed Non-Reg STC: https://tests.stockfishchess.org/tests/view/65b0e132c865510db026da27 LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 243232 W: 62432 L: 62437 D: 118363 Ptnml(0-2): 910, 28937, 61900, 28986, 883 Passed Non-Reg LTC: https://tests.stockfishchess.org/tests/view/65b2053bc865510db026eea1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 190596 W: 47666 L: 47618 D: 95312 Ptnml(0-2): 115, 21710, 51596, 21766, 111 closes https://github.com/official-stockfish/Stockfish/pull/5015 Bench: 1492957 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9b74141bcfa..6a464961a61 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1027,7 +1027,7 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 31) + 2 * (PvNode && tte->is_pv()) + && depth >= 4 - (thisThread->completedDepth > 31) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { From 13eb023fc09343c80c45f51df83a1b9f6401bd35 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 27 Jan 2024 10:54:44 +0100 Subject: [PATCH 0371/1309] Simplify array initializations also retire a few std::memset calls. Passed non-regresion STC: https://tests.stockfishchess.org/tests/view/65b8e162c865510db0276901 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 97504 W: 25294 L: 25140 D: 47070 Ptnml(1-2): 378, 11102, 25667, 11198, 407 closes https://github.com/official-stockfish/Stockfish/pull/5018 No functional change --- src/position.cpp | 10 +++++----- src/search.cpp | 40 ++++++++++++++++++++-------------------- src/search.h | 13 +++++++------ 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 6202381d072..c89b1eb0889 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -19,6 +19,7 @@ #include "position.h" #include +#include #include #include #include @@ -107,9 +108,8 @@ inline int H1(Key h) { return h & 0x1fff; } inline int H2(Key h) { return (h >> 16) & 0x1fff; } // Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves -Key cuckoo[8192]; -Move cuckooMove[8192]; - +std::array cuckoo; +std::array cuckooMove; // Initializes at startup the various arrays used to compute hash keys void Position::init() { @@ -130,8 +130,8 @@ void Position::init() { Zobrist::noPawns = rng.rand(); // Prepare the cuckoo tables - std::memset(cuckoo, 0, sizeof(cuckoo)); - std::memset(cuckooMove, 0, sizeof(cuckooMove)); + cuckoo.fill(0); + cuckooMove.fill(Move::none()); [[maybe_unused]] int count = 0; for (Piece pc : Pieces) for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) diff --git a/src/search.cpp b/src/search.cpp index 6a464961a61..29b5c5243b3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -212,21 +211,26 @@ void Search::Worker::start_searching() { // consumed, the user stops the search, or the maximum search depth is reached. void Search::Worker::iterative_deepening() { + SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + + Move pv[MAX_PLY + 1]; + + Depth lastBestMoveDepth = 0; + Value lastBestScore = -VALUE_INFINITE; + auto lastBestPV = std::vector{Move::none()}; + + Value alpha, beta; + Value bestValue = -VALUE_INFINITE; + Color us = rootPos.side_to_move(); + double timeReduction = 1, totBestMoveChanges = 0; + int delta, iterIdx = 0; + // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), // (ss + 2) is needed for initialization of cutOffCnt and killers. - Stack stack[MAX_PLY + 10], *ss = stack + 7; - Move pv[MAX_PLY + 1]; - Value alpha, beta; - Value lastBestScore = -VALUE_INFINITE; - std::vector lastBestPV = {Move::none()}; - Depth lastBestMoveDepth = 0; - SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); - double timeReduction = 1, totBestMoveChanges = 0; - Color us = rootPos.side_to_move(); - int delta, iterIdx = 0; - - std::memset(ss - 7, 0, 10 * sizeof(Stack)); + Stack stack[MAX_PLY + 10] = {}; + Stack* ss = stack + 7; + for (int i = 7; i > 0; --i) { (ss - i)->continuationHistory = @@ -239,16 +243,12 @@ void Search::Worker::iterative_deepening() { ss->pv = pv; - Value bestValue = -VALUE_INFINITE; - if (mainThread) { if (mainThread->bestPreviousScore == VALUE_INFINITE) - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = VALUE_ZERO; + mainThread->iterValue.fill(VALUE_ZERO); else - for (int i = 0; i < 4; ++i) - mainThread->iterValue[i] = mainThread->bestPreviousScore; + mainThread->iterValue.fill(mainThread->bestPreviousScore); } size_t multiPV = size_t(options["MultiPV"]); @@ -489,7 +489,7 @@ void Search::Worker::clear() { h->fill(-71); - for (int i = 1; i < MAX_MOVES; ++i) + for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } diff --git a/src/search.h b/src/search.h index 3a099c5dddf..b4a65d8e6f6 100644 --- a/src/search.h +++ b/src/search.h @@ -19,6 +19,7 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include #include #include #include @@ -153,11 +154,11 @@ class SearchManager: public ISearchManager { int callsCnt; std::atomic_bool ponder; - double previousTimeReduction; - Value bestPreviousScore; - Value bestPreviousAverageScore; - Value iterValue[4]; - bool stopOnPonderhit; + std::array iterValue; + double previousTimeReduction; + Value bestPreviousScore; + Value bestPreviousAverageScore; + bool stopOnPonderhit; size_t id; }; @@ -233,7 +234,7 @@ class Worker { size_t thread_idx; // Reductions lookup table initialized at startup - int reductions[MAX_MOVES]; // [depth or moveNumber] + std::array reductions; // [depth or moveNumber] // The main thread has a SearchManager, the others have a NullSearchManager std::unique_ptr manager; From 16afec058229067e2f3965672aff450d6a9babc7 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 27 Jan 2024 22:27:22 +0100 Subject: [PATCH 0372/1309] Refactor pv printing Also fix the case which is currently printing depth 0. fixes #5019 closes https://github.com/official-stockfish/Stockfish/pull/5020 No functional change --- src/search.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++-------- src/search.h | 6 +++++ src/uci.cpp | 58 +---------------------------------------- src/uci.h | 11 -------- 4 files changed, 66 insertions(+), 79 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 29b5c5243b3..0a07c8bb972 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "evaluate.h" #include "misc.h" @@ -192,9 +193,7 @@ void Search::Worker::start_searching() { // Send again PV info if we have a new best thread if (bestThread != this) - sync_cout << UCI::pv(*bestThread, main_manager()->tm.elapsed(threads.nodes_searched()), - threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - tbConfig.rootInTB) + sync_cout << main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth) << sync_endl; sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); @@ -336,10 +335,7 @@ void Search::Worker::iterative_deepening() { // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) - sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), - threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - tbConfig.rootInTB) - << sync_endl; + sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. @@ -376,10 +372,7 @@ void Search::Worker::iterative_deepening() { // had time to fully search other root-moves. Thus we suppress this output and // below pick a proven score/PV for this thread (from the previous iteration). && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) - sync_cout << UCI::pv(*this, mainThread->tm.elapsed(threads.nodes_searched()), - threads.nodes_searched(), threads.tb_hits(), tt.hashfull(), - tbConfig.rootInTB) - << sync_endl; + sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; } if (!threads.stop) @@ -1878,6 +1871,61 @@ void SearchManager::check_time(Search::Worker& worker) { worker.threads.stop = worker.threads.abortedSearch = true; } +std::string SearchManager::pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const { + std::stringstream ss; + + const auto nodes = threads.nodes_searched(); + const auto& rootMoves = worker.rootMoves; + const auto& pos = worker.rootPos; + size_t pvIdx = worker.pvIdx; + TimePoint time = tm.elapsed(nodes) + 1; + size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); + uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); + + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB; + v = tb ? rootMoves[i].tbScore : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info" + << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 + << " score " << UCI::value(v); + + if (worker.options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); + + if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + ss << (rootMoves[i].scoreLowerbound + ? " lowerbound" + : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + + ss << " nodes " << nodes << " nps " << nodes * 1000 / time << " hashfull " << tt.hashfull() + << " tbhits " << tbHits << " time " << time << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(m, pos.is_chess960()); + } + + return ss.str(); +} + // Called in case we have no ponder move before exiting the search, // for instance, in case we stop the search during a fail high at root. // We try hard to have a ponder move to return to the GUI, diff --git a/src/search.h b/src/search.h index b4a65d8e6f6..c8534b40177 100644 --- a/src/search.h +++ b/src/search.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "misc.h" #include "movepick.h" @@ -150,6 +151,11 @@ class SearchManager: public ISearchManager { public: void check_time(Search::Worker& worker) override; + std::string pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const; + Stockfish::TimeManagement tm; int callsCnt; std::atomic_bool ponder; diff --git a/src/uci.cpp b/src/uci.cpp index e6107d4713a..d1d69d69752 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "benchmark.h" #include "evaluate.h" @@ -365,63 +366,6 @@ std::string UCI::move(Move m, bool chess960) { return move; } -std::string UCI::pv(const Search::Worker& workerThread, - TimePoint elapsed, - uint64_t nodesSearched, - uint64_t tb_hits, - int hashfull, - bool rootInTB) { - std::stringstream ss; - TimePoint time = elapsed + 1; - const auto& rootMoves = workerThread.rootMoves; - const auto& depth = workerThread.completedDepth; - const auto& pos = workerThread.rootPos; - size_t pvIdx = workerThread.pvIdx; - size_t multiPV = std::min(size_t(workerThread.options["MultiPV"]), rootMoves.size()); - uint64_t tbHits = tb_hits + (rootInTB ? rootMoves.size() : 0); - - - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = rootMoves[i].score != -VALUE_INFINITE; - - if (depth == 1 && !updated && i > 0) - continue; - - Depth d = updated ? depth : std::max(1, depth - 1); - Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore; - - if (v == -VALUE_INFINITE) - v = VALUE_ZERO; - - bool tb = rootInTB && std::abs(v) <= VALUE_TB; - v = tb ? rootMoves[i].tbScore : v; - - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; - - ss << "info" - << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << value(v); - - if (workerThread.options["UCI_ShowWDL"]) - ss << wdl(v, pos.game_ply()); - - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound - ? " lowerbound" - : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); - - ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / time << " hashfull " - << hashfull << " tbhits " << tbHits << " time " << time << " pv"; - - for (Move m : rootMoves[i].pv) - ss << " " << move(m, pos.is_chess960()); - } - - return ss.str(); -} - namespace { // The win rate model returns the probability of winning (in per mille units) given an // eval and a game ply. It fits the LTC fishtest statistics rather accurately. diff --git a/src/uci.h b/src/uci.h index cd113b1ad29..9d5f524ad65 100644 --- a/src/uci.h +++ b/src/uci.h @@ -19,7 +19,6 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED -#include #include #include #include @@ -37,10 +36,6 @@ namespace Eval::NNUE { enum NetSize : int; } -namespace Search { -class Worker; -} - class Move; enum Square : int; using Value = int; @@ -55,12 +50,6 @@ class UCI { static std::string value(Value v); static std::string square(Square s); static std::string move(Move m, bool chess960); - static std::string pv(const Search::Worker& workerThread, - TimePoint elapsed, - uint64_t nodesSearched, - uint64_t tb_hits, - int hashfull, - bool rootInTB); static std::string wdl(Value v, int ply); static Move to_move(const Position& pos, std::string& str); From 3cce4c4cf4e39f05cea01e1020080284bf5a0fae Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 31 Jan 2024 22:47:02 +0100 Subject: [PATCH 0373/1309] Add Apple Silicon Runners to CI GitHub CI runners are available for macOS 14, these runners are using apple silicon chips (M1). https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/ closes https://github.com/official-stockfish/Stockfish/pull/5025 No functional change --- .github/workflows/stockfish_binaries.yml | 46 ++++++++++++++++++-- .github/workflows/stockfish_compile_test.yml | 17 ++++++++ .github/workflows/stockfish_test.yml | 22 ++++++++-- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml index eff2c2c9471..2911badacf7 100644 --- a/.github/workflows/stockfish_binaries.yml +++ b/.github/workflows/stockfish_binaries.yml @@ -31,6 +31,13 @@ jobs: comp: clang shell: bash archive_ext: tar + - name: MacOS 14 Apple Clang M1 + os: macos-14 + simple_name: macos-m1 + compiler: clang++ + comp: clang + shell: bash + archive_ext: tar - name: Windows 2022 Mingw-w64 GCC x86_64 os: windows-2022 simple_name: windows @@ -51,9 +58,32 @@ jobs: - x86-64-avx512 - x86-64-vnni256 - x86-64-vnni512 + - apple-silicon exclude: + # Apple M1 + - binaries: x86-64 + config: { os: macos-14 } + - binaries: x86-64-sse41-popcnt + config: { os: macos-14 } + - binaries: x86-64-avx2 + config: { os: macos-14 } + - binaries: x86-64-bmi2 + config: { os: macos-14 } + - binaries: x86-64-avxvnni + config: { os: macos-14 } + - binaries: x86-64-avxvnni + config: { os: macos-14 } + - binaries: x86-64-avx512 + config: { os: macos-14 } + - binaries: x86-64-vnni256 + config: { os: macos-14 } + - binaries: x86-64-vnni512 + config: { os: macos-14 } + - binaries: x86-64-avxvnni config: { ubuntu-20.04 } + + # Apple x86_64 (no sde) - binaries: x86-64-avxvnni config: { os: macos-13 } - binaries: x86-64-avx512 @@ -62,6 +92,14 @@ jobs: config: { os: macos-13 } - binaries: x86-64-vnni512 config: { os: macos-13 } + + # Apple silicon from windows, macos-13 and ubuntu + - binaries: apple-silicon + config: { os: windows-2022 } + - binaries: apple-silicon + config: { os: macos-13 } + - binaries: apple-silicon + config: { os: ubuntu-20.04 } defaults: run: working-directory: src @@ -77,7 +115,7 @@ jobs: - name: Install fixed GCC on Linux if: runner.os == 'Linux' - uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 + uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 with: version: 11 @@ -90,7 +128,7 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 + uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 with: environmentVariableName: SDE_DIR sdeVersion: 9.27.0 @@ -183,7 +221,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} @@ -206,7 +244,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml index 1adc3e34b7c..a47fcb0fc31 100644 --- a/.github/workflows/stockfish_compile_test.yml +++ b/.github/workflows/stockfish_compile_test.yml @@ -26,6 +26,12 @@ jobs: compiler: clang++ comp: clang shell: bash + - name: MacOS 14 Apple Clang M1 + os: macos-14 + compiler: clang++ + comp: clang + shell: bash + m1: true - name: MacOS 13 GCC 11 os: macos-13 compiler: g++-11 @@ -75,26 +81,37 @@ jobs: # x86-64 with newer extensions tests - name: Compile x86-64-avx2 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-avx2 build - name: Compile x86-64-bmi2 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-bmi2 build - name: Compile x86-64-avx512 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-avx512 build - name: Compile x86-64-vnni512 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-vnni512 build - name: Compile x86-64-vnni256 build + if: ${{ ! matrix.config.m1 }} run: | make clean make -j2 ARCH=x86-64-vnni256 build + + - name: Compile apple-silicon build + if: matrix.config.m1 + run: | + make clean + make -j2 ARCH=apple-silicon build diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/stockfish_test.yml index cff3ef1b184..867099ee58c 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/stockfish_test.yml @@ -43,16 +43,16 @@ jobs: compiler: g++ comp: gcc run_riscv64_tests: true - base_image: 'riscv64/alpine:edge' - platform: linux/riscv64 + base_image: "riscv64/alpine:edge" + platform: linux/riscv64 shell: bash - name: Linux GCC ppc64 os: ubuntu-22.04 compiler: g++ comp: gcc run_ppc64_tests: true - base_image: 'ppc64le/alpine:latest' - platform: linux/ppc64le + base_image: "ppc64le/alpine:latest" + platform: linux/ppc64le shell: bash - name: MacOS 13 Apple Clang os: macos-13 @@ -60,6 +60,13 @@ jobs: comp: clang run_64bit_tests: true shell: bash + - name: MacOS 14 Apple Clang M1 + os: macos-14 + compiler: clang++ + comp: clang + run_64bit_tests: false + run_m1_tests: true + shell: bash - name: MacOS 13 GCC 11 os: macos-13 compiler: g++-11 @@ -281,6 +288,13 @@ jobs: make -j2 ARCH=general-64 build ../tests/signature.sh $benchref + - name: Test apple-silicon build + if: matrix.config.run_m1_tests + run: | + make clean + make -j2 ARCH=apple-silicon build + ../tests/signature.sh $benchref + # armv8 tests - name: Test armv8 build From 56b342f9b27827e77cb9e898aa2ed69b628d672f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 27 Jan 2024 21:55:00 +0300 Subject: [PATCH 0374/1309] Simplify the extension formula Simplify the extension formula in the case of cutNode by removing the depth condition and always setting extension to -2. Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 277280 W: 70760 L: 70802 D: 135718 Ptnml(0-2): 971, 31775, 73153, 31807, 934 https://tests.stockfishchess.org/tests/view/65ad08f779aa8af82b979dd6 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 452976 W: 112992 L: 113215 D: 226769 Ptnml(0-2): 266, 51041, 124112, 50788, 281 https://tests.stockfishchess.org/tests/view/65ae466fc865510db026a760 closes https://github.com/official-stockfish/Stockfish/pull/5021 Bench: 1492957 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0a07c8bb972..6eb05081092 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1065,7 +1065,7 @@ Value Search::Worker::search( // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) - extension = depth < 20 ? -2 : -1; + extension = -2; // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) else if (ttValue <= value) From f2b6b5cfc9bd27c0595520c96f6e1f8416296f75 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Thu, 1 Feb 2024 18:48:45 +0000 Subject: [PATCH 0375/1309] Introduce Triple Extensions This replaces singularquietLMR with triple instead of double extending non-capture ttmoves that have value far below singularBeta. This threshold value is initially set to 200, there is scope for more scaling by reducing it as occured with double extensions. Passed STC: https://tests.stockfishchess.org/tests/view/65b683b8c865510db0274074 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 222912 W: 58141 L: 57535 D: 107236 Ptnml(0-2): 1063, 26244, 56154, 27014, 981 Passed LTC: https://tests.stockfishchess.org/tests/view/65bae6d4c865510db0278eb5 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66306 W: 16825 L: 16440 D: 33041 Ptnml(0-2): 40, 7374, 17952, 7735, 52 closes https://github.com/official-stockfish/Stockfish/pull/5027 bench 1394701 --- src/search.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6eb05081092..d47582a57f1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -522,7 +522,7 @@ Value Search::Worker::search( Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture, singularQuietLMR; + bool givesCheck, improving, priorCapture; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -900,7 +900,7 @@ Value Search::Worker::search( contHist, &thisThread->pawnHistory, countermove, ss->killers); value = bestValue; - moveCountPruning = singularQuietLMR = false; + moveCountPruning = false; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1034,13 +1034,12 @@ Value Search::Worker::search( if (value < singularBeta) { - extension = 1; - singularQuietLMR = !ttCapture; + extension = 1; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 12) + if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 15) { - extension = 2; + extension = 2 + (value < singularBeta - 200 && !ttCapture); depth += depth < 15; } } @@ -1091,7 +1090,7 @@ Value Search::Worker::search( // Add extension to new depth newDepth += extension; - ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension == 2); + ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension >= 2); // Speculative prefetch as early as possible prefetch(tt.first_entry(pos.key_after(move))); @@ -1125,10 +1124,6 @@ Value Search::Worker::search( if (PvNode && tte->bound() != BOUND_UPPER) r--; - // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo) - if (singularQuietLMR) - r--; - // Increase reduction on repetition (~1 Elo) if (move == (ss - 4)->currentMove && pos.has_repeated()) r += 2; From e815227c3081269f7b37538cf5f32c838991db29 Mon Sep 17 00:00:00 2001 From: gab8192 Date: Thu, 1 Feb 2024 20:38:48 +0100 Subject: [PATCH 0376/1309] Simplify LMR condition Apply LMR on captures the same way it is applied on quiets Passed Non-Reg STC: https://tests.stockfishchess.org/tests/view/65bbf39bc865510db027a14a LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 77152 W: 19970 L: 19791 D: 37391 Ptnml(0-2): 304, 9159, 19496, 9288, 329 Passed Non-Reg LTC: https://tests.stockfishchess.org/tests/view/65bc8889c865510db027ac9e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 103230 W: 25997 L: 25858 D: 51375 Ptnml(0-2): 71, 11687, 27958, 11830, 69 Hit rate of removed condition (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1)) Total 1253801 Hits 1228904 Hit Rate (%) 98.0143 Hit rate of previous LMR (depth >= 2 && moveCount > 1 + rootNode && ...) Total 1253801 Hits 727234 Hit Rate (%) 58.0023 Hit rate of simplified LMR (depth >= 2 && moveCount > 1 + rootNode) Total 1201839 Hits 713540 Hit Rate (%) 59.3707 closes https://github.com/official-stockfish/Stockfish/pull/5028 Bench: 1438224 --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d47582a57f1..538f0f30ae2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1146,11 +1146,7 @@ Value Search::Worker::search( r -= ss->statScore / 15373; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) - // We use various heuristics for the sons of a node after the first son has - // been searched. In general, we would like to reduce them, but there are many - // cases where we extend a son if it has good chances to be "interesting". - if (depth >= 2 && moveCount > 1 + rootNode - && (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1))) + if (depth >= 2 && moveCount > 1 + rootNode) { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension From ededadcd6f7fbb9eb122f5fe336025cc4b11753b Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Thu, 1 Feb 2024 08:13:06 +0800 Subject: [PATCH 0377/1309] VVLTC search tune Search parameters were tuned at 60+0.6 8-thread. Link to the tuning attempt: https://tests.stockfishchess.org/tests/view/65b84e8dc865510db0276030 The most significant change is the triple extension parameter, from 200 to 78. This presumably improves scaling. Additionally, the value < singularBeta - 2 condition for double extensions was removed. This can simply be considered a parameter tweak from 2 to 0. Passed VVLTC: https://tests.stockfishchess.org/tests/view/65baec69c865510db0278f19 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 26136 W: 6564 L: 6305 D: 13267 Ptnml(0-2): 2, 2413, 7977, 2676, 0 Passed VVLTC vs passed PR #5027: https://tests.stockfishchess.org/tests/view/65bc2adfc865510db027a561 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 52968 W: 13372 L: 13046 D: 26550 Ptnml(0-2): 4, 4944, 16265, 5264, 7 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65be5514c865510db027cbc5 closes https://github.com/official-stockfish/Stockfish/pull/5029 Bench: 1478189 --- src/search.cpp | 74 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 538f0f30ae2..e57f2557cf8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 114 - 47 * noTtCutNode; + Value futilityMult = 116 - 47 * noTtCutNode; return (futilityMult * d - 3 * futilityMult / 2 * improving); } @@ -66,15 +66,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 14095; + v += cv * std::abs(cv) / 12890; return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(265 * d - 349, 1112); } +int stat_bonus(Depth d) { return std::min(253 * d - 356, 1117); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(482 * d - 326, 1172); } +int stat_malus(Depth d) { return std::min(517 * d - 308, 1206); } // 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); } @@ -297,12 +297,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(9) + int(avg) * avg / 13181; + delta = Value(9) + int(avg) * avg / 12480; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, int(VALUE_INFINITE)); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 132 * avg / (std::abs(avg) + 98); + optimism[us] = 131 * avg / (std::abs(avg) + 95); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -726,7 +726,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1680, 1406); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1661, 1495); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -747,7 +747,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 435 - (327 - 167 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 450 - (332 - 160 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -760,20 +760,20 @@ Value Search::Worker::search( && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - (ss - 1)->statScore / 327 >= beta - && eval >= beta && eval < 27734 // smaller than TB wins + && eval >= beta && eval < 28702 // smaller than TB wins && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17787 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 22 * depth + 313 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17379 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 329 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 148, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -787,7 +787,7 @@ Value Search::Worker::search( // Do not return unproven mate or TB scores if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { - if (thisThread->nmpMinPly || depth < 15) + if (thisThread->nmpMinPly || depth < 16) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed @@ -820,7 +820,7 @@ Value Search::Worker::search( if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 173 - 73 * improving; + probCutBeta = beta + 182 - 68 * improving; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -880,7 +880,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 427; + probCutBeta = beta + 446; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -963,7 +963,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 277 + 298 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 279 + 295 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -971,7 +971,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -203 * depth)) + if (!pos.see_ge(move, -204 * depth)) continue; } else @@ -983,17 +983,17 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4195 * depth) + if (lmrDepth < 6 && history < -4215 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 6992; + lmrDepth += history / 6658; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 63 ? 137 : 64) - + 111 * lmrDepth + && ss->staticEval + (bestValue < ss->staticEval - 58 ? 139 : 55) + + 121 * lmrDepth <= alpha) continue; @@ -1020,11 +1020,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 31) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (58 + 52 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (62 + 52 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1037,10 +1037,10 @@ Value Search::Worker::search( extension = 1; // Avoid search explosion by limiting the number of double extensions - if (!PvNode && value < singularBeta - 2 && ss->doubleExtensions <= 15) + if (!PvNode && ss->doubleExtensions <= 16) { - extension = 2 + (value < singularBeta - 200 && !ttCapture); - depth += depth < 15; + extension = 2 + (value < singularBeta - 78 && !ttCapture); + depth += depth < 16; } } @@ -1077,14 +1077,14 @@ Value Search::Worker::search( // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][move.to_sq()] >= 4111) + && (*contHist[0])[movedPiece][move.to_sq()] >= 4339) extension = 1; // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4484) + > 4356) extension = 1; } @@ -1140,10 +1140,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4119; + + (*contHist[3])[movedPiece][move.to_sq()] - 4409; // Decrease/increase reduction for moves with a good/bad history (~25 Elo) - r -= ss->statScore / 15373; + r -= ss->statScore / 14894; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1162,7 +1162,7 @@ Value Search::Worker::search( { // 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 + 51 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 49 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1278,7 +1278,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13195 && value > -12346) + if (depth > 2 && depth < 13 && beta < 13710 && value > -12589) depth -= 2; assert(depth > 0); @@ -1317,8 +1317,8 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -16797) - + ((ss - 1)->moveCount > 10); + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15401) + + ((ss - 1)->moveCount > 11); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1476,7 +1476,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 186; + futilityBase = ss->staticEval + 204; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1556,7 +1556,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -76)) + if (!pos.see_ge(move, -75)) continue; } @@ -1711,7 +1711,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 177 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 59691d46a13880534802fe7e610f56813f0e47fc Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 4 Feb 2024 12:59:26 +0300 Subject: [PATCH 0378/1309] Assorted trivial cleanups Renaming doubleExtensions variable to multiExtensions, since now we have also triple extensions. Some extra cleanups. Recent tests used to measure the elo worth: https://tests.stockfishchess.org/tests/view/659fd0c379aa8af82b96abc3 https://tests.stockfishchess.org/tests/view/65a8f3da79aa8af82b9751e3 https://tests.stockfishchess.org/tests/view/65b51824c865510db0272740 https://tests.stockfishchess.org/tests/view/65b58fbfc865510db0272f5b closes https://github.com/official-stockfish/Stockfish/pull/5032 No functional change --- src/nnue/nnue_feature_transformer.h | 4 +--- src/search.cpp | 20 ++++++++++---------- src/search.h | 11 +++++------ 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 9a162ac9853..3399b82df6a 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -281,7 +281,7 @@ class FeatureTransformer { reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); vec_t* out = reinterpret_cast(output + offset); - for (IndexType j = 0; j < NumOutputChunks; j += 1) + for (IndexType j = 0; j < NumOutputChunks; ++j) { const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); @@ -676,9 +676,7 @@ class FeatureTransformer { update_accumulator_incremental(pos, oldest_st, states_to_update); } else - { update_accumulator_refresh(pos); - } } template diff --git a/src/search.cpp b/src/search.cpp index e57f2557cf8..336678c0617 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -571,7 +571,7 @@ Value Search::Worker::search( (ss + 1)->excludedMove = bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; - ss->doubleExtensions = (ss - 1)->doubleExtensions; + ss->multipleExtensions = (ss - 1)->multipleExtensions; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; @@ -1036,8 +1036,8 @@ Value Search::Worker::search( { extension = 1; - // Avoid search explosion by limiting the number of double extensions - if (!PvNode && ss->doubleExtensions <= 16) + // We make sure to limit the extensions in some way to avoid a search explosion + if (!PvNode && ss->multipleExtensions <= 16) { extension = 2 + (value < singularBeta - 78 && !ttCapture); depth += depth < 16; @@ -1090,7 +1090,7 @@ Value Search::Worker::search( // Add extension to new depth newDepth += extension; - ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension >= 2); + ss->multipleExtensions = (ss - 1)->multipleExtensions + (extension >= 2); // Speculative prefetch as early as possible prefetch(tt.first_entry(pos.key_after(move))); @@ -1142,7 +1142,7 @@ Value Search::Worker::search( + (*contHist[1])[movedPiece][move.to_sq()] + (*contHist[3])[movedPiece][move.to_sq()] - 4409; - // Decrease/increase reduction for moves with a good/bad history (~25 Elo) + // Decrease/increase reduction for moves with a good/bad history (~8 Elo) r -= ss->statScore / 14894; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) @@ -1150,7 +1150,7 @@ Value Search::Worker::search( { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension - // beyond the first move depth. This may lead to hidden double extensions. + // beyond the first move depth. This may lead to hidden multiple extensions. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r, newDepth + 1)); @@ -1371,8 +1371,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); - // Check if we have an upcoming move that draws by repetition, or - // if the opponent had an alternative move earlier to this position. + // Check if we have an upcoming move that draws by repetition, or if + // the opponent had an alternative move earlier to this position. (~1 Elo) if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(this->nodes); @@ -1520,7 +1520,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, 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. + // than alpha we can prune this move. (~2 Elo) if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); @@ -1528,7 +1528,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, } // If static eval is much lower than alpha and move is not winning material - // we can prune this move. + // we can prune this move. (~2 Elo) if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) { bestValue = std::max(bestValue, futilityBase); diff --git a/src/search.h b/src/search.h index c8534b40177..97cb2ca40b2 100644 --- a/src/search.h +++ b/src/search.h @@ -67,7 +67,7 @@ struct Stack { bool inCheck; bool ttPv; bool ttHit; - int doubleExtensions; + int multipleExtensions; int cutoffCnt; }; @@ -136,9 +136,8 @@ struct SharedState { class Worker; -// Null Object Pattern, implement a common interface -// for the SearchManagers. A Null Object will be given to -// non-mainthread workers. +// Null Object Pattern, implement a common interface for the SearchManagers. +// A Null Object will be given to non-mainthread workers. class ISearchManager { public: virtual ~ISearchManager() {} @@ -185,8 +184,8 @@ class Worker { // Reset histories, usually before a new game void clear(); - // Called when the program receives the UCI 'go' - // command. It searches from the root position and outputs the "bestmove". + // Called when the program receives the UCI 'go' command. + // It searches from the root position and outputs the "bestmove". void start_searching(); bool is_mainthread() const { return thread_idx == 0; } From a20726eb0b6c049e191ce0f04dac4f4f923efcee Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 9 Feb 2024 17:56:58 +0100 Subject: [PATCH 0379/1309] Refactor the CI workflows This refactors the CI workflows to group some logic and makes sure that all (pre)release binaries are actually tested. The screenshot below shows the execution logic of the reworked ci, https://github.com/Disservin/Stockfish/actions/runs/7773581379. You can also hover over the cards to see the execution flow. The `matrix.json` and `arm_matrix.json` define the binaries which will be uploaded to GitHub. Afterwards a matrix is created and each job compiles a profile guided build for that arch and uploads that as an artifact to GitHub. The Binaries/ARM_Binaries workflow's are called when the previous step has been completed, and uploads all artifacts to the (pre)release. This also fixes some indentations and renames the workflows, see https://github.com/official-stockfish/Stockfish/actions, where every workflow is called `Stockfish` vs https://github.com/Disservin/Stockfish/actions. It also increases the parallel compilation used for make from `-j2 to -j4`. It now also prevents the prerelease action from running on forks. A test release can be viewed here https://github.com/Disservin/Stockfish/releases. closes https://github.com/official-stockfish/Stockfish/pull/5035 No functional change --- .github/ci/arm_matrix.json | 51 ++++ .github/{workflows => ci}/libcxx17.imp | 0 .github/ci/matrix.json | 160 +++++++++++ .github/workflows/arm_compilation.yml | 94 +++++++ ...fish_format_check.yml => clang-format.yml} | 22 +- .github/workflows/codeql.yml | 54 ++-- .github/workflows/compilation.yml | 89 +++++++ .../{stockfish_analyzers.yml => iwyu.yml} | 4 +- ...tockfish_sanitizers.yml => sanitizers.yml} | 7 +- .github/workflows/stockfish.yml | 60 +++-- .github/workflows/stockfish_arm_binaries.yml | 170 ------------ .github/workflows/stockfish_binaries.yml | 252 ------------------ .github/workflows/stockfish_compile_test.yml | 117 -------- .../{stockfish_test.yml => tests.yml} | 49 ++-- .github/workflows/upload_binaries.yml | 105 ++++++++ 15 files changed, 612 insertions(+), 622 deletions(-) create mode 100644 .github/ci/arm_matrix.json rename .github/{workflows => ci}/libcxx17.imp (100%) create mode 100644 .github/ci/matrix.json create mode 100644 .github/workflows/arm_compilation.yml rename .github/workflows/{stockfish_format_check.yml => clang-format.yml} (84%) create mode 100644 .github/workflows/compilation.yml rename .github/workflows/{stockfish_analyzers.yml => iwyu.yml} (92%) rename .github/workflows/{stockfish_sanitizers.yml => sanitizers.yml} (93%) delete mode 100644 .github/workflows/stockfish_arm_binaries.yml delete mode 100644 .github/workflows/stockfish_binaries.yml delete mode 100644 .github/workflows/stockfish_compile_test.yml rename .github/workflows/{stockfish_test.yml => tests.yml} (91%) create mode 100644 .github/workflows/upload_binaries.yml diff --git a/.github/ci/arm_matrix.json b/.github/ci/arm_matrix.json new file mode 100644 index 00000000000..70f2efaa21e --- /dev/null +++ b/.github/ci/arm_matrix.json @@ -0,0 +1,51 @@ +{ + "config": [ + { + "name": "Android NDK aarch64", + "os": "ubuntu-22.04", + "simple_name": "android", + "compiler": "aarch64-linux-android21-clang++", + "emu": "qemu-aarch64", + "comp": "ndk", + "shell": "bash", + "archive_ext": "tar" + }, + { + "name": "Android NDK arm", + "os": "ubuntu-22.04", + "simple_name": "android", + "compiler": "armv7a-linux-androideabi21-clang++", + "emu": "qemu-arm", + "comp": "ndk", + "shell": "bash", + "archive_ext": "tar" + } + ], + "binaries": ["armv8-dotprod", "armv8", "armv7", "armv7-neon"], + "exclude": [ + { + "binaries": "armv8-dotprod", + "config": { + "compiler": "armv7a-linux-androideabi21-clang++" + } + }, + { + "binaries": "armv8", + "config": { + "compiler": "armv7a-linux-androideabi21-clang++" + } + }, + { + "binaries": "armv7", + "config": { + "compiler": "aarch64-linux-android21-clang++" + } + }, + { + "binaries": "armv7-neon", + "config": { + "compiler": "aarch64-linux-android21-clang++" + } + } + ] +} diff --git a/.github/workflows/libcxx17.imp b/.github/ci/libcxx17.imp similarity index 100% rename from .github/workflows/libcxx17.imp rename to .github/ci/libcxx17.imp diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json new file mode 100644 index 00000000000..c6563eadf2f --- /dev/null +++ b/.github/ci/matrix.json @@ -0,0 +1,160 @@ +{ + "config": [ + { + "name": "Ubuntu 20.04 GCC", + "os": "ubuntu-20.04", + "simple_name": "ubuntu", + "compiler": "g++", + "comp": "gcc", + "shell": "bash", + "archive_ext": "tar", + "sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future --" + }, + { + "name": "MacOS 13 Apple Clang", + "os": "macos-13", + "simple_name": "macos", + "compiler": "clang++", + "comp": "clang", + "shell": "bash", + "archive_ext": "tar" + }, + { + "name": "MacOS 14 Apple Clang M1", + "os": "macos-14", + "simple_name": "macos-m1", + "compiler": "clang++", + "comp": "clang", + "shell": "bash", + "archive_ext": "tar" + }, + { + "name": "Windows 2022 Mingw-w64 GCC x86_64", + "os": "windows-2022", + "simple_name": "windows", + "compiler": "g++", + "comp": "mingw", + "msys_sys": "mingw64", + "msys_env": "x86_64-gcc", + "shell": "msys2 {0}", + "ext": ".exe", + "sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future --", + "archive_ext": "zip" + } + ], + "binaries": [ + "x86-64", + "x86-64-sse41-popcnt", + "x86-64-avx2", + "x86-64-bmi2", + "x86-64-avxvnni", + "x86-64-avx512", + "x86-64-vnni256", + "x86-64-vnni512", + "apple-silicon" + ], + "exclude": [ + { + "binaries": "x86-64", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-sse41-popcnt", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avx2", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-bmi2", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avx512", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-vnni256", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-vnni512", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "ubuntu-20.04": null + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "x86-64-avx512", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "x86-64-vnni256", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "x86-64-vnni512", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "windows-2022" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "ubuntu-20.04" + } + } + ] +} diff --git a/.github/workflows/arm_compilation.yml b/.github/workflows/arm_compilation.yml new file mode 100644 index 00000000000..ef141971d2b --- /dev/null +++ b/.github/workflows/arm_compilation.yml @@ -0,0 +1,94 @@ +name: Compilation +on: + workflow_call: + inputs: + matrix: + type: string + required: true +jobs: + Compilation: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EMU: ${{ matrix.config.emu }} + EXT: ${{ matrix.config.ext }} + BINARY: ${{ matrix.binaries }} + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.matrix) }} + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download required linux packages + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install qemu-user + + - name: Install NDK + if: runner.os == 'Linux' + run: | + if [ $COMP == ndk ]; then + NDKV="21.4.7075529" + ANDROID_ROOT=/usr/local/lib/android + ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk + SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager + echo "y" | $SDKMANAGER "ndk;$NDKV" + ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV + ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin + echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV + fi + + - name: Extract the bench number from the commit history + run: | + for hash in $(git rev-list -100 HEAD); do + benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true + done + [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" + + - name: Download the used network from the fishtest framework + run: make net + + - name: Check compiler + run: | + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + fi + $COMPILER -v + + - name: Test help target + run: make help + + - name: Check git + run: git --version + + # Compile profile guided builds + + - name: Compile ${{ matrix.binaries }} build + run: | + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + fi + make clean + make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU + make strip ARCH=$BINARY COMP=$COMP + WINE_PATH=$EMU ../tests/signature.sh $benchref + mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT + + - name: Remove non src files + run: git clean -fx + + - name: Upload artifact for (pre)-release + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + path: . diff --git a/.github/workflows/stockfish_format_check.yml b/.github/workflows/clang-format.yml similarity index 84% rename from .github/workflows/stockfish_format_check.yml rename to .github/workflows/clang-format.yml index 7a47ab6f406..0eb3fc70dab 100644 --- a/.github/workflows/stockfish_format_check.yml +++ b/.github/workflows/clang-format.yml @@ -3,17 +3,17 @@ # executes no shell script nor runs make. # Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ -name: Stockfish +name: Clang-Format on: pull_request_target: branches: - - 'master' + - "master" paths: - - '**.cpp' - - '**.h' + - "**.cpp" + - "**.h" jobs: - Stockfish: - name: clang-format check + Clang-Format: + name: Clang-Format runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 @@ -21,16 +21,16 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Run clang-format style check - uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 + uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 id: clang-format continue-on-error: true with: - clang-format-version: '17' - exclude-regex: 'incbin' + clang-format-version: "17" + exclude-regex: "incbin" - name: Comment on PR if: steps.clang-format.outcome == 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | clang-format 17 needs to be run on this PR. @@ -42,7 +42,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome != 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 with: message: | _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d6da8a1c288..1c3a3a6b468 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,12 +2,12 @@ name: "CodeQL" on: push: - branches: [ 'master' ] + branches: ["master"] pull_request: # The branches below must be a subset of the branches above - branches: [ 'master' ] + branches: ["master"] schedule: - - cron: '17 18 * * 1' + - cron: "17 18 * * 1" jobs: analyze: @@ -21,33 +21,33 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'cpp' ] + language: ["cpp"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Use only 'java' to analyze code written in Java, Kotlin, or both # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - name: Build - working-directory: src - run: make -j build ARCH=x86-64-modern - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Build + working-directory: src + run: make -j build ARCH=x86-64-modern + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml new file mode 100644 index 00000000000..964b5f05edc --- /dev/null +++ b/.github/workflows/compilation.yml @@ -0,0 +1,89 @@ +name: Compilation +on: + workflow_call: + inputs: + matrix: + type: string + required: true +jobs: + Compilation: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EXT: ${{ matrix.config.ext }} + NAME: ${{ matrix.config.simple_name }} + BINARY: ${{ matrix.binaries }} + SDE: ${{ matrix.config.sde }} + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.matrix) }} + defaults: + run: + working-directory: src + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v4 + + - name: Install fixed GCC on Linux + if: runner.os == 'Linux' + uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 + with: + version: 11 + + - name: Setup msys and install required packages + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.config.msys_sys }} + install: mingw-w64-${{ matrix.config.msys_env }} make git zip + + - name: Download SDE package + if: runner.os == 'Linux' || runner.os == 'Windows' + uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 + with: + environmentVariableName: SDE_DIR + sdeVersion: 9.27.0 + + - name: Download the used network from the fishtest framework + run: make net + + - name: Check compiler + run: $COMPILER -v + + - name: Test help target + run: make help + + - name: Check git + run: git --version + + - name: Check compiler + run: $COMPILER -v + + - name: Show g++ cpu info + if: runner.os != 'macOS' + run: g++ -Q -march=native --help=target + + - name: Show clang++ cpu info + if: runner.os == 'macOS' + run: clang++ -E - -march=native -### + + # x86-64 with newer extensions tests + + - name: Compile ${{ matrix.config.binaries }} build + run: | + make clean + make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE" + make strip ARCH=$BINARY COMP=$COMP + WINE_PATH="$SDE" ../tests/signature.sh $benchref + mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT + + - name: Remove non src files + run: git clean -fx + + - name: Upload artifact for (pre)-release + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + path: . diff --git a/.github/workflows/stockfish_analyzers.yml b/.github/workflows/iwyu.yml similarity index 92% rename from .github/workflows/stockfish_analyzers.yml rename to .github/workflows/iwyu.yml index f54cdd7ca6b..0552a598c8f 100644 --- a/.github/workflows/stockfish_analyzers.yml +++ b/.github/workflows/iwyu.yml @@ -1,4 +1,4 @@ -name: Stockfish +name: IWYU on: workflow_call: jobs: @@ -44,4 +44,4 @@ jobs: make analyze COMP=clang CXX=include-what-you-use - CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/workflows/libcxx17.imp' -Xiwyu --error" + CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/ci/libcxx17.imp' -Xiwyu --error" diff --git a/.github/workflows/stockfish_sanitizers.yml b/.github/workflows/sanitizers.yml similarity index 93% rename from .github/workflows/stockfish_sanitizers.yml rename to .github/workflows/sanitizers.yml index e3f046178f5..7ab1f997ad7 100644 --- a/.github/workflows/stockfish_sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -1,8 +1,8 @@ -name: Stockfish +name: Sanitizers on: workflow_call: jobs: - Stockfish: + Test-under-sanitizers: name: ${{ matrix.sanitizers.name }} runs-on: ${{ matrix.config.os }} env: @@ -10,6 +10,7 @@ jobs: COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: + fail-fast: false matrix: config: - name: Ubuntu 22.04 GCC @@ -60,5 +61,5 @@ jobs: run: | export CXXFLAGS="-O1 -fno-inline" make clean - make -j2 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null + make -j4 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index e8db52351dd..1c0dd0e1336 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -1,8 +1,8 @@ name: Stockfish on: push: - tags: - - '*' + tags: + - "*" branches: - master - tools @@ -13,7 +13,7 @@ on: - tools jobs: Prerelease: - if: github.ref == 'refs/heads/master' + if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')) runs-on: ubuntu-latest steps: # returns null if no pre-release exists @@ -25,24 +25,52 @@ jobs: echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV - # delete old previous pre-release and tag - - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 + # delete old previous pre-release and tag + - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 if: env.COMMIT_SHA != 'null' with: tag_name: ${{ env.COMMIT_SHA }} github_token: ${{ secrets.GITHUB_TOKEN }} - - Analyzers: - uses: ./.github/workflows/stockfish_analyzers.yml + Matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }} + steps: + - uses: actions/checkout@v4 + - id: set-matrix + run: | + TASKS=$(echo $(cat .github/ci/matrix.json) ) + echo "MATRIX=$TASKS" >> $GITHUB_OUTPUT + - id: set-arm-matrix + run: | + TASKS_ARM=$(echo $(cat .github/ci/arm_matrix.json) ) + echo "ARM_MATRIX=$TASKS_ARM" >> $GITHUB_OUTPUT + Compilation: + needs: [Matrix] + uses: ./.github/workflows/compilation.yml + with: + matrix: ${{ needs.Matrix.outputs.matrix }} + ARMCompilation: + needs: [Matrix] + uses: ./.github/workflows/arm_compilation.yml + with: + matrix: ${{ needs.Matrix.outputs.arm_matrix }} + IWYU: + uses: ./.github/workflows/iwyu.yml Sanitizers: - uses: ./.github/workflows/stockfish_sanitizers.yml + uses: ./.github/workflows/sanitizers.yml Tests: - uses: ./.github/workflows/stockfish_test.yml - Compiles: - uses: ./.github/workflows/stockfish_compile_test.yml + uses: ./.github/workflows/tests.yml Binaries: - if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') - uses: ./.github/workflows/stockfish_binaries.yml + if: github.repository == 'official-stockfish/Stockfish' + needs: [Matrix, Prerelease, Compilation] + uses: ./.github/workflows/upload_binaries.yml + with: + matrix: ${{ needs.Matrix.outputs.matrix }} ARM_Binaries: - if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag') - uses: ./.github/workflows/stockfish_arm_binaries.yml + if: github.repository == 'official-stockfish/Stockfish' + needs: [Matrix, Prerelease, ARMCompilation] + uses: ./.github/workflows/upload_binaries.yml + with: + matrix: ${{ needs.Matrix.outputs.arm_matrix }} diff --git a/.github/workflows/stockfish_arm_binaries.yml b/.github/workflows/stockfish_arm_binaries.yml deleted file mode 100644 index 203c00f2ce2..00000000000 --- a/.github/workflows/stockfish_arm_binaries.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: Stockfish -on: - workflow_call: -jobs: - Stockfish: - name: ${{ matrix.config.name }} ${{ matrix.binaries }} - runs-on: ${{ matrix.config.os }} - env: - COMPILER: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} - EMU: ${{ matrix.config.emu }} - EXT: ${{ matrix.config.ext }} - OS: ${{ matrix.config.os }} - BINARY: ${{ matrix.binaries }} - strategy: - matrix: - config: - - name: Android NDK aarch64 - os: ubuntu-22.04 - compiler: aarch64-linux-android21-clang++ - emu: qemu-aarch64 - comp: ndk - shell: bash - - name: Android NDK arm - os: ubuntu-22.04 - compiler: armv7a-linux-androideabi21-clang++ - emu: qemu-arm - comp: ndk - shell: bash - binaries: - - armv8-dotprod - - armv8 - - armv7 - - armv7-neon - exclude: - - binaries: armv8-dotprod - config: {compiler: armv7a-linux-androideabi21-clang++} - - binaries: armv8 - config: {compiler: armv7a-linux-androideabi21-clang++} - - binaries: armv7 - config: {compiler: aarch64-linux-android21-clang++} - - binaries: armv7-neon - config: {compiler: aarch64-linux-android21-clang++} - defaults: - run: - working-directory: src - shell: ${{ matrix.config.shell }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Download required linux packages - if: runner.os == 'Linux' - run: | - sudo apt update - sudo apt install qemu-user - - - name: Install NDK - if: runner.os == 'Linux' - run: | - if [ $COMP == ndk ]; then - NDKV="21.4.7075529" - ANDROID_ROOT=/usr/local/lib/android - ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk - SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager - echo "y" | $SDKMANAGER "ndk;$NDKV" - ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV - ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin - echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV - fi - - - name: Extract the bench number from the commit history - run: | - for hash in $(git rev-list -100 HEAD); do - benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true - done - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" - - - name: Download the used network from the fishtest framework - run: make net - - - name: Check compiler - run: | - if [ $COMP == ndk ]; then - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH - fi - $COMPILER -v - - - name: Test help target - run: make help - - - name: Check git - run: git --version - - # Compile profile guided builds - - - name: Compile ${{ matrix.binaries }} build - run: | - if [ $COMP == ndk ]; then - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH - export LDFLAGS="-static -Wno-unused-command-line-argument" - fi - make clean - make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU - make strip ARCH=$BINARY COMP=$COMP - WINE_PATH=$EMU ../tests/signature.sh $benchref - mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT - - - name: Remove non src files - run: rm -f *.o .depend *.nnue - - - name: Download wiki - run: | - git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki - cd ../wiki - rm -rf .git - - - name: Create tar archive. - run: | - cd .. - mkdir stockfish - cp -r wiki stockfish/ - cp -r src stockfish/ - cp stockfish-android-$BINARY$EXT stockfish/ - cp "Top CPU Contributors.txt" stockfish/ - cp Copying.txt stockfish/ - cp AUTHORS stockfish/ - cp CITATION.cff stockfish/ - cp README.md stockfish/ - cp CONTRIBUTING.md stockfish/ - tar -cvf stockfish-android-$BINARY.tar stockfish - - - name: Upload binaries - uses: actions/upload-artifact@v3 - with: - name: stockfish-android-${{ matrix.binaries }} - path: stockfish-android-${{ matrix.binaries }}.tar - - - name: Release - if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - files: stockfish-android-${{ matrix.binaries }}.tar - - - name: Get last commit sha - id: last_commit - run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV - - - name: Get commit date - id: commit_date - run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV - - # Make sure that an old ci which still runs on master doesn't recreate a prerelease - - name: Check Pullable Commits - id: check_commits - run: | - git fetch - CHANGES=$(git rev-list HEAD..origin/master --count) - echo "CHANGES=$CHANGES" >> $GITHUB_ENV - - - name: Prerelease - if: github.ref_name == 'master' && env.CHANGES == '0' - continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - prerelease: true - files: stockfish-android-${{ matrix.binaries }}.tar diff --git a/.github/workflows/stockfish_binaries.yml b/.github/workflows/stockfish_binaries.yml deleted file mode 100644 index 2911badacf7..00000000000 --- a/.github/workflows/stockfish_binaries.yml +++ /dev/null @@ -1,252 +0,0 @@ -name: Stockfish -on: - workflow_call: -jobs: - Stockfish: - name: ${{ matrix.config.name }} ${{ matrix.binaries }} - runs-on: ${{ matrix.config.os }} - env: - COMPILER: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} - EXT: ${{ matrix.config.ext }} - SDE: ${{ matrix.config.sde }} - NAME: ${{ matrix.config.simple_name }} - BINARY: ${{ matrix.binaries }} - strategy: - fail-fast: false - matrix: - config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 - simple_name: ubuntu - compiler: g++ - comp: gcc - shell: bash - archive_ext: tar - sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future -- - - name: MacOS 13 Apple Clang - os: macos-13 - simple_name: macos - compiler: clang++ - comp: clang - shell: bash - archive_ext: tar - - name: MacOS 14 Apple Clang M1 - os: macos-14 - simple_name: macos-m1 - compiler: clang++ - comp: clang - shell: bash - archive_ext: tar - - name: Windows 2022 Mingw-w64 GCC x86_64 - os: windows-2022 - simple_name: windows - compiler: g++ - comp: mingw - msys_sys: mingw64 - msys_env: x86_64-gcc - shell: msys2 {0} - ext: .exe - sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future -- - archive_ext: zip - binaries: - - x86-64 - - x86-64-sse41-popcnt - - x86-64-avx2 - - x86-64-bmi2 - - x86-64-avxvnni - - x86-64-avx512 - - x86-64-vnni256 - - x86-64-vnni512 - - apple-silicon - exclude: - # Apple M1 - - binaries: x86-64 - config: { os: macos-14 } - - binaries: x86-64-sse41-popcnt - config: { os: macos-14 } - - binaries: x86-64-avx2 - config: { os: macos-14 } - - binaries: x86-64-bmi2 - config: { os: macos-14 } - - binaries: x86-64-avxvnni - config: { os: macos-14 } - - binaries: x86-64-avxvnni - config: { os: macos-14 } - - binaries: x86-64-avx512 - config: { os: macos-14 } - - binaries: x86-64-vnni256 - config: { os: macos-14 } - - binaries: x86-64-vnni512 - config: { os: macos-14 } - - - binaries: x86-64-avxvnni - config: { ubuntu-20.04 } - - # Apple x86_64 (no sde) - - binaries: x86-64-avxvnni - config: { os: macos-13 } - - binaries: x86-64-avx512 - config: { os: macos-13 } - - binaries: x86-64-vnni256 - config: { os: macos-13 } - - binaries: x86-64-vnni512 - config: { os: macos-13 } - - # Apple silicon from windows, macos-13 and ubuntu - - binaries: apple-silicon - config: { os: windows-2022 } - - binaries: apple-silicon - config: { os: macos-13 } - - binaries: apple-silicon - config: { os: ubuntu-20.04 } - defaults: - run: - working-directory: src - shell: ${{ matrix.config.shell }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Download required macOS packages - if: runner.os == 'macOS' - run: brew install coreutils - - - name: Install fixed GCC on Linux - if: runner.os == 'Linux' - uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3 - with: - version: 11 - - - name: Setup msys and install required packages - if: runner.os == 'Windows' - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.config.msys_sys }} - install: mingw-w64-${{ matrix.config.msys_env }} make git zip - - - name: Download SDE package - if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 - with: - environmentVariableName: SDE_DIR - sdeVersion: 9.27.0 - - - name: Download the used network from the fishtest framework - run: make net - - - name: Extract the bench number from the commit history - run: | - for hash in $(git rev-list -100 HEAD); do - benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true - done - [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found" - - - name: Check compiler - run: $COMPILER -v - - - name: Show g++ cpu info - if: runner.os != 'macOS' - run: g++ -Q -march=native --help=target - - - name: Show clang++ cpu info - if: runner.os == 'macOS' - run: clang++ -E - -march=native -### - - - name: Test help target - run: make help - - - name: Check git - run: git --version - - # Compile profile guided builds - - - name: Compile ${{ matrix.binaries }} build - run: | - make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE" - make strip ARCH=$BINARY COMP=$COMP - WINE_PATH="$SDE" ../tests/signature.sh $benchref - mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT - - - name: Remove non src files - run: git clean -fx - - - name: Download wiki - run: | - git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki - rm -rf ../wiki/.git - - - name: Create directory. - run: | - cd .. - mkdir stockfish - cp -r wiki stockfish/ - cp -r src stockfish/ - cp stockfish-$NAME-$BINARY$EXT stockfish/ - cp "Top CPU Contributors.txt" stockfish/ - cp Copying.txt stockfish/ - cp AUTHORS stockfish/ - cp CITATION.cff stockfish/ - cp README.md stockfish/ - cp CONTRIBUTING.md stockfish/ - - - name: Create tar - if: runner.os != 'Windows' - run: | - cd .. - tar -cvf stockfish-$NAME-$BINARY.tar stockfish - - - name: Create zip - if: runner.os == 'Windows' - run: | - cd .. - zip -r stockfish-$NAME-$BINARY.zip stockfish - - - name: Upload binaries - if: runner.os != 'Windows' - uses: actions/upload-artifact@v3 - with: - name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} - path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar - - # Artifacts automatically get zipped. - # To avoid double-zipping, we use the unzipped directory - - name: Upload binaries - if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 - with: - name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }} - path: stockfish - - - name: Release - if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} - - - name: Get last commit sha - id: last_commit - run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV - - - name: Get commit date - id: commit_date - run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV - - # Make sure that an old ci that still runs on master doesn't recreate a prerelease - - name: Check Pullable Commits - id: check_commits - run: | - git fetch - CHANGES=$(git rev-list HEAD..origin/master --count) - echo "CHANGES=$CHANGES" >> $GITHUB_ENV - - - name: Prerelease - if: github.ref_name == 'master' && env.CHANGES == '0' - continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 - with: - name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} - prerelease: true - files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} diff --git a/.github/workflows/stockfish_compile_test.yml b/.github/workflows/stockfish_compile_test.yml deleted file mode 100644 index a47fcb0fc31..00000000000 --- a/.github/workflows/stockfish_compile_test.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Stockfish -on: - workflow_call: -jobs: - Stockfish: - name: ${{ matrix.config.name }} - runs-on: ${{ matrix.config.os }} - env: - COMPILER: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} - strategy: - matrix: - config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 - compiler: g++ - comp: gcc - shell: bash - - name: Ubuntu 20.04 Clang - os: ubuntu-20.04 - compiler: clang++ - comp: clang - shell: bash - - name: MacOS 13 Apple Clang - os: macos-13 - compiler: clang++ - comp: clang - shell: bash - - name: MacOS 14 Apple Clang M1 - os: macos-14 - compiler: clang++ - comp: clang - shell: bash - m1: true - - name: MacOS 13 GCC 11 - os: macos-13 - compiler: g++-11 - comp: gcc - shell: bash - - name: Windows 2022 Mingw-w64 GCC x86_64 - os: windows-2022 - compiler: g++ - comp: mingw - msys_sys: mingw64 - msys_env: x86_64-gcc - shell: msys2 {0} - - name: Windows 2022 Mingw-w64 Clang x86_64 - os: windows-2022 - compiler: clang++ - comp: clang - msys_sys: clang64 - msys_env: clang-x86_64-clang - shell: msys2 {0} - - defaults: - run: - working-directory: src - shell: ${{ matrix.config.shell }} - steps: - - uses: actions/checkout@v4 - - - name: Setup msys and install required packages - if: runner.os == 'Windows' - uses: msys2/setup-msys2@v2 - with: - msystem: ${{matrix.config.msys_sys}} - install: mingw-w64-${{matrix.config.msys_env}} make git - - - name: Download the used network from the fishtest framework - run: make net - - - name: Check compiler - run: $COMPILER -v - - - name: Test help target - run: make help - - - name: Check git - run: git --version - - # x86-64 with newer extensions tests - - - name: Compile x86-64-avx2 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-avx2 build - - - name: Compile x86-64-bmi2 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-bmi2 build - - - name: Compile x86-64-avx512 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-avx512 build - - - name: Compile x86-64-vnni512 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-vnni512 build - - - name: Compile x86-64-vnni256 build - if: ${{ ! matrix.config.m1 }} - run: | - make clean - make -j2 ARCH=x86-64-vnni256 build - - - name: Compile apple-silicon build - if: matrix.config.m1 - run: | - make clean - make -j2 ARCH=apple-silicon build diff --git a/.github/workflows/stockfish_test.yml b/.github/workflows/tests.yml similarity index 91% rename from .github/workflows/stockfish_test.yml rename to .github/workflows/tests.yml index 867099ee58c..702e86e5f74 100644 --- a/.github/workflows/stockfish_test.yml +++ b/.github/workflows/tests.yml @@ -1,8 +1,8 @@ -name: Stockfish +name: Tests on: workflow_call: jobs: - Stockfish: + Test-Targets: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} env: @@ -10,6 +10,7 @@ jobs: COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: + fail-fast: false matrix: config: - name: Ubuntu 20.04 GCC @@ -190,35 +191,35 @@ jobs: run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-32 optimize=no debug=yes build + make -j4 ARCH=x86-32 optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-32 build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=x86-32 build + make -j4 ARCH=x86-32 build ../tests/signature.sh $benchref - name: Test x86-32-sse41-popcnt build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=x86-32-sse41-popcnt build + make -j4 ARCH=x86-32-sse41-popcnt build ../tests/signature.sh $benchref - name: Test x86-32-sse2 build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=x86-32-sse2 build + make -j4 ARCH=x86-32-sse2 build ../tests/signature.sh $benchref - name: Test general-32 build if: matrix.config.run_32bit_tests run: | make clean - make -j2 ARCH=general-32 build + make -j4 ARCH=general-32 build ../tests/signature.sh $benchref # x86-64 tests @@ -228,21 +229,21 @@ jobs: run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean - make -j2 ARCH=x86-64-avx2 optimize=no debug=yes build + make -j4 ARCH=x86-64-avx2 optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-64-bmi2 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-bmi2 build + make -j4 ARCH=x86-64-bmi2 build ../tests/signature.sh $benchref - name: Test x86-64-avx2 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-avx2 build + make -j4 ARCH=x86-64-avx2 build ../tests/signature.sh $benchref # Test a deprecated arch @@ -250,49 +251,49 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-modern build + make -j4 ARCH=x86-64-modern build ../tests/signature.sh $benchref - name: Test x86-64-sse41-popcnt build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-sse41-popcnt build + make -j4 ARCH=x86-64-sse41-popcnt build ../tests/signature.sh $benchref - name: Test x86-64-ssse3 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-ssse3 build + make -j4 ARCH=x86-64-ssse3 build ../tests/signature.sh $benchref - name: Test x86-64-sse3-popcnt build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-sse3-popcnt build + make -j4 ARCH=x86-64-sse3-popcnt build ../tests/signature.sh $benchref - name: Test x86-64 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64 build + make -j4 ARCH=x86-64 build ../tests/signature.sh $benchref - name: Test general-64 build if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=general-64 build + make -j4 ARCH=general-64 build ../tests/signature.sh $benchref - name: Test apple-silicon build if: matrix.config.run_m1_tests run: | make clean - make -j2 ARCH=apple-silicon build + make -j4 ARCH=apple-silicon build ../tests/signature.sh $benchref # armv8 tests @@ -303,7 +304,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv8 build + make -j4 ARCH=armv8 build ../tests/signature.sh $benchref - name: Test armv8-dotprod build @@ -312,7 +313,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv8-dotprod build + make -j4 ARCH=armv8-dotprod build ../tests/signature.sh $benchref # armv7 tests @@ -323,7 +324,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv7 build + make -j4 ARCH=armv7 build ../tests/signature.sh $benchref - name: Test armv7-neon build @@ -332,7 +333,7 @@ jobs: export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH export LDFLAGS="-static -Wno-unused-command-line-argument" make clean - make -j2 ARCH=armv7-neon build + make -j4 ARCH=armv7-neon build ../tests/signature.sh $benchref # riscv64 tests @@ -340,7 +341,7 @@ jobs: - name: Test riscv64 build if: matrix.config.run_riscv64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > script.sh + echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref @@ -349,7 +350,7 @@ jobs: - name: Test ppc64 build if: matrix.config.run_ppc64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > script.sh + echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=ppc-64 build" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder ../tests/signature.sh $benchref @@ -359,6 +360,6 @@ jobs: if: matrix.config.run_64bit_tests run: | make clean - make -j2 ARCH=x86-64-avx2 build + make -j4 ARCH=x86-64-avx2 build ../tests/perft.sh ../tests/reprosearch.sh diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml new file mode 100644 index 00000000000..7c8f470b125 --- /dev/null +++ b/.github/workflows/upload_binaries.yml @@ -0,0 +1,105 @@ +name: Upload Binaries +on: + workflow_call: + inputs: + matrix: + type: string + required: true + +jobs: + Artifacts: + name: ${{ matrix.config.name }} ${{ matrix.binaries }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + EXT: ${{ matrix.config.ext }} + NAME: ${{ matrix.config.simple_name }} + BINARY: ${{ matrix.binaries }} + SDE: ${{ matrix.config.sde }} + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.matrix) }} + defaults: + run: + shell: ${{ matrix.config.shell }} + steps: + - uses: actions/checkout@v4 + + - name: Download artifact from compilation + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} + + - name: Setup msys and install required packages + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.config.msys_sys }} + install: mingw-w64-${{ matrix.config.msys_env }} make git zip + + - name: Create Package + run: | + mkdir stockfish + + - name: Download wiki + run: | + git clone https://github.com/official-stockfish/Stockfish.wiki.git wiki + rm -rf wiki/.git + mv wiki stockfish/ + + - name: Copy files + run: | + mv "${{ matrix.config.simple_name }} ${{ matrix.binaries }}" stockfish-workflow + cd stockfish-workflow + cp -r src ../stockfish/ + cp stockfish-$NAME-$BINARY$EXT ../stockfish/ + cp "Top CPU Contributors.txt" ../stockfish/ + cp Copying.txt ../stockfish/ + cp AUTHORS ../stockfish/ + cp CITATION.cff ../stockfish/ + cp README.md ../stockfish/ + cp CONTRIBUTING.md ../stockfish/ + + - name: Create tar + if: runner.os != 'Windows' + run: | + tar -cvf stockfish-$NAME-$BINARY.tar stockfish + + - name: Create zip + if: runner.os == 'Windows' + run: | + zip -r stockfish-$NAME-$BINARY.zip stockfish + + - name: Release + if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + with: + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + + - name: Get last commit sha + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Make sure that an old ci that still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + continue-on-error: true + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true + files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} From f2984471c90d82284720f681314ff87bf5fd833c Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 1 Feb 2024 21:09:48 +0100 Subject: [PATCH 0380/1309] Tweak capture scoring for move ordering Move divisor from capture scoring to good capture check and sligthly increase it. This has several effects: - its a speedup because for quience and probcut search the division now never happens. For main search its delayed and can be avoided if a good capture triggers a cutoff - through the higher resolution of scores we have a more granular sorting STC: https://tests.stockfishchess.org/tests/view/65bf2a93c865510db027dc27 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 470016 W: 122150 L: 121173 D: 226693 Ptnml(0-2): 2133, 55705, 118374, 56644, 2152 LTC: https://tests.stockfishchess.org/tests/view/65c1d16dc865510db0281339 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 98988 W: 25121 L: 24667 D: 49200 Ptnml(0-2): 77, 10998, 26884, 11464, 71 closes https://github.com/official-stockfish/Stockfish/pull/5036 Bench: 1233867 --- src/movepick.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b2638350de1..e86438c3fb8 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -167,9 +167,8 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = - (7 * int(PieceValue[pos.piece_on(m.to_sq())]) - + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]) - / 16; + 7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]; else if constexpr (Type == QUIETS) { @@ -269,7 +268,8 @@ Move MovePicker::next_move(bool skipQuiets) { case GOOD_CAPTURE : if (select([&]() { // Move losing capture to endBadCaptures to be tried later - return pos.see_ge(*cur, -cur->value) ? true : (*endBadCaptures++ = *cur, false); + return pos.see_ge(*cur, -cur->value / 18) ? true + : (*endBadCaptures++ = *cur, false); })) return *(cur - 1); From c0107b3c27e9542a3319e9af0396794b7b2df890 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:56:13 +0800 Subject: [PATCH 0381/1309] Remove simple eval With the recent introduction of the dual NNUE, the need for simple eval is no longer there. Passed STC: https://tests.stockfishchess.org/tests/view/65c1f735c865510db0281652 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 85312 W: 22009 L: 21837 D: 41466 Ptnml(0-2): 334, 10155, 21567, 10205, 395 Passed LTC: https://tests.stockfishchess.org/tests/view/65c2d64bc865510db0282810 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 49956 W: 12596 L: 12402 D: 24958 Ptnml(0-2): 28, 5553, 13624, 5743, 30 closes https://github.com/official-stockfish/Stockfish/pull/5037 Bench 1213676 --- src/evaluate.cpp | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 45658798c52..c8656fc2eac 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -199,33 +199,24 @@ Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); - int v; - Color stm = pos.side_to_move(); - int shuffling = pos.rule50_count(); - int simpleEval = simple_eval(pos, stm); - - bool lazy = std::abs(simpleEval) > 2550; - if (lazy) - v = simpleEval; - else - { - bool smallNet = std::abs(simpleEval) > 1050; + int simpleEval = simple_eval(pos, pos.side_to_move()); + bool smallNet = std::abs(simpleEval) > 1050; - int nnueComplexity; + int nnueComplexity; - Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) - : NNUE::evaluate(pos, true, &nnueComplexity); + Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) + : NNUE::evaluate(pos, true, &nnueComplexity); - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; - int npm = pos.non_pawn_material() / 64; - v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; - } + int npm = pos.non_pawn_material() / 64; + int v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; // Damp down the evaluation linearly when shuffling - v = v * (200 - shuffling) / 214; + int shuffling = pos.rule50_count(); + v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 15093d43c413a9af4b8adfe4d7031f822c58fbf2 Mon Sep 17 00:00:00 2001 From: gahtan-syarif <155860115+gahtan-syarif@users.noreply.github.com> Date: Thu, 8 Feb 2024 04:20:45 +0700 Subject: [PATCH 0382/1309] Simplify opponent movecount reduction This removes the reduction decrease that occured when the previous ply had a movecount greater than 7. Passed STC: https://tests.stockfishchess.org/tests/view/65c3f6dac865510db0283ef6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 11968 W: 3205 L: 2953 D: 5810 Ptnml(0-2): 38, 1310, 3064, 1506, 66 Passed LTC: https://tests.stockfishchess.org/tests/view/65c42377c865510db0284217 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 35676 W: 9113 L: 8905 D: 17658 Ptnml(0-2): 22, 3893, 9802, 4097, 24 closes https://github.com/official-stockfish/Stockfish/pull/5040 Bench: 1148379 --- AUTHORS | 1 + src/search.cpp | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index cc8edafa4aa..9b1faee7220 100644 --- a/AUTHORS +++ b/AUTHORS @@ -78,6 +78,7 @@ Fauzi Akram Dabat (fauzi2) Felix Wittmann gamander Gabriele Lombardo (gabe) +Gahtan Nahdi Gary Heckman (gheckman) George Sobala (gsobala) gguliash diff --git a/src/search.cpp b/src/search.cpp index 336678c0617..63e7699e3fb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1108,10 +1108,6 @@ Value Search::Worker::search( if (ss->ttPv) r -= 1 + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); - // Decrease reduction if opponent's move count is high (~1 Elo) - if ((ss - 1)->moveCount > 7) - r--; - // Increase reduction for cut nodes (~4 Elo) if (cutNode) r += 2 - (tte->depth() >= depth && ss->ttPv); From 96837bc4396d205536cdaabfc17e4885a48b0588 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:00:51 +0800 Subject: [PATCH 0383/1309] Remove check extension Passed simplification STC: https://tests.stockfishchess.org/tests/view/65c38d2ac865510db02836cf LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 52288 W: 13578 L: 13371 D: 25339 Ptnml(0-2): 197, 6171, 13265, 6250, 261 Passed simplification LTC: https://tests.stockfishchess.org/tests/view/65c4470ec865510db0284473 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 44958 W: 11255 L: 11055 D: 22648 Ptnml(0-2): 37, 4962, 12274, 5176, 30 closes https://github.com/official-stockfish/Stockfish/pull/5041 Bench: 1116591 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 63e7699e3fb..05afc9ce3dc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1071,10 +1071,6 @@ Value Search::Worker::search( extension = -1; } - // Check extensions (~1 Elo) - else if (givesCheck && depth > 10) - extension = 1; - // Quiet ttMove extensions (~1 Elo) else if (PvNode && move == ttMove && move == ss->killers[0] && (*contHist[0])[movedPiece][move.to_sq()] >= 4339) From 9699f4f79ae9bb193c1f74adf53886a1a56f8a91 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 8 Feb 2024 13:54:40 -0800 Subject: [PATCH 0384/1309] Fix the alignment of the transformer buffer Fixes the issue mentioned in https://github.com/official-stockfish/Stockfish/commit/584d9efedcde330eeb96a99215552ddfb06f52ba#r138417600. Thanks to @cj5716 and @peregrineshahin for spotting this! closes https://github.com/official-stockfish/Stockfish/pull/5042 No functional change --- src/nnue/evaluate_nnue.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index d4a4dbe4c45..5bd7e83d22f 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -197,11 +197,10 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer < Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr - > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall + : TransformedFeatureDimensionsBig, + nullptr > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else From 21dff6c2765d075a4e109e5dda98b7bf5af2cec9 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 10 Feb 2024 11:32:10 +0100 Subject: [PATCH 0385/1309] Update CI actions - Update codeql to v3 - Switch from dev-drprasad to native github cli - Update softprops/action-gh-release to node 20 commit `thollander/actions-comment-pull-request` needs to be bumped to node20 too, but the author hasnt done so atm closes https://github.com/official-stockfish/Stockfish/pull/5044 No functional change --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/stockfish.yml | 8 ++++---- .github/workflows/upload_binaries.yml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1c3a3a6b468..d949a5a7649 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,7 +33,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,6 +48,6 @@ jobs: run: make -j build ARCH=x86-64-modern - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 1c0dd0e1336..22cd9af3fd2 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -26,11 +26,11 @@ jobs: echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV # delete old previous pre-release and tag - - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1 + - uses: actions/checkout@v4 + - run: gh release delete ${{ env.COMMIT_SHA }} --cleanup-tag if: env.COMMIT_SHA != 'null' - with: - tag_name: ${{ env.COMMIT_SHA }} - github_token: ${{ secrets.GITHUB_TOKEN }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Matrix: runs-on: ubuntu-latest outputs: diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 7c8f470b125..97bcf96f219 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -74,7 +74,7 @@ jobs: - name: Release if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag' - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 with: files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} From 3d5b16df7c2125ba52a25b3d4b69fc1261c4eb80 Mon Sep 17 00:00:00 2001 From: GoldenRare Date: Tue, 6 Feb 2024 05:45:52 -0500 Subject: [PATCH 0386/1309] Remove unnecessary assignments related to adjusted static evaluation In both search and qsearch, there are instances where we do unadjustedStaticEval = ss->staticEval = eval/bestValue = tte->eval(), but immediately after re-assign ss-static and eval/bestValue to some new value, which makes the initial assignment redundant. closes https://github.com/official-stockfish/Stockfish/pull/5045 No functional change --- src/search.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 05afc9ce3dc..b186d8a9ec7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -701,9 +701,9 @@ Value Search::Worker::search( else if (ss->ttHit) { // Never assume anything about values stored in TT - unadjustedStaticEval = ss->staticEval = eval = tte->eval(); - if (eval == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = tte->eval(); + if (unadjustedStaticEval == VALUE_NONE) + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos); @@ -715,7 +715,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = ss->staticEval = eval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -1434,9 +1434,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (ss->ttHit) { // Never assume anything about values stored in TT - if ((unadjustedStaticEval = ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) - unadjustedStaticEval = ss->staticEval = bestValue = - evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = tte->eval(); + if (unadjustedStaticEval == VALUE_NONE) + unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1448,10 +1448,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, else { // In case of null move search, use previous static eval with a different sign - unadjustedStaticEval = ss->staticEval = bestValue = - (ss - 1)->currentMove != Move::null() ? evaluate(pos, thisThread->optimism[us]) - : -(ss - 1)->staticEval; - ss->staticEval = bestValue = + unadjustedStaticEval = (ss - 1)->currentMove != Move::null() + ? evaluate(pos, thisThread->optimism[us]) + : -(ss - 1)->staticEval; + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); } From 9068fdc57bcaaa142938e18a529761de1063f786 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 10 Feb 2024 13:58:38 +0100 Subject: [PATCH 0387/1309] Assorted cleanups Assorted cleanups closes https://github.com/official-stockfish/Stockfish/pull/5046 No functional change Co-Authored-By: Shahin M. Shahin <41402573+peregrineshahin@users.noreply.github.com> Co-Authored-By: cj5716 <125858804+cj5716@users.noreply.github.com> --- src/bitboard.h | 3 --- src/evaluate.cpp | 2 +- src/movepick.cpp | 2 +- src/position.h | 2 -- src/search.cpp | 37 ++++++++++++++++--------------------- src/search.h | 18 +++++++----------- 6 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index d028be02906..cdff4c759bc 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -163,7 +163,6 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) { inline Bitboard line_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); - return LineBB[s1][s2]; } @@ -178,7 +177,6 @@ inline Bitboard line_bb(Square s1, Square s2) { inline Bitboard between_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); - return BetweenBB[s1][s2]; } @@ -216,7 +214,6 @@ template inline Bitboard attacks_bb(Square s) { assert((Pt != PAWN) && (is_ok(s))); - return PseudoAttacks[Pt][s]; } diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c8656fc2eac..da0867660af 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -219,7 +219,7 @@ Value Eval::evaluate(const Position& pos, int optimism) { v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range - v = std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); return v; } diff --git a/src/movepick.cpp b/src/movepick.cpp index e86438c3fb8..33791922e4b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -173,7 +173,7 @@ void MovePicker::score() { else if constexpr (Type == QUIETS) { Piece pc = pos.moved_piece(m); - PieceType pt = type_of(pos.moved_piece(m)); + PieceType pt = type_of(pc); Square from = m.from_sq(); Square to = m.to_sq(); diff --git a/src/position.h b/src/position.h index 7ce3556f0e9..154ed652942 100644 --- a/src/position.h +++ b/src/position.h @@ -252,13 +252,11 @@ inline CastlingRights Position::castling_rights(Color c) const { inline bool Position::castling_impeded(CastlingRights cr) const { assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return pieces() & castlingPath[cr]; } inline Square Position::castling_rook_square(CastlingRights cr) const { assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); - return castlingRookSquare[cr]; } diff --git a/src/search.cpp b/src/search.cpp index b186d8a9ec7..4e0d808e371 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -67,7 +67,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; v += cv * std::abs(cv) / 12890; - return std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth @@ -297,9 +297,9 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = Value(9) + int(avg) * avg / 12480; + delta = 9 + avg * avg / 12480; alpha = std::max(avg - delta, -VALUE_INFINITE); - beta = std::min(avg + delta, int(VALUE_INFINITE)); + beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) optimism[us] = 131 * avg / (std::abs(avg) + 95); @@ -350,7 +350,7 @@ void Search::Worker::iterative_deepening() { } else if (bestValue >= beta) { - beta = std::min(bestValue + delta, int(VALUE_INFINITE)); + beta = std::min(bestValue + delta, VALUE_INFINITE); ++failedHighCnt; } else @@ -481,7 +481,6 @@ void Search::Worker::clear() { for (auto& h : to) h->fill(-71); - for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -538,7 +537,7 @@ Value Search::Worker::search( // Check for the available remaining time if (is_mainthread()) - main_manager()->check_time(*this); + main_manager()->check_time(*thisThread); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) if (PvNode && thisThread->selDepth < ss->ply + 1) @@ -680,10 +679,8 @@ Value Search::Worker::search( } } - - Value unadjustedStaticEval = VALUE_NONE; - // Step 6. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) { // Skip early pruning when in check @@ -820,11 +817,10 @@ Value Search::Worker::search( if (cutNode && depth >= 8 && !ttMove) depth -= 2; - probCutBeta = beta + 182 - 68 * improving; - // Step 11. ProbCut (~10 Elo) // 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 + 182 - 68 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -1285,7 +1281,6 @@ Value Search::Worker::search( { if (capture) capturesSearched[captureCount++] = move; - else quietsSearched[quietCount++] = move; } @@ -1424,9 +1419,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttValue; - Value unadjustedStaticEval = VALUE_NONE; - // Step 4. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; else @@ -1521,7 +1515,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // If static eval is much lower than alpha and move is not winning material // we can prune this move. (~2 Elo) - if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + if (futilityBase <= alpha && !pos.see_ge(move, 1)) { bestValue = std::max(bestValue, futilityBase); continue; @@ -1597,7 +1591,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (ss->inCheck && bestValue == -VALUE_INFINITE) { assert(!MoveList(pos).size()); - return mated_in(ss->ply); // Plies to mate from the root } @@ -1615,6 +1608,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, return bestValue; } +Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { + int reductionScale = reductions[d] * reductions[mn]; + return (reductionScale + 1177 - delta * 776 / rootDelta) / 1024 + (!i && reductionScale > 842); +} namespace { // Adjusts a mate or TB score from "plies to mate from the root" @@ -1623,7 +1620,6 @@ namespace { Value value_to_tt(Value v, int ply) { assert(v != VALUE_NONE); - return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; } @@ -1805,9 +1801,9 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = int((weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) - / 128); + int push = (weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) + / 128; if (rootMoves[i].score + push >= maxScore) { @@ -1921,7 +1917,6 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po bool ttHit; assert(pv.size() == 1); - if (pv[0] == Move::none()) return false; diff --git a/src/search.h b/src/search.h index 97cb2ca40b2..2d1077f825e 100644 --- a/src/search.h +++ b/src/search.h @@ -47,7 +47,6 @@ enum NodeType { class TranspositionTable; class ThreadPool; class OptionsMap; -class UCI; namespace Search { @@ -124,10 +123,12 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& o, ThreadPool& tp, TranspositionTable& t) : - options(o), - threads(tp), - tt(t) {} + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable) : + options(optionsMap), + threads(threadPool), + tt(transpositionTable) {} const OptionsMap& options; ThreadPool& threads; @@ -209,11 +210,7 @@ class Worker { template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); - Depth reduction(bool i, Depth d, int mn, int delta) { - int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1177 - int(delta) * 776 / int(rootDelta)) / 1024 - + (!i && reductionScale > 842); - } + Depth reduction(bool i, Depth d, int mn, int delta); // Get a pointer to the search manager, only allowed to be called by the // main thread. @@ -251,7 +248,6 @@ class Worker { TranspositionTable& tt; friend class Stockfish::ThreadPool; - friend class Stockfish::UCI; friend class SearchManager; }; From 91a4cea437fc0ae177808dd0a9ef791f38229c7b Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 10 Feb 2024 20:17:21 +0300 Subject: [PATCH 0388/1309] Adjust best value in main search depending on depth This patch does similar thing to how it's done for qsearch - in case of fail high adjust result to lower value. Difference is that it is done only for non-pv nodes and it's depth dependent - so lower depth entries will have bigger adjustment and higher depth entries will have smaller adjustment. Passed STC: https://tests.stockfishchess.org/tests/view/65c3c0cbc865510db0283b21 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 112032 W: 29142 L: 28705 D: 54185 Ptnml(0-2): 479, 13152, 28326, 13571, 488 Passed LTC: https://tests.stockfishchess.org/tests/view/65c52e62c865510db02855d5 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 132480 W: 33457 L: 32936 D: 66087 Ptnml(0-2): 67, 14697, 36222, 15156, 98 closes https://github.com/official-stockfish/Stockfish/pull/5047 Bench: 1168241 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 4e0d808e371..7a60e9731ee 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1293,6 +1293,11 @@ Value Search::Worker::search( assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); + // Adjust best value for fail high cases at non-pv nodes + if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && + std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + bestValue = (bestValue * (depth + 2) + beta) / (depth + 3); + if (!moveCount) bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; From 531747ee7889d9b61b9841a57bb6d582459999d6 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 10 Feb 2024 15:06:38 -0800 Subject: [PATCH 0389/1309] Improve thread voting inefficiencies Initialize the unordered map to a reasonable number of buckets and make the move hashes well distributed. For more see https://github.com/official-stockfish/Stockfish/pull/4958#issuecomment-1937351190 Also make bestThreadPV and newThreadPV references so we don't copy entire vectors. closes https://github.com/official-stockfish/Stockfish/pull/5048 No functional change --- src/thread.cpp | 8 +++----- src/types.h | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/thread.cpp b/src/thread.cpp index 3cce7c56295..2e42abd42fc 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -210,10 +210,9 @@ void ThreadPool::start_thinking(const OptionsMap& options, Thread* ThreadPool::get_best_thread() const { - std::unordered_map votes; - Thread* bestThread = threads.front(); Value minScore = VALUE_NONE; + std::unordered_map votes(2 * std::min(size(), bestThread->worker->rootMoves.size())); // Find the minimum score of all threads for (Thread* th : threads) @@ -232,13 +231,12 @@ Thread* ThreadPool::get_best_thread() const { const auto bestThreadScore = bestThread->worker->rootMoves[0].score; const auto newThreadScore = th->worker->rootMoves[0].score; - const auto bestThreadPV = bestThread->worker->rootMoves[0].pv; - const auto newThreadPV = th->worker->rootMoves[0].pv; + const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv; + const auto& newThreadPV = th->worker->rootMoves[0].pv; const auto bestThreadMoveVote = votes[bestThreadPV[0]]; const auto newThreadMoveVote = votes[newThreadPV[0]]; - const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; diff --git a/src/types.h b/src/types.h index e83b306dc73..8b0ffb0ca0f 100644 --- a/src/types.h +++ b/src/types.h @@ -397,7 +397,7 @@ class Move { constexpr std::uint16_t raw() const { return data; } struct MoveHash { - std::size_t operator()(const Move& m) const { return m.data; } + std::size_t operator()(const Move& m) const { return make_key(m.data); } }; protected: From c115e5171eed2650b59f9a8da7cd6153e4cff873 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sat, 10 Feb 2024 04:00:36 +0700 Subject: [PATCH 0390/1309] Remove quiet tt move extensions Passed STC: https://tests.stockfishchess.org/tests/view/65c6934cc865510db0286e90 LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 54016 W: 14065 L: 13854 D: 26097 Ptnml(0-2): 231, 6381, 13581, 6576, 239 Passed LTC: https://tests.stockfishchess.org/tests/view/65c72b91c865510db0287a1a LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 55098 W: 13850 L: 13658 D: 27590 Ptnml(0-2): 37, 6257, 14777, 6433, 45 closes https://github.com/official-stockfish/Stockfish/pull/5049 Bench: 1027182 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7a60e9731ee..b36eb05d423 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,11 +1067,6 @@ Value Search::Worker::search( extension = -1; } - // Quiet ttMove extensions (~1 Elo) - else if (PvNode && move == ttMove && move == ss->killers[0] - && (*contHist[0])[movedPiece][move.to_sq()] >= 4339) - extension = 1; - // Recapture extensions (~1 Elo) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] From 7ccde25baf03e77926644b282fed68ba0b5ddf95 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 11 Feb 2024 20:13:19 +0100 Subject: [PATCH 0391/1309] Format code using clang-format No functional change --- src/search.cpp | 4 ++-- src/thread.cpp | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b36eb05d423..7bae7a4a86b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1289,8 +1289,8 @@ Value Search::Worker::search( assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && - std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) bestValue = (bestValue * (depth + 2) + beta) / (depth + 3); if (!moveCount) diff --git a/src/thread.cpp b/src/thread.cpp index 2e42abd42fc..61beb3994d0 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -212,7 +212,9 @@ Thread* ThreadPool::get_best_thread() const { Thread* bestThread = threads.front(); Value minScore = VALUE_NONE; - std::unordered_map votes(2 * std::min(size(), bestThread->worker->rootMoves.size())); + + std::unordered_map votes( + 2 * std::min(size(), bestThread->worker->rootMoves.size())); // Find the minimum score of all threads for (Thread* th : threads) From 5c0388310731ba4bf7989210cb69be67b53bb43d Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:59:05 +0800 Subject: [PATCH 0392/1309] VVLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Search parameters were tuned using 16k games at VVLTC. They were tuned starting with the new parameters (in search only) of PR #5039. Passed VVLTC: https://tests.stockfishchess.org/tests/view/65c8a8fc1d8e83c78bfcd163 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 20826 W: 5355 L: 5100 D: 10371 Ptnml(0-2): 1, 1941, 6275, 2194, 2 Passed 2nd VVLTC: https://tests.stockfishchess.org/tests/view/65cadc2d1d8e83c78bfcfdaf LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 17710 W: 4611 L: 4352 D: 8747 Ptnml(0-2): 1, 1586, 5422, 1845, 1 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65cb6aed1d8e83c78bfd0802 Elo: -1.46 ± 1.8 (95%) LOS: 5.5% Total: 40000 W: 10267 L: 10435 D: 19298 Ptnml(0-2): 200, 4860, 10023, 4742, 175 nElo: -2.77 ± 3.4 (95%) PairsRatio: 0.97 Bench: 1198939 --- src/search.cpp | 64 +++++++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7bae7a4a86b..ae1c3414380 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 116 - 47 * noTtCutNode; + Value futilityMult = 117 - 44 * noTtCutNode; return (futilityMult * d - 3 * futilityMult / 2 * improving); } @@ -66,15 +66,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 12890; + v += cv * std::abs(cv) / 12475; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(253 * d - 356, 1117); } +int stat_bonus(Depth d) { return std::min(246 * d - 351, 1136); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(517 * d - 308, 1206); } +int stat_malus(Depth d) { return std::min(519 * d - 306, 1258); } // 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); } @@ -297,12 +297,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12480; + delta = 9 + avg * avg / 12487; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 131 * avg / (std::abs(avg) + 95); + optimism[us] = 134 * avg / (std::abs(avg) + 97); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -482,7 +482,7 @@ void Search::Worker::clear() { h->fill(-71); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((20.37 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((18.79 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -723,7 +723,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1661, 1495); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1723, 1455); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -744,7 +744,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 450 - (332 - 160 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 438 - (332 - 154 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -755,22 +755,22 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) - - (ss - 1)->statScore / 327 + - (ss - 1)->statScore / 314 >= beta - && eval >= beta && eval < 28702 // smaller than TB wins + && eval >= beta && eval < 30016 // smaller than TB wins && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 17379 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 329 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16620 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 330 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 148, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 154, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -820,7 +820,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 182 - 68 * improving; + probCutBeta = beta + 181 - 68 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -876,7 +876,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 446; + probCutBeta = beta + 452; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -959,7 +959,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 279 + 295 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 277 + 292 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -967,7 +967,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -204 * depth)) + if (!pos.see_ge(move, -197 * depth)) continue; } else @@ -979,16 +979,16 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4215 * depth) + if (lmrDepth < 6 && history < -4211 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 6658; + lmrDepth += history / 6437; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 58 ? 139 : 55) + && ss->staticEval + (bestValue < ss->staticEval - 57 ? 144 : 57) + 121 * lmrDepth <= alpha) continue; @@ -1016,11 +1016,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (62 + 52 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (60 + 54 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1071,7 +1071,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4356) + > 4394) extension = 1; } @@ -1123,10 +1123,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4409; + + (*contHist[3])[movedPiece][move.to_sq()] - 4392; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 14894; + r -= ss->statScore / 14189; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1261,7 +1261,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 13 && beta < 13710 && value > -12589) + if (depth > 2 && depth < 13 && beta < 13652 && value > -12761) depth -= 2; assert(depth > 0); @@ -1304,7 +1304,7 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15401) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15736) + ((ss - 1)->moveCount > 11); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1462,7 +1462,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 204; + futilityBase = ss->staticEval + 206; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1542,7 +1542,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -75)) + if (!pos.see_ge(move, -74)) continue; } @@ -1610,7 +1610,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1177 - delta * 776 / rootDelta) / 1024 + (!i && reductionScale > 842); + return (reductionScale + 1118 - delta * 793 / rootDelta) / 1024 + (!i && reductionScale > 863); } namespace { @@ -1699,7 +1699,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 166 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From f4f0b32d55defe93a79ec7afcce47d1d795879a8 Mon Sep 17 00:00:00 2001 From: Tierynn Byrnes Date: Tue, 6 Feb 2024 14:55:28 +1000 Subject: [PATCH 0393/1309] Refactor timeman.cpp Move optExtra, optConstant and maxConstant into lower scope. closes https://github.com/official-stockfish/Stockfish/pull/5052 No functional change --- AUTHORS | 1 + src/timeman.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9b1faee7220..40a38bd5d89 100644 --- a/AUTHORS +++ b/AUTHORS @@ -216,6 +216,7 @@ Taras Vuk (TarasVuk) Thanar2 thaspel theo77186 +TierynnB Ting-Hsuan Huang (fffelix-huang) Tobias Steinmann Tomasz Sobczyk (Sopel97) diff --git a/src/timeman.cpp b/src/timeman.cpp index 121f8edb985..72a447af5b8 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -28,7 +28,6 @@ namespace Stockfish { - TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } TimePoint TimeManagement::elapsed(size_t nodes) const { @@ -89,18 +88,19 @@ void TimeManagement::init(Search::LimitsType& limits, TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); - // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); - - // Calculate time constants based on current time left. - double optConstant = std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); - double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); - // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed actual available // game time for the current move, so also cap to 20% of available game time. if (limits.movestogo == 0) { + // Use extra time with larger increments + double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); + + // Calculate time constants based on current time left. + double optConstant = + std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); + double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); + optScale = std::min(0.0120 + std::pow(ply + 3.1, 0.44) * optConstant, 0.21 * limits.time[us] / double(timeLeft)) * optExtra; From bf2c7306ac7f83200ba4d894867e3c0c78c0802c Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 11 Feb 2024 14:19:44 +0100 Subject: [PATCH 0394/1309] Use node counting to early stop search This introduces a form of node counting which can be used to further tweak the usage of our search time. The current approach stops the search when almost all nodes are searched on a single move. The idea originally came from Koivisto, but the implemention is a bit different, Koivisto scales the optimal time by the nodes effort and then determines if the search should be stopped. We just scale down the `totalTime` and stop the search if we exceed it and the effort is large enough. Passed STC: https://tests.stockfishchess.org/tests/view/65c8e0661d8e83c78bfcd5ec LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 88672 W: 22907 L: 22512 D: 43253 Ptnml(0-2): 310, 10163, 23041, 10466, 356 Passed LTC: https://tests.stockfishchess.org/tests/view/65ca632b1d8e83c78bfcf554 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 170856 W: 42910 L: 42320 D: 85626 Ptnml(0-2): 104, 18337, 47960, 18919, 108 closes https://github.com/official-stockfish/Stockfish/pull/5053 Bench: 1198939 --- src/search.cpp | 17 +++++++++++++++++ src/search.h | 2 ++ src/thread.cpp | 2 ++ 3 files changed, 21 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index ae1c3414380..9574465388e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -418,6 +419,10 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { + auto bestmove = rootMoves[0].pv[0]; + int nodesEffort = effort[bestmove.from_sq()][bestmove.to_sq()] * 100 + / std::max(size_t(1), size_t(nodes)); + double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 616.6; @@ -435,6 +440,13 @@ void Search::Worker::iterative_deepening() { if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); + if (completedDepth >= 10 && nodesEffort >= 95 + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 3 / 4 + && !mainThread->ponder) + { + threads.stop = true; + } + // Stop the search if we have exceeded the totalTime if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) { @@ -1087,6 +1099,8 @@ Value Search::Worker::search( ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; + uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; + // Step 16. Make the move thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); @@ -1186,6 +1200,9 @@ Value Search::Worker::search( // Step 19. Undo move pos.undo_move(move); + if (rootNode) + effort[move.from_sq()][move.to_sq()] += nodes - nodeCount; + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); // Step 20. Check for a new best move diff --git a/src/search.h b/src/search.h index 2d1077f825e..4a1c68bb9ce 100644 --- a/src/search.h +++ b/src/search.h @@ -219,6 +219,8 @@ class Worker { return static_cast(manager.get()); } + std::array, SQUARE_NB> effort; + LimitsType limits; size_t pvIdx, pvLast; diff --git a/src/thread.cpp b/src/thread.cpp index 61beb3994d0..95646601106 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "misc.h" #include "movegen.h" @@ -203,6 +204,7 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); th->worker->rootState = setupStates->back(); th->worker->tbConfig = tbConfig; + th->worker->effort = {}; } main_thread()->start_searching(); From 9d61822b5dda7f99d46ed9ad346afa8c34c302a8 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sat, 10 Feb 2024 03:51:05 +0700 Subject: [PATCH 0395/1309] Remove penalty for quiet ttMove that fails low Passed STC non-reg: https://tests.stockfishchess.org/tests/view/65c691a7c865510db0286e6e LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 234336 W: 60258 L: 60255 D: 113823 Ptnml(0-2): 966, 28141, 58918, 28210, 933 Passed LTC non-reg: https://tests.stockfishchess.org/tests/view/65c8d0d31d8e83c78bfcd4a6 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235206 W: 59134 L: 59132 D: 116940 Ptnml(0-2): 135, 26908, 63517, 26906, 137 https://github.com/official-stockfish/Stockfish/pull/5054 Bench: 1287996 --- src/search.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9574465388e..cb71acbaa5b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -621,13 +621,6 @@ Value Search::Worker::search( update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_malus(depth + 1)); } - // Penalty for a quiet ttMove that fails low (~1 Elo) - else if (!ttCapture) - { - int penalty = -stat_malus(depth); - thisThread->mainHistory[us][ttMove.from_to()] << penalty; - update_continuation_histories(ss, pos.moved_piece(ttMove), ttMove.to_sq(), penalty); - } } // Partial workaround for the graph history interaction problem From f3df0cfb84250f03662a6fd50ea20c9677a0a1d0 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:50:16 +0800 Subject: [PATCH 0396/1309] Simplify TT PV reduction This also removes some incorrect fail-high logic. Passed STC: https://tests.stockfishchess.org/tests/view/65cb3b641d8e83c78bfd04a9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 87968 W: 22634 L: 22468 D: 42866 Ptnml(0-2): 315, 10436, 22323, 10588, 322 Passed LTC: https://tests.stockfishchess.org/tests/view/65cccee21d8e83c78bfd222c LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 70794 W: 17846 L: 17672 D: 35276 Ptnml(0-2): 44, 7980, 19189, 8126, 58 closes https://github.com/official-stockfish/Stockfish/pull/5055 Bench: 1474424 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cb71acbaa5b..c407ae6ba6e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1098,9 +1098,9 @@ Value Search::Worker::search( thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - // Decrease reduction if position is or has been on the PV (~5 Elo) + // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1 + (ttValue > alpha) + (ttValue > beta && tte->depth() >= depth); + r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); // Increase reduction for cut nodes (~4 Elo) if (cutNode) From 8e75548f2a10969c1c9211056999efbcebe63f9a Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 6 Feb 2024 11:21:15 -0500 Subject: [PATCH 0397/1309] Update default main net to nn-b1a57edbea57.nnue Created by retraining the previous main net `nn-baff1edbea57.nnue` with: - some of the same options as before: ranger21, more WDL skipping - the addition of T80 nov+dec 2023 data - increasing loss by 15% when prediction is too high, up from 10% - use of torch.compile to speed up training by over 25% ```yaml experiment-name: 2560--S9-514G-T80-augtodec2023-more-wdl-skip-15p-more-loss-high-q-sk28 training-dataset: # https://github.com/official-stockfish/Stockfish/pull/4782 - /data/S6-514G-1ee1aba5ed.binpack - /data/test80-aug2023-2tb7p.v6.min.binpack - /data/test80-sep2023-2tb7p.binpack - /data/test80-oct2023-2tb7p.binpack - /data/test80-nov2023-2tb7p.binpack - /data/test80-dec2023-2tb7p.binpack early-fen-skipping: 28 start-from-engine-test-net: True nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip-15p-more-loss-high-q-torch-compile num-epochs: 1000 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` Epoch 819 trained with the above config led to this PR. Use of torch.compile decorators in nnue-pytorch model.py was found to speed up training by at least 25% on Ampere gpus when using recent pytorch compiled with cuda 12: https://catalog.ngc.nvidia.com/orgs/nvidia/containers/pytorch See recent main net PRs for more info on - ranger21 and more WDL skipping: https://github.com/official-stockfish/Stockfish/pull/4942 - increasing loss when Q is too high: https://github.com/official-stockfish/Stockfish/pull/4972 Training data can be found at: https://robotmoon.com/nnue-training-data/ Passed STC: https://tests.stockfishchess.org/tests/view/65cd76151d8e83c78bfd2f52 LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 78336 W: 20504 L: 20115 D: 37717 Ptnml(0-2): 317, 9225, 19721, 9562, 343 Passed LTC: https://tests.stockfishchess.org/tests/view/65ce5be61d8e83c78bfd43e9 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 41016 W: 10492 L: 10159 D: 20365 Ptnml(0-2): 22, 4533, 11071, 4854, 28 closes https://github.com/official-stockfish/Stockfish/pull/5056 Bench: 1351997 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 729baa6bcb8..53928bf6494 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ Value evaluate(const Position& pos, int optimism); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultNameBig "nn-baff1edbea57.nnue" +#define EvalFileDefaultNameBig "nn-b1a57edbea57.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" struct EvalFile { From fc41f64dfd8a61d0e275ddbecec292833458b86a Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Tue, 13 Feb 2024 17:46:37 +0800 Subject: [PATCH 0398/1309] Simplify PV node reduction Reduce less on PV nodes even with an upperbound TT entry. Passed STC: https://tests.stockfishchess.org/tests/view/65cb3a861d8e83c78bfd0497 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 118752 W: 30441 L: 30307 D: 58004 Ptnml(0-2): 476, 14179, 29921, 14335, 465 Passed LTC: https://tests.stockfishchess.org/tests/view/65cd3b951d8e83c78bfd2b0d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 155058 W: 38549 L: 38464 D: 78045 Ptnml(0-2): 85, 17521, 42219, 17632, 72 closes https://github.com/official-stockfish/Stockfish/pull/5057 Bench: 1303971 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c407ae6ba6e..55a92947d32 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1111,7 +1111,7 @@ Value Search::Worker::search( r++; // Decrease reduction for PvNodes (~3 Elo) - if (PvNode && tte->bound() != BOUND_UPPER) + if (PvNode) r--; // Increase reduction on repetition (~1 Elo) From d07033d5da9c4e1a383f8be45bd9be43ce7b2f95 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 11:56:33 +0100 Subject: [PATCH 0399/1309] Expose EvalFileSmall option for small net Since https://github.com/official-stockfish/fishtest/pull/1870 has been merged it's time for this update. 5k Fixed Games showed no problems. https://tests.stockfishchess.org/tests/view/65d9cc274c0e22b904f574d7 closes https://github.com/official-stockfish/Stockfish/pull/5068 No functional change --- src/evaluate.cpp | 13 +++---------- src/uci.cpp | 3 +++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index da0867660af..f22c0d06cbe 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -77,11 +77,7 @@ NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, for (auto& [netSize, evalFile] : evalFiles) { - // Replace with - // options[evalFile.optionName] - // once fishtest supports the uci option EvalFileSmall - std::string user_eval_file = - netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; + std::string user_eval_file = options[evalFile.optionName]; if (user_eval_file.empty()) user_eval_file = evalFile.defaultName; @@ -149,11 +145,8 @@ void NNUE::verify(const OptionsMap& optio for (const auto& [netSize, evalFile] : evalFiles) { - // Replace with - // options[evalFile.optionName] - // once fishtest supports the uci option EvalFileSmall - std::string user_eval_file = - netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; + std::string user_eval_file = options[evalFile.optionName]; + if (user_eval_file.empty()) user_eval_file = evalFile.defaultName; diff --git a/src/uci.cpp b/src/uci.cpp index d1d69d69752..35f725b5e89 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -83,6 +83,9 @@ UCI::UCI(int argc, char** argv) : options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); }); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option&) { + evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + }); threads.set({options, threads, tt}); From bec83a1869ae6d1bbcfc7d82d569e12823b03bfa Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 17:54:06 +0100 Subject: [PATCH 0400/1309] Update Top CPU Contributors closes https://github.com/official-stockfish/Stockfish/pull/5069 No functional change --- Top CPU Contributors.txt | 189 +++++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 85 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 74c471b7404..11636e840b5 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,139 +1,146 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20. +Contributors to Fishtest with >10,000 CPU hours, as of 2024-02-24. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 37457426 2850540907 -technologov 14135647 742892808 -linrock 4423514 303254809 +noobpwnftw 39302472 3055513453 +technologov 20845762 994893444 +linrock 8616428 560281417 mlang 3026000 200065824 +okrout 2332151 222639518 +pemo 1800019 60274069 dew 1689162 100033738 -okrout 1578136 148855886 -pemo 1508508 48814305 -grandphish2 1461406 91540343 -TueRens 1194790 70400852 -JojoM 947612 61773190 +TueRens 1474943 75121774 +grandphish2 1463002 91616949 +JojoM 1109702 72927902 +olafm 978631 71037944 +sebastronomy 939955 44920556 tvijlbrief 796125 51897690 -sebastronomy 742434 38218524 +gvreuls 711320 49142318 mibere 703840 46867607 -gvreuls 651026 42988582 -oz 543438 39314736 -cw 517858 34869755 +oz 646268 46293638 +rpngn 572571 38928563 +leszek 531858 39316505 +cw 518116 34894291 fastgm 503862 30260818 -leszek 467278 33514883 -CSU_Dynasty 464940 31177118 -ctoks 434416 28506889 -crunchy 427035 27344275 -maximmasiutin 424795 26577722 -bcross 415722 29060963 -olafm 395922 32268020 -rpngn 348378 24560289 -velislav 342567 22138992 +CSU_Dynasty 468784 31385034 +ctoks 434591 28520597 +maximmasiutin 429983 27066286 +crunchy 427414 27371625 +bcross 415724 29061187 +velislav 342588 22140902 +mgrabiak 338763 23999170 Fisherman 327231 21829379 -mgrabiak 300612 20608380 +robal 299836 20213182 Dantist 296386 18031762 -nordlandia 246201 16189678 -robal 241300 15656382 +ncfish1 267604 17881149 +nordlandia 249322 16420192 marrco 234581 17714473 -ncfish1 227517 15233777 +tolkki963 233490 19773930 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 +Calis007 188631 12795784 Thanar 179852 12365359 +Fifis 176209 10638245 vdv 175544 9904472 spams 157128 10319326 +DesolatedDodo 156659 10210328 +armo9494 155355 10566898 sqrt2 147963 9724586 -DesolatedDodo 146350 9536172 -Calis007 143165 9478764 -vdbergh 138650 9064413 +jcAEie 140086 10603658 +vdbergh 139746 9172061 CoffeeOne 137100 5024116 -armo9494 136191 9460264 malala 136182 8002293 xoto 133759 9159372 davar 129023 8376525 DMBK 122960 8980062 dsmith 122059 7570238 +javran 121564 10144656 amicic 119661 7938029 +sschnee 118107 7389266 +Wolfgang 114616 8070494 Data 113305 8220352 BrunoBanani 112960 7436849 +Wencey 111502 5991676 +cuistot 108503 7006992 CypressChess 108331 7759788 skiminki 107583 7218170 -jcAEie 105675 8238962 MaZePallas 102823 6633619 sterni1971 100532 5880772 sunu 100167 7040199 zeryl 99331 6221261 -thirdlife 99124 2242380 +thirdlife 99156 2245320 ElbertoOne 99028 7023771 -cuistot 98853 6069816 +Dubslow 98600 6903242 +markkulix 97010 7643900 bigpen0r 94809 6529203 brabos 92118 6186135 -Wolfgang 91939 6105872 +Maxim 90818 3283364 psk 89957 5984901 -sschnee 88235 5268000 +megaman7de 88822 6052132 racerschmacer 85805 6122790 -Fifis 85722 5709729 -Dubslow 84986 6042456 +maposora 85710 7778146 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 BRAVONE 81239 5054681 nssy 76497 5259388 jromang 76106 5236025 teddybaer 75125 5407666 -tolkki963 74762 5149662 -megaman7de 74351 4940352 -Wencey 74181 4711488 Pking_cda 73776 5293873 -yurikvelo 73150 5004382 -markkulix 72607 5304642 +yurikvelo 73516 5036928 +MarcusTullius 71053 4803477 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 +Spprtr 69646 4806763 +Mineta 66325 4537742 manap 66273 4121774 +szupaw 65468 5669742 tinker 64333 4268790 qurashee 61208 3429862 -Mineta 59357 4418202 -Spprtr 58723 3911011 -AGI 58147 4325994 +woutboat 59496 4906352 +AGI 58195 4329580 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 -MarcusTullius 56746 3762951 ttruscott 56010 3680085 rkl 55132 4164467 +jmdana 54697 4012593 renouve 53811 3501516 -javran 53785 4627608 +notchris 52433 4044590 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 +Goatminola 51004 4432492 rap 49985 3219146 pb00067 49733 3298934 +GPUex 48686 3684998 OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 +oryx 45533 3539290 VoyagerOne 45476 3452465 -jmdana 44893 3065205 -maposora 44597 4039578 -oryx 44570 3454238 speedycpu 43842 3003273 jbwiebe 43305 2805433 -GPUex 42378 3133332 Antihistamine 41788 2761312 mhunt 41735 2691355 homyur 39893 2850481 gri 39871 2515779 Garf 37741 2999686 SC 37299 2731694 +Sylvain27 36520 1467082 csnodgrass 36207 2688994 +Gaster319 35655 3149442 strelock 34716 2074055 -szupaw 34102 2880346 EthanOConnor 33370 2090311 slakovv 32915 2021889 +gopeto 31884 2076712 Gelma 31771 1551204 -gopeto 31671 2060990 kdave 31157 2198362 manapbk 30987 1810399 +ZacHFX 30551 2238078 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 @@ -142,27 +149,31 @@ hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 belzedar94 28846 1811530 +votoanthuan 27978 2285818 +shawnxu 27438 2465810 chriswk 26902 1868317 xwziegtm 26897 2124586 achambord 26582 1767323 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25288 1689730 +Ulysses 25397 1701264 +Jopo12321 25227 1652482 SFTUser 25182 1675689 -nabildanial 24942 1519409 +nabildanial 25068 1531665 Sharaf_DG 24765 1786697 -Maxim 24705 1502062 rodneyc 24376 1416402 +jsys14 24297 1721230 agg177 23890 1395014 -Goatminola 23763 1956036 -Ente 23639 1671638 -Jopo12321 23467 1483172 +srowen 23842 1342508 +Ente 23752 1678188 +jojo2357 23479 2061238 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 cisco2015 22920 1763301 -jsys14 22824 1591906 Zirie 22542 1472937 +Nullvalue 22490 1970374 +AndreasKrug 22485 1769491 team-oh 22272 1636708 Roady 22220 1465606 MazeOfGalious 21978 1629593 @@ -173,79 +184,83 @@ dex 21612 1467203 nesoneg 21494 1463031 user213718 21454 1404128 sphinx 21211 1384728 -AndreasKrug 21097 1634811 +qoo_charly_cai 21135 1514907 jjoshua2 21001 1423089 Zake9298 20938 1565848 horst.prack 20878 1465656 0xB00B1ES 20590 1208666 +Serpensin 20487 1729674 +Dinde 20440 1292390 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 wei 19973 1745989 -notchris 19958 1800128 -Serpensin 19840 1697528 -Gaster319 19712 1677310 fishtester 19617 1257388 rstoesser 19569 1293588 eudhan 19274 1283717 -votoanthuan 19108 1609992 vulcan 18871 1729392 Karpovbot 18766 1053178 -qoo_charly_cai 18543 1284937 +WoodMan777 18556 1628264 jundery 18445 1115855 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17414 981289 +ols 17291 1042003 iisiraider 17275 1049015 +Skiff84 17111 950248 DragonLord 17014 1162790 redstone59 16842 1461780 -Alb11747 16787 1213926 +Karby 16839 1010124 +Alb11747 16787 1213990 +pirt 16493 1237199 +Naven94 16414 951718 +wizardassassin 16392 1148672 IgorLeMasson 16064 1147232 -Karby 15982 979610 scuzzi 15757 968735 ako027ako 15671 1173203 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 -Naven94 15054 834762 OssumOpossum 14857 1007129 -ZacHFX 14783 1021842 +LunaticBFF57 14525 1190310 enedene 14476 905279 +IslandLambda 14393 958196 bpfliegel 14233 882523 +YELNAMRON 14230 1128094 mpx86 14019 759568 jpulman 13982 870599 -Skiff84 13826 721996 +getraideBFF 13871 1172846 +Nesa92 13806 1116101 crocogoat 13803 1117422 -Nesa92 13786 1114691 joster 13710 946160 mbeier 13650 1044928 Hjax 13535 915487 -Nullvalue 13468 1140498 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 -pirt 13100 1009897 Machariel 13010 863104 infinigon 12991 943216 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 +mschmidt 12644 863193 korposzczur 12606 838168 +tsim67 12570 890180 +Jackfish 12553 836958 fatmurphy 12547 853210 +Oakwen 12503 853105 SapphireBrand 12416 969604 -Oakwen 12399 844109 deflectooor 12386 579392 modolief 12386 896470 +TataneSan 12358 609332 Farseer 12249 694108 -Jackfish 12213 805008 pgontarz 12151 848794 dbernier 12103 860824 -getraideBFF 12072 1024966 +FormazChar 11989 907809 stocky 11954 699440 -mschmidt 11941 803401 -MooTheCow 11870 773598 -FormazChar 11766 885707 +somethingintheshadows 11940 989472 +MooTheCow 11892 776126 +3cho 11842 1036786 whelanh 11557 245188 -3cho 11494 1031076 infinity 11470 727027 aga 11412 695127 torbjo 11395 729145 @@ -253,15 +268,19 @@ Thomas A. Anderson 11372 732094 savage84 11358 670860 d64 11263 789184 ali-al-zhrani 11245 779246 +ckaz 11170 680866 snicolet 11106 869170 dapper 11032 771402 -ols 10947 624903 -Karmatron 10828 677458 +Ethnikoi 10993 945906 +Snuuka 10938 435504 +Karmatron 10859 678058 basepi 10637 744851 +jibarbosa 10628 857100 Cubox 10621 826448 +mecevdimitar 10609 787318 michaelrpg 10509 739239 +Def9Infinity 10427 686978 OIVAS7572 10420 995586 -jojo2357 10419 929708 -WoodMan777 10380 873720 +wxt9861 10412 1013864 Garruk 10365 706465 dzjp 10343 732529 From 5c2b3859579e20cb1abc8d53ae7ee229fbc1e335 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 24 Feb 2024 17:55:08 +0100 Subject: [PATCH 0401/1309] Update the WDL model Based on 130M positions from 2.1M games. ``` Look recursively in directory pgns for games from SPRT tests using books matching "UHO_4060_v..epd|UHO_Lichess_4852_v1.epd" for SF revisions between 8e75548f2a10969c1c9211056999efbcebe63f9a (from 2024-02-17 17:11:46 +0100) and HEAD (from 2024-02-17 17:13:07 +0100). Based on 127920843 positions from 2109240 games, NormalizeToPawnValue should change from 345 to 356. ``` The patch only affects the UCI-reported cp and wdl values. closes https://github.com/official-stockfish/Stockfish/pull/5070 No functional change --- src/uci.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 35f725b5e89..4d4ea689715 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -45,7 +45,7 @@ namespace Stockfish { constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int NormalizeToPawnValue = 345; +constexpr int NormalizeToPawnValue = 356; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; UCI::UCI(int argc, char** argv) : @@ -380,8 +380,8 @@ int win_rate_model(Value v, int ply) { // The coefficients of a third-order polynomial fit is based on the fishtest data // for two parameters that need to transform eval to the argument of a logistic // function. - constexpr double as[] = {-2.00568292, 10.45906746, 1.67438883, 334.45864705}; - constexpr double bs[] = {-4.97134419, 36.15096345, -82.25513499, 117.35186805}; + constexpr double as[] = {-1.06249702, 7.42016937, 0.89425629, 348.60356174}; + constexpr double bs[] = {-5.33122190, 39.57831533, -90.84473771, 123.40620748}; // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at move 32. static_assert(NormalizeToPawnValue == int(0.5 + as[0] + as[1] + as[2] + as[3])); From e67cc979fd2c0e66dfc2b2f2daa0117458cfc462 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 21 Feb 2024 22:17:23 +0100 Subject: [PATCH 0402/1309] Stockfish 16.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 16.1 Bench: 1303971 --- Stockfish 16.1 Today, we have the pleasure to announce Stockfish 16.1. As always, you can freely download it at https://stockfishchess.org/download and use it in the GUI of your choice[1]. Don't forget to join our Discord server[2] to get in touch with the community of developers and users of the project! *Quality of chess play* In our testing against its predecessor, Stockfish 16.1 shows a notable improvement in performance, with an Elo gain of up to 27 points and winning over 2 times more game pairs[3] than it loses. *Update highlights* *Improved evaluation* - Updated neural network architecture: The neural network architecture has undergone two updates and is currently in its 8th version[4]. - Removal of handcrafted evaluation (HCE): This release marks the removal of the traditional handcrafted evaluation and the transition to a fully neural network-based approach[5]. - Dual NNUE: For the first time, Stockfish includes a secondary neural network[6], used to quickly evaluate positions that are easily decided. *UCI Options removed* `Use NNUE` and `UCI_AnalyseMode`[7] have been removed as they no longer had any effect. `SlowMover`[8] has also been removed in favor of `Move Overhead`. *More binaries* We now offer 13 new binaries. These new binaries include `avx512`, `vnni256`, `vnni512`, `m1-apple-silicon`, and `armv8-dotprod`, which take advantage of specific CPU instructions for improved performance. For most users, using `sse41-popcnt` (formerly `modern`), `avx2`, or `bmi2` should be enough, but if your CPU supports these new instructions, feel free to try them! *Development changes* - Updated testing book: This new book[9], now derived exclusively from the open Lichess database[10], is 10 times larger than its predecessor, and has been used to test potential improvements to Stockfish over the past few months. - Consolidation of repositories: Aiming to simplify access to our resources, we have moved most Stockfish-related repositories into the official Stockfish organization[11] on GitHub. - Growing maintainer team: We welcome Disservin[12] to the team of maintainers of the project! This extra pair of hands will ensure the lasting success of Stockfish. *Thank you* The Stockfish project builds on a thriving community of enthusiasts (thanks everybody!) who contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We would like to express our gratitude for the 10k stars[13] that light up our GitHub project! Thank you for your support and encouragement – your recognition means a lot to us. We invite our chess fans to join the Fishtest testing framework[14], and programmers to contribute to the project either directly to Stockfish[15] (C++), to Fishtest[16] (HTML, CSS, JavaScript, and Python), to our trainer nnue-pytorch[17] (C++ and Python), or to our website[18] (HTML, CSS/SCSS, and JavaScript). The Stockfish team [1] https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage#download-a-chess-gui [2] https://discord.gg/GWDRS3kU6R [3] https://tests.stockfishchess.org/tests/view/65d666051d8e83c78bfddbd8 [4] https://github.com/official-stockfish/nnue-pytorch/blob/master/docs/nnue.md#sfnnv8-architecture [5] https://github.com/official-stockfish/Stockfish/commit/af110e0 [6] https://github.com/official-stockfish/Stockfish/commit/584d9ef [7] https://github.com/official-stockfish/Stockfish/commit/c53d2ec [8] https://github.com/official-stockfish/Stockfish/commit/536d692 [9] https://github.com/official-stockfish/books/commit/426eca4 [10] https://database.lichess.org/ [11] https://github.com/official-stockfish/ [12] https://github.com/Disservin [13] https://github.com/official-stockfish/Stockfish/stargazers [14] https://github.com/official-stockfish/fishtest/wiki/Running-the-worker [15] https://github.com/official-stockfish/Stockfish [16] https://github.com/official-stockfish/fishtest [17] https://github.com/official-stockfish/nnue-pytorch [18] https://github.com/official-stockfish/stockfish-web --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 4885a5cd35c..1d089971090 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -75,7 +75,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "dev"; +constexpr std::string_view version = "16.1"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From abcc090a625d7891146645ee493aec5ee8046dc5 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 20:40:48 +0100 Subject: [PATCH 0403/1309] Restore development closes https://github.com/official-stockfish/Stockfish/pull/5073 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 1d089971090..4885a5cd35c 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -75,7 +75,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "16.1"; +constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From c83c7f4e713c31a9d0811cdb8fb6469dc3a330be Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 24 Feb 2024 20:30:40 +0100 Subject: [PATCH 0404/1309] Make binaries executable again in CI closes https://github.com/official-stockfish/Stockfish/pull/5072 No functional change --- .github/workflows/upload_binaries.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 97bcf96f219..0dfd7244961 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -65,6 +65,7 @@ jobs: - name: Create tar if: runner.os != 'Windows' run: | + chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT tar -cvf stockfish-$NAME-$BINARY.tar stockfish - name: Create zip From 0c22d5bb1ab735a5191f713c18428d66a17535df Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 25 Feb 2024 11:16:55 +0100 Subject: [PATCH 0405/1309] Update Actions to Node20 ensure our CI continues to run after Node16 is obsolote on github. closes https://github.com/official-stockfish/Stockfish/pull/5074 No functional change --- .github/workflows/clang-format.yml | 4 ++-- .github/workflows/upload_binaries.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 0eb3fc70dab..e20e0d5d623 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -30,7 +30,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome == 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0 with: message: | clang-format 17 needs to be run on this PR. @@ -42,7 +42,7 @@ jobs: - name: Comment on PR if: steps.clang-format.outcome != 'failure' - uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3 + uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0 with: message: | _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 0dfd7244961..015b514ce4b 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -98,7 +98,7 @@ jobs: - name: Prerelease if: github.ref_name == 'master' && env.CHANGES == '0' continue-on-error: true - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1 + uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} From f77eddfa2ff6d5f496b398a9d743a5f02d358dc8 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:27:40 +0700 Subject: [PATCH 0406/1309] Join conditions for move sorting heuristics closes https://github.com/official-stockfish/Stockfish/pull/5078 No functional change. --- src/search.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 55a92947d32..3de1f69b817 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -607,20 +607,17 @@ Value Search::Worker::search( && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) - if (ttMove) + if (ttMove && ttValue >= beta) { - if (ttValue >= beta) - { - // Bonus for a quiet ttMove that fails high (~2 Elo) - if (!ttCapture) - update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); - - // Extra penalty for early quiet moves of - // the previous ply (~0 Elo on STC, ~2 Elo on LTC). - if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_malus(depth + 1)); - } + // Bonus for a quiet ttMove that fails high (~2 Elo) + if (!ttCapture) + update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); + + // Extra penalty for early quiet moves of + // the previous ply (~0 Elo on STC, ~2 Elo on LTC). + if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + -stat_malus(depth + 1)); } // Partial workaround for the graph history interaction problem From 0a3eb1d8fa1815f1f800e38b990247b9e58e27f5 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 20 Feb 2024 22:47:26 +0100 Subject: [PATCH 0407/1309] Document TT code more Slight refactor of the TT code with the goal to make it easier to understand / tweak. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/65d51e401d8e83c78bfdc427 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 56416 W: 14750 L: 14550 D: 27116 Ptnml(0-2): 227, 6386, 14796, 6558, 241 closes https://github.com/official-stockfish/Stockfish/pull/5061 No functional change --- src/tt.cpp | 33 ++++++++++++++++++++------------- src/tt.h | 25 ++++++++++++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index f3f58979d1b..8ef06e6355c 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -19,6 +19,7 @@ #include "tt.h" #include +#include #include #include #include @@ -53,6 +54,18 @@ void TTEntry::save( } +uint8_t TTEntry::relative_age(const uint8_t generation8) const { + // Due to our packed storage format for generation and its cyclic + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + + return (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8) + & TranspositionTable::GENERATION_MASK; +} + + // Sets the size of the transposition table, // measured in megabytes. Transposition table consists of a power of 2 number // of clusters and each cluster consists of ClusterSize number of TTEntry. @@ -111,24 +124,18 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) { - tte[i].genBound8 = - uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh + constexpr uint8_t lowerBits = GENERATION_DELTA - 1; - return found = bool(tte[i].depth8), &tte[i]; + // Refresh with new generation, keeping the lower bits the same. + tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & lowerBits)); + return found = bool(tte[i].depth8), &tte[i]; } // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) - // Due to our packed storage format for generation and its cyclic - // nature we add GENERATION_CYCLE (256 is the modulus, plus what - // is needed to keep the unrelated lowest n bits from affecting - // the result) to calculate the entry age correctly even after - // generation8 overflows into the next cycle. - if (replace->depth8 - - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) - > tte[i].depth8 - - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) + if (replace->depth8 - replace->relative_age(generation8) + > tte[i].depth8 - tte[i].relative_age(generation8)) replace = &tte[i]; return found = false, replace; @@ -137,7 +144,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { // Returns an approximation of the hashtable // occupation during a search. The hash is x permill full, as per UCI protocol. - +// Only counts entries which match the current generation. int TranspositionTable::hashfull() const { int cnt = 0; diff --git a/src/tt.h b/src/tt.h index 4115ee7ae51..554a81a572f 100644 --- a/src/tt.h +++ b/src/tt.h @@ -46,6 +46,8 @@ struct TTEntry { bool is_pv() const { return bool(genBound8 & 0x4); } Bound bound() const { return Bound(genBound8 & 0x3); } void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); + // The returned age is a multiple of TranspositionTable::GENERATION_DELTA + uint8_t relative_age(const uint8_t generation8) const; private: friend class TranspositionTable; @@ -76,16 +78,25 @@ class TranspositionTable { static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); // Constants used to refresh the hash table periodically - static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things - static constexpr int GENERATION_DELTA = - (1 << GENERATION_BITS); // increment for generation field - static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length - static constexpr int GENERATION_MASK = - (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + + // We have 8 bits available where the lowest 3 bits are + // reserved for other things. + static constexpr unsigned GENERATION_BITS = 3; + // increment for generation field + static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); + // cycle length + static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA; + // mask to pull out generation number + static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; public: ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things + + void new_search() { + // increment by delta to keep lower bits as is + generation8 += GENERATION_DELTA; + } + TTEntry* probe(const Key key, bool& found) const; int hashfull() const; void resize(size_t mbSize, int threadCount); From 7831131591fca89714a376099d6581ec0242244f Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 29 Feb 2024 14:27:00 -0800 Subject: [PATCH 0408/1309] Only evaluate the PSQT part of the small net for large evals. Thanks to Viren6 for suggesting to set complexity to 0. STC https://tests.stockfishchess.org/tests/view/65d7d6709b2da0226a5a203f LLR: 2.92 (-2.94,2.94) <0.00,2.00> Total: 328384 W: 85316 L: 84554 D: 158514 Ptnml(0-2): 1414, 39076, 82486, 39766, 1450 LTC https://tests.stockfishchess.org/tests/view/65dce6d290f639b028a54d2e LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 165162 W: 41918 L: 41330 D: 81914 Ptnml(0-2): 102, 18332, 45124, 18922, 101 closes https://github.com/official-stockfish/Stockfish/pull/5083 bench: 1504003 --- src/evaluate.cpp | 5 +- src/nnue/evaluate_nnue.cpp | 47 +++--- src/nnue/evaluate_nnue.h | 5 +- src/nnue/nnue_accumulator.h | 1 + src/nnue/nnue_feature_transformer.h | 252 +++++++++++++++------------- src/position.cpp | 23 ++- 6 files changed, 189 insertions(+), 144 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index f22c0d06cbe..cd026036b4c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -194,11 +194,12 @@ Value Eval::evaluate(const Position& pos, int optimism) { int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = std::abs(simpleEval) > 1050; + bool psqtOnly = std::abs(simpleEval) > 2500; int nnueComplexity; - Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) - : NNUE::evaluate(pos, true, &nnueComplexity); + Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity, psqtOnly) + : NNUE::evaluate(pos, true, &nnueComplexity, false); // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 5bd7e83d22f..efcf5b01734 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -179,16 +179,16 @@ write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDe void hint_common_parent_position(const Position& pos) { - int simpleEval = simple_eval(pos, pos.side_to_move()); - if (std::abs(simpleEval) > 1050) - featureTransformerSmall->hint_common_access(pos); + int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); + if (simpleEvalAbs > 1050) + featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > 2500); else - featureTransformerBig->hint_common_access(pos); + featureTransformerBig->hint_common_access(pos, false); } // Evaluation function. Perform differential calculation. template -Value evaluate(const Position& pos, bool adjusted, int* complexity) { +Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -213,15 +213,19 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = Net_Size == Small - ? featureTransformerSmall->transform(pos, transformedFeatures, bucket) - : featureTransformerBig->transform(pos, transformedFeatures, bucket); - const auto positional = Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) - : networkBig[bucket]->propagate(transformedFeatures); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = + Net_Size == Small + ? featureTransformerSmall->transform(pos, transformedFeatures, bucket, psqtOnly) + : featureTransformerBig->transform(pos, transformedFeatures, bucket, psqtOnly); + + const auto positional = + !psqtOnly ? (Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) + : networkBig[bucket]->propagate(transformedFeatures)) + : 0; if (complexity) - *complexity = std::abs(psqt - positional) / OutputScale; + *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; // Give more value to positional evaluation when adjusted flag is set if (adjusted) @@ -231,8 +235,8 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { return static_cast((psqt + positional) / OutputScale); } -template Value evaluate(const Position& pos, bool adjusted, int* complexity); -template Value evaluate(const Position& pos, bool adjusted, int* complexity); +template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); +template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); @@ -265,8 +269,9 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { t.correctBucket = (pos.count() - 1) / 4; for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { - const auto materialist = featureTransformerBig->transform(pos, transformedFeatures, bucket); - const auto positional = networkBig[bucket]->propagate(transformedFeatures); + const auto materialist = + featureTransformerBig->transform(pos, transformedFeatures, bucket, false); + const auto positional = networkBig[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); t.positional[bucket] = static_cast(positional / OutputScale); @@ -370,16 +375,18 @@ std::string trace(Position& pos) { auto st = pos.state(); pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = false; - st->accumulatorBig.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; Value eval = evaluate(pos); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = false; - st->accumulatorBig.computed[BLACK] = false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; } writeSquare(f, r, pc, v); diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index ea88f890227..c7b378604c5 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -76,7 +76,10 @@ using LargePagePtr = std::unique_ptr>; std::string trace(Position& pos); template -Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); +Value evaluate(const Position& pos, + bool adjusted = false, + int* complexity = nullptr, + bool psqtOnly = false); void hint_common_parent_position(const Position& pos); std::optional load_eval(std::istream& stream, NetSize netSize); diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 0b05d00da28..c0746b4ee86 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -34,6 +34,7 @@ struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[2][Size]; std::int32_t psqtAccumulation[2][PSQTBuckets]; bool computed[2]; + bool computedPSQT[2]; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 3399b82df6a..b42f160475f 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -250,18 +250,21 @@ class FeatureTransformer { } // Convert input features - std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { - update_accumulator(pos); - update_accumulator(pos); + std::int32_t + transform(const Position& pos, OutputType* output, int bucket, bool psqtOnly) const { + update_accumulator(pos, psqtOnly); + update_accumulator(pos, psqtOnly); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& accumulation = (pos.state()->*accPtr).accumulation; const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; - - const auto psqt = + const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; + if (psqtOnly) + return psqt; + + const auto& accumulation = (pos.state()->*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) { @@ -312,20 +315,22 @@ class FeatureTransformer { return psqt; } // end of function transform() - void hint_common_access(const Position& pos) const { - hint_common_access_for_perspective(pos); - hint_common_access_for_perspective(pos); + void hint_common_access(const Position& pos, bool psqtOnly) const { + hint_common_access_for_perspective(pos, psqtOnly); + hint_common_access_for_perspective(pos, psqtOnly); } private: template [[nodiscard]] std::pair - try_find_computed_accumulator(const Position& pos) const { + try_find_computed_accumulator(const Position& pos, bool psqtOnly) 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(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !(st->*accPtr).computed[Perspective]) + while (st->previous + && (!(st->*accPtr).computedPSQT[Perspective] + || (!psqtOnly && !(st->*accPtr).computed[Perspective]))) { // This governs when a full feature refresh is needed and how many // updates are better than just one full refresh. @@ -347,7 +352,8 @@ class FeatureTransformer { template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, - StateInfo* states_to_update[N]) const { + StateInfo* states_to_update[N], + bool psqtOnly) const { static_assert(N > 0); assert(states_to_update[N - 1] == nullptr); @@ -383,7 +389,8 @@ class FeatureTransformer { for (; i >= 0; --i) { - (states_to_update[i]->*accPtr).computed[Perspective] = true; + (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; + (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; @@ -403,31 +410,34 @@ class FeatureTransformer { { assert(states_to_update[0]); - auto accIn = - reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + if (!psqtOnly) + { + auto accIn = + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); + auto accOut = reinterpret_cast( + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); - const IndexType offsetR0 = HalfDimensions * removed[0][0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; - auto columnA = reinterpret_cast(&weights[offsetA]); + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); - if (removed[0].size() == 1) - { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); - } - else - { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); + if (removed[0].size() == 1) + { + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); + } } auto accPsqtIn = @@ -461,41 +471,43 @@ class FeatureTransformer { } else { - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - // Load accumulator - auto accTileIn = reinterpret_cast( - &(st->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) + if (!psqtOnly) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } + // Load accumulator + auto accTileIn = reinterpret_cast( + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); - // Difference calculation for the activated features - for (const auto index : added[i]) + for (IndexType i = 0; states_to_update[i]; ++i) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = + reinterpret_cast(&(states_to_update[i]->*accPtr) + .accumulation[Perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + vec_store(&accTileOut[k], acc[k]); } - - // Store accumulator - auto accTileOut = reinterpret_cast( - &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); } - } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -537,8 +549,10 @@ class FeatureTransformer { #else for (IndexType i = 0; states_to_update[i]; ++i) { - std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], - (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); + if (!psqtOnly) + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], + HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = @@ -549,10 +563,12 @@ class FeatureTransformer { // Difference calculation for the deactivated features for (const auto index : removed[i]) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] -= @@ -562,10 +578,12 @@ class FeatureTransformer { // Difference calculation for the activated features for (const auto index : added[i]) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] += @@ -576,7 +594,7 @@ class FeatureTransformer { } template - void update_accumulator_refresh(const Position& pos) const { + void update_accumulator_refresh(const Position& pos, bool psqtOnly) const { #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array // is defined in the VECTOR code below, once in each branch @@ -587,32 +605,34 @@ class FeatureTransformer { // Refresh the accumulator // Could be extracted to a separate function because it's done in 2 places, // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = !psqtOnly; + accumulator.computedPSQT[Perspective] = true; FeatureSet::IndexList active; FeatureSet::append_active_indices(pos, active); #ifdef VECTOR - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - for (const auto index : active) + if (!psqtOnly) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); - auto accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -635,18 +655,21 @@ class FeatureTransformer { } #else - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); + if (!psqtOnly) + std::memcpy(accumulator.accumulation[Perspective], biases, + HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) accumulator.psqtAccumulation[Perspective][k] = 0; for (const auto index : active) { - const IndexType offset = HalfDimensions * index; - - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[Perspective][j] += weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) accumulator.psqtAccumulation[Perspective][k] += @@ -656,7 +679,7 @@ class FeatureTransformer { } template - void hint_common_access_for_perspective(const Position& pos) const { + void hint_common_access_for_perspective(const Position& pos, bool psqtOnly) const { // Works like update_accumulator, but performs less work. // Updates ONLY the accumulator for pos. @@ -664,27 +687,31 @@ class FeatureTransformer { // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. // Fast early exit. - if ((pos.state()->*accPtr).computed[Perspective]) + if ((pos.state()->*accPtr).computed[Perspective] + || (psqtOnly && (pos.state()->*accPtr).computedPSQT[Perspective])) return; - auto [oldest_st, _] = try_find_computed_accumulator(pos); + auto [oldest_st, _] = try_find_computed_accumulator(pos, psqtOnly); - if ((oldest_st->*accPtr).computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective] + || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) { // Only update current position accumulator to minimize work. StateInfo* states_to_update[2] = {pos.state(), nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); } else - update_accumulator_refresh(pos); + update_accumulator_refresh(pos, psqtOnly); } template - void update_accumulator(const Position& pos) const { + void update_accumulator(const Position& pos, bool psqtOnly) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos); + auto [oldest_st, next] = try_find_computed_accumulator(pos, psqtOnly); - if ((oldest_st->*accPtr).computed[Perspective]) + if ((oldest_st->*accPtr).computed[Perspective] + || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) { if (next == nullptr) return; @@ -697,12 +724,11 @@ class FeatureTransformer { StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update); + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); } else - { - update_accumulator_refresh(pos); - } + update_accumulator_refresh(pos, psqtOnly); } alignas(CacheLineSize) BiasType biases[HalfDimensions]; diff --git a/src/position.cpp b/src/position.cpp index c89b1eb0889..2263afe7669 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -680,10 +680,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - auto& dp = st->dirtyPiece; - dp.dirty_num = 1; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = + st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = + false; + + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; Color us = sideToMove; Color them = ~us; @@ -965,10 +969,13 @@ void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { newSt.previous = st; 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; + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = + st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = + false; if (st->epSquare != SQ_NONE) { From 6d0d430860fb72137339c001b991f2f4440e45eb Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sun, 18 Feb 2024 13:47:52 +0700 Subject: [PATCH 0409/1309] Simplify IIR Simplified depth reduction for PV nodes without a ttMove to 3. Passed STC non-reg: https://tests.stockfishchess.org/tests/view/65d1a90a1d8e83c78bfd855a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 363168 W: 93648 L: 93791 D: 175729 Ptnml(0-2): 1557, 43692, 91221, 43565, 1549 Passed LTC non-reg: https://tests.stockfishchess.org/tests/view/65d5612d1d8e83c78bfdc8e2 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 58818 W: 14946 L: 14761 D: 29111 Ptnml(0-2): 36, 6595, 15962, 6780, 36 closes https://github.com/official-stockfish/Stockfish/pull/5062 Bench: 1505827 --- src/search.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3de1f69b817..951e206c4b9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -805,13 +805,11 @@ Value Search::Worker::search( } // Step 10. Internal iterative reductions (~9 Elo) - // For PV nodes without a ttMove, we decrease depth by 2, - // or by 4 if the current position is present in the TT and - // the stored depth is greater than or equal to the current depth. - // Use qsearch if depth <= 0. + // For PV nodes without a ttMove, we decrease depth by 3. if (PvNode && !ttMove) - depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth); + depth -= 3; + // Use qsearch if depth <= 0. if (depth <= 0) return qsearch(pos, ss, alpha, beta); From b0ac8a4e3bad2ed1b066850b6f151ddbe48d1dc6 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Thu, 22 Feb 2024 04:37:26 +0700 Subject: [PATCH 0410/1309] Simplify extension when ttMove is assumed to fail high over current beta Simplify extension value to -3 when ttMove is assumed to fail high over current beta. Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65d66ed81d8e83c78bfddcba LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235136 W: 60711 L: 60708 D: 113717 Ptnml(0-2): 969, 27904, 59874, 27797, 1024 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65da2994944f2a78d4733107 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 662850 W: 166161 L: 166602 D: 330087 Ptnml(0-2): 394, 74895, 181274, 74482, 380 closes https://github.com/official-stockfish/Stockfish/pull/5088 Bench: 1553115 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 951e206c4b9..70baffe348f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1056,7 +1056,7 @@ Value Search::Worker::search( // If the ttMove is assumed to fail high over current beta (~7 Elo) else if (ttValue >= beta) - extension = -2 - !PvNode; + extension = -3; // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) From a615efb19f5dfb4b205ed3a9dd8525e54e8777cc Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 26 Feb 2024 18:08:22 +0300 Subject: [PATCH 0411/1309] Simplify Time Management Instead of having a formula for using extra time with larger increments. Simply set it to 1 when the increment is lower than 0.5s and to 1.1 when the increment is higher. The values can later on be further improved. Passed STC: https://tests.stockfishchess.org/tests/view/65d25d3c1d8e83c78bfd9293 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 27488 W: 7077 L: 6848 D: 13563 Ptnml(0-2): 96, 3041, 7267, 3218, 122 Passed LTC: https://tests.stockfishchess.org/tests/view/65d2a72c1d8e83c78bfd97fa LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 137568 W: 34612 L: 34512 D: 68444 Ptnml(0-2): 60, 14672, 39221, 14770, 61 Passed VLTC: https://tests.stockfishchess.org/tests/view/65d7d7d39b2da0226a5a205b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 139650 W: 35229 L: 35134 D: 69287 Ptnml(0-2): 33, 14227, 41218, 14306, 41 Passed also the TCEC TC style suggested by vondele: https://tests.stockfishchess.org/tests/view/65e4ca73416ecd92c162a57d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134150 W: 34278 L: 34163 D: 65709 Ptnml(0-2): 561, 15727, 34444, 15722, 621 closes https://github.com/official-stockfish/Stockfish/pull/5076 Bench: 1553115 --- src/timeman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 72a447af5b8..e620dede7c9 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -94,7 +94,7 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.movestogo == 0) { // Use extra time with larger increments - double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11); + double optExtra = limits.inc[us] < 500 ? 1.0 : 1.1; // Calculate time constants based on current time left. double optConstant = From a96b0d46093c67707e4e75e7aa5aa057b7c131a2 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 4 Mar 2024 16:13:36 +0300 Subject: [PATCH 0412/1309] Update elo estimates Tests used to change the elo worth of some functions: https://tests.stockfishchess.org/tests/view/65c3f69dc865510db0283eef https://tests.stockfishchess.org/tests/view/65c3f935c865510db0283f2a https://tests.stockfishchess.org/tests/view/65d1489f1d8e83c78bfd7dbf https://tests.stockfishchess.org/tests/view/65ce9d361d8e83c78bfd4951 https://tests.stockfishchess.org/tests/view/65cfcd901d8e83c78bfd6184 closes https://github.com/official-stockfish/Stockfish/pull/5089 No functional change --- src/search.cpp | 8 ++++---- src/timeman.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 70baffe348f..8ed7841e2c1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -614,7 +614,7 @@ Value Search::Worker::search( update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); // Extra penalty for early quiet moves of - // the previous ply (~0 Elo on STC, ~2 Elo on LTC). + // the previous ply (~1 Elo on STC, ~2 Elo on LTC) if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_malus(depth + 1)); @@ -1067,7 +1067,7 @@ Value Search::Worker::search( extension = -1; } - // Recapture extensions (~1 Elo) + // Recapture extensions (~0 Elo on STC, ~1 Elo on LTC) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] @@ -1105,7 +1105,7 @@ Value Search::Worker::search( if (ttCapture) r++; - // Decrease reduction for PvNodes (~3 Elo) + // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) r--; @@ -1167,7 +1167,7 @@ Value Search::Worker::search( // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { - // Increase reduction if ttMove is not present (~1 Elo) + // Increase reduction if ttMove is not present (~6 Elo) if (!ttMove) r += 2; diff --git a/src/timeman.cpp b/src/timeman.cpp index e620dede7c9..b64ec77388f 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -89,8 +89,8 @@ void TimeManagement::init(Search::LimitsType& limits, - moveOverhead * (2 + mtg)); // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed actual available - // game time for the current move, so also cap to 20% of available game time. + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available game time. if (limits.movestogo == 0) { // Use extra time with larger increments From bd579ab5d1a931a09a62f2ed33b5149ada7bc65f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 1 Mar 2024 10:34:03 -0800 Subject: [PATCH 0413/1309] Update default main net to nn-1ceb1ade0001.nnue Created by retraining the previous main net `nn-b1a57edbea57.nnue` with: - some of the same options as before: - ranger21, more WDL skipping, 15% more loss when Q is too high - removal of the huge 514G pre-interleaved binpack - removal of SF-generated dfrc data (dfrc99-16tb7p-filt-v2.min.binpack) - interleaving many binpacks at training time - training with some bestmove capture positions where SEE < 0 - increased usage of torch.compile to speed up training by up to 40% ```yaml experiment-name: 2560--S10-dfrc0-to-dec2023-skip-more-wdl-15p-more-loss-high-q-see-ge0-sk28 nnue-pytorch-branch: linrock/nnue-pytorch/r21-more-wdl-skip-15p-more-loss-high-q-skip-see-ge0-torch-compile-more start-from-engine-test-net: True early-fen-skipping: 28 training-dataset: # similar, not the exact same as: # https://github.com/official-stockfish/Stockfish/pull/4635 - /data/S5-5af/leela96.v2.min.binpack - /data/S5-5af/test60-2021-11-12-novdec-12tb7p.v6-dd.min.binpack - /data/S5-5af/test77-2021-12-dec-16tb7p.v6-dd.min.binpack - /data/S5-5af/test78-2022-01-to-05-jantomay-16tb7p.v6-dd.min.binpack - /data/S5-5af/test78-2022-06-to-09-juntosep-16tb7p.v6-dd.min.binpack - /data/S5-5af/test79-2022-04-apr-16tb7p.v6-dd.min.binpack - /data/S5-5af/test79-2022-05-may-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-06-jun-16tb7p.v6-dd.min.unmin.binpack - /data/S5-5af/test80-2022-07-jul-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-08-aug-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-09-sep-16tb7p.v6-dd.min.unmin.binpack - /data/S5-5af/test80-2022-10-oct-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2022-11-nov-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2023-01-jan-16tb7p.v6-sk20.min.binpack - /data/S5-5af/test80-2023-02-feb-16tb7p.v6-dd.min.binpack - /data/S5-5af/test80-2023-03-mar-2tb7p.min.unmin.binpack - /data/S5-5af/test80-2023-04-apr-2tb7p.binpack - /data/S5-5af/test80-2023-05-may-2tb7p.min.dd.binpack # https://github.com/official-stockfish/Stockfish/pull/4782 - /data/S6-1ee1aba5ed/test80-2023-06-jun-2tb7p.binpack - /data/S6-1ee1aba5ed/test80-2023-07-jul-2tb7p.min.binpack # https://github.com/official-stockfish/Stockfish/pull/4972 - /data/S8-baff1edbea57/test80-2023-08-aug-2tb7p.v6.min.binpack - /data/S8-baff1edbea57/test80-2023-09-sep-2tb7p.binpack - /data/S8-baff1edbea57/test80-2023-10-oct-2tb7p.binpack # https://github.com/official-stockfish/Stockfish/pull/5056 - /data/S9-b1a57edbea57/test80-2023-11-nov-2tb7p.binpack - /data/S9-b1a57edbea57/test80-2023-12-dec-2tb7p.binpack num-epochs: 800 lr: 4.375e-4 gamma: 0.995 start-lambda: 1.0 end-lambda: 0.7 ``` This particular net was reached at epoch 759. Use of more torch.compile decorators in nnue-pytorch model.py than in the previous main net training run sped up training by up to 40% on Tesla gpus when using recent pytorch compiled with cuda 12: https://github.com/linrock/nnue-tools/blob/7fb9831/Dockerfile Skipping positions with bestmove captures where static exchange evaluation is >= 0 is based on the implementation from Sopel's NNUE training & experimentation log: https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY Experiment 293 - only skip captures with see>=0 Positions with bestmove captures where score == 0 are always skipped for compatibility with minimized binpacks, since the original minimizer sets scores to 0 for slight improvements in compression. The trainer branch used was: https://github.com/linrock/nnue-pytorch/tree/r21-more-wdl-skip-15p-more-loss-high-q-skip-see-ge0-torch-compile-more Binpacks were renamed to be sorted chronologically by default when sorted by name. The binpack data are otherwise the same as binpacks with similar names in the prior naming convention. Training data can be found at: https://robotmoon.com/nnue-training-data/ Passed STC: https://tests.stockfishchess.org/tests/view/65e3ddd1f2ef6c733362ae5c LLR: 2.92 (-2.94,2.94) <0.00,2.00> Total: 149792 W: 39153 L: 38661 D: 71978 Ptnml(0-2): 675, 17586, 37905, 18032, 698 Passed LTC: https://tests.stockfishchess.org/tests/view/65e4d91c416ecd92c162a69b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 64416 W: 16517 L: 16135 D: 31764 Ptnml(0-2): 38, 7218, 17313, 7602, 37 closes https://github.com/official-stockfish/Stockfish/pull/5090 Bench: 1373183 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 53928bf6494..33fed3d5f5f 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,7 +39,7 @@ Value evaluate(const Position& pos, int optimism); // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. -#define EvalFileDefaultNameBig "nn-b1a57edbea57.nnue" +#define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" struct EvalFile { From 1db969e6200afe4f023469a56aa5edf755d92bbb Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Thu, 15 Feb 2024 23:01:02 +0100 Subject: [PATCH 0414/1309] Reduce futility_margin if opponents last move was bad This reduces the futiltiy_margin if our opponents last move was bad by around ~1/3 when not improving and ~1/2.7 when improving, the idea being to retroactively futility prune moves that were played, but turned out to be bad. A bad move is being defined as their staticEval before their move being lower as our staticEval now is. If the depth is 2 and we are improving the opponent worsening flag is not set, in order to not risk having a too low futility_margin, due to the fact that when these conditions are met the futility_margin already drops quite low. Passed STC: https://tests.stockfishchess.org/tests/live_elo/65e3977bf2ef6c733362aae3 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 122432 W: 31884 L: 31436 D: 59112 Ptnml(0-2): 467, 14404, 31035, 14834, 476 Passed LTC: https://tests.stockfishchess.org/tests/live_elo/65e47f40f2ef6c733362b6d2 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 421692 W: 106572 L: 105452 D: 209668 Ptnml(0-2): 216, 47217, 114865, 48327, 221 closes https://github.com/official-stockfish/Stockfish/pull/5092 Bench: 1565939 --- src/search.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ed7841e2c1..f135a0900a6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -53,11 +53,13 @@ using namespace Search; namespace { - // Futility margin -Value futility_margin(Depth d, bool noTtCutNode, bool improving) { - Value futilityMult = 117 - 44 * noTtCutNode; - return (futilityMult * d - 3 * futilityMult / 2 * improving); +Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { + Value futilityMult = 117 - 44 * noTtCutNode; + Value improvingDeduction = 3 * improving * futilityMult / 2; + Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; + + return futilityMult * d - improvingDeduction - worseningDeduction; } constexpr int futility_move_count(bool improving, Depth depth) { @@ -533,7 +535,7 @@ Value Search::Worker::search( Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture; + bool givesCheck, improving, priorCapture, opponenWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -742,6 +744,8 @@ Value Search::Worker::search( ? ss->staticEval > (ss - 2)->staticEval : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; + opponenWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); + // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. @@ -756,7 +760,7 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving) + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponenWorsening) - (ss - 1)->statScore / 314 >= beta && eval >= beta && eval < 30016 // smaller than TB wins From 6136d094c5f46456964889754ae2d6098834b14f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 7 Mar 2024 11:57:18 +0300 Subject: [PATCH 0415/1309] Introduce double extensions for PV nodes Our double/triple extensions were allowed only for non-pv nodes. This patch allows them to be done for PV nodes, with some stricter conditions. Passed STC: https://tests.stockfishchess.org/tests/view/65d657ec1d8e83c78bfddab8 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 339424 W: 88097 L: 87318 D: 164009 Ptnml(0-2): 1573, 39935, 85729, 41090, 1385 Passed LTC: https://tests.stockfishchess.org/tests/view/65dd63824b19edc854ebc433 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 459564 W: 115812 L: 114614 D: 229138 Ptnml(0-2): 248, 51441, 125173, 52705, 215 closes https://github.com/official-stockfish/Stockfish/pull/5093 Bench: 1714391 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index f135a0900a6..ff32ecc1a0e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1042,6 +1042,8 @@ Value Search::Worker::search( extension = 2 + (value < singularBeta - 78 && !ttCapture); depth += depth < 16; } + if (PvNode && !ttCapture && ss->multipleExtensions <= 5 && value < singularBeta - 50) + extension = 2; } // Multi-cut pruning From 748791f80dbc29793e473e3e9eda83ffa0afcfaa Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Wed, 6 Mar 2024 20:56:55 +0300 Subject: [PATCH 0416/1309] Fix `go mate x` in multithreading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes two issues with master for go mate x: - when running go mate x in losing positions, master always goes to the maximal depth, arguably against what the UCI protocol demands - when running go mate x in winning positions with multiple threads, master may return non-mate scores from the search (this issue is present in stockfish since at least sf16) The issues are fixed by (a) also checking if score is mate -x and by (b) only letting mainthread stop the search for go mate x commands, and by not looking for a best thread but using mainthread as per the default. Related: niklasf/python-chess#1070 More diagnostics can be found here peregrineshahin#6 (comment) closes https://github.com/official-stockfish/Stockfish/pull/5094 No functional change Co-Authored-By: Robert Nürnberg <28635489+robertnurnberg@users.noreply.github.com> --- src/search.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ff32ecc1a0e..7bf3e7f96f9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -187,7 +187,7 @@ void Search::Worker::start_searching() { Skill skill = Skill(options["Skill Level"], options["UCI_LimitStrength"] ? int(options["UCI_Elo"]) : 0); - if (int(options["MultiPV"]) == 1 && !limits.depth && !skill.enabled() + if (int(options["MultiPV"]) == 1 && !limits.depth && !limits.mate && !skill.enabled() && rootMoves[0].pv[0] != Move::none()) bestThread = threads.get_best_thread()->worker.get(); @@ -399,14 +399,18 @@ void Search::Worker::iterative_deepening() { lastBestMoveDepth = rootDepth; } - // Have we found a "mate in x"? - if (limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * limits.mate) - threads.stop = true; - if (!mainThread) continue; + // Have we found a "mate in x"? + if (limits.mate && rootMoves[0].score == rootMoves[0].uciScore + && ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - rootMoves[0].score <= 2 * limits.mate) + || (rootMoves[0].score != -VALUE_INFINITE + && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY + && VALUE_MATE + rootMoves[0].score <= 2 * limits.mate))) + threads.stop = true; + // If the skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(rootMoves, multiPV); From 0f01a516d2ddd475bbe3bccab176dbbccb879053 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:48:02 +0800 Subject: [PATCH 0417/1309] VLTC time management tune Result of 35k games of SPSA tuning at 180+1.8. Tuning attempt can be found here: https://tests.stockfishchess.org/tests/view/65e40599f2ef6c733362b03b Passed VLTC 180+1.8: https://tests.stockfishchess.org/tests/view/65e5a6f5416ecd92c162b5d4 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 31950 W: 8225 L: 7949 D: 15776 Ptnml(0-2): 3, 3195, 9309, 3459, 9 Passed VLTC 240+2.4: https://tests.stockfishchess.org/tests/view/65e714de0ec64f0526c3d1f1 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 65108 W: 16558 L: 16202 D: 32348 Ptnml(0-2): 7, 6366, 19449, 6728, 4 closes https://github.com/official-stockfish/Stockfish/pull/5095 Bench: 1714391 --- src/search.cpp | 20 ++++++++++---------- src/timeman.cpp | 14 +++++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7bf3e7f96f9..335149d576f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -429,15 +429,15 @@ void Search::Worker::iterative_deepening() { int nodesEffort = effort[bestmove.from_sq()][bestmove.to_sq()] * 100 / std::max(size_t(1), size_t(nodes)); - double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue) - + 6 * (mainThread->iterValue[iterIdx] - bestValue)) - / 616.6; - fallingEval = std::clamp(fallingEval, 0.51, 1.51); + double fallingEval = (1067 + 223 * (mainThread->bestPreviousAverageScore - bestValue) + + 97 * (mainThread->iterValue[iterIdx] - bestValue)) + / 10000.0; + fallingEval = std::clamp(fallingEval, 0.580, 1.667); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69; - double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction); - double bestMoveInstability = 1 + 1.79 * totBestMoveChanges / threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; + double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); + double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; @@ -446,8 +446,8 @@ void Search::Worker::iterative_deepening() { if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); - if (completedDepth >= 10 && nodesEffort >= 95 - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 3 / 4 + if (completedDepth >= 10 && nodesEffort >= 97 + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.739 && !mainThread->ponder) { threads.stop = true; @@ -464,7 +464,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else if (!mainThread->ponder - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.50) + && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.506) threads.increaseDepth = false; else threads.increaseDepth = true; diff --git a/src/timeman.cpp b/src/timeman.cpp index b64ec77388f..4607344eab9 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -94,17 +94,17 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.movestogo == 0) { // Use extra time with larger increments - double optExtra = limits.inc[us] < 500 ? 1.0 : 1.1; + double optExtra = limits.inc[us] < 500 ? 1.0 : 1.13; // Calculate time constants based on current time left. double optConstant = - std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049); - double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76); + std::min(0.00308 + 0.000319 * std::log10(limits.time[us] / 1000.0), 0.00506); + double maxConstant = std::max(3.39 + 3.01 * std::log10(limits.time[us] / 1000.0), 2.93); - optScale = std::min(0.0120 + std::pow(ply + 3.1, 0.44) * optConstant, - 0.21 * limits.time[us] / double(timeLeft)) + optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, + 0.213 * limits.time[us] / double(timeLeft)) * optExtra; - maxScale = std::min(6.9, maxConstant + ply / 12.2); + maxScale = std::min(6.64, maxConstant + ply / 12.0); } // x moves in y seconds (+ z increment) @@ -117,7 +117,7 @@ void TimeManagement::init(Search::LimitsType& limits, // Limit the maximum possible time for this move optimumTime = TimePoint(optScale * timeLeft); maximumTime = - TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + TimePoint(std::min(0.825 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (options["Ponder"]) optimumTime += optimumTime / 4; From 632f1c21cd271e7c4c242fdafa328a55ec63b9cb Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Thu, 7 Mar 2024 22:01:40 +0100 Subject: [PATCH 0418/1309] Fix wrong constant usage in go mate Fixes an oversight in https://github.com/official-stockfish/Stockfish/pull/5094 In theory, master could stop search when run with `go mate 247` and return a TB loss (not a mate score). Also fixes the spelling of opponenWorsening. closes https://github.com/official-stockfish/Stockfish/pull/5096 No functional change --- src/search.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 335149d576f..533cf61b613 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -407,7 +407,7 @@ void Search::Worker::iterative_deepening() { && ((rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - rootMoves[0].score <= 2 * limits.mate) || (rootMoves[0].score != -VALUE_INFINITE - && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY + && rootMoves[0].score <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + rootMoves[0].score <= 2 * limits.mate))) threads.stop = true; @@ -539,7 +539,7 @@ Value Search::Worker::search( Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; - bool givesCheck, improving, priorCapture, opponenWorsening; + bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; @@ -748,7 +748,7 @@ Value Search::Worker::search( ? ss->staticEval > (ss - 2)->staticEval : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; - opponenWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); + opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, @@ -764,7 +764,7 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponenWorsening) + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - (ss - 1)->statScore / 314 >= beta && eval >= beta && eval < 30016 // smaller than TB wins @@ -1046,7 +1046,8 @@ Value Search::Worker::search( extension = 2 + (value < singularBeta - 78 && !ttCapture); depth += depth < 16; } - if (PvNode && !ttCapture && ss->multipleExtensions <= 5 && value < singularBeta - 50) + if (PvNode && !ttCapture && ss->multipleExtensions <= 5 + && value < singularBeta - 50) extension = 2; } From b6dfd6bd54979b5ba96716c2b246d84e5fa5b9fb Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 9 Mar 2024 12:14:57 +0100 Subject: [PATCH 0419/1309] Assorted cleanups - fix naming convention for `workingDirectory` - use type alias for `EvalFiles` everywhere - move `ponderMode` into `LimitsType` - move limits parsing into standalone static function closes https://github.com/official-stockfish/Stockfish/pull/5098 No functional change --- src/main.cpp | 3 +-- src/nnue/evaluate_nnue.cpp | 7 +++---- src/nnue/evaluate_nnue.h | 11 ++--------- src/search.h | 2 ++ src/thread.cpp | 5 ++--- src/thread.h | 3 +-- src/uci.cpp | 15 ++++++++++----- src/uci.h | 13 +++++-------- 8 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index de07d6a8738..6ce656d7fe8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,7 +17,6 @@ */ #include -#include #include "bitboard.h" #include "evaluate.h" @@ -40,7 +39,7 @@ int main(int argc, char* argv[]) { Tune::init(uci.options); - uci.evalFiles = Eval::NNUE::load_networks(uci.workingDirectory(), uci.options, uci.evalFiles); + uci.evalFiles = Eval::NNUE::load_networks(uci.working_directory(), uci.options, uci.evalFiles); uci.loop(); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index efcf5b01734..1efd83bf684 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include "../evaluate.h" #include "../misc.h" @@ -452,9 +451,9 @@ bool save_eval(std::ostream& stream, } // Save eval, to a file given by its name -bool save_eval(const std::optional& filename, - NetSize netSize, - const std::unordered_map& evalFiles) { +bool save_eval(const std::optional& filename, + NetSize netSize, + const EvalFiles& evalFiles) { std::string actualFilename; std::string msg; diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index c7b378604c5..febe8f9d9b9 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -26,8 +26,8 @@ #include #include #include -#include +#include "../evaluate.h" #include "../misc.h" #include "../types.h" #include "nnue_architecture.h" @@ -35,11 +35,6 @@ namespace Stockfish { class Position; - -namespace Eval { -struct EvalFile; -} - } namespace Stockfish::Eval::NNUE { @@ -87,9 +82,7 @@ bool save_eval(std::ostream& stream, NetSize netSize, const std::string& name, const std::string& netDescription); -bool save_eval(const std::optional& filename, - NetSize netSize, - const std::unordered_map&); +bool save_eval(const std::optional& filename, NetSize netSize, const EvalFiles&); } // namespace Stockfish::Eval::NNUE diff --git a/src/search.h b/src/search.h index 4a1c68bb9ce..bb9f63fff82 100644 --- a/src/search.h +++ b/src/search.h @@ -109,6 +109,7 @@ struct LimitsType { time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); movestogo = depth = mate = perft = infinite = 0; nodes = 0; + ponderMode = false; } bool use_time_management() const { return time[WHITE] || time[BLACK]; } @@ -117,6 +118,7 @@ struct LimitsType { TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; int movestogo, depth, mate, perft, infinite; uint64_t nodes; + bool ponderMode; }; diff --git a/src/thread.cpp b/src/thread.cpp index 95646601106..b62f5d8a39d 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -163,13 +163,12 @@ void ThreadPool::clear() { void ThreadPool::start_thinking(const OptionsMap& options, Position& pos, StateListPtr& states, - Search::LimitsType limits, - bool ponderMode) { + Search::LimitsType limits) { main_thread()->wait_for_search_finished(); main_manager()->stopOnPonderhit = stop = abortedSearch = false; - main_manager()->ponder = ponderMode; + main_manager()->ponder = limits.ponderMode; increaseDepth = true; diff --git a/src/thread.h b/src/thread.h index a2a1d18c454..0d4c252ccc3 100644 --- a/src/thread.h +++ b/src/thread.h @@ -79,8 +79,7 @@ class ThreadPool { } } - void - start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType, bool = false); + void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); void clear(); void set(Search::SharedState); diff --git a/src/uci.cpp b/src/uci.cpp index 4d4ea689715..357369bf562 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -175,11 +175,9 @@ void UCI::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } -void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { - +Search::LimitsType UCI::parse_limits(const Position& pos, std::istream& is) { Search::LimitsType limits; std::string token; - bool ponderMode = false; limits.startTime = now(); // The search starts as early as possible @@ -211,7 +209,14 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { else if (token == "infinite") limits.infinite = 1; else if (token == "ponder") - ponderMode = true; + limits.ponderMode = true; + + return limits; +} + +void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { + + Search::LimitsType limits = parse_limits(pos, is); Eval::NNUE::verify(options, evalFiles); @@ -221,7 +226,7 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { return; } - threads.start_thinking(options, pos, states, limits, ponderMode); + threads.start_thinking(options, pos, states, limits); } void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { diff --git a/src/uci.h b/src/uci.h index 9d5f524ad65..f25bb8d517f 100644 --- a/src/uci.h +++ b/src/uci.h @@ -21,7 +21,6 @@ #include #include -#include #include "evaluate.h" #include "misc.h" @@ -29,13 +28,10 @@ #include "thread.h" #include "tt.h" #include "ucioption.h" +#include "search.h" namespace Stockfish { -namespace Eval::NNUE { -enum NetSize : int; -} - class Move; enum Square : int; using Value = int; @@ -53,11 +49,12 @@ class UCI { static std::string wdl(Value v, int ply); static Move to_move(const Position& pos, std::string& str); - const std::string& workingDirectory() const { return cli.workingDirectory; } + static Search::LimitsType parse_limits(const Position& pos, std::istream& is); - OptionsMap options; + const std::string& working_directory() const { return cli.workingDirectory; } - std::unordered_map evalFiles; + OptionsMap options; + Eval::NNUE::EvalFiles evalFiles; private: TranspositionTable tt; From 10e27329783c9f92bad8b7bd7fe496382a7fc0cd Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:58:21 +0100 Subject: [PATCH 0420/1309] VVLTC search tune Result of 32k games of tuning at 60+0.6 8-thread. Link to the tuning attempt: https://tests.stockfishchess.org/tests/view/65def7b04b19edc854ebdec8 Passed VVLTC first SPRT: https://tests.stockfishchess.org/tests/view/65e51b53416ecd92c162ab7f LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 37570 W: 9613 L: 9342 D: 18615 Ptnml(0-2): 2, 3454, 11601, 3727, 1 Passed VVLTC second SPRT: https://tests.stockfishchess.org/tests/view/65e87d1c0ec64f0526c3eb39 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 123158 W: 31463 L: 31006 D: 60689 Ptnml(0-2): 5, 11589, 37935, 12044, 6 Note: The small net and psqt-only thresholds have been moved to evaluate.h. The reasoning is that these values are used in both `evaluate.cpp` and `evaluate_nnue.cpp`, and thus unifying their usage avoids inconsistencies during testing, where one occurrence is changed without the other (this happened during the search tune SPRT). closes https://github.com/official-stockfish/Stockfish/pull/5101 Bench: 1741218 --- src/evaluate.cpp | 4 +- src/evaluate.h | 2 + src/nnue/evaluate_nnue.cpp | 4 +- src/search.cpp | 75 +++++++++++++++++++------------------- 4 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index cd026036b4c..1eb58fd24b3 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -193,8 +193,8 @@ Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); int simpleEval = simple_eval(pos, pos.side_to_move()); - bool smallNet = std::abs(simpleEval) > 1050; - bool psqtOnly = std::abs(simpleEval) > 2500; + bool smallNet = std::abs(simpleEval) > SmallNetThreshold; + bool psqtOnly = std::abs(simpleEval) > PsqtOnlyThreshold; int nnueComplexity; diff --git a/src/evaluate.h b/src/evaluate.h index 33fed3d5f5f..a690a3bbaf8 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -31,6 +31,8 @@ class OptionsMap; namespace Eval { +constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; + std::string trace(Position& pos); int simple_eval(const Position& pos, Color c); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index 1efd83bf684..854fed06e82 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -179,8 +179,8 @@ write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDe void hint_common_parent_position(const Position& pos) { int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); - if (simpleEvalAbs > 1050) - featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > 2500); + if (simpleEvalAbs > Eval::SmallNetThreshold) + featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); else featureTransformerBig->hint_common_access(pos, false); } diff --git a/src/search.cpp b/src/search.cpp index 533cf61b613..75a747d999a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,7 +55,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 117 - 44 * noTtCutNode; + Value futilityMult = 121 - 43 * noTtCutNode; Value improvingDeduction = 3 * improving * futilityMult / 2; Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; @@ -69,15 +69,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 12475; + v += cv * std::abs(cv) / 10759; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(246 * d - 351, 1136); } +int stat_bonus(Depth d) { return std::min(249 * d - 327, 1192); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(519 * d - 306, 1258); } +int stat_malus(Depth d) { return std::min(516 * d - 299, 1432); } // 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); } @@ -300,12 +300,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12487; + delta = 9 + avg * avg / 12804; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 134 * avg / (std::abs(avg) + 97); + optimism[us] = 131 * avg / (std::abs(avg) + 90); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -500,7 +500,7 @@ void Search::Worker::clear() { h->fill(-71); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((18.79 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((19.02 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -731,7 +731,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1723, 1455); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1237); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -754,7 +754,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 438 - (332 - 154 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 462 - (296 - 145 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -763,24 +763,23 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 11 + if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 314 + - (ss - 1)->statScore / 287 >= beta - && eval >= beta && eval < 30016 // smaller than TB wins - && (!ttMove || ttCapture)) + && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16620 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 21 * depth + 330 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16211 + && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 154, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 151, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -828,7 +827,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 181 - 68 * improving; + probCutBeta = beta + 164 - 62 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -884,7 +883,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 452; + probCutBeta = beta + 410; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -967,7 +966,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 277 + 292 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 298 + 288 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -975,7 +974,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -197 * depth)) + if (!pos.see_ge(move, -202 * depth)) continue; } else @@ -987,17 +986,17 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4211 * depth) + if (lmrDepth < 6 && history < -4125 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 6437; + lmrDepth += history / 5686; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 57 ? 144 : 57) - + 121 * lmrDepth + && ss->staticEval + (bestValue < ss->staticEval - 55 ? 153 : 58) + + 118 * lmrDepth <= alpha) continue; @@ -1024,11 +1023,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (60 + 54 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (58 + 55 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1044,7 +1043,7 @@ Value Search::Worker::search( if (!PvNode && ss->multipleExtensions <= 16) { extension = 2 + (value < singularBeta - 78 && !ttCapture); - depth += depth < 16; + depth += depth < 14; } if (PvNode && !ttCapture && ss->multipleExtensions <= 5 && value < singularBeta - 50) @@ -1082,7 +1081,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4394) + > 4315) extension = 1; } @@ -1136,10 +1135,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4392; + + (*contHist[3])[movedPiece][move.to_sq()] - 4587; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 14189; + r -= ss->statScore / 12372; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1158,7 +1157,7 @@ Value Search::Worker::search( { // 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 + 49 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 48 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1277,7 +1276,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 13 && beta < 13652 && value > -12761) + if (depth > 2 && depth < 12 && beta < 13665 && value > -12276) depth -= 2; assert(depth > 0); @@ -1320,8 +1319,8 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -15736) - + ((ss - 1)->moveCount > 11); + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14446) + + ((ss - 1)->moveCount > 10); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1478,7 +1477,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 206; + futilityBase = ss->staticEval + 221; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1558,7 +1557,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -74)) + if (!pos.see_ge(move, -79)) continue; } @@ -1626,7 +1625,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1118 - delta * 793 / rootDelta) / 1024 + (!i && reductionScale > 863); + return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 952); } namespace { @@ -1715,7 +1714,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 166 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From f072634e245774f957b715118ecb586264cf04f1 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Mon, 11 Mar 2024 06:25:07 +0700 Subject: [PATCH 0421/1309] Simplify opponentWorsening condition Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65ea18650ec64f0526c4033a LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 226624 W: 58601 L: 58589 D: 109434 Ptnml(0-2): 1030, 27193, 56819, 27275, 995 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65eb7a220ec64f0526c4161a LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 243882 W: 61462 L: 61469 D: 120951 Ptnml(0-2): 197, 27559, 66419, 27586, 180 closes https://github.com/official-stockfish/Stockfish/pull/5102 Bench: 1601012 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 75a747d999a..16f45df16dd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -748,7 +748,7 @@ Value Search::Worker::search( ? ss->staticEval > (ss - 2)->staticEval : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; - opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2 && (depth != 2 || !improving); + opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, From 1a26d698de33ed50f182b325b359da61bab67abe Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 9 Mar 2024 14:42:37 +0100 Subject: [PATCH 0422/1309] Refactor Network Usage Continuing from PR #4968, this update improves how Stockfish handles network usage, making it easier to manage and modify networks in the future. With the introduction of a dedicated Network class, creating networks has become straightforward. See uci.cpp: ```cpp NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig) ``` The new `Network` encapsulates all network-related logic, significantly reducing the complexity previously required to support multiple network types, such as the distinction between small and big networks #4915. Non-Regression STC: https://tests.stockfishchess.org/tests/view/65edd26c0ec64f0526c43584 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 33760 W: 8887 L: 8661 D: 16212 Ptnml(0-2): 143, 3795, 8808, 3961, 173 Non-Regression SMP STC: https://tests.stockfishchess.org/tests/view/65ed71970ec64f0526c42fdd LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 59088 W: 15121 L: 14931 D: 29036 Ptnml(0-2): 110, 6640, 15829, 6880, 85 Compiled with `make -j profile-build` ``` bash ./bench_parallel.sh ./stockfish ./stockfish-nnue 13 50 sf_base = 1568540 +/- 7637 (95%) sf_test = 1573129 +/- 7301 (95%) diff = 4589 +/- 8720 (95%) speedup = 0.29260% +/- 0.556% (95%) ``` Compiled with `make -j build` ``` bash ./bench_parallel.sh ./stockfish ./stockfish-nnue 13 50 sf_base = 1472653 +/- 7293 (95%) sf_test = 1491928 +/- 7661 (95%) diff = 19275 +/- 7154 (95%) speedup = 1.30886% +/- 0.486% (95%) ``` closes https://github.com/official-stockfish/Stockfish/pull/5100 No functional change --- src/Makefile | 8 +- src/evaluate.cpp | 164 +----------- src/evaluate.h | 32 +-- src/main.cpp | 3 - src/misc.h | 25 ++ src/nnue/evaluate_nnue.cpp | 488 ----------------------------------- src/nnue/evaluate_nnue.h | 89 ------- src/nnue/network.cpp | 422 ++++++++++++++++++++++++++++++ src/nnue/network.h | 128 +++++++++ src/nnue/nnue_architecture.h | 7 +- src/nnue/nnue_misc.cpp | 202 +++++++++++++++ src/nnue/nnue_misc.h | 63 +++++ src/search.cpp | 31 ++- src/search.h | 32 ++- src/thread.cpp | 10 +- src/thread.h | 17 +- src/uci.cpp | 45 ++-- src/uci.h | 8 +- 18 files changed, 948 insertions(+), 826 deletions(-) delete mode 100644 src/nnue/evaluate_nnue.cpp delete mode 100644 src/nnue/evaluate_nnue.h create mode 100644 src/nnue/network.cpp create mode 100644 src/nnue/network.h create mode 100644 src/nnue/nnue_misc.cpp create mode 100644 src/nnue/nnue_misc.h diff --git a/src/Makefile b/src/Makefile index 907b6155028..bd04d2c410e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,15 +55,15 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ - nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ + nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.cpp OBJS = $(notdir $(SRCS:.cpp=.o)) @@ -502,7 +502,7 @@ endif # In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. # Currently we don't know how to make PGO builds with the NDK yet. ifeq ($(COMP),ndk) - CXXFLAGS += -stdlib=libc++ -fPIE + CXXFLAGS += -stdlib=libc++ -fPIE -mcmodel=large comp=clang ifeq ($(arch),armv7) CXX=armv7a-linux-androideabi16-clang++ diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1eb58fd24b3..56abe6cb9c8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -22,161 +22,18 @@ #include #include #include -#include #include #include -#include #include -#include -#include -#include "incbin/incbin.h" -#include "misc.h" -#include "nnue/evaluate_nnue.h" -#include "nnue/nnue_architecture.h" +#include "nnue/network.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "types.h" #include "uci.h" -#include "ucioption.h" - -// Macro to embed the default efficiently updatable neural network (NNUE) file -// data in the engine binary (using incbin.h, by Dale Weiler). -// This macro invocation will declare the following three variables -// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data -// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end -// const unsigned int gEmbeddedNNUESize; // the size of the embedded file -// Note that this does not work in Microsoft Visual Studio. -#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) -INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); -INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); -#else -const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; -const unsigned int gEmbeddedNNUEBigSize = 1; -const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; -const unsigned int gEmbeddedNNUESmallSize = 1; -#endif - namespace Stockfish { -namespace Eval { - - -// Tries to load a NNUE network at startup time, or when the engine -// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" -// The name of the NNUE network is always retrieved from the EvalFile option. -// We search the given network in three locations: internally (the default -// network may be embedded in the binary), in the active working directory and -// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY -// variable to have the engine search in a special directory in their distro. -NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, - const OptionsMap& options, - NNUE::EvalFiles evalFiles) { - - for (auto& [netSize, evalFile] : evalFiles) - { - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - -#if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = {"", "", rootDirectory, - stringify(DEFAULT_NNUE_DIRECTORY)}; -#else - std::vector dirs = {"", "", rootDirectory}; -#endif - - for (const std::string& directory : dirs) - { - if (evalFile.current != user_eval_file) - { - if (directory != "") - { - std::ifstream stream(directory + user_eval_file, std::ios::binary); - auto description = NNUE::load_eval(stream, netSize); - - if (description.has_value()) - { - evalFile.current = user_eval_file; - evalFile.netDescription = description.value(); - } - } - - if (directory == "" && user_eval_file == evalFile.defaultName) - { - // C++ way to prepare a buffer for a memory stream - class MemoryBuffer: public std::basic_streambuf { - public: - MemoryBuffer(char* p, size_t n) { - setg(p, p, p + n); - setp(p, p + n); - } - }; - - MemoryBuffer buffer( - const_cast(reinterpret_cast( - netSize == Small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)), - size_t(netSize == Small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize)); - (void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable - (void) gEmbeddedNNUESmallEnd; - - std::istream stream(&buffer); - auto description = NNUE::load_eval(stream, netSize); - - if (description.has_value()) - { - evalFile.current = user_eval_file; - evalFile.netDescription = description.value(); - } - } - } - } - } - - return evalFiles; -} - -// Verifies that the last net used was loaded successfully -void NNUE::verify(const OptionsMap& options, - const std::unordered_map& evalFiles) { - - for (const auto& [netSize, evalFile] : evalFiles) - { - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - - if (evalFile.current != user_eval_file) - { - std::string msg1 = - "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = - "The network file " + user_eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, " - "including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: " - "https://tests.stockfishchess.org/api/nn/" - + evalFile.defaultName; - std::string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - exit(EXIT_FAILURE); - } - - sync_cout << "info string NNUE evaluation using " << user_eval_file << sync_endl; - } -} -} - // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. @@ -188,7 +45,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, int optimism) { +Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int optimism) { assert(!pos.checkers()); @@ -198,8 +55,8 @@ Value Eval::evaluate(const Position& pos, int optimism) { int nnueComplexity; - Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity, psqtOnly) - : NNUE::evaluate(pos, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) + : networks.big.evaluate(pos, true, &nnueComplexity, false); // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; @@ -222,23 +79,22 @@ Value Eval::evaluate(const Position& pos, int optimism) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos) { +std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pos.checkers()) return "Final evaluation: none (in check)"; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + ss << '\n' << NNUE::trace(pos, networks) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; + Value v = networks.big.evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos, VALUE_ZERO); + v = evaluate(networks, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index a690a3bbaf8..754a92eb7eb 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -20,51 +20,33 @@ #define EVALUATE_H_INCLUDED #include -#include #include "types.h" namespace Stockfish { class Position; -class OptionsMap; namespace Eval { constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; -std::string trace(Position& pos); - -int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos, int optimism); - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the -// name of the macro, as it is used in the Makefile. +// name of the macro or the location where this macro is defined, as it is used +// in the Makefile/Fishtest. #define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" -struct EvalFile { - // UCI option name - std::string optionName; - // Default net name, will use one of the macros above - std::string defaultName; - // Selected net name, either via uci option or default - std::string current; - // Net description extracted from the net file - std::string netDescription; -}; - namespace NNUE { +struct Networks; +} -enum NetSize : int; - -using EvalFiles = std::unordered_map; +std::string trace(Position& pos, const Eval::NNUE::Networks& networks); -EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles); -void verify(const OptionsMap&, const EvalFiles&); +int simple_eval(const Position& pos, Color c); +Value evaluate(const NNUE::Networks& networks, const Position& pos, int optimism); -} // namespace NNUE } // namespace Eval diff --git a/src/main.cpp b/src/main.cpp index 6ce656d7fe8..33d5d375fca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,6 @@ #include #include "bitboard.h" -#include "evaluate.h" #include "misc.h" #include "position.h" #include "tune.h" @@ -39,8 +38,6 @@ int main(int argc, char* argv[]) { Tune::init(uci.options); - uci.evalFiles = Eval::NNUE::load_networks(uci.working_directory(), uci.options, uci.evalFiles); - uci.loop(); return 0; diff --git a/src/misc.h b/src/misc.h index f73e7889a8d..9ad5c3ca57e 100644 --- a/src/misc.h +++ b/src/misc.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,30 @@ void* aligned_large_pages_alloc(size_t size); // nop if mem == nullptr void aligned_large_pages_free(void* mem); +// Deleter for automating release of memory area +template +struct AlignedDeleter { + void operator()(T* ptr) const { + ptr->~T(); + std_aligned_free(ptr); + } +}; + +template +struct LargePageDeleter { + void operator()(T* ptr) const { + ptr->~T(); + aligned_large_pages_free(ptr); + } +}; + +template +using AlignedPtr = std::unique_ptr>; + +template +using LargePagePtr = std::unique_ptr>; + + void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); void dbg_stdev_of(int64_t value, int slot = 0); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp deleted file mode 100644 index 854fed06e82..00000000000 --- a/src/nnue/evaluate_nnue.cpp +++ /dev/null @@ -1,488 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// Code for calculating NNUE evaluation function - -#include "evaluate_nnue.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../evaluate.h" -#include "../misc.h" -#include "../position.h" -#include "../types.h" -#include "../uci.h" -#include "nnue_accumulator.h" -#include "nnue_common.h" - -namespace Stockfish::Eval::NNUE { - -// Input feature converter -LargePagePtr> - featureTransformerBig; -LargePagePtr> - featureTransformerSmall; - -// Evaluation function -AlignedPtr> networkBig[LayerStacks]; -AlignedPtr> networkSmall[LayerStacks]; - -// Evaluation function file names - -namespace Detail { - -// Initialize the evaluation function parameters -template -void initialize(AlignedPtr& pointer) { - - pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - -template -void initialize(LargePagePtr& pointer) { - - static_assert(alignof(T) <= 4096, - "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); - pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - -// Read evaluation function parameters -template -bool read_parameters(std::istream& stream, T& reference) { - - std::uint32_t header; - header = read_little_endian(stream); - if (!stream || header != T::get_hash_value()) - return false; - return reference.read_parameters(stream); -} - -// Write evaluation function parameters -template -bool write_parameters(std::ostream& stream, const T& reference) { - - write_little_endian(stream, T::get_hash_value()); - return reference.write_parameters(stream); -} - -} // namespace Detail - - -// Initialize the evaluation function parameters -static void initialize(NetSize netSize) { - - if (netSize == Small) - { - Detail::initialize(featureTransformerSmall); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(networkSmall[i]); - } - else - { - Detail::initialize(featureTransformerBig); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(networkBig[i]); - } -} - -// Read network header -static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { - std::uint32_t version, size; - - version = read_little_endian(stream); - *hashValue = read_little_endian(stream); - size = read_little_endian(stream); - if (!stream || version != Version) - return false; - desc->resize(size); - stream.read(&(*desc)[0], size); - return !stream.fail(); -} - -// Write network header -static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { - write_little_endian(stream, Version); - write_little_endian(stream, hashValue); - write_little_endian(stream, std::uint32_t(desc.size())); - stream.write(&desc[0], desc.size()); - return !stream.fail(); -} - -// Read network parameters -static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) { - - std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) - return false; - if (hashValue != HashValue[netSize]) - return false; - if (netSize == Big && !Detail::read_parameters(stream, *featureTransformerBig)) - return false; - if (netSize == Small && !Detail::read_parameters(stream, *featureTransformerSmall)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (netSize == Big && !Detail::read_parameters(stream, *(networkBig[i]))) - return false; - if (netSize == Small && !Detail::read_parameters(stream, *(networkSmall[i]))) - return false; - } - return stream && stream.peek() == std::ios::traits_type::eof(); -} - -// Write network parameters -static bool -write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) { - - if (!write_header(stream, HashValue[netSize], netDescription)) - return false; - if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) - return false; - if (netSize == Small && !Detail::write_parameters(stream, *featureTransformerSmall)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (netSize == Big && !Detail::write_parameters(stream, *(networkBig[i]))) - return false; - if (netSize == Small && !Detail::write_parameters(stream, *(networkSmall[i]))) - return false; - } - return bool(stream); -} - -void hint_common_parent_position(const Position& pos) { - - int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); - if (simpleEvalAbs > Eval::SmallNetThreshold) - featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); - else - featureTransformerBig->hint_common_access(pos, false); -} - -// Evaluation function. Perform differential calculation. -template -Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly) { - - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. - - constexpr uint64_t alignment = CacheLineSize; - constexpr int delta = 24; - -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; - - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else - - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr > ::BufferSize]; -#endif - - ASSERT_ALIGNED(transformedFeatures, alignment); - - const int bucket = (pos.count() - 1) / 4; - const auto psqt = - Net_Size == Small - ? featureTransformerSmall->transform(pos, transformedFeatures, bucket, psqtOnly) - : featureTransformerBig->transform(pos, transformedFeatures, bucket, psqtOnly); - - const auto positional = - !psqtOnly ? (Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) - : networkBig[bucket]->propagate(transformedFeatures)) - : 0; - - if (complexity) - *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; - - // Give more value to positional evaluation when adjusted flag is set - if (adjusted) - return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) - / (1024 * OutputScale)); - else - return static_cast((psqt + positional) / OutputScale); -} - -template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); -template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); - -struct NnueEvalTrace { - static_assert(LayerStacks == PSQTBuckets); - - Value psqt[LayerStacks]; - Value positional[LayerStacks]; - std::size_t correctBucket; -}; - -static NnueEvalTrace trace_evaluate(const Position& pos) { - - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. - constexpr uint64_t alignment = CacheLineSize; - -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; - - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer::BufferSize]; -#endif - - ASSERT_ALIGNED(transformedFeatures, alignment); - - NnueEvalTrace t{}; - t.correctBucket = (pos.count() - 1) / 4; - for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) - { - const auto materialist = - featureTransformerBig->transform(pos, transformedFeatures, bucket, false); - const auto positional = networkBig[bucket]->propagate(transformedFeatures); - - t.psqt[bucket] = static_cast(materialist / OutputScale); - t.positional[bucket] = static_cast(positional / OutputScale); - } - - return t; -} - -constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); - - -// Converts a Value into (centi)pawns and writes it in a buffer. -// The buffer must have capacity for at least 5 chars. -static void format_cp_compact(Value v, char* buffer) { - - buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - - int cp = std::abs(UCI::to_cp(v)); - if (cp >= 10000) - { - buffer[1] = '0' + cp / 10000; - cp %= 10000; - buffer[2] = '0' + cp / 1000; - cp %= 1000; - buffer[3] = '0' + cp / 100; - buffer[4] = ' '; - } - else if (cp >= 1000) - { - buffer[1] = '0' + cp / 1000; - cp %= 1000; - buffer[2] = '0' + cp / 100; - cp %= 100; - buffer[3] = '.'; - buffer[4] = '0' + cp / 10; - } - else - { - buffer[1] = '0' + cp / 100; - cp %= 100; - buffer[2] = '.'; - buffer[3] = '0' + cp / 10; - cp %= 10; - buffer[4] = '0' + cp / 1; - } -} - - -// Converts a Value into pawns, always keeping two decimals -static void format_cp_aligned_dot(Value v, std::stringstream& stream) { - - const double pawns = std::abs(0.01 * UCI::to_cp(v)); - - stream << (v < 0 ? '-' - : v > 0 ? '+' - : ' ') - << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; -} - - -// Returns a string with the value of each piece on a board, -// and a table for (PSQT, Layers) values bucket by bucket. -std::string trace(Position& pos) { - - std::stringstream ss; - - char board[3 * 8 + 1][8 * 8 + 2]; - std::memset(board, ' ', sizeof(board)); - for (int row = 0; row < 3 * 8 + 1; ++row) - board[row][8 * 8 + 1] = '\0'; - - // A lambda to output one box of the board - auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - const int x = int(file) * 8; - const int y = (7 - int(rank)) * 3; - for (int i = 1; i < 8; ++i) - board[y][x + i] = board[y + 3][x + i] = '-'; - for (int i = 1; i < 3; ++i) - board[y + i][x] = board[y + i][x + 8] = '|'; - board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; - if (pc != NO_PIECE) - board[y + 1][x + 4] = PieceToChar[pc]; - if (value != VALUE_NONE) - format_cp_compact(value, &board[y + 2][x + 2]); - }; - - // We estimate the value of each piece by doing a differential evaluation from - // the current base eval, simulating the removal of the piece from its square. - Value base = evaluate(pos); - base = pos.side_to_move() == WHITE ? base : -base; - - for (File f = FILE_A; f <= FILE_H; ++f) - for (Rank r = RANK_1; r <= RANK_8; ++r) - { - Square sq = make_square(f, r); - Piece pc = pos.piece_on(sq); - Value v = VALUE_NONE; - - if (pc != NO_PIECE && type_of(pc) != KING) - { - auto st = pos.state(); - - pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; - - Value eval = evaluate(pos); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; - - pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; - } - - writeSquare(f, r, pc, v); - } - - ss << " NNUE derived piece values:\n"; - for (int row = 0; row < 3 * 8 + 1; ++row) - ss << board[row] << '\n'; - ss << '\n'; - - auto t = trace_evaluate(pos); - - ss << " NNUE network contributions " - << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl - << "+------------+------------+------------+------------+\n" - << "| Bucket | Material | Positional | Total |\n" - << "| | (PSQT) | (Layers) | |\n" - << "+------------+------------+------------+------------+\n"; - - for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) - { - ss << "| " << bucket << " "; - ss << " | "; - format_cp_aligned_dot(t.psqt[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.positional[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); - ss << " " - << " |"; - if (bucket == t.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; - } - - ss << "+------------+------------+------------+------------+\n"; - - return ss.str(); -} - - -// Load eval, from a file stream or a memory stream -std::optional load_eval(std::istream& stream, NetSize netSize) { - - initialize(netSize); - std::string netDescription; - return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription) - : std::nullopt; -} - -// Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream, - NetSize netSize, - const std::string& name, - const std::string& netDescription) { - - if (name.empty() || name == "None") - return false; - - return write_parameters(stream, netSize, netDescription); -} - -// Save eval, to a file given by its name -bool save_eval(const std::optional& filename, - NetSize netSize, - const EvalFiles& evalFiles) { - - std::string actualFilename; - std::string msg; - - if (filename.has_value()) - actualFilename = filename.value(); - else - { - if (evalFiles.at(netSize).current - != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) - { - msg = "Failed to export a net. " - "A non-embedded net can only be saved if the filename is specified"; - - sync_cout << msg << sync_endl; - return false; - } - actualFilename = (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig); - } - - std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current, - evalFiles.at(netSize).netDescription); - - msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; - - sync_cout << msg << sync_endl; - return saved; -} - - -} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h deleted file mode 100644 index febe8f9d9b9..00000000000 --- a/src/nnue/evaluate_nnue.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// header used in NNUE evaluation function - -#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED -#define NNUE_EVALUATE_NNUE_H_INCLUDED - -#include -#include -#include -#include -#include - -#include "../evaluate.h" -#include "../misc.h" -#include "../types.h" -#include "nnue_architecture.h" -#include "nnue_feature_transformer.h" - -namespace Stockfish { -class Position; -} - -namespace Stockfish::Eval::NNUE { - -// Hash value of evaluation function structure -constexpr std::uint32_t HashValue[2] = { - FeatureTransformer::get_hash_value() - ^ Network::get_hash_value(), - FeatureTransformer::get_hash_value() - ^ Network::get_hash_value()}; - -// Deleter for automating release of memory area -template -struct AlignedDeleter { - void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); - } -}; - -template -struct LargePageDeleter { - void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); - } -}; - -template -using AlignedPtr = std::unique_ptr>; - -template -using LargePagePtr = std::unique_ptr>; - -std::string trace(Position& pos); -template -Value evaluate(const Position& pos, - bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false); -void hint_common_parent_position(const Position& pos); - -std::optional load_eval(std::istream& stream, NetSize netSize); -bool save_eval(std::ostream& stream, - NetSize netSize, - const std::string& name, - const std::string& netDescription); -bool save_eval(const std::optional& filename, NetSize netSize, const EvalFiles&); - -} // namespace Stockfish::Eval::NNUE - -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp new file mode 100644 index 00000000000..5d4e0954d78 --- /dev/null +++ b/src/nnue/network.cpp @@ -0,0 +1,422 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "network.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../incbin/incbin.h" +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "nnue_misc.h" + +namespace { +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). +// This macro invocation will declare the following three variables +// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data +// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end +// const unsigned int gEmbeddedNNUESize; // the size of the embedded file +// Note that this does not work in Microsoft Visual Studio. +#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); +#else +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; +#endif +} + + +namespace Stockfish::Eval::NNUE { + +const EmbeddedNNUE embeddedNNUEBig(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); +const EmbeddedNNUE + embeddedNNUESmall(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); + + +namespace Detail { + +// Initialize the evaluation function parameters +template +void initialize(AlignedPtr& pointer) { + + pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); +} + +template +void initialize(LargePagePtr& pointer) { + + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); +} + +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { + + std::uint32_t header; + header = read_little_endian(stream); + if (!stream || header != T::get_hash_value()) + return false; + return reference.read_parameters(stream); +} + +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, const T& reference) { + + write_little_endian(stream, T::get_hash_value()); + return reference.write_parameters(stream); +} + +} // namespace Detail + + +template +void Network::load(const std::string& rootDirectory, std::string evalfilePath) { +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", rootDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", rootDirectory}; +#endif + + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + for (const auto& directory : dirs) + { + if (evalFile.current != evalfilePath) + { + if (directory != "") + { + load_user_net(directory, evalfilePath); + } + + if (directory == "" && evalfilePath == evalFile.defaultName) + { + load_internal(); + } + } + } +} + + +template +bool Network::save(const std::optional& filename) const { + std::string actualFilename; + std::string msg; + + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (evalFile.current != evalFile.defaultName) + { + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; + return false; + } + + actualFilename = evalFile.defaultName; + } + + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save(stream, evalFile.current, evalFile.netDescription); + + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; +} + + +template +Value Network::evaluate(const Position& pos, + bool adjusted, + int* complexity, + bool psqtOnly) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + constexpr int delta = 24; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType transformedFeatures + [FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket, psqtOnly); + const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; + + if (complexity) + *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; + + // Give more value to positional evaluation when adjusted flag is set + if (adjusted) + return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) + / (1024 * OutputScale)); + else + return static_cast((psqt + positional) / OutputScale); +} + + +template +void Network::verify(std::string evalfilePath) const { + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + if (evalFile.current != evalfilePath) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + exit(EXIT_FAILURE); + } + + sync_cout << "info string NNUE evaluation using " << evalfilePath << sync_endl; +} + + +template +void Network::hint_common_access(const Position& pos, bool psqtOnl) const { + featureTransformer->hint_common_access(pos, psqtOnl); +} + + +template +NnueEvalTrace Network::trace_evaluate(const Position& pos) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType transformedFeatures + [FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + NnueEvalTrace t{}; + t.correctBucket = (pos.count() - 1) / 4; + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = + featureTransformer->transform(pos, transformedFeatures, bucket, false); + const auto positional = network[bucket]->propagate(transformedFeatures); + + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); + } + + return t; +} + + +template +void Network::load_user_net(const std::string& dir, + const std::string& evalfilePath) { + std::ifstream stream(dir + evalfilePath, std::ios::binary); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalfilePath; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::load_internal() { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; + + MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), + size_t(embedded.size)); + + std::istream stream(&buffer); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalFile.defaultName; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::initialize() { + Detail::initialize(featureTransformer); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(network[i]); +} + + +template +bool Network::save(std::ostream& stream, + const std::string& name, + const std::string& netDescription) const { + if (name.empty() || name == "None") + return false; + + return write_parameters(stream, netDescription); +} + + +template +std::optional Network::load(std::istream& stream) { + initialize(); + std::string description; + + return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt; +} + + +// Read network header +template +bool Network::read_header(std::istream& stream, + std::uint32_t* hashValue, + std::string* desc) const { + std::uint32_t version, size; + + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); +} + + +// Write network header +template +bool Network::write_header(std::ostream& stream, + std::uint32_t hashValue, + const std::string& desc) const { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, std::uint32_t(desc.size())); + stream.write(&desc[0], desc.size()); + return !stream.fail(); +} + + +template +bool Network::read_parameters(std::istream& stream, + std::string& netDescription) const { + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != Network::hash) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::read_parameters(stream, *(network[i]))) + return false; + } + return stream && stream.peek() == std::ios::traits_type::eof(); +} + + +template +bool Network::write_parameters(std::ostream& stream, + const std::string& netDescription) const { + if (!write_header(stream, Network::hash, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::write_parameters(stream, *(network[i]))) + return false; + } + return bool(stream); +} + +// Explicit template instantiation + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h new file mode 100644 index 00000000000..c1ed7717914 --- /dev/null +++ b/src/nnue/network.h @@ -0,0 +1,128 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NETWORK_H_INCLUDED +#define NETWORK_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_feature_transformer.h" +#include "nnue_misc.h" + +namespace Stockfish::Eval::NNUE { + +struct EmbeddedNNUE { + EmbeddedNNUE(const unsigned char* embeddedData, + const unsigned char* embeddedEnd, + const unsigned int embeddedSize) : + data(embeddedData), + end(embeddedEnd), + size(embeddedSize) {} + const unsigned char* data; + const unsigned char* end; + const unsigned int size; +}; + +extern const EmbeddedNNUE embeddedNNUEBig; +extern const EmbeddedNNUE embeddedNNUESmall; + +template +class Network { + public: + Network(EvalFile file, EmbeddedNNUE embeddedEval) : + evalFile(file), + embedded(embeddedEval) {} + + void load(const std::string& rootDirectory, std::string evalfilePath); + bool save(const std::optional& filename) const; + + + Value evaluate(const Position& pos, + bool adjusted = false, + int* complexity = nullptr, + bool psqtOnly = false) const; + + + void hint_common_access(const Position& pos, bool psqtOnl) const; + + void verify(std::string evalfilePath) const; + NnueEvalTrace trace_evaluate(const Position& pos) const; + + private: + void load_user_net(const std::string&, const std::string&); + void load_internal(); + + void initialize(); + + bool save(std::ostream&, const std::string&, const std::string&) const; + std::optional load(std::istream&); + + bool read_header(std::istream&, std::uint32_t*, std::string*) const; + bool write_header(std::ostream&, std::uint32_t, const std::string&) const; + + bool read_parameters(std::istream&, std::string&) const; + bool write_parameters(std::ostream&, const std::string&) const; + + // Input feature converter + LargePagePtr featureTransformer; + + // Evaluation function + AlignedPtr network[LayerStacks]; + + EvalFile evalFile; + EmbeddedNNUE embedded; + + // Hash value of evaluation function structure + static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); +}; + +// Definitions of the network types +using SmallFeatureTransformer = + FeatureTransformer; +using SmallNetworkArchitecture = + NetworkArchitecture; + +using BigFeatureTransformer = + FeatureTransformer; +using BigNetworkArchitecture = NetworkArchitecture; + +using NetworkBig = Network; +using NetworkSmall = Network; + + +struct Networks { + Networks(NetworkBig&& nB, NetworkSmall&& nS) : + big(std::move(nB)), + small(std::move(nS)) {} + + NetworkBig big; + NetworkSmall small; +}; + + +} // namespace Stockfish + +#endif diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index b222ab997db..05efb813754 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -37,11 +37,6 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; -enum NetSize : int { - Big, - Small -}; - // Number of input feature dimensions after conversion constexpr IndexType TransformedFeatureDimensionsBig = 2560; constexpr int L2Big = 15; @@ -55,7 +50,7 @@ constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; template -struct Network { +struct NetworkArchitecture { static constexpr IndexType TransformedFeatureDimensions = L1; static constexpr int FC_0_OUTPUTS = L2; static constexpr int FC_1_OUTPUTS = L3; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp new file mode 100644 index 00000000000..c443aaf19b2 --- /dev/null +++ b/src/nnue/nnue_misc.cpp @@ -0,0 +1,202 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include "nnue_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../position.h" +#include "../types.h" +#include "../uci.h" +#include "network.h" +#include "nnue_accumulator.h" + +namespace Stockfish::Eval::NNUE { + + +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); + + +void hint_common_parent_position(const Position& pos, const Networks& networks) { + + int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); + if (simpleEvalAbs > Eval::SmallNetThreshold) + networks.small.hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); + else + networks.big.hint_common_access(pos, false); +} + + +// Converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +static void format_cp_compact(Value v, char* buffer) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(UCI::to_cp(v)); + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; + buffer[3] = '0' + cp / 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; + cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; + cp %= 10; + buffer[4] = '0' + cp / 1; + } +} + + +// Converts a Value into pawns, always keeping two decimals +static void format_cp_aligned_dot(Value v, std::stringstream& stream) { + + const double pawns = std::abs(0.01 * UCI::to_cp(v)); + + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} + + +// Returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { + + std::stringstream ss; + + char board[3 * 8 + 1][8 * 8 + 2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y + 2][x + 2]); + }; + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + Value base = networks.big.evaluate(pos); + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= FILE_H; ++f) + for (Rank r = RANK_1; r <= RANK_8; ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); + + pos.remove_piece(sq); + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; + + Value eval = networks.big.evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3 * 8 + 1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + auto t = networks.big.trace_evaluate(pos); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + ss << "| " << bucket << " "; + ss << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + ss << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); +} + + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h new file mode 100644 index 00000000000..5eab02184c6 --- /dev/null +++ b/src/nnue/nnue_misc.h @@ -0,0 +1,63 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_MISC_H_INCLUDED +#define NNUE_MISC_H_INCLUDED + +#include +#include + +#include "../types.h" +#include "nnue_architecture.h" + +namespace Stockfish { + +class Position; + +namespace Eval::NNUE { + +struct EvalFile { + // Default net name, will use one of the EvalFileDefaultName* macros defined + // in evaluate.h + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; + + +struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; +}; + + +struct Networks; + + +std::string trace(Position& pos, const Networks& networks); +void hint_common_parent_position(const Position& pos, const Networks& networks); + +} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish + +#endif // #ifndef NNUE_MISC_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 16f45df16dd..f4ec8fb16dc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -27,15 +27,15 @@ #include #include #include -#include #include +#include #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" -#include "nnue/evaluate_nnue.h" #include "nnue/nnue_common.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -135,7 +135,8 @@ Search::Worker::Worker(SharedState& sharedState, manager(std::move(sm)), options(sharedState.options), threads(sharedState.threads), - tt(sharedState.tt) { + tt(sharedState.tt), + networks(sharedState.networks) { clear(); } @@ -566,8 +567,9 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(networks, pos, thisThread->optimism[us]) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -700,7 +702,7 @@ Value Search::Worker::search( { // 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); + Eval::NNUE::hint_common_parent_position(pos, networks); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -708,9 +710,9 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, networks); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -720,7 +722,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -877,7 +879,7 @@ Value Search::Worker::search( } } - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, networks); } moves_loop: // When in check, search starts here @@ -1413,8 +1415,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) - : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(networks, pos, thisThread->optimism[us]) + : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1445,7 +1448,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1458,7 +1461,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = (ss - 1)->currentMove != Move::null() - ? evaluate(pos, thisThread->optimism[us]) + ? evaluate(networks, pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); diff --git a/src/search.h b/src/search.h index bb9f63fff82..4908e535c6a 100644 --- a/src/search.h +++ b/src/search.h @@ -25,8 +25,8 @@ #include #include #include -#include #include +#include #include "misc.h" #include "movepick.h" @@ -37,6 +37,10 @@ namespace Stockfish { +namespace Eval::NNUE { +struct Networks; +} + // Different node types, used as a template parameter enum NodeType { NonPV, @@ -125,16 +129,20 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& optionsMap, - ThreadPool& threadPool, - TranspositionTable& transpositionTable) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const Eval::NNUE::Networks& nets) : options(optionsMap), threads(threadPool), - tt(transpositionTable) {} + tt(transpositionTable), + networks(nets) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Networks& networks; }; class Worker; @@ -176,6 +184,7 @@ class NullSearchManager: public ISearchManager { void check_time(Search::Worker&) override {} }; + // Search::Worker is the class that does the actual search. // It is instantiated once per thread, and it is responsible for keeping track // of the search history, and storing data required for the search. @@ -247,9 +256,10 @@ class Worker { Tablebases::Config tbConfig; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Networks& networks; friend class Stockfish::ThreadPool; friend class SearchManager; diff --git a/src/thread.cpp b/src/thread.cpp index b62f5d8a39d..a3823d0cfd0 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -19,12 +19,12 @@ #include "thread.h" #include +#include #include #include #include #include #include -#include #include "misc.h" #include "movegen.h" @@ -62,6 +62,7 @@ Thread::~Thread() { stdThread.join(); } + // Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -109,6 +110,13 @@ void Thread::idle_loop() { } } +Search::SearchManager* ThreadPool::main_manager() { + return static_cast(main_thread()->worker.get()->manager.get()); +} + +uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); } +uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); } + // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. diff --git a/src/thread.h b/src/thread.h index 0d4c252ccc3..81fcc72a7ee 100644 --- a/src/thread.h +++ b/src/thread.h @@ -33,6 +33,7 @@ namespace Stockfish { + class OptionsMap; using Value = int; @@ -83,15 +84,13 @@ class ThreadPool { void clear(); void set(Search::SharedState); - Search::SearchManager* main_manager() const { - return static_cast(main_thread()->worker.get()->manager.get()); - }; - Thread* main_thread() const { return threads.front(); } - uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); } - uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; + Search::SearchManager* main_manager(); + Thread* main_thread() const { return threads.front(); } + uint64_t nodes_searched() const; + uint64_t tb_hits() const; + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; std::atomic_bool stop, abortedSearch, increaseDepth; diff --git a/src/uci.cpp b/src/uci.cpp index 357369bf562..fda336b0d8e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,25 +22,25 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include "benchmark.h" #include "evaluate.h" #include "movegen.h" -#include "nnue/evaluate_nnue.h" -#include "nnue/nnue_architecture.h" +#include "nnue/network.h" +#include "nnue/nnue_common.h" +#include "perft.h" #include "position.h" #include "search.h" #include "syzygy/tbprobe.h" #include "types.h" #include "ucioption.h" -#include "perft.h" namespace Stockfish { @@ -48,17 +48,20 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKB constexpr int NormalizeToPawnValue = 356; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; -UCI::UCI(int argc, char** argv) : - cli(argc, argv) { - evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}}, - {Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}}; +namespace NN = Eval::NNUE; +UCI::UCI(int argc, char** argv) : + networks(NN::Networks( + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::embeddedNNUESmall))), + cli(argc, argv) { + options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - threads.set({options, threads, tt}); + threads.set({options, threads, tt, networks}); }); options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { @@ -80,14 +83,17 @@ UCI::UCI(int argc, char** argv) : options["SyzygyProbeDepth"] << Option(1, 1, 100); options["Syzygy50MoveRule"] << Option(true); options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { - evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) { + networks.big.load(cli.binaryDirectory, o); }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option&) { - evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { + networks.small.load(cli.binaryDirectory, o); }); - threads.set({options, threads, tt}); + networks.big.load(cli.binaryDirectory, options["EvalFile"]); + networks.small.load(cli.binaryDirectory, options["EvalFileSmall"]); + + threads.set({options, threads, tt, networks}); search_clear(); // After threads are up } @@ -157,7 +163,7 @@ void UCI::loop() { std::string f; if (is >> std::skipws >> f) filename = f; - Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles); + networks.big.save(filename); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout @@ -218,7 +224,8 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits = parse_limits(pos, is); - Eval::NNUE::verify(options, evalFiles); + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); if (limits.perft) { @@ -283,9 +290,11 @@ void UCI::trace_eval(Position& pos) { Position p; p.set(pos.fen(), options["UCI_Chess960"], &states->back()); - Eval::NNUE::verify(options, evalFiles); + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); + - sync_cout << "\n" << Eval::trace(p) << sync_endl; + sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; } void UCI::search_clear() { diff --git a/src/uci.h b/src/uci.h index f25bb8d517f..dd55862ad17 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,13 +22,13 @@ #include #include -#include "evaluate.h" #include "misc.h" +#include "nnue/network.h" #include "position.h" +#include "search.h" #include "thread.h" #include "tt.h" #include "ucioption.h" -#include "search.h" namespace Stockfish { @@ -53,8 +53,8 @@ class UCI { const std::string& working_directory() const { return cli.workingDirectory; } - OptionsMap options; - Eval::NNUE::EvalFiles evalFiles; + OptionsMap options; + Eval::NNUE::Networks networks; private: TranspositionTable tt; From daa3ef9148e8a141d2562900924d578d6462ba72 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 9 Mar 2024 19:49:59 +0300 Subject: [PATCH 0423/1309] Simplify increaseDepth to boolean expression Non-functional Simplification, maintaining the same logic as before. Big thanks to @peregrineshahin for helping with the code. Passed non-regression bounds: https://tests.stockfishchess.org/tests/view/65ec93860ec64f0526c42375 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 101088 W: 26196 L: 26047 D: 48845 Ptnml(0-2): 405, 11580, 26473, 11633, 453 closes https://github.com/official-stockfish/Stockfish/pull/5103 No functional change --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f4ec8fb16dc..a3e53e025fa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -464,11 +464,10 @@ void Search::Worker::iterative_deepening() { else threads.stop = true; } - else if (!mainThread->ponder - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.506) - threads.increaseDepth = false; else - threads.increaseDepth = true; + threads.increaseDepth = + mainThread->ponder + || mainThread->tm.elapsed(threads.nodes_searched()) <= totalTime * 0.506; } mainThread->iterValue[iterIdx] = bestValue; From 627974c99fcd5a3dcbd5a8e0eb12f2afeb2d0a9a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 12 Mar 2024 17:10:28 +0300 Subject: [PATCH 0424/1309] Search + Eval + Movepick Tune Passed STC: https://tests.stockfishchess.org/tests/view/65ef15220ec64f0526c44b04 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 24480 W: 6459 L: 6153 D: 11868 Ptnml(0-2): 101, 2798, 6184, 3008, 149 Passed LTC: https://tests.stockfishchess.org/tests/view/65ef4bac0ec64f0526c44f50 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 53316 W: 13561 L: 13203 D: 26552 Ptnml(0-2): 27, 5925, 14408, 6259, 39 closes https://github.com/official-stockfish/Stockfish/pull/5104 Bench: 1715522 --- src/evaluate.cpp | 8 ++++---- src/evaluate.h | 2 +- src/movepick.cpp | 16 ++++++++-------- src/search.cpp | 24 ++++++++++++------------ src/tt.cpp | 4 ++-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 56abe6cb9c8..f4d18d8e4e0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,15 +59,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, : networks.big.evaluate(pos, true, &nnueComplexity, false); // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 524; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 31950; int npm = pos.non_pawn_material() / 64; - int v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; + int v = (nnue * (927 + npm + 9 * pos.count()) + optimism * (159 + npm)) / 1000; // Damp down the evaluation linearly when shuffling int shuffling = pos.rule50_count(); - v = v * (200 - shuffling) / 214; + v = v * (195 - shuffling) / 228; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/evaluate.h b/src/evaluate.h index 754a92eb7eb..bd11e0c1675 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; +constexpr inline int SmallNetThreshold = 1136, PsqtOnlyThreshold = 2656; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/movepick.cpp b/src/movepick.cpp index 33791922e4b..c1119cf11eb 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -190,18 +190,18 @@ void MovePicker::score() { m.value += bool(pos.check_squares(pt) & to) * 16384; // bonus for escaping from capture - m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000 - : pt == ROOK && !(to & threatenedByMinor) ? 25000 - : !(to & threatenedByPawn) ? 15000 + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51000 + : pt == ROOK && !(to & threatenedByMinor) ? 24950 + : !(to & threatenedByPawn) ? 14450 : 0) : 0; // malus for putting piece en prise m.value -= !(threatenedPieces & from) - ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000 - + bool(to & threatenedByMinor) * 10000 - : pt == ROOK ? bool(to & threatenedByMinor) * 25000 - : pt != PAWN ? bool(to & threatenedByPawn) * 15000 + ? (pt == QUEEN ? bool(to & threatenedByRook) * 48150 + + bool(to & threatenedByMinor) * 10650 + : pt == ROOK ? bool(to & threatenedByMinor) * 24500 + : pt != PAWN ? bool(to & threatenedByPawn) * 14950 : 0) : 0; } @@ -241,7 +241,7 @@ Move MovePicker::select(Pred filter) { // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { - auto quiet_threshold = [](Depth d) { return -3330 * d; }; + auto quiet_threshold = [](Depth d) { return -3550 * d; }; top: switch (stage) diff --git a/src/search.cpp b/src/search.cpp index a3e53e025fa..5cc6796888d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,8 +55,8 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 121 - 43 * noTtCutNode; - Value improvingDeduction = 3 * improving * futilityMult / 2; + Value futilityMult = 122 - 46 * noTtCutNode; + Value improvingDeduction = 57 * improving * futilityMult / 32; Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; @@ -69,7 +69,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 10759; + v += cv * std::abs(cv) / 11450; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -77,7 +77,7 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { int stat_bonus(Depth d) { return std::min(249 * d - 327, 1192); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(516 * d - 299, 1432); } +int stat_malus(Depth d) { return std::min(516 * d - 299, 1254); } // 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); } @@ -301,12 +301,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12804; + delta = 9 + avg * avg / 12800; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 131 * avg / (std::abs(avg) + 90); + optimism[us] = 130 * avg / (std::abs(avg) + 90); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -500,7 +500,7 @@ void Search::Worker::clear() { h->fill(-71); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((19.02 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((19.80 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -732,12 +732,12 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1237); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1238); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; 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 / 4; + << bonus / 2; } // Set up the improving flag, which is true if current static evaluation is @@ -828,7 +828,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 164 - 62 * improving; + probCutBeta = beta + 168 - 64 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -1139,7 +1139,7 @@ Value Search::Worker::search( + (*contHist[3])[movedPiece][move.to_sq()] - 4587; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 12372; + r -= ss->statScore / 14956; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1627,7 +1627,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 952); + return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 950); } namespace { diff --git a/src/tt.cpp b/src/tt.cpp index 8ef06e6355c..e62e0c17053 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -134,8 +134,8 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) - if (replace->depth8 - replace->relative_age(generation8) - > tte[i].depth8 - tte[i].relative_age(generation8)) + if (replace->depth8 - replace->relative_age(generation8) * 2 + > tte[i].depth8 - tte[i].relative_age(generation8) * 2) replace = &tte[i]; return found = false, replace; From 55df0ee00914f6bb5e9ecce4ace47f00b758098c Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 12 Mar 2024 18:20:19 +0100 Subject: [PATCH 0425/1309] Fix Raspberry Pi Compilation Reported by @Torom over discord. > dev build fails on Raspberry Pi 5 with clang ``` clang++ -o stockfish benchmark.o bitboard.o evaluate.o main.o misc.o movegen.o movepick.o position.o search.o thread.o timeman.o tt.o uci.o ucioption.o tune.o tbprobe.o nnue_misc.o half_ka_v2_hm.o network.o -fprofile-instr-generate -latomic -lpthread -Wall -Wcast-qual -fno-exceptions -std=c++17 -fprofile-instr-generate -pedantic -Wextra -Wshadow -Wmissing-prototypes -Wconditional-uninitialized -DUSE_PTHREADS -DNDEBUG -O3 -funroll-loops -DIS_64BIT -DUSE_POPCNT -DUSE_NEON=8 -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD -DGIT_SHA=627974c9 -DGIT_DATE=20240312 -DARCH=armv8-dotprod -flto=full /tmp/lto-llvm-e9300e.o: in function `_GLOBAL__sub_I_network.cpp': ld-temp.o:(.text.startup+0x704c): relocation truncated to fit: R_AARCH64_LDST64_ABS_LO12_NC against symbol `gEmbeddedNNUEBigEnd' defined in .rodata section in /tmp/lto-llvm-e9300e.o /usr/bin/ld: ld-temp.o:(.text.startup+0x704c): warning: one possible cause of this error is that the symbol is being referenced in the indicated code as if it had a larger alignment than was declared where it was defined ld-temp.o:(.text.startup+0x7068): relocation truncated to fit: R_AARCH64_LDST64_ABS_LO12_NC against symbol `gEmbeddedNNUESmallEnd' defined in .rodata section in /tmp/lto-llvm-e9300e.o /usr/bin/ld: ld-temp.o:(.text.startup+0x7068): warning: one possible cause of this error is that the symbol is being referenced in the indicated code as if it had a larger alignment than was declared where it was defined clang: error: linker command failed with exit code 1 (use -v to see invocation) make[2]: *** [Makefile:1051: stockfish] Error 1 make[2]: Leaving directory '/home/torsten/chess/Stockfish_master/src' make[1]: *** [Makefile:1058: clang-profile-make] Error 2 make[1]: Leaving directory '/home/torsten/chess/Stockfish_master/src' make: *** [Makefile:886: profile-build] Error 2 ``` closes https://github.com/official-stockfish/Stockfish/pull/5106 No functional change --- src/Makefile | 2 +- src/nnue/network.cpp | 28 ++++++++++++++++++++++++---- src/nnue/network.h | 24 ++++++++---------------- src/uci.cpp | 4 ++-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/Makefile b/src/Makefile index bd04d2c410e..75f31108159 100644 --- a/src/Makefile +++ b/src/Makefile @@ -502,7 +502,7 @@ endif # In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. # Currently we don't know how to make PGO builds with the NDK yet. ifeq ($(COMP),ndk) - CXXFLAGS += -stdlib=libc++ -fPIE -mcmodel=large + CXXFLAGS += -stdlib=libc++ -fPIE comp=clang ifeq ($(arch),armv7) CXX=armv7a-linux-androideabi16-clang++ diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 5d4e0954d78..bea3e7cb398 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -55,15 +55,33 @@ const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; const unsigned int gEmbeddedNNUESmallSize = 1; #endif + +struct EmbeddedNNUE { + EmbeddedNNUE(const unsigned char* embeddedData, + const unsigned char* embeddedEnd, + const unsigned int embeddedSize) : + data(embeddedData), + end(embeddedEnd), + size(embeddedSize) {} + const unsigned char* data; + const unsigned char* end; + const unsigned int size; +}; + +using namespace Stockfish::Eval::NNUE; + +EmbeddedNNUE get_embedded(EmbeddedNNUEType type) { + if (type == EmbeddedNNUEType::BIG) + return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); + else + return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); +} + } namespace Stockfish::Eval::NNUE { -const EmbeddedNNUE embeddedNNUEBig(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); -const EmbeddedNNUE - embeddedNNUESmall(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); - namespace Detail { @@ -302,6 +320,8 @@ void Network::load_internal() { } }; + const auto embedded = get_embedded(embeddedType); + MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), size_t(embedded.size)); diff --git a/src/nnue/network.h b/src/nnue/network.h index c1ed7717914..21e1c622205 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -34,27 +34,19 @@ namespace Stockfish::Eval::NNUE { -struct EmbeddedNNUE { - EmbeddedNNUE(const unsigned char* embeddedData, - const unsigned char* embeddedEnd, - const unsigned int embeddedSize) : - data(embeddedData), - end(embeddedEnd), - size(embeddedSize) {} - const unsigned char* data; - const unsigned char* end; - const unsigned int size; + +enum class EmbeddedNNUEType { + BIG, + SMALL, }; -extern const EmbeddedNNUE embeddedNNUEBig; -extern const EmbeddedNNUE embeddedNNUESmall; template class Network { public: - Network(EvalFile file, EmbeddedNNUE embeddedEval) : + Network(EvalFile file, EmbeddedNNUEType type) : evalFile(file), - embedded(embeddedEval) {} + embeddedType(type) {} void load(const std::string& rootDirectory, std::string evalfilePath); bool save(const std::optional& filename) const; @@ -92,8 +84,8 @@ class Network { // Evaluation function AlignedPtr network[LayerStacks]; - EvalFile evalFile; - EmbeddedNNUE embedded; + EvalFile evalFile; + EmbeddedNNUEType embeddedType; // Hash value of evaluation function structure static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); diff --git a/src/uci.cpp b/src/uci.cpp index fda336b0d8e..cf0e3f09cc7 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -54,8 +54,8 @@ namespace NN = Eval::NNUE; UCI::UCI(int argc, char** argv) : networks(NN::Networks( - NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig), - NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::embeddedNNUESmall))), + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))), cli(argc, argv) { options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); From ee2ee6bdc42af80430e7468da4208e379b40c393 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 13 Mar 2024 19:21:13 +0300 Subject: [PATCH 0426/1309] Give more bonuses to quiet move that caused a fail low Give extra bonus if search result is far below static evaluation of position. Passed STC: https://tests.stockfishchess.org/tests/view/65edf1250ec64f0526c43787 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 90816 W: 23713 L: 23307 D: 43796 Ptnml(0-2): 401, 10725, 22742, 11147, 393 Passed LTC: https://tests.stockfishchess.org/tests/view/65ef5ed70ec64f0526c450af LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66618 W: 16950 L: 16565 D: 33103 Ptnml(0-2): 35, 7372, 18139, 7699, 64 closes https://github.com/official-stockfish/Stockfish/pull/5111 Bench: 2002517 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5cc6796888d..5bd59712a5b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1321,7 +1321,8 @@ Value Search::Worker::search( else if (!priorCapture && prevSq != SQ_NONE) { int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14446) - + ((ss - 1)->moveCount > 10); + + ((ss - 1)->moveCount > 11) + + (!ss->inCheck && bestValue <= ss->staticEval - 150); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] From 23493de08272226394fb69c4f31182b48b0e739e Mon Sep 17 00:00:00 2001 From: Lemmy <10430540+TierynnB@users.noreply.github.com> Date: Mon, 11 Mar 2024 07:57:22 +1000 Subject: [PATCH 0427/1309] Sudden Death - Improve TM Due to the 50 estimated move horizon, once a sudden death game got below 1 second, the time allocation for optimumTime would go into the negative and SF would start instamoving. To counter this, once limits.time is below 1 second, the move horizon will start decreasing, at a rate of 1 move per 20ms. This was just what seemed a reasonable rate of decay. Fishtest sudden death TC 5+0 https://tests.stockfishchess.org/tests/live_elo/65ee2cdf0ec64f0526c43bbb LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 3348 W: 1070 L: 727 D:1551 Ptnml(0-2): 46, 277, 738, 514, 99 Fishtest SD TC 10+0 https://tests.stockfishchess.org/tests/live_elo/65ee401e0ec64f0526c43cf7 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 3780 W: 1097 L: 808 D: 1875 Ptnml(0-2): 11, 353, 919, 550, 57 Neutral Non-Regression STC 10+0.1 https://tests.stockfishchess.org/tests/live_elo/65ee45ff0ec64f0526c43d68 LLR: 2.95 (-2.94,2.94) <-1.75, 0.25> Total: 123616 W: 32054 L: 31927 D:59635 Ptnml(0-2): 493, 14323, 32105, 14338, 549 Neutral Non-Regression LTC 60+0.6 https://tests.stockfishchess.org/tests/live_elo/65ef1eec0ec64f0526c44bc4 LLR: 2.95 (-2.94,2.94) <-1.75, 0.25> Total: 130482 W: 32961 L: 32855 D:64666 Ptnml(0-2): 88, 13412, 38123, 13542, 76 closes https://github.com/official-stockfish/Stockfish/pull/5112 Bench: 2002517 --- src/timeman.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/timeman.cpp b/src/timeman.cpp index 4607344eab9..229ff3e9d6b 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -84,6 +84,12 @@ void TimeManagement::init(Search::LimitsType& limits, // Maximum move horizon of 50 moves int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + // if less than one second, gradually reduce mtg + if (limits.time[us] < 1000 && (double(mtg) / limits.time[us] > 0.05)) + { + mtg = limits.time[us] * 0.05; + } + // Make sure timeLeft is > 0 since we may use it as a divisor TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); From abd82396a1ceef4d49c8be30c366964bf18a74e2 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:55:56 +0800 Subject: [PATCH 0428/1309] Make effort part of RootMove struct Also includes several small cleanups. Passed STC: https://tests.stockfishchess.org/tests/view/65f15cfe0ec64f0526c473a0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 71136 W: 18456 L: 18273 D: 34407 Ptnml(0-2): 311, 8014, 18708, 8251, 284 closes https://github.com/official-stockfish/Stockfish/pull/5114 No functional change --- src/search.cpp | 13 ++++--------- src/search.h | 3 +-- src/thread.cpp | 2 -- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5bd59712a5b..fc92d1a9c32 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -214,7 +214,7 @@ void Search::Worker::start_searching() { // consumed, the user stops the search, or the maximum search depth is reached. void Search::Worker::iterative_deepening() { - SearchManager* mainThread = (thread_idx == 0 ? main_manager() : nullptr); + SearchManager* mainThread = (is_mainthread() ? main_manager() : nullptr); Move pv[MAX_PLY + 1]; @@ -426,9 +426,7 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - auto bestmove = rootMoves[0].pv[0]; - int nodesEffort = effort[bestmove.from_sq()][bestmove.to_sq()] * 100 - / std::max(size_t(1), size_t(nodes)); + int nodesEffort = rootMoves[0].effort * 100 / std::max(size_t(1), size_t(nodes)); double fallingEval = (1067 + 223 * (mainThread->bestPreviousAverageScore - bestValue) + 97 * (mainThread->iterValue[iterIdx] - bestValue)) @@ -450,9 +448,7 @@ void Search::Worker::iterative_deepening() { if (completedDepth >= 10 && nodesEffort >= 97 && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.739 && !mainThread->ponder) - { threads.stop = true; - } // Stop the search if we have exceeded the totalTime if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) @@ -1199,9 +1195,6 @@ Value Search::Worker::search( // Step 19. Undo move pos.undo_move(move); - if (rootNode) - effort[move.from_sq()][move.to_sq()] += nodes - nodeCount; - assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); // Step 20. Check for a new best move @@ -1216,6 +1209,8 @@ Value Search::Worker::search( RootMove& rm = *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); + rm.effort += nodes - nodeCount; + rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; diff --git a/src/search.h b/src/search.h index 4908e535c6a..22f75ffd4d8 100644 --- a/src/search.h +++ b/src/search.h @@ -89,6 +89,7 @@ struct RootMove { return m.score != score ? m.score < score : m.previousScore < previousScore; } + uint64_t effort = 0; Value score = -VALUE_INFINITE; Value previousScore = -VALUE_INFINITE; Value averageScore = -VALUE_INFINITE; @@ -230,8 +231,6 @@ class Worker { return static_cast(manager.get()); } - std::array, SQUARE_NB> effort; - LimitsType limits; size_t pvIdx, pvLast; diff --git a/src/thread.cpp b/src/thread.cpp index a3823d0cfd0..d968271f1e6 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -19,7 +19,6 @@ #include "thread.h" #include -#include #include #include #include @@ -211,7 +210,6 @@ void ThreadPool::start_thinking(const OptionsMap& options, th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); th->worker->rootState = setupStates->back(); th->worker->tbConfig = tbConfig; - th->worker->effort = {}; } main_thread()->start_searching(); From fb07281f5590bc216ecbacd468aa0d06fdead70c Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 14 Mar 2024 10:38:20 +0100 Subject: [PATCH 0429/1309] Fix false positives from ThreadSanitizer Since Linux Kernel 6.5 we are getting false positives from the ci, lower the ALSR entropy to disable ALSR, which works as a temporary workaround. https://github.com/google/sanitizers/issues/1716 https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 closes https://github.com/official-stockfish/Stockfish/pull/5115 No functional change --- tests/instrumented.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 2a3eadc074e..525c7e04085 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -8,6 +8,13 @@ error() } trap 'error ${LINENO}' ERR +# Since Linux Kernel 6.5 we are getting false positives from the ci, +# lower the ALSR entropy to disable ALSR, which works as a temporary workaround. +# https://github.com/google/sanitizers/issues/1716 +# https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 +sudo sysctl -w vm.mmap_rnd_bits=28 + + # define suitable post and prefixes for testing options case $1 in --valgrind) From ed604600042a4f2c0023ac9a189215e50fc7aa0f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 15 Mar 2024 18:55:40 +0300 Subject: [PATCH 0430/1309] Clamp history bonus to stats range Before, one always had to keep track of the bonus one assigns to a history to stop the stats from overflowing. This is a quality of life improvement. Since this would often go unnoticed during benching. Passed non-regression bounds: https://tests.stockfishchess.org/tests/view/65ef2af40ec64f0526c44cbc LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 179232 W: 46513 L: 46450 D: 86269 Ptnml(0-2): 716, 20323, 47452, 20432, 693 closes https://github.com/official-stockfish/Stockfish/pull/5116 No functional change --- src/movepick.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 357918a90f2..a16fdcd273e 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -19,17 +19,17 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED +#include #include #include -#include #include #include #include #include // IWYU pragma: keep #include "movegen.h" -#include "types.h" #include "position.h" +#include "types.h" namespace Stockfish { @@ -69,10 +69,11 @@ class StatsEntry { operator const T&() const { return entry; } void operator<<(int bonus) { - assert(std::abs(bonus) <= D); // Ensure range is [-D, D] static_assert(D <= std::numeric_limits::max(), "D overflows T"); - entry += bonus - entry * std::abs(bonus) / D; + // Make sure that bonus is in range [-D, D] + int clampedBonus = std::clamp(bonus, -D, D); + entry += clampedBonus - entry * std::abs(clampedBonus) / D; assert(std::abs(entry) <= D); } From 134e6d7bb400a372d168806e0f6f60d5e23a4cbf Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 17 Mar 2024 10:33:03 +0100 Subject: [PATCH 0431/1309] Consistent use of anonymous namespace Also change `bindThisThread` to match the current code style for function naming. closes https://github.com/official-stockfish/Stockfish/pull/5118 No functional change --- src/misc.cpp | 8 ++++--- src/misc.h | 2 +- src/nnue/nnue_misc.cpp | 7 +++--- src/thread.cpp | 2 +- src/tt.cpp | 2 +- src/tune.cpp | 52 +++++++++++++++++++++++------------------- 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 4885a5cd35c..270d25ad4bc 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -596,14 +596,15 @@ namespace WinProcGroup { #ifndef _WIN32 -void bindThisThread(size_t) {} +void bind_this_thread(size_t) {} #else +namespace { // Retrieves logical processor information using Windows-specific // API and returns the best node id for the thread with index idx. Original // code from Texel by Peter Österlund. -static int best_node(size_t idx) { +int best_node(size_t idx) { int threads = 0; int nodes = 0; @@ -668,10 +669,11 @@ static int best_node(size_t idx) { // then return -1 and let the OS to decide what to do. return idx < groups.size() ? groups[idx] : -1; } +} // Sets the group affinity of the current thread -void bindThisThread(size_t idx) { +void bind_this_thread(size_t idx) { // Use only local variables to be thread-safe int node = best_node(idx); diff --git a/src/misc.h b/src/misc.h index 9ad5c3ca57e..de34ee111f7 100644 --- a/src/misc.h +++ b/src/misc.h @@ -200,7 +200,7 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { // called to set group affinity for each thread. Original code from Texel by // Peter Österlund. namespace WinProcGroup { -void bindThisThread(size_t idx); +void bind_this_thread(size_t idx); } diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index c443aaf19b2..7005a61025e 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -51,10 +51,10 @@ void hint_common_parent_position(const Position& pos, const Networks& networks) networks.big.hint_common_access(pos, false); } - +namespace { // Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. -static void format_cp_compact(Value v, char* buffer) { +void format_cp_compact(Value v, char* buffer) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); @@ -90,7 +90,7 @@ static void format_cp_compact(Value v, char* buffer) { // Converts a Value into pawns, always keeping two decimals -static void format_cp_aligned_dot(Value v, std::stringstream& stream) { +void format_cp_aligned_dot(Value v, std::stringstream& stream) { const double pawns = std::abs(0.01 * UCI::to_cp(v)); @@ -99,6 +99,7 @@ static void format_cp_aligned_dot(Value v, std::stringstream& stream) { : ' ') << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; } +} // Returns a string with the value of each piece on a board, diff --git a/src/thread.cpp b/src/thread.cpp index d968271f1e6..90add4ad059 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -91,7 +91,7 @@ void Thread::idle_loop() { // just check if running threads are below a threshold, in this case, all this // NUMA machinery is not needed. if (nthreads > 8) - WinProcGroup::bindThisThread(idx); + WinProcGroup::bind_this_thread(idx); while (true) { diff --git a/src/tt.cpp b/src/tt.cpp index e62e0c17053..9d4d2eca47c 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -95,7 +95,7 @@ void TranspositionTable::clear(size_t threadCount) { threads.emplace_back([this, idx, threadCount]() { // Thread binding gives faster search on systems with a first-touch policy if (threadCount > 8) - WinProcGroup::bindThisThread(idx); + WinProcGroup::bind_this_thread(idx); // Each thread will zero its part of the hash table const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx), diff --git a/src/tune.cpp b/src/tune.cpp index 88b3b7912d5..3e5ebe5e6c3 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -30,37 +30,22 @@ using std::string; namespace Stockfish { -bool Tune::update_on_last; -const Option* LastOption = nullptr; -OptionsMap* Tune::options; -static std::map TuneResults; +bool Tune::update_on_last; +const Option* LastOption = nullptr; +OptionsMap* Tune::options; -string Tune::next(string& names, bool pop) { - - string name; - - do - { - string token = names.substr(0, names.find(',')); - - if (pop) - names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace - - } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); - - return name; -} +namespace { +std::map TuneResults; -static void on_tune(const Option& o) { +void on_tune(const Option& o) { if (!Tune::update_on_last || LastOption == &o) Tune::read_options(); } -static void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { + +void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { // Do not generate option when there is nothing to tune (ie. min = max) if (r(v).first == r(v).second) @@ -77,6 +62,27 @@ static void make_option(OptionsMap* options, const string& n, int v, const SetRa << (r(v).second - r(v).first) / 20.0 << "," << "0.0020" << std::endl; } +} + +string Tune::next(string& names, bool pop) { + + string name; + + do + { + string token = names.substr(0, names.find(',')); + + if (pop) + names.erase(0, token.size() + 1); + + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace + + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); + + return name; +} + template<> void Tune::Entry::init_option() { From 117e08c26454f2107a13d35945e3508ca6c0de5d Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 17 Mar 2024 12:34:02 +0100 Subject: [PATCH 0432/1309] Fix header name in Makefile No functional change --- src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 75f31108159..672171bcd59 100644 --- a/src/Makefile +++ b/src/Makefile @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.cpp + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h OBJS = $(notdir $(SRCS:.cpp=.o)) From 9b92ada935ddf920491156be22f609afaca4d840 Mon Sep 17 00:00:00 2001 From: Robert Nurnberg Date: Sun, 17 Mar 2024 15:39:01 +0100 Subject: [PATCH 0433/1309] Base WDL model on material count and normalize evals dynamically This PR proposes to change the parameter dependence of Stockfish's internal WDL model from full move counter to material count. In addition it ensures that an evaluation of 100 centipawns always corresponds to a 50% win probability at fishtest LTC, whereas for master this holds only at move number 32. See also https://github.com/official-stockfish/Stockfish/pull/4920 and the discussion therein. The new model was fitted based on about 340M positions extracted from 5.6M fishtest LTC games from the last three weeks, involving SF versions from e67cc979fd2c0e66dfc2b2f2daa0117458cfc462 (SF 16.1) to current master. The involved commands are for [WDL_model](https://github.com/official-stockfish/WDL_model) are: ``` ./updateWDL.sh --firstrev e67cc979fd2c0e66dfc2b2f2daa0117458cfc462 python scoreWDL.py updateWDL.json --plot save --pgnName update_material.png --momType "material" --momTarget 58 --materialMin 10 --modelFitting optimizeProbability ``` The anchor `58` for the material count value was chosen to be as close as possible to the observed average material count of fishtest LTC games at move 32 (`43`), while not changing the value of `NormalizeToPawnValue` compared to the move-based WDL model by more than 1. The patch only affects the displayed cp and wdl values. closes https://github.com/official-stockfish/Stockfish/pull/5121 No functional change --- src/evaluate.cpp | 4 +- src/nnue/nnue_misc.cpp | 18 ++++---- src/search.cpp | 7 +-- src/uci.cpp | 99 +++++++++++++++++++++++++----------------- src/uci.h | 6 +-- 5 files changed, 76 insertions(+), 58 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index f4d18d8e4e0..c7adf50946f 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -92,11 +92,11 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { Value v = networks.big.evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; + ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)\n"; v = evaluate(networks, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; + ss << "Final evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; ss << "\n"; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 7005a61025e..725d90d27d6 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -54,11 +54,11 @@ void hint_common_parent_position(const Position& pos, const Networks& networks) namespace { // Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. -void format_cp_compact(Value v, char* buffer) { +void format_cp_compact(Value v, char* buffer, const Position& pos) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(UCI::to_cp(v)); + int cp = std::abs(UCI::to_cp(v, pos)); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; @@ -90,9 +90,9 @@ void format_cp_compact(Value v, char* buffer) { // Converts a Value into pawns, always keeping two decimals -void format_cp_aligned_dot(Value v, std::stringstream& stream) { +void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) { - const double pawns = std::abs(0.01 * UCI::to_cp(v)); + const double pawns = std::abs(0.01 * UCI::to_cp(v, pos)); stream << (v < 0 ? '-' : v > 0 ? '+' @@ -114,7 +114,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { board[row][8 * 8 + 1] = '\0'; // A lambda to output one box of the board - auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { + auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) { const int x = int(file) * 8; const int y = (7 - int(rank)) * 3; for (int i = 1; i < 8; ++i) @@ -125,7 +125,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pc != NO_PIECE) board[y + 1][x + 4] = PieceToChar[pc]; if (value != VALUE_NONE) - format_cp_compact(value, &board[y + 2][x + 2]); + format_cp_compact(value, &board[y + 2][x + 2], pos); }; // We estimate the value of each piece by doing a differential evaluation from @@ -180,13 +180,13 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { { ss << "| " << bucket << " "; ss << " | "; - format_cp_aligned_dot(t.psqt[bucket], ss); + format_cp_aligned_dot(t.psqt[bucket], ss, pos); ss << " " << " | "; - format_cp_aligned_dot(t.positional[bucket], ss); + format_cp_aligned_dot(t.positional[bucket], ss, pos); ss << " " << " | "; - format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); ss << " " << " |"; if (bucket == t.correctBucket) diff --git a/src/search.cpp b/src/search.cpp index fc92d1a9c32..9929ec27ed2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -155,7 +155,8 @@ void Search::Worker::start_searching() { { rootMoves.emplace_back(Move::none()); sync_cout << "info depth 0 score " - << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; + << UCI::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) + << sync_endl; } else { @@ -1898,10 +1899,10 @@ std::string SearchManager::pv(const Search::Worker& worker, ss << "info" << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCI::value(v); + << " score " << UCI::to_score(v, pos); if (worker.options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos.game_ply()); + ss << UCI::wdl(v, pos); if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact ss << (rootMoves[i].scoreLowerbound diff --git a/src/uci.cpp b/src/uci.cpp index cf0e3f09cc7..cc03005ffc0 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "benchmark.h" @@ -44,9 +45,8 @@ namespace Stockfish { -constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int NormalizeToPawnValue = 356; -constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; namespace NN = Eval::NNUE; @@ -338,15 +338,43 @@ void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) } } -int UCI::to_cp(Value v) { return 100 * v / NormalizeToPawnValue; } +namespace { +std::pair win_rate_params(const Position& pos) { + + int material = pos.count() + 3 * pos.count() + 3 * pos.count() + + 5 * pos.count() + 9 * pos.count(); + + // The fitted model only uses data for material counts in [10, 78], and is anchored at count 58. + double m = std::clamp(material, 10, 78) / 58.0; + + // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model + constexpr double as[] = {-185.71965483, 504.85014385, -438.58295743, 474.04604627}; + constexpr double bs[] = {89.23542728, -137.02141296, 73.28669021, 47.53376190}; + + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; -std::string UCI::value(Value v) { + return {a, b}; +} + +// The win rate model is 1 / (1 + exp((a - eval) / b)), where a = p_a(material) and b = p_b(material). +// It fits the LTC fishtest statistics rather accurately. +int win_rate_model(Value v, const Position& pos) { + + auto [a, b] = win_rate_params(pos); + + // Return the win rate in per mille units, rounded to the nearest integer. + return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); +} +} + +std::string UCI::to_score(Value v, const Position& pos) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); std::stringstream ss; if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << to_cp(v); + ss << "cp " << to_cp(v, pos); else if (std::abs(v) <= VALUE_TB) { const int ply = VALUE_TB - std::abs(v); // recompute ss->ply @@ -358,6 +386,30 @@ std::string UCI::value(Value v) { return ss.str(); } +// Turns a Value to an integer centipawn number, +// without treatment of mate and similar special scores. +int UCI::to_cp(Value v, const Position& pos) { + + // In general, the score can be defined via the the WDL as + // (log(1/L - 1) - log(1/W - 1)) / ((log(1/L - 1) + log(1/W - 1)) + // Based on our win_rate_model, this simply yields v / a. + + auto [a, b] = win_rate_params(pos); + + return std::round(100 * int(v) / a); +} + +std::string UCI::wdl(Value v, const Position& pos) { + std::stringstream ss; + + int wdl_w = win_rate_model(v, pos); + int wdl_l = win_rate_model(-v, pos); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + std::string UCI::square(Square s) { return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } @@ -383,41 +435,6 @@ std::string UCI::move(Move m, bool chess960) { return move; } -namespace { -// The win rate model returns the probability of winning (in per mille units) given an -// eval and a game ply. It fits the LTC fishtest statistics rather accurately. -int win_rate_model(Value v, int ply) { - - // The fitted model only uses data for moves in [8, 120], and is anchored at move 32. - double m = std::clamp(ply / 2 + 1, 8, 120) / 32.0; - - // The coefficients of a third-order polynomial fit is based on the fishtest data - // for two parameters that need to transform eval to the argument of a logistic - // function. - constexpr double as[] = {-1.06249702, 7.42016937, 0.89425629, 348.60356174}; - constexpr double bs[] = {-5.33122190, 39.57831533, -90.84473771, 123.40620748}; - - // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at move 32. - static_assert(NormalizeToPawnValue == int(0.5 + as[0] + as[1] + as[2] + as[3])); - - double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; - double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; - - // Return the win rate in per mille units, rounded to the nearest integer. - return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); -} -} - -std::string UCI::wdl(Value v, int ply) { - std::stringstream ss; - - int wdl_w = win_rate_model(v, ply); - int wdl_l = win_rate_model(-v, ply); - int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; - - return ss.str(); -} Move UCI::to_move(const Position& pos, std::string& str) { if (str.length() == 5) diff --git a/src/uci.h b/src/uci.h index dd55862ad17..237928d9abc 100644 --- a/src/uci.h +++ b/src/uci.h @@ -42,11 +42,11 @@ class UCI { void loop(); - static int to_cp(Value v); - static std::string value(Value v); + static int to_cp(Value v, const Position& pos); + static std::string to_score(Value v, const Position& pos); static std::string square(Square s); static std::string move(Move m, bool chess960); - static std::string wdl(Value v, int ply); + static std::string wdl(Value v, const Position& pos); static Move to_move(const Position& pos, std::string& str); static Search::LimitsType parse_limits(const Position& pos, std::istream& is); From 1a6c22c5114c6ae9f916a69e0446b6c9eb864d72 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Fri, 15 Mar 2024 16:50:27 +0700 Subject: [PATCH 0434/1309] Evaluation adjustment for different eval types Gives different eval scaling parameters for the three different types of evaluation (bignet, smallnet, psqtOnly). Passed STC: https://tests.stockfishchess.org/tests/view/65f4b0020ec64f0526c4a3bd LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 168064 W: 43507 L: 42987 D: 81570 Ptnml(0-2): 662, 19871, 42445, 20393, 661 Passed LTC: https://tests.stockfishchess.org/tests/view/65f6be1a0ec64f0526c4c361 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 162564 W: 41188 L: 40604 D: 80772 Ptnml(0-2): 120, 18112, 44216, 18732, 102 closes https://github.com/official-stockfish/Stockfish/pull/5122 Bench: 2113576 --- src/evaluate.cpp | 37 +++++++++++++++++++++++++------------ src/evaluate.h | 2 +- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index c7adf50946f..3d1643fbd54 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -52,22 +52,35 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = std::abs(simpleEval) > SmallNetThreshold; bool psqtOnly = std::abs(simpleEval) > PsqtOnlyThreshold; - - int nnueComplexity; + int nnueComplexity; + int v; Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, true, &nnueComplexity, false); - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 524; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 31950; - - int npm = pos.non_pawn_material() / 64; - int v = (nnue * (927 + npm + 9 * pos.count()) + optimism * (159 + npm)) / 1000; - - // Damp down the evaluation linearly when shuffling - int shuffling = pos.rule50_count(); - v = v * (195 - shuffling) / 228; + const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, + int npmConstant, int evalDiv, int shufflingConstant, + int shufflingDiv) { + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; + nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / nnueDiv; + + int npm = pos.non_pawn_material() / 64; + v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) + + optimism * (npmConstant + npm)) + / evalDiv; + + // Damp down the evaluation linearly when shuffling + int shuffling = pos.rule50_count(); + v = v * (shufflingConstant - shuffling) / shufflingDiv; + }; + + if (!smallNet) + adjustEval(513, 32395, 919, 11, 145, 1036, 178, 204); + else if (psqtOnly) + adjustEval(517, 32857, 908, 7, 155, 1019, 224, 238); + else + adjustEval(499, 32793, 903, 9, 147, 1067, 208, 211); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/evaluate.h b/src/evaluate.h index bd11e0c1675..29dff40d05b 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1136, PsqtOnlyThreshold = 2656; +constexpr inline int SmallNetThreshold = 1050, PsqtOnlyThreshold = 2500; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the From 8e61d70499a9cebf3b7d97cf74295be5261ac0af Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Tue, 19 Mar 2024 02:45:09 +0700 Subject: [PATCH 0435/1309] Remove reduction increase on repetition Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65f89ae30ec64f0526c4e0ff LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 173568 W: 45005 L: 44936 D: 83627 Ptnml(0-2): 684, 19878, 45628, 19873, 721 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65fa0f370ec64f0526c4f42d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 21138 W: 5432 L: 5216 D: 10490 Ptnml(0-2): 13, 2107, 6112, 2325, 12 closes https://github.com/official-stockfish/Stockfish/pull/5123 Bench: 2109005 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9929ec27ed2..ca36c570b91 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1117,10 +1117,6 @@ Value Search::Worker::search( if (PvNode) r--; - // Increase reduction on repetition (~1 Elo) - if (move == (ss - 4)->currentMove && pos.has_repeated()) - r += 2; - // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) r++; From 7e427639ce2868cb31f36c9b1de3071f61a63618 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 20 Mar 2024 16:36:10 +0100 Subject: [PATCH 0436/1309] Add cmath header to movepick.h No functional change --- src/movepick.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/movepick.h b/src/movepick.h index a16fdcd273e..b81f76e18f2 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From d99f89506bd0ed535fb1c55dbb7cc8f7c29444d4 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 17 Mar 2024 11:20:41 +0800 Subject: [PATCH 0437/1309] VVLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This set of parameters was derived from 3 tuning attempts: https://tests.stockfishchess.org/tests/view/65d19ab61d8e83c78bfd8436 (80+0.8 x8, ~40k games) Then tuned with one of linrock's early L1-3072 nets: https://tests.stockfishchess.org/tests/view/65def7b04b19edc854ebdec8 (VVLTC, ~36k games) Starting from the result of this tuning, the parameters were then tuned with the current master net: https://tests.stockfishchess.org/tests/view/65f11c420ec64f0526c46fc4 (VVLTC, ~45k games) Additionally, at the start of the third tuning phase, 2 parameters were manually changed: Notably, the triple extension margin was decreased from 78 to 22. This idea was given by Vizvezdenec: https://tests.stockfishchess.org/tests/view/65f0a2360ec64f0526c46752. The PvNode extension margin was also adjusted from 50 to 40. This tune also differs from previous tuning attempts by tuning the evaluation thresholds for smallnet and psqt-only. The former was increased through the tuning, and this is hypothesized to scale better at VVLTC, although there is not much evidence of it. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/65f6761d0ec64f0526c4be88 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 44688 W: 11421 L: 11140 D: 22127 Ptnml(0-2): 1, 4170, 13722, 4449, 2 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/65fa31a30ec64f0526c4f611 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 27450 W: 7057 L: 6778 D: 13615 Ptnml(0-2): 4, 2545, 8346, 2828, 2 STC Elo estimate: https://tests.stockfishchess.org/tests/view/65fd3e540ec64f0526c521ae Elo: -7.84 ± 1.8 (95%) LOS: 0.0% Total: 40000 W: 9899 L: 10802 D: 19299 Ptnml(0-2): 203, 5221, 10025, 4378, 173 nElo: -14.91 ± 3.4 (95%) PairsRatio: 0.84 closes https://github.com/official-stockfish/Stockfish/pull/5130 Bench: 1876107 --- src/evaluate.h | 2 +- src/search.cpp | 74 +++++++++++++++++++++++++------------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 29dff40d05b..5f2b2c5422a 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1050, PsqtOnlyThreshold = 2500; +constexpr inline int SmallNetThreshold = 1165, PsqtOnlyThreshold = 2500; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/search.cpp b/src/search.cpp index ca36c570b91..f0e7f847f24 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -55,9 +55,9 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 122 - 46 * noTtCutNode; - Value improvingDeduction = 57 * improving * futilityMult / 32; - Value worseningDeduction = (331 + 45 * improving) * oppWorsening * futilityMult / 1024; + Value futilityMult = 118 - 44 * noTtCutNode; + Value improvingDeduction = 53 * improving * futilityMult / 32; + Value worseningDeduction = (309 + 47 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -69,15 +69,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 11450; + v += cv * std::abs(cv) / 11175; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(249 * d - 327, 1192); } +int stat_bonus(Depth d) { return std::min(223 * d - 332, 1258); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(516 * d - 299, 1254); } +int stat_malus(Depth d) { return std::min(536 * d - 299, 1353); } // 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); } @@ -302,12 +302,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 12800; + delta = 10 + avg * avg / 12493; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 130 * avg / (std::abs(avg) + 90); + optimism[us] = 132 * avg / (std::abs(avg) + 89); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -494,7 +494,7 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-71); + h->fill(-67); for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((19.80 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); @@ -729,7 +729,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1621, 1238); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1578, 1291); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -752,7 +752,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 462 - (296 - 145 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 488 - (289 - 142 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -763,13 +763,13 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 287 + - (ss - 1)->statScore / 267 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16211 + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16878 && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) @@ -777,7 +777,7 @@ Value Search::Worker::search( assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 151, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -825,7 +825,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 168 - 64 * improving; + probCutBeta = beta + 170 - 64 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -881,7 +881,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 410; + probCutBeta = beta + 409; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -964,7 +964,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 298 + 288 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 297 + 284 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -972,7 +972,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -202 * depth)) + if (!pos.see_ge(move, -203 * depth)) continue; } else @@ -984,24 +984,24 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4125 * depth) + if (lmrDepth < 6 && history < -4040 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 5686; + lmrDepth += history / 5637; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 55 ? 153 : 58) - + 118 * lmrDepth + && ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) + + 125 * lmrDepth <= alpha) continue; lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1021,11 +1021,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 29) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (58 + 55 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (58 + 58 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1040,11 +1040,11 @@ Value Search::Worker::search( // We make sure to limit the extensions in some way to avoid a search explosion if (!PvNode && ss->multipleExtensions <= 16) { - extension = 2 + (value < singularBeta - 78 && !ttCapture); + extension = 2 + (value < singularBeta - 22 && !ttCapture); depth += depth < 14; } if (PvNode && !ttCapture && ss->multipleExtensions <= 5 - && value < singularBeta - 50) + && value < singularBeta - 37) extension = 2; } @@ -1079,7 +1079,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4315) + > 4026) extension = 1; } @@ -1129,10 +1129,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4587; + + (*contHist[3])[movedPiece][move.to_sq()] - 4723; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 14956; + r -= ss->statScore / 13659; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1151,7 +1151,7 @@ Value Search::Worker::search( { // 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 + 48 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 47 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1269,7 +1269,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13665 && value > -12276) + if (depth > 2 && depth < 12 && beta < 14206 && value > -12077) depth -= 2; assert(depth > 0); @@ -1312,7 +1312,7 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14446) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14963) + ((ss - 1)->moveCount > 11) + (!ss->inCheck && bestValue <= ss->staticEval - 150); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, @@ -1472,7 +1472,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 221; + futilityBase = ss->staticEval + 226; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1552,7 +1552,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -79)) + if (!pos.see_ge(move, -78)) continue; } @@ -1620,7 +1620,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1091 - delta * 759 / rootDelta) / 1024 + (!i && reductionScale > 950); + return (reductionScale + 1107 - delta * 725 / rootDelta) / 1024 + (!i && reductionScale > 956); } namespace { @@ -1709,7 +1709,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 167 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 5001d49f42033bcf36a6c57401f891791031b4d8 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 21 Mar 2024 01:25:59 -0700 Subject: [PATCH 0438/1309] Update nnue_feature_transformer.h Unroll update_accumulator_refresh to process two active indices simultaneously. The compiler might not unroll effectively because the number of active indices isn't known at compile time. STC https://tests.stockfishchess.org/tests/view/65faa8850ec64f0526c4fca9 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 130464 W: 33882 L: 33431 D: 63151 Ptnml(0-2): 539, 14591, 34501, 15082, 519 closes https://github.com/official-stockfish/Stockfish/pull/5125 No functional change --- src/nnue/nnue_feature_transformer.h | 33 +++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index b42f160475f..888edebbdb3 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -619,8 +619,22 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasesTile[k]; - for (const auto index : active) + int i = 0; + for (; i < int(active.size()) - 1; i += 2) { + IndexType index0 = active[i]; + IndexType index1 = active[i + 1]; + const IndexType offset0 = HalfDimensions * index0 + j * TileHeight; + const IndexType offset1 = HalfDimensions * index1 + j * TileHeight; + auto column0 = reinterpret_cast(&weights[offset0]); + auto column1 = reinterpret_cast(&weights[offset1]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_add_16(column0[k], column1[k])); + } + for (; i < int(active.size()); ++i) + { + IndexType index = active[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; auto column = reinterpret_cast(&weights[offset]); @@ -639,8 +653,23 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); - for (const auto index : active) + int i = 0; + for (; i < int(active.size()) - 1; i += 2) + { + IndexType index0 = active[i]; + IndexType index1 = active[i + 1]; + const IndexType offset0 = PSQTBuckets * index0 + j * PsqtTileHeight; + const IndexType offset1 = PSQTBuckets * index1 + j * PsqtTileHeight; + auto columnPsqt0 = reinterpret_cast(&psqtWeights[offset0]); + auto columnPsqt1 = reinterpret_cast(&psqtWeights[offset1]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = + vec_add_psqt_32(psqt[k], vec_add_psqt_32(columnPsqt0[k], columnPsqt1[k])); + } + for (; i < int(active.size()); ++i) { + IndexType index = active[i]; const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); From 7998570414ba489b3dfe239b424c200041396e7f Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 23 Mar 2024 10:34:32 +0100 Subject: [PATCH 0439/1309] Add functionality to export small net Usage ``` export_net ``` closes https://github.com/official-stockfish/Stockfish/pull/5133 No functional change --- src/uci.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index cc03005ffc0..25884f577bd 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -159,11 +159,16 @@ void UCI::loop() { sync_cout << compiler_info() << sync_endl; else if (token == "export_net") { - std::optional filename; - std::string f; - if (is >> std::skipws >> f) - filename = f; - networks.big.save(filename); + std::pair, std::string> files[2]; + + if (is >> std::skipws >> files[0].second) + files[0].first = files[0].second; + + if (is >> std::skipws >> files[1].second) + files[1].first = files[1].second; + + networks.big.save(files[0].first); + networks.small.save(files[1].first); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout From d49b3738bc89353a9318d54af400f02c91d2d69a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 24 Mar 2024 14:02:27 +0300 Subject: [PATCH 0440/1309] Tweak the stats bonus and malus For depth 1 we don't have a negative score anymore. Passed STC: https://tests.stockfishchess.org/tests/view/65fb055c0ec64f0526c5024f LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 117120 W: 30468 L: 30023 D: 56629 Ptnml(0-2): 526, 13759, 29539, 14216, 520 Passed LTC: https://tests.stockfishchess.org/tests/view/65fdca4b0ec64f0526c5293f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 54816 W: 13955 L: 13595 D: 27266 Ptnml(0-2): 30, 6046, 14897, 6404, 31 closes https://github.com/official-stockfish/Stockfish/pull/5134 Bench: 1876428 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f0e7f847f24..e1508a7f06d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -74,10 +74,10 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(223 * d - 332, 1258); } +int stat_bonus(Depth d) { return std::clamp(245 * d - 320, 0, 1296); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(536 * d - 299, 1353); } +int stat_malus(Depth d) { return (d < 4 ? 554 * d - 303 : 1203); } // 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); } @@ -1709,7 +1709,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From ed24e3a0a6e6958b26fefc4c43078c0bb46f5376 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sat, 23 Mar 2024 03:48:44 +0700 Subject: [PATCH 0441/1309] Remove material imbalance from nnue eval Passed non-reg STC: https://tests.stockfishchess.org/tests/view/65fdf11f0ec64f0526c52b57 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 76480 W: 19893 L: 19712 D: 36875 Ptnml(0-2): 339, 9107, 19157, 9308, 329 Passed non-reg LTC: https://tests.stockfishchess.org/tests/view/65fee22e0ec64f0526c53885 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 150948 W: 38078 L: 37988 D: 74882 Ptnml(0-2): 111, 16997, 41148, 17127, 91 closes https://github.com/official-stockfish/Stockfish/pull/5135 Bench: 2103324 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3d1643fbd54..bc705b857df 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -63,7 +63,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int shufflingDiv) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; - nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / nnueDiv; + nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; int npm = pos.non_pawn_material() / 64; v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) From e636f73ab83da13fd352658e1eeecd369479c491 Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Wed, 27 Mar 2024 08:20:49 +0000 Subject: [PATCH 0442/1309] Vary time use with eval Adjust time use depending on the current eval. Passed STC : LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 101696 W: 26651 L: 26238 D: 48807 Ptnml(0-2): 400, 11602, 26459, 11959, 428 https://tests.stockfishchess.org/tests/live_elo/660187a50ec64f0526c557f6 Passed LTC : LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 60648 W: 15550 L: 15187 D: 29911 Ptnml(0-2): 40, 6356, 17171, 6715, 42 https://tests.stockfishchess.org/tests/live_elo/660298ed0ec64f0526c566d0 Values were found using two tunes with the final values taken from the ltc tune after 62k games : stc - https://tests.stockfishchess.org/tests/view/65fb526b0ec64f0526c50694 ltc - https://tests.stockfishchess.org/tests/view/65fd36e60ec64f0526c5214b Ideas for future work; * tune these values with the other TM adjustments * try narrower bands * calculate adjustment for exact eval by interpolation closes https://github.com/official-stockfish/Stockfish/pull/5138 No functional change --- src/search.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e1508a7f06d..f045bc47962 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -53,6 +53,9 @@ using namespace Search; namespace { +static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, + 1.002, 0.992, 0.947, 1.046, 1.001}; + // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 118 - 44 * noTtCutNode; @@ -438,9 +441,10 @@ void Search::Worker::iterative_deepening() { timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); + int el = std::clamp((bestValue + 750) / 150, 0, 9); - double totalTime = - mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; + double totalTime = mainThread->tm.optimum() * fallingEval * reduction + * bestMoveInstability * EvalLevel[el]; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) From 0ef5d05102ce0632d7b076706d26eb4eba7fcb70 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 29 Mar 2024 00:17:37 +0300 Subject: [PATCH 0443/1309] Adjust best value after a pruned quiet move Logic somewhat similar to how we adjust best value after pruned captures in qsearch, but in search this patch does it after pruned quiet moves and also to not full scale of futility value but to smth in between best value and futility value. Passed STC: https://tests.stockfishchess.org/tests/view/6601cf900ec64f0526c55c30 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 59936 W: 15722 L: 15369 D: 28845 Ptnml(0-2): 182, 7097, 15112, 7340, 237 Passed LTC: https://tests.stockfishchess.org/tests/view/66029b2d0ec64f0526c566f1 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 118362 W: 29953 L: 29460 D: 58949 Ptnml(0-2): 68, 13159, 32249, 13622, 83 closes https://github.com/official-stockfish/Stockfish/pull/5141 bench 1772608 --- src/search.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f045bc47962..dfb27285104 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -995,12 +995,17 @@ Value Search::Worker::search( lmrDepth += history / 5637; + Value futilityValue = + ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) + 125 * lmrDepth; + // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 15 - && ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) - + 125 * lmrDepth - <= alpha) + if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) + { + if (bestValue <= futilityValue && abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY + && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) + bestValue = (bestValue + futilityValue * 3) / 4; continue; + } lmrDepth = std::max(lmrDepth, 0); From e13e4cfb8340cdb26a00679681a0f163c6b4f0a9 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 27 Mar 2024 02:58:59 -0700 Subject: [PATCH 0444/1309] Simplify NMP Condition Remove eval >= ss->staticEval condition for Null Move Pruning. Passed non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 44000 W: 11420 L: 11202 D: 21378 Ptnml(0-2): 174, 5243, 10978, 5401, 204 https://tests.stockfishchess.org/tests/live_elo/6603ee490ec64f0526c57984 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82956 W: 20978 L: 20818 D: 41160 Ptnml(0-2): 54, 9353, 22499, 9523, 49 https://tests.stockfishchess.org/tests/live_elo/660464b50ec64f0526c5804d closes https://github.com/official-stockfish/Stockfish/pull/5142 Bench 1759189 --- AUTHORS | 1 + src/search.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 40a38bd5d89..abae401c1ef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -204,6 +204,7 @@ sf-x Shahin M. Shahin (peregrine) Shane Booth (shane31) Shawn Varghese (xXH4CKST3RXx) +Shawn Xu (xu-shawn) Siad Daboul (Topologist) Stefan Geschwentner (locutus2) Stefano Cardanobile (Stefano80) diff --git a/src/search.cpp b/src/search.cpp index dfb27285104..fb5a2f003c7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -774,8 +774,8 @@ Value Search::Worker::search( // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16878 - && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 20 * depth + 314 - && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly + && eval >= beta && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); From 68d58d94da15d175269eaa06b3d224062cf72a70 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 29 Mar 2024 10:25:41 +0100 Subject: [PATCH 0445/1309] Fix usage of abs vs std::abs close https://github.com/official-stockfish/Stockfish/pull/5143 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fb5a2f003c7..3f882aabdf5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1001,7 +1001,7 @@ Value Search::Worker::search( // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) { - if (bestValue <= futilityValue && abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY + if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) bestValue = (bestValue + futilityValue * 3) / 4; continue; From ec598b380db41fa54100cf3de51fe4be17eaf08b Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 29 Mar 2024 10:49:53 +0100 Subject: [PATCH 0446/1309] Improve prerelease creation workflow In the last couple of months we sometimes saw duplicated prereleases uploaded to GitHub, possibly due to some racy behavior when concurrent jobs create a prerelease. This now creates an empty prerelease at the beginning of the CI and the binaries are later just attached to this one. closes https://github.com/official-stockfish/Stockfish/pull/5144 No functional change --- .github/workflows/stockfish.yml | 38 ++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 22cd9af3fd2..13d57f9ed9b 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -16,6 +16,8 @@ jobs: if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')) runs-on: ubuntu-latest steps: + - uses: actions/checkout@v4 + # returns null if no pre-release exists - name: Get Commit SHA of Latest Pre-release run: | @@ -23,14 +25,40 @@ jobs: sudo apt-get update sudo apt-get install -y curl jq - echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV + echo "COMMIT_SHA_TAG=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV - # delete old previous pre-release and tag - - uses: actions/checkout@v4 - - run: gh release delete ${{ env.COMMIT_SHA }} --cleanup-tag - if: env.COMMIT_SHA != 'null' + # delete old previous pre-release and tag + - run: gh release delete ${{ env.COMMIT_SHA_TAG }} --cleanup-tag + if: env.COMMIT_SHA_TAG != 'null' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Make sure that an old ci that still runs on master doesn't recreate a prerelease + - name: Check Pullable Commits + id: check_commits + run: | + git fetch + CHANGES=$(git rev-list HEAD..origin/master --count) + echo "CHANGES=$CHANGES" >> $GITHUB_ENV + + - name: Get last commit SHA + id: last_commit + run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV + + - name: Get commit date + id: commit_date + run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + + # Create a new pre-release, the other upload_binaries.yml will upload the binaries + # to this pre-release. + - name: Create Prerelease + if: github.ref_name == 'master' && env.CHANGES == '0' + uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 + with: + name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} + prerelease: true + Matrix: runs-on: ubuntu-latest outputs: From c964942da225ace51e1446deb29e7f43bf21360e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 30 Mar 2024 10:54:36 +0100 Subject: [PATCH 0447/1309] Avoid a note related to an ABI change current master triggers a gcc note: parameter passing for argument of type 'std::pair' when C++17 is enabled changed to match C++14 in GCC 10.1 while this is inconsequential, and just informative https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111516 we can easily avoid it. closes https://github.com/official-stockfish/Stockfish/pull/5145 No functional change --- src/uci.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/uci.cpp b/src/uci.cpp index 25884f577bd..ee95d5be5e6 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -344,7 +344,13 @@ void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) } namespace { -std::pair win_rate_params(const Position& pos) { + +struct WinRateParams { + double a; + double b; +}; + +WinRateParams win_rate_params(const Position& pos) { int material = pos.count() + 3 * pos.count() + 3 * pos.count() + 5 * pos.count() + 9 * pos.count(); From 0716b845fdef8a20102b07eaec074b8da8162523 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Tue, 2 Apr 2024 04:38:54 +0100 Subject: [PATCH 0448/1309] Update NNUE architecture to SFNNv9 and net nn-ae6a388e4a1a.nnue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part 1: PyTorch Training, linrock Trained with a 10-stage sequence from scratch, starting in May 2023: https://github.com/linrock/nnue-tools/blob/master/exp-sequences/3072-10stage-SFNNv9.yml While the training methods were similar to the L1-2560 training sequence, the last two stages introduced min-v2 binpacks, where bestmove capture and in-check position scores were not zeroed during minimization, for compatibility with skipping SEE >= 0 positions and future research. Training data can be found at: https://robotmoon.com/nnue-training-data This net was tested at epoch 679 of the 10th training stage: https://tests.stockfishchess.org/tests/view/65f32e460ec64f0526c48dbc Part 2: SPSA Training, Viren6 The net was then SPSA tuned. This consisted of the output weights (32 * 8) and biases (8) as well as the L3 biases (32 * 8) and L2 biases (16 * 8), totalling 648 params in total. The SPSA tune can be found here: https://tests.stockfishchess.org/tests/view/65fc33ba0ec64f0526c512e3 With the help of Disservin , the initial weights were extracted with: https://github.com/Viren6/Stockfish/tree/new228 The net was saved with the tuned weights using: https://github.com/Viren6/Stockfish/tree/new241 Earlier nets of the SPSA failed STC compared to the base 3072 net of part 1: https://tests.stockfishchess.org/tests/view/65ff356e0ec64f0526c53c98 Therefore it is suspected that the SPSA at VVLTC has added extra scaling on top of the scaling of increasing the L1 size. Passed VVLTC 1: https://tests.stockfishchess.org/tests/view/6604a9020ec64f0526c583da LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 53042 W: 13554 L: 13256 D: 26232 Ptnml(0-2): 12, 5147, 15903, 5449, 10 Passed VVLTC 2: https://tests.stockfishchess.org/tests/view/660ad1b60ec64f0526c5dd23 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 17506 W: 4574 L: 4315 D: 8617 Ptnml(0-2): 1, 1567, 5362, 1818, 5 STC Elo estimate: https://tests.stockfishchess.org/tests/view/660b834d01aaec5069f87cb0 Elo: -7.66 ± 3.8 (95%) LOS: 0.0% Total: 9618 W: 2440 L: 2652 D: 4526 Ptnml(0-2): 80, 1281, 2261, 1145, 42 nElo: -13.94 ± 6.9 (95%) PairsRatio: 0.87 closes https://tests.stockfishchess.org/tests/view/660b834d01aaec5069f87cb0 bench 1823302 Co-Authored-By: Linmiao Xu --- src/evaluate.h | 2 +- src/nnue/nnue_architecture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 5f2b2c5422a..97ca4c4b665 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -35,7 +35,7 @@ constexpr inline int SmallNetThreshold = 1165, PsqtOnlyThreshold = 2500; // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" +#define EvalFileDefaultNameBig "nn-ae6a388e4a1a.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 05efb813754..7f73f87fd5e 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -38,7 +38,7 @@ namespace Stockfish::Eval::NNUE { using FeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensionsBig = 2560; +constexpr IndexType TransformedFeatureDimensionsBig = 3072; constexpr int L2Big = 15; constexpr int L3Big = 32; From 299707d2c2cbf1694bb21ed4a375b54ef35d719e Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 17 Mar 2024 12:33:14 +0100 Subject: [PATCH 0449/1309] Split UCI into UCIEngine and Engine This is another refactor which aims to decouple uci from stockfish. A new engine class manages all engine related logic and uci is a "small" wrapper around it. In the future we should also try to remove the need for the Position object in the uci and replace the options with an actual options struct instead of using a map. Also convert the std::string's in the Info structs a string_view. closes #5147 No functional change --- src/Makefile | 4 +- src/engine.cpp | 153 +++++++++++++++++++++++++++++++++++++++++ src/engine.h | 84 ++++++++++++++++++++++ src/evaluate.cpp | 4 +- src/main.cpp | 5 +- src/misc.cpp | 31 +++++---- src/misc.h | 10 +-- src/nnue/nnue_misc.cpp | 4 +- src/perft.h | 2 +- src/position.cpp | 6 +- src/search.cpp | 16 +++-- src/tune.h | 2 +- src/uci.cpp | 146 ++++++++++++++------------------------- src/uci.h | 27 +++----- 14 files changed, 343 insertions(+), 151 deletions(-) create mode 100644 src/engine.cpp create mode 100644 src/engine.h diff --git a/src/Makefile b/src/Makefile index 672171bcd59..6315bda82df 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/engine.cpp b/src/engine.cpp new file mode 100644 index 00000000000..79a2c604742 --- /dev/null +++ b/src/engine.cpp @@ -0,0 +1,153 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "engine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark.h" +#include "evaluate.h" +#include "movegen.h" +#include "nnue/network.h" +#include "nnue/nnue_common.h" +#include "perft.h" +#include "position.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "types.h" +#include "ucioption.h" + +namespace Stockfish { + +namespace NN = Eval::NNUE; + +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + +Engine::Engine(std::string path) : + binaryDirectory(CommandLine::get_binary_directory(path)), + states(new std::deque(1)), + networks(NN::Networks( + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { + Tune::init(options); + pos.set(StartFEN, false, &states->back()); +} + +void Engine::go(const Search::LimitsType& limits) { + verify_networks(); + + if (limits.perft) + { + perft(pos.fen(), limits.perft, options["UCI_Chess960"]); + return; + } + + threads.start_thinking(options, pos, states, limits); +} +void Engine::stop() { threads.stop = true; } + +void Engine::search_clear() { + wait_for_search_finished(); + + tt.clear(options["Threads"]); + threads.clear(); + + // @TODO wont work multiple instances + Tablebases::init(options["SyzygyPath"]); // Free mapped files +} + +void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } + +void Engine::set_position(const std::string& fen, const std::vector& moves) { + // Drop the old state and create a new one + states = StateListPtr(new std::deque(1)); + pos.set(fen, options["UCI_Chess960"], &states->back()); + + for (const auto& move : moves) + { + auto m = UCIEngine::to_move(pos, move); + + if (m == Move::none()) + break; + + states->emplace_back(); + pos.do_move(m, states->back()); + } +} + +// modifiers + +void Engine::resize_threads() { threads.set({options, threads, tt, networks}); } + +void Engine::set_tt_size(size_t mb) { + wait_for_search_finished(); + tt.resize(mb, options["Threads"]); +} + +void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } + +// network related + +void Engine::verify_networks() { + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); +} + +void Engine::load_networks() { + networks.big.load(binaryDirectory, options["EvalFile"]); + networks.small.load(binaryDirectory, options["EvalFileSmall"]); +} + +void Engine::load_big_network(const std::string& file) { networks.big.load(binaryDirectory, file); } + +void Engine::load_small_network(const std::string& file) { + networks.small.load(binaryDirectory, file); +} + +void Engine::save_network(const std::pair, std::string> files[2]) { + networks.big.save(files[0].first); + networks.small.save(files[1].first); +} + +// utility functions + +OptionsMap& Engine::get_options() { return options; } + +uint64_t Engine::nodes_searched() const { return threads.nodes_searched(); } + +void Engine::trace_eval() { + StateListPtr trace_states(new std::deque(1)); + Position p; + p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back()); + + verify_networks(); + + sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; +} + +} \ No newline at end of file diff --git a/src/engine.h b/src/engine.h new file mode 100644 index 00000000000..6afc423de93 --- /dev/null +++ b/src/engine.h @@ -0,0 +1,84 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ENGINE_H_INCLUDED +#define ENGINE_H_INCLUDED + +#include "misc.h" +#include "nnue/network.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +namespace Stockfish { + +class Engine { + public: + Engine(std::string path = ""); + ~Engine() { wait_for_search_finished(); } + + // non blocking call to start searching + void go(const Search::LimitsType&); + // non blocking call to stop searching + void stop(); + + // blocking call to wait for search to finish + void wait_for_search_finished(); + // set a new position, moves are in UCI format + void set_position(const std::string& fen, const std::vector& moves); + + // modifiers + + void resize_threads(); + void set_tt_size(size_t mb); + void set_ponderhit(bool); + void search_clear(); + + // network related + + void verify_networks(); + void load_networks(); + void load_big_network(const std::string& file); + void load_small_network(const std::string& file); + void save_network(const std::pair, std::string> files[2]); + + // utility functions + + void trace_eval(); + // nodes since last search clear + uint64_t nodes_searched() const; + OptionsMap& get_options(); + + private: + const std::string binaryDirectory; + + Position pos; + StateListPtr states; + + OptionsMap options; + ThreadPool threads; + TranspositionTable tt; + Eval::NNUE::Networks networks; +}; + +} // namespace Stockfish + + +#endif // #ifndef ENGINE_H_INCLUDED \ No newline at end of file diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bc705b857df..dcbfedb499a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -105,11 +105,11 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { Value v = networks.big.evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)\n"; + ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; v = evaluate(networks, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v, pos) << " (white side)"; + ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; ss << "\n"; diff --git a/src/main.cpp b/src/main.cpp index 33d5d375fca..4e72c00398a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,10 +34,7 @@ int main(int argc, char* argv[]) { Bitboards::init(); Position::init(); - UCI uci(argc, argv); - - Tune::init(uci.options); - + UCIEngine uci(argc, argv); uci.loop(); return 0; diff --git a/src/misc.cpp b/src/misc.cpp index 270d25ad4bc..1abb81b14c2 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -723,13 +723,9 @@ void bind_this_thread(size_t idx) { #define GETCWD getcwd #endif -CommandLine::CommandLine(int _argc, char** _argv) : - argc(_argc), - argv(_argv) { - std::string pathSeparator; - // Extract the path+name of the executable binary - std::string argv0 = argv[0]; +std::string CommandLine::get_binary_directory(std::string argv0) { + std::string pathSeparator; #ifdef _WIN32 pathSeparator = "\\"; @@ -745,15 +741,11 @@ CommandLine::CommandLine(int _argc, char** _argv) : #endif // Extract the working directory - workingDirectory = ""; - char buff[40000]; - char* cwd = GETCWD(buff, 40000); - if (cwd) - workingDirectory = cwd; + auto workingDirectory = CommandLine::get_working_directory(); // Extract the binary directory path from argv0 - binaryDirectory = argv0; - size_t pos = binaryDirectory.find_last_of("\\/"); + auto binaryDirectory = argv0; + size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) binaryDirectory = "." + pathSeparator; else @@ -762,6 +754,19 @@ CommandLine::CommandLine(int _argc, char** _argv) : // Pattern replacement: "./" at the start of path is replaced by the working directory if (binaryDirectory.find("." + pathSeparator) == 0) binaryDirectory.replace(0, 1, workingDirectory); + + return binaryDirectory; } +std::string CommandLine::get_working_directory() { + std::string workingDirectory = ""; + char buff[40000]; + char* cwd = GETCWD(buff, 40000); + if (cwd) + workingDirectory = cwd; + + return workingDirectory; +} + + } // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index de34ee111f7..d75b236ff71 100644 --- a/src/misc.h +++ b/src/misc.h @@ -206,13 +206,15 @@ void bind_this_thread(size_t idx); struct CommandLine { public: - CommandLine(int, char**); + CommandLine(int _argc, char** _argv) : + argc(_argc), + argv(_argv) {} + + static std::string get_binary_directory(std::string argv0); + static std::string get_working_directory(); int argc; char** argv; - - std::string binaryDirectory; // path of the executable directory - std::string workingDirectory; // path of the working directory }; namespace Utility { diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 725d90d27d6..3fa6e1b6180 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -58,7 +58,7 @@ void format_cp_compact(Value v, char* buffer, const Position& pos) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - int cp = std::abs(UCI::to_cp(v, pos)); + int cp = std::abs(UCIEngine::to_cp(v, pos)); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; @@ -92,7 +92,7 @@ void format_cp_compact(Value v, char* buffer, const Position& pos) { // Converts a Value into pawns, always keeping two decimals void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) { - const double pawns = std::abs(0.01 * UCI::to_cp(v, pos)); + const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos)); stream << (v < 0 ? '-' : v > 0 ? '+' diff --git a/src/perft.h b/src/perft.h index 2edc3ad0a6e..2dbab828a18 100644 --- a/src/perft.h +++ b/src/perft.h @@ -51,7 +51,7 @@ uint64_t perft(Position& pos, Depth depth) { pos.undo_move(m); } if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + sync_cout << UCIEngine::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; } return nodes; } diff --git a/src/position.cpp b/src/position.cpp index 2263afe7669..fd1678959d5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -78,7 +78,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { << std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: "; for (Bitboard b = pos.checkers(); b;) - os << UCI::square(pop_lsb(b)) << " "; + os << UCIEngine::square(pop_lsb(b)) << " "; if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { @@ -431,8 +431,8 @@ string Position::fen() const { if (!can_castle(ANY_CASTLING)) ss << '-'; - ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50 - << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + ss << (ep_square() == SQ_NONE ? " - " : " " + UCIEngine::square(ep_square()) + " ") + << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; return ss.str(); } diff --git a/src/search.cpp b/src/search.cpp index 3f882aabdf5..efc00750613 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -158,7 +158,7 @@ void Search::Worker::start_searching() { { rootMoves.emplace_back(Move::none()); sync_cout << "info depth 0 score " - << UCI::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) + << UCIEngine::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) << sync_endl; } else @@ -204,11 +204,13 @@ void Search::Worker::start_searching() { sync_cout << main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth) << sync_endl; - sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + sync_cout << "bestmove " + << UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) - std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + std::cout << " ponder " + << UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); std::cout << sync_endl; } @@ -933,7 +935,7 @@ Value Search::Worker::search( if (rootNode && is_mainthread() && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) sync_cout << "info depth " << depth << " currmove " - << UCI::move(move, pos.is_chess960()) << " currmovenumber " + << UCIEngine::move(move, pos.is_chess960()) << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; if (PvNode) (ss + 1)->pv = nullptr; @@ -1904,10 +1906,10 @@ std::string SearchManager::pv(const Search::Worker& worker, ss << "info" << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCI::to_score(v, pos); + << " score " << UCIEngine::to_score(v, pos); if (worker.options["UCI_ShowWDL"]) - ss << UCI::wdl(v, pos); + ss << UCIEngine::wdl(v, pos); if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact ss << (rootMoves[i].scoreLowerbound @@ -1918,7 +1920,7 @@ std::string SearchManager::pv(const Search::Worker& worker, << " tbhits " << tbHits << " time " << time << " pv"; for (Move m : rootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); + ss << " " << UCIEngine::move(m, pos.is_chess960()); } return ss.str(); diff --git a/src/tune.h b/src/tune.h index b88c085fd4b..079614db28a 100644 --- a/src/tune.h +++ b/src/tune.h @@ -158,7 +158,7 @@ class Tune { for (auto& e : instance().list) e->init_option(); read_options(); - } // Deferred, due to UCI::Options access + } // Deferred, due to UCIEngine::Options access static void read_options() { for (auto& e : instance().list) e->read_option(); diff --git a/src/uci.cpp b/src/uci.cpp index ee95d5be5e6..ed23c00a49d 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -32,6 +32,7 @@ #include #include "benchmark.h" +#include "engine.h" #include "evaluate.h" #include "movegen.h" #include "nnue/network.h" @@ -49,27 +50,19 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; -namespace NN = Eval::NNUE; - - -UCI::UCI(int argc, char** argv) : - networks(NN::Networks( - NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), - NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))), +UCIEngine::UCIEngine(int argc, char** argv) : + engine(argv[0]), cli(argc, argv) { + auto& options = engine.get_options(); + options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); - options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - threads.set({options, threads, tt, networks}); - }); + options["Threads"] << Option(1, 1, 1024, [this](const Option&) { engine.resize_threads(); }); - options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { - threads.main_thread()->wait_for_search_finished(); - tt.resize(o, options["Threads"]); - }); + options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { engine.set_tt_size(o); }); - options["Clear Hash"] << Option([this](const Option&) { search_clear(); }); + options["Clear Hash"] << Option([this](const Option&) { engine.search_clear(); }); options["Ponder"] << Option(false); options["MultiPV"] << Option(1, 1, MAX_MOVES); options["Skill Level"] << Option(20, 0, 20); @@ -83,22 +76,17 @@ UCI::UCI(int argc, char** argv) : options["SyzygyProbeDepth"] << Option(1, 1, 100); options["Syzygy50MoveRule"] << Option(true); options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) { - networks.big.load(cli.binaryDirectory, o); - }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { - networks.small.load(cli.binaryDirectory, o); - }); - - networks.big.load(cli.binaryDirectory, options["EvalFile"]); - networks.small.load(cli.binaryDirectory, options["EvalFileSmall"]); - - threads.set({options, threads, tt, networks}); - - search_clear(); // After threads are up + options["EvalFile"] << Option(EvalFileDefaultNameBig, + [this](const Option& o) { engine.load_big_network(o); }); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, + [this](const Option& o) { engine.load_small_network(o); }); + + engine.load_networks(); + engine.resize_threads(); + engine.search_clear(); // After threads are up } -void UCI::loop() { +void UCIEngine::loop() { Position pos; std::string token, cmd; @@ -121,27 +109,27 @@ void UCI::loop() { is >> std::skipws >> token; if (token == "quit" || token == "stop") - threads.stop = true; + engine.stop(); // The GUI sends 'ponderhit' to tell that the user has played the expected move. // So, 'ponderhit' is sent if pondering was done on the same move that the user // has played. The search should continue, but should also switch from pondering // to the normal search. else if (token == "ponderhit") - threads.main_manager()->ponder = false; // Switch to the normal search + engine.set_ponderhit(false); else if (token == "uci") sync_cout << "id name " << engine_info(true) << "\n" - << options << "\nuciok" << sync_endl; + << engine.get_options() << "\nuciok" << sync_endl; else if (token == "setoption") setoption(is); else if (token == "go") - go(pos, is, states); + go(pos, is); else if (token == "position") - position(pos, is, states); + position(is); else if (token == "ucinewgame") - search_clear(); + engine.search_clear(); else if (token == "isready") sync_cout << "readyok" << sync_endl; @@ -150,11 +138,11 @@ void UCI::loop() { else if (token == "flip") pos.flip(); else if (token == "bench") - bench(pos, is, states); + bench(pos, is); else if (token == "d") sync_cout << pos << sync_endl; else if (token == "eval") - trace_eval(pos); + engine.trace_eval(); else if (token == "compiler") sync_cout << compiler_info() << sync_endl; else if (token == "export_net") @@ -167,8 +155,7 @@ void UCI::loop() { if (is >> std::skipws >> files[1].second) files[1].first = files[1].second; - networks.big.save(files[0].first); - networks.small.save(files[1].first); + engine.save_network(files); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout @@ -186,7 +173,7 @@ void UCI::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } -Search::LimitsType UCI::parse_limits(const Position& pos, std::istream& is) { +Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is) { Search::LimitsType limits; std::string token; @@ -225,23 +212,13 @@ Search::LimitsType UCI::parse_limits(const Position& pos, std::istream& is) { return limits; } -void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { +void UCIEngine::go(Position& pos, std::istringstream& is) { Search::LimitsType limits = parse_limits(pos, is); - - networks.big.verify(options["EvalFile"]); - networks.small.verify(options["EvalFileSmall"]); - - if (limits.perft) - { - perft(pos.fen(), limits.perft, options["UCI_Chess960"]); - return; - } - - threads.start_thinking(options, pos, states, limits); + engine.go(limits); } -void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { +void UCIEngine::bench(Position& pos, std::istream& args) { std::string token; uint64_t num, nodes = 0, cnt = 1; @@ -263,20 +240,20 @@ void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { << std::endl; if (token == "go") { - go(pos, is, states); - threads.main_thread()->wait_for_search_finished(); - nodes += threads.nodes_searched(); + go(pos, is); + engine.wait_for_search_finished(); + nodes += engine.nodes_searched(); } else - trace_eval(pos); + engine.trace_eval(); } else if (token == "setoption") setoption(is); else if (token == "position") - position(pos, is, states); + position(is); else if (token == "ucinewgame") { - search_clear(); // Search::clear() may take a while + engine.search_clear(); // search_clear may take a while elapsed = now(); } } @@ -290,33 +267,13 @@ void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) { << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; } -void UCI::trace_eval(Position& pos) { - StateListPtr states(new std::deque(1)); - Position p; - p.set(pos.fen(), options["UCI_Chess960"], &states->back()); - - networks.big.verify(options["EvalFile"]); - networks.small.verify(options["EvalFileSmall"]); - - sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; +void UCIEngine::setoption(std::istringstream& is) { + engine.wait_for_search_finished(); + engine.get_options().setoption(is); } -void UCI::search_clear() { - threads.main_thread()->wait_for_search_finished(); - - tt.clear(options["Threads"]); - threads.clear(); - Tablebases::init(options["SyzygyPath"]); // Free mapped files -} - -void UCI::setoption(std::istringstream& is) { - threads.main_thread()->wait_for_search_finished(); - options.setoption(is); -} - -void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) { - Move m; +void UCIEngine::position(std::istringstream& is) { std::string token, fen; is >> token; @@ -332,15 +289,14 @@ void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) else return; - states = StateListPtr(new std::deque(1)); // Drop the old state and create a new one - pos.set(fen, options["UCI_Chess960"], &states->back()); + std::vector moves; - // Parse the move list, if any - while (is >> token && (m = to_move(pos, token)) != Move::none()) + while (is >> token) { - states->emplace_back(); - pos.do_move(m, states->back()); + moves.push_back(token); } + + engine.set_position(fen, moves); } namespace { @@ -379,7 +335,7 @@ int win_rate_model(Value v, const Position& pos) { } } -std::string UCI::to_score(Value v, const Position& pos) { +std::string UCIEngine::to_score(Value v, const Position& pos) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); std::stringstream ss; @@ -399,7 +355,7 @@ std::string UCI::to_score(Value v, const Position& pos) { // Turns a Value to an integer centipawn number, // without treatment of mate and similar special scores. -int UCI::to_cp(Value v, const Position& pos) { +int UCIEngine::to_cp(Value v, const Position& pos) { // In general, the score can be defined via the the WDL as // (log(1/L - 1) - log(1/W - 1)) / ((log(1/L - 1) + log(1/W - 1)) @@ -410,7 +366,7 @@ int UCI::to_cp(Value v, const Position& pos) { return std::round(100 * int(v) / a); } -std::string UCI::wdl(Value v, const Position& pos) { +std::string UCIEngine::wdl(Value v, const Position& pos) { std::stringstream ss; int wdl_w = win_rate_model(v, pos); @@ -421,11 +377,11 @@ std::string UCI::wdl(Value v, const Position& pos) { return ss.str(); } -std::string UCI::square(Square s) { +std::string UCIEngine::square(Square s) { return std::string{char('a' + file_of(s)), char('1' + rank_of(s))}; } -std::string UCI::move(Move m, bool chess960) { +std::string UCIEngine::move(Move m, bool chess960) { if (m == Move::none()) return "(none)"; @@ -447,7 +403,7 @@ std::string UCI::move(Move m, bool chess960) { } -Move UCI::to_move(const Position& pos, std::string& str) { +Move UCIEngine::to_move(const Position& pos, std::string str) { if (str.length() == 5) str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased diff --git a/src/uci.h b/src/uci.h index 237928d9abc..c4e90b48d35 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,6 +22,7 @@ #include #include +#include "engine.h" #include "misc.h" #include "nnue/network.h" #include "position.h" @@ -36,9 +37,9 @@ class Move; enum Square : int; using Value = int; -class UCI { +class UCIEngine { public: - UCI(int argc, char** argv); + UCIEngine(int argc, char** argv); void loop(); @@ -47,25 +48,17 @@ class UCI { static std::string square(Square s); static std::string move(Move m, bool chess960); static std::string wdl(Value v, const Position& pos); - static Move to_move(const Position& pos, std::string& str); + static Move to_move(const Position& pos, std::string str); static Search::LimitsType parse_limits(const Position& pos, std::istream& is); - const std::string& working_directory() const { return cli.workingDirectory; } - - OptionsMap options; - Eval::NNUE::Networks networks; - private: - TranspositionTable tt; - ThreadPool threads; - CommandLine cli; - - void go(Position& pos, std::istringstream& is, StateListPtr& states); - void bench(Position& pos, std::istream& args, StateListPtr& states); - void position(Position& pos, std::istringstream& is, StateListPtr& states); - void trace_eval(Position& pos); - void search_clear(); + Engine engine; + CommandLine cli; + + void go(Position& pos, std::istringstream& is); + void bench(Position& pos, std::istream& args); + void position(std::istringstream& is); void setoption(std::istringstream& is); }; From 9032c6cbe74ccf7e8963755501e7e6cc473ae471 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 23 Mar 2024 10:22:20 +0100 Subject: [PATCH 0450/1309] Transform search output to engine callbacks Part 2 of the Split UCI into UCIEngine and Engine refactor. This creates function callbacks for search to use when an update should occur. The benching in uci.cpp for example does this to extract the total nodes searched. No functional change --- src/Makefile | 4 +- src/engine.cpp | 42 +++++++++++-------- src/engine.h | 26 +++++++++--- src/main.cpp | 5 ++- src/score.cpp | 48 +++++++++++++++++++++ src/score.h | 69 ++++++++++++++++++++++++++++++ src/search.cpp | 82 +++++++++++++++++++----------------- src/search.h | 55 +++++++++++++++++++++--- src/thread.cpp | 16 +++---- src/thread.h | 2 +- src/uci.cpp | 112 +++++++++++++++++++++++++++++++++++++++---------- src/uci.h | 17 +++++--- 12 files changed, 373 insertions(+), 105 deletions(-) create mode 100644 src/score.cpp create mode 100644 src/score.h diff --git a/src/Makefile b/src/Makefile index 6315bda82df..550f5404d14 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/engine.cpp b/src/engine.cpp index 79a2c604742..12fa5c3fd02 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -18,21 +18,15 @@ #include "engine.h" -#include -#include -#include -#include -#include -#include #include #include -#include -#include +#include +#include +#include #include -#include "benchmark.h" #include "evaluate.h" -#include "movegen.h" +#include "misc.h" #include "nnue/network.h" #include "nnue/nnue_common.h" #include "perft.h" @@ -40,6 +34,7 @@ #include "search.h" #include "syzygy/tbprobe.h" #include "types.h" +#include "uci.h" #include "ucioption.h" namespace Stockfish { @@ -54,7 +49,6 @@ Engine::Engine(std::string path) : networks(NN::Networks( NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { - Tune::init(options); pos.set(StartFEN, false, &states->back()); } @@ -77,10 +71,26 @@ void Engine::search_clear() { tt.clear(options["Threads"]); threads.clear(); - // @TODO wont work multiple instances + // @TODO wont work with multiple instances Tablebases::init(options["SyzygyPath"]); // Free mapped files } +void Engine::set_on_update_no_moves(std::function&& f) { + updateContext.onUpdateNoMoves = std::move(f); +} + +void Engine::set_on_update_full(std::function&& f) { + updateContext.onUpdateFull = std::move(f); +} + +void Engine::set_on_iter(std::function&& f) { + updateContext.onIter = std::move(f); +} + +void Engine::set_on_bestmove(std::function&& f) { + updateContext.onBestmove = std::move(f); +} + void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } void Engine::set_position(const std::string& fen, const std::vector& moves) { @@ -102,7 +112,7 @@ void Engine::set_position(const std::string& fen, const std::vector // modifiers -void Engine::resize_threads() { threads.set({options, threads, tt, networks}); } +void Engine::resize_threads() { threads.set({options, threads, tt, networks}, updateContext); } void Engine::set_tt_size(size_t mb) { wait_for_search_finished(); @@ -113,7 +123,7 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } // network related -void Engine::verify_networks() { +void Engine::verify_networks() const { networks.big.verify(options["EvalFile"]); networks.small.verify(options["EvalFileSmall"]); } @@ -138,9 +148,7 @@ void Engine::save_network(const std::pair, std::strin OptionsMap& Engine::get_options() { return options; } -uint64_t Engine::nodes_searched() const { return threads.nodes_searched(); } - -void Engine::trace_eval() { +void Engine::trace_eval() const { StateListPtr trace_states(new std::deque(1)); Position p; p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back()); diff --git a/src/engine.h b/src/engine.h index 6afc423de93..f74209d9095 100644 --- a/src/engine.h +++ b/src/engine.h @@ -19,7 +19,14 @@ #ifndef ENGINE_H_INCLUDED #define ENGINE_H_INCLUDED -#include "misc.h" +#include +#include +#include +#include +#include +#include +#include + #include "nnue/network.h" #include "position.h" #include "search.h" @@ -31,6 +38,10 @@ namespace Stockfish { class Engine { public: + using InfoShort = Search::InfoShort; + using InfoFull = Search::InfoFull; + using InfoIter = Search::InfoIteration; + Engine(std::string path = ""); ~Engine() { wait_for_search_finished(); } @@ -51,9 +62,14 @@ class Engine { void set_ponderhit(bool); void search_clear(); + void set_on_update_no_moves(std::function&&); + void set_on_update_full(std::function&&); + void set_on_iter(std::function&&); + void set_on_bestmove(std::function&&); + // network related - void verify_networks(); + void verify_networks() const; void load_networks(); void load_big_network(const std::string& file); void load_small_network(const std::string& file); @@ -61,9 +77,7 @@ class Engine { // utility functions - void trace_eval(); - // nodes since last search clear - uint64_t nodes_searched() const; + void trace_eval() const; OptionsMap& get_options(); private: @@ -76,6 +90,8 @@ class Engine { ThreadPool threads; TranspositionTable tt; Eval::NNUE::Networks networks; + + Search::SearchManager::UpdateContext updateContext; }; } // namespace Stockfish diff --git a/src/main.cpp b/src/main.cpp index 4e72c00398a..a6a3d1c4e85 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,9 @@ #include "bitboard.h" #include "misc.h" #include "position.h" -#include "tune.h" #include "types.h" #include "uci.h" +#include "tune.h" using namespace Stockfish; @@ -35,6 +35,9 @@ int main(int argc, char* argv[]) { Position::init(); UCIEngine uci(argc, argv); + + Tune::init(uci.engine_options()); + uci.loop(); return 0; diff --git a/src/score.cpp b/src/score.cpp new file mode 100644 index 00000000000..d1a8a6abe4d --- /dev/null +++ b/src/score.cpp @@ -0,0 +1,48 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "score.h" + +#include +#include +#include + +#include "uci.h" + +namespace Stockfish { + +Score::Score(Value v, const Position& pos) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + + if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + { + score = InternalUnits{UCIEngine::to_cp(v, pos)}; + } + else if (std::abs(v) <= VALUE_TB) + { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? TBWin{distance} : TBWin{-distance}; + } + else + { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{distance} : Mate{-distance}; + } +} + +} \ No newline at end of file diff --git a/src/score.h b/src/score.h new file mode 100644 index 00000000000..b94d9f7fb6b --- /dev/null +++ b/src/score.h @@ -0,0 +1,69 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SCORE_H_INCLUDED +#define SCORE_H_INCLUDED + +#include +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +class Score { + public: + struct Mate { + int plies; + }; + + struct TBWin { + int plies; + }; + + struct InternalUnits { + int value; + }; + + Score() = default; + Score(Value v, const Position& pos); + + template + bool is() const { + return std::holds_alternative(score); + } + + template + T get() const { + return std::get(score); + } + + template + decltype(auto) visit(F&& f) const { + return std::visit(std::forward(f), score); + } + + private: + std::variant score; +}; + +} + +#endif // #ifndef SCORE_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index efc00750613..51cd1ae11dc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -26,8 +26,7 @@ #include #include #include -#include -#include +#include #include #include "evaluate.h" @@ -157,9 +156,8 @@ void Search::Worker::start_searching() { if (rootMoves.empty()) { rootMoves.emplace_back(Move::none()); - sync_cout << "info depth 0 score " - << UCIEngine::to_score(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos) - << sync_endl; + main_manager()->updates.onUpdateNoMoves( + {0, {rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW, rootPos}}); } else { @@ -201,18 +199,16 @@ void Search::Worker::start_searching() { // Send again PV info if we have a new best thread if (bestThread != this) - sync_cout << main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth) - << sync_endl; + main_manager()->pv(*bestThread, threads, tt, bestThread->completedDepth); - sync_cout << "bestmove " - << UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + std::string ponder; if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) - std::cout << " ponder " - << UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + ponder = UCIEngine::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); - std::cout << sync_endl; + auto bestmove = UCIEngine::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + main_manager()->updates.onBestmove(bestmove, ponder); } // Main iterative deepening loop. It calls search() @@ -345,7 +341,7 @@ void Search::Worker::iterative_deepening() { // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) - sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; + main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. @@ -382,7 +378,7 @@ void Search::Worker::iterative_deepening() { // had time to fully search other root-moves. Thus we suppress this output and // below pick a proven score/PV for this thread (from the previous iteration). && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) - sync_cout << main_manager()->pv(*this, threads, tt, rootDepth) << sync_endl; + main_manager()->pv(*this, threads, tt, rootDepth); } if (!threads.stop) @@ -934,9 +930,10 @@ Value Search::Worker::search( if (rootNode && is_mainthread() && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) - sync_cout << "info depth " << depth << " currmove " - << UCIEngine::move(move, pos.is_chess960()) << " currmovenumber " - << moveCount + thisThread->pvIdx << sync_endl; + { + main_manager()->updates.onIter( + {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); + } if (PvNode) (ss + 1)->pv = nullptr; @@ -1871,11 +1868,10 @@ void SearchManager::check_time(Search::Worker& worker) { worker.threads.stop = worker.threads.abortedSearch = true; } -std::string SearchManager::pv(const Search::Worker& worker, - const ThreadPool& threads, - const TranspositionTable& tt, - Depth depth) const { - std::stringstream ss; +void SearchManager::pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const { const auto nodes = threads.nodes_searched(); const auto& rootMoves = worker.rootMoves; @@ -1901,29 +1897,39 @@ std::string SearchManager::pv(const Search::Worker& worker, bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB; v = tb ? rootMoves[i].tbScore : v; - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; + std::string pv; + for (Move m : rootMoves[i].pv) + pv += UCIEngine::move(m, pos.is_chess960()) + " "; + + // remove last whitespace + if (!pv.empty()) + pv.pop_back(); - ss << "info" - << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCIEngine::to_score(v, pos); + auto wdl = worker.options["UCI_ShowWDL"] ? UCIEngine::wdl(v, pos) : ""; + auto bound = rootMoves[i].scoreLowerbound + ? "lowerbound" + : (rootMoves[i].scoreUpperbound ? "upperbound" : ""); - if (worker.options["UCI_ShowWDL"]) - ss << UCIEngine::wdl(v, pos); + InfoFull info; + + info.depth = d; + info.selDepth = rootMoves[i].selDepth; + info.multiPV = i + 1; + info.score = {v, pos}; + info.wdl = wdl; if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact - ss << (rootMoves[i].scoreLowerbound - ? " lowerbound" - : (rootMoves[i].scoreUpperbound ? " upperbound" : "")); + info.bound = bound; - ss << " nodes " << nodes << " nps " << nodes * 1000 / time << " hashfull " << tt.hashfull() - << " tbhits " << tbHits << " time " << time << " pv"; + info.timeMs = time; + info.nodes = nodes; + info.nps = nodes * 1000 / time; + info.tbHits = tbHits; + info.pv = pv; + info.hashfull = tt.hashfull(); - for (Move m : rootMoves[i].pv) - ss << " " << UCIEngine::move(m, pos.is_chess960()); + updates.onUpdateFull(info); } - - return ss.str(); } // Called in case we have no ponder move before exiting the search, diff --git a/src/search.h b/src/search.h index 22f75ffd4d8..d1464840310 100644 --- a/src/search.h +++ b/src/search.h @@ -24,13 +24,15 @@ #include #include #include +#include #include -#include +#include #include #include "misc.h" #include "movepick.h" #include "position.h" +#include "score.h" #include "syzygy/tbprobe.h" #include "timeman.h" #include "types.h" @@ -139,7 +141,6 @@ struct SharedState { tt(transpositionTable), networks(nets) {} - const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; @@ -156,16 +157,56 @@ class ISearchManager { virtual void check_time(Search::Worker&) = 0; }; +struct InfoShort { + int depth; + Score score; +}; + +struct InfoFull: InfoShort { + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string_view pv; + int hashfull; +}; + +struct InfoIteration { + int depth; + std::string_view currmove; + size_t currmovenumber; +}; + // SearchManager manages the search from the main thread. It is responsible for // keeping track of the time, and storing data strictly related to the main thread. class SearchManager: public ISearchManager { public: + using UpdateShort = std::function; + using UpdateFull = std::function; + using UpdateIter = std::function; + using UpdateBestmove = std::function; + + struct UpdateContext { + UpdateShort onUpdateNoMoves; + UpdateFull onUpdateFull; + UpdateIter onIter; + UpdateBestmove onBestmove; + }; + + + SearchManager(const UpdateContext& updateContext) : + updates(updateContext) {} + void check_time(Search::Worker& worker) override; - std::string pv(const Search::Worker& worker, - const ThreadPool& threads, - const TranspositionTable& tt, - Depth depth) const; + void pv(const Search::Worker& worker, + const ThreadPool& threads, + const TranspositionTable& tt, + Depth depth) const; Stockfish::TimeManagement tm; int callsCnt; @@ -178,6 +219,8 @@ class SearchManager: public ISearchManager { bool stopOnPonderhit; size_t id; + + const UpdateContext& updates; }; class NullSearchManager: public ISearchManager { diff --git a/src/thread.cpp b/src/thread.cpp index 90add4ad059..85a2bcbb167 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -119,7 +119,8 @@ uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. -void ThreadPool::set(Search::SharedState sharedState) { +void ThreadPool::set(Search::SharedState sharedState, + const Search::SearchManager::UpdateContext& updateContext) { if (threads.size() > 0) // destroy any existing thread(s) { @@ -133,14 +134,15 @@ void ThreadPool::set(Search::SharedState sharedState) { if (requested > 0) // create new thread(s) { - threads.push_back(new Thread( - sharedState, std::unique_ptr(new Search::SearchManager()), 0)); - + auto manager = std::make_unique(updateContext); + threads.push_back(new Thread(sharedState, std::move(manager), 0)); while (threads.size() < requested) - threads.push_back(new Thread( - sharedState, std::unique_ptr(new Search::NullSearchManager()), - threads.size())); + { + auto null_manager = std::make_unique(); + threads.push_back(new Thread(sharedState, std::move(null_manager), threads.size())); + } + clear(); main_thread()->wait_for_search_finished(); diff --git a/src/thread.h b/src/thread.h index 81fcc72a7ee..223652aec99 100644 --- a/src/thread.h +++ b/src/thread.h @@ -82,7 +82,7 @@ class ThreadPool { void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); void clear(); - void set(Search::SharedState); + void set(Search::SharedState, const Search::SearchManager::UpdateContext&); Search::SearchManager* main_manager(); Thread* main_thread() const { return threads.front(); } diff --git a/src/uci.cpp b/src/uci.cpp index ed23c00a49d..d6936d38b23 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -19,15 +19,14 @@ #include "uci.h" #include -#include #include #include #include -#include #include #include #include #include +#include #include #include @@ -35,10 +34,8 @@ #include "engine.h" #include "evaluate.h" #include "movegen.h" -#include "nnue/network.h" -#include "nnue/nnue_common.h" -#include "perft.h" #include "position.h" +#include "score.h" #include "search.h" #include "syzygy/tbprobe.h" #include "types.h" @@ -49,6 +46,13 @@ namespace Stockfish { constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +template +struct overload: Ts... { + using Ts::operator()...; +}; + +template +overload(Ts...) -> overload; UCIEngine::UCIEngine(int argc, char** argv) : engine(argv[0]), @@ -81,6 +85,12 @@ UCIEngine::UCIEngine(int argc, char** argv) : options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { engine.load_small_network(o); }); + + engine.set_on_iter([](const auto& i) { on_iter(i); }); + engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); }); + engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); + engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); }); + engine.load_networks(); engine.resize_threads(); engine.search_clear(); // After threads are up @@ -221,6 +231,13 @@ void UCIEngine::go(Position& pos, std::istringstream& is) { void UCIEngine::bench(Position& pos, std::istream& args) { std::string token; uint64_t num, nodes = 0, cnt = 1; + uint64_t nodesSearched = 0; + const auto& options = engine.get_options(); + + engine.set_on_update_full([&](const auto& i) { + nodesSearched = i.nodes; + on_update_full(i, options["UCI_ShowWDL"]); + }); std::vector list = setup_bench(pos, args); @@ -242,7 +259,8 @@ void UCIEngine::bench(Position& pos, std::istream& args) { { go(pos, is); engine.wait_for_search_finished(); - nodes += engine.nodes_searched(); + nodes += nodesSearched; + nodesSearched = 0; } else engine.trace_eval(); @@ -265,6 +283,9 @@ void UCIEngine::bench(Position& pos, std::istream& args) { std::cerr << "\n===========================" << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; + + // reset callback, to not capture a dangling reference to nodesSearched + engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); } @@ -335,22 +356,22 @@ int win_rate_model(Value v, const Position& pos) { } } -std::string UCIEngine::to_score(Value v, const Position& pos) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - - std::stringstream ss; - - if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) - ss << "cp " << to_cp(v, pos); - else if (std::abs(v) <= VALUE_TB) - { - const int ply = VALUE_TB - std::abs(v); // recompute ss->ply - ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply); - } - else - ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; - - return ss.str(); +std::string UCIEngine::format_score(const Score& s) { + constexpr int TB_CP = 20000; + const auto format = + overload{[](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : -mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::TBWin tb) -> std::string { + return std::string("cp ") + + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP + tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { + return std::string("cp ") + std::to_string(units.value); + }}; + + return s.visit(format); } // Turns a Value to an integer centipawn number, @@ -414,4 +435,51 @@ Move UCIEngine::to_move(const Position& pos, std::string str) { return Move::none(); } +void UCIEngine::on_update_no_moves(const Engine::InfoShort& info) { + sync_cout << "info depth" << info.depth << " score " << format_score(info.score) << sync_endl; +} + +void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) { + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // + + if (showWDL) + ss << " wdl " << info.wdl; + + if (!info.bound.empty()) + ss << " " << info.bound; + + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // + + sync_cout << ss.str() << sync_endl; +} + +void UCIEngine::on_iter(const Engine::InfoIter& info) { + std::stringstream ss; + + ss << "info"; + ss << " depth " << info.depth // + << " currmove " << info.currmove // + << " currmovenumber " << info.currmovenumber; // + + sync_cout << ss.str() << sync_endl; +} + +void UCIEngine::on_bestmove(std::string_view bestmove, std::string_view ponder) { + sync_cout << "bestmove " << bestmove; + if (!ponder.empty()) + std::cout << " ponder " << ponder; + std::cout << sync_endl; +} + } // namespace Stockfish diff --git a/src/uci.h b/src/uci.h index c4e90b48d35..fa8c57fd912 100644 --- a/src/uci.h +++ b/src/uci.h @@ -21,19 +21,17 @@ #include #include +#include #include "engine.h" #include "misc.h" -#include "nnue/network.h" -#include "position.h" #include "search.h" -#include "thread.h" -#include "tt.h" -#include "ucioption.h" namespace Stockfish { +class Position; class Move; +class Score; enum Square : int; using Value = int; @@ -44,7 +42,7 @@ class UCIEngine { void loop(); static int to_cp(Value v, const Position& pos); - static std::string to_score(Value v, const Position& pos); + static std::string format_score(const Score& s); static std::string square(Square s); static std::string move(Move m, bool chess960); static std::string wdl(Value v, const Position& pos); @@ -52,6 +50,8 @@ class UCIEngine { static Search::LimitsType parse_limits(const Position& pos, std::istream& is); + auto& engine_options() { return engine.get_options(); } + private: Engine engine; CommandLine cli; @@ -60,6 +60,11 @@ class UCIEngine { void bench(Position& pos, std::istream& args); void position(std::istringstream& is); void setoption(std::istringstream& is); + + static void on_update_no_moves(const Engine::InfoShort& info); + static void on_update_full(const Engine::InfoFull& info, bool showWDL); + static void on_iter(const Engine::InfoIter& info); + static void on_bestmove(std::string_view bestmove, std::string_view ponder); }; } // namespace Stockfish From 1adf8e1ae662f2eecc5d652ece7b9f53014cf057 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 6 Apr 2024 22:39:29 +0800 Subject: [PATCH 0451/1309] VVLTC search tune MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parameters were tuned in 3 stages: * Using an earlier L1-3072 net, and with triple extension margin manually set to 0: https://tests.stockfishchess.org/tests/view/65ffdf5d0ec64f0526c544f2 (~30k games) * Continue tuning, but with the previous master net (L1-2560). https://tests.stockfishchess.org/tests/view/660663f00ec64f0526c59c41 (~27k games) * Starting with the parameters from step 2, use the current L1-3072 net, and allow the triple extension margin to be tuned starting from 0: https://tests.stockfishchess.org/tests/view/660c16b8216a13d9498e7536 (40k games) Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/66115eacbfeb43334bf7eddd LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 27138 W: 7045 L: 6789 D: 13304 Ptnml(0-2): 1, 2421, 8471, 2673, 3 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/661483623eb00c8ccc0049c1 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 26242 W: 6807 L: 6535 D: 12900 Ptnml(0-2): 0, 2353, 8143, 2625, 0 STC Elo estimate: https://tests.stockfishchess.org/tests/view/66175ca55a4693796d96608c Elo: -10.53 ± 2.4 (95%) LOS: 0.0% Total: 21584 W: 5294 L: 5948 D: 10342 Ptnml(0-2): 102, 2937, 5363, 2293, 97 nElo: -19.99 ± 4.7 (95%) PairsRatio: 0.79 closes https://github.com/official-stockfish/Stockfish/pull/5162 Bench: 1381387 --- src/evaluate.h | 2 +- src/search.cpp | 76 +++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 97ca4c4b665..da9c7074ec1 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1165, PsqtOnlyThreshold = 2500; +constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/search.cpp b/src/search.cpp index 51cd1ae11dc..a131c958e7b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -58,8 +58,8 @@ static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 118 - 44 * noTtCutNode; - Value improvingDeduction = 53 * improving * futilityMult / 32; - Value worseningDeduction = (309 + 47 * improving) * oppWorsening * futilityMult / 1024; + Value improvingDeduction = 52 * improving * futilityMult / 32; + Value worseningDeduction = (310 + 48 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -71,15 +71,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 11175; + v += cv * std::abs(cv) / 9260; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(245 * d - 320, 0, 1296); } +int stat_bonus(Depth d) { return std::clamp(211 * d - 315, 0, 1291); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 554 * d - 303 : 1203); } +int stat_malus(Depth d) { return (d < 4 ? 572 * d - 285 : 1372); } // 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); } @@ -303,12 +303,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 12493; + delta = 11 + avg * avg / 11254; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 132 * avg / (std::abs(avg) + 89); + optimism[us] = 125 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -496,10 +496,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-67); + h->fill(-65); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((19.80 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((20.14 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); } @@ -731,7 +731,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1578, 1291); + int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1644, 1384); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -754,7 +754,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 488 - (289 - 142 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 471 - (276 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -765,21 +765,21 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 267 + - (ss - 1)->statScore / 284 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16878 - && eval >= beta && ss->staticEval >= beta - 20 * depth + 314 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001 + && eval >= beta && ss->staticEval >= beta - 21 * depth + 315 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -827,7 +827,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 170 - 64 * improving; + probCutBeta = beta + 169 - 63 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -883,7 +883,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 409; + probCutBeta = beta + 436; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -967,7 +967,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.piece_on(move.to_sq()); int futilityEval = - ss->staticEval + 297 + 284 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 287 + 277 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityEval < alpha) @@ -975,7 +975,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -203 * depth)) + if (!pos.see_ge(move, -199 * depth)) continue; } else @@ -987,15 +987,15 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4040 * depth) + if (lmrDepth < 6 && history < -4173 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 5637; + lmrDepth += history / 5285; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 59 ? 141 : 58) + 125 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 58) + 131 * lmrDepth; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) @@ -1009,7 +1009,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1029,11 +1029,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 30) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (58 + 58 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (64 + 59 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1048,11 +1048,11 @@ Value Search::Worker::search( // We make sure to limit the extensions in some way to avoid a search explosion if (!PvNode && ss->multipleExtensions <= 16) { - extension = 2 + (value < singularBeta - 22 && !ttCapture); + extension = 2 + (value < singularBeta - 11 && !ttCapture); depth += depth < 14; } if (PvNode && !ttCapture && ss->multipleExtensions <= 5 - && value < singularBeta - 37) + && value < singularBeta - 38) extension = 2; } @@ -1087,7 +1087,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4026) + > 3807) extension = 1; } @@ -1137,10 +1137,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 4723; + + (*contHist[3])[movedPiece][move.to_sq()] - 5007; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 13659; + r -= ss->statScore / 12901; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1159,7 +1159,7 @@ Value Search::Worker::search( { // 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 + 47 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1277,7 +1277,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 14206 && value > -12077) + if (depth > 2 && depth < 12 && beta < 13132 && value > -13295) depth -= 2; assert(depth > 0); @@ -1320,9 +1320,9 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14963) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) + ((ss - 1)->moveCount > 11) - + (!ss->inCheck && bestValue <= ss->staticEval - 150); + + (!ss->inCheck && bestValue <= ss->staticEval - 144); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1480,7 +1480,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 226; + futilityBase = ss->staticEval + 246; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1560,7 +1560,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -78)) + if (!pos.see_ge(move, -79)) continue; } @@ -1628,7 +1628,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1107 - delta * 725 / rootDelta) / 1024 + (!i && reductionScale > 956); + return (reductionScale + 1123 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); } namespace { @@ -1717,7 +1717,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 168 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 185 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus // Increase stats for the best move in case it was a quiet move From 94484db6e83ad791b8782fd120f32db2ab72bf11 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 8 Apr 2024 13:07:41 -0700 Subject: [PATCH 0452/1309] Avoid permuting inputs during transform() Avoid permuting inputs during transform() and instead do it once at load time. Affects AVX2 and newer Intel architectures only. https://tests.stockfishchess.org/tests/view/661306613eb00c8ccc0033c7 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 108480 W: 28319 L: 27898 D: 52263 Ptnml(0-2): 436, 12259, 28438, 12662, 445 speedups measured such as e.g. ``` Result of 100 runs ================== base (./stockfish.master ) = 1241128 +/- 3757 test (./stockfish.patch ) = 1247713 +/- 3689 diff = +6585 +/- 2583 speedup = +0.0053 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/5160 No functional change --- src/nnue/nnue_feature_transformer.h | 78 +++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 888edebbdb3..3101c8d2689 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -60,10 +60,9 @@ using psqt_vec_t = __m256i; #define vec_set_16(a) _mm512_set1_epi16(a) #define vec_max_16(a, b) _mm512_max_epi16(a, b) #define vec_min_16(a, b) _mm512_min_epi16(a, b) -inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { - vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)); - return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted); -} + // Inverse permuted at load time + #define vec_msb_pack_16(a, b) \ + _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a, b) _mm256_store_si256(a, b) #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) @@ -84,10 +83,9 @@ using psqt_vec_t = __m256i; #define vec_set_16(a) _mm256_set1_epi16(a) #define vec_max_16(a, b) _mm256_max_epi16(a, b) #define vec_min_16(a, b) _mm256_min_epi16(a, b) -inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { - vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)); - return _mm256_permute4x64_epi64(compacted, 0b11011000); -} + // Inverse permuted at load time + #define vec_msb_pack_16(a, b) \ + _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a, b) _mm256_store_si256(a, b) #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) @@ -229,6 +227,62 @@ class FeatureTransformer { 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, tmp1; + 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 + } + + static constexpr void inverse_order_packs([[maybe_unused]] uint64_t* v) { +#if defined(USE_AVX512) // Inverse _mm512_packs_epi16 ordering + uint64_t tmp0, tmp1; + 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*)) const { +#if defined(USE_AVX2) + #if defined(USE_AVX512) + constexpr IndexType di = 16; + #else + constexpr IndexType di = 8; + #endif + uint64_t* b = reinterpret_cast(const_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(const_cast(&weights[j * HalfDimensions])); + for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(uint64_t); + i += di) + order_fn(&w[i]); + } +#endif + } + // Read network parameters bool read_parameters(std::istream& stream) { @@ -236,16 +290,20 @@ class FeatureTransformer { read_leb_128(stream, weights, HalfDimensions * InputDimensions); read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + permute_weights(inverse_order_packs); return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { + permute_weights(order_packs); + 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); return !stream.fail(); } @@ -276,8 +334,8 @@ class FeatureTransformer { static_assert((HalfDimensions / 2) % OutputChunkSize == 0); constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; - vec_t Zero = vec_zero(); - vec_t One = vec_set_16(127); + const vec_t Zero = vec_zero(); + const vec_t One = vec_set_16(127); const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); const vec_t* in1 = From de2244284b301c5bf15b248b5e3538aee92bb295 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 5 Apr 2024 11:34:11 +0200 Subject: [PATCH 0453/1309] Remove COMPILER from Makefile The same functionality is available by using COMPCXX and having another variable which does the same is just confusing. There was only one mention on Stockfish Wiki about this which has been changed to COMPCXX. closes https://github.com/official-stockfish/Stockfish/pull/5154 No functional change --- .github/workflows/arm_compilation.yml | 4 ++-- .github/workflows/compilation.yml | 6 +++--- .github/workflows/sanitizers.yml | 4 ++-- .github/workflows/tests.yml | 6 +++--- .github/workflows/upload_binaries.yml | 2 +- src/Makefile | 5 ----- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/.github/workflows/arm_compilation.yml b/.github/workflows/arm_compilation.yml index ef141971d2b..3934ac2d636 100644 --- a/.github/workflows/arm_compilation.yml +++ b/.github/workflows/arm_compilation.yml @@ -10,7 +10,7 @@ jobs: name: ${{ matrix.config.name }} ${{ matrix.binaries }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EMU: ${{ matrix.config.emu }} EXT: ${{ matrix.config.ext }} @@ -62,7 +62,7 @@ jobs: if [ $COMP == ndk ]; then export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH fi - $COMPILER -v + $COMPCXX -v - name: Test help target run: make help diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 964b5f05edc..3524d5e9f2e 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -10,7 +10,7 @@ jobs: name: ${{ matrix.config.name }} ${{ matrix.binaries }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} NAME: ${{ matrix.config.simple_name }} @@ -50,7 +50,7 @@ jobs: run: make net - name: Check compiler - run: $COMPILER -v + run: $COMPCXX -v - name: Test help target run: make help @@ -59,7 +59,7 @@ jobs: run: git --version - name: Check compiler - run: $COMPILER -v + run: $COMPCXX -v - name: Show g++ cpu info if: runner.os != 'macOS' diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 7ab1f997ad7..612f1275ce7 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -6,7 +6,7 @@ jobs: name: ${{ matrix.sanitizers.name }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: @@ -47,7 +47,7 @@ jobs: run: make net - name: Check compiler - run: $COMPILER -v + run: $COMPCXX -v - name: Test help target run: make help diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 702e86e5f74..328c9cf94b1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ jobs: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: @@ -172,9 +172,9 @@ jobs: if [ $COMP == ndk ]; then export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH fi - $COMPILER -v + $COMPCXX -v else - echo "$COMPILER -v" > script.sh + echo "$COMPCXX -v" > script.sh docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder fi diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 015b514ce4b..acf91a8f331 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -11,7 +11,7 @@ jobs: name: ${{ matrix.config.name }} ${{ matrix.binaries }} runs-on: ${{ matrix.config.os }} env: - COMPILER: ${{ matrix.config.compiler }} + COMPCXX: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} NAME: ${{ matrix.config.simple_name }} diff --git a/src/Makefile b/src/Makefile index 550f5404d14..45f38b01322 100644 --- a/src/Makefile +++ b/src/Makefile @@ -546,11 +546,6 @@ else endif endif -### Travis CI script uses COMPILER to overwrite CXX -ifdef COMPILER - COMPCXX=$(COMPILER) -endif - ### Allow overwriting CXX from command line ifdef COMPCXX CXX=$(COMPCXX) From d6bdcec52c33a67970721c4443136b42265a6148 Mon Sep 17 00:00:00 2001 From: gab8192 Date: Wed, 3 Apr 2024 23:41:24 +0200 Subject: [PATCH 0454/1309] Remove an useless assignment The assignment (ss + 1)->excludedMove = Move::none() can be simplified away because when that line is reached, (ss + 1)->excludedMove is always already none. The only moment stack[x]->excludedMove is modified, is during singular search, but it is reset to none right after the singular search is finished. closes https://github.com/official-stockfish/Stockfish/pull/5153 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a131c958e7b..3d84eb36017 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -585,7 +585,7 @@ Value Search::Worker::search( assert(0 <= ss->ply && ss->ply < MAX_PLY); - (ss + 1)->excludedMove = bestMove = Move::none(); + bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; ss->multipleExtensions = (ss - 1)->multipleExtensions; From 249eec67152d334d76c0f981907a6f5787289443 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 11 Apr 2024 14:00:46 +0300 Subject: [PATCH 0455/1309] Simplify the depth-dependent part of the best value adjustment formula in main search Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 139648 W: 36171 L: 36061 D: 67416 Ptnml(0-2): 545, 16685, 35282, 16739, 573 https://tests.stockfishchess.org/tests/view/660d953b8ff4a059828d625d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 222894 W: 56519 L: 56505 D: 109870 Ptnml(0-2): 112, 25145, 60971, 25055, 164 https://tests.stockfishchess.org/tests/view/660fd4afbfeb43334bf7d558 closes https://github.com/official-stockfish/Stockfish/pull/5164 bench: 1479416 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3d84eb36017..24805aa70ea 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -585,7 +585,7 @@ Value Search::Worker::search( assert(0 <= ss->ply && ss->ply < MAX_PLY); - bestMove = Move::none(); + bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; ss->multipleExtensions = (ss - 1)->multipleExtensions; @@ -1307,7 +1307,7 @@ Value Search::Worker::search( // Adjust best value for fail high cases at non-pv nodes if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) - bestValue = (bestValue * (depth + 2) + beta) / (depth + 3); + bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; From e58b3b4665469a793a0976d7a28f61fcd771b565 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Fri, 12 Apr 2024 08:39:39 +0200 Subject: [PATCH 0456/1309] Fix wrong mate sign introduced yesterday by the UCI refactoring 9032c6cbe fixes #5166 closes https://github.com/official-stockfish/Stockfish/pull/5167 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index d6936d38b23..a328ccb0868 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -360,12 +360,12 @@ std::string UCIEngine::format_score(const Score& s) { constexpr int TB_CP = 20000; const auto format = overload{[](Score::Mate mate) -> std::string { - auto m = (mate.plies > 0 ? (mate.plies + 1) : -mate.plies) / 2; + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; return std::string("mate ") + std::to_string(m); }, [](Score::TBWin tb) -> std::string { return std::string("cp ") - + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP + tb.plies)); + + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP - tb.plies)); }, [](Score::InternalUnits units) -> std::string { return std::string("cp ") + std::to_string(units.value); From 14f6eab07d1d1e1a59372974e5534128676e9440 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:32:31 +0300 Subject: [PATCH 0457/1309] Fix some more UCI output further fall-out of the refactoring, fixes: * the position object in UCI is not never getting updated if position token is used * duplicate string of " wdl " See also: https://discord.com/channels/435943710472011776/1032922913499783169/1228227522945351690 https://discord.com/channels/435943710472011776/813919248455827515/1228288106449338398 closes https://github.com/official-stockfish/Stockfish/pull/5168 No functional change Co-Authored-By: disservin <45608332+disservin@users.noreply.github.com> --- src/uci.cpp | 9 +++++---- src/uci.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index a328ccb0868..a15bc7d4b5c 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -137,7 +137,7 @@ void UCIEngine::loop() { else if (token == "go") go(pos, is); else if (token == "position") - position(is); + position(pos, is); else if (token == "ucinewgame") engine.search_clear(); else if (token == "isready") @@ -268,7 +268,7 @@ void UCIEngine::bench(Position& pos, std::istream& args) { else if (token == "setoption") setoption(is); else if (token == "position") - position(is); + position(pos, is); else if (token == "ucinewgame") { engine.search_clear(); // search_clear may take a while @@ -294,7 +294,7 @@ void UCIEngine::setoption(std::istringstream& is) { engine.get_options().setoption(is); } -void UCIEngine::position(std::istringstream& is) { +void UCIEngine::position(Position& pos, std::istringstream& is) { std::string token, fen; is >> token; @@ -317,6 +317,7 @@ void UCIEngine::position(std::istringstream& is) { moves.push_back(token); } + pos.set(fen, engine.get_options()["UCI_Chess960"], pos.state()); engine.set_position(fen, moves); } @@ -393,7 +394,7 @@ std::string UCIEngine::wdl(Value v, const Position& pos) { int wdl_w = win_rate_model(v, pos); int wdl_l = win_rate_model(-v, pos); int wdl_d = 1000 - wdl_w - wdl_l; - ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + ss << wdl_w << " " << wdl_d << " " << wdl_l; return ss.str(); } diff --git a/src/uci.h b/src/uci.h index fa8c57fd912..fa359db4549 100644 --- a/src/uci.h +++ b/src/uci.h @@ -58,7 +58,7 @@ class UCIEngine { void go(Position& pos, std::istringstream& is); void bench(Position& pos, std::istream& args); - void position(std::istringstream& is); + void position(Position& pos, std::istringstream& is); void setoption(std::istringstream& is); static void on_update_no_moves(const Engine::InfoShort& info); From 4912f5b0b5f2656bc5fcdb0af480765ad5aa8932 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 12 Apr 2024 19:11:10 +0200 Subject: [PATCH 0458/1309] Remove duplicated Position object in UCIEngine Also fixes searchmoves. Drop the need of a Position object in uci.cpp. A side note, it is still required for the static functions, but these should be moved to a different namespace/class later on, since sf kinda relies on them. closes https://github.com/official-stockfish/Stockfish/pull/5169 No functional change --- src/benchmark.cpp | 6 ++---- src/benchmark.h | 4 +--- src/engine.cpp | 16 ++++++++++++++-- src/engine.h | 3 +++ src/search.h | 11 ++++++----- src/thread.cpp | 16 +++++++++++++--- src/uci.cpp | 48 +++++++++++++++++++++++------------------------ src/uci.h | 9 +++++---- 8 files changed, 67 insertions(+), 46 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 50f8612d912..267a6b4b2a9 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -23,8 +23,6 @@ #include #include -#include "position.h" - namespace { // clang-format off @@ -108,7 +106,7 @@ namespace Stockfish { // bench 64 1 100000 default nodes : search default positions for 100K nodes each // bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec // bench 16 1 5 blah perft : run a perft 5 on positions in file "blah" -std::vector setup_bench(const Position& current, std::istream& is) { +std::vector setup_bench(const std::string& currentFen, std::istream& is) { std::vector fens, list; std::string go, token; @@ -126,7 +124,7 @@ std::vector setup_bench(const Position& current, std::istream& is) fens = Defaults; else if (fenFile == "current") - fens.push_back(current.fen()); + fens.push_back(currentFen); else { diff --git a/src/benchmark.h b/src/benchmark.h index 86f8a0ad50b..8905fcb1825 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -25,9 +25,7 @@ namespace Stockfish { -class Position; - -std::vector setup_bench(const Position&, std::istream&); +std::vector setup_bench(const std::string&, std::istream&); } // namespace Stockfish diff --git a/src/engine.cpp b/src/engine.cpp index 12fa5c3fd02..325b971ea46 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "evaluate.h" #include "misc.h" @@ -146,8 +148,6 @@ void Engine::save_network(const std::pair, std::strin // utility functions -OptionsMap& Engine::get_options() { return options; } - void Engine::trace_eval() const { StateListPtr trace_states(new std::deque(1)); Position p; @@ -158,4 +158,16 @@ void Engine::trace_eval() const { sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; } +OptionsMap& Engine::get_options() { return options; } + +std::string Engine::fen() const { return pos.fen(); } + +void Engine::flip() { pos.flip(); } + +std::string Engine::visualize() const { + std::stringstream ss; + ss << pos; + return ss.str(); +} + } \ No newline at end of file diff --git a/src/engine.h b/src/engine.h index f74209d9095..7122ee59d0b 100644 --- a/src/engine.h +++ b/src/engine.h @@ -79,6 +79,9 @@ class Engine { void trace_eval() const; OptionsMap& get_options(); + std::string fen() const; + void flip(); + std::string visualize() const; private: const std::string binaryDirectory; diff --git a/src/search.h b/src/search.h index d1464840310..d30a06feafa 100644 --- a/src/search.h +++ b/src/search.h @@ -28,6 +28,7 @@ #include #include #include +#include #include "misc.h" #include "movepick.h" @@ -121,11 +122,11 @@ struct LimitsType { bool use_time_management() const { return time[WHITE] || time[BLACK]; } - std::vector searchmoves; - TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; - int movestogo, depth, mate, perft, infinite; - uint64_t nodes; - bool ponderMode; + std::vector searchmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; }; diff --git a/src/thread.cpp b/src/thread.cpp index 85a2bcbb167..1438c9f9d5c 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "misc.h" #include "movegen.h" @@ -33,6 +34,7 @@ #include "tt.h" #include "types.h" #include "ucioption.h" +#include "uci.h" namespace Stockfish { @@ -182,10 +184,18 @@ void ThreadPool::start_thinking(const OptionsMap& options, increaseDepth = true; Search::RootMoves rootMoves; + const auto legalmoves = MoveList(pos); - for (const auto& m : MoveList(pos)) - if (limits.searchmoves.empty() - || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + for (const auto& uciMove : limits.searchmoves) + { + auto move = UCIEngine::to_move(pos, uciMove); + + if (std::find(legalmoves.begin(), legalmoves.end(), move) != legalmoves.end()) + rootMoves.emplace_back(move); + } + + if (rootMoves.empty()) + for (const auto& m : legalmoves) rootMoves.emplace_back(m); Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves); diff --git a/src/uci.cpp b/src/uci.cpp index a15bc7d4b5c..8f697836913 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,8 +22,6 @@ #include #include #include -#include -#include #include #include #include @@ -98,11 +96,7 @@ UCIEngine::UCIEngine(int argc, char** argv) : void UCIEngine::loop() { - Position pos; - std::string token, cmd; - StateListPtr states(new std::deque(1)); - - pos.set(StartFEN, false, &states->back()); + std::string token, cmd; for (int i = 1; i < cli.argc; ++i) cmd += std::string(cli.argv[i]) + " "; @@ -135,9 +129,9 @@ void UCIEngine::loop() { else if (token == "setoption") setoption(is); else if (token == "go") - go(pos, is); + go(is); else if (token == "position") - position(pos, is); + position(is); else if (token == "ucinewgame") engine.search_clear(); else if (token == "isready") @@ -146,11 +140,11 @@ void UCIEngine::loop() { // Add custom non-UCI commands, mainly for debugging purposes. // These commands must not be used during a search! else if (token == "flip") - pos.flip(); + engine.flip(); else if (token == "bench") - bench(pos, is); + bench(is); else if (token == "d") - sync_cout << pos << sync_endl; + sync_cout << engine.visualize() << sync_endl; else if (token == "eval") engine.trace_eval(); else if (token == "compiler") @@ -183,7 +177,7 @@ void UCIEngine::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } -Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is) { +Search::LimitsType UCIEngine::parse_limits(std::istream& is) { Search::LimitsType limits; std::string token; @@ -192,7 +186,7 @@ Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is while (is >> token) if (token == "searchmoves") // Needs to be the last command on the line while (is >> token) - limits.searchmoves.push_back(to_move(pos, token)); + limits.searchmoves.push_back(to_lower(token)); else if (token == "wtime") is >> limits.time[WHITE]; @@ -222,13 +216,13 @@ Search::LimitsType UCIEngine::parse_limits(const Position& pos, std::istream& is return limits; } -void UCIEngine::go(Position& pos, std::istringstream& is) { +void UCIEngine::go(std::istringstream& is) { - Search::LimitsType limits = parse_limits(pos, is); + Search::LimitsType limits = parse_limits(is); engine.go(limits); } -void UCIEngine::bench(Position& pos, std::istream& args) { +void UCIEngine::bench(std::istream& args) { std::string token; uint64_t num, nodes = 0, cnt = 1; uint64_t nodesSearched = 0; @@ -239,7 +233,7 @@ void UCIEngine::bench(Position& pos, std::istream& args) { on_update_full(i, options["UCI_ShowWDL"]); }); - std::vector list = setup_bench(pos, args); + std::vector list = setup_bench(engine.fen(), args); num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); @@ -253,11 +247,11 @@ void UCIEngine::bench(Position& pos, std::istream& args) { if (token == "go" || token == "eval") { - std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" + std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << engine.fen() << ")" << std::endl; if (token == "go") { - go(pos, is); + go(is); engine.wait_for_search_finished(); nodes += nodesSearched; nodesSearched = 0; @@ -268,7 +262,7 @@ void UCIEngine::bench(Position& pos, std::istream& args) { else if (token == "setoption") setoption(is); else if (token == "position") - position(pos, is); + position(is); else if (token == "ucinewgame") { engine.search_clear(); // search_clear may take a while @@ -294,7 +288,7 @@ void UCIEngine::setoption(std::istringstream& is) { engine.get_options().setoption(is); } -void UCIEngine::position(Position& pos, std::istringstream& is) { +void UCIEngine::position(std::istringstream& is) { std::string token, fen; is >> token; @@ -317,7 +311,6 @@ void UCIEngine::position(Position& pos, std::istringstream& is) { moves.push_back(token); } - pos.set(fen, engine.get_options()["UCI_Chess960"], pos.state()); engine.set_position(fen, moves); } @@ -425,9 +418,14 @@ std::string UCIEngine::move(Move m, bool chess960) { } +std::string UCIEngine::to_lower(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::tolower(c); }); + + return str; +} + Move UCIEngine::to_move(const Position& pos, std::string str) { - if (str.length() == 5) - str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased + str = to_lower(str); for (const auto& m : MoveList(pos)) if (str == move(m, pos.is_chess960())) diff --git a/src/uci.h b/src/uci.h index fa359db4549..ee8c2814aee 100644 --- a/src/uci.h +++ b/src/uci.h @@ -46,9 +46,10 @@ class UCIEngine { static std::string square(Square s); static std::string move(Move m, bool chess960); static std::string wdl(Value v, const Position& pos); + static std::string to_lower(std::string str); static Move to_move(const Position& pos, std::string str); - static Search::LimitsType parse_limits(const Position& pos, std::istream& is); + static Search::LimitsType parse_limits(std::istream& is); auto& engine_options() { return engine.get_options(); } @@ -56,9 +57,9 @@ class UCIEngine { Engine engine; CommandLine cli; - void go(Position& pos, std::istringstream& is); - void bench(Position& pos, std::istream& args); - void position(Position& pos, std::istringstream& is); + void go(std::istringstream& is); + void bench(std::istream& args); + void position(std::istringstream& is); void setoption(std::istringstream& is); static void on_update_no_moves(const Engine::InfoShort& info); From c55ae376f62de80fd20822954aaa6c7cd23eb2fa Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 13 Apr 2024 21:54:10 +0200 Subject: [PATCH 0459/1309] Fix wrong sign for 200 TB score Fix another case of 9032c6cbe74ccf7e8963755501e7e6cc473ae471 * TB values can have a distance of 0, mainly when we are in a tb position but haven't found mate. * Add a missing whitespace to UCIEngine::on_update_no_moves() Closes https://github.com/official-stockfish/Stockfish/pull/5172 No functional change --- src/score.cpp | 2 +- src/score.h | 7 ++++--- src/uci.cpp | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/score.cpp b/src/score.cpp index d1a8a6abe4d..292f53406e2 100644 --- a/src/score.cpp +++ b/src/score.cpp @@ -36,7 +36,7 @@ Score::Score(Value v, const Position& pos) { else if (std::abs(v) <= VALUE_TB) { auto distance = VALUE_TB - std::abs(v); - score = (v > 0) ? TBWin{distance} : TBWin{-distance}; + score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false}; } else { diff --git a/src/score.h b/src/score.h index b94d9f7fb6b..2eb40f7e08e 100644 --- a/src/score.h +++ b/src/score.h @@ -34,8 +34,9 @@ class Score { int plies; }; - struct TBWin { - int plies; + struct Tablebase { + int plies; + bool win; }; struct InternalUnits { @@ -61,7 +62,7 @@ class Score { } private: - std::variant score; + std::variant score; }; } diff --git a/src/uci.cpp b/src/uci.cpp index 8f697836913..8e20207b79e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -357,9 +357,9 @@ std::string UCIEngine::format_score(const Score& s) { auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; return std::string("mate ") + std::to_string(m); }, - [](Score::TBWin tb) -> std::string { + [](Score::Tablebase tb) -> std::string { return std::string("cp ") - + std::to_string((tb.plies > 0 ? TB_CP - tb.plies : -TB_CP - tb.plies)); + + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); }, [](Score::InternalUnits units) -> std::string { return std::string("cp ") + std::to_string(units.value); @@ -435,7 +435,7 @@ Move UCIEngine::to_move(const Position& pos, std::string str) { } void UCIEngine::on_update_no_moves(const Engine::InfoShort& info) { - sync_cout << "info depth" << info.depth << " score " << format_score(info.score) << sync_endl; + sync_cout << "info depth " << info.depth << " score " << format_score(info.score) << sync_endl; } void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) { From 432995ad82119070afa0bf720eb65d800bcbf817 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 7 Apr 2024 14:26:23 +0200 Subject: [PATCH 0460/1309] Update outdated comments closes https://github.com/official-stockfish/Stockfish/pull/5158 No functional change --- src/position.cpp | 1 - src/search.cpp | 4 ++-- src/tt.cpp | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index fd1678959d5..78e62bda303 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -744,7 +744,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update board and piece lists remove_piece(capsq); - // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; diff --git a/src/search.cpp b/src/search.cpp index 24805aa70ea..0eb0f45e1f9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1083,7 +1083,7 @@ Value Search::Worker::search( extension = -1; } - // Recapture extensions (~0 Elo on STC, ~1 Elo on LTC) + // Extension for capturing the previous moved piece (~0 Elo on STC, ~1 Elo on LTC) else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] @@ -1147,7 +1147,7 @@ Value Search::Worker::search( { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension - // beyond the first move depth. This may lead to hidden multiple extensions. + // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r, newDepth + 1)); diff --git a/src/tt.cpp b/src/tt.cpp index 9d4d2eca47c..41ed4591f39 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -67,7 +67,7 @@ uint8_t TTEntry::relative_age(const uint8_t generation8) const { // Sets the size of the transposition table, -// measured in megabytes. Transposition table consists of a power of 2 number +// measured in megabytes. Transposition table consists // of clusters and each cluster consists of ClusterSize number of TTEntry. void TranspositionTable::resize(size_t mbSize, int threadCount) { aligned_large_pages_free(table); From d3fc1d835e5144cc98d6a7658fb8cfd9370792f1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 10 Apr 2024 23:10:07 +0200 Subject: [PATCH 0461/1309] Refactor elapsed time checks in search Small improvement of the elapsed time usage in search, makes the code easier to read overall. Also Search::Worker::iterative_deepening() now only checks the elapsed time once, instead of 3 times in a row. Non Regression STC: https://tests.stockfishchess.org/tests/view/6617005d5a4693796d965c3c LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 61024 W: 16002 L: 15806 D: 29216 Ptnml(0-2): 243, 6874, 16102, 7030, 263 closes https://github.com/official-stockfish/Stockfish/pull/5163 No functional change --- src/search.cpp | 28 +++++++++++++++------------- src/search.h | 2 ++ src/timeman.cpp | 3 --- src/timeman.h | 6 ++++-- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0eb0f45e1f9..00636865fe7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -340,7 +340,7 @@ void Search::Worker::iterative_deepening() { // When failing high/low give some update (without cluttering // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + && elapsed() > 3000) main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and @@ -371,8 +371,7 @@ void Search::Worker::iterative_deepening() { std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); if (mainThread - && (threads.stop || pvIdx + 1 == multiPV - || mainThread->tm.elapsed(threads.nodes_searched()) > 3000) + && (threads.stop || pvIdx + 1 == multiPV || elapsed() > 3000) // A thread that aborted search can have mated-in/TB-loss PV and score // that cannot be trusted, i.e. it can be delayed or refuted if we would have // had time to fully search other root-moves. Thus we suppress this output and @@ -448,13 +447,14 @@ void Search::Worker::iterative_deepening() { if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); - if (completedDepth >= 10 && nodesEffort >= 97 - && mainThread->tm.elapsed(threads.nodes_searched()) > totalTime * 0.739 + auto elapsedTime = elapsed(); + + if (completedDepth >= 10 && nodesEffort >= 97 && elapsedTime > totalTime * 0.739 && !mainThread->ponder) threads.stop = true; // Stop the search if we have exceeded the totalTime - if (mainThread->tm.elapsed(threads.nodes_searched()) > totalTime) + if (elapsedTime > totalTime) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". @@ -464,9 +464,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = - mainThread->ponder - || mainThread->tm.elapsed(threads.nodes_searched()) <= totalTime * 0.506; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.506; } mainThread->iterValue[iterIdx] = bestValue; @@ -928,8 +926,7 @@ Value Search::Worker::search( ss->moveCount = ++moveCount; - if (rootNode && is_mainthread() - && main_manager()->tm.elapsed(threads.nodes_searched()) > 3000) + if (rootNode && is_mainthread() && elapsed() > 3000) { main_manager()->updates.onIter( {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); @@ -1631,6 +1628,11 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { return (reductionScale + 1123 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); } +TimePoint Search::Worker::elapsed() const { + return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); }); +} + + namespace { // Adjusts a mate or TB score from "plies to mate from the root" // to "plies to mate from the current position". Standard scores are unchanged. @@ -1845,7 +1847,7 @@ void SearchManager::check_time(Search::Worker& worker) { static TimePoint lastInfoTime = now(); - TimePoint elapsed = tm.elapsed(worker.threads.nodes_searched()); + TimePoint elapsed = tm.elapsed([&worker]() { return worker.threads.nodes_searched(); }); TimePoint tick = worker.limits.startTime + elapsed; if (tick - lastInfoTime >= 1000) @@ -1877,7 +1879,7 @@ void SearchManager::pv(const Search::Worker& worker, const auto& rootMoves = worker.rootMoves; const auto& pos = worker.rootPos; size_t pvIdx = worker.pvIdx; - TimePoint time = tm.elapsed(nodes) + 1; + TimePoint time = tm.elapsed([nodes]() { return nodes; }) + 1; size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); diff --git a/src/search.h b/src/search.h index d30a06feafa..3ceaf5ddd0b 100644 --- a/src/search.h +++ b/src/search.h @@ -275,6 +275,8 @@ class Worker { return static_cast(manager.get()); } + TimePoint elapsed() const; + LimitsType limits; size_t pvIdx, pvLast; diff --git a/src/timeman.cpp b/src/timeman.cpp index 229ff3e9d6b..c651745f02e 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -30,9 +30,6 @@ namespace Stockfish { TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } -TimePoint TimeManagement::elapsed(size_t nodes) const { - return useNodesTime ? TimePoint(nodes) : now() - startTime; -} void TimeManagement::clear() { availableNodes = 0; // When in 'nodes as time' mode diff --git a/src/timeman.h b/src/timeman.h index b07712a25c2..35c3cfc0680 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -19,7 +19,6 @@ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED -#include #include #include "misc.h" @@ -41,7 +40,10 @@ class TimeManagement { TimePoint optimum() const; TimePoint maximum() const; - TimePoint elapsed(std::size_t nodes) const; + template + TimePoint elapsed(FUNC nodes) const { + return useNodesTime ? TimePoint(nodes()) : now() - startTime; + } void clear(); void advance_nodes_time(std::int64_t nodes); From 9021a61807ae8f869ffd7ba55d1b4f0404379dca Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 12 Apr 2024 00:00:59 +0300 Subject: [PATCH 0462/1309] Trivial cleanup Make naming and declaration of futilityValue in search consistent between different places. closes https://github.com/official-stockfish/Stockfish/pull/5165 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 00636865fe7..6813c1a5f61 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -963,11 +963,11 @@ Value Search::Worker::search( if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { Piece capturedPiece = pos.piece_on(move.to_sq()); - int futilityEval = - ss->staticEval + 287 + 277 * lmrDepth + PieceValue[capturedPiece] + Value futilityValue = + ss->staticEval + 288 + 277 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; - if (futilityEval < alpha) + if (futilityValue <= alpha) continue; } @@ -1389,7 +1389,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Key posKey; Move ttMove, move, bestMove; Depth ttDepth; - Value bestValue, value, ttValue, futilityValue, futilityBase; + Value bestValue, value, ttValue, futilityBase; bool pvHit, givesCheck, capture; int moveCount; Color us = pos.side_to_move(); @@ -1518,7 +1518,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (moveCount > 2) continue; - futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())]; + 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) From d0e72c19fa878645afd3d2f573a2587b02e26d47 Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Mon, 15 Apr 2024 11:55:28 +0700 Subject: [PATCH 0463/1309] fix clang compiler warning for avx512 build Initialize variable in constexpr function to get rid of clang compiler warning for avx512 build. closes https://github.com/official-stockfish/Stockfish/pull/5176 Non-functional change --- src/nnue/nnue_feature_transformer.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 3101c8d2689..0a0f4217fdb 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -229,8 +229,7 @@ class FeatureTransformer { static constexpr void order_packs([[maybe_unused]] uint64_t* v) { #if defined(USE_AVX512) // _mm512_packs_epi16 ordering - uint64_t tmp0, tmp1; - tmp0 = v[2], tmp1 = v[3]; + 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; @@ -246,8 +245,7 @@ class FeatureTransformer { static constexpr void inverse_order_packs([[maybe_unused]] uint64_t* v) { #if defined(USE_AVX512) // Inverse _mm512_packs_epi16 ordering - uint64_t tmp0, tmp1; - tmp0 = v[2], tmp1 = v[3]; + 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; From 6fc7da44ad9c7e2ba6062d5c79daafd29a4dcd6f Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Tue, 16 Apr 2024 08:23:42 +0200 Subject: [PATCH 0464/1309] update the WDL model The patch only changes the displayed cp and wdl values. closes https://github.com/official-stockfish/Stockfish/pull/5178 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 8e20207b79e..c707f6dc403 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -330,8 +330,8 @@ WinRateParams win_rate_params(const Position& pos) { double m = std::clamp(material, 10, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-185.71965483, 504.85014385, -438.58295743, 474.04604627}; - constexpr double bs[] = {89.23542728, -137.02141296, 73.28669021, 47.53376190}; + constexpr double as[] = {-150.77043883, 394.96159472, -321.73403766, 406.15850091}; + constexpr double bs[] = {62.33245393, -91.02264855, 45.88486850, 51.63461272}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From 1a8de45b8c2887e8d5efe61498f3acccf5f36116 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 20 Apr 2024 15:33:07 +0200 Subject: [PATCH 0465/1309] Improve CI the recent refactoring has shown some limitations of our testing, hence we add a couple of more tests including: * expected mate score * expected mated score * expected in TB win score * expected in TB loss score * expected info line output * expected info line output (wdl) closes https://github.com/official-stockfish/Stockfish/pull/5181 No functional change --- .github/workflows/sanitizers.yml | 3 + tests/instrumented.sh | 116 +++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 612f1275ce7..78260a182bf 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -31,6 +31,9 @@ jobs: - name: Run under valgrind-thread make_option: "" instrumented_option: valgrind-thread + - name: Run non-instrumented + make_option: "" + instrumented_option: none defaults: run: working-directory: src diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 525c7e04085..ac534c16ab4 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -21,14 +21,14 @@ case $1 in echo "valgrind testing started" prefix='' exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full' - postfix='1>/dev/null' + postfix='' threads="1" ;; --valgrind-thread) echo "valgrind-thread testing started" prefix='' exeprefix='valgrind --fair-sched=try --error-exitcode=42' - postfix='1>/dev/null' + postfix='' threads="2" ;; --sanitizer-undefined) @@ -112,7 +112,12 @@ diff $network verify.nnue # more general testing, following an uci protocol exchange cat << EOF > game.exp set timeout 240 + # to correctly catch eof we need the following line + # expect_before timeout { exit 2 } eof { exit 3 } + expect_before timeout { exit 2 } + spawn $exeprefix ./stockfish + expect "Stockfish" send "uci\n" expect "uciok" @@ -125,27 +130,101 @@ cat << EOF > game.exp send "go nodes 1000\n" expect "bestmove" + send "ucinewgame\n" send "position startpos moves e2e4 e7e6\n" send "go nodes 1000\n" expect "bestmove" + send "ucinewgame\n" send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" send "go depth 10\n" expect "bestmove" - send "setoption name UCI_ShowWDL value true\n" - send "position startpos\n" + send "ucinewgame\n" + send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" send "flip\n" - send "go depth 5\n" + send "go depth 10\n" expect "bestmove" - send "setoption name Skill Level value 10\n" + send "ucinewgame\n" send "position startpos\n" send "go depth 5\n" + expect -re {info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect "bestmove" + + send "ucinewgame\n" + send "setoption name UCI_ShowWDL value true\n" + send "position startpos\n" + send "go depth 9\n" + expect -re {info depth 1 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 2 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 3 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 4 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 5 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 6 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 7 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 8 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} + expect -re {info depth 9 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} expect "bestmove" send "setoption name Clear Hash\n" + send "ucinewgame\n" + send "position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6\n" + send "go depth 18\n" + expect "score mate 1" + expect "pv d5e6" + expect "bestmove d5e6" + + send "ucinewgame\n" + send "position fen 2brrb2/8/p7/Q7/1p1kpPp1/1P1pN1K1/3P4/8 b - -\n" + send "go depth 18\n" + expect "score mate -1" + expect "bestmove" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go depth 18\n" + expect "score mate 2 * pv c6d7 * f7f5" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go mate 2\n" + expect "score mate 2 * pv c6d7" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go nodes 10000\n" + expect "score mate 2 * pv c6d7 * f7f5" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 1NR2B2/5p2/5p2/1p1kpp2/1P2rp2/2P1pB2/2P1P1K1/8 b - - \n" + send "go depth 18\n" + expect "score mate -2" + expect "pv d5e6 c8d8" + expect "bestmove d5e6" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7 f2f1q\n" + send "go depth 18\n" + expect "score mate 1 * pv f7f5" + expect "bestmove f7f5" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" + send "go depth 18 searchmoves c6d7\n" + expect "score mate 2 * pv c6d7 * f7f5" + expect "bestmove c6d7" + + send "ucinewgame\n" + send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7\n" + send "go depth 18 searchmoves e3e2\n" + expect "score mate -1 * pv e3e2 f7f5" + expect "bestmove e3e2" + send "setoption name EvalFile value verify.nnue\n" send "position startpos\n" send "go depth 5\n" @@ -154,6 +233,13 @@ cat << EOF > game.exp send "setoption name MultiPV value 4\n" send "position startpos\n" send "go depth 5\n" + expect "bestmove" + + send "setoption name Skill Level value 10\n" + send "position startpos\n" + send "go depth 5\n" + expect "bestmove" + send "setoption name Skill Level value 20\n" send "quit\n" expect eof @@ -171,17 +257,30 @@ fi cat << EOF > syzygy.exp set timeout 240 + # to correctly catch eof we need the following line + # expect_before timeout { exit 2 } eof { exit 3 } + expect_before timeout { exit 2 } spawn $exeprefix ./stockfish + expect "Stockfish" send "uci\n" send "setoption name SyzygyPath value ../tests/syzygy/\n" - expect "info string Found 35 tablebases" {} timeout {exit 1} + expect "info string Found 35 tablebases" send "bench 128 1 8 default depth\n" + expect "Nodes searched :" send "ucinewgame\n" send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n" send "go depth 5\n" + expect -re {score cp 20000|score mate} expect "bestmove" + send "ucinewgame\n" send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n" send "go depth 5\n" + expect -re {score cp 20000|score mate} + expect "bestmove" + send "ucinewgame\n" + send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 b - - 0 1\n" + send "go depth 5\n" + expect -re {score cp -20000|score mate} expect "bestmove" send "quit\n" expect eof @@ -194,6 +293,9 @@ EOF for exp in game.exp syzygy.exp do + echo "======== $exp ==============" + cat $exp + echo "============================" echo "$prefix expect $exp $postfix" eval "$prefix expect $exp $postfix" From 56a9cc512e5ffb2310ad6e4676c77ce0485f31f3 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 20 Apr 2024 20:37:39 +0200 Subject: [PATCH 0466/1309] Move ALSR change to CI Workflow file It makes more sense to not (potentially) change the developers alsr entropy setting to make the test run through. This should be an active choice even if the test then might fail locally for them. closes https://github.com/official-stockfish/Stockfish/pull/5182 No functional change --- .github/workflows/sanitizers.yml | 8 ++++++++ tests/instrumented.sh | 7 ------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 78260a182bf..b75c06cfbbe 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -58,6 +58,14 @@ jobs: - name: Check git run: git --version + # Since Linux Kernel 6.5 we are getting false positives from the ci, + # lower the ALSR entropy to disable ALSR, which works as a temporary workaround. + # https://github.com/google/sanitizers/issues/1716 + # https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 + + - name: Lower ALSR entropy + run: sudo sysctl -w vm.mmap_rnd_bits=28 + # Sanitizers - name: ${{ matrix.sanitizers.name }} diff --git a/tests/instrumented.sh b/tests/instrumented.sh index ac534c16ab4..4c63fc5714e 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -8,13 +8,6 @@ error() } trap 'error ${LINENO}' ERR -# Since Linux Kernel 6.5 we are getting false positives from the ci, -# lower the ALSR entropy to disable ALSR, which works as a temporary workaround. -# https://github.com/google/sanitizers/issues/1716 -# https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762 -sudo sysctl -w vm.mmap_rnd_bits=28 - - # define suitable post and prefixes for testing options case $1 in --valgrind) From d47aa639bd614b37a59f87e6ab68496580f0cf3e Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:50:09 +0800 Subject: [PATCH 0467/1309] Tweak TT aging and replacement strategies We change the definition of "age" from "age of this position" to "age of this TT entry". In this way, despite being on the same position, when we save into TT, we always prefer the new entry as compared to the old one. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 152256 W: 39597 L: 39110 D: 73549 Ptnml(0-2): 556, 17562, 39398, 18063, 549 https://tests.stockfishchess.org/tests/view/6620faee3fe04ce4cefbf215 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 51564 W: 13242 L: 12895 D: 25427 Ptnml(0-2): 24, 5464, 14463, 5803, 28 https://tests.stockfishchess.org/tests/view/66231ab53fe04ce4cefc153e closes #5184 Bench 1479416 --- src/tt.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 41ed4591f39..4885a781a5e 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -40,7 +40,8 @@ void TTEntry::save( move16 = m; // Overwrite less valuable entries (cheapest checks first) - if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4) + if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4 + || relative_age(generation8)) { assert(d > DEPTH_OFFSET); assert(d < 256 + DEPTH_OFFSET); @@ -123,13 +124,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16 || !tte[i].depth8) - { - constexpr uint8_t lowerBits = GENERATION_DELTA - 1; - - // Refresh with new generation, keeping the lower bits the same. - tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & lowerBits)); - return found = bool(tte[i].depth8), &tte[i]; - } + return found = bool(tte[i].depth8), &tte[i]; // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; From ddd250b9d655117920dd65a973cea2f8b3c57fce Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 22 Apr 2024 19:24:10 +0200 Subject: [PATCH 0468/1309] Restore NPS output for Perft Previously it was possible to also get the node counter after running a bench with perft, i.e. `./stockfish bench 1 1 5 current perft`, caused by a small regression from the uci refactoring. ``` Nodes searched: 4865609 =========================== Total time (ms) : 18 Nodes searched : 4865609 Nodes/second : 270311611 ```` closes https://github.com/official-stockfish/Stockfish/pull/5188 No functional change --- src/benchmark.cpp | 2 +- src/benchmark.h | 2 +- src/engine.cpp | 14 ++++++++------ src/engine.h | 4 ++++ src/perft.h | 7 +++---- src/uci.cpp | 26 ++++++++++++++++++++++---- src/uci.h | 10 ++++++---- 7 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 267a6b4b2a9..3622ac8afc8 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -93,7 +93,7 @@ const std::vector Defaults = { } // namespace -namespace Stockfish { +namespace Stockfish::Benchmark { // Builds a list of UCI commands to be run by bench. There // are five parameters: TT size in MB, number of search threads that diff --git a/src/benchmark.h b/src/benchmark.h index 8905fcb1825..b1eba40f38b 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -23,7 +23,7 @@ #include #include -namespace Stockfish { +namespace Stockfish::Benchmark { std::vector setup_bench(const std::string&, std::istream&); diff --git a/src/engine.cpp b/src/engine.cpp index 325b971ea46..4625e00a816 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "evaluate.h" #include "misc.h" @@ -54,14 +55,15 @@ Engine::Engine(std::string path) : pos.set(StartFEN, false, &states->back()); } -void Engine::go(const Search::LimitsType& limits) { +std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { verify_networks(); - if (limits.perft) - { - perft(pos.fen(), limits.perft, options["UCI_Chess960"]); - return; - } + return Benchmark::perft(fen, depth, isChess960); +} + +void Engine::go(const Search::LimitsType& limits) { + assert(limits.perft == 0); + verify_networks(); threads.start_thinking(options, pos, states, limits); } diff --git a/src/engine.h b/src/engine.h index 7122ee59d0b..041f5678585 100644 --- a/src/engine.h +++ b/src/engine.h @@ -26,6 +26,7 @@ #include #include #include +#include #include "nnue/network.h" #include "position.h" @@ -33,6 +34,7 @@ #include "thread.h" #include "tt.h" #include "ucioption.h" +#include "syzygy/tbprobe.h" // for Stockfish::Depth namespace Stockfish { @@ -45,6 +47,8 @@ class Engine { Engine(std::string path = ""); ~Engine() { wait_for_search_finished(); } + std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960); + // non blocking call to start searching void go(const Search::LimitsType&); // non blocking call to stop searching diff --git a/src/perft.h b/src/perft.h index 2dbab828a18..e907742da05 100644 --- a/src/perft.h +++ b/src/perft.h @@ -26,7 +26,7 @@ #include "types.h" #include "uci.h" -namespace Stockfish { +namespace Stockfish::Benchmark { // Utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. @@ -56,13 +56,12 @@ uint64_t perft(Position& pos, Depth depth) { return nodes; } -inline void perft(const std::string& fen, Depth depth, bool isChess960) { +inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) { StateListPtr states(new std::deque(1)); Position p; p.set(fen, isChess960, &states->back()); - uint64_t nodes = perft(p, depth); - sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return perft(p, depth); } } diff --git a/src/uci.cpp b/src/uci.cpp index c707f6dc403..cb686a027db 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -219,7 +219,11 @@ Search::LimitsType UCIEngine::parse_limits(std::istream& is) { void UCIEngine::go(std::istringstream& is) { Search::LimitsType limits = parse_limits(is); - engine.go(limits); + + if (limits.perft) + perft(limits); + else + engine.go(limits); } void UCIEngine::bench(std::istream& args) { @@ -233,7 +237,7 @@ void UCIEngine::bench(std::istream& args) { on_update_full(i, options["UCI_ShowWDL"]); }); - std::vector list = setup_bench(engine.fen(), args); + std::vector list = Benchmark::setup_bench(engine.fen(), args); num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; }); @@ -251,8 +255,16 @@ void UCIEngine::bench(std::istream& args) { << std::endl; if (token == "go") { - go(is); - engine.wait_for_search_finished(); + Search::LimitsType limits = parse_limits(is); + + if (limits.perft) + nodes = perft(limits); + else + { + engine.go(limits); + engine.wait_for_search_finished(); + } + nodes += nodesSearched; nodesSearched = 0; } @@ -288,6 +300,12 @@ void UCIEngine::setoption(std::istringstream& is) { engine.get_options().setoption(is); } +std::uint64_t UCIEngine::perft(const Search::LimitsType& limits) { + auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return nodes; +} + void UCIEngine::position(std::istringstream& is) { std::string token, fen; diff --git a/src/uci.h b/src/uci.h index ee8c2814aee..55d580f9727 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "engine.h" #include "misc.h" @@ -57,10 +58,11 @@ class UCIEngine { Engine engine; CommandLine cli; - void go(std::istringstream& is); - void bench(std::istream& args); - void position(std::istringstream& is); - void setoption(std::istringstream& is); + void go(std::istringstream& is); + void bench(std::istream& args); + void position(std::istringstream& is); + void setoption(std::istringstream& is); + std::uint64_t perft(const Search::LimitsType&); static void on_update_no_moves(const Engine::InfoShort& info); static void on_update_full(const Engine::InfoFull& info, bool showWDL); From fcba524793222fcdb1ca4254697b15e168f39ad2 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 22 Apr 2024 14:34:22 +0300 Subject: [PATCH 0469/1309] Tune Search Parameters Parameters Tune, adding also another tunable parameter (npmDiv) to be variable for different nets (bignet, smallnet, psqtOnly smallnet). P.s: The changed values are only the parameters where there is agreement among the different time controls, so in other words, the tunings are telling us that changing these specific values to this specific direction is good in all time controls, so there shouldn't be a high risk of regressing at longer time controls. Passed STC: LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 39552 W: 10329 L: 9999 D: 19224 Ptnml(0-2): 156, 4592, 9989, 4844, 195 https://tests.stockfishchess.org/tests/view/661be9a0bd68065432a088c0 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 56394 W: 14439 L: 14078 D: 27877 Ptnml(0-2): 30, 6152, 15480, 6497, 38 https://tests.stockfishchess.org/tests/view/661c746296961e72eb565406 closes https://github.com/official-stockfish/Stockfish/pull/5187 Bench: 1836777 --- src/evaluate.cpp | 14 +++++++------- src/movepick.cpp | 10 +++++----- src/search.cpp | 46 +++++++++++++++++++++++----------------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index dcbfedb499a..ec120a480a3 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -58,14 +58,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, true, &nnueComplexity, false); - const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, - int npmConstant, int evalDiv, int shufflingConstant, - int shufflingDiv) { + const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, + int pawnCountMul, int npmConstant, int evalDiv, + int shufflingConstant, int shufflingDiv) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; - int npm = pos.non_pawn_material() / 64; + int npm = pos.non_pawn_material() / npmDiv; v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) + optimism * (npmConstant + npm)) / evalDiv; @@ -76,11 +76,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, }; if (!smallNet) - adjustEval(513, 32395, 919, 11, 145, 1036, 178, 204); + adjustEval(524, 32395, 66, 942, 11, 139, 1058, 178, 204); else if (psqtOnly) - adjustEval(517, 32857, 908, 7, 155, 1019, 224, 238); + adjustEval(517, 32857, 65, 908, 7, 155, 1006, 224, 238); else - adjustEval(499, 32793, 903, 9, 147, 1067, 208, 211); + adjustEval(515, 32793, 63, 944, 9, 140, 1067, 206, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/movepick.cpp b/src/movepick.cpp index c1119cf11eb..4a93662db43 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -190,8 +190,8 @@ void MovePicker::score() { m.value += bool(pos.check_squares(pt) & to) * 16384; // bonus for escaping from capture - m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51000 - : pt == ROOK && !(to & threatenedByMinor) ? 24950 + m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700 + : pt == ROOK && !(to & threatenedByMinor) ? 25600 : !(to & threatenedByPawn) ? 14450 : 0) : 0; @@ -200,7 +200,7 @@ void MovePicker::score() { m.value -= !(threatenedPieces & from) ? (pt == QUEEN ? bool(to & threatenedByRook) * 48150 + bool(to & threatenedByMinor) * 10650 - : pt == ROOK ? bool(to & threatenedByMinor) * 24500 + : pt == ROOK ? bool(to & threatenedByMinor) * 24335 : pt != PAWN ? bool(to & threatenedByPawn) * 14950 : 0) : 0; @@ -241,7 +241,7 @@ Move MovePicker::select(Pred filter) { // moves left, picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { - auto quiet_threshold = [](Depth d) { return -3550 * d; }; + auto quiet_threshold = [](Depth d) { return -3560 * d; }; top: switch (stage) @@ -310,7 +310,7 @@ Move MovePicker::next_move(bool skipQuiets) { return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; })) { - if ((cur - 1)->value > -8000 || (cur - 1)->value <= quiet_threshold(depth)) + if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) return *(cur - 1); // Remaining quiets are bad diff --git a/src/search.cpp b/src/search.cpp index 6813c1a5f61..183b7bcee5d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -57,9 +57,9 @@ static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 118 - 44 * noTtCutNode; + Value futilityMult = 118 - 45 * noTtCutNode; Value improvingDeduction = 52 * improving * futilityMult / 32; - Value worseningDeduction = (310 + 48 * improving) * oppWorsening * futilityMult / 1024; + Value worseningDeduction = (316 + 48 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -76,10 +76,10 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(211 * d - 315, 0, 1291); } +int stat_bonus(Depth d) { return std::clamp(214 * d - 318, 16, 1304); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 572 * d - 285 : 1372); } +int stat_malus(Depth d) { return (d < 4 ? 572 * d - 284 : 1355); } // 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); } @@ -303,12 +303,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 11 + avg * avg / 11254; + delta = 10 + avg * avg / 11480; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 125 * avg / (std::abs(avg) + 91); + optimism[us] = 122 * avg / (std::abs(avg) + 92); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -752,7 +752,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 471 - (276 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 471 - (275 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -763,14 +763,14 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 284 + - (ss - 1)->statScore / 286 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 315 && !excludedMove + && eval >= beta && ss->staticEval >= beta - 21 * depth + 312 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { @@ -881,7 +881,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 436; + probCutBeta = beta + 452; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -964,7 +964,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.piece_on(move.to_sq()); Value futilityValue = - ss->staticEval + 288 + 277 * lmrDepth + PieceValue[capturedPiece] + ss->staticEval + 285 + 277 * lmrDepth + PieceValue[capturedPiece] + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] / 7; if (futilityValue <= alpha) @@ -972,7 +972,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -199 * depth)) + if (!pos.see_ge(move, -203 * depth)) continue; } else @@ -992,10 +992,10 @@ Value Search::Worker::search( lmrDepth += history / 5285; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 58) + 131 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 57) + 131 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 14 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1006,7 +1006,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1026,11 +1026,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (64 + 59 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (65 + 59 * (ss->ttPv && !PvNode)) * depth / 63; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1134,10 +1134,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 5007; + + (*contHist[3])[movedPiece][move.to_sq()] - 5024; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 12901; + r -= ss->statScore / 13182; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1274,7 +1274,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13132 && value > -13295) + if (depth > 2 && depth < 12 && beta < 13546 && value > -13478) depth -= 2; assert(depth > 0); @@ -1319,7 +1319,7 @@ Value Search::Worker::search( { int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) + ((ss - 1)->moveCount > 11) - + (!ss->inCheck && bestValue <= ss->staticEval - 144); + + (!ss->inCheck && bestValue <= ss->staticEval - 142); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1477,7 +1477,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 246; + futilityBase = ss->staticEval + 250; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1625,7 +1625,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1123 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); + return (reductionScale + 1150 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); } TimePoint Search::Worker::elapsed() const { From 49ef4c935a5cb0e4d94096e6354caa06b36b3e3c Mon Sep 17 00:00:00 2001 From: gab8192 Date: Sat, 20 Apr 2024 21:26:00 +0200 Subject: [PATCH 0470/1309] Implement accumulator refresh table For each thread persist an accumulator cache for the network, where each cache contains multiple entries for each of the possible king squares. When the accumulator needs to be refreshed, the cached entry is used to more efficiently update the accumulator, instead of rebuilding it from scratch. This idea, was first described by Luecx (author of Koivisto) and is commonly referred to as "Finny Tables". When the accumulator needs to be refreshed, instead of filling it with biases and adding every piece from scratch, we... 1. Take the `AccumulatorRefreshEntry` associated with the new king bucket 2. Calculate the features to activate and deactivate (from differences between bitboards in the entry and bitboards of the actual position) 3. Apply the updates on the refresh entry 4. Copy the content of the refresh entry accumulator to the accumulator we were refreshing 5. Copy the bitboards from the position to the refresh entry, to match the newly updated accumulator Results at STC: https://tests.stockfishchess.org/tests/view/662301573fe04ce4cefc1386 (first version) https://tests.stockfishchess.org/tests/view/6627fa063fe04ce4cefc6560 (final) Non-Regression between first and final: https://tests.stockfishchess.org/tests/view/662801e33fe04ce4cefc660a STC SMP: https://tests.stockfishchess.org/tests/view/662808133fe04ce4cefc667c closes https://github.com/official-stockfish/Stockfish/pull/5183 No functional change --- src/evaluate.cpp | 19 ++- src/evaluate.h | 8 +- src/nnue/features/half_ka_v2_hm.cpp | 4 +- src/nnue/features/half_ka_v2_hm.h | 8 +- src/nnue/network.cpp | 45 ++++--- src/nnue/network.h | 24 ++-- src/nnue/nnue_accumulator.h | 70 +++++++++- src/nnue/nnue_feature_transformer.h | 192 ++++++++++++++++++++++++++-- src/nnue/nnue_misc.cpp | 17 ++- src/nnue/nnue_misc.h | 9 +- src/search.cpp | 26 ++-- src/search.h | 7 +- 12 files changed, 349 insertions(+), 80 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index ec120a480a3..f5746ca5199 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -25,12 +25,14 @@ #include #include #include +#include #include "nnue/network.h" #include "nnue/nnue_misc.h" #include "position.h" #include "types.h" #include "uci.h" +#include "nnue/nnue_accumulator.h" namespace Stockfish { @@ -45,7 +47,10 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int optimism) { +Value Eval::evaluate(const Eval::NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorCaches& caches, + int optimism) { assert(!pos.checkers()); @@ -55,8 +60,8 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) - : networks.big.evaluate(pos, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, nullptr, true, &nnueComplexity, psqtOnly) + : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, int pawnCountMul, int npmConstant, int evalDiv, @@ -94,20 +99,22 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, // Trace scores are from white's point of view std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { + auto caches = std::make_unique(); + if (pos.checkers()) return "Final evaluation: none (in check)"; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos, networks) << '\n'; + ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v = networks.big.evaluate(pos, false); + Value v = networks.big.evaluate(pos, &caches->big, false); v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; - v = evaluate(networks, pos, VALUE_ZERO); + v = evaluate(networks, pos, *caches, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index da9c7074ec1..38615ff7d68 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -40,14 +40,16 @@ constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389; namespace NNUE { struct Networks; +struct AccumulatorCaches; } std::string trace(Position& pos, const Eval::NNUE::Networks& networks); int simple_eval(const Position& pos, Color c); -Value evaluate(const NNUE::Networks& networks, const Position& pos, int optimism); - - +Value evaluate(const NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorCaches& caches, + int optimism); } // namespace Eval } // namespace Stockfish diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5789db4844a..71782a7b731 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -23,7 +23,7 @@ #include "../../bitboard.h" #include "../../position.h" #include "../../types.h" -#include "../nnue_common.h" +#include "../nnue_accumulator.h" namespace Stockfish::Eval::NNUE::Features { @@ -49,6 +49,8 @@ void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) // Explicit template instantiations template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); // Get a list of indices for recently changed features template diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 8363184f430..96349704745 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -63,10 +63,6 @@ class HalfKAv2_hm { {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}}; - // Index of a feature for a given king position and another piece on some square - template - static IndexType make_index(Square s, Piece pc, Square ksq); - public: // Feature name static constexpr const char* Name = "HalfKAv2_hm(Friend)"; @@ -126,6 +122,10 @@ class HalfKAv2_hm { static constexpr IndexType MaxActiveDimensions = 32; using IndexList = ValueList; + // Index of a feature for a given king position and another piece on some square + template + static IndexType make_index(Square s, Piece pc, Square ksq); + // Get a list of indices for active features template static void append_active_indices(const Position& pos, IndexList& active); diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index bea3e7cb398..656ad97a1e3 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -186,10 +186,11 @@ bool Network::save(const std::optional& filename template -Value Network::evaluate(const Position& pos, - bool adjusted, - int* complexity, - bool psqtOnly) const { +Value Network::evaluate(const Position& pos, + AccumulatorCaches::Cache* cache, + bool adjusted, + int* complexity, + bool psqtOnly) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -197,20 +198,21 @@ Value Network::evaluate(const Position& pos, constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures - [FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket, psqtOnly); + const auto psqt = + featureTransformer->transform(pos, cache, transformedFeatures, bucket, psqtOnly); const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; if (complexity) @@ -255,26 +257,29 @@ void Network::verify(std::string evalfilePath) const { template -void Network::hint_common_access(const Position& pos, bool psqtOnl) const { - featureTransformer->hint_common_access(pos, psqtOnl); +void Network::hint_common_access(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnl) const { + featureTransformer->hint_common_access(pos, cache, psqtOnl); } - template -NnueEvalTrace Network::trace_evaluate(const Position& pos) const { +NnueEvalTrace +Network::trace_evaluate(const Position& pos, + AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + TransformedFeatureType + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType transformedFeatures - [FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType + transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); @@ -284,7 +289,7 @@ NnueEvalTrace Network::trace_evaluate(const Position& pos) co for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, transformedFeatures, bucket, false); + featureTransformer->transform(pos, cache, transformedFeatures, bucket, false); const auto positional = network[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); diff --git a/src/nnue/network.h b/src/nnue/network.h index 21e1c622205..df59732d955 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -31,10 +31,10 @@ #include "nnue_architecture.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" +#include "nnue_accumulator.h" namespace Stockfish::Eval::NNUE { - enum class EmbeddedNNUEType { BIG, SMALL, @@ -43,6 +43,8 @@ enum class EmbeddedNNUEType { template class Network { + static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions; + public: Network(EvalFile file, EmbeddedNNUEType type) : evalFile(file), @@ -51,17 +53,20 @@ class Network { void load(const std::string& rootDirectory, std::string evalfilePath); bool save(const std::optional& filename) const; + Value evaluate(const Position& pos, + AccumulatorCaches::Cache* cache, + bool adjusted = false, + int* complexity = nullptr, + bool psqtOnly = false) const; - Value evaluate(const Position& pos, - bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false) const; - - void hint_common_access(const Position& pos, bool psqtOnl) const; + void hint_common_access(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnl) const; void verify(std::string evalfilePath) const; - NnueEvalTrace trace_evaluate(const Position& pos) const; + NnueEvalTrace trace_evaluate(const Position& pos, + AccumulatorCaches::Cache* cache) const; private: void load_user_net(const std::string&, const std::string&); @@ -89,6 +94,9 @@ class Network { // Hash value of evaluation function structure static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); + + template + friend struct AccumulatorCaches::Cache; }; // Definitions of the network types diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index c0746b4ee86..8d73dbef5ad 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -28,13 +28,75 @@ namespace Stockfish::Eval::NNUE { +using BiasType = std::int16_t; +using PSQTWeightType = std::int32_t; +using IndexType = std::uint32_t; + // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[2][Size]; - std::int32_t psqtAccumulation[2][PSQTBuckets]; - bool computed[2]; - bool computedPSQT[2]; + std::int16_t accumulation[COLOR_NB][Size]; + std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; + bool computed[COLOR_NB]; + bool computedPSQT[COLOR_NB]; +}; + + +// AccumulatorCaches struct provides per-thread accumulator caches, where each +// cache contains multiple entries for each of the possible king squares. +// When the accumulator needs to be refreshed, the cached entry is used to more +// efficiently update the accumulator, instead of rebuilding it from scratch. +// This idea, was first described by Luecx (author of Koivisto) and +// is commonly referred to as "Finny Tables". +struct AccumulatorCaches { + + template + struct alignas(CacheLineSize) Cache { + + struct alignas(CacheLineSize) Entry { + BiasType accumulation[COLOR_NB][Size]; + PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets]; + Bitboard byColorBB[COLOR_NB][COLOR_NB]; + Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB]; + + // To initialize a refresh entry, we set all its bitboards empty, + // so we put the biases in the accumulation, without any weights on top + void clear(const BiasType* biases) { + + std::memset(byColorBB, 0, sizeof(byColorBB)); + std::memset(byTypeBB, 0, sizeof(byTypeBB)); + + std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType)); + std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType)); + + std::memset(psqtAccumulation, 0, sizeof(psqtAccumulation)); + } + }; + + template + void clear(const Network& network) { + for (auto& entry : entries) + entry.clear(network.featureTransformer->biases); + } + + void clear(const BiasType* biases) { + for (auto& entry : entries) + entry.clear(biases); + } + + Entry& operator[](Square sq) { return entries[sq]; } + + std::array entries; + }; + + template + void clear(const Networks& networks) { + big.clear(networks.big); + } + + // When adding a new cache for a network, i.e. the smallnet + // the appropriate condition must be added to FeatureTransformer::update_accumulator_refresh. + Cache big; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 0a0f4217fdb..88f0e4031a4 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -195,10 +195,10 @@ template StateInfo::*accPtr> class FeatureTransformer { - private: // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + private: #ifdef VECTOR static constexpr int NumRegs = BestRegisterCount(); @@ -306,10 +306,13 @@ class FeatureTransformer { } // Convert input features - std::int32_t - transform(const Position& pos, OutputType* output, int bucket, bool psqtOnly) const { - update_accumulator(pos, psqtOnly); - update_accumulator(pos, psqtOnly); + std::int32_t transform(const Position& pos, + AccumulatorCaches::Cache* cache, + OutputType* output, + int bucket, + bool psqtOnly) const { + update_accumulator(pos, cache, psqtOnly); + update_accumulator(pos, cache, psqtOnly); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; @@ -371,9 +374,11 @@ class FeatureTransformer { return psqt; } // end of function transform() - void hint_common_access(const Position& pos, bool psqtOnly) const { - hint_common_access_for_perspective(pos, psqtOnly); - hint_common_access_for_perspective(pos, psqtOnly); + void hint_common_access(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { + hint_common_access_for_perspective(pos, cache, psqtOnly); + hint_common_access_for_perspective(pos, cache, psqtOnly); } private: @@ -650,7 +655,161 @@ class FeatureTransformer { } template - void update_accumulator_refresh(const Position& pos, bool psqtOnly) const { + void update_accumulator_refresh_cache(const Position& pos, + AccumulatorCaches::Cache* cache) const { + assert(cache != nullptr); + + Square ksq = pos.square(Perspective); + + auto& entry = (*cache)[ksq]; + + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = true; + accumulator.computedPSQT[Perspective] = true; + + FeatureSet::IndexList removed, added; + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = + entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + } + } + +#ifdef VECTOR + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; + + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto entryTile = + reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = entryTile[k]; + + for (int i = 0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + for (int i = 0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + for (IndexType k = 0; k < NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + auto entryTilePsqt = reinterpret_cast( + &entry.psqtAccumulation[Perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = entryTilePsqt[k]; + + for (int i = 0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + for (int i = 0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&entryTilePsqt[k], psqt[k]); + } + +#else + + for (const auto index : added) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } + for (const auto index : removed) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] -= weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + } + +#endif + + // The accumulator of the refresh entry has been updated. + // Now copy its content to the actual accumulator we were refreshing + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective], + sizeof(int32_t) * PSQTBuckets); + + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], + sizeof(BiasType) * HalfDimensions); + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[Perspective][c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[Perspective][pt] = pos.pieces(pt); + } + + template + void + update_accumulator_refresh(const Position& pos, + [[maybe_unused]] AccumulatorCaches::Cache* cache, + bool psqtOnly) const { + + // When we are refreshing the accumulator of the big net, + // redirect to the version of refresh that uses the refresh table. + // Using the cache for the small net is not beneficial. + if constexpr (HalfDimensions == Eval::NNUE::TransformedFeatureDimensionsBig) + { + update_accumulator_refresh_cache(pos, cache); + return; + } + #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array // is defined in the VECTOR code below, once in each branch @@ -764,7 +923,9 @@ class FeatureTransformer { } template - void hint_common_access_for_perspective(const Position& pos, bool psqtOnly) const { + void hint_common_access_for_perspective(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { // Works like update_accumulator, but performs less work. // Updates ONLY the accumulator for pos. @@ -787,11 +948,13 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, psqtOnly); + update_accumulator_refresh(pos, cache, psqtOnly); } template - void update_accumulator(const Position& pos, bool psqtOnly) const { + void update_accumulator(const Position& pos, + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { auto [oldest_st, next] = try_find_computed_accumulator(pos, psqtOnly); @@ -813,9 +976,12 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, psqtOnly); + update_accumulator_refresh(pos, cache, psqtOnly); } + template + friend struct AccumulatorCaches::Cache; + alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 3fa6e1b6180..51838fefa44 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -42,13 +42,15 @@ namespace Stockfish::Eval::NNUE { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -void hint_common_parent_position(const Position& pos, const Networks& networks) { +void hint_common_parent_position(const Position& pos, + const Networks& networks, + AccumulatorCaches& caches) { int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, nullptr, simpleEvalAbs > Eval::PsqtOnlyThreshold); else - networks.big.hint_common_access(pos, false); + networks.big.hint_common_access(pos, &caches.big, false); } namespace { @@ -104,7 +106,8 @@ void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& p // Returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. -std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { +std::string +trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) { std::stringstream ss; @@ -130,7 +133,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - Value base = networks.big.evaluate(pos); + Value base = networks.big.evaluate(pos, &caches.big); base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) @@ -149,7 +152,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = false; - Value eval = networks.big.evaluate(pos); + Value eval = networks.big.evaluate(pos, &caches.big); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; @@ -167,7 +170,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << board[row] << '\n'; ss << '\n'; - auto t = networks.big.trace_evaluate(pos); + auto t = networks.big.trace_evaluate(pos, &caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index 5eab02184c6..27a93f88435 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -50,12 +50,13 @@ struct NnueEvalTrace { std::size_t correctBucket; }; - struct Networks; +struct AccumulatorCaches; - -std::string trace(Position& pos, const Networks& networks); -void hint_common_parent_position(const Position& pos, const Networks& networks); +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/search.cpp b/src/search.cpp index 183b7bcee5d..893daab20e6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -33,6 +33,8 @@ #include "misc.h" #include "movegen.h" #include "movepick.h" +#include "nnue/network.h" +#include "nnue/nnue_accumulator.h" #include "nnue/nnue_common.h" #include "nnue/nnue_misc.h" #include "position.h" @@ -135,6 +137,7 @@ Search::Worker::Worker(SharedState& sharedState, // Unpack the SharedState struct into member variables thread_idx(thread_id), manager(std::move(sm)), + refreshTable(), options(sharedState.options), threads(sharedState.threads), tt(sharedState.tt), @@ -143,6 +146,10 @@ Search::Worker::Worker(SharedState& sharedState, } void Search::Worker::start_searching() { + + // Initialize accumulator refresh entries + refreshTable.clear(networks); + // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -564,7 +571,7 @@ Value Search::Worker::search( if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks, pos, thisThread->optimism[us]) + ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score @@ -698,7 +705,7 @@ Value Search::Worker::search( { // 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); + Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -706,9 +713,9 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]); else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos, networks); + Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -718,7 +725,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -875,7 +882,7 @@ Value Search::Worker::search( } } - Eval::NNUE::hint_common_parent_position(pos, networks); + Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); } moves_loop: // When in check, search starts here @@ -1413,7 +1420,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks, pos, thisThread->optimism[us]) + ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1445,7 +1452,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); + unadjustedStaticEval = + evaluate(networks, pos, refreshTable, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1458,7 +1466,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = (ss - 1)->currentMove != Move::null() - ? evaluate(networks, pos, thisThread->optimism[us]) + ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) : -(ss - 1)->staticEval; ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); diff --git a/src/search.h b/src/search.h index 3ceaf5ddd0b..0fd778b47e6 100644 --- a/src/search.h +++ b/src/search.h @@ -26,9 +26,9 @@ #include #include #include +#include #include #include -#include #include "misc.h" #include "movepick.h" @@ -37,6 +37,7 @@ #include "syzygy/tbprobe.h" #include "timeman.h" #include "types.h" +#include "nnue/nnue_accumulator.h" namespace Stockfish { @@ -301,6 +302,10 @@ class Worker { Tablebases::Config tbConfig; + // Used by NNUE + + Eval::NNUE::AccumulatorCaches refreshTable; + const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; From 886ed90ec3599cdf0dc4e7d07b0543a27028c6c0 Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:27:40 +0100 Subject: [PATCH 0471/1309] Use less time on recaptures Credit for the idea goes to peregrine on discord. Passed STC 10+0.1: https://tests.stockfishchess.org/tests/view/662652623fe04ce4cefc48cf LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 75712 W: 19793 L: 19423 D: 36496 Ptnml(0-2): 258, 8487, 20023, 8803, 285 Passed LTC 60+0.6: https://tests.stockfishchess.org/tests/view/6627495e3fe04ce4cefc59b6 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 49788 W: 12743 L: 12404 D: 24641 Ptnml(0-2): 29, 5141, 14215, 5480, 29 The code was updated slightly and tested for non-regression against the original code at STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 41952 W: 10912 L: 10698 D: 20342 Ptnml(0-2): 133, 4825, 10835, 5061, 122 https://tests.stockfishchess.org/tests/view/662d84f56115ff6764c7e438 closes https://github.com/official-stockfish/Stockfish/pull/5189 Bench: 1836777 --- src/engine.cpp | 12 ++++++++++-- src/engine.h | 11 +++++++---- src/search.cpp | 7 ++++--- src/search.h | 4 ++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 4625e00a816..72a37ce9b0b 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -53,6 +53,7 @@ Engine::Engine(std::string path) : NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { pos.set(StartFEN, false, &states->back()); + capSq = SQ_NONE; } std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { @@ -61,9 +62,10 @@ std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960 return Benchmark::perft(fen, depth, isChess960); } -void Engine::go(const Search::LimitsType& limits) { +void Engine::go(Search::LimitsType& limits) { assert(limits.perft == 0); verify_networks(); + limits.capSq = capSq; threads.start_thinking(options, pos, states, limits); } @@ -102,6 +104,7 @@ void Engine::set_position(const std::string& fen, const std::vector states = StateListPtr(new std::deque(1)); pos.set(fen, options["UCI_Chess960"], &states->back()); + capSq = SQ_NONE; for (const auto& move : moves) { auto m = UCIEngine::to_move(pos, move); @@ -111,6 +114,11 @@ void Engine::set_position(const std::string& fen, const std::vector states->emplace_back(); pos.do_move(m, states->back()); + + capSq = SQ_NONE; + DirtyPiece& dp = states->back().dirtyPiece; + if (dp.dirty_num > 1 && dp.to[1] == SQ_NONE) + capSq = m.to_sq(); } } @@ -172,4 +180,4 @@ std::string Engine::visualize() const { return ss.str(); } -} \ No newline at end of file +} diff --git a/src/engine.h b/src/engine.h index 041f5678585..64a814cb4aa 100644 --- a/src/engine.h +++ b/src/engine.h @@ -20,24 +20,26 @@ #define ENGINE_H_INCLUDED #include +#include #include #include #include #include #include #include -#include #include "nnue/network.h" #include "position.h" #include "search.h" +#include "syzygy/tbprobe.h" // for Stockfish::Depth #include "thread.h" #include "tt.h" #include "ucioption.h" -#include "syzygy/tbprobe.h" // for Stockfish::Depth namespace Stockfish { +enum Square : int; + class Engine { public: using InfoShort = Search::InfoShort; @@ -50,7 +52,7 @@ class Engine { std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960); // non blocking call to start searching - void go(const Search::LimitsType&); + void go(Search::LimitsType&); // non blocking call to stop searching void stop(); @@ -92,6 +94,7 @@ class Engine { Position pos; StateListPtr states; + Square capSq; OptionsMap options; ThreadPool threads; @@ -104,4 +107,4 @@ class Engine { } // namespace Stockfish -#endif // #ifndef ENGINE_H_INCLUDED \ No newline at end of file +#endif // #ifndef ENGINE_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 893daab20e6..396e5aa06c8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -54,8 +54,8 @@ using namespace Search; namespace { -static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971, - 1.002, 0.992, 0.947, 1.046, 1.001}; +static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, + 0.942, 0.933, 0.890, 0.984, 0.941}; // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { @@ -446,9 +446,10 @@ void Search::Worker::iterative_deepening() { double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); int el = std::clamp((bestValue + 750) / 150, 0, 9); + double recapture = limits.capSq == rootMoves[0].pv[0].to_sq() ? 0.955 : 1.005; double totalTime = mainThread->tm.optimum() * fallingEval * reduction - * bestMoveInstability * EvalLevel[el]; + * bestMoveInstability * EvalLevel[el] * recapture; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) diff --git a/src/search.h b/src/search.h index 0fd778b47e6..9b3528c8741 100644 --- a/src/search.h +++ b/src/search.h @@ -109,8 +109,7 @@ struct RootMove { using RootMoves = std::vector; -// LimitsType struct stores information sent by GUI about available time to -// search the current move, maximum depth/time, or if we are in analysis mode. +// LimitsType struct stores information sent by the caller about the analysis required. struct LimitsType { // Init explicitly due to broken value-initialization of non POD in MSVC @@ -128,6 +127,7 @@ struct LimitsType { int movestogo, depth, mate, perft, infinite; uint64_t nodes; bool ponderMode; + Square capSq; }; From 3502c8ae426506453ca64e87e48d962b327c2356 Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 25 Apr 2024 19:20:57 +0200 Subject: [PATCH 0472/1309] Fix missing initialization of AccumulatorCaches in Eval::trace Add a constructor to `AccumulatorCaches` instead of just calling `clear(networks)` to prevent similar issues from appearing in the future. fixes https://github.com/official-stockfish/Stockfish/issues/5190 closes https://github.com/official-stockfish/Stockfish/pull/5191 No functional change --- src/evaluate.cpp | 2 +- src/nnue/nnue_accumulator.h | 5 +++++ src/search.cpp | 4 ++-- src/search.h | 7 +++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index f5746ca5199..6e101e7830a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -99,7 +99,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Trace scores are from white's point of view std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { - auto caches = std::make_unique(); + auto caches = std::make_unique(networks); if (pos.checkers()) return "Final evaluation: none (in check)"; diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 8d73dbef5ad..f65385688de 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -50,6 +50,11 @@ struct alignas(CacheLineSize) Accumulator { // is commonly referred to as "Finny Tables". struct AccumulatorCaches { + template + AccumulatorCaches(const Networks& networks) { + clear(networks); + } + template struct alignas(CacheLineSize) Cache { diff --git a/src/search.cpp b/src/search.cpp index 396e5aa06c8..11373707b34 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -137,11 +137,11 @@ Search::Worker::Worker(SharedState& sharedState, // Unpack the SharedState struct into member variables thread_idx(thread_id), manager(std::move(sm)), - refreshTable(), options(sharedState.options), threads(sharedState.threads), tt(sharedState.tt), - networks(sharedState.networks) { + networks(sharedState.networks), + refreshTable(networks) { clear(); } diff --git a/src/search.h b/src/search.h index 9b3528c8741..444e3b8bb1d 100644 --- a/src/search.h +++ b/src/search.h @@ -302,15 +302,14 @@ class Worker { Tablebases::Config tbConfig; - // Used by NNUE - - Eval::NNUE::AccumulatorCaches refreshTable; - const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; const Eval::NNUE::Networks& networks; + // Used by NNUE + Eval::NNUE::AccumulatorCaches refreshTable; + friend class Stockfish::ThreadPool; friend class SearchManager; }; From bc45cbc820a53a9fc405c06ca67bd7be3970344e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 27 Apr 2024 18:09:45 +0200 Subject: [PATCH 0473/1309] Output some basic info about the used networks Adds size in memory as well as layer sizes as in info string NNUE evaluation using nn-ae6a388e4a1a.nnue (132MiB, (22528, 3072, 15, 32, 1)) info string NNUE evaluation using nn-baff1ede1f90.nnue (6MiB, (22528, 128, 15, 32, 1)) For example, the size in MiB is useful to keep the fishtest memory sizes up-to-date, the L1-L3 sizes give a useful hint about the architecture used. closes https://github.com/official-stockfish/Stockfish/pull/5193 No functional change --- src/nnue/network.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 656ad97a1e3..42320bae1ab 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -252,7 +252,11 @@ void Network::verify(std::string evalfilePath) const { exit(EXIT_FAILURE); } - sync_cout << "info string NNUE evaluation using " << evalfilePath << sync_endl; + size_t size = sizeof(*featureTransformer) + sizeof(*network) * LayerStacks; + sync_cout << "info string NNUE evaluation using " << evalfilePath << " (" + << size / (1024 * 1024) << "MiB, (" << featureTransformer->InputDimensions << ", " + << network[0]->TransformedFeatureDimensions << ", " << network[0]->FC_0_OUTPUTS + << ", " << network[0]->FC_1_OUTPUTS << ", 1))" << sync_endl; } From 940a3a7383f48cea7aacbbe335671aa0d3ead1ae Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 25 Apr 2024 18:20:08 -0700 Subject: [PATCH 0474/1309] Cache small net w/ psqtOnly support Caching the small net in the same way as the big net allows them to share the same code path and completely removes update_accumulator_refresh(). STC: https://tests.stockfishchess.org/tests/view/662bfb5ed46f72253dcfed85 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 151712 W: 39252 L: 39158 D: 73302 Ptnml(0-2): 565, 17474, 39683, 17570, 564 closes https://github.com/official-stockfish/Stockfish/pull/5194 Bench: 1836777 --- src/evaluate.cpp | 2 +- src/nnue/network.cpp | 4 +- src/nnue/network.h | 2 +- src/nnue/nnue_accumulator.h | 6 +- src/nnue/nnue_feature_transformer.h | 263 ++++++++-------------------- src/nnue/nnue_misc.cpp | 2 +- 6 files changed, 86 insertions(+), 193 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6e101e7830a..345925f6b2a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -60,7 +60,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, nullptr, true, &nnueComplexity, psqtOnly) + Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 42320bae1ab..2eca18bd15d 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -263,8 +263,8 @@ void Network::verify(std::string evalfilePath) const { template void Network::hint_common_access(const Position& pos, AccumulatorCaches::Cache* cache, - bool psqtOnl) const { - featureTransformer->hint_common_access(pos, cache, psqtOnl); + bool psqtOnly) const { + featureTransformer->hint_common_access(pos, cache, psqtOnly); } template diff --git a/src/nnue/network.h b/src/nnue/network.h index df59732d955..053b7d19c82 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -62,7 +62,7 @@ class Network { void hint_common_access(const Position& pos, AccumulatorCaches::Cache* cache, - bool psqtOnl) const; + bool psqtOnly) const; void verify(std::string evalfilePath) const; NnueEvalTrace trace_evaluate(const Position& pos, diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index f65385688de..dd313958fe6 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -63,6 +63,7 @@ struct AccumulatorCaches { PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets]; Bitboard byColorBB[COLOR_NB][COLOR_NB]; Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB]; + bool psqtOnly; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top @@ -70,6 +71,7 @@ struct AccumulatorCaches { std::memset(byColorBB, 0, sizeof(byColorBB)); std::memset(byTypeBB, 0, sizeof(byTypeBB)); + psqtOnly = false; std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType)); std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType)); @@ -97,11 +99,11 @@ struct AccumulatorCaches { template void clear(const Networks& networks) { big.clear(networks.big); + small.clear(networks.small); } - // When adding a new cache for a network, i.e. the smallnet - // the appropriate condition must be added to FeatureTransformer::update_accumulator_refresh. Cache big; + Cache small; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 88f0e4031a4..60957ebeb77 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -656,75 +656,84 @@ class FeatureTransformer { template void update_accumulator_refresh_cache(const Position& pos, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache* cache, + bool psqtOnly) const { assert(cache != nullptr); Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq]; - - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; - accumulator.computedPSQT[Perspective] = true; - FeatureSet::IndexList removed, added; - for (Color c : {WHITE, BLACK}) + + if (entry.psqtOnly && !psqtOnly) { - for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.clear(biases); + FeatureSet::append_active_indices(pos, added); + } + else + { + for (Color c : {WHITE, BLACK}) { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = - entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; - - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) + for (PieceType pt = PAWN; pt <= KING; ++pt) { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = + entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } } } } + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = !psqtOnly; + accumulator.computedPSQT[Perspective] = true; + #ifdef VECTOR vec_t acc[NumRegs]; psqt_vec_t psqt[NumPsqtRegs]; - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto entryTile = - reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = entryTile[k]; - - for (int i = 0; i < int(added.size()); ++i) + if (!psqtOnly) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + auto entryTile = + reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = entryTile[k]; - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - for (int i = 0; i < int(removed.size()); ++i) - { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + for (int i = 0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + for (int i = 0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; k++) - vec_store(&entryTile[k], acc[k]); - } + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + for (IndexType k = 0; k < NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -760,18 +769,24 @@ class FeatureTransformer { for (const auto index : added) { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] += weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] += weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } for (const auto index : removed) { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] -= weights[offset + j]; + if (!psqtOnly) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[Perspective][j] -= weights[offset + j]; + } for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; @@ -782,144 +797,20 @@ class FeatureTransformer { // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing + if (!psqtOnly) + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], + sizeof(BiasType) * HalfDimensions); + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective], sizeof(int32_t) * PSQTBuckets); - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], - sizeof(BiasType) * HalfDimensions); - for (Color c : {WHITE, BLACK}) entry.byColorBB[Perspective][c] = pos.pieces(c); for (PieceType pt = PAWN; pt <= KING; ++pt) entry.byTypeBB[Perspective][pt] = pos.pieces(pt); - } - - template - void - update_accumulator_refresh(const Position& pos, - [[maybe_unused]] AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - - // When we are refreshing the accumulator of the big net, - // redirect to the version of refresh that uses the refresh table. - // Using the cache for the small net is not beneficial. - if constexpr (HalfDimensions == Eval::NNUE::TransformedFeatureDimensionsBig) - { - update_accumulator_refresh_cache(pos, cache); - return; - } -#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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; -#endif - - // Refresh the accumulator - // Could be extracted to a separate function because it's done in 2 places, - // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = !psqtOnly; - accumulator.computedPSQT[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); - -#ifdef VECTOR - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - int i = 0; - for (; i < int(active.size()) - 1; i += 2) - { - IndexType index0 = active[i]; - IndexType index1 = active[i + 1]; - const IndexType offset0 = HalfDimensions * index0 + j * TileHeight; - const IndexType offset1 = HalfDimensions * index1 + j * TileHeight; - auto column0 = reinterpret_cast(&weights[offset0]); - auto column1 = reinterpret_cast(&weights[offset1]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_add_16(column0[k], column1[k])); - } - for (; i < int(active.size()); ++i) - { - IndexType index = active[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - auto accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); - - int i = 0; - for (; i < int(active.size()) - 1; i += 2) - { - IndexType index0 = active[i]; - IndexType index1 = active[i + 1]; - const IndexType offset0 = PSQTBuckets * index0 + j * PsqtTileHeight; - const IndexType offset1 = PSQTBuckets * index1 + j * PsqtTileHeight; - auto columnPsqt0 = reinterpret_cast(&psqtWeights[offset0]); - auto columnPsqt1 = reinterpret_cast(&psqtWeights[offset1]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = - vec_add_psqt_32(psqt[k], vec_add_psqt_32(columnPsqt0[k], columnPsqt1[k])); - } - for (; i < int(active.size()); ++i) - { - IndexType index = active[i]; - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - -#else - if (!psqtOnly) - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; - - for (const auto index : active) - { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; - } - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += - psqtWeights[index * PSQTBuckets + k]; - } -#endif + entry.psqtOnly = psqtOnly; } template @@ -948,7 +839,7 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache, psqtOnly); } template @@ -976,7 +867,7 @@ class FeatureTransformer { psqtOnly); } else - update_accumulator_refresh(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache, psqtOnly); } template diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 51838fefa44..e92dcc71086 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -48,7 +48,7 @@ void hint_common_parent_position(const Position& pos, int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, nullptr, simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, &caches.small, simpleEvalAbs > Eval::PsqtOnlyThreshold); else networks.big.hint_common_access(pos, &caches.big, false); } From a129c0695be921acfbb3f5c966eef756d0b6f843 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 28 Apr 2024 10:28:25 -0700 Subject: [PATCH 0475/1309] Combine remove and add in update_accumulator_refresh_cache() Combine remove and add in update_accumulator_refresh_cache(). Move remove before add to match other parts of the code. STC: https://tests.stockfishchess.org/tests/view/662d96dc6115ff6764c7f4ca LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 364032 W: 94421 L: 93624 D: 175987 Ptnml(0-2): 1261, 41983, 94811, 42620, 1341 closes https://github.com/official-stockfish/Stockfish/pull/5194 Bench: 1836777 --- src/evaluate.cpp | 5 +-- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_feature_transformer.h | 53 ++++++++++++++++++----------- src/nnue/nnue_misc.cpp | 3 +- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 345925f6b2a..fe6b83aa111 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -60,8 +60,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) - : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); + Value nnue = smallNet + ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) + : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, int pawnCountMul, int npmConstant, int evalDiv, diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index dd313958fe6..a2b3b98988e 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -102,7 +102,7 @@ struct AccumulatorCaches { small.clear(networks.small); } - Cache big; + Cache big; Cache small; }; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 60957ebeb77..6b3f78a9a4b 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -660,8 +660,8 @@ class FeatureTransformer { bool psqtOnly) const { assert(cache != nullptr); - Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq]; + Square ksq = pos.square(Perspective); + auto& entry = (*cache)[ksq]; FeatureSet::IndexList removed, added; if (entry.psqtOnly && !psqtOnly) @@ -712,16 +712,20 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; - for (int i = 0; i < int(added.size()); ++i) + int i0 = 0; + for (; i0 < int(std::min(removed.size(), added.size())); ++i0) { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + IndexType indexR = removed[i0]; + const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; + auto columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i0]; + const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; + auto columnA = reinterpret_cast(&weights[offsetA]); for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); } - for (int i = 0; i < int(removed.size()); ++i) + for (int i = i0; i < int(removed.size()); ++i) { IndexType index = removed[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; @@ -730,6 +734,15 @@ class FeatureTransformer { for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } + for (int i = i0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } for (IndexType k = 0; k < NumRegs; k++) vec_store(&entryTile[k], acc[k]); @@ -742,23 +755,23 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; - for (int i = 0; i < int(added.size()); ++i) + for (int i = 0; i < int(removed.size()); ++i) { - IndexType index = added[i]; + IndexType index = removed[i]; const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } - for (int i = 0; i < int(removed.size()); ++i) + for (int i = 0; i < int(added.size()); ++i) { - IndexType index = removed[i]; + IndexType index = added[i]; const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } for (std::size_t k = 0; k < NumPsqtRegs; ++k) @@ -767,29 +780,29 @@ class FeatureTransformer { #else - for (const auto index : added) + for (const auto index : removed) { if (!psqtOnly) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] += weights[offset + j]; + entry.accumulation[Perspective][j] -= weights[offset + j]; } for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; } - for (const auto index : removed) + for (const auto index : added) { if (!psqtOnly) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] -= weights[offset + j]; + entry.accumulation[Perspective][j] += weights[offset + j]; } for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; } #endif diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index e92dcc71086..21685d0f2a3 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -48,7 +48,8 @@ void hint_common_parent_position(const Position& pos, int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, &caches.small, simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, &caches.small, + simpleEvalAbs > Eval::PsqtOnlyThreshold); else networks.big.hint_common_access(pos, &caches.big, false); } From 834e8ff619b212baf402c3922f8fde9af979cd0c Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 28 Apr 2024 08:53:28 +0800 Subject: [PATCH 0476/1309] Penalise the TT move in multicut Passed STC: LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 185504 W: 48079 L: 47533 D: 89892 Ptnml(0-2): 716, 21866, 46988, 22520, 662 https://tests.stockfishchess.org/tests/view/662d9e1d6115ff6764c7f83d Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 75612 W: 19351 L: 18948 D: 37313 Ptnml(0-2): 46, 8363, 20592, 8752, 53 https://tests.stockfishchess.org/tests/view/662dc9dc6115ff6764c80fea closes https://github.com/official-stockfish/Stockfish/pull/5195 Bench: 1415435 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 11373707b34..ad59b35a545 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,7 +1067,12 @@ Value Search::Worker::search( // we assume this expected cut-node is not singular (multiple moves fail high), // and we can prune the whole subtree by returning a softbound. else if (singularBeta >= beta) + { + if (!ttCapture) + update_quiet_stats(pos, ss, *this, ttMove, -stat_malus(depth)); + return singularBeta; + } // Negative extensions // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, From 48a3b7c0ee7d32441a5a4519c85bd1e93e467f6e Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 28 Apr 2024 16:04:28 +0200 Subject: [PATCH 0477/1309] Simplify non-pawn material divisor to a constant Passed STC: https://tests.stockfishchess.org/tests/view/662942603fe04ce4cefc7aba LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 272832 W: 70456 L: 70497 D: 131879 Ptnml(0-2): 1020, 32619, 69154, 32628, 995 Passed LTC: https://tests.stockfishchess.org/tests/view/662dfe3b6115ff6764c829eb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 100254 W: 25446 L: 25303 D: 49505 Ptnml(0-2): 121, 11292, 27166, 11419, 129 closes https://github.com/official-stockfish/Stockfish/pull/5198 Bench: 1544645 --- src/evaluate.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index fe6b83aa111..1d41f3a266b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -64,14 +64,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); - const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, - int pawnCountMul, int npmConstant, int evalDiv, - int shufflingConstant, int shufflingDiv) { + const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, + int npmConstant, int evalDiv, int shufflingConstant, + int shufflingDiv) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; - int npm = pos.non_pawn_material() / npmDiv; + int npm = pos.non_pawn_material() / 64; v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) + optimism * (npmConstant + npm)) / evalDiv; @@ -82,11 +82,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, }; if (!smallNet) - adjustEval(524, 32395, 66, 942, 11, 139, 1058, 178, 204); + adjustEval(524, 32395, 942, 11, 139, 1058, 178, 204); else if (psqtOnly) - adjustEval(517, 32857, 65, 908, 7, 155, 1006, 224, 238); + adjustEval(517, 32857, 908, 7, 155, 1006, 224, 238); else - adjustEval(515, 32793, 63, 944, 9, 140, 1067, 206, 206); + adjustEval(515, 32793, 944, 9, 140, 1067, 206, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 0fe64286457549d2f80cd7792088375aaa9bee55 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 28 Apr 2024 16:53:47 +0200 Subject: [PATCH 0478/1309] More reduction at cut nodes which are not a former PV node But the tt move and first killer are excluded. This idea is based on following LMR condition tuning https://tests.stockfishchess.org/tests/view/66228bed3fe04ce4cefc0c71 by using only the two largest terms P[0] and P[1]. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 173248 W: 45091 L: 44565 D: 83592 Ptnml(0-2): 693, 20534, 43673, 21002, 722 https://tests.stockfishchess.org/tests/view/6629603b3fe04ce4cefc7d37 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 722394 W: 183231 L: 181487 D: 357676 Ptnml(0-2): 462, 80650, 197252, 82348, 485 https://tests.stockfishchess.org/tests/view/662cbe45d46f72253dcff7bf closes https://github.com/official-stockfish/Stockfish/pull/5199 Bench: 1619613 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index ad59b35a545..3718c37813b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1123,6 +1123,9 @@ Value Search::Worker::search( if (ss->ttPv) r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); + else if (cutNode && move != ttMove && move != ss->killers[0]) + r++; + // Increase reduction for cut nodes (~4 Elo) if (cutNode) r += 2 - (tte->depth() >= depth && ss->ttPv); From 5d720325596699ceba2743776cb39f9cea1754f5 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 20 Apr 2024 00:29:01 -0500 Subject: [PATCH 0479/1309] Use capture history to better judge which sacrifices to explore This idea has been bouncing around a while. @Vizvezdenec tried it a couple years ago in Stockfish without results, but its recent arrival in Ethereal inspired him and thence me to try it afresh in Stockfish. (Also factor out the now-common code with futpruning for captures.) STC: https://tests.stockfishchess.org/tests/view/662355bc3fe04ce4cefc18ac LLR: 2.92 (-2.94,2.94) <0.00,2.00> Total: 45760 W: 11970 L: 11640 D: 22150 Ptnml(0-2): 124, 5371, 11625, 5571, 189 LTC: https://tests.stockfishchess.org/tests/view/662dda396115ff6764c817c9 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 243828 W: 62042 L: 61287 D: 120499 Ptnml(0-2): 211, 27202, 66329, 27965, 207 closes https://github.com/official-stockfish/Stockfish/pull/5200 Bench: 1480008 --- src/search.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3718c37813b..e4f170be61d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -967,20 +967,22 @@ Value Search::Worker::search( if (capture || givesCheck) { + Piece capturedPiece = pos.piece_on(move.to_sq()); + int captHist = + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; + // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Piece capturedPiece = pos.piece_on(move.to_sq()); - Value futilityValue = - ss->staticEval + 285 + 277 * lmrDepth + PieceValue[capturedPiece] - + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] - / 7; + Value futilityValue = ss->staticEval + 285 + 277 * lmrDepth + + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -203 * depth)) + int seeHist = std::clamp(captHist / 32, -199 * depth, 199 * depth); + if (!pos.see_ge(move, -203 * depth - seeHist)) continue; } else From eb20de36c05b4101af37b2bf3783c570a47bb1cc Mon Sep 17 00:00:00 2001 From: Ciekce <44617491+Ciekce@users.noreply.github.com> Date: Mon, 29 Apr 2024 01:45:56 +0100 Subject: [PATCH 0480/1309] Avoid unnecessary creation of accumulator cache Saves a (currently) 800 KB allocation and deallocation when running `eval`, not particularly significant and zero impact on play but not necessary either. closes https://github.com/official-stockfish/Stockfish/pull/5201 No functional change --- AUTHORS | 1 + src/evaluate.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index abae401c1ef..36b2b6f7942 100644 --- a/AUTHORS +++ b/AUTHORS @@ -46,6 +46,7 @@ Bryan Cross (crossbr) candirufish Chess13234 Chris Cain (ceebo) +Ciekce clefrks Clemens L. (rn5f107s2) Cody Ho (aesrentai) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1d41f3a266b..e3aa249ca41 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -100,11 +100,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Trace scores are from white's point of view std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { - auto caches = std::make_unique(networks); - if (pos.checkers()) return "Final evaluation: none (in check)"; + auto caches = std::make_unique(networks); + std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; From 6a9b8a0c7b913b9d4c4474bae7804184d20e8c4a Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 28 Apr 2024 16:33:59 +0800 Subject: [PATCH 0481/1309] Optimise NNUE Accumulator updates Passed STC: https://tests.stockfishchess.org/tests/view/662e3c6a5e9274400985a741 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 86176 W: 22284 L: 21905 D: 41987 Ptnml(0-2): 254, 9572, 23051, 9963, 248 closes https://github.com/official-stockfish/Stockfish/pull/5202 No functional change --- src/nnue/nnue_feature_transformer.h | 76 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 6b3f78a9a4b..402a47a815d 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -404,19 +404,25 @@ class FeatureTransformer { return {st, next}; } - // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. + // NOTE: The parameter states_to_update is an array of position states. // All states must be sequential, that is states_to_update[i] must either be reachable - // by repeatedly applying ->previous from states_to_update[i+1] or - // states_to_update[i] == nullptr. + // by repeatedly applying ->previous from states_to_update[i+1]. // computed_st must be reachable by repeatedly applying ->previous on - // states_to_update[0], if not nullptr. + // states_to_update[0]. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N], bool psqtOnly) const { static_assert(N > 0); - assert(states_to_update[N - 1] == nullptr); + assert([&]() { + for (size_t i = 0; i < N; ++i) + { + if (states_to_update[i] == nullptr) + return false; + } + return true; + }()); #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array @@ -425,11 +431,7 @@ class FeatureTransformer { psqt_vec_t psqt[NumPsqtRegs]; #endif - if (states_to_update[0] == nullptr) - return; - // Update incrementally going back through states_to_update. - // Gather all features to be updated. const Square ksq = pos.square(Perspective); @@ -437,28 +439,18 @@ class FeatureTransformer { // 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. - FeatureSet::IndexList removed[N - 1], added[N - 1]; + FeatureSet::IndexList removed[N], added[N]; + for (int i = N - 1; i >= 0; --i) { - int i = - N - - 2; // Last potential state to update. Skip last element because it must be nullptr. - while (states_to_update[i] == nullptr) - --i; - - StateInfo* st2 = states_to_update[i]; - - for (; i >= 0; --i) - { - (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; - (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; + (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; + (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; - const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - for (; st2 != end_state; st2 = st2->previous) - FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, - removed[i], added[i]); - } + for (StateInfo* st2 = states_to_update[i]; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, removed[i], + added[i]); } StateInfo* st = computed_st; @@ -466,8 +458,7 @@ class FeatureTransformer { // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2) - && added[0].size() == 1) + if (N == 1 && (removed[0].size() == 1 || removed[0].size() == 2) && added[0].size() == 1) { assert(states_to_update[0]); @@ -541,7 +532,7 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_load(&accTileIn[k]); - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { // Difference calculation for the deactivated features for (const auto index : removed[i]) @@ -578,7 +569,7 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { // Difference calculation for the deactivated features for (const auto index : removed[i]) @@ -608,7 +599,7 @@ class FeatureTransformer { } } #else - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { if (!psqtOnly) std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], @@ -847,8 +838,8 @@ class FeatureTransformer { || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) { // Only update current position accumulator to minimize work. - StateInfo* states_to_update[2] = {pos.state(), nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update, + StateInfo* states_to_update[1] = {pos.state()}; + update_accumulator_incremental(pos, oldest_st, states_to_update, psqtOnly); } else @@ -873,11 +864,20 @@ class FeatureTransformer { // 1. for the current position // 2. the next accumulator after the computed one // The heuristic may change in the future. - StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), - nullptr}; + if (next == pos.state()) + { + StateInfo* states_to_update[1] = {next}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); + } + else + { + StateInfo* states_to_update[2] = {next, pos.state()}; + + update_accumulator_incremental(pos, oldest_st, states_to_update, + psqtOnly); + } } else update_accumulator_refresh_cache(pos, cache, psqtOnly); From be142337d843ef3afc675e27628ab8e896c32cce Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 29 Apr 2024 20:37:54 -0700 Subject: [PATCH 0482/1309] Accumulator cache bugfix and cleanup STC: https://tests.stockfishchess.org/tests/view/663068913a05f1bf7a511dc2 LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 70304 W: 18211 L: 18026 D: 34067 Ptnml(0-2): 232, 7966, 18582, 8129, 243 1) Fixes a bug introduced in https://github.com/official-stockfish/Stockfish/pull/5194. Only one psqtOnly flag was used for two perspectives which was causing wrong entries to be cleared and marked. 2) The finny caches should be cleared like histories and not at the start of every search. closes https://github.com/official-stockfish/Stockfish/pull/5203 No functional change --- src/nnue/nnue_accumulator.h | 28 ++++++++++++--------------- src/nnue/nnue_feature_transformer.h | 30 ++++++++++++++--------------- src/search.cpp | 5 ++--- 3 files changed, 28 insertions(+), 35 deletions(-) diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index a2b3b98988e..179feba553e 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -59,31 +59,27 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Cache { struct alignas(CacheLineSize) Entry { - BiasType accumulation[COLOR_NB][Size]; - PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets]; - Bitboard byColorBB[COLOR_NB][COLOR_NB]; - Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB]; + BiasType accumulation[Size]; + PSQTWeightType psqtAccumulation[PSQTBuckets]; + Bitboard byColorBB[COLOR_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; bool psqtOnly; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top void clear(const BiasType* biases) { - std::memset(byColorBB, 0, sizeof(byColorBB)); - std::memset(byTypeBB, 0, sizeof(byTypeBB)); - psqtOnly = false; - - std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType)); - std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType)); - - std::memset(psqtAccumulation, 0, sizeof(psqtAccumulation)); + std::memcpy(accumulation, biases, sizeof(accumulation)); + std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, + sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; template void clear(const Network& network) { - for (auto& entry : entries) - entry.clear(network.featureTransformer->biases); + for (auto& entries1D : entries) + for (auto& entry : entries1D) + entry.clear(network.featureTransformer->biases); } void clear(const BiasType* biases) { @@ -91,9 +87,9 @@ struct AccumulatorCaches { entry.clear(biases); } - Entry& operator[](Square sq) { return entries[sq]; } + std::array& operator[](Square sq) { return entries[sq]; } - std::array entries; + std::array, SQUARE_NB> entries; }; template diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 402a47a815d..4647ecd066d 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -652,7 +652,7 @@ class FeatureTransformer { assert(cache != nullptr); Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq]; + auto& entry = (*cache)[ksq][Perspective]; FeatureSet::IndexList removed, added; if (entry.psqtOnly && !psqtOnly) @@ -666,9 +666,8 @@ class FeatureTransformer { { for (PieceType pt = PAWN; pt <= KING; ++pt) { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = - entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; const Bitboard newBB = pos.pieces(c, pt); Bitboard toRemove = oldBB & ~newBB; Bitboard toAdd = newBB & ~oldBB; @@ -698,8 +697,7 @@ class FeatureTransformer { if (!psqtOnly) for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - auto entryTile = - reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); + auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; @@ -741,8 +739,8 @@ class FeatureTransformer { for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - auto entryTilePsqt = reinterpret_cast( - &entry.psqtAccumulation[Perspective][j * PsqtTileHeight]); + auto entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; @@ -777,11 +775,11 @@ class FeatureTransformer { { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] -= weights[offset + j]; + entry.accumulation[j] -= weights[offset + j]; } for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; } for (const auto index : added) { @@ -789,11 +787,11 @@ class FeatureTransformer { { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] += weights[offset + j]; + entry.accumulation[j] += weights[offset + j]; } for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; + entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; } #endif @@ -802,17 +800,17 @@ class FeatureTransformer { // Now copy its content to the actual accumulator we were refreshing if (!psqtOnly) - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, sizeof(BiasType) * HalfDimensions); - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective], + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, sizeof(int32_t) * PSQTBuckets); for (Color c : {WHITE, BLACK}) - entry.byColorBB[Perspective][c] = pos.pieces(c); + entry.byColorBB[c] = pos.pieces(c); for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[Perspective][pt] = pos.pieces(pt); + entry.byTypeBB[pt] = pos.pieces(pt); entry.psqtOnly = psqtOnly; } diff --git a/src/search.cpp b/src/search.cpp index e4f170be61d..b8e515f0267 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -147,9 +147,6 @@ Search::Worker::Worker(SharedState& sharedState, void Search::Worker::start_searching() { - // Initialize accumulator refresh entries - refreshTable.clear(networks); - // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -506,6 +503,8 @@ void Search::Worker::clear() { for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((20.14 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + + refreshTable.clear(networks); } From be026bdcb2c71501dffab4a04dabef682661e664 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 1 May 2024 15:10:23 +0200 Subject: [PATCH 0483/1309] Clear Workers after changing the network ensures internal state (e.g. accumulator cache) is consistent with network closes https://github.com/official-stockfish/Stockfish/pull/5204 No functional change --- src/engine.cpp | 10 +++++++--- src/thread.cpp | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 72a37ce9b0b..e8da24aa9e8 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -141,14 +141,18 @@ void Engine::verify_networks() const { } void Engine::load_networks() { - networks.big.load(binaryDirectory, options["EvalFile"]); - networks.small.load(binaryDirectory, options["EvalFileSmall"]); + load_big_network(options["EvalFile"]); + load_small_network(options["EvalFileSmall"]); } -void Engine::load_big_network(const std::string& file) { networks.big.load(binaryDirectory, file); } +void Engine::load_big_network(const std::string& file) { + networks.big.load(binaryDirectory, file); + threads.clear(); +} void Engine::load_small_network(const std::string& file) { networks.small.load(binaryDirectory, file); + threads.clear(); } void Engine::save_network(const std::pair, std::string> files[2]) { diff --git a/src/thread.cpp b/src/thread.cpp index 1438c9f9d5c..9052654baf6 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -161,6 +161,9 @@ void ThreadPool::clear() { for (Thread* th : threads) th->worker->clear(); + if (threads.size() == 0) + return; + main_manager()->callsCnt = 0; main_manager()->bestPreviousScore = VALUE_INFINITE; main_manager()->bestPreviousAverageScore = VALUE_INFINITE; From 8ee9905d8beddc01fa70e39c439b076c2d661acb Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sat, 4 May 2024 09:52:27 +0800 Subject: [PATCH 0484/1309] Remove PSQT-only mode Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94208 W: 24270 L: 24112 D: 45826 Ptnml(0-2): 286, 11186, 24009, 11330, 293 https://tests.stockfishchess.org/tests/view/6635ddd773559a8aa8582826 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 114960 W: 29107 L: 28982 D: 56871 Ptnml(0-2): 37, 12683, 31924, 12790, 46 https://tests.stockfishchess.org/tests/view/663604a973559a8aa85881ed closes #5214 Bench 1653939 --- src/evaluate.cpp | 8 +- src/evaluate.h | 2 +- src/nnue/network.cpp | 21 +- src/nnue/network.h | 6 +- src/nnue/nnue_accumulator.h | 2 - src/nnue/nnue_feature_transformer.h | 340 ++++++++++++---------------- src/nnue/nnue_misc.cpp | 13 +- src/position.cpp | 18 +- 8 files changed, 169 insertions(+), 241 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e3aa249ca41..11999b554b7 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -56,13 +56,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = std::abs(simpleEval) > SmallNetThreshold; - bool psqtOnly = std::abs(simpleEval) > PsqtOnlyThreshold; int nnueComplexity; int v; - Value nnue = smallNet - ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity, psqtOnly) - : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) + : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, int npmConstant, int evalDiv, int shufflingConstant, @@ -83,8 +81,6 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, if (!smallNet) adjustEval(524, 32395, 942, 11, 139, 1058, 178, 204); - else if (psqtOnly) - adjustEval(517, 32857, 908, 7, 155, 1006, 224, 238); else adjustEval(515, 32793, 944, 9, 140, 1067, 206, 206); diff --git a/src/evaluate.h b/src/evaluate.h index 38615ff7d68..2d244ff6722 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389; +constexpr inline int SmallNetThreshold = 1274; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 2eca18bd15d..de2c7eca6d5 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -189,8 +189,7 @@ template Value Network::evaluate(const Position& pos, AccumulatorCaches::Cache* cache, bool adjusted, - int* complexity, - bool psqtOnly) const { + int* complexity) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -210,13 +209,12 @@ Value Network::evaluate(const Position& ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = - featureTransformer->transform(pos, cache, transformedFeatures, bucket, psqtOnly); - const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); + const auto positional = network[bucket]->propagate(transformedFeatures); if (complexity) - *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; + *complexity = std::abs(psqt - positional) / OutputScale; // Give more value to positional evaluation when adjusted flag is set if (adjusted) @@ -261,10 +259,9 @@ void Network::verify(std::string evalfilePath) const { template -void Network::hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - featureTransformer->hint_common_access(pos, cache, psqtOnly); +void Network::hint_common_access( + const Position& pos, AccumulatorCaches::Cache* cache) const { + featureTransformer->hint_common_access(pos, cache); } template @@ -293,7 +290,7 @@ Network::trace_evaluate(const Position& for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, cache, transformedFeatures, bucket, false); + featureTransformer->transform(pos, cache, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); diff --git a/src/nnue/network.h b/src/nnue/network.h index 053b7d19c82..23f56663094 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -56,13 +56,11 @@ class Network { Value evaluate(const Position& pos, AccumulatorCaches::Cache* cache, bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false) const; + int* complexity = nullptr) const; void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const; + AccumulatorCaches::Cache* cache) const; void verify(std::string evalfilePath) const; NnueEvalTrace trace_evaluate(const Position& pos, diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 179feba553e..b8dcf1e480f 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -38,7 +38,6 @@ struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[COLOR_NB][Size]; std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; bool computed[COLOR_NB]; - bool computedPSQT[COLOR_NB]; }; @@ -63,7 +62,6 @@ struct AccumulatorCaches { PSQTWeightType psqtAccumulation[PSQTBuckets]; Bitboard byColorBB[COLOR_NB]; Bitboard byTypeBB[PIECE_TYPE_NB]; - bool psqtOnly; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 4647ecd066d..018b715e638 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -309,10 +309,9 @@ class FeatureTransformer { std::int32_t transform(const Position& pos, AccumulatorCaches::Cache* cache, OutputType* output, - int bucket, - bool psqtOnly) const { - update_accumulator(pos, cache, psqtOnly); - update_accumulator(pos, cache, psqtOnly); + int bucket) const { + update_accumulator(pos, cache); + update_accumulator(pos, cache); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; @@ -320,9 +319,6 @@ class FeatureTransformer { (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; - if (psqtOnly) - return psqt; - const auto& accumulation = (pos.state()->*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) @@ -375,23 +371,20 @@ class FeatureTransformer { } // end of function transform() void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - hint_common_access_for_perspective(pos, cache, psqtOnly); - hint_common_access_for_perspective(pos, cache, psqtOnly); + AccumulatorCaches::Cache* cache) const { + hint_common_access_for_perspective(pos, cache); + hint_common_access_for_perspective(pos, cache); } private: template [[nodiscard]] std::pair - try_find_computed_accumulator(const Position& pos, bool psqtOnly) const { + 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(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); - while (st->previous - && (!(st->*accPtr).computedPSQT[Perspective] - || (!psqtOnly && !(st->*accPtr).computed[Perspective]))) + 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. @@ -412,8 +405,7 @@ class FeatureTransformer { template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, - StateInfo* states_to_update[N], - bool psqtOnly) const { + StateInfo* states_to_update[N]) const { static_assert(N > 0); assert([&]() { for (size_t i = 0; i < N; ++i) @@ -443,8 +435,7 @@ class FeatureTransformer { for (int i = N - 1; i >= 0; --i) { - (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; - (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; + (states_to_update[i]->*accPtr).computed[Perspective] = true; const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; @@ -462,34 +453,31 @@ class FeatureTransformer { { assert(states_to_update[0]); - if (!psqtOnly) - { - auto accIn = - reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + auto accIn = + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); + auto accOut = reinterpret_cast( + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); - const IndexType offsetR0 = HalfDimensions * removed[0][0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; - auto columnA = reinterpret_cast(&weights[offsetA]); + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); - if (removed[0].size() == 1) - { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); - } - else - { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); + if (removed[0].size() == 1) + { + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); - } + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); } auto accPsqtIn = @@ -523,43 +511,41 @@ class FeatureTransformer { } else { - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTileIn = reinterpret_cast( + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; i < N; ++i) { - // Load accumulator - auto accTileIn = reinterpret_cast( - &(st->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } - for (IndexType i = 0; i < N; ++i) + // Difference calculation for the activated features + for (const auto index : added[i]) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - auto accTileOut = - reinterpret_cast(&(states_to_update[i]->*accPtr) - .accumulation[Perspective][j * TileHeight]); + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); + acc[k] = vec_add_16(acc[k], column[k]); } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); } + } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -601,10 +587,8 @@ class FeatureTransformer { #else for (IndexType i = 0; i < N; ++i) { - if (!psqtOnly) - std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], - (st->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = @@ -615,12 +599,9 @@ class FeatureTransformer { // Difference calculation for the deactivated features for (const auto index : removed[i]) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] -= @@ -630,12 +611,9 @@ class FeatureTransformer { // Difference calculation for the activated features for (const auto index : added[i]) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] += @@ -647,96 +625,85 @@ class FeatureTransformer { template void update_accumulator_refresh_cache(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { assert(cache != nullptr); Square ksq = pos.square(Perspective); auto& entry = (*cache)[ksq][Perspective]; FeatureSet::IndexList removed, added; - if (entry.psqtOnly && !psqtOnly) - { - entry.clear(biases); - FeatureSet::append_active_indices(pos, added); - } - else + for (Color c : {WHITE, BLACK}) { - for (Color c : {WHITE, BLACK}) + for (PieceType pt = PAWN; pt <= KING; ++pt) { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); - } + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); } } } - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = !psqtOnly; - accumulator.computedPSQT[Perspective] = true; + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = true; #ifdef VECTOR vec_t acc[NumRegs]; psqt_vec_t psqt[NumPsqtRegs]; - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) - { - auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = entryTile[k]; - - int i0 = 0; - for (; i0 < int(std::min(removed.size(), added.size())); ++i0) - { - IndexType indexR = removed[i0]; - const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; - auto columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i0]; - const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; - auto columnA = reinterpret_cast(&weights[offsetA]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); - } - for (int i = i0; i < int(removed.size()); ++i) - { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = entryTile[k]; - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - for (int i = i0; i < int(added.size()); ++i) - { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + int i0 = 0; + for (; i0 < int(std::min(removed.size(), added.size())); ++i0) + { + IndexType indexR = removed[i0]; + const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; + auto columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i0]; + const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; + auto columnA = reinterpret_cast(&weights[offsetA]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); + } + for (int i = i0; i < int(removed.size()); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (int i = i0; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; k++) - vec_store(&entryTile[k], acc[k]); + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); } + for (IndexType k = 0; k < NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + } + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { auto entryTilePsqt = @@ -771,24 +738,18 @@ class FeatureTransformer { for (const auto index : removed) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] -= weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; } for (const auto index : added) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] += weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; @@ -799,9 +760,8 @@ class FeatureTransformer { // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing - if (!psqtOnly) - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, - sizeof(BiasType) * HalfDimensions); + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + sizeof(BiasType) * HalfDimensions); std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, sizeof(int32_t) * PSQTBuckets); @@ -811,14 +771,11 @@ class FeatureTransformer { for (PieceType pt = PAWN; pt <= KING; ++pt) entry.byTypeBB[pt] = pos.pieces(pt); - - entry.psqtOnly = psqtOnly; } template void hint_common_access_for_perspective(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { // Works like update_accumulator, but performs less work. // Updates ONLY the accumulator for pos. @@ -826,33 +783,28 @@ class FeatureTransformer { // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. // Fast early exit. - if ((pos.state()->*accPtr).computed[Perspective] - || (psqtOnly && (pos.state()->*accPtr).computedPSQT[Perspective])) + if ((pos.state()->*accPtr).computed[Perspective]) return; - auto [oldest_st, _] = try_find_computed_accumulator(pos, psqtOnly); + auto [oldest_st, _] = try_find_computed_accumulator(pos); - if ((oldest_st->*accPtr).computed[Perspective] - || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) + if ((oldest_st->*accPtr).computed[Perspective]) { // Only update current position accumulator to minimize work. StateInfo* states_to_update[1] = {pos.state()}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else - update_accumulator_refresh_cache(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache); } template void update_accumulator(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos, psqtOnly); + auto [oldest_st, next] = try_find_computed_accumulator(pos); - if ((oldest_st->*accPtr).computed[Perspective] - || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) + if ((oldest_st->*accPtr).computed[Perspective]) { if (next == nullptr) return; @@ -866,19 +818,17 @@ class FeatureTransformer { { StateInfo* states_to_update[1] = {next}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update); } else { StateInfo* states_to_update[2] = {next, pos.state()}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update); } } else - update_accumulator_refresh_cache(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache); } template diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 21685d0f2a3..bf73a58bf07 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -48,10 +48,9 @@ void hint_common_parent_position(const Position& pos, int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, &caches.small, - simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, &caches.small); else - networks.big.hint_common_access(pos, &caches.big, false); + networks.big.hint_common_access(pos, &caches.big); } namespace { @@ -149,18 +148,14 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat auto st = pos.state(); pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; Value eval = networks.big.evaluate(pos, &caches.big); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); diff --git a/src/position.cpp b/src/position.cpp index 78e62bda303..b46ba029985 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -680,11 +680,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = - st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; auto& dp = st->dirtyPiece; dp.dirty_num = 1; @@ -968,13 +965,10 @@ void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { newSt.previous = st; 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->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = - st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = - false; + 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) { From 351a2e22dd8ad8bc3b2204e1e80d4d5860a778d6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 4 May 2024 10:33:26 +0300 Subject: [PATCH 0485/1309] Add extra bonuses to some moves that forced a fail low The previous patch on this idea was giving bonuses to this moves if best value of search is far below current static evaluation. This patch does similar thing but adds extra bonus when best value of search is far below static evaluation before previous move. Passed STC: https://tests.stockfishchess.org/tests/view/66355fc819566d64b481d6a4 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 454144 W: 116575 L: 115656 D: 221913 Ptnml(0-2): 1060, 53410, 117215, 54325, 1062 Passed LTC: https://tests.stockfishchess.org/tests/view/6635c61a73559a8aa858012d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 136578 W: 34858 L: 34335 D: 67385 closes https://github.com/official-stockfish/Stockfish/pull/5209 Bench: 1614825 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b8e515f0267..cd80e9392d6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1335,8 +1335,8 @@ Value Search::Worker::search( else if (!priorCapture && prevSq != SQ_NONE) { int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) - + ((ss - 1)->moveCount > 11) - + (!ss->inCheck && bestValue <= ss->staticEval - 142); + + ((ss - 1)->moveCount > 11) + (!ss->inCheck && bestValue <= ss->staticEval - 142) + + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 77); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] From 741aaf8a38c75535e01a3f5506877654547ebb33 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Sat, 4 May 2024 17:29:23 +0100 Subject: [PATCH 0486/1309] Introduce Quadruple Extensions This patch introduces quadruple extensions, with the new condition of not ttPv. It also generalises all margins, so that extensions can still occur if conditions are only partially fulfilled, but with a stricter margin. Failed STC: LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 16096 W: 3984 L: 4228 D: 7884 Ptnml(0-2): 72, 2067, 4002, 1847, 60 https://tests.stockfishchess.org/tests/view/66316422d01fb9ac9bcdbdcd Passed VVLTC 1: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 96660 W: 24550 L: 24210 D: 47900 Ptnml(0-2): 5, 8776, 30426, 9120, 3 https://tests.stockfishchess.org/tests/view/66361f2c74fa3f41ef2ee091 Passed VVLTC 2: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 80546 W: 20495 L: 20120 D: 39931 Ptnml(0-2): 6, 7477, 24929, 7858, 3 https://tests.stockfishchess.org/tests/view/66350cf739ba8e443112b3fa closes https://github.com/official-stockfish/Stockfish/pull/5211 bench 2233743 --- src/search.cpp | 23 ++++++++++------------- src/search.h | 1 - 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cd80e9392d6..06d31510e83 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -593,7 +593,6 @@ Value Search::Worker::search( bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; - ss->multipleExtensions = (ss - 1)->multipleExtensions; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; @@ -1049,17 +1048,16 @@ Value Search::Worker::search( if (value < singularBeta) { - extension = 1; - - // We make sure to limit the extensions in some way to avoid a search explosion - if (!PvNode && ss->multipleExtensions <= 16) - { - extension = 2 + (value < singularBeta - 11 && !ttCapture); - depth += depth < 14; - } - if (PvNode && !ttCapture && ss->multipleExtensions <= 5 - && value < singularBeta - 38) - extension = 2; + int doubleMargin = 251 * PvNode - 241 * !ttCapture; + int tripleMargin = + 135 + 234 * PvNode - 248 * !ttCapture + 124 * (ss->ttPv || !ttCapture); + int quadMargin = 447 + 354 * PvNode - 300 * !ttCapture + 206 * ss->ttPv; + + extension = 1 + (value < singularBeta - doubleMargin) + + (value < singularBeta - tripleMargin) + + (value < singularBeta - quadMargin); + + depth += ((!PvNode) && (depth < 14)); } // Multi-cut pruning @@ -1104,7 +1102,6 @@ Value Search::Worker::search( // Add extension to new depth newDepth += extension; - ss->multipleExtensions = (ss - 1)->multipleExtensions + (extension >= 2); // Speculative prefetch as early as possible prefetch(tt.first_entry(pos.key_after(move))); diff --git a/src/search.h b/src/search.h index 444e3b8bb1d..cb73a5afddf 100644 --- a/src/search.h +++ b/src/search.h @@ -74,7 +74,6 @@ struct Stack { bool inCheck; bool ttPv; bool ttHit; - int multipleExtensions; int cutoffCnt; }; From d712ed38d1c6c9c76ad375efbd4b8a0469200c0b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 4 May 2024 21:43:07 +0300 Subject: [PATCH 0487/1309] Simplify shuffling and optimism divisors to constants Shuffling divisor and Optimism divisors passed STC & LTC separately: shuf STC: https://tests.stockfishchess.org/tests/view/66356316b4e9bdbc7228b995 shuf LTC: https://tests.stockfishchess.org/tests/view/6635815a73559a8aa857c1dc opt STC: https://tests.stockfishchess.org/tests/view/66356326b4e9bdbc7228b9a0 opt LTC: https://tests.stockfishchess.org/tests/view/663615c673559a8aa8589f8a And then passed LTC together: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 178278 W: 45039 L: 44979 D: 88260 Ptnml(0-2): 43, 19776, 49460, 19798, 62 https://tests.stockfishchess.org/tests/view/66363f19cdb7cf5da64e22a3 closes https://github.com/official-stockfish/Stockfish/pull/5212 Bench: 2198243 --- src/evaluate.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 11999b554b7..5be7e7a1bb4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -62,11 +62,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); - const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul, - int npmConstant, int evalDiv, int shufflingConstant, - int shufflingDiv) { + const auto adjustEval = [&](int nnueDiv, int pawnCountConstant, int pawnCountMul, + int npmConstant, int evalDiv, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; int npm = pos.non_pawn_material() / 64; @@ -76,13 +75,13 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Damp down the evaluation linearly when shuffling int shuffling = pos.rule50_count(); - v = v * (shufflingConstant - shuffling) / shufflingDiv; + v = v * (shufflingConstant - shuffling) / 207; }; if (!smallNet) - adjustEval(524, 32395, 942, 11, 139, 1058, 178, 204); + adjustEval(32395, 942, 11, 139, 1058, 178); else - adjustEval(515, 32793, 944, 9, 140, 1067, 206, 206); + adjustEval(32793, 944, 9, 140, 1067, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 6da1590de0980ca569827e2905f5b423e1a00a52 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 1 May 2024 18:31:38 +0800 Subject: [PATCH 0488/1309] Some history fixes and tidy-up This adds the functions `update_refutations` and `update_quiet_histories` to better distinguish the two. `update_quiet_stats` now just calls both of these functions. The functional side of this patch is two-fold: 1. Stop refutations being updated when we carry out multicut 2. Update pawn history every time we update other quiet histories Yellow STC: LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 238976 W: 61506 L: 61415 D: 116055 Ptnml(0-2): 846, 28628, 60456, 28705, 853 https://tests.stockfishchess.org/tests/view/66321b5ed01fb9ac9bcdca83 However, it passed in <-1.75, 0.25> bounds: $ python3 sprt.py --wins 61506 --losses 61415 --draws 116055 --elo0 -1.75 --elo1 0.25 ELO: 0.132 +- 0.998 [-0.865, 1.13] LLR: 4.15 [-1.75, 0.25] (-2.94, 2.94) H1 Accepted Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 399126 W: 100730 L: 100896 D: 197500 Ptnml(0-2): 116, 44328, 110843, 44158, 118 https://tests.stockfishchess.org/tests/view/66357b0473559a8aa857ba6f closes #5215 Bench 2370967 --- src/search.cpp | 51 +++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 06d31510e83..43f18af2156 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -114,8 +114,11 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_stats( +void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move); +void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); void update_all_stats(const Position& pos, Stack* ss, Search::Worker& workerThread, @@ -1068,7 +1071,7 @@ Value Search::Worker::search( else if (singularBeta >= beta) { if (!ttCapture) - update_quiet_stats(pos, ss, *this, ttMove, -stat_malus(depth)); + update_quiet_histories(pos, ss, *this, ttMove, -stat_malus(depth)); return singularBeta; } @@ -1724,7 +1727,6 @@ void update_all_stats(const Position& pos, int captureCount, Depth depth) { - Color us = pos.side_to_move(); CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; @@ -1737,23 +1739,11 @@ void update_all_stats(const Position& pos, int bestMoveBonus = bestValue > beta + 185 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus - // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); - int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; - // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) - { - workerThread - .pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] - << -quietMoveMalus; - - workerThread.mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - quietsSearched[i].to_sq(), -quietMoveMalus); - } + update_quiet_histories(pos, ss, workerThread, quietsSearched[i], -quietMoveMalus); } else { @@ -1794,10 +1784,8 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } } - // Updates move sorting heuristics -void update_quiet_stats( - const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { +void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move) { // Update killers if (ss->killers[0] != move) @@ -1806,10 +1794,6 @@ void update_quiet_stats( ss->killers[0] = move; } - Color us = pos.side_to_move(); - workerThread.mainHistory[us][move.from_to()] << bonus; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); - // Update countermove history if (((ss - 1)->currentMove).is_ok()) { @@ -1817,6 +1801,27 @@ void update_quiet_stats( workerThread.counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } + +void update_quiet_histories( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { + + Color us = pos.side_to_move(); + workerThread.mainHistory[us][move.from_to()] << bonus; + + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); + + int pIndex = pawn_structure_index(pos); + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus; +} + +// Updates move sorting heuristics +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { + + update_refutations(pos, ss, workerThread, move); + update_quiet_histories(pos, ss, workerThread, move, bonus); +} + } // When playing with strength handicap, choose the best move among a set of RootMoves From f1612612457fd90f9842b2432d795ee6e2e26ebc Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 5 May 2024 05:20:05 +0300 Subject: [PATCH 0489/1309] Adjust history usage in moves loop pruning After experiments with conthist 5 addition failed really bad divions by 2 passed as a gainer. Passed STC: https://tests.stockfishchess.org/tests/view/6636d7114b68b70d858035ce LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 35936 W: 9287 L: 8976 D: 17673 Ptnml(0-2): 81, 4129, 9234, 4446, 78 Passed LTC: https://tests.stockfishchess.org/tests/view/6636ddb64b68b70d858040a8 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 82428 W: 21035 L: 20622 D: 40771 Ptnml(0-2): 29, 8985, 22775, 9394, 31 closes https://github.com/official-stockfish/Stockfish/pull/5217 Bench: 2309253 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 43f18af2156..a60f4d36ee4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -991,7 +991,7 @@ Value Search::Worker::search( int history = (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] / 2 + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) From 61f12a4c383a76c5304aa2cf9cb6e47d5aae0606 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Wed, 1 May 2024 15:54:17 +0800 Subject: [PATCH 0490/1309] Simplify accumulator refreshes Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/6631f5d5d01fb9ac9bcdc7d0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 57472 W: 14979 L: 14784 D: 27709 Ptnml(0-2): 185, 6486, 15192, 6695, 178 closes https://github.com/official-stockfish/Stockfish/pull/5207 No functional change --- src/nnue/nnue_feature_transformer.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 018b715e638..2b11adefbbb 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -668,20 +668,20 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; - int i0 = 0; - for (; i0 < int(std::min(removed.size(), added.size())); ++i0) + int i = 0; + for (; i < int(std::min(removed.size(), added.size())); ++i) { - IndexType indexR = removed[i0]; + IndexType indexR = removed[i]; const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; auto columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i0]; + IndexType indexA = added[i]; const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; auto columnA = reinterpret_cast(&weights[offsetA]); for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); } - for (int i = i0; i < int(removed.size()); ++i) + for (; i < int(removed.size()); ++i) { IndexType index = removed[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; @@ -690,7 +690,7 @@ class FeatureTransformer { for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } - for (int i = i0; i < int(added.size()); ++i) + for (; i < int(added.size()); ++i) { IndexType index = added[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; From 070e564c389eb2c263f3982060ab5899b67d0a62 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 5 May 2024 07:36:48 +0800 Subject: [PATCH 0491/1309] VVLTC search tune This patch is the result of two tuning stages: 1. ~32k games at 60+0.6 th8: https://tests.stockfishchess.org/tests/view/662d9dea6115ff6764c7f817 2. ~193k games at 80+0.8 th6, based on PR #5211: https://tests.stockfishchess.org/tests/view/663587e273559a8aa857ca00. Based on extensive VVLTC tuning and testing both before and after #5211, it is observed that introduction of new extensions positively affected the search tune results. Passed VVLTC 70+0.7 th7 1st sprt: https://tests.stockfishchess.org/tests/view/6636c6f04b68b70d85801409 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 18566 W: 4864 L: 4620 D: 9082 Ptnml(0-2): 0, 1608, 5827, 1844, 4 Passed VVLTC 70+0.7 th7 2nd sprt: https://tests.stockfishchess.org/tests/view/6636d4b84b68b70d85802ab7 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 43142 W: 11141 L: 10838 D: 21163 Ptnml(0-2): 4, 3915, 13427, 4224, 1 Passed VVLTC 70+0.7 3rd sprt: https://tests.stockfishchess.org/tests/view/66376b4f9819650825aa230b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 40322 W: 10374 L: 10076 D: 19872 Ptnml(0-2): 1, 3660, 12544, 3952, 4 The first two sprts were run against passed #5211. The third sprt was run against latest master. closes https://github.com/official-stockfish/Stockfish/pull/5216 Bench: 2180675 --- src/search.cpp | 80 +++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a60f4d36ee4..6830e4b1279 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -59,9 +59,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 118 - 45 * noTtCutNode; - Value improvingDeduction = 52 * improving * futilityMult / 32; - Value worseningDeduction = (316 + 48 * improving) * oppWorsening * futilityMult / 1024; + Value futilityMult = 126 - 46 * noTtCutNode; + Value improvingDeduction = 58 * improving * futilityMult / 32; + Value worseningDeduction = (323 + 52 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -73,15 +73,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 9260; + v += cv * std::abs(cv) / 7350; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(214 * d - 318, 16, 1304); } +int stat_bonus(Depth d) { return std::clamp(208 * d - 297, 16, 1406); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 572 * d - 284 : 1355); } +int stat_malus(Depth d) { return (d < 4 ? 520 * d - 312 : 1479); } // 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); } @@ -310,12 +310,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 11480; + delta = 10 + avg * avg / 9530; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 122 * avg / (std::abs(avg) + 92); + optimism[us] = 119 * avg / (std::abs(avg) + 88); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -502,10 +502,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-65); + h->fill(-60); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((20.14 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((18.93 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks); } @@ -738,7 +738,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1644, 1384); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1796, 1526); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -761,7 +761,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 471 - (275 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 433 - (302 - 141 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -770,23 +770,23 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 12 + if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 286 + - (ss - 1)->statScore / 254 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 312 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16993 + && eval >= beta && ss->staticEval >= beta - 19 * depth + 326 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 134, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -834,7 +834,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 169 - 63 * improving; + probCutBeta = beta + 159 - 66 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -890,7 +890,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 452; + probCutBeta = beta + 420; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -975,15 +975,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 285 + 277 * lmrDepth + Value futilityValue = ss->staticEval + 295 + 280 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -199 * depth, 199 * depth); - if (!pos.see_ge(move, -203 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -197 * depth, 196 * depth); + if (!pos.see_ge(move, -186 * depth - seeHist)) continue; } else @@ -995,18 +995,18 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4173 * depth) + if (lmrDepth < 6 && history < -4081 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 5285; + lmrDepth += history / 4768; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 57) + 131 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 52 ? 134 : 54) + 142 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 14 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1017,7 +1017,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -28 * lmrDepth * lmrDepth)) continue; } } @@ -1037,11 +1037,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (65 + 59 * (ss->ttPv && !PvNode)) * depth / 63; + Value singularBeta = ttValue - (65 + 52 * (ss->ttPv && !PvNode)) * depth / 63; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1099,7 +1099,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 3807) + > 4016) extension = 1; } @@ -1151,10 +1151,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 5024; + + (*contHist[3])[movedPiece][move.to_sq()] - 5078; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 13182; + r -= ss->statScore / 12076; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1173,7 +1173,7 @@ Value Search::Worker::search( { // 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 + 42 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1291,7 +1291,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13546 && value > -13478) + if (depth > 2 && depth < 13 && beta < 15868 && value > -14630) depth -= 2; assert(depth > 0); @@ -1334,8 +1334,8 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) - + ((ss - 1)->moveCount > 11) + (!ss->inCheck && bestValue <= ss->staticEval - 142) + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14455) + + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 130) + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 77); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); @@ -1495,7 +1495,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 250; + futilityBase = ss->staticEval + 270; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1575,7 +1575,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -79)) + if (!pos.see_ge(move, -69)) continue; } @@ -1643,7 +1643,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1150 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); + return (reductionScale + 1318 - delta * 760 / rootDelta) / 1024 + (!i && reductionScale > 1066); } TimePoint Search::Worker::elapsed() const { @@ -1736,7 +1736,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 185 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 165 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); From 2d5e248f58595c81c1d075f5874e4c18ca8b1998 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 7 May 2024 15:03:58 +0300 Subject: [PATCH 0492/1309] Tweak reduction formula based on depth The idea came to me by checking for trends from the megafauzi tunes, since the values of the divisor for this specific formula were as follows: stc: 15990 mtc: 16117 ltc: 14805 vltc: 12719 new vltc passed by Muzhen: 12076 This shows a clear trend related to time control, the higher it is, the lower the optimum value for the divisor seems to be. So I tried a simple formula, using educated guesses based on some calculations, tests show it works pretty fine, and it can still be further tuned at VLTC in the future to scale even better. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 431360 W: 110791 L: 109898 D: 210671 Ptnml(0-2): 1182, 50846, 110698, 51805, 1149 https://tests.stockfishchess.org/tests/view/663770409819650825aa269f Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 114114 W: 29109 L: 28625 D: 56380 Ptnml(0-2): 105, 12628, 31101, 13124, 99 https://tests.stockfishchess.org/tests/view/66378c099819650825aa73f6 https://github.com/official-stockfish/Stockfish/pull/5223 bench: 2273551 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 6830e4b1279..2c3fc56e3b2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1154,7 +1154,7 @@ Value Search::Worker::search( + (*contHist[3])[movedPiece][move.to_sq()] - 5078; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 12076; + r -= ss->statScore / std::max(21000 - (depth * 305), 12000); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) From 3bdfa0fb4a837f51f142cc1e862837c6f9167796 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 7 May 2024 15:03:58 +0300 Subject: [PATCH 0493/1309] Depth dependent statscore based reductions Test a modification of Fawzi's PR #5223, against that PR. parameters locally tuned with nevergrad4sf. passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 1047424 W: 271478 L: 269649 D: 506297 Ptnml(0-2): 3851, 124543, 265290, 125982, 4046 https://tests.stockfishchess.org/tests/view/663b0889ca93dad645f7c58c passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 796236 W: 201712 L: 199825 D: 394699 Ptnml(0-2): 361, 88381, 218778, 90206, 392 https://tests.stockfishchess.org/tests/view/663be6adca93dad645f7f509 https://github.com/official-stockfish/Stockfish/pull/5228 Bench: 3346224 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2c3fc56e3b2..3eec00b0852 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1154,7 +1154,7 @@ Value Search::Worker::search( + (*contHist[3])[movedPiece][move.to_sq()] - 5078; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / std::max(21000 - (depth * 305), 12000); + r -= ss->statScore / (17662 - std::min(depth, 16) * 105); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) From d1b8d8bab377eb873385bb4f8662062398f16686 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 8 May 2024 21:59:03 +0300 Subject: [PATCH 0494/1309] Refactor quiet moves pruning in qsearch Make it formula more in line with what we use in search - current formula is more or less the one we used years ago for search but since then it was remade, this patch remakes qsearch formula to almost exactly the same as we use in search - with sum of conthist 0, 1 and pawn structure history. Passed STC: https://tests.stockfishchess.org/tests/view/6639c8421343f0cb16716206 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 84992 W: 22414 L: 22019 D: 40559 Ptnml(0-2): 358, 9992, 21440, 10309, 397 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 119136 W: 30407 L: 29916 D: 58813 Ptnml(0-2): 46, 13192, 32622, 13641, 67 closes https://github.com/official-stockfish/Stockfish/pull/5224 Bench: 2138659 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3eec00b0852..633f9b51513 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1570,8 +1570,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, break; // Continuation history based pruning (~3 Elo) - if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] < 0 - && (*contHist[1])[pos.moved_piece(move)][move.to_sq()] < 0) + if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)][move.to_sq()] <= 4000) continue; // Do not search moves with bad enough SEE values (~5 Elo) From db147fe2586527a854516016699949af53dc5b17 Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Wed, 8 May 2024 22:08:56 +0200 Subject: [PATCH 0495/1309] IIR on cutnodes if there is a ttMove but the ttBound is upper If there is an upper bound stored in the transposition table, but we still have a ttMove, the upperbound indicates that the last time the ttMove was tried, it failed low. This fail low indicates that the ttMove may not be good, so this patch introduces a depth reduction of one for cutnodes with such ttMoves. Passed STC: https://tests.stockfishchess.org/tests/view/663be4d1ca93dad645f7f45f LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 139424 W: 35900 L: 35433 D: 68091 Ptnml(0-2): 425, 16357, 35743, 16700, 487 Passed LTC: https://tests.stockfishchess.org/tests/view/663bec95ca93dad645f7f5c8 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 129690 W: 32902 L: 32390 D: 64398 Ptnml(0-2): 63, 14304, 35610, 14794, 74 closes https://github.com/official-stockfish/Stockfish/pull/5227 bench 2257437 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 633f9b51513..767ea2380b4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -828,8 +828,8 @@ Value Search::Worker::search( return qsearch(pos, ss, alpha, beta); // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough. - if (cutNode && depth >= 8 && !ttMove) - depth -= 2; + if (cutNode && depth >= 8 && (!ttMove || tte->bound() == BOUND_UPPER)) + depth -= 1 + !ttMove; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value From 2dbb44e28d2e5b3c72ddbbd6f436d41f75031a22 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 8 May 2024 03:26:09 +0900 Subject: [PATCH 0496/1309] Fix nodestime 1. The current time management system utilizes limits.inc and limits.time, which can represent either milliseconds or node count, depending on whether the nodestime option is active. There have been several modifications which brought Elo gain for typical uses (i.e. real-time matches), however some of these changes overlooked such distinction. This patch adjusts constants and multiplication/division to more accurately simulate real TC conditions when nodestime is used. 2. The advance_nodes_time function has a bug that can extend the time limit when availableNodes reaches exact zero. This patch fixes the bug by initializing the variable to -1 and make sure it does not go below zero. 3. elapsed_time function is newly introduced to print PV in the UCI output based on real time. This makes PV output more consistent with the behavior of trivial use cases. closes https://github.com/official-stockfish/Stockfish/pull/5186 No functional changes --- src/search.cpp | 22 ++++++++++++++++------ src/search.h | 1 + src/timeman.cpp | 46 +++++++++++++++++++++++++++------------------- src/timeman.h | 5 +++-- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 767ea2380b4..684b760ecfb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -190,8 +190,8 @@ void Search::Worker::start_searching() { // When playing in 'nodes as time' mode, subtract the searched nodes from // the available ones before exiting. if (limits.npmsec) - main_manager()->tm.advance_nodes_time(limits.inc[rootPos.side_to_move()] - - threads.nodes_searched()); + main_manager()->tm.advance_nodes_time(threads.nodes_searched() + - limits.inc[rootPos.side_to_move()]); Worker* bestThread = this; Skill skill = @@ -347,7 +347,7 @@ void Search::Worker::iterative_deepening() { // When failing high/low give some update (without cluttering // the UI) before a re-search. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && elapsed() > 3000) + && elapsed_time() > 3000) main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and @@ -378,7 +378,7 @@ void Search::Worker::iterative_deepening() { std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); if (mainThread - && (threads.stop || pvIdx + 1 == multiPV || elapsed() > 3000) + && (threads.stop || pvIdx + 1 == multiPV || elapsed_time() > 3000) // A thread that aborted search can have mated-in/TB-loss PV and score // that cannot be trusted, i.e. it can be delayed or refuted if we would have // had time to fully search other root-moves. Thus we suppress this output and @@ -935,7 +935,7 @@ Value Search::Worker::search( ss->moveCount = ++moveCount; - if (rootNode && is_mainthread() && elapsed() > 3000) + if (rootNode && is_mainthread() && elapsed_time() > 3000) { main_manager()->updates.onIter( {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); @@ -1647,10 +1647,20 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { return (reductionScale + 1318 - delta * 760 / rootDelta) / 1024 + (!i && reductionScale > 1066); } +// elapsed() returns the time elapsed since the search started. If the +// 'nodestime' option is enabled, it will return the count of nodes searched +// instead. This function is called to check whether the search should be +// stopped based on predefined thresholds like time limits or nodes searched. +// +// elapsed_time() returns the actual time elapsed since the start of the search. +// This function is intended for use only when printing PV outputs, and not used +// for making decisions within the search algorithm itself. TimePoint Search::Worker::elapsed() const { return main_manager()->tm.elapsed([this]() { return threads.nodes_searched(); }); } +TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } + namespace { // Adjusts a mate or TB score from "plies to mate from the root" @@ -1900,7 +1910,7 @@ void SearchManager::pv(const Search::Worker& worker, const auto& rootMoves = worker.rootMoves; const auto& pos = worker.rootPos; size_t pvIdx = worker.pvIdx; - TimePoint time = tm.elapsed([nodes]() { return nodes; }) + 1; + TimePoint time = tm.elapsed_time() + 1; size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); diff --git a/src/search.h b/src/search.h index cb73a5afddf..c824daf931c 100644 --- a/src/search.h +++ b/src/search.h @@ -276,6 +276,7 @@ class Worker { } TimePoint elapsed() const; + TimePoint elapsed_time() const; LimitsType limits; diff --git a/src/timeman.cpp b/src/timeman.cpp index c651745f02e..4feb329b335 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -32,12 +32,12 @@ TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } void TimeManagement::clear() { - availableNodes = 0; // When in 'nodes as time' mode + availableNodes = -1; // When in 'nodes as time' mode } void TimeManagement::advance_nodes_time(std::int64_t nodes) { assert(useNodesTime); - availableNodes += nodes; + availableNodes = std::max(int64_t(0), availableNodes - nodes); } // Called at the beginning of the search and calculates @@ -48,14 +48,17 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options) { - // If we have no time, no need to initialize TM, except for the start time, - // which is used by movetime. - startTime = limits.startTime; + TimePoint npmsec = TimePoint(options["nodestime"]); + + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + useNodesTime = npmsec != 0; + if (limits.time[us] == 0) return; TimePoint moveOverhead = TimePoint(options["Move Overhead"]); - TimePoint npmsec = TimePoint(options["nodestime"]); // optScale is a percentage of available time to use for the current move. // maxScale is a multiplier applied to optimumTime. @@ -65,26 +68,31 @@ void TimeManagement::init(Search::LimitsType& limits, // to nodes, and use resulting values in time management formulas. // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) // must be much lower than the real engine speed. - if (npmsec) + if (useNodesTime) { - useNodesTime = true; - - if (!availableNodes) // Only once at game start + if (availableNodes == -1) // Only once at game start availableNodes = npmsec * limits.time[us]; // Time is in msec // Convert from milliseconds to nodes limits.time[us] = TimePoint(availableNodes); limits.inc[us] *= npmsec; limits.npmsec = npmsec; + moveOverhead *= npmsec; } + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const int64_t scaleFactor = useNodesTime ? npmsec : 1; + const TimePoint scaledTime = limits.time[us] / scaleFactor; + const TimePoint scaledInc = limits.inc[us] / scaleFactor; + // Maximum move horizon of 50 moves int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; - // if less than one second, gradually reduce mtg - if (limits.time[us] < 1000 && (double(mtg) / limits.time[us] > 0.05)) + // If less than one second, gradually reduce mtg + if (scaledTime < 1000 && double(mtg) / scaledInc > 0.05) { - mtg = limits.time[us] * 0.05; + mtg = scaledTime * 0.05; } // Make sure timeLeft is > 0 since we may use it as a divisor @@ -97,15 +105,15 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.movestogo == 0) { // Use extra time with larger increments - double optExtra = limits.inc[us] < 500 ? 1.0 : 1.13; + double optExtra = scaledInc < 500 ? 1.0 : 1.13; // Calculate time constants based on current time left. - double optConstant = - std::min(0.00308 + 0.000319 * std::log10(limits.time[us] / 1000.0), 0.00506); - double maxConstant = std::max(3.39 + 3.01 * std::log10(limits.time[us] / 1000.0), 2.93); + double logTimeInSec = std::log10(scaledTime / 1000.0); + double optConstant = std::min(0.00308 + 0.000319 * logTimeInSec, 0.00506); + double maxConstant = std::max(3.39 + 3.01 * logTimeInSec, 2.93); optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, - 0.213 * limits.time[us] / double(timeLeft)) + 0.213 * limits.time[us] / timeLeft) * optExtra; maxScale = std::min(6.64, maxConstant + ply / 12.0); } @@ -113,7 +121,7 @@ void TimeManagement::init(Search::LimitsType& limits, // x moves in y seconds (+ z increment) else { - optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft)); + optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft); maxScale = std::min(6.3, 1.5 + 0.11 * mtg); } diff --git a/src/timeman.h b/src/timeman.h index 35c3cfc0680..1b6bd849ae2 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -42,8 +42,9 @@ class TimeManagement { TimePoint maximum() const; template TimePoint elapsed(FUNC nodes) const { - return useNodesTime ? TimePoint(nodes()) : now() - startTime; + return useNodesTime ? TimePoint(nodes()) : elapsed_time(); } + TimePoint elapsed_time() const { return now() - startTime; }; void clear(); void advance_nodes_time(std::int64_t nodes); @@ -53,7 +54,7 @@ class TimeManagement { TimePoint optimumTime; TimePoint maximumTime; - std::int64_t availableNodes = 0; // When in 'nodes as time' mode + std::int64_t availableNodes = -1; // When in 'nodes as time' mode bool useNodesTime = false; // True if we are in 'nodes as time' mode }; From 9d6dab06a8274c4e09b437110f86bdb1ea7edb0f Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 5 May 2024 03:10:26 +0300 Subject: [PATCH 0497/1309] simplify moveCountPruning no (significant) speedup upon renewed testing Passed stc: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 88992 W: 22779 L: 22633 D: 43580 Ptnml(0-2): 137, 8706, 26681, 8818, 154 https://tests.stockfishchess.org/tests/view/6636c4844b68b70d85800dae closes https://github.com/official-stockfish/Stockfish/pull/5213 No functional change. --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 684b760ecfb..1d0cb4ab69e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -960,8 +960,7 @@ Value Search::Worker::search( if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - if (!moveCountPruning) - moveCountPruning = moveCount >= futility_move_count(improving, depth); + moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search int lmrDepth = newDepth - r; From 3b4ddf4ae6362ddef063cc644d1466754015482e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 6 May 2024 20:18:12 +0300 Subject: [PATCH 0498/1309] Simplify away conthist 3 from statscore Following previous elo gainer that gained by making conthist 3 less important in pruning this patch simplifies away this history from calculation of statscore. Passed STC: https://tests.stockfishchess.org/tests/view/6637aa7e9819650825aa93e0 LLR: 3.00 (-2.94,2.94) <-1.75,0.25> Total: 35392 W: 9352 L: 9120 D: 16920 Ptnml(0-2): 141, 4145, 8888, 4385, 137 Passed LTC: https://tests.stockfishchess.org/tests/view/66383cd8493aaaf4b7ea90c5 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 33948 W: 8714 L: 8503 D: 16731 Ptnml(0-2): 39, 3701, 9270, 3938, 26 closes https://github.com/official-stockfish/Stockfish/pull/5220 Bench: 2508571 --- src/search.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1d0cb4ab69e..d9f997e8ee5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1149,8 +1149,7 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 5078; + + (*contHist[1])[movedPiece][move.to_sq()] - 5078; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) r -= ss->statScore / (17662 - std::min(depth, 16) * 105); @@ -1569,9 +1568,12 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, break; // Continuation history based pruning (~3 Elo) - if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] - + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)][move.to_sq()] <= 4000) + if (!capture + && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] + [move.to_sq()] + <= 4000) continue; // Do not search moves with bad enough SEE values (~5 Elo) From 23439e4096bc28deb2e4e935f24c5ddb22999dc5 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 7 May 2024 09:27:04 +0300 Subject: [PATCH 0499/1309] Remove conthist 3 from moves loop pruning Followup to previous gainer that made it twice less impactful there - this patch removes it entirely as a simplification. Passed STC: https://tests.stockfishchess.org/tests/view/6637aa7e9819650825aa93e0 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 26208 W: 6930 L: 6694 D: 12584 Ptnml(0-2): 113, 2997, 6652, 3225, 117 Passed LTC: https://tests.stockfishchess.org/tests/view/66383cba493aaaf4b7ea90c2 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 67866 W: 17294 L: 17118 D: 33454 Ptnml(0-2): 46, 7627, 18415, 7795, 50 closes https://github.com/official-stockfish/Stockfish/pull/5221 Bench: 2691699 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d9f997e8ee5..448da7e2546 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -990,7 +990,6 @@ Value Search::Worker::search( int history = (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] / 2 + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) From 574ad14b323465314c8d5d5a81af995cb58b07c9 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 9 May 2024 02:56:43 +0300 Subject: [PATCH 0500/1309] Simplify depth formula based on score improvement Simplify depth formula based on score improvement. This idea was first tried by cj5716 Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 347104 W: 89683 L: 89804 D: 167617 Ptnml(0-2): 1357, 38824, 93307, 38711, 1353 https://tests.stockfishchess.org/tests/view/66378edf9819650825aa75d0 Passed LTC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 63000 W: 15851 L: 15694 D: 31455 Ptnml(0-2): 22, 5396, 20499, 5569, 14 https://tests.stockfishchess.org/tests/view/663c04e5c0b75d7f7b97d461 closes https://github.com/official-stockfish/Stockfish/pull/5225 Bench: 2691699 Co-Authored-By: cj5716 <125858804+cj5716@users.noreply.github.com> --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 448da7e2546..fdf9871cd0c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1288,7 +1288,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 13 && beta < 15868 && value > -14630) + if (depth > 2 && depth < 13 && abs(value) < VALUE_TB_WIN_IN_MAX_PLY) depth -= 2; assert(depth > 0); From c43425b0b1167665b2f9520690e639c80977c067 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 8 May 2024 14:26:01 -0700 Subject: [PATCH 0501/1309] Simplify Away Negative Extension This patch simplifies away the negative extension applied when the value returned by the transposition table is assumed to fail low over the value of reduced search. Passed STC: LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 248736 W: 64293 L: 64302 D: 120141 Ptnml(0-2): 925, 29833, 62831, 29884, 895 https://tests.stockfishchess.org/tests/view/663bee3bca93dad645f7f64a Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 254970 W: 64289 L: 64308 D: 126373 Ptnml(0-2): 110, 28428, 70422, 28421, 104 https://tests.stockfishchess.org/tests/view/663c11f0c0b75d7f7b97d4bb closes https://github.com/official-stockfish/Stockfish/pull/5226 Bench: 2353057 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fdf9871cd0c..4572ffc900d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1087,10 +1087,6 @@ Value Search::Worker::search( // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo) else if (cutNode) extension = -2; - - // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo) - else if (ttValue <= value) - extension = -1; } // Extension for capturing the previous moved piece (~0 Elo on STC, ~1 Elo on LTC) From b8812138e8e4e6ebd9d1c46ca9da15ddab1eb1ae Mon Sep 17 00:00:00 2001 From: xu-shawn <50402888+xu-shawn@users.noreply.github.com> Date: Thu, 9 May 2024 00:11:09 -0700 Subject: [PATCH 0502/1309] Fix usage of abs vs std::abs closes https://github.com/official-stockfish/Stockfish/pull/5229 no functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4572ffc900d..6c30c3e9181 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1284,7 +1284,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 13 && abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + if (depth > 2 && depth < 13 && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) depth -= 2; assert(depth > 0); From 540545d12792dc554e3a4cd1b09633c31a16d31b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 9 May 2024 00:38:43 -0700 Subject: [PATCH 0503/1309] simplify away quietCheckEvasions pruning simplifies away the pruning of quiet evasion moves in quiescent search. Passed STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 343520 W: 88356 L: 88470 D: 166694 Ptnml(0-2): 1061, 40073, 89706, 39759, 1161 https://tests.stockfishchess.org/tests/view/663c7ddfc0b75d7f7b980f3b Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 168744 W: 42454 L: 42384 D: 83906 Ptnml(0-2): 75, 18678, 46782, 18776, 61 https://tests.stockfishchess.org/tests/view/663ce34fc0b75d7f7b981ed9 closes https://github.com/official-stockfish/Stockfish/pull/5231 bench 3681552 --- src/search.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6c30c3e9181..3867a3975df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1502,8 +1502,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); - int quietCheckEvasions = 0; - // Step 5. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. while ((move = mp.next_move()) != Move::none()) @@ -1556,12 +1554,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, } } - // We prune after the second quiet check evasion move, where being 'in check' is - // implicitly checked through the counter, and being a 'quiet move' apart from - // being a tt move is assumed after an increment because captures are pushed ahead. - if (quietCheckEvasions > 1) - break; - // Continuation history based pruning (~3 Elo) if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] @@ -1585,8 +1577,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, &thisThread ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; - quietCheckEvasions += !capture && ss->inCheck; - // Step 7. Make and search the move thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); From 813c5aa5329011e218dad8dc53d61504cecadc3f Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 12 May 2024 17:49:30 +0800 Subject: [PATCH 0504/1309] VVLTC search tune Tuned at 111k games of VVLTC. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/664090c6d163897c63214324 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 83046 W: 21071 L: 20747 D: 41228 Ptnml(0-2): 2, 7574, 26048, 7896, 3 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/6640cb2abaa6260a5688dc17 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 68630 W: 17620 L: 17270 D: 33740 Ptnml(0-2): 4, 6242, 21471, 6596, 2 closes https://github.com/official-stockfish/Stockfish/pull/5240 Bench: 1752471 --- src/search.cpp | 84 +++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3867a3975df..1d9e0d81abd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -59,9 +59,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 126 - 46 * noTtCutNode; - Value improvingDeduction = 58 * improving * futilityMult / 32; - Value worseningDeduction = (323 + 52 * improving) * oppWorsening * futilityMult / 1024; + Value futilityMult = 131 - 48 * noTtCutNode; + Value improvingDeduction = 57 * improving * futilityMult / 32; + Value worseningDeduction = (309 + 52 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -73,15 +73,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 7350; + v += cv * std::abs(cv) / 7179; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(208 * d - 297, 16, 1406); } +int stat_bonus(Depth d) { return std::clamp(200 * d - 280, 16, 1495); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 520 * d - 312 : 1479); } +int stat_malus(Depth d) { return (d < 4 ? 586 * d - 284 : 1639); } // 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); } @@ -310,12 +310,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 9530; + delta = 10 + avg * avg / 9474; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 119 * avg / (std::abs(avg) + 88); + optimism[us] = 117 * avg / (std::abs(avg) + 88); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -502,10 +502,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-60); + h->fill(-62); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((18.93 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((21.19 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks); } @@ -738,7 +738,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1796, 1526); + int bonus = std::clamp(-12 * int((ss - 1)->staticEval + ss->staticEval), -1749, 1602); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -761,7 +761,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 433 - (302 - 141 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 473 - (308 - 138 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -772,21 +772,21 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 254 + - (ss - 1)->statScore / 258 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16993 - && eval >= beta && ss->staticEval >= beta - 19 * depth + 326 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16079 + && eval >= beta && ss->staticEval >= beta - 21 * depth + 324 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 134, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -834,7 +834,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 159 - 66 * improving; + probCutBeta = beta + 177 - 65 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -890,7 +890,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 420; + probCutBeta = beta + 428; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -974,15 +974,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 295 + 280 * lmrDepth + Value futilityValue = ss->staticEval + 305 + 272 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -197 * depth, 196 * depth); - if (!pos.see_ge(move, -186 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -185 * depth, 182 * depth); + if (!pos.see_ge(move, -176 * depth - seeHist)) continue; } else @@ -993,18 +993,18 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4081 * depth) + if (lmrDepth < 6 && history < -4360 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 4768; + lmrDepth += history / 4507; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 52 ? 134 : 54) + 142 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 54 ? 142 : 55) + 132 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1015,7 +1015,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -28 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1035,11 +1035,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (65 + 52 * (ss->ttPv && !PvNode)) * depth / 63; + Value singularBeta = ttValue - (59 + 49 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1049,10 +1049,10 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 251 * PvNode - 241 * !ttCapture; + int doubleMargin = 285 * PvNode - 228 * !ttCapture; int tripleMargin = - 135 + 234 * PvNode - 248 * !ttCapture + 124 * (ss->ttPv || !ttCapture); - int quadMargin = 447 + 354 * PvNode - 300 * !ttCapture + 206 * ss->ttPv; + 121 + 238 * PvNode - 259 * !ttCapture + 117 * (ss->ttPv || !ttCapture); + int quadMargin = 471 + 343 * PvNode - 281 * !ttCapture + 217 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin) @@ -1093,7 +1093,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4016) + > 4041) extension = 1; } @@ -1144,10 +1144,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 5078; + + (*contHist[1])[movedPiece][move.to_sq()] - 5313; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / (17662 - std::min(depth, 16) * 105); + r -= ss->statScore / (16145 - std::min(depth, 15) * 102); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1166,7 +1166,7 @@ Value Search::Worker::search( { // 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 doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1327,9 +1327,9 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14455) - + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 130) - + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 77); + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14323) + + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 127) + + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1488,7 +1488,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 270; + futilityBase = ss->staticEval + 259; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1560,11 +1560,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()] - <= 4000) + <= 4057) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -69)) + if (!pos.see_ge(move, -68)) continue; } @@ -1630,7 +1630,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1318 - delta * 760 / rootDelta) / 1024 + (!i && reductionScale > 1066); + return (reductionScale + 1284 - delta * 755 / rootDelta) / 1024 + (!i && reductionScale > 1133); } // elapsed() returns the time elapsed since the search started. If the From d3f081ed8ad749cc7e07c0d85b4e8818678f952f Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 9 May 2024 21:10:24 +0300 Subject: [PATCH 0505/1309] Adjust standpat return value in qsearch Instead of returning value itself return value between it and beta for non pv nodes - analogous to what we do after actual search there. Passed STC: https://tests.stockfishchess.org/tests/view/663cb1b4c0b75d7f7b98188e LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 131552 W: 34131 L: 33673 D: 63748 Ptnml(0-2): 420, 15446, 33600, 15876, 434 Passed LTC: https://tests.stockfishchess.org/tests/view/663cda5dc0b75d7f7b981c6f LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 282798 W: 71658 L: 70833 D: 140307 Ptnml(0-2): 112, 31187, 77979, 32006, 115 closes https://github.com/official-stockfish/Stockfish/pull/5233 Bench: 1606672 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 1d9e0d81abd..ae2b1de23e9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1478,6 +1478,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { + if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && !PvNode) + bestValue = (3 * bestValue + beta) / 4; if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, Move::none(), unadjustedStaticEval, tt.generation()); From 53f363041cd96be840244f989823781ecd21b658 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 9 May 2024 13:47:00 -0400 Subject: [PATCH 0506/1309] Simplify npm constants when adjusting eval Passed non-regression STC: https://tests.stockfishchess.org/tests/view/663d0c4f507ebe1c0e91ec8d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 162784 W: 41987 L: 41906 D: 78891 Ptnml(0-2): 520, 19338, 41591, 19427, 516 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/663d20fd507ebe1c0e91f405 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 457242 W: 115022 L: 115250 D: 226970 Ptnml(0-2): 271, 51566, 125179, 51330, 275 closes https://github.com/official-stockfish/Stockfish/pull/5237 Bench: 2238216 --- src/evaluate.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 5be7e7a1bb4..cfe20601e56 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -62,15 +62,13 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); - const auto adjustEval = [&](int nnueDiv, int pawnCountConstant, int pawnCountMul, - int npmConstant, int evalDiv, int shufflingConstant) { + const auto adjustEval = [&](int nnueDiv, int pawnCountMul, int evalDiv, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; int npm = pos.non_pawn_material() / 64; - v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) - + optimism * (npmConstant + npm)) + v = (nnue * (npm + 943 + pawnCountMul * pos.count()) + optimism * (npm + 140)) / evalDiv; // Damp down the evaluation linearly when shuffling @@ -79,9 +77,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, }; if (!smallNet) - adjustEval(32395, 942, 11, 139, 1058, 178); + adjustEval(32395, 11, 1058, 178); else - adjustEval(32793, 944, 9, 140, 1067, 206); + adjustEval(32793, 9, 1067, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 0b08953174d222270100690b45fad0dc47c01f98 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 9 May 2024 14:03:35 -0400 Subject: [PATCH 0507/1309] Re-evaluate some small net positions for more accurate evals Use main net evals when small net evals hint that higher eval accuracy may be worth the slower eval speeds. With Finny caches, re-evals with the main net are less expensive than before. Original idea by mstembera who I've added as co-author to this PR. Based on reEval tests by mstembera: https://tests.stockfishchess.org/tests/view/65e69187b6345c1b934866e5 https://tests.stockfishchess.org/tests/view/65e863aa0ec64f0526c3e991 A few variants of this patch also passed LTC: https://tests.stockfishchess.org/tests/view/663d2108507ebe1c0e91f407 https://tests.stockfishchess.org/tests/view/663e388c3a2f9702074bc152 Passed STC: https://tests.stockfishchess.org/tests/view/663dadbd1a61d6377f190e2c LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 92320 W: 23941 L: 23531 D: 44848 Ptnml(0-2): 430, 10993, 22931, 11349, 457 Passed LTC: https://tests.stockfishchess.org/tests/view/663ef48b2948bf9aa698690c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 98934 W: 24907 L: 24457 D: 49570 Ptnml(0-2): 48, 10952, 27027, 11382, 58 closes https://github.com/official-stockfish/Stockfish/pull/5238 bench 1876282 Co-Authored-By: mstembera <5421953+mstembera@users.noreply.github.com> --- src/evaluate.cpp | 3 +++ src/evaluate.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index cfe20601e56..b5f28d5aede 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -62,6 +62,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); + if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 500)) + nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); + const auto adjustEval = [&](int nnueDiv, int pawnCountMul, int evalDiv, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; diff --git a/src/evaluate.h b/src/evaluate.h index 2d244ff6722..afaf35ebe17 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1274; +constexpr inline int SmallNetThreshold = 1174; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the From e608eab8dd9f7bd68f192d56d742f621674b8fa8 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 12 May 2024 04:45:01 -0700 Subject: [PATCH 0508/1309] Optimize update_accumulator_refresh_cache() STC https://tests.stockfishchess.org/tests/view/664105df26ac5f9b286d30e6 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 178528 W: 46235 L: 45750 D: 86543 Ptnml(0-2): 505, 17792, 52142, 18363, 462 Combo of two yellow speedups https://tests.stockfishchess.org/tests/view/6640abf9d163897c63214f5c LLR: -2.93 (-2.94,2.94) <0.00,2.00> Total: 355744 W: 91714 L: 91470 D: 172560 Ptnml(0-2): 913, 36233, 103384, 36381, 961 https://tests.stockfishchess.org/tests/view/6628ce073fe04ce4cefc739c LLR: -2.93 (-2.94,2.94) <0.00,2.00> Total: 627040 W: 162001 L: 161339 D: 303700 Ptnml(0-2): 2268, 72379, 163532, 73105, 2236 closes https://github.com/official-stockfish/Stockfish/pull/5239 No functional change --- src/nnue/nnue_feature_transformer.h | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 2b11adefbbb..bcd14e6fbbc 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -664,7 +664,11 @@ class FeatureTransformer { for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); + auto accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); + auto entryTile = + reinterpret_cast(&entry.accumulation[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; @@ -679,7 +683,7 @@ class FeatureTransformer { auto columnA = reinterpret_cast(&weights[offsetA]); for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); + acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); } for (; i < int(removed.size()); ++i) { @@ -702,12 +706,17 @@ class FeatureTransformer { for (IndexType k = 0; k < NumRegs; k++) vec_store(&entryTile[k], acc[k]); + for (IndexType k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - auto entryTilePsqt = - reinterpret_cast(&entry.psqtAccumulation[j * PsqtTileHeight]); + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); + auto entryTilePsqt = reinterpret_cast( + &entry.psqtAccumulation[j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; @@ -732,6 +741,8 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&entryTilePsqt[k], psqt[k]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); } #else @@ -755,8 +766,6 @@ class FeatureTransformer { entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; } -#endif - // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing @@ -765,6 +774,7 @@ class FeatureTransformer { std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, sizeof(int32_t) * PSQTBuckets); +#endif for (Color c : {WHITE, BLACK}) entry.byColorBB[c] = pos.pieces(c); From 2682c2127d1360524915f6cd68cbeabfdd19ce26 Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Mon, 13 May 2024 07:19:18 +0100 Subject: [PATCH 0509/1309] Use 5% less time on first move Stockfish appears to take too much time on the first move of a game and then not enough on moves 2,3,4... Probably caused by most of the factors that increase time usually applying on the first move. Attempts to give more time to the subsequent moves have not worked so far, but this change to simply reduce first move time by 5% worked. STC 10+0.1 : LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 78496 W: 20516 L: 20135 D: 37845 Ptnml(0-2): 340, 8859, 20456, 9266, 327 https://tests.stockfishchess.org/tests/view/663d47bf507ebe1c0e9200ba LTC 60+0.6 : LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 94872 W: 24179 L: 23751 D: 46942 Ptnml(0-2): 61, 9743, 27405, 10161, 66 https://tests.stockfishchess.org/tests/view/663e779cbb28828150dd9089 closes https://github.com/official-stockfish/Stockfish/pull/5235 Bench: 1876282 --- src/nnue/nnue_feature_transformer.h | 9 ++++----- src/search.cpp | 3 ++- src/search.h | 1 + src/thread.cpp | 1 + src/timeman.cpp | 11 +++++++---- src/timeman.h | 3 ++- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index bcd14e6fbbc..7b7aada312c 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -666,8 +666,7 @@ class FeatureTransformer { { auto accTile = reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); - auto entryTile = - reinterpret_cast(&entry.accumulation[j * TileHeight]); + auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; @@ -714,9 +713,9 @@ class FeatureTransformer { { auto accTilePsqt = reinterpret_cast( &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - auto entryTilePsqt = reinterpret_cast( - &entry.psqtAccumulation[j * PsqtTileHeight]); - + auto entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; diff --git a/src/search.cpp b/src/search.cpp index ae2b1de23e9..edbb58c62cc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -157,7 +157,8 @@ void Search::Worker::start_searching() { return; } - main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options); + main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options, + main_manager()->originalPly); tt.new_search(); if (rootMoves.empty()) diff --git a/src/search.h b/src/search.h index c824daf931c..6e5b22bda32 100644 --- a/src/search.h +++ b/src/search.h @@ -210,6 +210,7 @@ class SearchManager: public ISearchManager { Depth depth) const; Stockfish::TimeManagement tm; + int originalPly; int callsCnt; std::atomic_bool ponder; diff --git a/src/thread.cpp b/src/thread.cpp index 9052654baf6..8724cb49cd1 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -167,6 +167,7 @@ void ThreadPool::clear() { main_manager()->callsCnt = 0; main_manager()->bestPreviousScore = VALUE_INFINITE; main_manager()->bestPreviousAverageScore = VALUE_INFINITE; + main_manager()->originalPly = -1; main_manager()->previousTimeReduction = 1.0; main_manager()->tm.clear(); } diff --git a/src/timeman.cpp b/src/timeman.cpp index 4feb329b335..f389e082d8e 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -44,10 +44,8 @@ void TimeManagement::advance_nodes_time(std::int64_t nodes) { // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) -void TimeManagement::init(Search::LimitsType& limits, - Color us, - int ply, - const OptionsMap& options) { +void TimeManagement::init( + Search::LimitsType& limits, Color us, int ply, const OptionsMap& options, int& originalPly) { TimePoint npmsec = TimePoint(options["nodestime"]); // If we have no time, we don't need to fully initialize TM. @@ -58,6 +56,9 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.time[us] == 0) return; + if (originalPly == -1) + originalPly = ply; + TimePoint moveOverhead = TimePoint(options["Move Overhead"]); // optScale is a percentage of available time to use for the current move. @@ -106,6 +107,8 @@ void TimeManagement::init(Search::LimitsType& limits, { // Use extra time with larger increments double optExtra = scaledInc < 500 ? 1.0 : 1.13; + if (ply - originalPly < 2) + optExtra *= 0.95; // Calculate time constants based on current time left. double logTimeInSec = std::log10(scaledTime / 1000.0); diff --git a/src/timeman.h b/src/timeman.h index 1b6bd849ae2..8f1bb56397d 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -36,7 +36,8 @@ struct LimitsType; // the maximum available time, the game move number, and other parameters. class TimeManagement { public: - void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options); + void init( + Search::LimitsType& limits, Color us, int ply, const OptionsMap& options, int& originalPly); TimePoint optimum() const; TimePoint maximum() const; From fa114266fa7ea996c6d2ef12c625547b1aefddc1 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 13 May 2024 14:08:19 +0300 Subject: [PATCH 0510/1309] Add extra bonus for high-depth condition Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 54208 W: 14058 L: 13717 D: 26433 Ptnml(0-2): 166, 6277, 13885, 6602, 174 https://tests.stockfishchess.org/tests/view/664136d8f9f4e8fc783c9b82 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 112548 W: 28492 L: 28018 D: 56038 Ptnml(0-2): 53, 12186, 31318, 12668, 49 https://tests.stockfishchess.org/tests/view/664143fef9f4e8fc783c9bf6 closes https://github.com/official-stockfish/Stockfish/pull/5242 Bench: 1725980 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index edbb58c62cc..30f718bd704 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -739,7 +739,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-12 * int((ss - 1)->staticEval + ss->staticEval), -1749, 1602); + int bonus = std::clamp(-12 * int((ss - 1)->staticEval + ss->staticEval), -1749, 1584); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -1328,8 +1328,8 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14323) - + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 127) + int bonus = (depth > 4) + (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14323) + + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 120) + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); From 9e45644c50e4650e4603ddef3e8147a8daf3a790 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 14 May 2024 20:10:01 +0300 Subject: [PATCH 0511/1309] Add extra bonus to pawn history for a move that caused a fail low Basically the same idea as it is for continuation/main history, but it has some tweaks. 1) it has * 2 multiplier for bonus instead of full/half bonus - for whatever reason this seems to work better; 2) attempts with this type of big bonuses scaled somewhat poorly (or were unlucky at longer time controls), but after measuring the fact that average value of pawn history in LMR after adding this bonuses increased by substantial number (for multiplier 1,5 it increased by smth like 400~ from 8192 cap) attempts were made to make default pawn history negative to compensate it - and version with multiplier 2 and initial fill value -900 passed. Passed STC: https://tests.stockfishchess.org/tests/view/66424815f9f4e8fc783cba59 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 115008 W: 30001 L: 29564 D: 55443 Ptnml(0-2): 432, 13629, 28903, 14150, 390 Passed LTC: https://tests.stockfishchess.org/tests/view/6642f5437134c82f3f7a3ffa LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 56448 W: 14432 L: 14067 D: 27949 Ptnml(0-2): 36, 6268, 15254, 6627, 39 Bench: 1857237 --- src/search.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 30f718bd704..09a9cc920ab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -496,7 +496,7 @@ void Search::Worker::clear() { counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(0); - pawnHistory.fill(0); + pawnHistory.fill(-900); correctionHistory.fill(0); for (bool inCheck : {false, true}) @@ -1335,6 +1335,11 @@ Value Search::Worker::search( stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << stat_bonus(depth) * bonus / 2; + + + 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] + << stat_bonus(depth) * bonus * 2; } if (PvNode) From 09dba1f0806a973d1f9f4ebf04b7a45d81683168 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 13 May 2024 15:28:48 -0700 Subject: [PATCH 0512/1309] Call adjustEval with correct parameters after rescore Set smallNet to false after rescoring so we call adjustEval() w/ correct parameters. STC: https://tests.stockfishchess.org/tests/view/664308687134c82f3f7a4003 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 146912 W: 37856 L: 37756 D: 71300 Ptnml(0-2): 566, 17562, 37122, 17618, 588 LTC: https://tests.stockfishchess.org/tests/view/6643a0821f32a966da7485d6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 390414 W: 98015 L: 98173 D: 194226 Ptnml(0-2): 162, 43555, 107929, 43401, 160 closes https://github.com/official-stockfish/Stockfish/pull/5244 Bench: 1819318 --- src/evaluate.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index b5f28d5aede..de1adc989c7 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -63,7 +63,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 500)) - nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); + { + nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); + smallNet = false; + } const auto adjustEval = [&](int nnueDiv, int pawnCountMul, int evalDiv, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance From 9b90cd88f0ddd568e43161a0ada7daf02fc59c67 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Wed, 15 May 2024 04:10:58 +0200 Subject: [PATCH 0513/1309] Reduce more when improving and ttvalue is lower than alpha More reduction if position is improving but value from TT doesn't exceeds alpha but the tt move is excluded. This idea is based on following LMR condition tuning https://tests.stockfishchess.org/tests/view/66423a1bf9f4e8fc783cba37 by using only three of the four largest terms P[3], P[18] and P[12]. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 27840 W: 7309 L: 7004 D: 13527 Ptnml(0-2): 85, 3219, 7018, 3502, 96 https://tests.stockfishchess.org/tests/view/6643dc1cbc537f56194508ba Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 191280 W: 48656 L: 48020 D: 94604 Ptnml(0-2): 78, 20979, 52903, 21589, 91 https://tests.stockfishchess.org/tests/view/6643e543bc537f5619451683 closes https://github.com/official-stockfish/Stockfish/pull/5245 Bench: 1430835 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 09a9cc920ab..2dadd0dca47 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1134,6 +1134,9 @@ Value Search::Worker::search( if (PvNode) r--; + if (improving && ttValue <= alpha && move != ttMove) + r++; + // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) r++; From 1f3a0fda2e3a0d4aa825dd148c2593fb3631bf82 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 13 May 2024 18:06:38 -0400 Subject: [PATCH 0514/1309] Use same eval divisor for both nets Passed non-regression STC: https://tests.stockfishchess.org/tests/view/66428f146577e9d2c8a29cf8 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 241024 W: 62173 L: 62177 D: 116674 Ptnml(0-2): 904, 28648, 61407, 28654, 899 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6643ae6f1f32a966da74977b LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 193710 W: 48762 L: 48717 D: 96231 Ptnml(0-2): 70, 21599, 53481, 21626, 79 closes https://github.com/official-stockfish/Stockfish/pull/5246 Bench: 1700680 --- src/evaluate.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index de1adc989c7..76d630dd9e4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -68,14 +68,13 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, smallNet = false; } - const auto adjustEval = [&](int nnueDiv, int pawnCountMul, int evalDiv, int shufflingConstant) { + const auto adjustEval = [&](int nnueDiv, int pawnCountMul, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; int npm = pos.non_pawn_material() / 64; - v = (nnue * (npm + 943 + pawnCountMul * pos.count()) + optimism * (npm + 140)) - / evalDiv; + v = (nnue * (npm + 943 + pawnCountMul * pos.count()) + optimism * (npm + 140)) / 1058; // Damp down the evaluation linearly when shuffling int shuffling = pos.rule50_count(); @@ -83,9 +82,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, }; if (!smallNet) - adjustEval(32395, 11, 1058, 178); + adjustEval(32395, 11, 178); else - adjustEval(32793, 9, 1067, 206); + adjustEval(32793, 9, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From dcb02337844d71e56df57b9a8ba17646f953711c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 15 May 2024 14:22:36 +0300 Subject: [PATCH 0515/1309] Simplifying improving and worsening deduction formulas Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 77696 W: 20052 L: 19878 D: 37766 Ptnml(0-2): 222, 9124, 19994, 9274, 234 https://tests.stockfishchess.org/tests/view/66440032bc537f561945171e Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 234414 W: 58874 L: 58871 D: 116669 Ptnml(0-2): 96, 26147, 64742, 26102, 120 https://tests.stockfishchess.org/tests/view/6644094cbc537f5619451735 closes https://github.com/official-stockfish/Stockfish/pull/5248 Bench: 1336738 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2dadd0dca47..d9041c66a65 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -60,8 +60,8 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 131 - 48 * noTtCutNode; - Value improvingDeduction = 57 * improving * futilityMult / 32; - Value worseningDeduction = (309 + 52 * improving) * oppWorsening * futilityMult / 1024; + Value improvingDeduction = 2 * improving * futilityMult; + Value worseningDeduction = 330 * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } From 541406ab9151891b3a42f49030a6167cfca55599 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 14 May 2024 16:51:02 -0400 Subject: [PATCH 0516/1309] Use same nnue divisor for both nets Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6643ceeabc537f56194506f6 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 224800 W: 57910 L: 57896 D: 108994 Ptnml(0-2): 673, 26790, 57519, 26686, 732 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6643ff15bc537f5619451719 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 347658 W: 87574 L: 87688 D: 172396 Ptnml(0-2): 207, 39004, 95488, 38956, 174 closes https://github.com/official-stockfish/Stockfish/pull/5250 Bench: 1804704 --- src/evaluate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 76d630dd9e4..3ce148627d5 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -68,10 +68,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, smallNet = false; } - const auto adjustEval = [&](int nnueDiv, int pawnCountMul, int shufflingConstant) { + const auto adjustEval = [&](int pawnCountMul, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; - nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; + nnue -= nnue * (nnueComplexity * 5 / 3) / 32395; int npm = pos.non_pawn_material() / 64; v = (nnue * (npm + 943 + pawnCountMul * pos.count()) + optimism * (npm + 140)) / 1058; @@ -82,9 +82,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, }; if (!smallNet) - adjustEval(32395, 11, 178); + adjustEval(11, 178); else - adjustEval(32793, 9, 206); + adjustEval(9, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From e3c9ed77aa62e096d52bb558193279b804f53a84 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 15 May 2024 22:32:55 +0800 Subject: [PATCH 0517/1309] Revert "Reduce more when improving and ttvalue is lower than alpha" The patch regressed significantly at longer time controls. Passed VLTC: https://tests.stockfishchess.org/tests/view/6644c7a2bc537f5619453096 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 43336 W: 11177 L: 10884 D: 21275 Ptnml(0-2): 3, 4432, 12507, 4721, 5 Passed VVLTC: https://tests.stockfishchess.org/tests/view/66450c974aa4fa9a83b6d0b0 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 32394 W: 8350 L: 8072 D: 15972 Ptnml(0-2): 2, 2798, 10317, 3080, 0 closes https://github.com/official-stockfish/Stockfish/pull/5251 Bench: 1594188 --- src/search.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d9041c66a65..bdcecd1c26c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1134,9 +1134,6 @@ Value Search::Worker::search( if (PvNode) r--; - if (improving && ttValue <= alpha && move != ttMove) - r++; - // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) r++; From 47597641dc8da7c65d0f1d987f784af09d6aec15 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 15 May 2024 13:22:46 -0400 Subject: [PATCH 0518/1309] Lower smallnet threshold linearly as pawn count decreases Passed STC: https://tests.stockfishchess.org/tests/view/6644f677324e96f42f89d894 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 377920 W: 97135 L: 96322 D: 184463 Ptnml(0-2): 1044, 44259, 97588, 44978, 1091 Passed LTC: https://tests.stockfishchess.org/tests/view/664548af93ce6da3e93b31b3 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 169056 W: 42901 L: 42312 D: 83843 Ptnml(0-2): 58, 18538, 46753, 19115, 64 closes https://github.com/official-stockfish/Stockfish/pull/5252 Bench: 1991750 --- src/evaluate.cpp | 2 +- src/evaluate.h | 2 +- src/nnue/nnue_misc.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3ce148627d5..498ec161bb0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -55,7 +55,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); int simpleEval = simple_eval(pos, pos.side_to_move()); - bool smallNet = std::abs(simpleEval) > SmallNetThreshold; + bool smallNet = std::abs(simpleEval) > SmallNetThreshold + 6 * pos.count(); int nnueComplexity; int v; diff --git a/src/evaluate.h b/src/evaluate.h index afaf35ebe17..6612ec9daf5 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1174; +constexpr inline int SmallNetThreshold = 1126; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index bf73a58bf07..8a777912043 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -47,7 +47,7 @@ void hint_common_parent_position(const Position& pos, AccumulatorCaches& caches) { int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); - if (simpleEvalAbs > Eval::SmallNetThreshold) + if (simpleEvalAbs > Eval::SmallNetThreshold + 6 * pos.count()) networks.small.hint_common_access(pos, &caches.small); else networks.big.hint_common_access(pos, &caches.big); From e0227a627288c786fdd3b12452303ff4eabba5b0 Mon Sep 17 00:00:00 2001 From: Rak Laptudirm Date: Wed, 15 May 2024 22:26:12 +0530 Subject: [PATCH 0519/1309] Improve comment closes https://github.com/official-stockfish/Stockfish/pull/5249 No functional change --- src/tt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tt.cpp b/src/tt.cpp index 4885a781a5e..cb46fc8a9a6 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -35,7 +35,7 @@ namespace Stockfish { void TTEntry::save( Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) { - // Preserve any existing move for the same position + // Preserve the old ttmove if we don't have a new one if (m || uint16_t(k) != key16) move16 = m; From 1b7dea3f851cd5c5411ba6f07a2f935bfb7da8a9 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 15 May 2024 19:26:48 -0400 Subject: [PATCH 0520/1309] Update default main net to nn-c721dfca8cd3.nnue Created by first retraining the spsa-tuned main net `nn-ae6a388e4a1a.nnue` with: - using v6-dd data without bestmove captures removed - addition of T80 mar2024 data - increasing loss by 20% when Q is too high - torch.compile changes for marginal training speed gains And then SPSA tuning weights of epoch 899 following methods described in: https://github.com/official-stockfish/Stockfish/pull/5149 This net was reached at 92k out of 120k steps in this 70+0.7 th 7 SPSA tuning run: https://tests.stockfishchess.org/tests/view/66413b7df9f4e8fc783c9bbb Thanks to @Viren6 for suggesting usage of: - c value 4 for the weights - c value 128 for the biases Scripts for automating applying fishtest spsa params to exporting tuned .nnue are in: https://github.com/linrock/nnue-tools/tree/master/spsa Before spsa tuning, epoch 899 was nn-f85738aefa84.nnue https://tests.stockfishchess.org/tests/view/663e5c893a2f9702074bc167 After initially training with max-epoch 800, training was resumed with max-epoch 1000. ``` experiment-name: 3072--S11--more-data-v6-dd-t80-mar2024--see-ge0-20p-more-loss-high-q-sk28-l8 nnue-pytorch-branch: linrock/nnue-pytorch/3072-r21-skip-more-wdl-see-ge0-20p-more-loss-high-q-torch-compile-more start-from-engine-test-net: False start-from-model: /data/config/apr2024-3072/nn-ae6a388e4a1a.nnue early-fen-skipping: 28 training-dataset: /data/S11-mar2024/: - leela96.v2.min.binpack - test60-2021-11-12-novdec-12tb7p.v6-dd.min.binpack - test78-2022-01-to-05-jantomay-16tb7p.v6-dd.min.binpack - test80-2022-06-jun-16tb7p.v6-dd.min.binpack - test80-2022-08-aug-16tb7p.v6-dd.min.binpack - test80-2022-09-sep-16tb7p.v6-dd.min.binpack - test80-2023-01-jan-16tb7p.v6-sk20.min.binpack - test80-2023-02-feb-16tb7p.v6-sk20.min.binpack - test80-2023-03-mar-2tb7p.v6-sk16.min.binpack - test80-2023-04-apr-2tb7p.v6-sk16.min.binpack - test80-2023-05-may-2tb7p.v6.min.binpack # https://github.com/official-stockfish/Stockfish/pull/4782 - test80-2023-06-jun-2tb7p.binpack - test80-2023-07-jul-2tb7p.binpack # https://github.com/official-stockfish/Stockfish/pull/4972 - test80-2023-08-aug-2tb7p.v6.min.binpack - test80-2023-09-sep-2tb7p.binpack - test80-2023-10-oct-2tb7p.binpack # S9 new data: https://github.com/official-stockfish/Stockfish/pull/5056 - test80-2023-11-nov-2tb7p.binpack - test80-2023-12-dec-2tb7p.binpack # S10 new data: https://github.com/official-stockfish/Stockfish/pull/5149 - test80-2024-01-jan-2tb7p.binpack - test80-2024-02-feb-2tb7p.binpack # S11 new data - test80-2024-03-mar-2tb7p.binpack /data/filt-v6-dd/: - test77-dec2021-16tb7p-filter-v6-dd.binpack - test78-juntosep2022-16tb7p-filter-v6-dd.binpack - test79-apr2022-16tb7p-filter-v6-dd.binpack - test79-may2022-16tb7p-filter-v6-dd.binpack - test80-jul2022-16tb7p-filter-v6-dd.binpack - test80-oct2022-16tb7p-filter-v6-dd.binpack - test80-nov2022-16tb7p-filter-v6-dd.binpack num-epochs: 1000 lr: 4.375e-4 gamma: 0.995 start-lambda: 0.8 end-lambda: 0.7 ``` Training data can be found at: https://robotmoon.com/nnue-training-data/ Local elo at 25k nodes per move: nn-epoch899.nnue : 4.6 +/- 1.4 Passed STC: https://tests.stockfishchess.org/tests/view/6645454893ce6da3e93b31ae LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 95232 W: 24598 L: 24194 D: 46440 Ptnml(0-2): 294, 11215, 24180, 11647, 280 Passed LTC: https://tests.stockfishchess.org/tests/view/6645522d93ce6da3e93b31df LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 320544 W: 81432 L: 80524 D: 158588 Ptnml(0-2): 164, 35659, 87696, 36611, 142 closes https://github.com/official-stockfish/Stockfish/pull/5254 bench 1995552 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 6612ec9daf5..c87be53c14f 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -35,7 +35,7 @@ constexpr inline int SmallNetThreshold = 1126; // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-ae6a388e4a1a.nnue" +#define EvalFileDefaultNameBig "nn-c721dfca8cd3.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { From d92d1f31809afc8aa83cc14fcbd54b95258d09ad Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 16 May 2024 01:48:56 -0400 Subject: [PATCH 0521/1309] Move smallnet threshold logic into a function Now that the smallnet threshold is no longer a constant, use a function to organize it with other eval code. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/66459fa093ce6da3e93b5ba2 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 217600 W: 56281 L: 56260 D: 105059 Ptnml(0-2): 756, 23787, 59729, 23736, 792 closes https://github.com/official-stockfish/Stockfish/pull/5255 No functional change --- src/evaluate.cpp | 6 +++++- src/evaluate.h | 3 +-- src/nnue/nnue_misc.cpp | 4 +--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 498ec161bb0..09402b8b100 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -44,6 +44,10 @@ int Eval::simple_eval(const Position& pos, Color c) { + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } +bool Eval::use_smallnet(const Position& pos) { + int simpleEval = simple_eval(pos, pos.side_to_move()); + return std::abs(simpleEval) > 1126 + 6 * pos.count(); +} // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. @@ -55,7 +59,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); int simpleEval = simple_eval(pos, pos.side_to_move()); - bool smallNet = std::abs(simpleEval) > SmallNetThreshold + 6 * pos.count(); + bool smallNet = use_smallnet(pos); int nnueComplexity; int v; diff --git a/src/evaluate.h b/src/evaluate.h index c87be53c14f..4b3e91acf4c 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,8 +29,6 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1126; - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used @@ -46,6 +44,7 @@ struct AccumulatorCaches; std::string trace(Position& pos, const Eval::NNUE::Networks& networks); int simple_eval(const Position& pos, Color c); +bool use_smallnet(const Position& pos); Value evaluate(const NNUE::Networks& networks, const Position& pos, Eval::NNUE::AccumulatorCaches& caches, diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 8a777912043..a13c717c3d8 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -45,9 +45,7 @@ constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); void hint_common_parent_position(const Position& pos, const Networks& networks, AccumulatorCaches& caches) { - - int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); - if (simpleEvalAbs > Eval::SmallNetThreshold + 6 * pos.count()) + if (Eval::use_smallnet(pos)) networks.small.hint_common_access(pos, &caches.small); else networks.big.hint_common_access(pos, &caches.big); From f5e15441b8e3b8087024d309313e8a4d6c48bba7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 18 May 2024 01:22:41 +0300 Subject: [PATCH 0522/1309] Early Exit in Bitboards::sliding_attack() he original code checks for occupancy within the loop condition. By moving this check inside the loop and adding an early exit condition, we can avoid unnecessary iterations if a blocking piece is encountered. Passed stc: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 127200 W: 33129 L: 32700 D: 61371 Ptnml(0-2): 424, 13243, 35826, 13694, 413 https://tests.stockfishchess.org/tests/view/664646006dcff0d1d6b05bca closes https://github.com/official-stockfish/Stockfish/pull/5256 No functional change --- src/bitboard.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 32c626d4773..c842ca1271e 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -124,8 +124,14 @@ Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) { Square s = sq; - while (safe_destination(s, d) && !(occupied & s)) + while (safe_destination(s, d)) + { attacks |= (s += d); + if (occupied & s) + { + break; + } + } } return attacks; From 285f1d2a663fb111f7124272403923eab4251982 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 15 May 2024 22:26:15 -0700 Subject: [PATCH 0523/1309] Tweak NMP Formula Passed STC: LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 241728 W: 62440 L: 61811 D: 117477 Ptnml(0-2): 914, 28467, 61458, 29126, 899 https://tests.stockfishchess.org/tests/live_elo/6645992993ce6da3e93b5b99 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 167850 W: 42620 L: 42030 D: 83200 Ptnml(0-2): 82, 18412, 46354, 18988, 89 https://tests.stockfishchess.org/tests/live_elo/6647c5726dcff0d1d6b05dd3 closes https://github.com/official-stockfish/Stockfish/pull/5257 Bench: 1636018 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index bdcecd1c26c..06c6e1987aa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -787,7 +787,7 @@ Value Search::Worker::search( assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; From 99dfc63e0321cb8544ce5455993df00a6c817ba3 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 17 May 2024 19:27:20 -0400 Subject: [PATCH 0524/1309] Use one nnue pawn count multiplier Switch to the value used by the main net. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6647e8096dcff0d1d6b05e96 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 51040 W: 13249 L: 13044 D: 24747 Ptnml(0-2): 139, 6029, 13016, 6160, 176 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6647f4a46dcff0d1d6b05eea LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 20460 W: 5195 L: 4972 D: 10293 Ptnml(0-2): 8, 2178, 5637, 2397, 10 https://github.com/official-stockfish/Stockfish/pull/5258 bench 1887462 --- src/evaluate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 09402b8b100..abb04fcc2e1 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -72,13 +72,13 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, smallNet = false; } - const auto adjustEval = [&](int pawnCountMul, int shufflingConstant) { + const auto adjustEval = [&](int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; nnue -= nnue * (nnueComplexity * 5 / 3) / 32395; int npm = pos.non_pawn_material() / 64; - v = (nnue * (npm + 943 + pawnCountMul * pos.count()) + optimism * (npm + 140)) / 1058; + v = (nnue * (npm + 943 + 11 * pos.count()) + optimism * (npm + 140)) / 1058; // Damp down the evaluation linearly when shuffling int shuffling = pos.rule50_count(); @@ -86,9 +86,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, }; if (!smallNet) - adjustEval(11, 178); + adjustEval(178); else - adjustEval(9, 206); + adjustEval(206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 4edd1a389e4146a610098a841841f37f58980213 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 17 May 2024 17:45:09 -0700 Subject: [PATCH 0525/1309] Simplify Away Quadruple Extensions serendipitous gainer Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 95472 W: 24176 L: 24031 D: 47265 Ptnml(0-2): 52, 10533, 26414, 10692, 45 https://tests.stockfishchess.org/tests/live_elo/6647fa596dcff0d1d6b05efa Passed VVLTC 70+7 th 7: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 6772 W: 1793 L: 1583 D: 3396 Ptnml(0-2): 0, 502, 2172, 712, 0 https://tests.stockfishchess.org/tests/live_elo/6648277a6dcff0d1d6b05ffb Passed VVLTC 70+7 th 7 (2x): https://tests.stockfishchess.org/tests/view/66484c896dcff0d1d6b0619d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 5424 W: 1469 L: 1254 D: 2701 Ptnml(0-2): 0, 394, 1710, 607, 1 closes https://github.com/official-stockfish/Stockfish/pull/5259 Bench: 1441794 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 06c6e1987aa..2b95043fd37 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,11 +1053,9 @@ Value Search::Worker::search( int doubleMargin = 285 * PvNode - 228 * !ttCapture; int tripleMargin = 121 + 238 * PvNode - 259 * !ttCapture + 117 * (ss->ttPv || !ttCapture); - int quadMargin = 471 + 343 * PvNode - 281 * !ttCapture + 217 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) - + (value < singularBeta - tripleMargin) - + (value < singularBeta - quadMargin); + + (value < singularBeta - tripleMargin); depth += ((!PvNode) && (depth < 14)); } From 2694fce928e5eec867d56d853b416c9f389c284d Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 17 May 2024 21:38:38 -0400 Subject: [PATCH 0526/1309] Simplify away adjustEval lambda Now that only the shuffling constant differs between nets, a lambda for adjusting eval is no longer needed. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/664806ca6dcff0d1d6b05f34 LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 31552 W: 8175 L: 7959 D: 15418 Ptnml(0-2): 76, 3180, 9065, 3362, 93 closes https://github.com/official-stockfish/Stockfish/pull/5260 No functional change --- src/evaluate.cpp | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index abb04fcc2e1..e5ebd45ae03 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -72,23 +72,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, smallNet = false; } - const auto adjustEval = [&](int shufflingConstant) { - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; - nnue -= nnue * (nnueComplexity * 5 / 3) / 32395; - - int npm = pos.non_pawn_material() / 64; - v = (nnue * (npm + 943 + 11 * pos.count()) + optimism * (npm + 140)) / 1058; - - // Damp down the evaluation linearly when shuffling - int shuffling = pos.rule50_count(); - v = v * (shufflingConstant - shuffling) / 207; - }; - - if (!smallNet) - adjustEval(178); - else - adjustEval(206); + // Blend optimism and eval with nnue complexity and material imbalance + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; + nnue -= nnue * (nnueComplexity * 5 / 3) / 32395; + + int npm = pos.non_pawn_material() / 64; + v = (nnue * (npm + 943 + 11 * pos.count()) + optimism * (npm + 140)) / 1058; + + // Damp down the evaluation linearly when shuffling + v = v * ((smallNet ? 206 : 178) - pos.rule50_count()) / 207; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From 99f1bacfd6864afca86ae74f33232b9cdfb3828c Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 18 May 2024 22:15:41 +0800 Subject: [PATCH 0527/1309] VVLTC search tune Tuned with 85k games at VVLTC. VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/6648b836308cceea45533ad7 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 14880 W: 3890 L: 3652 D: 7338 Ptnml(0-2): 0, 1255, 4694, 1489, 2 VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/6648c34f308cceea45533b4f LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 24984 W: 6502 L: 6235 D: 12247 Ptnml(0-2): 1, 2178, 7867, 2445, 1 closes https://github.com/official-stockfish/Stockfish/pull/5264 Bench: 1198142 --- src/search.cpp | 84 +++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2b95043fd37..54990ce6f03 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -59,9 +59,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 131 - 48 * noTtCutNode; - Value improvingDeduction = 2 * improving * futilityMult; - Value worseningDeduction = 330 * oppWorsening * futilityMult / 1024; + Value futilityMult = 127 - 48 * noTtCutNode; + Value improvingDeduction = 65 * improving * futilityMult / 32; + Value worseningDeduction = 334 * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -73,15 +73,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 7179; + v += cv * std::abs(cv) / 6047; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(200 * d - 280, 16, 1495); } +int stat_bonus(Depth d) { return std::clamp(187 * d - 288, 17, 1548); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 586 * d - 284 : 1639); } +int stat_malus(Depth d) { return (d < 4 ? 630 * d - 281 : 1741); } // 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); } @@ -311,12 +311,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 9474; + delta = 10 + avg * avg / 9828; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 117 * avg / (std::abs(avg) + 88); + optimism[us] = 116 * avg / (std::abs(avg) + 84); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -503,10 +503,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-62); + h->fill(-60); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((21.19 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((21.69 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks); } @@ -739,7 +739,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-12 * int((ss - 1)->staticEval + ss->staticEval), -1749, 1584); + int bonus = std::clamp(-11 * int((ss - 1)->staticEval + ss->staticEval), -1729, 1517); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -762,7 +762,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 473 - (308 - 138 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 474 - (326 - 139 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -773,21 +773,21 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 258 + - (ss - 1)->statScore / 252 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16079 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 324 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 15246 + && eval >= beta && ss->staticEval >= beta - 21 * depth + 366 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -835,7 +835,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 177 - 65 * improving; + probCutBeta = beta + 176 - 65 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -891,7 +891,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 428; + probCutBeta = beta + 440; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -975,15 +975,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 305 + 272 * lmrDepth + Value futilityValue = ss->staticEval + 276 + 256 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -185 * depth, 182 * depth); - if (!pos.see_ge(move, -176 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -177 * depth, 175 * depth); + if (!pos.see_ge(move, -183 * depth - seeHist)) continue; } else @@ -994,18 +994,18 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4360 * depth) + if (lmrDepth < 6 && history < -4076 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 4507; + lmrDepth += history / 4401; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 54 ? 142 : 55) + 132 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 53 ? 151 : 57) + 140 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 10 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1016,7 +1016,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1036,11 +1036,11 @@ Value Search::Worker::search( // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 35) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (59 + 49 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (57 + 50 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1050,14 +1050,14 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 285 * PvNode - 228 * !ttCapture; + int doubleMargin = 298 * PvNode - 209 * !ttCapture; int tripleMargin = - 121 + 238 * PvNode - 259 * !ttCapture + 117 * (ss->ttPv || !ttCapture); + 117 + 252 * PvNode - 270 * !ttCapture + 111 * (ss->ttPv || !ttCapture); extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += ((!PvNode) && (depth < 14)); + depth += ((!PvNode) && (depth < 15)); } // Multi-cut pruning @@ -1092,7 +1092,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4041) + > 3748) extension = 1; } @@ -1143,10 +1143,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 5313; + + (*contHist[1])[movedPiece][move.to_sq()] - 5266; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / (16145 - std::min(depth, 15) * 102); + r -= ss->statScore / (14519 - std::min(depth, 15) * 103); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1165,7 +1165,7 @@ Value Search::Worker::search( { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1326,9 +1326,9 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 4) + (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14323) - + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 120) - + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76); + int bonus = (depth > 4) + (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -13241) + + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 127) + + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 74); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1494,7 +1494,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 259; + futilityBase = ss->staticEval + 264; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1566,11 +1566,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()] - <= 4057) + <= 4348) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -68)) + if (!pos.see_ge(move, -63)) continue; } @@ -1636,7 +1636,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1284 - delta * 755 / rootDelta) / 1024 + (!i && reductionScale > 1133); + return (reductionScale + 1147 - delta * 755 / rootDelta) / 1024 + (!i && reductionScale > 1125); } // elapsed() returns the time elapsed since the search started. If the From 2d3258162387bf38551962bf2c9dd1d47e72b4dd Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Sun, 19 May 2024 01:40:29 +0100 Subject: [PATCH 0528/1309] Revert "Simplify Away Quadruple Extensions" This reverts commit 4edd1a3 The unusual result of (combined) +12.0 +- 3.7 in the 2 VVLTC simplification SPRTs ran was the result of base having only 64MB of hash instead of 512MB (Asymmetric hash). Vizvezdenec was the one to notice this. closes https://github.com/official-stockfish/Stockfish/pull/5265 bench 1404295 Co-Authored-By: Michael Chaly <26898827+Vizvezdenec@users.noreply.github.com> --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 54990ce6f03..cbd454efb76 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,9 +1053,11 @@ Value Search::Worker::search( int doubleMargin = 298 * PvNode - 209 * !ttCapture; int tripleMargin = 117 + 252 * PvNode - 270 * !ttCapture + 111 * (ss->ttPv || !ttCapture); + int quadMargin = 471 + 343 * PvNode - 281 * !ttCapture + 217 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) - + (value < singularBeta - tripleMargin); + + (value < singularBeta - tripleMargin) + + (value < singularBeta - quadMargin); depth += ((!PvNode) && (depth < 15)); } From 27eb49a2211c90650ef64d5102e6e36ca5e69af0 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Fri, 17 May 2024 18:05:12 +0800 Subject: [PATCH 0529/1309] Simplify ClippedReLU Removes some max calls Some speedup stats, courtesy of @AndyGrant (albeit measured in an alternate implementation) Dev 749240 nps Base 748495 nps Gain 0.100% 289936 games STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 203040 W: 52213 L: 52179 D: 98648 Ptnml(0-2): 480, 20722, 59139, 20642, 537 https://tests.stockfishchess.org/tests/view/664805fe6dcff0d1d6b05f2c closes #5261 No functional change --- src/nnue/layers/clipped_relu.h | 48 ++++++++++++++++------------------ src/nnue/nnue_misc.cpp | 9 +++---- src/tune.cpp | 6 ++--- src/uci.cpp | 6 ++--- 4 files changed, 30 insertions(+), 39 deletions(-) diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 813234c59cd..2ee378ad881 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -65,41 +65,37 @@ class ClippedReLU { if constexpr (InputDimensions % SimdWidth == 0) { constexpr IndexType NumChunks = InputDimensions / SimdWidth; - const __m256i Zero = _mm256_setzero_si256(); const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m256i*>(output); for (IndexType i = 0; i < NumChunks; ++i) { const __m256i words0 = - _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]), - _mm256_load_si256(&in[i * 4 + 1])), + _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits); const __m256i words1 = - _mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]), - _mm256_load_si256(&in[i * 4 + 3])), + _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits); - _mm256_store_si256( - &out[i], _mm256_permutevar8x32_epi32( - _mm256_max_epi8(_mm256_packs_epi16(words0, words1), Zero), Offsets)); + _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32( + _mm256_packs_epi16(words0, words1), Offsets)); } } else { constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); - const __m128i Zero = _mm_setzero_si128(); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); for (IndexType i = 0; i < NumChunks; ++i) { - const __m128i words0 = _mm_srai_epi16( - _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + const __m128i words0 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); - const __m128i words1 = _mm_srai_epi16( - _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + const __m128i words1 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); - const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); } } constexpr IndexType Start = InputDimensions % SimdWidth == 0 @@ -109,9 +105,7 @@ class ClippedReLU { #elif defined(USE_SSE2) constexpr IndexType NumChunks = InputDimensions / SimdWidth; - #ifdef USE_SSE41 - const __m128i Zero = _mm_setzero_si128(); - #else + #ifndef USE_SSE41 const __m128i k0x80s = _mm_set1_epi8(-128); #endif @@ -119,6 +113,15 @@ class ClippedReLU { const auto out = reinterpret_cast<__m128i*>(output); for (IndexType i = 0; i < NumChunks; ++i) { + #if defined(USE_SSE41) + const __m128i words0 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + #else const __m128i words0 = _mm_srai_epi16( _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); @@ -126,15 +129,8 @@ class ClippedReLU { _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); const __m128i packedbytes = _mm_packs_epi16(words0, words1); - _mm_store_si128(&out[i], - - #ifdef USE_SSE41 - _mm_max_epi8(packedbytes, Zero) - #else - _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + _mm_store_si128(&out[i], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)); #endif - - ); } constexpr IndexType Start = NumChunks * SimdWidth; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index a13c717c3d8..b54bbaba3dd 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -178,14 +178,11 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << "| " << bucket << " "; ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss, pos); - ss << " " - << " | "; + ss << " " << " | "; format_cp_aligned_dot(t.positional[bucket], ss, pos); - ss << " " - << " | "; + ss << " " << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); - ss << " " - << " |"; + ss << " " << " |"; if (bucket == t.correctBucket) ss << " <-- this bucket is used"; ss << '\n'; diff --git a/src/tune.cpp b/src/tune.cpp index 3e5ebe5e6c3..84f59524fb4 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -59,8 +59,7 @@ void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) // Print formatted parameters, ready to be copy-pasted in Fishtest std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," - << (r(v).second - r(v).first) / 20.0 << "," - << "0.0020" << std::endl; + << (r(v).second - r(v).first) / 20.0 << "," << "0.0020" << std::endl; } } @@ -118,7 +117,6 @@ void Tune::Entry::read_option() { namespace Stockfish { -void Tune::read_results() { /* ...insert your values here... */ -} +void Tune::read_results() { /* ...insert your values here... */ } } // namespace Stockfish diff --git a/src/uci.cpp b/src/uci.cpp index cb686a027db..cb9d7b08556 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -286,9 +286,9 @@ void UCIEngine::bench(std::istream& args) { dbg_print(); - std::cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes - << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; + std::cerr << "\n===========================" << "\nTotal time (ms) : " << elapsed + << "\nNodes searched : " << nodes << "\nNodes/second : " << 1000 * nodes / elapsed + << std::endl; // reset callback, to not capture a dangling reference to nodesSearched engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); From a3bb7e626d1489bbbcc16014b16065849ec786b5 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 19 May 2024 10:12:05 +0200 Subject: [PATCH 0530/1309] Tweak continuation history bonus dependent on ply. This patch is based on following tuning https://tests.stockfishchess.org/tests/view/6648b2eb308cceea45533abe by only using the tuned factors for the continuation history. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 99904 W: 25865 L: 25457 D: 48582 Ptnml(0-2): 281, 11705, 25578, 12101, 287 https://tests.stockfishchess.org/tests/view/6648c136308cceea45533af8 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 36402 W: 9362 L: 9039 D: 18001 Ptnml(0-2): 20, 3952, 9951, 4241, 37 https://tests.stockfishchess.org/tests/view/6648ee3cb8fa20e74c39f3fd closes https://github.com/official-stockfish/Stockfish/pull/5267 Bench: 1917762 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index cbd454efb76..5b9c9bb00a1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1779,6 +1779,8 @@ void update_all_stats(const Position& pos, // by moves at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { + bonus = bonus * (112 * ss->ply + 136) / (159 * ss->ply + 124); + for (int i : {1, 2, 3, 4, 6}) { // Only update the first 2 continuation histories if we are in check From 4a66a7c9caeca70ea8cd4527de7ec1e839b6cf46 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 19 May 2024 18:48:43 +0300 Subject: [PATCH 0531/1309] Do more aggressive pawn history updates Tweak of recent patch that made pawn history to update for move that caused a fail low - and setting up default value of it to -900. This patch makes it more aggressive - twice bigger updates and default value -1100. Passed STC: https://tests.stockfishchess.org/tests/view/6648c5d4308cceea45533b5d LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 235200 W: 61090 L: 60476 D: 113634 Ptnml(0-2): 763, 27952, 59651, 28376, 858 Passed LTC: https://tests.stockfishchess.org/tests/view/664a1008ae57c1758ac5b523 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 20076 W: 5193 L: 4908 D: 9975 Ptnml(0-2): 7, 2105, 5534, 2380, 12 closes https://github.com/official-stockfish/Stockfish/pull/5268 Bench: 1590474 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5b9c9bb00a1..2618b984797 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -496,7 +496,7 @@ void Search::Worker::clear() { counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(0); - pawnHistory.fill(-900); + pawnHistory.fill(-1100); correctionHistory.fill(0); for (bool inCheck : {false, true}) @@ -1339,7 +1339,7 @@ Value Search::Worker::search( 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] - << stat_bonus(depth) * bonus * 2; + << stat_bonus(depth) * bonus * 4; } if (PvNode) From 81e21a69f02164fd988d5636a47c8790a1174b81 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 19 May 2024 10:12:05 +0200 Subject: [PATCH 0532/1309] Simplify the recently introduced ply-based cmh bonus factor. Replace it with a constant which is an approximation of the limit of the factor. STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 120064 W: 30967 L: 30836 D: 58261 Ptnml(0-2): 421, 14238, 30608, 14319, 446 https://tests.stockfishchess.org/tests/view/6649d146b8fa20e74c39f4ad LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 53856 W: 13719 L: 13530 D: 26607 Ptnml(0-2): 31, 5879, 14922, 6062, 34 https://tests.stockfishchess.org/tests/view/664a027fae57c1758ac5b4ee closes https://github.com/official-stockfish/Stockfish/pull/5270 Bench: 1355618 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2618b984797..7e95dd878c3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1779,7 +1779,7 @@ void update_all_stats(const Position& pos, // by moves at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - bonus = bonus * (112 * ss->ply + 136) / (159 * ss->ply + 124); + bonus = bonus * 45 / 64; for (int i : {1, 2, 3, 4, 6}) { From 4d88a63e607f44e59b9cc56b45984937e5eb123c Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 19 May 2024 14:01:49 -0400 Subject: [PATCH 0533/1309] Re-eval only if smallnet output flips from simple eval Recent attempts to change the smallnet nnue re-eval threshold did not show much elo difference: https://tests.stockfishchess.org/tests/view/664a29bb25a9058c4d21d53c https://tests.stockfishchess.org/tests/view/664a299925a9058c4d21d53a Passed non-regression STC: https://tests.stockfishchess.org/tests/view/664a3ea95fc7b70b8817aee2 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 22304 W: 5905 L: 5664 D: 10735 Ptnml(0-2): 67, 2602, 5603, 2783, 97 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/664a43d35fc7b70b8817aef4 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 37536 W: 9667 L: 9460 D: 18409 Ptnml(0-2): 25, 4090, 10321, 4317, 15 closes https://github.com/official-stockfish/Stockfish/pull/5271 bench 1287409 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e5ebd45ae03..2cf82eaf5a0 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -66,7 +66,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); - if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 500)) + if (smallNet && nnue * simpleEval < 0) { nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); smallNet = false; From 0c797367a3a9783ff87422d543eb2106fea3e948 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 20 May 2024 03:22:40 +0300 Subject: [PATCH 0534/1309] Update correction history in case of successful null move pruning Since null move pruning uses the same position it makes some sense to try to update correction history there in case of fail high. Update value is 4 times less than normal update. Passed STC: https://tests.stockfishchess.org/tests/view/664a011cae57c1758ac5b4dd LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 419360 W: 108390 L: 107505 D: 203465 Ptnml(0-2): 1416, 49603, 106724, 50554, 1383 Passed LTC: https://tests.stockfishchess.org/tests/view/664a53d95fc7b70b8817c65b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 193518 W: 49076 L: 48434 D: 96008 Ptnml(0-2): 89, 21335, 53263, 21989, 83 closes https://github.com/official-stockfish/Stockfish/pull/5272 bench 1301487 --- src/nnue/nnue_misc.cpp | 9 ++++++--- src/search.cpp | 9 +++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index b54bbaba3dd..a13c717c3d8 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -178,11 +178,14 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << "| " << bucket << " "; ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss, pos); - ss << " " << " | "; + ss << " " + << " | "; format_cp_aligned_dot(t.positional[bucket], ss, pos); - ss << " " << " | "; + ss << " " + << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); - ss << " " << " |"; + ss << " " + << " |"; if (bucket == t.correctBucket) ss << " <-- this bucket is used"; ss << '\n'; diff --git a/src/search.cpp b/src/search.cpp index 7e95dd878c3..2ed5d97bb5d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -802,7 +802,16 @@ Value Search::Worker::search( if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { if (thisThread->nmpMinPly || depth < 16) + { + if (nullValue >= ss->staticEval) + { + auto bonus = std::min(int(nullValue - ss->staticEval) * depth / 32, + CORRECTION_HISTORY_LIMIT / 16); + thisThread->correctionHistory[us][pawn_structure_index(pos)] + << bonus; + } return nullValue; + } assert(!thisThread->nmpMinPly); // Recursive verification is not allowed From b8ccaf038a21effba4613dca95f30eb1bc3d77b9 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 20 May 2024 03:14:00 +0300 Subject: [PATCH 0535/1309] Use same shuffling Constant for both nets Passed STC: https://tests.stockfishchess.org/tests/view/664a42b15fc7b70b8817aeef LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 87840 W: 22759 L: 22594 D: 42487 Ptnml(0-2): 335, 10351, 22324, 10634, 276 Passed LTC: https://tests.stockfishchess.org/tests/view/664a46995fc7b70b8817af02 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 163122 W: 41443 L: 41367 D: 80312 Ptnml(0-2): 105, 18154, 44927, 18310, 65 closes https://github.com/official-stockfish/Stockfish/pull/5273 bench: 1190174 --- src/evaluate.cpp | 2 +- src/tune.cpp | 6 ++++-- src/uci.cpp | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 2cf82eaf5a0..3a24657f9fd 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -80,7 +80,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, v = (nnue * (npm + 943 + 11 * pos.count()) + optimism * (npm + 140)) / 1058; // Damp down the evaluation linearly when shuffling - v = v * ((smallNet ? 206 : 178) - pos.rule50_count()) / 207; + v = v * (204 - pos.rule50_count()) / 208; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/tune.cpp b/src/tune.cpp index 84f59524fb4..3e5ebe5e6c3 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -59,7 +59,8 @@ void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) // Print formatted parameters, ready to be copy-pasted in Fishtest std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," - << (r(v).second - r(v).first) / 20.0 << "," << "0.0020" << std::endl; + << (r(v).second - r(v).first) / 20.0 << "," + << "0.0020" << std::endl; } } @@ -117,6 +118,7 @@ void Tune::Entry::read_option() { namespace Stockfish { -void Tune::read_results() { /* ...insert your values here... */ } +void Tune::read_results() { /* ...insert your values here... */ +} } // namespace Stockfish diff --git a/src/uci.cpp b/src/uci.cpp index cb9d7b08556..cb686a027db 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -286,9 +286,9 @@ void UCIEngine::bench(std::istream& args) { dbg_print(); - std::cerr << "\n===========================" << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes << "\nNodes/second : " << 1000 * nodes / elapsed - << std::endl; + std::cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; // reset callback, to not capture a dangling reference to nodesSearched engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); From daf9787de197ce9e5478f3e7ceec8c64cb3d549a Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 21 May 2024 00:40:55 +0300 Subject: [PATCH 0536/1309] Rescale pawn history updates This patch is somewhat of a continuation of recent pawn history gainers. It makes pawn history updates after search twice smaller. Since on average they make pawn history more negative offset is changed to lower value to remain average value approximately the same. https://tests.stockfishchess.org/tests/view/664b3af9830eb9f886614aab Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 170464 W: 44239 L: 43724 D: 82501 Ptnml(0-2): 523, 20278, 43128, 20767, 536 Passed LTC against pending PR : https://tests.stockfishchess.org/tests/view/664b8c58830eb9f886614b64 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 98178 W: 25015 L: 24569 D: 48594 Ptnml(0-2): 48, 10769, 27005, 11223, 44 closes https://github.com/official-stockfish/Stockfish/pull/5275 Bench: 1343175 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2ed5d97bb5d..4c5e521e779 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -496,7 +496,7 @@ void Search::Worker::clear() { counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(0); - pawnHistory.fill(-1100); + pawnHistory.fill(-1300); correctionHistory.fill(0); for (bool inCheck : {false, true}) @@ -1827,7 +1827,7 @@ void update_quiet_histories( update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus / 2; } // Updates move sorting heuristics From f27a9be29c74b1d12babeb8a06ee992a22d67c9a Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 18 May 2024 03:19:36 -0700 Subject: [PATCH 0537/1309] Reduce When TTValue is Above Alpha Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 53376 W: 13818 L: 13476 D: 26082 Ptnml(0-2): 156, 6212, 13626, 6522, 172 https://tests.stockfishchess.org/tests/view/664aa261830eb9f8866145e5 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 393444 W: 100096 L: 99042 D: 194306 Ptnml(0-2): 191, 43516, 108248, 44582, 185 https://tests.stockfishchess.org/tests/view/664ab54f830eb9f88661463c closes https://github.com/official-stockfish/Stockfish/pull/5276 Bench: 1024562 --- src/search.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4c5e521e779..2817247d22e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -42,6 +42,7 @@ #include "thread.h" #include "timeman.h" #include "tt.h" +#include "types.h" #include "uci.h" #include "ucioption.h" @@ -833,9 +834,12 @@ Value Search::Worker::search( if (PvNode && !ttMove) depth -= 3; + if (!PvNode && ss->ttHit && (tte->bound() & BOUND_UPPER) && ttValue > alpha + 5 * depth) + depth--; + // Use qsearch if depth <= 0. if (depth <= 0) - return qsearch(pos, ss, alpha, beta); + return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough. if (cutNode && depth >= 8 && (!ttMove || tte->bound() == BOUND_UPPER)) From 87bad0c38a2b6a654850e61127dc0667a49acf82 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 21 May 2024 02:19:54 +0300 Subject: [PATCH 0538/1309] Refine Evaluation Scaling with Piece-Specific Weights Refine Evaluation Scaling with Piece-Specific Weights, instead of the simplified npm method. I took the initial idea from Viren6 , as he worked on it in September of last year. I worked on it, and tuned it, and now it passed both tests. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 95712 W: 24731 L: 24325 D: 46656 Ptnml(0-2): 363, 11152, 24357, 11684, 300 https://tests.stockfishchess.org/tests/view/664b5493830eb9f886614af3 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 204480 W: 52167 L: 51501 D: 100812 Ptnml(0-2): 114, 22579, 56166, 23289, 92 https://tests.stockfishchess.org/tests/view/664b75dd830eb9f886614b44 closes https://github.com/official-stockfish/Stockfish/pull/5277 Bench: 1384337 --- src/evaluate.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 3a24657f9fd..44e69b3fddd 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -76,8 +76,13 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; nnue -= nnue * (nnueComplexity * 5 / 3) / 32395; - int npm = pos.non_pawn_material() / 64; - v = (nnue * (npm + 943 + 11 * pos.count()) + optimism * (npm + 140)) / 1058; + v = (nnue + * (32961 + 381 * pos.count() + 349 * pos.count() + + 392 * pos.count() + 649 * pos.count() + 1211 * pos.count()) + + optimism + * (4835 + 136 * pos.count() + 375 * pos.count() + + 403 * pos.count() + 628 * pos.count() + 1124 * pos.count())) + / 32768; // Damp down the evaluation linearly when shuffling v = v * (204 - pos.rule50_count()) / 208; From c86ec8ec2916924065138770e0201c2cfe6d3e72 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Tue, 21 May 2024 12:42:34 +0900 Subject: [PATCH 0539/1309] Remove cutoffCnt margin adjustment in razoring Passed non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 65344 W: 16767 L: 16578 D: 31999 Ptnml(0-2): 198, 7557, 16987, 7718, 212 https://tests.stockfishchess.org/tests/view/664bd895830eb9f886615a26 Passed non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 35214 W: 8999 L: 8791 D: 17424 Ptnml(0-2): 16, 3804, 9760, 4010, 17 https://tests.stockfishchess.org/tests/view/664bead5830eb9f886615a52 closes https://github.com/official-stockfish/Stockfish/pull/5278 Bench: 1296223 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2817247d22e..a152b931ed7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -762,8 +762,7 @@ Value Search::Worker::search( // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 474 - (326 - 139 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 474 - 324 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) From c14b69790a62aad89fcc471cde482923dfe57f1e Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 21 May 2024 13:55:20 -0400 Subject: [PATCH 0540/1309] Lower smallnet threshold with updated eval divisors Params found after 30k spsa games at 60+0.6, with initial values from 64k spsa games at 45+0.45 First spsa with 64k / 120k games at 45+0.45: https://tests.stockfishchess.org/tests/view/664a561b5fc7b70b8817c663 https://tests.stockfishchess.org/tests/view/664ae88e830eb9f8866146f9 Second spsa with 30k / 120k games at 60+0.6: https://tests.stockfishchess.org/tests/view/664be227830eb9f886615a36 Values found at 10k games at 60+0.6 also passed STC and LTC: https://tests.stockfishchess.org/tests/view/664bf4bd830eb9f886615a72 https://tests.stockfishchess.org/tests/view/664c0905830eb9f886615abf Passed STC: https://tests.stockfishchess.org/tests/view/664c139e830eb9f886615af2 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 69408 W: 18216 L: 17842 D: 33350 Ptnml(0-2): 257, 8275, 17401, 8379, 392 Passed LTC: https://tests.stockfishchess.org/tests/view/664cdaf7830eb9f886616a24 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 35466 W: 9075 L: 8758 D: 17633 Ptnml(0-2): 27, 3783, 9794, 4104, 25 closes https://github.com/official-stockfish/Stockfish/pull/5280 bench 1301287 --- src/evaluate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 44e69b3fddd..ca09aaf9e82 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -46,7 +46,7 @@ int Eval::simple_eval(const Position& pos, Color c) { bool Eval::use_smallnet(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 1126 + 6 * pos.count(); + return std::abs(simpleEval) > 1018 + 5 * pos.count(); } // Evaluate is the evaluator for the outer world. It returns a static evaluation @@ -73,8 +73,8 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, } // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; - nnue -= nnue * (nnueComplexity * 5 / 3) / 32395; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 620; + nnue -= nnue * (nnueComplexity * 5 / 3) / 32082; v = (nnue * (32961 + 381 * pos.count() + 349 * pos.count() @@ -82,7 +82,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, + optimism * (4835 + 136 * pos.count() + 375 * pos.count() + 403 * pos.count() + 628 * pos.count() + 1124 * pos.count())) - / 32768; + / 36860; // Damp down the evaluation linearly when shuffling v = v * (204 - pos.rule50_count()) / 208; From ed79745bb9e7207b604c62758ea45dd5c597ed8d Mon Sep 17 00:00:00 2001 From: Dubslow Date: Tue, 4 Apr 2023 22:55:52 -0500 Subject: [PATCH 0541/1309] Improve comments about DEPTH constants Also "fix" movepicker to allow depths between CHECKS and NO_CHECKS, which makes them easier to tweak (not that they get tweaked hardly ever) (This was more beneficial when there was a third stage to DEPTH_QS, but it's still an improvement now) closes https://github.com/official-stockfish/Stockfish/pull/5205 No functional change --- src/movepick.cpp | 4 ++-- src/search.cpp | 31 ++++++++++++++++++------------- src/tt.cpp | 12 ++++++++---- src/tt.h | 5 ++++- src/types.h | 17 +++++++++++------ 5 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 4a93662db43..7def0ce84fa 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -361,8 +361,8 @@ Move MovePicker::next_move(bool skipQuiets) { if (select([]() { return true; })) return *(cur - 1); - // If we did not find any move and we do not try checks, we have finished - if (depth != DEPTH_QS_CHECKS) + // If we found no move and the depth is too low to try checks, then we have finished + if (depth <= DEPTH_QS_NORMAL) return Move::none(); ++stage; diff --git a/src/search.cpp b/src/search.cpp index a152b931ed7..87cfdbc2b71 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -733,7 +733,7 @@ Value Search::Worker::search( ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, Move::none(), + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, tt.generation()); } @@ -1387,8 +1387,11 @@ Value Search::Worker::search( } -// Quiescence search function, which is called by the main search -// function with zero depth, or recursively with further decreasing depth per call. +// Quiescence search function, which is called by the main search function with zero depth, or +// recursively with further decreasing depth per call. 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 only. +// See https://www.chessprogramming.org/Horizon_Effect and https://www.chessprogramming.org/Quiescence_Search // (~155 Elo) template Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { @@ -1446,8 +1449,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(0 <= ss->ply && ss->ply < MAX_PLY); - // Decide the replacement and cutoff priority of the qsearch TT entries - ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; + // Note that unlike regular search, which stores literal depth, in QS we only store the + // current movegen stage. If in check, we search all evasions and thus store + // DEPTH_QS_CHECKS. (Evasions may be quiet, and _CHECKS includes quiets.) + ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NORMAL; // Step 3. Transposition table lookup posKey = pos.key(); @@ -1499,8 +1504,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && !PvNode) bestValue = (3 * bestValue + beta) / 4; if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, - Move::none(), unadjustedStaticEval, tt.generation()); + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, + DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, tt.generation()); return bestValue; } @@ -1514,16 +1519,16 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, (ss - 2)->continuationHistory}; - // Initialize a MovePicker object for the current position, and prepare - // to search the moves. Because the depth is <= 0 here, only captures, - // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) - // will be generated. + // Initialize a MovePicker object for the current position, and prepare to search the moves. + // We presently use two stages of qs movegen, first captures+checks, then captures only. + // (When in check, we simply search all evasions.) + // (Presently, having the checks stage is worth only 1 Elo, and may be removable in the near future, + // which would result in only a single stage of QS movegen.) Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); - // Step 5. Loop through all pseudo-legal moves until no moves remain - // or a beta cutoff occurs. + // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs. while ((move = mp.next_move()) != Move::none()) { assert(move.is_ok()); diff --git a/src/tt.cpp b/src/tt.cpp index cb46fc8a9a6..3f5b9d4d9b0 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -30,6 +30,10 @@ namespace Stockfish { +// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but +// 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits": +// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted below.) + // Populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. void TTEntry::save( @@ -40,14 +44,14 @@ void TTEntry::save( move16 = m; // Overwrite less valuable entries (cheapest checks first) - if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4 + if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 - 4 || relative_age(generation8)) { - assert(d > DEPTH_OFFSET); - assert(d < 256 + DEPTH_OFFSET); + assert(d > DEPTH_ENTRY_OFFSET); + assert(d < 256 + DEPTH_ENTRY_OFFSET); key16 = uint16_t(k); - depth8 = uint8_t(d - DEPTH_OFFSET); + depth8 = uint8_t(d - DEPTH_ENTRY_OFFSET); genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b); value16 = int16_t(v); eval16 = int16_t(ev); diff --git a/src/tt.h b/src/tt.h index 554a81a572f..7cc876fb9ea 100644 --- a/src/tt.h +++ b/src/tt.h @@ -37,12 +37,15 @@ namespace Stockfish { // move 16 bit // value 16 bit // eval value 16 bit +// +// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially. +// Equally, the store order in save() matches this order. struct TTEntry { Move move() const { return Move(move16); } Value value() const { return Value(value16); } Value eval() const { return Value(eval16); } - Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); } + Depth depth() const { return Depth(depth8 + DEPTH_ENTRY_OFFSET); } bool is_pv() const { return bool(genBound8 & 0x4); } Bound bound() const { return Bound(genBound8 & 0x3); } void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); diff --git a/src/types.h b/src/types.h index 8b0ffb0ca0f..aa4af012b12 100644 --- a/src/types.h +++ b/src/types.h @@ -187,12 +187,17 @@ constexpr Value PieceValue[PIECE_NB] = { using Depth = int; enum : int { - DEPTH_QS_CHECKS = 0, - DEPTH_QS_NO_CHECKS = -1, - - DEPTH_NONE = -6, - - DEPTH_OFFSET = -7 // value used only for TT entry occupancy check + // The following DEPTH_ constants are used for TT entries and QS movegen stages. In regular search, + // TT depth is literal: the search depth (effort) used to make the corresponding TT value. + // In qsearch, however, TT entries only store the current QS movegen stage (which should thus compare + // lower than any regular search depth). + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NORMAL = -1, + // For TT entries where no searching at all was done (whether regular or qsearch) we use + // _UNSEARCHED, which should thus compare lower than any QS or regular depth. _ENTRY_OFFSET is used + // only for the TT entry occupancy check (see tt.cpp), and should thus be lower than _UNSEARCHED. + DEPTH_UNSEARCHED = -6, + DEPTH_ENTRY_OFFSET = -7 }; // clang-format off From 6db47ed71aac3b1667dd68a08c39bfde0fe0a2ab Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Sun, 19 May 2024 02:58:01 +0100 Subject: [PATCH 0542/1309] Addition of new scaling comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch is intended to prevent patches like 9b90cd8 and the subsequent reversion e3c9ed7 from happening again. Scaling behaviour of the reduction adjustments in the non-linear scaling section have been proven to >8 sigma: STC: https://tests.stockfishchess.org/tests/view/6647b19f6dcff0d1d6b05d52 Elo: 4.28 ± 0.8 (95%) LOS: 100.0% Total: 200000 W: 52555 L: 50094 D: 97351 Ptnml(0-2): 573, 22628, 51248, 24867, 684 nElo: 8.35 ± 1.5 (95%) PairsRatio: 1.10 VLTC: https://tests.stockfishchess.org/tests/view/6647b1b06dcff0d1d6b05d54 Elo: -1.48 ± 1.0 (95%) LOS: 0.2% Total: 100000 W: 25009 L: 25436 D: 49555 Ptnml(0-2): 11, 10716, 28971, 10293, 9 nElo: -3.23 ± 2.2 (95%) PairsRatio: 0.96 The else if condition is moved to the non scaling section based on: https://tests.stockfishchess.org/tests/view/664567a193ce6da3e93b3232 (It has no proven scaling) General comment improvements and removal of a redundant margin condition have also been included. closes https://github.com/official-stockfish/Stockfish/pull/5266 No functional change --- src/search.cpp | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 87cfdbc2b71..0814181864c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -840,7 +840,8 @@ Value Search::Worker::search( if (depth <= 0) return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); - // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough. + // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, or + // by 1 if there is a ttMove with an upper bound. if (cutNode && depth >= 8 && (!ttMove || tte->bound() == BOUND_UPPER)) depth -= 1 + !ttMove; @@ -1042,11 +1043,14 @@ Value Search::Worker::search( // 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. - // Recursive singular search is avoided. + // Generally, higher singularBeta (i.e closer to ttValue) and lower extension + // margins scale well. + if (!rootNode && move == ttMove && !excludedMove && depth >= 4 - (thisThread->completedDepth > 35) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) @@ -1063,9 +1067,8 @@ Value Search::Worker::search( if (value < singularBeta) { int doubleMargin = 298 * PvNode - 209 * !ttCapture; - int tripleMargin = - 117 + 252 * PvNode - 270 * !ttCapture + 111 * (ss->ttPv || !ttCapture); - int quadMargin = 471 + 343 * PvNode - 281 * !ttCapture + 217 * ss->ttPv; + int tripleMargin = 117 + 252 * PvNode - 270 * !ttCapture + 111 * ss->ttPv; + int quadMargin = 471 + 343 * PvNode - 281 * !ttCapture + 217 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin) @@ -1127,25 +1130,30 @@ Value Search::Worker::search( thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); + // 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 if position is or has been on the PV (~7 Elo) if (ss->ttPv) r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); - else if (cutNode && move != ttMove && move != ss->killers[0]) - r++; + // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) + if (PvNode) + r--; + + // These reduction adjustments have no proven non-linear scaling. // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2 - (tte->depth() >= depth && ss->ttPv); + r += 2 - (tte->depth() >= depth && ss->ttPv) + + (!ss->ttPv && move != ttMove && move != ss->killers[0]); // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) r++; - // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) - if (PvNode) - r--; - // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) r++; From 1dcffa621065f58982feb462671d79404e51e088 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 21 May 2024 22:50:44 -0400 Subject: [PATCH 0543/1309] Comment about re-evaluating positions While the smallNet bool is no longer used as of now, setting it to false upon re-evaluation represents the correct eval state. closes https://github.com/official-stockfish/Stockfish/pull/5279 No functional change --- src/evaluate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index ca09aaf9e82..4c449774838 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -66,6 +66,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); + // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && nnue * simpleEval < 0) { nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); From c39b98b9e356f6d01d323c6e6d5badd50e31c980 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 21 May 2024 11:54:53 -0700 Subject: [PATCH 0544/1309] Simplify Away History Updates in Multicut Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 44896 W: 11600 L: 11388 D: 21908 Ptnml(0-2): 140, 5230, 11532, 5370, 176 https://tests.stockfishchess.org/tests/view/664cee31830eb9f886616a80 Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 56832 W: 14421 L: 14234 D: 28177 Ptnml(0-2): 37, 6251, 15643, 6458, 27 https://tests.stockfishchess.org/tests/view/664cfd4e830eb9f886616aa6 closes https://github.com/official-stockfish/Stockfish/pull/5281 Bench: 1119412 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0814181864c..a98468ec644 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1083,12 +1083,7 @@ Value Search::Worker::search( // we assume this expected cut-node is not singular (multiple moves fail high), // and we can prune the whole subtree by returning a softbound. else if (singularBeta >= beta) - { - if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttMove, -stat_malus(depth)); - return singularBeta; - } // Negative extensions // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, From c6a1e7fd4232ec151206fab16cb7daa23bfd7137 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Sun, 19 May 2024 13:15:42 +0800 Subject: [PATCH 0545/1309] Optimise pairwise multiplication This speedup was first inspired by a comment by @AndyGrant on my recent PR "If mullo_epi16 would preserve the signedness, then this could be used to remove 50% of the max operations during the halfkp-pairwise mat-mul relu deal." That got me thinking, because although mullo_epi16 did not preserve the signedness, mulhi_epi16 did, and so we could shift left and then use mulhi_epi16, instead of shifting right after the mullo. However, due to some issues with shifting into the sign bit, the FT weights and biases had to be multiplied by 2 for the optimisation to work. Speedup on "Arch=x86-64-bmi2 COMP=clang", courtesy of @Torom Result of 50 runs base (...es/stockfish) = 962946 +/- 1202 test (...ise-max-less) = 979696 +/- 1084 diff = +16750 +/- 1794 speedup = +0.0174 P(speedup > 0) = 1.0000 CPU: 4 x Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz Hyperthreading: on Also a speedup on "COMP=gcc", courtesy of Torom once again Result of 50 runs base (...tockfish_gcc) = 966033 +/- 1574 test (...max-less_gcc) = 983319 +/- 1513 diff = +17286 +/- 2515 speedup = +0.0179 P(speedup > 0) = 1.0000 CPU: 4 x Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz Hyperthreading: on Passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 67712 W: 17715 L: 17358 D: 32639 Ptnml(0-2): 225, 7472, 18140, 7759, 260 https://tests.stockfishchess.org/tests/view/664c1d75830eb9f886616906 closes https://github.com/official-stockfish/Stockfish/pull/5282 No functional change --- src/nnue/nnue_feature_transformer.h | 80 +++++++++++++++++++---------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7b7aada312c..483b84a8704 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -55,14 +55,14 @@ using psqt_vec_t = __m256i; #define vec_store(a, b) _mm512_store_si512(a, b) #define vec_add_16(a, b) _mm512_add_epi16(a, b) #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) - #define vec_mul_16(a, b) _mm512_mullo_epi16(a, b) + #define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b) #define vec_zero() _mm512_setzero_epi32() #define vec_set_16(a) _mm512_set1_epi16(a) #define vec_max_16(a, b) _mm512_max_epi16(a, b) #define vec_min_16(a, b) _mm512_min_epi16(a, b) + #define vec_slli_16(a, b) _mm512_slli_epi16(a, b) // Inverse permuted at load time - #define vec_msb_pack_16(a, b) \ - _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7)) + #define vec_packus_16(a, b) _mm512_packus_epi16(a, b) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a, b) _mm256_store_si256(a, b) #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) @@ -78,14 +78,14 @@ using psqt_vec_t = __m256i; #define vec_store(a, b) _mm256_store_si256(a, b) #define vec_add_16(a, b) _mm256_add_epi16(a, b) #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) - #define vec_mul_16(a, b) _mm256_mullo_epi16(a, b) + #define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b) #define vec_zero() _mm256_setzero_si256() #define vec_set_16(a) _mm256_set1_epi16(a) #define vec_max_16(a, b) _mm256_max_epi16(a, b) #define vec_min_16(a, b) _mm256_min_epi16(a, b) + #define vec_slli_16(a, b) _mm256_slli_epi16(a, b) // Inverse permuted at load time - #define vec_msb_pack_16(a, b) \ - _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7)) + #define vec_packus_16(a, b) _mm256_packus_epi16(a, b) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a, b) _mm256_store_si256(a, b) #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) @@ -101,12 +101,13 @@ using psqt_vec_t = __m128i; #define vec_store(a, b) *(a) = (b) #define vec_add_16(a, b) _mm_add_epi16(a, b) #define vec_sub_16(a, b) _mm_sub_epi16(a, b) - #define vec_mul_16(a, b) _mm_mullo_epi16(a, b) + #define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b) #define vec_zero() _mm_setzero_si128() #define vec_set_16(a) _mm_set1_epi16(a) #define vec_max_16(a, b) _mm_max_epi16(a, b) #define vec_min_16(a, b) _mm_min_epi16(a, b) - #define vec_msb_pack_16(a, b) _mm_packs_epi16(_mm_srli_epi16(a, 7), _mm_srli_epi16(b, 7)) + #define vec_slli_16(a, b) _mm_slli_epi16(a, b) + #define vec_packus_16(a, b) _mm_packus_epi16(a, b) #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a, b) *(a) = (b) #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) @@ -122,18 +123,14 @@ using psqt_vec_t = int32x4_t; #define vec_store(a, b) *(a) = (b) #define vec_add_16(a, b) vaddq_s16(a, b) #define vec_sub_16(a, b) vsubq_s16(a, b) - #define vec_mul_16(a, b) vmulq_s16(a, b) + #define vec_mulhi_16(a, b) vqdmulhq_s16(a, b) #define vec_zero() \ vec_t { 0 } #define vec_set_16(a) vdupq_n_s16(a) #define vec_max_16(a, b) vmaxq_s16(a, b) #define vec_min_16(a, b) vminq_s16(a, b) -inline vec_t vec_msb_pack_16(vec_t a, vec_t b) { - const int8x8_t shifta = vshrn_n_s16(a, 7); - const int8x8_t shiftb = vshrn_n_s16(b, 7); - const int8x16_t compacted = vcombine_s8(shifta, shiftb); - return *reinterpret_cast(&compacted); -} + #define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b)) + #define vec_packus_16(a, b) reinterpret_cast(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b))) #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a, b) *(a) = (b) #define vec_add_psqt_32(a, b) vaddq_s32(a, b) @@ -281,6 +278,19 @@ class FeatureTransformer { #endif } + inline void scale_weights(bool read) const { + for (IndexType j = 0; j < InputDimensions; ++j) + { + WeightType* w = const_cast(&weights[j * HalfDimensions]); + for (IndexType i = 0; i < HalfDimensions; ++i) + w[i] = read ? w[i] * 2 : w[i] / 2; + } + + BiasType* b = const_cast(biases); + for (IndexType i = 0; i < HalfDimensions; ++i) + b[i] = read ? b[i] * 2 : b[i] / 2; + } + // Read network parameters bool read_parameters(std::istream& stream) { @@ -289,6 +299,7 @@ class FeatureTransformer { read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); permute_weights(inverse_order_packs); + scale_weights(true); return !stream.fail(); } @@ -296,12 +307,14 @@ class FeatureTransformer { bool write_parameters(std::ostream& stream) const { permute_weights(order_packs); + 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); + scale_weights(true); return !stream.fail(); } @@ -332,7 +345,7 @@ class FeatureTransformer { constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; const vec_t Zero = vec_zero(); - const vec_t One = vec_set_16(127); + const vec_t One = vec_set_16(127 * 2); const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); const vec_t* in1 = @@ -341,15 +354,30 @@ class FeatureTransformer { for (IndexType j = 0; j < NumOutputChunks; ++j) { - const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero); - const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero); - const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero); - const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero); + // What we want to do is multiply inputs in a pairwise manner (after clipping), and then shift right by 9. + // Instead, we shift left by 7, and use mulhi, stripping the bottom 16 bits, effectively shifting right by 16, + // resulting in a net shift of 9 bits. We use mulhi because it maintains the sign of the multiplication (unlike mullo), + // allowing us to make use of packus to clip 2 of the inputs, resulting in a save of 2 "vec_max_16" calls. + // A special case is when we use NEON, where we shift left by 6 instead, because the instruction "vqdmulhq_s16" + // also doubles the return value after the multiplication, adding an extra shift to the left by 1, so we + // compensate by shifting less before the multiplication. + + #if defined(USE_SSE2) + constexpr int shift = 7; + #else + constexpr int shift = 6; + #endif + const vec_t sum0a = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift); + const vec_t sum0b = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift); + const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One); + const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One); - const vec_t pa = vec_mul_16(sum0a, sum1a); - const vec_t pb = vec_mul_16(sum0b, sum1b); + const vec_t pa = vec_mulhi_16(sum0a, sum1a); + const vec_t pb = vec_mulhi_16(sum0b, sum1b); - out[j] = vec_msb_pack_16(pa, pb); + out[j] = vec_packus_16(pa, pb); } #else @@ -359,9 +387,9 @@ class FeatureTransformer { BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - sum0 = std::clamp(sum0, 0, 127); - sum1 = std::clamp(sum1, 0, 127); - output[offset + j] = static_cast(unsigned(sum0 * sum1) / 128); + sum0 = std::clamp(sum0, 0, 127 * 2); + sum1 = std::clamp(sum1, 0, 127 * 2); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 512); } #endif From 72a345873d9cf24542dc73cd5a28eba7d23b0d2b Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 22 May 2024 09:09:04 +0800 Subject: [PATCH 0546/1309] Revert "Reduce When TTValue is Above Alpha" The patch regressed significantly at longer time controls. In particular, the `depth--` behavior was predicted to scale badly based on data from other variations of the patch. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/664d45cf830eb9f886616c7d LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 51292 W: 13242 L: 12954 D: 25096 Ptnml(0-2): 5, 4724, 15896, 5020, 1 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/664e641a928b1fb18de4e385 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 41884 W: 10933 L: 10634 D: 20317 Ptnml(0-2): 1, 3759, 13125, 4054, 3 closes https://github.com/official-stockfish/Stockfish/pull/5283 Bench: 1503815 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a98468ec644..477667306ad 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -833,12 +833,9 @@ Value Search::Worker::search( if (PvNode && !ttMove) depth -= 3; - if (!PvNode && ss->ttHit && (tte->bound() & BOUND_UPPER) && ttValue > alpha + 5 * depth) - depth--; - // Use qsearch if depth <= 0. if (depth <= 0) - return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); + return qsearch(pos, ss, alpha, beta); // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, or // by 1 if there is a ttMove with an upper bound. From 365aa85dcea3adee21b5e01a7941b4b18fdc8194 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 21 May 2024 16:24:49 -0400 Subject: [PATCH 0547/1309] Remove material imbalance param when adjusting optimism Passed non-regression STC: https://tests.stockfishchess.org/tests/view/664d033d830eb9f886616aff LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 102144 W: 26283 L: 26135 D: 49726 Ptnml(0-2): 292, 12201, 25991, 12243, 345 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/664d5c00830eb9f886616cb3 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 250032 W: 63022 L: 63036 D: 123974 Ptnml(0-2): 103, 27941, 68970, 27871, 131 closes https://github.com/official-stockfish/Stockfish/pull/5284 Bench: 1330940 --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4c449774838..7ca470af5ea 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -73,8 +73,8 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, smallNet = false; } - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 620; + // Blend optimism and eval with nnue complexity + optimism += optimism * nnueComplexity / 512; nnue -= nnue * (nnueComplexity * 5 / 3) / 32082; v = (nnue From 61acbfc7d310ed6044ba4fc5ef91a6c382d1c9a6 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Thu, 23 May 2024 08:28:46 +0800 Subject: [PATCH 0548/1309] VVLTC search tune Parameters were tuned in 2 stages: 1. 127k games at VVLTC: https://tests.stockfishchess.org/tests/view/6649f8dfb8fa20e74c39f52a. 2. 106k games at VVLTC: https://tests.stockfishchess.org/tests/view/664bfb77830eb9f886615a9d. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/664e8dd9928b1fb18de4e410 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 20466 W: 5340 L: 5093 D: 10033 Ptnml(0-2): 0, 1796, 6397, 2037, 3 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/664eb4aa928b1fb18de4e47d LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 15854 W: 4186 L: 3934 D: 7734 Ptnml(0-2): 1, 1367, 4938, 1621, 0 closes https://github.com/official-stockfish/Stockfish/pull/5286 Bench: 1558110 --- src/search.cpp | 88 +++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 477667306ad..563a5710f89 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -60,9 +60,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 127 - 48 * noTtCutNode; - Value improvingDeduction = 65 * improving * futilityMult / 32; - Value worseningDeduction = 334 * oppWorsening * futilityMult / 1024; + Value futilityMult = 129 - 43 * noTtCutNode; + Value improvingDeduction = 56 * improving * futilityMult / 32; + Value worseningDeduction = 336 * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -74,15 +74,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 6047; + v += cv * std::abs(cv) / 5435; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(187 * d - 288, 17, 1548); } +int stat_bonus(Depth d) { return std::clamp(205 * d - 283, 18, 1544); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 630 * d - 281 : 1741); } +int stat_malus(Depth d) { return (d < 4 ? 767 * d - 275 : 1911); } // 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); } @@ -312,12 +312,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 9828; + delta = 9 + avg * avg / 10502; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 116 * avg / (std::abs(avg) + 84); + optimism[us] = 122 * avg / (std::abs(avg) + 92); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -507,7 +507,7 @@ void Search::Worker::clear() { h->fill(-60); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((21.69 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((19.90 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks); } @@ -740,7 +740,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-11 * int((ss - 1)->staticEval + ss->staticEval), -1729, 1517); + int bonus = std::clamp(-11 * int((ss - 1)->staticEval + ss->staticEval), -1592, 1390); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -762,7 +762,7 @@ Value Search::Worker::search( // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 474 - 324 * depth * depth) + if (eval < alpha - 501 - 305 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -771,23 +771,23 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 11 + if (!ss->ttPv && depth < 12 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 252 + - (ss - 1)->statScore / 248 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 15246 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 366 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 13999 + && eval >= beta && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 177, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -845,7 +845,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 176 - 65 * improving; + probCutBeta = beta + 185 - 60 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -901,7 +901,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 440; + probCutBeta = beta + 361; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -985,15 +985,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 276 + 256 * lmrDepth + Value futilityValue = ss->staticEval + 283 + 235 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -177 * depth, 175 * depth); - if (!pos.see_ge(move, -183 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -183 * depth, 162 * depth); + if (!pos.see_ge(move, -166 * depth - seeHist)) continue; } else @@ -1004,18 +1004,18 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4076 * depth) + if (lmrDepth < 6 && history < -4427 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 4401; + lmrDepth += history / 3670; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 53 ? 151 : 57) + 140 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 51 ? 149 : 55) + 141 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 10 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1049,11 +1049,11 @@ Value Search::Worker::search( // margins scale well. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 35) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 38) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (57 + 50 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (58 + 64 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1063,15 +1063,15 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 298 * PvNode - 209 * !ttCapture; - int tripleMargin = 117 + 252 * PvNode - 270 * !ttCapture + 111 * ss->ttPv; - int quadMargin = 471 + 343 * PvNode - 281 * !ttCapture + 217 * ss->ttPv; + int doubleMargin = 304 * PvNode - 203 * !ttCapture; + int tripleMargin = 117 + 259 * PvNode - 296 * !ttCapture + 97 * ss->ttPv; + int quadMargin = 486 + 343 * PvNode - 273 * !ttCapture + 232 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin) + (value < singularBeta - quadMargin); - depth += ((!PvNode) && (depth < 15)); + depth += ((!PvNode) && (depth < 16)); } // Multi-cut pruning @@ -1101,7 +1101,7 @@ Value Search::Worker::search( else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 3748) + > 3988) extension = 1; } @@ -1157,10 +1157,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 5266; + + (*contHist[1])[movedPiece][move.to_sq()] - 5169; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / (14519 - std::min(depth, 15) * 103); + r -= ss->statScore / (12219 - std::min(depth, 13) * 120); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1179,7 +1179,7 @@ Value Search::Worker::search( { // 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 doDeeperSearch = value > (bestValue + 36 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1340,9 +1340,9 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 4) + (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -13241) - + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 127) - + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 74); + int bonus = (depth > 4) + (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14144) + + ((ss - 1)->moveCount > 9) + (!ss->inCheck && bestValue <= ss->staticEval - 115) + + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 81); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1513,7 +1513,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 264; + futilityBase = ss->staticEval + 279; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1585,11 +1585,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()] - <= 4348) + <= 4181) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -63)) + if (!pos.see_ge(move, -67)) continue; } @@ -1655,7 +1655,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) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1147 - delta * 755 / rootDelta) / 1024 + (!i && reductionScale > 1125); + return (reductionScale + 1222 - delta * 733 / rootDelta) / 1024 + (!i && reductionScale > 1231); } // elapsed() returns the time elapsed since the search started. If the @@ -1758,7 +1758,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 165 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 176 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); @@ -1796,7 +1796,7 @@ void update_all_stats(const Position& pos, // by moves at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - bonus = bonus * 45 / 64; + bonus = bonus * 47 / 64; for (int i : {1, 2, 3, 4, 6}) { From 4d876275cf127b9e7cf91cef984deafa2abb47d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Thu, 23 May 2024 22:03:43 +0200 Subject: [PATCH 0549/1309] Simplify material weights in evaluation This patch uses the same material weights for the nnue amplification term and the optimism term in evaluate(). STC: LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 83360 W: 21489 L: 21313 D: 40558 Ptnml(0-2): 303, 9934, 21056, 10058, 329 https://tests.stockfishchess.org/tests/view/664eee69928b1fb18de500d9 LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 192648 W: 48675 L: 48630 D: 95343 Ptnml(0-2): 82, 21484, 53161, 21501, 96 https://tests.stockfishchess.org/tests/view/664fa17aa86388d5e27d7d6e closes https://github.com/official-stockfish/Stockfish/pull/5287 Bench: 1495602 --- src/evaluate.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7ca470af5ea..75fe0f924a2 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -77,13 +77,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, optimism += optimism * nnueComplexity / 512; nnue -= nnue * (nnueComplexity * 5 / 3) / 32082; - v = (nnue - * (32961 + 381 * pos.count() + 349 * pos.count() - + 392 * pos.count() + 649 * pos.count() + 1211 * pos.count()) - + optimism - * (4835 + 136 * pos.count() + 375 * pos.count() - + 403 * pos.count() + 628 * pos.count() + 1124 * pos.count())) - / 36860; + int material = 200 * pos.count() + 350 * pos.count() + 400 * pos.count() + + 640 * pos.count() + 1200 * pos.count(); + + v = (nnue * (34000 + material) + optimism * (4400 + material)) / 36860; // Damp down the evaluation linearly when shuffling v = v * (204 - pos.rule50_count()) / 208; From 8bc3fd3871aaa2437105bdc141d5ac25a88ea885 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 24 May 2024 10:58:13 -0400 Subject: [PATCH 0550/1309] Lower smallnet threshold with tuned eval params The smallnet threshold is now below the training data range of the current smallnet (simple eval diff > 1k, nn-baff1edelf90.nnue) when no pawns are on the board. Params found with spsa at 93k / 120k games at 60+06: https://tests.stockfishchess.org/tests/view/664fa166a86388d5e27d7d6b Tuned on top of: https://github.com/official-stockfish/Stockfish/pull/5287 Passed STC: https://tests.stockfishchess.org/tests/view/664fc8b7a86388d5e27d8dac LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 64672 W: 16731 L: 16371 D: 31570 Ptnml(0-2): 239, 7463, 16517, 7933, 184 Passed LTC: https://tests.stockfishchess.org/tests/view/664fd5f9a86388d5e27d8dfe LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 210648 W: 53489 L: 52813 D: 104346 Ptnml(0-2): 102, 23129, 58164, 23849, 80 closes https://github.com/official-stockfish/Stockfish/pull/5288 Bench: 1717838 --- src/evaluate.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 75fe0f924a2..13a3f211741 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -46,7 +46,7 @@ int Eval::simple_eval(const Position& pos, Color c) { bool Eval::use_smallnet(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 1018 + 5 * pos.count(); + return std::abs(simpleEval) > 992 + 6 * pos.count(); } // Evaluate is the evaluator for the outer world. It returns a static evaluation @@ -74,13 +74,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, } // Blend optimism and eval with nnue complexity - optimism += optimism * nnueComplexity / 512; - nnue -= nnue * (nnueComplexity * 5 / 3) / 32082; + optimism += optimism * nnueComplexity / 470; + nnue -= nnue * (nnueComplexity * 5 / 3) / 32621; int material = 200 * pos.count() + 350 * pos.count() + 400 * pos.count() + 640 * pos.count() + 1200 * pos.count(); - v = (nnue * (34000 + material) + optimism * (4400 + material)) / 36860; + v = (nnue * (34000 + material + 135 * pos.count()) + + optimism * (4400 + material + 99 * pos.count())) + / 35967; // Damp down the evaluation linearly when shuffling v = v * (204 - pos.rule50_count()) / 208; From 8e1f273c7d10e2b49c07cdc16b09a3d4574acf4c Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" <41402573+peregrineshahin@users.noreply.github.com> Date: Fri, 24 May 2024 01:19:16 +0300 Subject: [PATCH 0551/1309] Remove rootDelta branch This makes rootDelta logic easier to understand, recalculating the value where it belongs so removes an unnecessary branch. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/664fc147a86388d5e27d8d8e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 206016 W: 53120 L: 53089 D: 99807 Ptnml(0-2): 591, 20928, 59888, 21061, 540 closes https://github.com/official-stockfish/Stockfish/pull/5289 No functional change --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 563a5710f89..ed264f55c42 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -330,6 +330,7 @@ void Search::Worker::iterative_deepening() { // for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); + rootDelta = beta - alpha; bestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting @@ -590,8 +591,6 @@ Value Search::Worker::search( if (alpha >= beta) return alpha; } - else - thisThread->rootDelta = beta - alpha; assert(0 <= ss->ply && ss->ply < MAX_PLY); From 5e98a4e43dd1c2698162bc3f848a0a98943f86c6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 24 May 2024 22:46:03 -0700 Subject: [PATCH 0552/1309] Simplify Away TT Cutoff Return Value Adjustments Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 198432 W: 51161 L: 51119 D: 96152 Ptnml(0-2): 772, 23670, 50273, 23746, 755 https://tests.stockfishchess.org/tests/view/66517b9ea86388d5e27da966 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 234150 W: 59200 L: 59197 D: 115753 Ptnml(0-2): 126, 26200, 64404, 26235, 110 https://tests.stockfishchess.org/tests/view/6653a84da86388d5e27daa63 closes https://github.com/official-stockfish/Stockfish/pull/5292 bench 1555200 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ed264f55c42..d253601dd61 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -637,9 +637,7 @@ Value Search::Worker::search( // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) - return ttValue >= beta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY - ? (ttValue * 3 + beta) / 4 - : ttValue; + return ttValue; } // Step 5. Tablebases probe From d0b9411b8275369074bb0de041257db2bccc6430 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 28 May 2024 13:49:30 +0300 Subject: [PATCH 0553/1309] Tweak return value in futility pruning Tweak the return value formula in futility pruning. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 60544 W: 15791 L: 15440 D: 29313 Ptnml(0-2): 193, 7024, 15520, 7309, 226 https://tests.stockfishchess.org/tests/view/6654ef22a86388d5e27db122 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 126426 W: 32317 L: 31812 D: 62297 Ptnml(0-2): 55, 13871, 34869, 14350, 68 https://tests.stockfishchess.org/tests/view/66550644a86388d5e27db649 closes https://github.com/official-stockfish/Stockfish/pull/5295 bench: 1856147 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d253601dd61..0dbc6a3a5db 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -773,7 +773,7 @@ Value Search::Worker::search( - (ss - 1)->statScore / 248 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) - return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; + return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 13999 From b0287dcb1c436887075962b596cf2068d2ca9ba8 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 28 May 2024 18:00:22 +0200 Subject: [PATCH 0554/1309] apply const to prefetch parameter closes https://github.com/official-stockfish/Stockfish/pull/5296 No functional change --- src/misc.cpp | 6 +++--- src/misc.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 1abb81b14c2..58f804204b2 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -415,14 +415,14 @@ void start_logger(const std::string& fname) { Logger::start(fname); } #ifdef NO_PREFETCH -void prefetch(void*) {} +void prefetch(const void*) {} #else -void prefetch(void* addr) { +void prefetch(const void* addr) { #if defined(_MSC_VER) - _mm_prefetch((char*) addr, _MM_HINT_T0); + _mm_prefetch((char const*) addr, _MM_HINT_T0); #else __builtin_prefetch(addr); #endif diff --git a/src/misc.h b/src/misc.h index d75b236ff71..3a905dfab49 100644 --- a/src/misc.h +++ b/src/misc.h @@ -40,7 +40,7 @@ std::string compiler_info(); // Preloads the given address in L1/L2 cache. This is a non-blocking // function that doesn't stall the CPU waiting for data to be loaded from memory, // which can be quite slow. -void prefetch(void* addr); +void prefetch(const void* addr); void start_logger(const std::string& fname); void* std_aligned_alloc(size_t alignment, size_t size); From a169c78b6d3b082068deb49a39aaa1fd75464c7f Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Fri, 17 May 2024 12:10:31 +0200 Subject: [PATCH 0555/1309] Improve performance on NUMA systems Allow for NUMA memory replication for NNUE weights. Bind threads to ensure execution on a specific NUMA node. This patch introduces NUMA memory replication, currently only utilized for the NNUE weights. Along with it comes all machinery required to identify NUMA nodes and bind threads to specific processors/nodes. It also comes with small changes to Thread and ThreadPool to allow easier execution of custom functions on the designated thread. Old thread binding (WinProcGroup) machinery is removed because it's incompatible with this patch. Small changes to unrelated parts of the code were made to ensure correctness, like some classes being made unmovable, raw pointers replaced with unique_ptr. etc. Windows 7 and Windows 10 is partially supported. Windows 11 is fully supported. Linux is fully supported, with explicit exclusion of Android. No additional dependencies. ----------------- A new UCI option `NumaPolicy` is introduced. It can take the following values: ``` system - gathers NUMA node information from the system (lscpu or windows api), for each threads binds it to a single NUMA node none - assumes there is 1 NUMA node, never binds threads auto - this is the default value, depends on the number of set threads and NUMA nodes, will only enable binding on multinode systems and when the number of threads reaches a threshold (dependent on node size and count) [[custom]] - // ':'-separated numa nodes // ','-separated cpu indices // supports "first-last" range syntax for cpu indices, for example '0-15,32-47:16-31,48-63' ``` Setting `NumaPolicy` forces recreation of the threads in the ThreadPool, which in turn forces the recreation of the TT. The threads are distributed among NUMA nodes in a round-robin fashion based on fill percentage (i.e. it will strive to fill all NUMA nodes evenly). Threads are bound to NUMA nodes, not specific processors, because that's our only requirement and the OS can schedule them better. Special care is made that maximum memory usage on systems that do not require memory replication stays as previously, that is, unnecessary copies are avoided. On linux the process' processor affinity is respected. This means that if you for example use taskset to restrict Stockfish to a single NUMA node then the `system` and `auto` settings will only see a single NUMA node (more precisely, the processors included in the current affinity mask) and act accordingly. ----------------- We can't ensure that a memory allocation takes place on a given NUMA node without using libnuma on linux, or using appropriate custom allocators on windows (https://learn.microsoft.com/en-us/windows/win32/memory/allocating-memory-from-a-numa-node), so to avoid complications the current implementation relies on first-touch policy. Due to this we also rely on the memory allocator to give us a new chunk of untouched memory from the system. This appears to work reliably on linux, but results may vary. MacOS is not supported, because AFAIK it's not affected, and implementation would be problematic anyway. Windows is supported since Windows 7 (https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-setthreadgroupaffinity). Until Windows 11/Server 2022 NUMA nodes are split such that they cannot span processor groups. This is because before Windows 11/Server 2022 it's not possible to set thread affinity spanning processor groups. The splitting is done manually in some cases (required after Windows 10 Build 20348). Since Windows 11/Server 2022 we can set affinites spanning processor group so this splitting is not done, so the behaviour is pretty much like on linux. Linux is supported, **without** libnuma requirement. `lscpu` is expected. ----------------- Passed 60+1 @ 256t 16000MB hash: https://tests.stockfishchess.org/tests/view/6654e443a86388d5e27db0d8 ``` LLR: 2.95 (-2.94,2.94) <0.00,10.00> Total: 278 W: 110 L: 29 D: 139 Ptnml(0-2): 0, 1, 56, 82, 0 ``` Passed SMP STC: https://tests.stockfishchess.org/tests/view/6654fc74a86388d5e27db1cd ``` LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67152 W: 17354 L: 17177 D: 32621 Ptnml(0-2): 64, 7428, 18408, 7619, 57 ``` Passed STC: https://tests.stockfishchess.org/tests/view/6654fb27a86388d5e27db15c ``` LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 131648 W: 34155 L: 34045 D: 63448 Ptnml(0-2): 426, 13878, 37096, 14008, 416 ``` fixes #5253 closes https://github.com/official-stockfish/Stockfish/pull/5285 No functional change --- .github/ci/libcxx17.imp | 1 + src/Makefile | 2 +- src/engine.cpp | 88 +++- src/engine.h | 31 +- src/misc.cpp | 134 +----- src/misc.h | 56 ++- src/nnue/network.cpp | 42 ++ src/nnue/network.h | 6 + src/numa.h | 904 ++++++++++++++++++++++++++++++++++++++++ src/search.cpp | 41 +- src/search.h | 37 +- src/thread.cpp | 192 ++++++--- src/thread.h | 91 +++- src/tt.cpp | 29 +- src/tt.h | 5 +- src/uci.cpp | 42 +- src/uci.h | 3 + src/ucioption.cpp | 2 + src/ucioption.h | 1 + 19 files changed, 1418 insertions(+), 289 deletions(-) create mode 100644 src/numa.h diff --git a/.github/ci/libcxx17.imp b/.github/ci/libcxx17.imp index 7bdcf5bc2de..d3a262b54e8 100644 --- a/.github/ci/libcxx17.imp +++ b/.github/ci/libcxx17.imp @@ -7,6 +7,7 @@ { include: [ "<__fwd/sstream.h>", private, "", public ] }, { include: [ "<__fwd/streambuf.h>", private, "", public ] }, { include: [ "<__fwd/string_view.h>", private, "", public ] }, + { include: [ "<__system_error/errc.h>", private, "", public ] }, # Mappings for includes between public headers { include: [ "", public, "", public ] }, diff --git a/src/Makefile b/src/Makefile index 45f38b01322..5119b615f6b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/engine.cpp b/src/engine.cpp index e8da24aa9e8..3fc27223a09 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -18,15 +18,15 @@ #include "engine.h" +#include #include +#include #include #include +#include #include #include #include -#include -#include -#include #include "evaluate.h" #include "misc.h" @@ -48,10 +48,14 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - Engine::Engine(std::string path) : binaryDirectory(CommandLine::get_binary_directory(path)), + numaContext(NumaConfig::from_system()), states(new std::deque(1)), - networks(NN::Networks( - NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), - NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { + threads(), + networks( + numaContext, + NN::Networks( + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { pos.set(StartFEN, false, &states->back()); capSq = SQ_NONE; } @@ -74,7 +78,7 @@ void Engine::stop() { threads.stop = true; } void Engine::search_clear() { wait_for_search_finished(); - tt.clear(options["Threads"]); + tt.clear(threads); threads.clear(); // @TODO wont work with multiple instances @@ -124,11 +128,35 @@ void Engine::set_position(const std::string& fen, const std::vector // modifiers -void Engine::resize_threads() { threads.set({options, threads, tt, networks}, updateContext); } +void Engine::set_numa_config_from_option(const std::string& o) { + if (o == "auto" || o == "system") + { + numaContext.set_numa_config(NumaConfig::from_system()); + } + else if (o == "none") + { + numaContext.set_numa_config(NumaConfig{}); + } + else + { + numaContext.set_numa_config(NumaConfig::from_string(o)); + } + + // Force reallocation of threads in case affinities need to change. + resize_threads(); +} + +void Engine::resize_threads() { + threads.wait_for_search_finished(); + threads.set(numaContext.get_numa_config(), {options, threads, tt, networks}, updateContext); + + // Reallocate the hash with the new threadpool size + set_tt_size(options["Hash"]); +} void Engine::set_tt_size(size_t mb) { wait_for_search_finished(); - tt.resize(mb, options["Threads"]); + tt.resize(mb, threads); } void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } @@ -136,28 +164,35 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } // network related void Engine::verify_networks() const { - networks.big.verify(options["EvalFile"]); - networks.small.verify(options["EvalFileSmall"]); + networks->big.verify(options["EvalFile"]); + networks->small.verify(options["EvalFileSmall"]); } void Engine::load_networks() { - load_big_network(options["EvalFile"]); - load_small_network(options["EvalFileSmall"]); + networks.modify_and_replicate([this](NN::Networks& networks_) { + networks_.big.load(binaryDirectory, options["EvalFile"]); + networks_.small.load(binaryDirectory, options["EvalFileSmall"]); + }); + threads.clear(); } void Engine::load_big_network(const std::string& file) { - networks.big.load(binaryDirectory, file); + networks.modify_and_replicate( + [this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); }); threads.clear(); } void Engine::load_small_network(const std::string& file) { - networks.small.load(binaryDirectory, file); + networks.modify_and_replicate( + [this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); }); threads.clear(); } void Engine::save_network(const std::pair, std::string> files[2]) { - networks.big.save(files[0].first); - networks.small.save(files[1].first); + networks.modify_and_replicate([&files](NN::Networks& networks_) { + networks_.big.save(files[0].first); + networks_.small.save(files[1].first); + }); } // utility functions @@ -169,7 +204,7 @@ void Engine::trace_eval() const { verify_networks(); - sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; + sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl; } OptionsMap& Engine::get_options() { return options; } @@ -184,4 +219,21 @@ std::string Engine::visualize() const { return ss.str(); } +std::vector> Engine::get_bound_thread_count_by_numa_node() const { + auto counts = threads.get_bound_thread_count_by_numa_node(); + const NumaConfig& cfg = numaContext.get_numa_config(); + std::vector> ratios; + NumaIndex n = 0; + for (; n < counts.size(); ++n) + ratios.emplace_back(counts[n], cfg.num_cpus_in_numa_node(n)); + if (!counts.empty()) + for (; n < cfg.num_numa_nodes(); ++n) + ratios.emplace_back(0, cfg.num_cpus_in_numa_node(n)); + return ratios; +} + +std::string Engine::get_numa_config_as_string() const { + return numaContext.get_numa_config().to_string(); +} + } diff --git a/src/engine.h b/src/engine.h index 64a814cb4aa..91a8a96b0dc 100644 --- a/src/engine.h +++ b/src/engine.h @@ -35,6 +35,7 @@ #include "thread.h" #include "tt.h" #include "ucioption.h" +#include "numa.h" namespace Stockfish { @@ -47,6 +48,13 @@ class Engine { using InfoIter = Search::InfoIteration; Engine(std::string path = ""); + + // Can't be movable due to components holding backreferences to fields + Engine(const Engine&) = delete; + Engine(Engine&&) = delete; + Engine& operator=(const Engine&) = delete; + Engine& operator=(Engine&&) = delete; + ~Engine() { wait_for_search_finished(); } std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960); @@ -63,6 +71,7 @@ class Engine { // modifiers + void set_numa_config_from_option(const std::string& o); void resize_threads(); void set_tt_size(size_t mb); void set_ponderhit(bool); @@ -83,23 +92,27 @@ class Engine { // utility functions - void trace_eval() const; - OptionsMap& get_options(); - std::string fen() const; - void flip(); - std::string visualize() const; + void trace_eval() const; + OptionsMap& get_options(); + std::string fen() const; + void flip(); + std::string visualize() const; + std::vector> get_bound_thread_count_by_numa_node() const; + std::string get_numa_config_as_string() const; private: const std::string binaryDirectory; + NumaReplicationContext numaContext; + Position pos; StateListPtr states; Square capSq; - OptionsMap options; - ThreadPool threads; - TranspositionTable tt; - Eval::NNUE::Networks networks; + OptionsMap options; + ThreadPool threads; + TranspositionTable tt; + NumaReplicated networks; Search::SearchManager::UpdateContext updateContext; }; diff --git a/src/misc.cpp b/src/misc.cpp index 58f804204b2..d48b75e1c28 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -48,6 +48,7 @@ using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGE #endif #include +#include #include #include #include @@ -56,6 +57,7 @@ using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGE #include #include #include +#include #include "types.h" @@ -592,129 +594,6 @@ void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } #endif -namespace WinProcGroup { - -#ifndef _WIN32 - -void bind_this_thread(size_t) {} - -#else - -namespace { -// Retrieves logical processor information using Windows-specific -// API and returns the best node id for the thread with index idx. Original -// code from Texel by Peter Österlund. -int best_node(size_t idx) { - - int threads = 0; - int nodes = 0; - int cores = 0; - DWORD returnLength = 0; - DWORD byteOffset = 0; - - // Early exit if the needed API is not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun1 = (fun1_t) (void (*)()) GetProcAddress(k32, "GetLogicalProcessorInformationEx"); - if (!fun1) - return -1; - - // First call to GetLogicalProcessorInformationEx() to get returnLength. - // We expect the call to fail due to null buffer. - if (fun1(RelationAll, nullptr, &returnLength)) - return -1; - - // Once we know returnLength, allocate the buffer - SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; - ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(returnLength); - - // Second call to GetLogicalProcessorInformationEx(), now we expect to succeed - if (!fun1(RelationAll, buffer, &returnLength)) - { - free(buffer); - return -1; - } - - while (byteOffset < returnLength) - { - if (ptr->Relationship == RelationNumaNode) - nodes++; - - else if (ptr->Relationship == RelationProcessorCore) - { - cores++; - threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; - } - - assert(ptr->Size); - byteOffset += ptr->Size; - ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((char*) ptr) + ptr->Size); - } - - free(buffer); - - std::vector groups; - - // Run as many threads as possible on the same node until the core limit is - // reached, then move on to filling the next node. - for (int n = 0; n < nodes; n++) - for (int i = 0; i < cores / nodes; i++) - groups.push_back(n); - - // In case a core has more than one logical processor (we assume 2) and we - // still have threads to allocate, spread them evenly across available nodes. - for (int t = 0; t < threads - cores; t++) - groups.push_back(t % nodes); - - // If we still have more threads than the total number of logical processors - // then return -1 and let the OS to decide what to do. - return idx < groups.size() ? groups[idx] : -1; -} -} - - -// Sets the group affinity of the current thread -void bind_this_thread(size_t idx) { - - // Use only local variables to be thread-safe - int node = best_node(idx); - - if (node == -1) - return; - - // Early exit if the needed API are not available at runtime - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto fun2 = fun2_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMaskEx")); - auto fun3 = fun3_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity")); - auto fun4 = fun4_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMask2")); - auto fun5 = fun5_t((void (*)()) GetProcAddress(k32, "GetMaximumProcessorGroupCount")); - - if (!fun2 || !fun3) - return; - - if (!fun4 || !fun5) - { - GROUP_AFFINITY affinity; - if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx - fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity - } - else - { - // If a numa node has more than one processor group, we assume they are - // sized equal and we spread threads evenly across the groups. - USHORT elements, returnedElements; - elements = fun5(); // GetMaximumProcessorGroupCount - GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY)); - if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2 - fun3(GetCurrentThread(), &affinity[idx % returnedElements], - nullptr); // SetThreadGroupAffinity - free(affinity); - } -} - -#endif - -} // namespace WinProcGroup - #ifdef _WIN32 #include #define GETCWD _getcwd @@ -723,6 +602,15 @@ void bind_this_thread(size_t idx) { #define GETCWD getcwd #endif +size_t str_to_size_t(const std::string& s) { + size_t value; + auto result = std::from_chars(s.data(), s.data() + s.size(), value); + + if (result.ec != std::errc()) + std::exit(EXIT_FAILURE); + + return value; +} std::string CommandLine::get_binary_directory(std::string argv0) { std::string pathSeparator; diff --git a/src/misc.h b/src/misc.h index 3a905dfab49..99cbecfdd2c 100644 --- a/src/misc.h +++ b/src/misc.h @@ -24,10 +24,12 @@ #include #include #include +#include #include #include #include #include +#include #define stringify2(x) #x #define stringify(x) stringify2(x) @@ -50,6 +52,8 @@ void* aligned_large_pages_alloc(size_t size); // nop if mem == nullptr void aligned_large_pages_free(void* mem); +size_t str_to_size_t(const std::string& s); + // Deleter for automating release of memory area template struct AlignedDeleter { @@ -73,6 +77,31 @@ using AlignedPtr = std::unique_ptr>; template using LargePagePtr = std::unique_ptr>; +struct PipeDeleter { + void operator()(FILE* file) const { + if (file != nullptr) + { + pclose(file); + } + } +}; + +#if defined(__linux__) + +inline std::optional get_system_command_output(const std::string& command) { + std::unique_ptr pipe(popen(command.c_str(), "r")); + if (!pipe) + return std::nullopt; + + std::string result; + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), pipe.get()) != nullptr) + result += buffer; + + return result; +} + +#endif void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); @@ -88,6 +117,24 @@ inline TimePoint now() { .count(); } +inline std::vector split(const std::string& s, const std::string& delimiter) { + size_t begin = 0; + std::vector res; + + for (;;) + { + const size_t end = s.find(delimiter, begin); + if (end == std::string::npos) + break; + + res.emplace_back(s.substr(begin, end - begin)); + begin = end + delimiter.size(); + } + + res.emplace_back(s.substr(begin)); + + return res; +} enum SyncCout { IO_LOCK, @@ -194,15 +241,6 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #endif } -// Under Windows it is not possible for a process to run on more than one -// logical processor group. This usually means being limited to using max 64 -// cores. To overcome this, some special platform-specific API should be -// called to set group affinity for each thread. Original code from Texel by -// Peter Österlund. -namespace WinProcGroup { -void bind_this_thread(size_t idx); -} - struct CommandLine { public: diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index de2c7eca6d5..db864fcd384 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,47 @@ bool write_parameters(std::ostream& stream, const T& reference) { } // namespace Detail +template +Network::Network(const Network& other) : + evalFile(other.evalFile), + embeddedType(other.embeddedType) { + if (other.featureTransformer) + { + Detail::initialize(featureTransformer); + *featureTransformer = *other.featureTransformer; + } + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (other.network[i]) + { + Detail::initialize(network[i]); + *(network[i]) = *(other.network[i]); + } + } +} + +template +Network& +Network::operator=(const Network& other) { + evalFile = other.evalFile; + embeddedType = other.embeddedType; + + if (other.featureTransformer) + { + Detail::initialize(featureTransformer); + *featureTransformer = *other.featureTransformer; + } + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (other.network[i]) + { + Detail::initialize(network[i]); + *(network[i]) = *(other.network[i]); + } + } + + return *this; +} template void Network::load(const std::string& rootDirectory, std::string evalfilePath) { diff --git a/src/nnue/network.h b/src/nnue/network.h index 23f56663094..f0ccfafcb4c 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -50,6 +50,12 @@ class Network { evalFile(file), embeddedType(type) {} + Network(const Network& other); + Network(Network&& other) = default; + + Network& operator=(const Network& other); + Network& operator=(Network&& other) = default; + void load(const std::string& rootDirectory, std::string evalfilePath); bool save(const std::optional& filename) const; diff --git a/src/numa.h b/src/numa.h new file mode 100644 index 00000000000..c04292daf01 --- /dev/null +++ b/src/numa.h @@ -0,0 +1,904 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NUMA_H_INCLUDED +#define NUMA_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// We support linux very well, but we explicitly do NOT support Android, partially because +// there are potential issues with `lscpu`, `popen` availability, and partially because +// there's no NUMA environments running Android and there probably won't be. +#if defined(__linux__) && !defined(__ANDROID__) + #if !defined(_GNU_SOURCE) + #define _GNU_SOURCE + #endif + #include +#elif defined(_WIN32) + +// On Windows each processor group can have up to 64 processors. +// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups +static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64; + + #if !defined(NOMINMAX) + #define NOMINMAX + #endif + #include + +// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadselectedcpusetmasks +using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT); + +// https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-setthreadgroupaffinity +using SetThreadGroupAffinity_t = BOOL (*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY); + +#endif + +#include "misc.h" + +namespace Stockfish { + +using CpuIndex = size_t; +using NumaIndex = size_t; + +inline const CpuIndex SYSTEM_THREADS_NB = + std::max(1, std::thread::hardware_concurrency()); + +// We want to abstract the purpose of storing the numa node index somewhat. +// Whoever is using this does not need to know the specifics of the replication +// machinery to be able to access NUMA replicated memory. +class NumaReplicatedAccessToken { + public: + NumaReplicatedAccessToken() : + n(0) {} + + explicit NumaReplicatedAccessToken(NumaIndex idx) : + n(idx) {} + + NumaIndex get_numa_index() const { return n; } + + private: + NumaIndex n; +}; + +// Designed as immutable, because there is no good reason to alter an already existing config +// in a way that doesn't require recreating it completely, and it would be complex and expensive +// to maintain class invariants. +// The CPU (processor) numbers always correspond to the actual numbering used by the system. +// NOTE: the numbering is only valid within the process, as for example on Windows +// every process gets a "virtualized" set of processors that respects the current affinity +// The NUMA node numbers MAY NOT correspond to the system's numbering of the NUMA nodes. +// In particular, empty nodes may be removed, or the user may create custom nodes. +// It is guaranteed that NUMA nodes are NOT empty, i.e. every node exposed by NumaConfig +// has at least one processor assigned. +// +// Until Stockfish doesn't support exceptions all places where an exception should be thrown +// are replaced by std::exit. +class NumaConfig { + public: + NumaConfig() : + highestCpuIndex(0), + customAffinity(false) { + const auto numCpus = SYSTEM_THREADS_NB; + add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1); + } + + static std::set get_process_affinity() { + std::set cpus; + + // For unsupported systems, or in case of a soft error, we may assume all processors + // are available for use. + [[maybe_unused]] auto set_to_all_cpus = [&]() { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + cpus.insert(c); + }; + +#if defined(__linux__) && !defined(__ANDROID__) + + // cpu_set_t by default holds 1024 entries. This may not be enough soon, + // but there is no easy way to determine how many threads there actually is. + // In this case we just choose a reasonable upper bound. + static constexpr CpuIndex MaxNumCpus = 1024 * 64; + + cpu_set_t* mask = CPU_ALLOC(MaxNumCpus); + if (mask == nullptr) + std::exit(EXIT_FAILURE); + + const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus); + + CPU_ZERO_S(masksize, mask); + + const int status = sched_getaffinity(0, masksize, mask); + + if (status != 0) + { + CPU_FREE(mask); + std::exit(EXIT_FAILURE); + } + + for (CpuIndex c = 0; c < MaxNumCpus; ++c) + if (CPU_ISSET_S(c, masksize, mask)) + cpus.insert(c); + + CPU_FREE(mask); + +#elif defined(_WIN32) + + // Windows is problematic and weird due to multiple ways of setting affinity, processor groups, + // and behaviour changes between versions. It's unclear if we can support this feature + // on Windows in the same way we do on Linux. + // Apparently when affinity is set via either start /affinity or msys2 taskset + // the function GetNumaProcessorNodeEx completely disregards the processors that we do not + // have affinity more. Moreover, the indices are shifted to start from 0, indicating that Windows + // is providing a whole new mapping of processors to this process. This is problematic in some cases + // but it at least allows us to [probably] support this affinity restriction feature by default. + // So overall, Windows appears to "virtualize" a set of processors and processor groups for every + // process. It's unclear if this assignment can change while the process is running. + // std::thread::hardware_concurrency() returns the number of processors that's consistent + // with GetNumaProcessorNodeEx, so we can just add all of them. + + set_to_all_cpus(); + +#else + + // For other systems we assume the process is allowed to execute on all processors. + set_to_all_cpus(); + +#endif + + return cpus; + } + + // This function queries the system for the mapping of processors to NUMA nodes. + // On Linux we utilize `lscpu` to avoid libnuma. + // On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see + // comment for Windows implementation of get_process_affinity + static NumaConfig from_system(bool respectProcessAffinity = true) { + NumaConfig cfg = empty(); + + std::set allowedCpus; + + if (respectProcessAffinity) + allowedCpus = get_process_affinity(); + else + { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + allowedCpus.insert(c); + } + + auto is_cpu_allowed = [&](CpuIndex c) { return allowedCpus.count(c) == 1; }; + +#if defined(__linux__) && !defined(__ANDROID__) + + // On Linux things are straightforward, since there's no processor groups and + // any thread can be scheduled on all processors. + // This command produces output in the following form + // CPU NODE + // 0 0 + // 1 0 + // 2 1 + // 3 1 + // + // On some systems it may use '-' to signify no NUMA node, in which case we assume it's in node 0. + auto lscpuOpt = get_system_command_output("lscpu -e=cpu,node"); + if (lscpuOpt.has_value()) + { + + std::istringstream ss(*lscpuOpt); + + // skip the list header + ss.ignore(std::numeric_limits::max(), '\n'); + + while (true) + { + CpuIndex c; + NumaIndex n; + + ss >> c; + + if (!ss) + break; + + ss >> n; + + if (!ss) + { + ss.clear(); + std::string dummy; + ss >> dummy; + n = 0; + } + + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(n, c); + } + } + else + { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(NumaIndex{0}, c); + } + +#elif defined(_WIN32) + + // Since Windows 11 and Windows Server 2022 thread affinities can span + // processor groups and can be set as such by a new WinAPI function. + static const bool CanAffinitySpanProcessorGroups = []() { + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( + (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); + return SetThreadSelectedCpuSetMasks_f != nullptr; + }(); + + WORD numProcGroups = GetActiveProcessorGroupCount(); + for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup) + { + for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) + { + PROCESSOR_NUMBER procnum; + procnum.Group = procGroup; + procnum.Number = number; + procnum.Reserved = 0; + USHORT nodeNumber; + + // When start /affinity or taskset was used to run this process with restricted affinity + // GetNumaProcessorNodeEx will NOT correspond to the system's processor setup, instead + // it appears to follow a completely new processor assignment, made specifically for this process, + // in which processors that this process has affinity for are remapped, and only those are remapped, + // to form a new set of processors. In other words, we can only get processors + // which we have affinity for this way. This means that the behaviour for + // `respectProcessAffinity == false` may be unexpected when affinity is set from outside, + // while the behaviour for `respectProcessAffinity == true` is given by default. + const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber); + const CpuIndex c = static_cast(procGroup) * WIN_PROCESSOR_GROUP_SIZE + + static_cast(number); + if (status != 0 && nodeNumber != std::numeric_limits::max() + && is_cpu_allowed(c)) + { + cfg.add_cpu_to_node(nodeNumber, c); + } + } + } + + // Split the NUMA nodes to be contained within a group if necessary. + // This is needed between Windows 10 Build 20348 and Windows 11, because + // the new NUMA allocation behaviour was introduced while there was + // still no way to set thread affinity spanning multiple processor groups. + // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support + if (!CanAffinitySpanProcessorGroups) + { + NumaConfig splitCfg = empty(); + + NumaIndex splitNodeIndex = 0; + for (const auto& cpus : cfg.nodes) + { + if (cpus.empty()) + continue; + + size_t lastProcGroupIndex = *(cpus.begin()) / WIN_PROCESSOR_GROUP_SIZE; + for (CpuIndex c : cpus) + { + const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; + if (procGroupIndex != lastProcGroupIndex) + { + splitNodeIndex += 1; + lastProcGroupIndex = procGroupIndex; + } + splitCfg.add_cpu_to_node(splitNodeIndex, c); + } + splitNodeIndex += 1; + } + + cfg = std::move(splitCfg); + } + +#else + + // Fallback for unsupported systems. + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(NumaIndex{0}, c); + +#endif + + // We have to ensure no empty NUMA nodes persist. + cfg.remove_empty_numa_nodes(); + + return cfg; + } + + // ':'-separated numa nodes + // ','-separated cpu indices + // supports "first-last" range syntax for cpu indices + // For example "0-15,128-143:16-31,144-159:32-47,160-175:48-63,176-191" + static NumaConfig from_string(const std::string& s) { + NumaConfig cfg = empty(); + + NumaIndex n = 0; + for (auto&& nodeStr : split(s, ":")) + { + bool addedAnyCpuInThisNode = false; + + for (const std::string& cpuStr : split(nodeStr, ",")) + { + if (cpuStr.empty()) + continue; + + auto parts = split(cpuStr, "-"); + if (parts.size() == 1) + { + const CpuIndex c = CpuIndex{str_to_size_t(parts[0])}; + if (!cfg.add_cpu_to_node(n, c)) + std::exit(EXIT_FAILURE); + } + else if (parts.size() == 2) + { + const CpuIndex cfirst = CpuIndex{str_to_size_t(parts[0])}; + const CpuIndex clast = CpuIndex{str_to_size_t(parts[1])}; + + if (!cfg.add_cpu_range_to_node(n, cfirst, clast)) + std::exit(EXIT_FAILURE); + } + else + { + std::exit(EXIT_FAILURE); + } + + addedAnyCpuInThisNode = true; + } + + if (addedAnyCpuInThisNode) + n += 1; + } + + cfg.customAffinity = true; + + return cfg; + } + + NumaConfig(const NumaConfig&) = delete; + NumaConfig(NumaConfig&&) = default; + NumaConfig& operator=(const NumaConfig&) = delete; + NumaConfig& operator=(NumaConfig&&) = default; + + bool is_cpu_assigned(CpuIndex n) const { return nodeByCpu.count(n) == 1; } + + NumaIndex num_numa_nodes() const { return nodes.size(); } + + CpuIndex num_cpus_in_numa_node(NumaIndex n) const { + assert(n < nodes.size()); + return nodes[n].size(); + } + + CpuIndex num_cpus() const { return nodeByCpu.size(); } + + bool requires_memory_replication() const { return customAffinity || nodes.size() > 1; } + + std::string to_string() const { + std::string str; + + bool isFirstNode = true; + for (auto&& cpus : nodes) + { + if (!isFirstNode) + str += ":"; + + bool isFirstSet = true; + auto rangeStart = cpus.begin(); + for (auto it = cpus.begin(); it != cpus.end(); ++it) + { + auto next = std::next(it); + if (next == cpus.end() || *next != *it + 1) + { + // cpus[i] is at the end of the range (may be of size 1) + if (!isFirstSet) + str += ","; + + const CpuIndex last = *it; + + if (it != rangeStart) + { + const CpuIndex first = *rangeStart; + + str += std::to_string(first); + str += "-"; + str += std::to_string(last); + } + else + str += std::to_string(last); + + rangeStart = next; + isFirstSet = false; + } + } + + isFirstNode = false; + } + + return str; + } + + bool suggests_binding_threads(CpuIndex numThreads) const { + // If we can reasonably determine that the threads can't be contained + // by the OS within the first NUMA node then we advise distributing + // and binding threads. When the threads are not bound we can only use + // NUMA memory replicated objects from the first node, so when the OS + // has to schedule on other nodes we lose performance. + // We also suggest binding if there's enough threads to distribute among nodes + // with minimal disparity. + // We try to ignore small nodes, in particular the empty ones. + + // If the affinity set by the user does not match the affinity given by the OS + // then binding is necessary to ensure the threads are running on correct processors. + if (customAffinity) + return true; + + // We obviously can't distribute a single thread, so a single thread should never be bound. + if (numThreads <= 1) + return false; + + size_t largestNodeSize = 0; + for (auto&& cpus : nodes) + if (cpus.size() > largestNodeSize) + largestNodeSize = cpus.size(); + + auto is_node_small = [largestNodeSize](const std::set& node) { + static constexpr double SmallNodeThreshold = 0.6; + return static_cast(node.size()) / static_cast(largestNodeSize) + <= SmallNodeThreshold; + }; + + size_t numNotSmallNodes = 0; + for (auto&& cpus : nodes) + if (!is_node_small(cpus)) + numNotSmallNodes += 1; + + return (numThreads > largestNodeSize / 2 || numThreads >= numNotSmallNodes * 4) + && nodes.size() > 1; + } + + std::vector distribute_threads_among_numa_nodes(CpuIndex numThreads) const { + std::vector ns; + + if (nodes.size() == 1) + { + // special case for when there's no NUMA nodes + // doesn't buy us much, but let's keep the default path simple + ns.resize(numThreads, NumaIndex{0}); + } + else + { + std::vector occupation(nodes.size(), 0); + for (CpuIndex c = 0; c < numThreads; ++c) + { + NumaIndex bestNode{0}; + float bestNodeFill = std::numeric_limits::max(); + for (NumaIndex n = 0; n < nodes.size(); ++n) + { + float fill = + static_cast(occupation[n] + 1) / static_cast(nodes[n].size()); + // NOTE: Do we want to perhaps fill the first available node up to 50% first before considering other nodes? + // Probably not, because it would interfere with running multiple instances. We basically shouldn't + // favor any particular node. + if (fill < bestNodeFill) + { + bestNode = n; + bestNodeFill = fill; + } + } + ns.emplace_back(bestNode); + occupation[bestNode] += 1; + } + } + + return ns; + } + + NumaReplicatedAccessToken bind_current_thread_to_numa_node(NumaIndex n) const { + if (n >= nodes.size() || nodes[n].size() == 0) + std::exit(EXIT_FAILURE); + +#if defined(__linux__) && !defined(__ANDROID__) + + cpu_set_t* mask = CPU_ALLOC(highestCpuIndex + 1); + if (mask == nullptr) + std::exit(EXIT_FAILURE); + + const size_t masksize = CPU_ALLOC_SIZE(highestCpuIndex + 1); + + CPU_ZERO_S(masksize, mask); + + for (CpuIndex c : nodes[n]) + CPU_SET_S(c, masksize, mask); + + const int status = sched_setaffinity(0, masksize, mask); + + CPU_FREE(mask); + + if (status != 0) + std::exit(EXIT_FAILURE); + + // We yield this thread just to be sure it gets rescheduled. + // This is defensive, allowed because this code is not performance critical. + sched_yield(); + +#elif defined(_WIN32) + + // Requires Windows 11. No good way to set thread affinity spanning processor groups before that. + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( + (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); + auto SetThreadGroupAffinity_f = + SetThreadGroupAffinity_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity")); + + if (SetThreadSelectedCpuSetMasks_f != nullptr) + { + // Only available on Windows 11 and Windows Server 2022 onwards. + const USHORT numProcGroups = + ((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE; + auto groupAffinities = std::make_unique(numProcGroups); + std::memset(groupAffinities.get(), 0, sizeof(GROUP_AFFINITY) * numProcGroups); + for (WORD i = 0; i < numProcGroups; ++i) + groupAffinities[i].Group = i; + + for (CpuIndex c : nodes[n]) + { + const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; + const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE; + groupAffinities[procGroupIndex].Mask |= KAFFINITY(1) << idxWithinProcGroup; + } + + HANDLE hThread = GetCurrentThread(); + + const BOOL status = + SetThreadSelectedCpuSetMasks_f(hThread, groupAffinities.get(), numProcGroups); + if (status == 0) + std::exit(EXIT_FAILURE); + + // We yield this thread just to be sure it gets rescheduled. + // This is defensive, allowed because this code is not performance critical. + SwitchToThread(); + } + else if (SetThreadGroupAffinity_f != nullptr) + { + // On earlier windows version (since windows 7) we can't run a single thread + // on multiple processor groups, so we need to restrict the group. + // We assume the group of the first processor listed for this node. + // Processors from outside this group will not be assigned for this thread. + // Normally this won't be an issue because windows used to assign NUMA nodes + // such that they can't span processor groups. However, since Windows 10 Build 20348 + // the behaviour changed, so there's a small window of versions between this and Windows 11 + // that might exhibit problems with not all processors being utilized. + // We handle this in NumaConfig::from_system by manually splitting the nodes when + // we detect that there's no function to set affinity spanning processor nodes. + // This is required because otherwise our thread distribution code may produce + // suboptimal results. + // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support + GROUP_AFFINITY affinity; + std::memset(&affinity, 0, sizeof(GROUP_AFFINITY)); + affinity.Group = static_cast(n); + // We use an ordered set so we're guaranteed to get the smallest cpu number here. + const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE; + for (CpuIndex c : nodes[n]) + { + const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; + const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE; + // We skip processors that are not in the same proccessor group. + // If everything was set up correctly this will never be an issue, + // but we have to account for bad NUMA node specification. + if (procGroupIndex != forcedProcGroupIndex) + continue; + + affinity.Mask |= KAFFINITY(1) << idxWithinProcGroup; + } + + HANDLE hThread = GetCurrentThread(); + + const BOOL status = SetThreadGroupAffinity_f(hThread, &affinity, nullptr); + if (status == 0) + std::exit(EXIT_FAILURE); + + // We yield this thread just to be sure it gets rescheduled. + // This is defensive, allowed because this code is not performance critical. + SwitchToThread(); + } + +#endif + + return NumaReplicatedAccessToken(n); + } + + template + void execute_on_numa_node(NumaIndex n, FuncT&& f) const { + std::thread th([this, &f, n]() { + bind_current_thread_to_numa_node(n); + std::forward(f)(); + }); + + th.join(); + } + + private: + std::vector> nodes; + std::map nodeByCpu; + CpuIndex highestCpuIndex; + + bool customAffinity; + + static NumaConfig empty() { return NumaConfig(EmptyNodeTag{}); } + + struct EmptyNodeTag {}; + + NumaConfig(EmptyNodeTag) : + highestCpuIndex(0), + customAffinity(false) {} + + void remove_empty_numa_nodes() { + std::vector> newNodes; + for (auto&& cpus : nodes) + if (!cpus.empty()) + newNodes.emplace_back(std::move(cpus)); + nodes = std::move(newNodes); + } + + // Returns true if successful + // Returns false if failed, i.e. when the cpu is already present + // strong guarantee, the structure remains unmodified + bool add_cpu_to_node(NumaIndex n, CpuIndex c) { + if (is_cpu_assigned(c)) + return false; + + while (nodes.size() <= n) + nodes.emplace_back(); + + nodes[n].insert(c); + nodeByCpu[c] = n; + + if (c > highestCpuIndex) + highestCpuIndex = c; + + return true; + } + + // Returns true if successful + // Returns false if failed, i.e. when any of the cpus is already present + // strong guarantee, the structure remains unmodified + bool add_cpu_range_to_node(NumaIndex n, CpuIndex cfirst, CpuIndex clast) { + for (CpuIndex c = cfirst; c <= clast; ++c) + if (is_cpu_assigned(c)) + return false; + + while (nodes.size() <= n) + nodes.emplace_back(); + + for (CpuIndex c = cfirst; c <= clast; ++c) + { + nodes[n].insert(c); + nodeByCpu[c] = n; + } + + if (clast > highestCpuIndex) + highestCpuIndex = clast; + + return true; + } +}; + +class NumaReplicationContext; + +// Instances of this class are tracked by the NumaReplicationContext instance +// NumaReplicationContext informs all tracked instances whenever NUMA configuration changes. +class NumaReplicatedBase { + public: + NumaReplicatedBase(NumaReplicationContext& ctx); + + NumaReplicatedBase(const NumaReplicatedBase&) = delete; + NumaReplicatedBase(NumaReplicatedBase&& other) noexcept; + + NumaReplicatedBase& operator=(const NumaReplicatedBase&) = delete; + NumaReplicatedBase& operator=(NumaReplicatedBase&& other) noexcept; + + virtual void on_numa_config_changed() = 0; + virtual ~NumaReplicatedBase(); + + const NumaConfig& get_numa_config() const; + + private: + NumaReplicationContext* context; +}; + +// We force boxing with a unique_ptr. If this becomes an issue due to added indirection we +// may need to add an option for a custom boxing type. +// When the NUMA config changes the value stored at the index 0 is replicated to other nodes. +template +class NumaReplicated: public NumaReplicatedBase { + public: + using ReplicatorFuncType = std::function; + + NumaReplicated(NumaReplicationContext& ctx) : + NumaReplicatedBase(ctx) { + replicate_from(T{}); + } + + NumaReplicated(NumaReplicationContext& ctx, T&& source) : + NumaReplicatedBase(ctx) { + replicate_from(std::move(source)); + } + + NumaReplicated(const NumaReplicated&) = delete; + NumaReplicated(NumaReplicated&& other) noexcept : + NumaReplicatedBase(std::move(other)), + instances(std::exchange(other.instances, {})) {} + + NumaReplicated& operator=(const NumaReplicated&) = delete; + NumaReplicated& operator=(NumaReplicated&& other) noexcept { + NumaReplicatedBase::operator=(*this, std::move(other)); + instances = std::exchange(other.instances, {}); + + return *this; + } + + NumaReplicated& operator=(T&& source) { + replicate_from(std::move(source)); + + return *this; + } + + ~NumaReplicated() override = default; + + const T& operator[](NumaReplicatedAccessToken token) const { + assert(token.get_numa_index() < instances.size()); + return *(instances[token.get_numa_index()]); + } + + const T& operator*() const { return *(instances[0]); } + + const T* operator->() const { return instances[0].get(); } + + template + void modify_and_replicate(FuncT&& f) { + auto source = std::move(instances[0]); + std::forward(f)(*source); + replicate_from(std::move(*source)); + } + + void on_numa_config_changed() override { + // Use the first one as the source. It doesn't matter which one we use, because they all must + // be identical, but the first one is guaranteed to exist. + auto source = std::move(instances[0]); + replicate_from(std::move(*source)); + } + + private: + std::vector> instances; + + void replicate_from(T&& source) { + instances.clear(); + + const NumaConfig& cfg = get_numa_config(); + if (cfg.requires_memory_replication()) + { + for (NumaIndex n = 0; n < cfg.num_numa_nodes(); ++n) + { + cfg.execute_on_numa_node( + n, [this, &source]() { instances.emplace_back(std::make_unique(source)); }); + } + } + else + { + assert(cfg.num_numa_nodes() == 1); + // We take advantage of the fact that replication is not required + // and reuse the source value, avoiding one copy operation. + instances.emplace_back(std::make_unique(std::move(source))); + } + } +}; + +class NumaReplicationContext { + public: + NumaReplicationContext(NumaConfig&& cfg) : + config(std::move(cfg)) {} + + NumaReplicationContext(const NumaReplicationContext&) = delete; + NumaReplicationContext(NumaReplicationContext&&) = delete; + + NumaReplicationContext& operator=(const NumaReplicationContext&) = delete; + NumaReplicationContext& operator=(NumaReplicationContext&&) = delete; + + ~NumaReplicationContext() { + // The context must outlive replicated objects + if (!trackedReplicatedObjects.empty()) + std::exit(EXIT_FAILURE); + } + + void attach(NumaReplicatedBase* obj) { + assert(trackedReplicatedObjects.count(obj) == 0); + trackedReplicatedObjects.insert(obj); + } + + void detach(NumaReplicatedBase* obj) { + assert(trackedReplicatedObjects.count(obj) == 1); + trackedReplicatedObjects.erase(obj); + } + + // oldObj may be invalid at this point + void move_attached([[maybe_unused]] NumaReplicatedBase* oldObj, NumaReplicatedBase* newObj) { + assert(trackedReplicatedObjects.count(oldObj) == 1); + assert(trackedReplicatedObjects.count(newObj) == 0); + trackedReplicatedObjects.erase(oldObj); + trackedReplicatedObjects.insert(newObj); + } + + void set_numa_config(NumaConfig&& cfg) { + config = std::move(cfg); + for (auto&& obj : trackedReplicatedObjects) + obj->on_numa_config_changed(); + } + + const NumaConfig& get_numa_config() const { return config; } + + private: + NumaConfig config; + + // std::set uses std::less by default, which is required for pointer comparison to be defined. + std::set trackedReplicatedObjects; +}; + +inline NumaReplicatedBase::NumaReplicatedBase(NumaReplicationContext& ctx) : + context(&ctx) { + context->attach(this); +} + +inline NumaReplicatedBase::NumaReplicatedBase(NumaReplicatedBase&& other) noexcept : + context(std::exchange(other.context, nullptr)) { + context->move_attached(&other, this); +} + +inline NumaReplicatedBase& NumaReplicatedBase::operator=(NumaReplicatedBase&& other) noexcept { + context = std::exchange(other.context, nullptr); + + context->move_attached(&other, this); + + return *this; +} + +inline NumaReplicatedBase::~NumaReplicatedBase() { + if (context != nullptr) + context->detach(this); +} + +inline const NumaConfig& NumaReplicatedBase::get_numa_config() const { + return context->get_numa_config(); +} + +} // namespace Stockfish + + +#endif // #ifndef NUMA_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 0dbc6a3a5db..c074e3421ea 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -137,15 +137,17 @@ void update_all_stats(const Position& pos, Search::Worker::Worker(SharedState& sharedState, std::unique_ptr sm, - size_t thread_id) : + size_t thread_id, + NumaReplicatedAccessToken token) : // Unpack the SharedState struct into member variables thread_idx(thread_id), + numaAccessToken(token), manager(std::move(sm)), options(sharedState.options), threads(sharedState.threads), tt(sharedState.tt), networks(sharedState.networks), - refreshTable(networks) { + refreshTable(networks[token]) { clear(); } @@ -428,7 +430,7 @@ void Search::Worker::iterative_deepening() { skill.pick_best(rootMoves, multiPV); // Use part of the gained time from a previous stable move for the current move - for (Thread* th : threads) + for (auto&& th : threads) { totBestMoveChanges += th->worker->bestMoveChanges; th->worker->bestMoveChanges = 0; @@ -510,7 +512,7 @@ void Search::Worker::clear() { for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((19.90 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); - refreshTable.clear(networks); + refreshTable.clear(networks[numaAccessToken]); } @@ -576,9 +578,9 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate( + networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -706,7 +708,7 @@ Value Search::Worker::search( { // 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, refreshTable); + Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -714,9 +716,10 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]); + unadjustedStaticEval = + evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); + Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -726,7 +729,8 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]); + unadjustedStaticEval = + evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -892,7 +896,7 @@ Value Search::Worker::search( } } - Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable); + Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); } moves_loop: // When in check, search starts here @@ -1441,7 +1445,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) + ? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1476,7 +1480,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) unadjustedStaticEval = - evaluate(networks, pos, refreshTable, thisThread->optimism[us]); + evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1488,10 +1492,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, else { // In case of null move search, use previous static eval with a different sign - unadjustedStaticEval = (ss - 1)->currentMove != Move::null() - ? evaluate(networks, pos, refreshTable, thisThread->optimism[us]) - : -(ss - 1)->staticEval; - ss->staticEval = bestValue = + unadjustedStaticEval = + (ss - 1)->currentMove != Move::null() + ? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) + : -(ss - 1)->staticEval; + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); } diff --git a/src/search.h b/src/search.h index 6e5b22bda32..a61f253c005 100644 --- a/src/search.h +++ b/src/search.h @@ -32,19 +32,17 @@ #include "misc.h" #include "movepick.h" +#include "nnue/network.h" +#include "nnue/nnue_accumulator.h" +#include "numa.h" #include "position.h" #include "score.h" #include "syzygy/tbprobe.h" #include "timeman.h" #include "types.h" -#include "nnue/nnue_accumulator.h" namespace Stockfish { -namespace Eval::NNUE { -struct Networks; -} - // Different node types, used as a template parameter enum NodeType { NonPV, @@ -133,19 +131,19 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& optionsMap, - ThreadPool& threadPool, - TranspositionTable& transpositionTable, - const Eval::NNUE::Networks& nets) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const NumaReplicated& nets) : options(optionsMap), threads(threadPool), tt(transpositionTable), networks(nets) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - const Eval::NNUE::Networks& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const NumaReplicated& networks; }; class Worker; @@ -236,7 +234,7 @@ class NullSearchManager: public ISearchManager { // of the search history, and storing data required for the search. class Worker { public: - Worker(SharedState&, std::unique_ptr, size_t); + Worker(SharedState&, std::unique_ptr, size_t, NumaReplicatedAccessToken); // Called at instantiation to initialize Reductions tables // Reset histories, usually before a new game @@ -293,7 +291,8 @@ class Worker { Depth rootDepth, completedDepth; Value rootDelta; - size_t thread_idx; + size_t thread_idx; + NumaReplicatedAccessToken numaAccessToken; // Reductions lookup table initialized at startup std::array reductions; // [depth or moveNumber] @@ -303,10 +302,10 @@ class Worker { Tablebases::Config tbConfig; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - const Eval::NNUE::Networks& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const NumaReplicated& networks; // Used by NNUE Eval::NNUE::AccumulatorCaches refreshTable; diff --git a/src/thread.cpp b/src/thread.cpp index 8724cb49cd1..5893f4b6d07 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -22,19 +22,17 @@ #include #include #include +#include #include #include -#include -#include "misc.h" #include "movegen.h" #include "search.h" #include "syzygy/tbprobe.h" #include "timeman.h" -#include "tt.h" #include "types.h" -#include "ucioption.h" #include "uci.h" +#include "ucioption.h" namespace Stockfish { @@ -42,13 +40,24 @@ namespace Stockfish { // in idle_loop(). Note that 'searching' and 'exit' should be already set. Thread::Thread(Search::SharedState& sharedState, std::unique_ptr sm, - size_t n) : - worker(std::make_unique(sharedState, std::move(sm), n)), + size_t n, + OptionalThreadToNumaNodeBinder binder) : idx(n), nthreads(sharedState.options["Threads"]), stdThread(&Thread::idle_loop, this) { wait_for_search_finished(); + + run_custom_job([this, &binder, &sharedState, &sm, n]() { + // Use the binder to [maybe] bind the threads to a NUMA node before doing + // the Worker allocation. + // Ideally we would also allocate the SearchManager here, but that's minor. + this->numaAccessToken = binder(); + this->worker = + std::make_unique(sharedState, std::move(sm), n, this->numaAccessToken); + }); + + wait_for_search_finished(); } @@ -66,12 +75,15 @@ Thread::~Thread() { // Wakes up the thread that will start the search void Thread::start_searching() { - mutex.lock(); - searching = true; - mutex.unlock(); // Unlock before notifying saves a few CPU-cycles - cv.notify_one(); // Wake up the thread in idle_loop() + assert(worker != nullptr); + run_custom_job([this]() { worker->start_searching(); }); } +// Wakes up the thread that will start the search +void Thread::clear_worker() { + assert(worker != nullptr); + run_custom_job([this]() { worker->clear(); }); +} // Blocks on the condition variable // until the thread has finished searching. @@ -81,20 +93,20 @@ void Thread::wait_for_search_finished() { cv.wait(lk, [&] { return !searching; }); } +void Thread::run_custom_job(std::function f) { + { + std::unique_lock lk(mutex); + cv.wait(lk, [&] { return !searching; }); + jobFunc = std::move(f); + searching = true; + } + cv.notify_one(); +} // Thread gets parked here, blocked on the // condition variable, when it has no work to do. void Thread::idle_loop() { - - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case, all this - // NUMA machinery is not needed. - if (nthreads > 8) - WinProcGroup::bind_this_thread(idx); - while (true) { std::unique_lock lk(mutex); @@ -105,9 +117,13 @@ void Thread::idle_loop() { if (exit) return; + std::function job = std::move(jobFunc); + jobFunc = nullptr; + lk.unlock(); - worker->start_searching(); + if (job) + job(); } } @@ -121,49 +137,82 @@ uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. -void ThreadPool::set(Search::SharedState sharedState, +void ThreadPool::set(const NumaConfig& numaConfig, + Search::SharedState sharedState, const Search::SearchManager::UpdateContext& updateContext) { if (threads.size() > 0) // destroy any existing thread(s) { main_thread()->wait_for_search_finished(); - while (threads.size() > 0) - delete threads.back(), threads.pop_back(); + threads.clear(); + + boundThreadToNumaNode.clear(); } const size_t requested = sharedState.options["Threads"]; if (requested > 0) // create new thread(s) { - auto manager = std::make_unique(updateContext); - threads.push_back(new Thread(sharedState, std::move(manager), 0)); + // Binding threads may be problematic when there's multiple NUMA nodes and + // multiple Stockfish instances running. In particular, if each instance + // runs a single thread then they would all be mapped to the first NUMA node. + // This is undesirable, and so the default behaviour (i.e. when the user does not + // change the NumaConfig UCI setting) is to not bind the threads to processors + // unless we know for sure that we span NUMA nodes and replication is required. + const std::string numaPolicy(sharedState.options["NumaPolicy"]); + const bool doBindThreads = [&]() { + if (numaPolicy == "none") + return false; + + if (numaPolicy == "auto") + return numaConfig.suggests_binding_threads(requested); + + // numaPolicy == "system", or explicitly set by the user + return true; + }(); + + boundThreadToNumaNode = doBindThreads + ? numaConfig.distribute_threads_among_numa_nodes(requested) + : std::vector{}; while (threads.size() < requested) { - auto null_manager = std::make_unique(); - threads.push_back(new Thread(sharedState, std::move(null_manager), threads.size())); + const size_t threadId = threads.size(); + const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0; + auto manager = threadId == 0 ? std::unique_ptr( + std::make_unique(updateContext)) + : std::make_unique(); + + // When not binding threads we want to force all access to happen + // from the same NUMA node, because in case of NUMA replicated memory + // accesses we don't want to trash cache in case the threads get scheduled + // on the same NUMA node. + auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId) + : OptionalThreadToNumaNodeBinder(numaId); + + threads.emplace_back( + std::make_unique(sharedState, std::move(manager), threadId, binder)); } clear(); main_thread()->wait_for_search_finished(); - - // Reallocate the hash with the new threadpool size - sharedState.tt.resize(sharedState.options["Hash"], requested); } } // Sets threadPool data to initial values void ThreadPool::clear() { - - for (Thread* th : threads) - th->worker->clear(); - if (threads.size() == 0) return; + for (auto&& th : threads) + th->clear_worker(); + + for (auto&& th : threads) + th->wait_for_search_finished(); + main_manager()->callsCnt = 0; main_manager()->bestPreviousScore = VALUE_INFINITE; main_manager()->bestPreviousAverageScore = VALUE_INFINITE; @@ -172,6 +221,17 @@ void ThreadPool::clear() { main_manager()->tm.clear(); } +void ThreadPool::run_on_thread(size_t threadId, std::function f) { + assert(threads.size() > threadId); + threads[threadId]->run_custom_job(std::move(f)); +} + +void ThreadPool::wait_on_thread(size_t threadId) { + assert(threads.size() > threadId); + threads[threadId]->wait_for_search_finished(); +} + +size_t ThreadPool::num_threads() const { return threads.size(); } // Wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. @@ -216,31 +276,36 @@ void ThreadPool::start_thinking(const OptionsMap& options, // be deduced from a fen string, so set() clears them and they are set from // setupStates->back() later. The rootState is per thread, earlier states are shared // since they are read-only. - for (Thread* th : threads) + for (auto&& th : threads) { - th->worker->limits = limits; - th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly = - th->worker->bestMoveChanges = 0; - th->worker->rootDepth = th->worker->completedDepth = 0; - th->worker->rootMoves = rootMoves; - th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); - th->worker->rootState = setupStates->back(); - th->worker->tbConfig = tbConfig; + th->run_custom_job([&]() { + th->worker->limits = limits; + th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly = + th->worker->bestMoveChanges = 0; + th->worker->rootDepth = th->worker->completedDepth = 0; + th->worker->rootMoves = rootMoves; + th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); + th->worker->rootState = setupStates->back(); + th->worker->tbConfig = tbConfig; + }); } + for (auto&& th : threads) + th->wait_for_search_finished(); + main_thread()->start_searching(); } Thread* ThreadPool::get_best_thread() const { - Thread* bestThread = threads.front(); + Thread* bestThread = threads.front().get(); Value minScore = VALUE_NONE; std::unordered_map votes( 2 * std::min(size(), bestThread->worker->rootMoves.size())); // Find the minimum score of all threads - for (Thread* th : threads) + for (auto&& th : threads) minScore = std::min(minScore, th->worker->rootMoves[0].score); // Vote according to score and depth, and select the best thread @@ -248,10 +313,10 @@ Thread* ThreadPool::get_best_thread() const { return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth); }; - for (Thread* th : threads) - votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th); + for (auto&& th : threads) + votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th.get()); - for (Thread* th : threads) + for (auto&& th : threads) { const auto bestThreadScore = bestThread->worker->rootMoves[0].score; const auto newThreadScore = th->worker->rootMoves[0].score; @@ -272,26 +337,26 @@ Thread* ThreadPool::get_best_thread() const { // Note that we make sure not to pick a thread with truncated-PV for better viewer experience. const bool betterVotingValue = - thread_voting_value(th) * int(newThreadPV.size() > 2) + thread_voting_value(th.get()) * int(newThreadPV.size() > 2) > thread_voting_value(bestThread) * int(bestThreadPV.size() > 2); if (bestThreadInProvenWin) { // Make sure we pick the shortest mate / TB conversion if (newThreadScore > bestThreadScore) - bestThread = th; + bestThread = th.get(); } else if (bestThreadInProvenLoss) { // Make sure we pick the shortest mated / TB conversion if (newThreadInProvenLoss && newThreadScore < bestThreadScore) - bestThread = th; + bestThread = th.get(); } else if (newThreadInProvenWin || newThreadInProvenLoss || (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY && (newThreadMoveVote > bestThreadMoveVote || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue)))) - bestThread = th; + bestThread = th.get(); } return bestThread; @@ -302,7 +367,7 @@ Thread* ThreadPool::get_best_thread() const { // Will be invoked by main thread after it has started searching void ThreadPool::start_searching() { - for (Thread* th : threads) + for (auto&& th : threads) if (th != threads.front()) th->start_searching(); } @@ -312,9 +377,28 @@ void ThreadPool::start_searching() { void ThreadPool::wait_for_search_finished() const { - for (Thread* th : threads) + for (auto&& th : threads) if (th != threads.front()) th->wait_for_search_finished(); } +std::vector ThreadPool::get_bound_thread_count_by_numa_node() const { + std::vector counts; + + if (!boundThreadToNumaNode.empty()) + { + NumaIndex highestNumaNode = 0; + for (NumaIndex n : boundThreadToNumaNode) + if (n > highestNumaNode) + highestNumaNode = n; + + counts.resize(highestNumaNode + 1, 0); + + for (NumaIndex n : boundThreadToNumaNode) + counts[n] += 1; + } + + return counts; +} + } // namespace Stockfish diff --git a/src/thread.h b/src/thread.h index 223652aec99..102b229907b 100644 --- a/src/thread.h +++ b/src/thread.h @@ -26,10 +26,12 @@ #include #include #include +#include #include "position.h" #include "search.h" #include "thread_win32_osx.h" +#include "numa.h" namespace Stockfish { @@ -37,6 +39,32 @@ namespace Stockfish { class OptionsMap; using Value = int; +// Sometimes we don't want to actually bind the threads, but the recipent still +// needs to think it runs on *some* NUMA node, such that it can access structures +// that rely on NUMA node knowledge. This class encapsulates this optional process +// such that the recipent does not need to know whether the binding happened or not. +class OptionalThreadToNumaNodeBinder { + public: + OptionalThreadToNumaNodeBinder(NumaIndex n) : + numaConfig(nullptr), + numaId(n) {} + + OptionalThreadToNumaNodeBinder(const NumaConfig& cfg, NumaIndex n) : + numaConfig(&cfg), + numaId(n) {} + + NumaReplicatedAccessToken operator()() const { + if (numaConfig != nullptr) + return numaConfig->bind_current_thread_to_numa_node(numaId); + else + return NumaReplicatedAccessToken(numaId); + } + + private: + const NumaConfig* numaConfig; + NumaIndex numaId; +}; + // Abstraction of a thread. It contains a pointer to the worker and a native thread. // After construction, the native thread is started with idle_loop() // waiting for a signal to start searching. @@ -44,22 +72,35 @@ using Value = int; // the search is finished, it goes back to idle_loop() waiting for a new signal. class Thread { public: - Thread(Search::SharedState&, std::unique_ptr, size_t); + Thread(Search::SharedState&, + std::unique_ptr, + size_t, + OptionalThreadToNumaNodeBinder); virtual ~Thread(); - void idle_loop(); - void start_searching(); + void idle_loop(); + void start_searching(); + void clear_worker(); + void run_custom_job(std::function f); + + // Thread has been slightly altered to allow running custom jobs, so + // this name is no longer correct. However, this class (and ThreadPool) + // require further work to make them properly generic while maintaining + // appropriate specificity regarding search, from the point of view of an + // outside user, so renaming of this function in left for whenever that happens. void wait_for_search_finished(); size_t id() const { return idx; } std::unique_ptr worker; + std::function jobFunc; private: - std::mutex mutex; - std::condition_variable cv; - size_t idx, nthreads; - bool exit = false, searching = true; // Set before starting std::thread - NativeThread stdThread; + std::mutex mutex; + std::condition_variable cv; + size_t idx, nthreads; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; + NumaReplicatedAccessToken numaAccessToken; }; @@ -67,31 +108,44 @@ class Thread { // parking and, most importantly, launching a thread. All the access to threads // is done through this class. class ThreadPool { - public: + ThreadPool() {} + ~ThreadPool() { // destroy any existing thread(s) if (threads.size() > 0) { main_thread()->wait_for_search_finished(); - while (threads.size() > 0) - delete threads.back(), threads.pop_back(); + threads.clear(); } } - void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); - void clear(); - void set(Search::SharedState, const Search::SearchManager::UpdateContext&); + ThreadPool(const ThreadPool&) = delete; + ThreadPool(ThreadPool&&) = delete; + + ThreadPool& operator=(const ThreadPool&) = delete; + ThreadPool& operator=(ThreadPool&&) = delete; + + void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType); + void run_on_thread(size_t threadId, std::function f); + void wait_on_thread(size_t threadId); + size_t num_threads() const; + void clear(); + void set(const NumaConfig& numaConfig, + Search::SharedState, + const Search::SearchManager::UpdateContext&); Search::SearchManager* main_manager(); - Thread* main_thread() const { return threads.front(); } + Thread* main_thread() const { return threads.front().get(); } uint64_t nodes_searched() const; uint64_t tb_hits() const; Thread* get_best_thread() const; void start_searching(); void wait_for_search_finished() const; + std::vector get_bound_thread_count_by_numa_node() const; + std::atomic_bool stop, abortedSearch, increaseDepth; auto cbegin() const noexcept { return threads.cbegin(); } @@ -102,13 +156,14 @@ class ThreadPool { auto empty() const noexcept { return threads.empty(); } private: - StateListPtr setupStates; - std::vector threads; + StateListPtr setupStates; + std::vector> threads; + std::vector boundThreadToNumaNode; uint64_t accumulate(std::atomic Search::Worker::*member) const { uint64_t sum = 0; - for (Thread* th : threads) + for (auto&& th : threads) sum += (th->worker.get()->*member).load(std::memory_order_relaxed); return sum; } diff --git a/src/tt.cpp b/src/tt.cpp index 3f5b9d4d9b0..79274f525b9 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -23,10 +23,10 @@ #include #include #include -#include -#include #include "misc.h" +#include "syzygy/tbprobe.h" +#include "thread.h" namespace Stockfish { @@ -74,7 +74,7 @@ uint8_t TTEntry::relative_age(const uint8_t generation8) const { // Sets the size of the transposition table, // measured in megabytes. Transposition table consists // of clusters and each cluster consists of ClusterSize number of TTEntry. -void TranspositionTable::resize(size_t mbSize, int threadCount) { +void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) { aligned_large_pages_free(table); clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); @@ -86,32 +86,29 @@ void TranspositionTable::resize(size_t mbSize, int threadCount) { exit(EXIT_FAILURE); } - clear(threadCount); + clear(threads); } // Initializes the entire transposition table to zero, // in a multi-threaded way. -void TranspositionTable::clear(size_t threadCount) { - std::vector threads; +void TranspositionTable::clear(ThreadPool& threads) { + const size_t threadCount = threads.num_threads(); - for (size_t idx = 0; idx < size_t(threadCount); ++idx) + for (size_t i = 0; i < threadCount; ++i) { - threads.emplace_back([this, idx, threadCount]() { - // Thread binding gives faster search on systems with a first-touch policy - if (threadCount > 8) - WinProcGroup::bind_this_thread(idx); - + threads.run_on_thread(i, [this, i, threadCount]() { // Each thread will zero its part of the hash table - const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx), - len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start; + const size_t stride = clusterCount / threadCount; + const size_t start = stride * i; + const size_t len = i + 1 != threadCount ? stride : clusterCount - start; std::memset(&table[start], 0, len * sizeof(Cluster)); }); } - for (std::thread& th : threads) - th.join(); + for (size_t i = 0; i < threadCount; ++i) + threads.wait_on_thread(i); } diff --git a/src/tt.h b/src/tt.h index 7cc876fb9ea..3b09ec4e1d9 100644 --- a/src/tt.h +++ b/src/tt.h @@ -63,6 +63,7 @@ struct TTEntry { int16_t eval16; }; +class ThreadPool; // A TranspositionTable is an array of Cluster, of size clusterCount. Each // cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry @@ -102,8 +103,8 @@ class TranspositionTable { TTEntry* probe(const Key key, bool& found) const; int hashfull() const; - void resize(size_t mbSize, int threadCount); - void clear(size_t threadCount); + void resize(size_t mbSize, ThreadPool& threads); + void clear(ThreadPool& threads); TTEntry* first_entry(const Key key) const { return &table[mul_hi64(key, clusterCount)].entry[0]; diff --git a/src/uci.cpp b/src/uci.cpp index cb686a027db..ab0dae3946e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -60,7 +60,16 @@ UCIEngine::UCIEngine(int argc, char** argv) : options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); - options["Threads"] << Option(1, 1, 1024, [this](const Option&) { engine.resize_threads(); }); + options["NumaPolicy"] << Option("auto", [this](const Option& o) { + engine.set_numa_config_from_option(o); + print_numa_config_information(); + print_thread_binding_information(); + }); + + options["Threads"] << Option(1, 1, 1024, [this](const Option&) { + engine.resize_threads(); + print_thread_binding_information(); + }); options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { engine.set_tt_size(o); }); @@ -123,8 +132,15 @@ void UCIEngine::loop() { engine.set_ponderhit(false); else if (token == "uci") + { sync_cout << "id name " << engine_info(true) << "\n" - << engine.get_options() << "\nuciok" << sync_endl; + << engine.get_options() << sync_endl; + + print_numa_config_information(); + print_thread_binding_information(); + + sync_cout << "uciok" << sync_endl; + } else if (token == "setoption") setoption(is); @@ -177,6 +193,28 @@ void UCIEngine::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } +void UCIEngine::print_numa_config_information() const { + auto cfgStr = engine.get_numa_config_as_string(); + sync_cout << "info string Available Processors: " << cfgStr << sync_endl; +} + +void UCIEngine::print_thread_binding_information() const { + auto boundThreadsByNode = engine.get_bound_thread_count_by_numa_node(); + if (!boundThreadsByNode.empty()) + { + sync_cout << "info string NUMA Node Thread Binding: "; + bool isFirst = true; + for (auto&& [current, total] : boundThreadsByNode) + { + if (!isFirst) + std::cout << ":"; + std::cout << current << "/" << total; + isFirst = false; + } + std::cout << sync_endl; + } +} + Search::LimitsType UCIEngine::parse_limits(std::istream& is) { Search::LimitsType limits; std::string token; diff --git a/src/uci.h b/src/uci.h index 55d580f9727..bac62bb90c0 100644 --- a/src/uci.h +++ b/src/uci.h @@ -42,6 +42,9 @@ class UCIEngine { void loop(); + void print_numa_config_information() const; + void print_thread_binding_information() const; + static int to_cp(Value v, const Position& pos); static std::string format_score(const Score& s); static std::string square(Square s); diff --git a/src/ucioption.cpp b/src/ucioption.cpp index e1ffe546525..4819a68db73 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -118,6 +118,8 @@ bool Option::operator==(const char* s) const { return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); } +bool Option::operator!=(const char* s) const { return !(*this == s); } + // Inits options and assigns idx in the correct printing order diff --git a/src/ucioption.h b/src/ucioption.h index b575d1646e6..16d46696145 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -67,6 +67,7 @@ class Option { operator int() const; operator std::string() const; bool operator==(const char*) const; + bool operator!=(const char*) const; friend std::ostream& operator<<(std::ostream&, const OptionsMap&); From 41acbcae1a8af4b23be397f7fe7234f3bc49a26e Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 29 May 2024 16:14:24 +0300 Subject: [PATCH 0556/1309] Simplifying malus for putting piece en prise formula Patch author: @ehsanrashid Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 116192 W: 30229 L: 30094 D: 55869 Ptnml(0-2): 451, 13880, 29351, 13911, 503 https://tests.stockfishchess.org/tests/view/66510a40a86388d5e27da936 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 441312 W: 111009 L: 111220 D: 219083 Ptnml(0-2): 217, 49390, 121659, 49167, 223 https://tests.stockfishchess.org/tests/view/66530696a86388d5e27da9e3 closes https://github.com/official-stockfish/Stockfish/pull/5304 Bench: 1987574 --- AUTHORS | 1 + src/movepick.cpp | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 36b2b6f7942..a232e115f7a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -68,6 +68,7 @@ Douglas Matos Gomes (dsmsgms) Dubslow Eduardo Cáceres (eduherminio) Eelco de Groot (KingDefender) +Ehsan Rashid (erashid) Elvin Liu (solarlight2) erbsenzaehler Ernesto Gatti diff --git a/src/movepick.cpp b/src/movepick.cpp index 7def0ce84fa..55f9ca0e802 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -197,13 +197,11 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= !(threatenedPieces & from) - ? (pt == QUEEN ? bool(to & threatenedByRook) * 48150 - + bool(to & threatenedByMinor) * 10650 - : pt == ROOK ? bool(to & threatenedByMinor) * 24335 - : pt != PAWN ? bool(to & threatenedByPawn) * 14950 - : 0) - : 0; + m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 48150 + + bool(to & threatenedByMinor) * 10650 + : pt == ROOK ? bool(to & threatenedByMinor) * 24335 + : pt != PAWN ? bool(to & threatenedByPawn) * 14950 + : 0); } else // Type == EVASIONS From c7b80f6c8a7b8267e019fc4ecb496f14f5256f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Mon, 27 May 2024 04:32:04 +0200 Subject: [PATCH 0557/1309] Merge pawn count terms using their average This simplification patch merges the pawn count terms in the eval formula with the material term, updating the offset constant for the nnue part of the formula from 34000 to 34300 because the average pawn count in middlegame positions evaluated during search is around 8. STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 138240 W: 35834 L: 35723 D: 66683 Ptnml(0-2): 527, 16587, 34817, 16626, 563 https://tests.stockfishchess.org/tests/view/6653f474a86388d5e27daaac LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 454272 W: 114787 L: 115012 D: 224473 Ptnml(0-2): 246, 51168, 124553, 50903, 266 https://tests.stockfishchess.org/tests/view/6654f256a86388d5e27db131 closes https://github.com/official-stockfish/Stockfish/pull/5303 Bench: 1279635 --- src/evaluate.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 13a3f211741..849b7bb683d 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -77,12 +77,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, optimism += optimism * nnueComplexity / 470; nnue -= nnue * (nnueComplexity * 5 / 3) / 32621; - int material = 200 * pos.count() + 350 * pos.count() + 400 * pos.count() + int material = 300 * pos.count() + 350 * pos.count() + 400 * pos.count() + 640 * pos.count() + 1200 * pos.count(); - v = (nnue * (34000 + material + 135 * pos.count()) - + optimism * (4400 + material + 99 * pos.count())) - / 35967; + v = (nnue * (34300 + material) + optimism * (4400 + material)) / 35967; // Damp down the evaluation linearly when shuffling v = v * (204 - pos.rule50_count()) / 208; From c14297a483f7905d61e6f22068d33b199916257a Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 27 May 2024 01:21:32 -0700 Subject: [PATCH 0558/1309] Tune Fail Low Bonus Fractional bonus idea is from @Ergodice on [discord](https://discord.com/channels/435943710472011776/735707599353151579/1244039134499180614). Values are tuned for 149k games at LTC. SPSA tune: https://tests.stockfishchess.org/tests/view/6652d5d5a86388d5e27da9d6 Failed STC: LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 67424 W: 17364 L: 17528 D: 32532 Ptnml(0-2): 238, 8043, 17299, 7909, 223 https://tests.stockfishchess.org/tests/view/66551e1ba86388d5e27db9f9 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 146910 W: 37141 L: 36695 D: 73074 Ptnml(0-2): 84, 16201, 40441, 16643, 86 https://tests.stockfishchess.org/tests/view/66559949a86388d5e27dcc5d Passed VLTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 27248 W: 6924 L: 6633 D: 13691 Ptnml(0-2): 5, 2744, 7835, 3035, 5 https://tests.stockfishchess.org/tests/view/66563f4da86388d5e27dd27a closes https://github.com/official-stockfish/Stockfish/pull/5299 Bench: 1390709 --- src/search.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c074e3421ea..425782ebf27 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1341,18 +1341,19 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 4) + (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14144) - + ((ss - 1)->moveCount > 9) + (!ss->inCheck && bestValue <= ss->staticEval - 115) - + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 81); + int bonus = (54 * (depth > 4) + 62 * (depth > 5) + 115 * (PvNode || cutNode) + + 186 * ((ss - 1)->statScore < -14144) + 121 * ((ss - 1)->moveCount > 9) + + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 115) + + 137 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 81)); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - stat_bonus(depth) * bonus); + stat_bonus(depth) * bonus / 100); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << stat_bonus(depth) * bonus / 2; + << stat_bonus(depth) * bonus / 200; 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] - << stat_bonus(depth) * bonus * 4; + << stat_bonus(depth) * bonus / 25; } if (PvNode) From a2f4e988aa03a1011b671af07a152682e35b4617 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 28 May 2024 13:32:09 -0700 Subject: [PATCH 0559/1309] Fix MSVC NUMA compile issues closes https://github.com/official-stockfish/Stockfish/pull/5298 No functional change --- src/misc.h | 4 ++-- src/numa.h | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/misc.h b/src/misc.h index 99cbecfdd2c..ec7f7b76c97 100644 --- a/src/misc.h +++ b/src/misc.h @@ -77,6 +77,8 @@ using AlignedPtr = std::unique_ptr>; template using LargePagePtr = std::unique_ptr>; +#if defined(__linux__) + struct PipeDeleter { void operator()(FILE* file) const { if (file != nullptr) @@ -86,8 +88,6 @@ struct PipeDeleter { } }; -#if defined(__linux__) - inline std::optional get_system_command_output(const std::string& command) { std::unique_ptr pipe(popen(command.c_str(), "r")); if (!pipe) diff --git a/src/numa.h b/src/numa.h index c04292daf01..03ee1fdf116 100644 --- a/src/numa.h +++ b/src/numa.h @@ -51,6 +51,9 @@ static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64; #define NOMINMAX #endif #include + #if defined small + #undef small + #endif // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadselectedcpusetmasks using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT); @@ -561,8 +564,8 @@ class NumaConfig { if (SetThreadSelectedCpuSetMasks_f != nullptr) { // Only available on Windows 11 and Windows Server 2022 onwards. - const USHORT numProcGroups = - ((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE; + const USHORT numProcGroups = USHORT( + ((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE); auto groupAffinities = std::make_unique(numProcGroups); std::memset(groupAffinities.get(), 0, sizeof(GROUP_AFFINITY) * numProcGroups); for (WORD i = 0; i < numProcGroups; ++i) From ae7eef51fde6d74f1a10269dec36bf6d80855a0a Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 28 May 2024 14:31:56 -0700 Subject: [PATCH 0560/1309] Simplify Fail Low Bonus Formula Tested against PR #5299 Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 76352 W: 19797 L: 19619 D: 36936 Ptnml(0-2): 236, 9017, 19509, 9161, 253 https://tests.stockfishchess.org/tests/view/66564f60a86388d5e27dd307 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 114624 W: 28946 L: 28821 D: 56857 Ptnml(0-2): 59, 12675, 31714, 12810, 54 https://tests.stockfishchess.org/tests/view/6656543da86388d5e27dd329 closes https://github.com/official-stockfish/Stockfish/pull/5301 Bench: 1212167 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 425782ebf27..5e9f647636d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1341,7 +1341,7 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (54 * (depth > 4) + 62 * (depth > 5) + 115 * (PvNode || cutNode) + int bonus = (116 * (depth > 5) + 115 * (PvNode || cutNode) + 186 * ((ss - 1)->statScore < -14144) + 121 * ((ss - 1)->moveCount > 9) + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 115) + 137 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 81)); From 3c62ad7e077a5ed0ea7b55422e03e7316dcbce7e Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Tue, 28 May 2024 19:40:40 +0100 Subject: [PATCH 0561/1309] Add compensation factor to adjust extra time according to time control As stockfish nets and search evolve, the existing time control appears to give too little time at STC, roughly correct at LTC, and too little at VLTC+. This change adds an adjustment to the optExtra calculation. This adjustment is easy to retune and refine, so it should be easier to keep up-to-date than the more complex calculations used for optConstant and optScale. Passed STC 10+0.1: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 169568 W: 43803 L: 43295 D: 82470 Ptnml(0-2): 485, 19679, 44055, 19973, 592 https://tests.stockfishchess.org/tests/view/66531865a86388d5e27da9fa Yellow LTC 60+0.6: LLR: -2.94 (-2.94,2.94) <0.50,2.50> Total: 209970 W: 53087 L: 52914 D: 103969 Ptnml(0-2): 91, 19652, 65314, 19849, 79 https://tests.stockfishchess.org/tests/view/6653e38ba86388d5e27daaa0 Passed VLTC 180+1.8 : LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 85618 W: 21735 L: 21342 D: 42541 Ptnml(0-2): 15, 8267, 25848, 8668, 11 https://tests.stockfishchess.org/tests/view/6655131da86388d5e27db95f closes https://github.com/official-stockfish/Stockfish/pull/5297 Bench: 1212167 --- src/search.cpp | 2 +- src/search.h | 1 + src/thread.cpp | 1 + src/timeman.cpp | 14 ++++++++++++-- src/timeman.h | 8 ++++++-- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5e9f647636d..ec4ae79d508 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -161,7 +161,7 @@ void Search::Worker::start_searching() { } main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options, - main_manager()->originalPly); + main_manager()->originalPly, main_manager()->originalTimeAdjust); tt.new_search(); if (rootMoves.empty()) diff --git a/src/search.h b/src/search.h index a61f253c005..7cff10d5590 100644 --- a/src/search.h +++ b/src/search.h @@ -208,6 +208,7 @@ class SearchManager: public ISearchManager { Depth depth) const; Stockfish::TimeManagement tm; + double originalTimeAdjust; int originalPly; int callsCnt; std::atomic_bool ponder; diff --git a/src/thread.cpp b/src/thread.cpp index 5893f4b6d07..71134ead6ea 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -217,6 +217,7 @@ void ThreadPool::clear() { main_manager()->bestPreviousScore = VALUE_INFINITE; main_manager()->bestPreviousAverageScore = VALUE_INFINITE; main_manager()->originalPly = -1; + main_manager()->originalTimeAdjust = -1; main_manager()->previousTimeReduction = 1.0; main_manager()->tm.clear(); } diff --git a/src/timeman.cpp b/src/timeman.cpp index f389e082d8e..f6ca298a82f 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -44,8 +44,12 @@ void TimeManagement::advance_nodes_time(std::int64_t nodes) { // the bounds of time allowed for the current game ply. We currently support: // 1) x basetime (+ z increment) // 2) x moves in y seconds (+ z increment) -void TimeManagement::init( - Search::LimitsType& limits, Color us, int ply, const OptionsMap& options, int& originalPly) { +void TimeManagement::init(Search::LimitsType& limits, + Color us, + int ply, + const OptionsMap& options, + int& originalPly, + double& originalTimeAdjust) { TimePoint npmsec = TimePoint(options["nodestime"]); // If we have no time, we don't need to fully initialize TM. @@ -100,6 +104,10 @@ void TimeManagement::init( TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.2078 + 0.1623 * std::log10(timeLeft); + // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed the actual available // game time for the current move, so also cap to a percentage of available game time. @@ -109,6 +117,7 @@ void TimeManagement::init( double optExtra = scaledInc < 500 ? 1.0 : 1.13; if (ply - originalPly < 2) optExtra *= 0.95; + optExtra *= originalTimeAdjust; // Calculate time constants based on current time left. double logTimeInSec = std::log10(scaledTime / 1000.0); @@ -118,6 +127,7 @@ void TimeManagement::init( optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, 0.213 * limits.time[us] / timeLeft) * optExtra; + maxScale = std::min(6.64, maxConstant + ply / 12.0); } diff --git a/src/timeman.h b/src/timeman.h index 8f1bb56397d..8b763089a70 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -36,8 +36,12 @@ struct LimitsType; // the maximum available time, the game move number, and other parameters. class TimeManagement { public: - void init( - Search::LimitsType& limits, Color us, int ply, const OptionsMap& options, int& originalPly); + void init(Search::LimitsType& limits, + Color us, + int ply, + const OptionsMap& options, + int& originalPly, + double& originalTimeAdjust); TimePoint optimum() const; TimePoint maximum() const; From 4a2291ed337730e5093af1532d36acf1f066989b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 23 May 2024 19:22:41 -0700 Subject: [PATCH 0562/1309] Simplify Away Quadruple Extension Passed non-regression VVLTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 90792 W: 23155 L: 23018 D: 44619 Ptnml(0-2): 6, 8406, 28432, 8549, 3 https://tests.stockfishchess.org/tests/view/664ffa4ca86388d5e27d8e7a Passed non-regression VLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 288136 W: 72608 L: 72659 D: 142869 Ptnml(0-2): 38, 30258, 83525, 30211, 36 https://tests.stockfishchess.org/tests/view/66551609a86388d5e27db9ae closes https://github.com/official-stockfish/Stockfish/pull/5293 bench 1501735 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ec4ae79d508..22e82be8f2b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1066,11 +1066,9 @@ Value Search::Worker::search( { int doubleMargin = 304 * PvNode - 203 * !ttCapture; int tripleMargin = 117 + 259 * PvNode - 296 * !ttCapture + 97 * ss->ttPv; - int quadMargin = 486 + 343 * PvNode - 273 * !ttCapture + 232 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) - + (value < singularBeta - tripleMargin) - + (value < singularBeta - quadMargin); + + (value < singularBeta - tripleMargin); depth += ((!PvNode) && (depth < 16)); } From 5ab3fe6db8ea7dff1310c792d66f2a906a5c19c5 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Tue, 28 May 2024 19:39:55 -0400 Subject: [PATCH 0563/1309] Simplify blending eval with nnue complexity Passed non-regression STC: https://tests.stockfishchess.org/tests/view/66567377a86388d5e27dd89c LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 144000 W: 37443 L: 37338 D: 69219 Ptnml(0-2): 587, 17260, 36208, 17351, 594 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/66567f29a86388d5e27dd924 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 112326 W: 28550 L: 28421 D: 55355 Ptnml(0-2): 66, 12732, 30434, 12869, 62 closes https://github.com/official-stockfish/Stockfish/pull/5305 bench 1554486 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 849b7bb683d..666697dddba 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -75,7 +75,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Blend optimism and eval with nnue complexity optimism += optimism * nnueComplexity / 470; - nnue -= nnue * (nnueComplexity * 5 / 3) / 32621; + nnue -= nnue * nnueComplexity / 20000; int material = 300 * pos.count() + 350 * pos.count() + 400 * pos.count() + 640 * pos.count() + 1200 * pos.count(); From 0ea6337ccfffa39b665e3a8371fcde668dddf4aa Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 30 May 2024 03:36:38 +0300 Subject: [PATCH 0564/1309] Remove Queen threatenedByMinor Remove Queen threatenedByMinor from movepick Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 54432 W: 14053 L: 13855 D: 26524 Ptnml(0-2): 124, 6347, 14090, 6517, 138 https://tests.stockfishchess.org/tests/view/66578d036b0e318cefa8d43d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 198168 W: 49979 L: 49940 D: 98249 Ptnml(0-2): 84, 21824, 55236, 21849, 91 https://tests.stockfishchess.org/tests/view/66579cf86b0e318cefa8d5b1 closes https://github.com/official-stockfish/Stockfish/pull/5306 bench: 1342438 --- src/movepick.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 55f9ca0e802..6c41916cd96 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -197,8 +197,7 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 48150 - + bool(to & threatenedByMinor) * 10650 + m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 : pt == ROOK ? bool(to & threatenedByMinor) * 24335 : pt != PAWN ? bool(to & threatenedByPawn) * 14950 : 0); From 35aff79843658aef55426d5d88be412f54d936b8 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 29 May 2024 21:18:55 -0400 Subject: [PATCH 0565/1309] Update default main net to nn-ddcfb9224cdb.nnue Created by further tuning the spsa-tuned main net `nn-c721dfca8cd3.nnue` with the same methods described in https://github.com/official-stockfish/Stockfish/pull/5254 This net was reached at 61k / 120k spsa games at 70+0.7 th 7: https://tests.stockfishchess.org/tests/view/665639d0a86388d5e27dd259 Passed STC: https://tests.stockfishchess.org/tests/view/6657d44e6b0e318cefa8d771 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 114688 W: 29775 L: 29344 D: 55569 Ptnml(0-2): 274, 13633, 29149, 13964, 324 Passed LTC: https://tests.stockfishchess.org/tests/view/6657e1e46b0e318cefa8d7a6 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 88152 W: 22412 L: 21988 D: 43752 Ptnml(0-2): 56, 9560, 24409, 10006, 45 closes https://github.com/official-stockfish/Stockfish/pull/5308 Bench: 1434678 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 4b3e91acf4c..4fab1a00137 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-c721dfca8cd3.nnue" +#define EvalFileDefaultNameBig "nn-ddcfb9224cdb.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" namespace NNUE { From a4ea183e7839f62665e706c13b508ccce86d5fd6 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Thu, 30 May 2024 09:05:36 +0200 Subject: [PATCH 0566/1309] Tweak and update the WDL model This PR updates the internal WDL model, using data from 2.5M games played by SF-dev (3c62ad7). Note that the normalizing constant has increased from 329 to 368. Changes to the fitting procedure: * the value for --materialMin was increased from 10 to 17: including data with less material leads to less accuracy for larger material count values * the data was filtered to only include single thread LTC games at 60+0.6 * the data was filtered to only include games from master against patches that are (approximatively) within 5 nElo of master For more information and plots of the model see PR#5309 closes https://github.com/official-stockfish/Stockfish/pull/5309 No functional change --- src/uci.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index ab0dae3946e..4b683116a9e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -382,12 +382,12 @@ WinRateParams win_rate_params(const Position& pos) { int material = pos.count() + 3 * pos.count() + 3 * pos.count() + 5 * pos.count() + 9 * pos.count(); - // The fitted model only uses data for material counts in [10, 78], and is anchored at count 58. - double m = std::clamp(material, 10, 78) / 58.0; + // The fitted model only uses data for material counts in [17, 78], and is anchored at count 58. + double m = std::clamp(material, 17, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-150.77043883, 394.96159472, -321.73403766, 406.15850091}; - constexpr double bs[] = {62.33245393, -91.02264855, 45.88486850, 51.63461272}; + constexpr double as[] = {-41.25712052, 121.47473115, -124.46958843, 411.84490997}; + constexpr double bs[] = {84.92998051, -143.66658718, 80.09988253, 49.80869370}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; @@ -428,8 +428,8 @@ std::string UCIEngine::format_score(const Score& s) { // without treatment of mate and similar special scores. int UCIEngine::to_cp(Value v, const Position& pos) { - // In general, the score can be defined via the the WDL as - // (log(1/L - 1) - log(1/W - 1)) / ((log(1/L - 1) + log(1/W - 1)) + // In general, the score can be defined via the WDL as + // (log(1/L - 1) - log(1/W - 1)) / (log(1/L - 1) + log(1/W - 1)). // Based on our win_rate_model, this simply yields v / a. auto [a, b] = win_rate_params(pos); From a77a895c3b7460f86b11a3ddfe3528f5be1276b9 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Thu, 30 May 2024 08:18:04 +0100 Subject: [PATCH 0567/1309] Add extension condition to cutoffCnt Decrease cutoffCnt increment by 1 if extension is 2 or greater. Passed STC: https://tests.stockfishchess.org/tests/view/66577a696b0e318cefa8d34d LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 99200 W: 25703 L: 25297 D: 48200 Ptnml(0-2): 253, 11660, 25390, 12022, 275 Passed LTC: https://tests.stockfishchess.org/tests/view/665787ab6b0e318cefa8d411 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 124530 W: 31659 L: 31161 D: 61710 Ptnml(0-2): 58, 13578, 34489, 14088, 52 closes https://github.com/official-stockfish/Stockfish/pull/5310 bench 1623228 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 22e82be8f2b..d72dbfa1548 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1289,7 +1289,7 @@ Value Search::Worker::search( if (value >= beta) { - ss->cutoffCnt += 1 + !ttMove; + ss->cutoffCnt += 1 + !ttMove - (extension >= 2); assert(value >= beta); // Fail high break; } From d1a71fdaa7cc7d749495bbf5d63919a4a0b42303 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 18 May 2024 17:54:13 +0300 Subject: [PATCH 0568/1309] Functional simplification in the transposition table Passed STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 154848 W: 39838 L: 39750 D: 75260 Ptnml(0-2): 404, 16214, 44087, 16328, 391 https://tests.stockfishchess.org/tests/view/664892b088b8c6a2bbe430fc Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 68172 W: 17296 L: 17137 D: 33739 Ptnml(0-2): 23, 6349, 21185, 6504, 25 https://tests.stockfishchess.org/tests/view/6648aabfa0781149e383e526 closes https://github.com/official-stockfish/Stockfish/pull/5263 Bench: 1623228 --- src/tt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tt.cpp b/src/tt.cpp index 79274f525b9..f95170e9406 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -124,7 +124,7 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster for (int i = 0; i < ClusterSize; ++i) - if (tte[i].key16 == key16 || !tte[i].depth8) + if (tte[i].key16 == key16) return found = bool(tte[i].depth8), &tte[i]; // Find an entry to be replaced according to the replacement strategy From b280d2f06553e8c8d98379fe547f3b995cc56d59 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 30 May 2024 19:27:12 +0300 Subject: [PATCH 0569/1309] Allow tt cutoffs for shallower depths in certain conditions Current master allows tt cutoffs only when depth from tt is strictly greater than current node depth. This patch also allows them when it's equal and if tt value is lower or equal to beta. Passed STC: https://tests.stockfishchess.org/tests/view/66578e2e6b0e318cefa8d447 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 26592 W: 6944 L: 6645 D: 13003 Ptnml(0-2): 67, 3039, 6795, 3318, 77 Passed LTC: https://tests.stockfishchess.org/tests/view/6657f46b6b0e318cefa8d7e9 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 142572 W: 36315 L: 35776 D: 70481 Ptnml(0-2): 70, 15666, 39288, 16179, 83 closes https://github.com/official-stockfish/Stockfish/pull/5314 Bench: 1368486 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d72dbfa1548..638af546d09 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -618,7 +618,7 @@ Value Search::Worker::search( ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); // At non-PV nodes we check for an early TT cutoff - if (!PvNode && !excludedMove && tte->depth() > depth + if (!PvNode && !excludedMove && tte->depth() > depth - (ttValue <= beta) && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) { From 02eae528330347b4c91f3d8fa4de7fc8629a5ac0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 30 May 2024 20:44:21 +0300 Subject: [PATCH 0570/1309] Simplifying the malus for putting piece en prise formula Simplifying the malus for putting piece en prise formula by merging the minor pieces and pawns (removing the pawn exclusion from the formula). Passed STC: https://tests.stockfishchess.org/tests/view/66578d9c6b0e318cefa8d441 LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 314272 W: 80705 L: 80786 D: 152781 Ptnml(0-2): 873, 37577, 80366, 37398, 922 Passed LTC (before rebasing): https://tests.stockfishchess.org/tests/view/6657b5ee6b0e318cefa8d6ab LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 117000 W: 29447 L: 29324 D: 58229 Ptnml(0-2): 47, 12877, 32535, 12988, 53 Passed LTC (also after rebasing): https://tests.stockfishchess.org/tests/view/6658803d6b0e318cefa8fd99 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 244992 W: 61807 L: 61814 D: 121371 Ptnml(0-2): 125, 27420, 67414, 27411, 126 closes https://github.com/official-stockfish/Stockfish/pull/5316 Bench: 1484840 --- src/movepick.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6c41916cd96..b6828a30b72 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -199,8 +199,7 @@ void MovePicker::score() { // malus for putting piece en prise m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 : pt == ROOK ? bool(to & threatenedByMinor) * 24335 - : pt != PAWN ? bool(to & threatenedByPawn) * 14950 - : 0); + : bool(to & threatenedByPawn) * 14900); } else // Type == EVASIONS From 596fb4842bdbb872dae8023a930f1dda8b48cad1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 30 May 2024 19:55:59 +0200 Subject: [PATCH 0571/1309] NUMA: Fix concurrency counting for windows systems If there is more than 1 processor group, std::thread::hardware_concurrency should not be used. fixes #5307 closes https://github.com/official-stockfish/Stockfish/pull/5311 No functional change --- src/misc.cpp | 41 ++++++++++++++++++----------------------- src/numa.h | 25 +++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index d48b75e1c28..a45becf5d99 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -34,16 +34,10 @@ // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. extern "C" { -using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP, - PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, - PDWORD); -using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY); -using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); -using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT); -using fun5_t = WORD (*)(); -using fun6_t = bool (*)(HANDLE, DWORD, PHANDLE); -using fun7_t = bool (*)(LPCSTR, LPCSTR, PLUID); -using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); +using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using AdjustTokenPrivileges_t = + bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); } #endif @@ -488,23 +482,25 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize if (!hAdvapi32) hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); - if (!fun6) + auto OpenProcessToken_f = + OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!OpenProcessToken_f) return nullptr; - auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); - if (!fun7) + auto LookupPrivilegeValueA_f = + LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!LookupPrivilegeValueA_f) return nullptr; - auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); - if (!fun8) + auto AdjustTokenPrivileges_f = + AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!AdjustTokenPrivileges_f) return nullptr; // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!fun6( // OpenProcessToken() + if (!OpenProcessToken_f( // OpenProcessToken() GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) return nullptr; - if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid) - nullptr, "SeLockMemoryPrivilege", &luid)) + if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) { TOKEN_PRIVILEGES tp{}; TOKEN_PRIVILEGES prevTp{}; @@ -516,8 +512,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (fun8( // AdjustTokenPrivileges() - hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) + if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, + &prevTpLen) && GetLastError() == ERROR_SUCCESS) { // Round up size to full pages and allocate @@ -526,8 +522,7 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize PAGE_READWRITE); // Privilege no longer needed, restore previous state - fun8( // AdjustTokenPrivileges () - hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); } } diff --git a/src/numa.h b/src/numa.h index 03ee1fdf116..644f212ee70 100644 --- a/src/numa.h +++ b/src/numa.h @@ -61,6 +61,7 @@ using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT) // https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-setthreadgroupaffinity using SetThreadGroupAffinity_t = BOOL (*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY); +using GetActiveProcessorCount_t = DWORD (*)(WORD); #endif #include "misc.h" @@ -70,8 +71,28 @@ namespace Stockfish { using CpuIndex = size_t; using NumaIndex = size_t; -inline const CpuIndex SYSTEM_THREADS_NB = - std::max(1, std::thread::hardware_concurrency()); +inline CpuIndex get_hardware_concurrency() { + CpuIndex concurrency = std::thread::hardware_concurrency(); + + // Get all processors across all processor groups on windows, since ::hardware_concurrency + // only returns the number of processors in the first group, because only these + // are available to std::thread. +#ifdef _WIN64 + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto GetActiveProcessorCount_f = + GetActiveProcessorCount_t((void (*)()) GetProcAddress(k32, "GetActiveProcessorCount")); + + if (GetActiveProcessorCount_f != nullptr) + { + concurrency = GetActiveProcessorCount_f(ALL_PROCESSOR_GROUPS); + } +#endif + + return concurrency; +} + +inline const CpuIndex SYSTEM_THREADS_NB = std::max(1, get_hardware_concurrency()); + // We want to abstract the purpose of storing the numa node index somewhat. // Whoever is using this does not need to know the specifics of the replication From f1bb4164bf481c44e707751aa8a4bb8da20d4fa1 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Thu, 30 May 2024 12:56:44 +0200 Subject: [PATCH 0572/1309] Fix process' processor affinity determination on Windows. Specialize and privatize NumaConfig::get_process_affinity. Only enable NUMA capability for 64-bit Windows. Following #5307 and some more testing it was determined that the way affinity was being determined on Windows was incorrect, based on incorrect assumptions about GetNumaProcessorNodeEx. This patch fixes the issue by attempting to retrieve the actual process' processor affinity using Windows API. However one issue persists that is not addressable due to limitations of Windows, and will have to be considered a limitation. If affinities were set using SetThreadAffinityMask instead of SetThreadSelectedCpuSetMasks and GetProcessGroupAffinity returns more than 1 group it is NOT POSSIBLE to determine the affinity programmatically on Windows. In such case the implementation assumes no affinites are set and will consider all processors available for execution. closes https://github.com/official-stockfish/Stockfish/pull/5312 No functional change --- src/numa.h | 260 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 167 insertions(+), 93 deletions(-) diff --git a/src/numa.h b/src/numa.h index 644f212ee70..3c9c823aa39 100644 --- a/src/numa.h +++ b/src/numa.h @@ -41,7 +41,7 @@ #define _GNU_SOURCE #endif #include -#elif defined(_WIN32) +#elif defined(_WIN64) // On Windows each processor group can have up to 64 processors. // https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups @@ -61,7 +61,18 @@ using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT) // https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-setthreadgroupaffinity using SetThreadGroupAffinity_t = BOOL (*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY); +// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadselectedcpusetmasks +using GetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT, PUSHORT); + +// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessaffinitymask +using GetProcessAffinityMask_t = BOOL (*)(HANDLE, PDWORD_PTR, PDWORD_PTR); + +// https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-getprocessgroupaffinity +using GetProcessGroupAffinity_t = BOOL (*)(HANDLE, PUSHORT, PUSHORT); + +// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getactiveprocessorcount using GetActiveProcessorCount_t = DWORD (*)(WORD); + #endif #include "misc.h" @@ -115,8 +126,6 @@ class NumaReplicatedAccessToken { // in a way that doesn't require recreating it completely, and it would be complex and expensive // to maintain class invariants. // The CPU (processor) numbers always correspond to the actual numbering used by the system. -// NOTE: the numbering is only valid within the process, as for example on Windows -// every process gets a "virtualized" set of processors that respects the current affinity // The NUMA node numbers MAY NOT correspond to the system's numbering of the NUMA nodes. // In particular, empty nodes may be removed, or the user may create custom nodes. // It is guaranteed that NUMA nodes are NOT empty, i.e. every node exposed by NumaConfig @@ -133,92 +142,21 @@ class NumaConfig { add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1); } - static std::set get_process_affinity() { - std::set cpus; - - // For unsupported systems, or in case of a soft error, we may assume all processors - // are available for use. - [[maybe_unused]] auto set_to_all_cpus = [&]() { - for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) - cpus.insert(c); - }; - -#if defined(__linux__) && !defined(__ANDROID__) - - // cpu_set_t by default holds 1024 entries. This may not be enough soon, - // but there is no easy way to determine how many threads there actually is. - // In this case we just choose a reasonable upper bound. - static constexpr CpuIndex MaxNumCpus = 1024 * 64; - - cpu_set_t* mask = CPU_ALLOC(MaxNumCpus); - if (mask == nullptr) - std::exit(EXIT_FAILURE); - - const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus); - - CPU_ZERO_S(masksize, mask); - - const int status = sched_getaffinity(0, masksize, mask); - - if (status != 0) - { - CPU_FREE(mask); - std::exit(EXIT_FAILURE); - } - - for (CpuIndex c = 0; c < MaxNumCpus; ++c) - if (CPU_ISSET_S(c, masksize, mask)) - cpus.insert(c); - - CPU_FREE(mask); - -#elif defined(_WIN32) - - // Windows is problematic and weird due to multiple ways of setting affinity, processor groups, - // and behaviour changes between versions. It's unclear if we can support this feature - // on Windows in the same way we do on Linux. - // Apparently when affinity is set via either start /affinity or msys2 taskset - // the function GetNumaProcessorNodeEx completely disregards the processors that we do not - // have affinity more. Moreover, the indices are shifted to start from 0, indicating that Windows - // is providing a whole new mapping of processors to this process. This is problematic in some cases - // but it at least allows us to [probably] support this affinity restriction feature by default. - // So overall, Windows appears to "virtualize" a set of processors and processor groups for every - // process. It's unclear if this assignment can change while the process is running. - // std::thread::hardware_concurrency() returns the number of processors that's consistent - // with GetNumaProcessorNodeEx, so we can just add all of them. - - set_to_all_cpus(); - -#else - - // For other systems we assume the process is allowed to execute on all processors. - set_to_all_cpus(); - -#endif - - return cpus; - } - // This function queries the system for the mapping of processors to NUMA nodes. // On Linux we utilize `lscpu` to avoid libnuma. - // On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see - // comment for Windows implementation of get_process_affinity - static NumaConfig from_system(bool respectProcessAffinity = true) { + static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) { NumaConfig cfg = empty(); +#if defined(__linux__) && !defined(__ANDROID__) + std::set allowedCpus; if (respectProcessAffinity) allowedCpus = get_process_affinity(); - else - { - for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) - allowedCpus.insert(c); - } - auto is_cpu_allowed = [&](CpuIndex c) { return allowedCpus.count(c) == 1; }; - -#if defined(__linux__) && !defined(__ANDROID__) + auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) { + return !respectProcessAffinity || allowedCpus.count(c) == 1; + }; // On Linux things are straightforward, since there's no processor groups and // any thread can be scheduled on all processors. @@ -270,7 +208,19 @@ class NumaConfig { cfg.add_cpu_to_node(NumaIndex{0}, c); } -#elif defined(_WIN32) +#elif defined(_WIN64) + + std::optional> allowedCpus; + + if (respectProcessAffinity) + allowedCpus = get_process_affinity(); + + // The affinity can't be determined in all cases on Windows, but we at least guarantee + // that the number of allowed processors is >= number of processors in the affinity mask. + // In case the user is not satisfied they must set the processor numbers explicitly. + auto is_cpu_allowed = [&allowedCpus](CpuIndex c) { + return !allowedCpus.has_value() || allowedCpus->count(c) == 1; + }; // Since Windows 11 and Windows Server 2022 thread affinities can span // processor groups and can be set as such by a new WinAPI function. @@ -292,14 +242,6 @@ class NumaConfig { procnum.Reserved = 0; USHORT nodeNumber; - // When start /affinity or taskset was used to run this process with restricted affinity - // GetNumaProcessorNodeEx will NOT correspond to the system's processor setup, instead - // it appears to follow a completely new processor assignment, made specifically for this process, - // in which processors that this process has affinity for are remapped, and only those are remapped, - // to form a new set of processors. In other words, we can only get processors - // which we have affinity for this way. This means that the behaviour for - // `respectProcessAffinity == false` may be unexpected when affinity is set from outside, - // while the behaviour for `respectProcessAffinity == true` is given by default. const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber); const CpuIndex c = static_cast(procGroup) * WIN_PROCESSOR_GROUP_SIZE + static_cast(number); @@ -347,8 +289,7 @@ class NumaConfig { // Fallback for unsupported systems. for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) - if (is_cpu_allowed(c)) - cfg.add_cpu_to_node(NumaIndex{0}, c); + cfg.add_cpu_to_node(NumaIndex{0}, c); #endif @@ -573,7 +514,7 @@ class NumaConfig { // This is defensive, allowed because this code is not performance critical. sched_yield(); -#elif defined(_WIN32) +#elif defined(_WIN64) // Requires Windows 11. No good way to set thread affinity spanning processor groups before that. HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); @@ -627,9 +568,9 @@ class NumaConfig { // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support GROUP_AFFINITY affinity; std::memset(&affinity, 0, sizeof(GROUP_AFFINITY)); - affinity.Group = static_cast(n); // We use an ordered set so we're guaranteed to get the smallest cpu number here. const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE; + affinity.Group = static_cast(forcedProcGroupIndex); for (CpuIndex c : nodes[n]) { const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; @@ -733,6 +674,139 @@ class NumaConfig { return true; } + + +#if defined(__linux__) && !defined(__ANDROID__) + + static std::set get_process_affinity() { + + std::set cpus; + + // For unsupported systems, or in case of a soft error, we may assume all processors + // are available for use. + [[maybe_unused]] auto set_to_all_cpus = [&]() { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + cpus.insert(c); + }; + + // cpu_set_t by default holds 1024 entries. This may not be enough soon, + // but there is no easy way to determine how many threads there actually is. + // In this case we just choose a reasonable upper bound. + static constexpr CpuIndex MaxNumCpus = 1024 * 64; + + cpu_set_t* mask = CPU_ALLOC(MaxNumCpus); + if (mask == nullptr) + std::exit(EXIT_FAILURE); + + const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus); + + CPU_ZERO_S(masksize, mask); + + const int status = sched_getaffinity(0, masksize, mask); + + if (status != 0) + { + CPU_FREE(mask); + std::exit(EXIT_FAILURE); + } + + for (CpuIndex c = 0; c < MaxNumCpus; ++c) + if (CPU_ISSET_S(c, masksize, mask)) + cpus.insert(c); + + CPU_FREE(mask); + + return cpus; + } + +#elif defined(_WIN64) + + // On Windows there are two ways to set affinity, and therefore 2 ways to get it. + // These are not consistent, so we have to check both. + // In some cases it is actually not possible to determine affinity. + // For example when two different threads have affinity on different processor groups, + // set using SetThreadAffinityMask, we can't retrieve the actual affinities. + // From documentation on GetProcessAffinityMask: + // > If the calling process contains threads in multiple groups, + // > the function returns zero for both affinity masks. + // In such cases we just give up and assume we have affinity for all processors. + // nullopt means no affinity is set, that is, all processors are allowed + static std::optional> get_process_affinity() { + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto GetThreadSelectedCpuSetMasks_f = GetThreadSelectedCpuSetMasks_t( + (void (*)()) GetProcAddress(k32, "GetThreadSelectedCpuSetMasks")); + auto GetProcessAffinityMask_f = + GetProcessAffinityMask_t((void (*)()) GetProcAddress(k32, "GetProcessAffinityMask")); + auto GetProcessGroupAffinity_f = + GetProcessGroupAffinity_t((void (*)()) GetProcAddress(k32, "GetProcessGroupAffinity")); + + if (GetThreadSelectedCpuSetMasks_f != nullptr) + { + std::set cpus; + + USHORT RequiredMaskCount; + GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount); + + // If RequiredMaskCount then these affinities were never set, but it's not consistent + // so GetProcessAffinityMask may still return some affinity. + if (RequiredMaskCount > 0) + { + auto groupAffinities = std::make_unique(RequiredMaskCount); + + GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(), + RequiredMaskCount, &RequiredMaskCount); + + for (USHORT i = 0; i < RequiredMaskCount; ++i) + { + const size_t procGroupIndex = groupAffinities[i].Group; + + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (groupAffinities[i].Mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + } + + return cpus; + } + } + + if (GetProcessAffinityMask_f != nullptr && GetProcessGroupAffinity_f != nullptr) + { + std::set cpus; + + DWORD_PTR proc, sys; + BOOL status = GetProcessAffinityMask_f(GetCurrentProcess(), &proc, &sys); + if (status == 0) + return std::nullopt; + + // We can't determine affinity because it spans processor groups. + if (proc == 0) + return std::nullopt; + + // We are expecting a single group. + USHORT GroupCount = 1; + USHORT GroupArray[1]; + status = GetProcessGroupAffinity_f(GetCurrentProcess(), &GroupCount, GroupArray); + if (status == 0 || GroupCount != 1) + return std::nullopt; + + const size_t procGroupIndex = GroupArray[0]; + + uint64_t mask = static_cast(proc); + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + + return cpus; + } + + return std::nullopt; + } + +#endif }; class NumaReplicationContext; From 86694b5914c63ee5b0f964108cbd7eacca14c93a Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Thu, 30 May 2024 18:18:51 +0200 Subject: [PATCH 0573/1309] Replace std::from_chars with std::stoull the former was not widely supported, requiring newer compiler versions. closes https://github.com/official-stockfish/Stockfish/pull/5313 No functional change --- src/misc.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index a45becf5d99..7a44732944a 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -42,16 +42,15 @@ using AdjustTokenPrivileges_t = #endif #include -#include #include #include #include #include #include +#include #include #include #include -#include #include "types.h" @@ -598,13 +597,10 @@ void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } #endif size_t str_to_size_t(const std::string& s) { - size_t value; - auto result = std::from_chars(s.data(), s.data() + s.size(), value); - - if (result.ec != std::errc()) + unsigned long long value = std::stoull(s); + if (value > std::numeric_limits::max()) std::exit(EXIT_FAILURE); - - return value; + return static_cast(value); } std::string CommandLine::get_binary_directory(std::string argv0) { From c8375c2fbd398f07b8488ae2d1b12fa1251fb69f Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Thu, 30 May 2024 17:22:53 +0200 Subject: [PATCH 0574/1309] On linux use sysfs instead of lscpu Use sysfs (https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node) to determine processor to NUMA node mapping. Avoids problems on some machines with high core count where lscpu was showing high cpu utilization. closes https://github.com/official-stockfish/Stockfish/pull/5315 No functional change --- src/misc.cpp | 13 +++++ src/misc.h | 24 ++++----- src/numa.h | 142 ++++++++++++++++++++++++++++----------------------- 3 files changed, 101 insertions(+), 78 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 7a44732944a..aa22e61f23f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -42,12 +42,14 @@ using AdjustTokenPrivileges_t = #endif #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -603,6 +605,17 @@ size_t str_to_size_t(const std::string& s) { return static_cast(value); } +std::optional read_file_to_string(const std::string& path) { + std::ifstream f(path, std::ios_base::binary); + if (!f) + return std::nullopt; + return std::string(std::istreambuf_iterator(f), std::istreambuf_iterator()); +} + +void remove_whitespace(std::string& s) { + s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return std::isspace(c); }), s.end()); +} + std::string CommandLine::get_binary_directory(std::string argv0) { std::string pathSeparator; diff --git a/src/misc.h b/src/misc.h index ec7f7b76c97..5c0bde44eda 100644 --- a/src/misc.h +++ b/src/misc.h @@ -88,21 +88,12 @@ struct PipeDeleter { } }; -inline std::optional get_system_command_output(const std::string& command) { - std::unique_ptr pipe(popen(command.c_str(), "r")); - if (!pipe) - return std::nullopt; - - std::string result; - char buffer[1024]; - while (fgets(buffer, sizeof(buffer), pipe.get()) != nullptr) - result += buffer; - - return result; -} - #endif +// Reads the file as bytes. +// Returns std::nullopt if the file does not exist. +std::optional read_file_to_string(const std::string& path); + void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); void dbg_stdev_of(int64_t value, int slot = 0); @@ -118,9 +109,12 @@ inline TimePoint now() { } inline std::vector split(const std::string& s, const std::string& delimiter) { - size_t begin = 0; std::vector res; + if (s.empty()) + return res; + + size_t begin = 0; for (;;) { const size_t end = s.find(delimiter, begin); @@ -136,6 +130,8 @@ inline std::vector split(const std::string& s, const std::string& d return res; } +void remove_whitespace(std::string& s); + enum SyncCout { IO_LOCK, IO_UNLOCK diff --git a/src/numa.h b/src/numa.h index 3c9c823aa39..0553309afef 100644 --- a/src/numa.h +++ b/src/numa.h @@ -33,9 +33,8 @@ #include #include -// We support linux very well, but we explicitly do NOT support Android, partially because -// there are potential issues with `lscpu`, `popen` availability, and partially because -// there's no NUMA environments running Android and there probably won't be. +// We support linux very well, but we explicitly do NOT support Android, because there's +// no affected systems, not worth maintaining. #if defined(__linux__) && !defined(__ANDROID__) #if !defined(_GNU_SOURCE) #define _GNU_SOURCE @@ -143,7 +142,9 @@ class NumaConfig { } // This function queries the system for the mapping of processors to NUMA nodes. - // On Linux we utilize `lscpu` to avoid libnuma. + // On Linux we read from standardized kernel sysfs, with a fallback to single NUMA node. + // On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see + // comment for Windows implementation of get_process_affinity static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) { NumaConfig cfg = empty(); @@ -160,48 +161,52 @@ class NumaConfig { // On Linux things are straightforward, since there's no processor groups and // any thread can be scheduled on all processors. - // This command produces output in the following form - // CPU NODE - // 0 0 - // 1 0 - // 2 1 - // 3 1 - // - // On some systems it may use '-' to signify no NUMA node, in which case we assume it's in node 0. - auto lscpuOpt = get_system_command_output("lscpu -e=cpu,node"); - if (lscpuOpt.has_value()) - { - std::istringstream ss(*lscpuOpt); + // We try to gather this information from the sysfs first + // https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node - // skip the list header - ss.ignore(std::numeric_limits::max(), '\n'); + bool useFallback = false; + auto fallback = [&]() { + useFallback = true; + cfg = empty(); + }; - while (true) + // /sys/devices/system/node/online contains information about active NUMA nodes + auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online"); + if (!nodeIdsStr.has_value() || nodeIdsStr->empty()) + { + fallback(); + } + else + { + remove_whitespace(*nodeIdsStr); + for (size_t n : indices_from_shortened_string(*nodeIdsStr)) { - CpuIndex c; - NumaIndex n; - - ss >> c; - - if (!ss) + // /sys/devices/system/node/node.../cpulist + std::string path = + std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist"; + auto cpuIdsStr = read_file_to_string(path); + // Now, we only bail if the file does not exist. Some nodes may be empty, that's fine. + // An empty node still has a file that appears to have some whitespace, so we need + // to handle that. + if (!cpuIdsStr.has_value()) + { + fallback(); break; - - ss >> n; - - if (!ss) + } + else { - ss.clear(); - std::string dummy; - ss >> dummy; - n = 0; + remove_whitespace(*cpuIdsStr); + for (size_t c : indices_from_shortened_string(*cpuIdsStr)) + { + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(n, c); + } } - - if (is_cpu_allowed(c)) - cfg.add_cpu_to_node(n, c); } } - else + + if (useFallback) { for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) if (is_cpu_allowed(c)) @@ -309,38 +314,17 @@ class NumaConfig { NumaIndex n = 0; for (auto&& nodeStr : split(s, ":")) { - bool addedAnyCpuInThisNode = false; - - for (const std::string& cpuStr : split(nodeStr, ",")) + auto indices = indices_from_shortened_string(nodeStr); + if (!indices.empty()) { - if (cpuStr.empty()) - continue; - - auto parts = split(cpuStr, "-"); - if (parts.size() == 1) + for (auto idx : indices) { - const CpuIndex c = CpuIndex{str_to_size_t(parts[0])}; - if (!cfg.add_cpu_to_node(n, c)) + if (!cfg.add_cpu_to_node(n, CpuIndex(idx))) std::exit(EXIT_FAILURE); } - else if (parts.size() == 2) - { - const CpuIndex cfirst = CpuIndex{str_to_size_t(parts[0])}; - const CpuIndex clast = CpuIndex{str_to_size_t(parts[1])}; - if (!cfg.add_cpu_range_to_node(n, cfirst, clast)) - std::exit(EXIT_FAILURE); - } - else - { - std::exit(EXIT_FAILURE); - } - - addedAnyCpuInThisNode = true; - } - - if (addedAnyCpuInThisNode) n += 1; + } } cfg.customAffinity = true; @@ -675,7 +659,6 @@ class NumaConfig { return true; } - #if defined(__linux__) && !defined(__ANDROID__) static std::set get_process_affinity() { @@ -807,6 +790,37 @@ class NumaConfig { } #endif + + static std::vector indices_from_shortened_string(const std::string& s) { + std::vector indices; + + if (s.empty()) + return indices; + + for (const std::string& ss : split(s, ",")) + { + if (ss.empty()) + continue; + + auto parts = split(ss, "-"); + if (parts.size() == 1) + { + const CpuIndex c = CpuIndex{str_to_size_t(parts[0])}; + indices.emplace_back(c); + } + else if (parts.size() == 2) + { + const CpuIndex cfirst = CpuIndex{str_to_size_t(parts[0])}; + const CpuIndex clast = CpuIndex{str_to_size_t(parts[1])}; + for (size_t c = cfirst; c <= clast; ++c) + { + indices.emplace_back(c); + } + } + } + + return indices; + } }; class NumaReplicationContext; From 54e74919d478def20cb103d1e9677a696073c92f Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 30 May 2024 21:42:48 +0200 Subject: [PATCH 0575/1309] Fix cross from Linux to Windows specifies Windows 7 required https://learn.microsoft.com/en-us/cpp/porting/modifying-winver-and-win32-winnt?view=msvc-170 closes https://github.com/official-stockfish/Stockfish/pull/5319 No functional change --- src/numa.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/numa.h b/src/numa.h index 0553309afef..ee84e1cf34d 100644 --- a/src/numa.h +++ b/src/numa.h @@ -42,6 +42,11 @@ #include #elif defined(_WIN64) + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + // On Windows each processor group can have up to 64 processors. // https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64; From de1ae4949daf2c6d36c50e51c132cee808e2ade0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 31 May 2024 04:01:02 +0300 Subject: [PATCH 0576/1309] Tweak first picked move (ttMove) reduction rule Tweak first picked move (ttMove) reduction rule: Instead of always resetting the reduction to 0, we now only do so if the current reduction is less than 2. If the current reduction is 2 or more, we decrease it by 2 instead. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 109504 W: 28340 L: 27919 D: 53245 Ptnml(0-2): 305, 12848, 28028, 13263, 308 https://tests.stockfishchess.org/tests/view/6658c2fa6b0e318cefa900c2 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 130410 W: 33248 L: 32738 D: 64424 Ptnml(0-2): 53, 14139, 36328, 14615, 70 https://tests.stockfishchess.org/tests/view/6658dd8a6b0e318cefa90173 closes https://github.com/official-stockfish/Stockfish/pull/5321 bench: 1224588 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 638af546d09..4086d50f156 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1149,10 +1149,10 @@ Value Search::Worker::search( if ((ss + 1)->cutoffCnt > 3) r++; - // Set reduction to 0 for first picked move (ttMove) (~2 Elo) - // Nullifies all previous reduction adjustments to ttMove and leaves only history to do them + // For first picked move (ttMove) reduce reduction + // but never allow it to go below 0 (~3 Elo) else if (move == ttMove) - r = 0; + r = std::max(0, r - 2); ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From 0ef809ac71702ee496a88f2cf305117511b555b2 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 29 May 2024 13:56:15 -0400 Subject: [PATCH 0577/1309] Quadratic smallnet threshold with re-evaluation The threshold now decreases more quickly as pawn count decreases, using the smallnet more compared to before. Combo of two eval patches: https://tests.stockfishchess.org/tests/view/66576c5f6b0e318cefa8d26e https://tests.stockfishchess.org/tests/view/664ced40830eb9f886616a77 Passed STC: https://tests.stockfishchess.org/tests/view/66588c136b0e318cefa8ff21 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 112608 W: 29336 L: 28908 D: 54364 Ptnml(0-2): 344, 13223, 28718, 13699, 320 Passed LTC: https://tests.stockfishchess.org/tests/view/6658c8786b0e318cefa900f5 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 108288 W: 27493 L: 27026 D: 53769 Ptnml(0-2): 54, 11821, 29930, 12282, 57 closes https://github.com/official-stockfish/Stockfish/pull/5323 bench 1728074 --- src/evaluate.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 666697dddba..35bc9301ab1 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -46,7 +46,8 @@ int Eval::simple_eval(const Position& pos, Color c) { bool Eval::use_smallnet(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 992 + 6 * pos.count(); + int pawnCount = pos.count(); + return std::abs(simpleEval) > 992 + 6 * pawnCount * pawnCount / 16; } // Evaluate is the evaluator for the outer world. It returns a static evaluation @@ -67,7 +68,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && nnue * simpleEval < 0) + if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 250)) { nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); smallNet = false; From b34a690cd4aa6d828ae0f47b427167f4e6392db7 Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Thu, 30 May 2024 21:18:42 +0200 Subject: [PATCH 0578/1309] MCP more after a bad singular search The idea is, that if we have the information that the singular search failed low and therefore produced an upperbound score, we can use the score from singularsearch as approximate upperbound as to what bestValue our non ttMoves will produce. If this value is well below alpha, we assume that all non-ttMoves will score below alpha and therfore can skip more moves. This patch also sets up variables for future patches wanting to use teh singular search result outside of singular extensions, in singularBound and singularValue, meaning further patches using this search result to affect various pruning techniques can be tried. Passed STC: https://tests.stockfishchess.org/tests/view/6658d13e6b0e318cefa90120 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 85632 W: 22112 L: 21725 D: 41795 Ptnml(0-2): 243, 10010, 21947, 10349, 267 Passed LTC: https://tests.stockfishchess.org/tests/view/6658dd356b0e318cefa9016a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 243978 W: 62014 L: 61272 D: 120692 Ptnml(0-2): 128, 26598, 67791, 27348, 124 closes https://github.com/official-stockfish/Stockfish/pull/5325 bench 1397172 --- src/search.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4086d50f156..f738530ade8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -550,11 +550,12 @@ Value Search::Worker::search( Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue, probCutBeta; + Value bestValue, value, ttValue, eval, maxValue, probCutBeta, singularValue; bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; + Bound singularBound; // Step 1. Initialize node Worker* thisThread = this; @@ -923,6 +924,8 @@ Value Search::Worker::search( value = bestValue; moveCountPruning = false; + singularValue = VALUE_INFINITE; + singularBound = BOUND_NONE; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -972,7 +975,9 @@ Value Search::Worker::search( if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - moveCountPruning = moveCount >= futility_move_count(improving, depth); + moveCountPruning = + moveCount >= futility_move_count(improving, depth) + - (singularBound == BOUND_UPPER && singularValue < alpha - 50); // Reduced depth of the next LMR search int lmrDepth = newDepth - r; @@ -1058,8 +1063,9 @@ Value Search::Worker::search( Depth singularDepth = newDepth / 2; ss->excludedMove = move; - value = + value = singularValue = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + singularBound = singularValue >= singularBeta ? BOUND_LOWER : BOUND_UPPER; ss->excludedMove = Move::none(); if (value < singularBeta) From cb4a62311985f685ba6f5457851527a3289073e6 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 27 May 2024 10:40:25 -0400 Subject: [PATCH 0579/1309] Update default smallnet to nn-37f18f62d772.nnue Created by training L1-128 from scratch with: - skipping based on simple eval in the trainer, for compatibility with regular binpacks without requiring pre-filtering all binpacks - minimum simple eval of 950, lower than 1000 previously - usage of some hse-v1 binpacks with minimum simple eval 1000 - addition of hse-v6 binpacks with minimum simple eval 500 - permuting the FT with 10k positions from fishpack32.binpack - torch.compile to speed up smallnet training Training is significantly slower when using non-pre-filtered binpacks due to the increased skipping required. This net was reached at epoch 339. ``` experiment-name: 128--S1-hse-1k-T80-v6-unfilt-less-sf--se-gt950-no-wld-skip training-dataset: /data/: - dfrc99-16tb7p.v2.min.binpack /data/hse-v1/: - leela96-filt-v2.min.high-simple-eval-1k.min-v2.binpack - test60-novdec2021-12tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.min-v2.binpack - test77-nov2021-2tb7p.no-db.min.high-simple-eval-1k.min-v2.binpack - test77-dec2021-16tb7p.no-db.min.high-simple-eval-1k.min-v2.binpack - test77-jan2022-2tb7p.high-simple-eval-1k.min-v2.binpack - test78-jantomay2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.min-v2.binpack - test78-juntosep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.min-v2.binpack - test79-apr2022-16tb7p.min.high-simple-eval-1k.min-v2.binpack - test79-may2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.min-v2.binpack - test80-apr2022-16tb7p.min.high-simple-eval-1k.min-v2.binpack - test80-may2022-16tb7p.high-simple-eval-1k.min-v2.binpack - test80-jun2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.min-v2.binpack - test80-jul2022-16tb7p.v6-dd.min.high-simple-eval-1k.min-v2.binpack - test80-sep2022-16tb7p-filter-v6-dd.min-mar2023.unmin.high-simple-eval-1k.min-v2.binpack - test80-nov2022-16tb7p-v6-dd.min.high-simple-eval-1k.min-v2.binpack /data/S11-mar2024/: - test80-2022-08-aug-16tb7p.v6-dd.min.binpack - test80-2022-10-oct-16tb7p.v6-dd.binpack - test80-2022-12-dec-16tb7p.min.binpack - test80-2023-01-jan-16tb7p.v6-sk20.min.binpack - test80-2023-02-feb-16tb7p.v6-sk20.min.binpack - test80-2023-03-mar-2tb7p.v6-sk16.min.binpack - test80-2023-04-apr-2tb7p.v6-sk16.min.binpack - test80-2023-05-may-2tb7p.v6.min.binpack - test80-2023-06-jun-2tb7p.binpack.min-v2.binpack - test80-2023-07-jul-2tb7p.binpack.min-v2.binpack - test80-2023-08-aug-2tb7p.v6.min.binpack - test80-2023-09-sep-2tb7p.binpack.hse-v6.binpack - test80-2023-10-oct-2tb7p.binpack.hse-v6.binpack - test80-2023-11-nov-2tb7p.binpack.hse-v6.binpack - test80-2023-12-dec-2tb7p.binpack.hse-v6.binpack - test80-2024-01-jan-2tb7p.binpack.hse-v6.binpack - test80-2024-02-feb-2tb7p.binpack.hse-v6.binpack - test80-2024-03-mar-2tb7p.binpack wld-fen-skipping: False nnue-pytorch-branch: linrock/nnue-pytorch/128-skipSimpleEval-lt950-torch-compile engine-test-branch: linrock/Stockfish/L1-128-nolazy engine-base-branch: linrock/Stockfish/L1-128 start-from-engine-test-net: False num-epochs: 500 start-lambda: 1.0 end-lambda: 1.0 ``` Training data can be found at: https://robotmoon.com/nnue-training-data/ Passed STC: https://tests.stockfishchess.org/tests/view/66549c16a86388d5e27daff5 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 196608 W: 51254 L: 50697 D: 94657 Ptnml(0-2): 722, 23244, 49796, 23839, 703 Passed LTC: https://tests.stockfishchess.org/tests/view/6658d1aa6b0e318cefa90122 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 122538 W: 31332 L: 30835 D: 60371 Ptnml(0-2): 69, 13407, 33811, 13922, 60 closes https://github.com/official-stockfish/Stockfish/pull/5333 bench --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 4fab1a00137..bdef9ceb620 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -34,7 +34,7 @@ namespace Eval { // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. #define EvalFileDefaultNameBig "nn-ddcfb9224cdb.nnue" -#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" +#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { struct Networks; From 783dfc2eb235236ff799618436d68d0c1a3f3807 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 1 Jun 2024 20:44:06 +0300 Subject: [PATCH 0580/1309] Adjust return bonus from tt cutoffs at fail highs This is reintroduction of the recently simplified logic - if positive tt cutoff occurs return not a tt value but smth between it and beta. Difference is that instead of static linear combination there we use basically the same formula as we do in the main search - with the only difference being using tt depth instead of depth, which makes a lot of sense. Passed STC: https://tests.stockfishchess.org/tests/view/665b3a34f4a1fd0c208ea870 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 54944 W: 14239 L: 13896 D: 26809 Ptnml(0-2): 151, 6407, 14008, 6760, 146 Passed LTC: https://tests.stockfishchess.org/tests/view/665b520011645bd3d3fac341 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 90540 W: 23070 L: 22640 D: 44830 Ptnml(0-2): 39, 9903, 24965, 10315, 48 closes https://github.com/official-stockfish/Stockfish/pull/5336 bench 1381237 --- src/search.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index f738530ade8..514b7b7d99b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -640,7 +640,12 @@ Value Search::Worker::search( // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) + { + if (ttValue >= beta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + ttValue = (ttValue * tte->depth() + beta) / (tte->depth() + 1); return ttValue; + } } // Step 5. Tablebases probe From b0870cf528ef90e8873719a36a448dafd73e3aee Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 1 Jun 2024 15:13:41 +0200 Subject: [PATCH 0581/1309] Avoid changing bestvalue in the case the ttValue contains mate scores, do not return them as bestValue, since they are not proven. passed STC https://tests.stockfishchess.org/tests/view/665b1ea5586058766677cfa3 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 58912 W: 15319 L: 15130 D: 28463 Ptnml(0-2): 141, 6562, 15854, 6765, 134 passed LTC: https://tests.stockfishchess.org/tests/view/665b2712586058766677cfc4 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 141666 W: 35976 L: 35879 D: 69811 Ptnml(0-2): 61, 15513, 39584, 15618, 57 closes https://github.com/official-stockfish/Stockfish/pull/5335 Bench: 1336115 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 514b7b7d99b..4dc7d3300e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1495,7 +1495,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~13 Elo) - if (ttValue != VALUE_NONE + if (std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttValue; } From ec1cda1d819f534c8d0bfc4624836157bc548eb6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 31 May 2024 22:29:29 +0300 Subject: [PATCH 0582/1309] Simplify histories movepick formula Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 81440 W: 21100 L: 20929 D: 39411 Ptnml(0-2): 248, 9659, 20718, 9864, 231 https://tests.stockfishchess.org/tests/view/6659a8b7ea624d64ea5f3208 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 85758 W: 21763 L: 21607 D: 42388 Ptnml(0-2): 34, 9606, 23463, 9722, 54 https://tests.stockfishchess.org/tests/view/6659d7bff426908fcc6b692c closes https://github.com/official-stockfish/Stockfish/pull/5326 bench: 1280472 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b6828a30b72..d333590751c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -178,7 +178,7 @@ void MovePicker::score() { Square to = m.to_sq(); // histories - m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; + m.value = (*mainHistory)[pos.side_to_move()][m.from_to()]; m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; From 180cab443896a6a37a3c39852ff124ce856987d2 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Sat, 1 Jun 2024 06:11:51 +0900 Subject: [PATCH 0583/1309] Simplify 50 move rule dampening Refactor the logic of 50 move rule dampening by removing a constant. Passed non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 35232 W: 9214 L: 8992 D: 17026 Ptnml(0-2): 114, 4081, 8999, 4313, 109 https://tests.stockfishchess.org/tests/view/665a329013d08af3c1725610 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 38406 W: 9732 L: 9530 D: 19144 Ptnml(0-2): 14, 4132, 10708, 4336, 13 https://tests.stockfishchess.org/tests/view/665a370913d08af3c1725651 https://github.com/official-stockfish/Stockfish/pull/5327 Bench: 1059739 --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 35bc9301ab1..eaf7ab5f981 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -81,10 +81,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int material = 300 * pos.count() + 350 * pos.count() + 400 * pos.count() + 640 * pos.count() + 1200 * pos.count(); - v = (nnue * (34300 + material) + optimism * (4400 + material)) / 35967; + v = (nnue * (34300 + material) + optimism * (4400 + material)) / 36672; // Damp down the evaluation linearly when shuffling - v = v * (204 - pos.rule50_count()) / 208; + v -= v * pos.rule50_count() / 212; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); From b009c43254c3483dd356e28b5b66ba62a724aa1d Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Sat, 1 Jun 2024 17:10:06 +0100 Subject: [PATCH 0584/1309] Simplify tm, removing faster 1st move and 1.13 extraTime. Passed STC 10+0.1 : LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 349760 W: 90112 L: 90231 D: 169417 Ptnml(0-2): 784, 37970, 97496, 37841, 789 https://tests.stockfishchess.org/tests/view/665aeee00223e235f05b7d21 Passed LTC 60+0.6 : LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 140082 W: 35463 L: 35370 D: 69249 Ptnml(0-2): 59, 13492, 42851, 13575, 64 https://tests.stockfishchess.org/tests/view/665b15e78da109e362924e5a closes https://github.com/official-stockfish/Stockfish/pull/5334 No functional change --- src/search.cpp | 3 +-- src/search.h | 1 - src/thread.cpp | 7 ++++--- src/timeman.cpp | 18 ++++-------------- src/timeman.h | 1 - 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4dc7d3300e3..35de756ff27 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -160,8 +160,7 @@ void Search::Worker::start_searching() { return; } - main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options, - main_manager()->originalPly, main_manager()->originalTimeAdjust); + main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options, main_manager()->originalTimeAdjust); tt.new_search(); if (rootMoves.empty()) diff --git a/src/search.h b/src/search.h index 7cff10d5590..01f7b8bdb00 100644 --- a/src/search.h +++ b/src/search.h @@ -209,7 +209,6 @@ class SearchManager: public ISearchManager { Stockfish::TimeManagement tm; double originalTimeAdjust; - int originalPly; int callsCnt; std::atomic_bool ponder; diff --git a/src/thread.cpp b/src/thread.cpp index 71134ead6ea..1b0fffc3572 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -213,12 +213,13 @@ void ThreadPool::clear() { for (auto&& th : threads) th->wait_for_search_finished(); + // These two affect the time taken on the first move of a game: + main_manager()->bestPreviousAverageScore = VALUE_INFINITE; + main_manager()->previousTimeReduction = 0.85; + main_manager()->callsCnt = 0; main_manager()->bestPreviousScore = VALUE_INFINITE; - main_manager()->bestPreviousAverageScore = VALUE_INFINITE; - main_manager()->originalPly = -1; main_manager()->originalTimeAdjust = -1; - main_manager()->previousTimeReduction = 1.0; main_manager()->tm.clear(); } diff --git a/src/timeman.cpp b/src/timeman.cpp index f6ca298a82f..9de70fdc613 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -48,7 +48,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options, - int& originalPly, double& originalTimeAdjust) { TimePoint npmsec = TimePoint(options["nodestime"]); @@ -60,9 +59,6 @@ void TimeManagement::init(Search::LimitsType& limits, if (limits.time[us] == 0) return; - if (originalPly == -1) - originalPly = ply; - TimePoint moveOverhead = TimePoint(options["Move Overhead"]); // optScale is a percentage of available time to use for the current move. @@ -104,20 +100,14 @@ void TimeManagement::init(Search::LimitsType& limits, TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); - // Extra time according to timeLeft - if (originalTimeAdjust < 0) - originalTimeAdjust = 0.2078 + 0.1623 * std::log10(timeLeft); - // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed the actual available // game time for the current move, so also cap to a percentage of available game time. if (limits.movestogo == 0) { - // Use extra time with larger increments - double optExtra = scaledInc < 500 ? 1.0 : 1.13; - if (ply - originalPly < 2) - optExtra *= 0.95; - optExtra *= originalTimeAdjust; + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.3285 * std::log10(timeLeft) - 0.4830; // Calculate time constants based on current time left. double logTimeInSec = std::log10(scaledTime / 1000.0); @@ -126,7 +116,7 @@ void TimeManagement::init(Search::LimitsType& limits, optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, 0.213 * limits.time[us] / timeLeft) - * optExtra; + * originalTimeAdjust; maxScale = std::min(6.64, maxConstant + ply / 12.0); } diff --git a/src/timeman.h b/src/timeman.h index 8b763089a70..10207a8a730 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -40,7 +40,6 @@ class TimeManagement { Color us, int ply, const OptionsMap& options, - int& originalPly, double& originalTimeAdjust); TimePoint optimum() const; From c17d73c554054db8cdc6eb39d667c1dca47d3818 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 1 Jun 2024 11:07:08 -0400 Subject: [PATCH 0585/1309] Simplify statScore divisor into a constant Passed non-regression STC: https://tests.stockfishchess.org/tests/view/665b392ff4a1fd0c208ea864 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 114752 W: 29628 L: 29495 D: 55629 Ptnml(0-2): 293, 13694, 29269, 13827, 293 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/665b588c11645bd3d3fac467 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 65322 W: 16549 L: 16373 D: 32400 Ptnml(0-2): 30, 7146, 18133, 7322, 30 closes https://github.com/official-stockfish/Stockfish/pull/5337 bench 1241443 --- src/numa.h | 2 +- src/search.cpp | 5 +++-- src/thread.cpp | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/numa.h b/src/numa.h index ee84e1cf34d..967e24a66e6 100644 --- a/src/numa.h +++ b/src/numa.h @@ -564,7 +564,7 @@ class NumaConfig { { const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE; const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE; - // We skip processors that are not in the same proccessor group. + // We skip processors that are not in the same processor group. // If everything was set up correctly this will never be an issue, // but we have to account for bad NUMA node specification. if (procGroupIndex != forcedProcGroupIndex) diff --git a/src/search.cpp b/src/search.cpp index 35de756ff27..84ca93f8e59 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -160,7 +160,8 @@ void Search::Worker::start_searching() { return; } - main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options, main_manager()->originalTimeAdjust); + main_manager()->tm.init(limits, rootPos.side_to_move(), rootPos.game_ply(), options, + main_manager()->originalTimeAdjust); tt.new_search(); if (rootMoves.empty()) @@ -1169,7 +1170,7 @@ Value Search::Worker::search( + (*contHist[1])[movedPiece][move.to_sq()] - 5169; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / (12219 - std::min(depth, 13) * 120); + r -= ss->statScore / 11049; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) diff --git a/src/thread.cpp b/src/thread.cpp index 1b0fffc3572..a36c2efb7c1 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -217,9 +217,9 @@ void ThreadPool::clear() { main_manager()->bestPreviousAverageScore = VALUE_INFINITE; main_manager()->previousTimeReduction = 0.85; - main_manager()->callsCnt = 0; - main_manager()->bestPreviousScore = VALUE_INFINITE; - main_manager()->originalTimeAdjust = -1; + main_manager()->callsCnt = 0; + main_manager()->bestPreviousScore = VALUE_INFINITE; + main_manager()->originalTimeAdjust = -1; main_manager()->tm.clear(); } From 8aaae0367cfed7ae5da54d330b65d76d4b1b13ae Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 2 Jun 2024 09:18:19 +0200 Subject: [PATCH 0586/1309] Revert "Adjust return bonus from tt cutoffs at fail highs" This reverts commit 783dfc2eb235236ff799618436d68d0c1a3f3807. could lead to a division by zero for: ttValue = (ttValue * tte->depth() + beta) / (tte->depth() + 1) as other threads can overwrite the tte with a QS depth of -1. closes https://github.com/official-stockfish/Stockfish/pull/5338 Bench: 1280020 --- src/search.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 84ca93f8e59..a2a75af0caf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -640,12 +640,7 @@ Value Search::Worker::search( // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) - { - if (ttValue >= beta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) - ttValue = (ttValue * tte->depth() + beta) / (tte->depth() + 1); return ttValue; - } } // Step 5. Tablebases probe From a2a7edf4c8fa145667135bf1bc7f4f67016f7608 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Sun, 2 Jun 2024 20:39:25 +0200 Subject: [PATCH 0587/1309] Fix GetProcessGroupAffinity call `GetProcessGroupAffinity` appears to require 4 byte alignment for `GroupArray` memory. See https://stackoverflow.com/q/78567676 for further information closes https://github.com/official-stockfish/Stockfish/pull/5340 No functional change --- src/numa.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/numa.h b/src/numa.h index 967e24a66e6..5934a0cd8dd 100644 --- a/src/numa.h +++ b/src/numa.h @@ -773,8 +773,8 @@ class NumaConfig { return std::nullopt; // We are expecting a single group. - USHORT GroupCount = 1; - USHORT GroupArray[1]; + USHORT GroupCount = 1; + alignas(4) USHORT GroupArray[1]; status = GetProcessGroupAffinity_f(GetCurrentProcess(), &GroupCount, GroupArray); if (status == 0 || GroupCount != 1) return std::nullopt; From 00a28ae325688346e63a452b2050bd1491085359 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 31 May 2024 10:53:10 +0200 Subject: [PATCH 0588/1309] Add helpers for managing aligned memory Previously, we had two type aliases, LargePagePtr and AlignedPtr, which required manually initializing the aligned memory for the pointer. The new helpers: - make_unique_aligned - make_unique_large_page are now available for allocating aligned memory (with large pages). They behave similarly to std::make_unique, ensuring objects allocated with these functions follow RAII. The old approach had issues with initializing non-trivial types or arrays of objects. The evaluation function of the network is now a unique pointer to an array instead of an array of unique pointers. Memory related functions have been moved into memory.h Passed High Hash Pressure Test Non-Regression STC: https://tests.stockfishchess.org/tests/view/665b2b36586058766677cfd2 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 476992 W: 122426 L: 122677 D: 231889 Ptnml(0-2): 1145, 51027, 134419, 50744, 1161 Failed Normal Non-Regression STC: https://tests.stockfishchess.org/tests/view/665b2997586058766677cfc8 LLR: -2.94 (-2.94,2.94) <-1.75,0.25> Total: 877312 W: 225233 L: 226395 D: 425684 Ptnml(0-2): 2110, 94642, 246239, 93630, 2035 Probably a fluke since there shouldn't be a real slowndown and it has also passed the high hash pressure test. closes https://github.com/official-stockfish/Stockfish/pull/5332 No functional change --- src/Makefile | 8 +- src/memory.cpp | 229 +++++++++++++++++++++++++++++++++++++++++++ src/memory.h | 215 ++++++++++++++++++++++++++++++++++++++++ src/misc.cpp | 199 +------------------------------------ src/misc.h | 48 +-------- src/nnue/network.cpp | 77 +++++---------- src/nnue/network.h | 6 +- src/numa.h | 1 + src/thread.h | 4 +- src/tt.cpp | 7 +- src/tt.h | 10 +- 11 files changed, 492 insertions(+), 312 deletions(-) create mode 100644 src/memory.cpp create mode 100644 src/memory.h diff --git a/src/Makefile b/src/Makefile index 5119b615f6b..29c4f879dfb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,7 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp memory.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ @@ -63,7 +63,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h OBJS = $(notdir $(SRCS:.cpp=.o)) @@ -489,8 +489,8 @@ ifeq ($(COMP),clang) endif ifeq ($(KERNEL),Darwin) - CXXFLAGS += -mmacosx-version-min=10.14 - LDFLAGS += -mmacosx-version-min=10.14 + CXXFLAGS += -mmacosx-version-min=10.15 + LDFLAGS += -mmacosx-version-min=10.15 ifneq ($(arch),any) CXXFLAGS += -arch $(arch) LDFLAGS += -arch $(arch) diff --git a/src/memory.cpp b/src/memory.cpp new file mode 100644 index 00000000000..565b39b2061 --- /dev/null +++ b/src/memory.cpp @@ -0,0 +1,229 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "memory.h" + +#include + +#if __has_include("features.h") + #include +#endif + +#if defined(__linux__) && !defined(__ANDROID__) + #include +#endif + +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ + || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ + || defined(__e2k__) + #define POSIXALIGNEDALLOC + #include +#endif + +#ifdef _WIN32 + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + + #ifndef NOMINMAX + #define NOMINMAX + #endif + + #include // std::hex, std::dec + #include // std::cerr + #include // std::endl + #include +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. +extern "C" { +using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); +using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using AdjustTokenPrivileges_t = + bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +} +#endif + + +namespace Stockfish { + +// Wrapper for systems where the c++17 implementation +// does not guarantee the availability of aligned_alloc(). Memory allocated with +// std_aligned_alloc() must be freed with std_aligned_free(). +void* std_aligned_alloc(size_t alignment, size_t size) { + // Apple requires 10.15, which is enforced in the makefile +#if defined(_ISOC11_SOURCE) || defined(__APPLE__) + return aligned_alloc(alignment, size); +#elif defined(POSIXALIGNEDALLOC) + void* mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + return _mm_malloc(size, alignment); +#elif defined(_WIN32) + return _aligned_malloc(size, alignment); +#else + return std::aligned_alloc(alignment, size); +#endif +} + +void std_aligned_free(void* ptr) { + +#if defined(POSIXALIGNEDALLOC) + free(ptr); +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + _mm_free(ptr); +#elif defined(_WIN32) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. + +#if defined(_WIN32) + +static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { + + #if !defined(_WIN64) + return nullptr; + #else + + HANDLE hProcessToken{}; + LUID luid{}; + void* mem = nullptr; + + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; + + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + auto OpenProcessToken_f = + OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!OpenProcessToken_f) + return nullptr; + auto LookupPrivilegeValueA_f = + LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!LookupPrivilegeValueA_f) + return nullptr; + auto AdjustTokenPrivileges_f = + AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!AdjustTokenPrivileges_f) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process + if (!OpenProcessToken_f( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; + + if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) + { + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, + // we still need to query GetLastError() to ensure that the privileges were actually obtained. + if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, + &prevTpLen) + && GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); + + // Privilege no longer needed, restore previous state + AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + } + } + + CloseHandle(hProcessToken); + + return mem; + + #endif +} + +void* aligned_large_pages_alloc(size_t allocSize) { + + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); + + // Fall back to regular, page-aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + return mem; +} + +#else + +void* aligned_large_pages_alloc(size_t allocSize) { + + #if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size + #else + constexpr size_t alignment = 4096; // assumed small page size + #endif + + // Round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void* mem = std_aligned_alloc(alignment, size); + #if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); + #endif + return mem; +} + +#endif + + +// aligned_large_pages_free() will free the previously allocated ttmem + +#if defined(_WIN32) + +void aligned_large_pages_free(void* mem) { + + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } +} + +#else + +void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } + +#endif +} // namespace Stockfish diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 00000000000..ad7ca6025b3 --- /dev/null +++ b/src/memory.h @@ -0,0 +1,215 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MEMORY_H_INCLUDED +#define MEMORY_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); +// memory aligned by page size, min alignment: 4096 bytes +void* aligned_large_pages_alloc(size_t size); +// nop if mem == nullptr +void aligned_large_pages_free(void* mem); + +// frees memory which was placed there with placement new. +// works for both single objects and arrays of unknown bound +template +void memory_deleter(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + // Explicitly needed to call the destructor + if constexpr (!std::is_trivially_destructible_v) + ptr->~T(); + + free_func(ptr); + return; +} + +// frees memory which was placed there with placement new. +// works for both single objects and arrays of unknown bound +template +void memory_deleter_array(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + + // Move back on the pointer to where the size is allocated. + const size_t array_offset = std::max(sizeof(size_t), alignof(T)); + char* raw_memory = reinterpret_cast(ptr) - array_offset; + + if constexpr (!std::is_trivially_destructible_v) + { + const size_t size = *reinterpret_cast(raw_memory); + + // Explicitly call the destructor for each element in reverse order + for (size_t i = size; i-- > 0;) + ptr[i].~T(); + } + + free_func(raw_memory); +} + +// Allocates memory for a single object and places it there with placement new. +template +inline std::enable_if_t, T*> memory_allocator(ALLOC_FUNC alloc_func, + Args&&... args) { + void* raw_memory = alloc_func(sizeof(T)); + ASSERT_ALIGNED(raw_memory, alignof(T)); + return new (raw_memory) T(std::forward(args)...); +} + +// Allocates memory for an array of unknown bound and places it there with placement new. +template +inline std::enable_if_t, std::remove_extent_t*> +memory_allocator(ALLOC_FUNC alloc_func, size_t num) { + using ElementType = std::remove_extent_t; + + const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType)); + + // save the array size in the memory location + char* raw_memory = + reinterpret_cast(alloc_func(array_offset + num * sizeof(ElementType))); + ASSERT_ALIGNED(raw_memory, alignof(T)); + + new (raw_memory) size_t(num); + + for (size_t i = 0; i < num; ++i) + new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType(); + + // Need to return the pointer at the start of the array so that the indexing in unique_ptr works + return reinterpret_cast(raw_memory + array_offset); +} + +// +// +// aligned large page unique ptr +// +// + +template +struct LargePageDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, aligned_large_pages_free); } +}; + +template +struct LargePageArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, aligned_large_pages_free); } +}; + +template +using LargePagePtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_large_page for single objects +template +std::enable_if_t, LargePagePtr> make_unique_large_page(Args&&... args) { + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + + T* obj = memory_allocator(aligned_large_pages_alloc, std::forward(args)...); + + return LargePagePtr(obj); +} + +// make_unique_large_page for arrays of unknown bound +template +std::enable_if_t, LargePagePtr> make_unique_large_page(size_t num) { + using ElementType = std::remove_extent_t; + + static_assert(alignof(ElementType) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + + ElementType* memory = memory_allocator(aligned_large_pages_alloc, num); + + return LargePagePtr(memory); +} + +// +// +// aligned unique ptr +// +// + +template +struct AlignedDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, std_aligned_free); } +}; + +template +struct AlignedArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, std_aligned_free); } +}; + +template +using AlignedPtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_aligned for single objects +template +std::enable_if_t, AlignedPtr> make_unique_aligned(Args&&... args) { + const auto func = [](size_t size) { return std_aligned_alloc(alignof(T), size); }; + T* obj = memory_allocator(func, std::forward(args)...); + + return AlignedPtr(obj); +} + +// make_unique_aligned for arrays of unknown bound +template +std::enable_if_t, AlignedPtr> make_unique_aligned(size_t num) { + using ElementType = std::remove_extent_t; + + const auto func = [](size_t size) { return std_aligned_alloc(alignof(ElementType), size); }; + ElementType* memory = memory_allocator(func, num); + + return AlignedPtr(memory); +} + + +// Get the first aligned element of an array. +// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, +// where N is the number of elements in the array. +template +T* align_ptr_up(T* ptr) { + static_assert(alignof(T) < Alignment); + + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast( + reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); +} + + +} // namespace Stockfish + +#endif // #ifndef MEMORY_H_INCLUDED diff --git a/src/misc.cpp b/src/misc.cpp index aa22e61f23f..a8bb46ec3c1 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -18,29 +18,6 @@ #include "misc.h" -#ifdef _WIN32 - #if _WIN32_WINNT < 0x0601 - #undef _WIN32_WINNT - #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes - #endif - - #ifndef NOMINMAX - #define NOMINMAX - #endif - - #include -// The needed Windows API for processor groups could be missed from old Windows -// versions, so instead of calling them directly (forcing the linker to resolve -// the calls at compile time), try to load them at runtime. To do this we need -// first to define the corresponding function pointers. -extern "C" { -using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); -using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); -using AdjustTokenPrivileges_t = - bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); -} -#endif - #include #include #include @@ -48,25 +25,14 @@ using AdjustTokenPrivileges_t = #include #include #include -#include #include +#include #include #include #include #include "types.h" -#if defined(__linux__) && !defined(__ANDROID__) - #include -#endif - -#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ - || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ - || defined(__e2k__) - #define POSIXALIGNEDALLOC - #include -#endif - namespace Stockfish { namespace { @@ -427,169 +393,6 @@ void prefetch(const void* addr) { #endif - -// Wrapper for systems where the c++17 implementation -// does not guarantee the availability of aligned_alloc(). Memory allocated with -// std_aligned_alloc() must be freed with std_aligned_free(). -void* std_aligned_alloc(size_t alignment, size_t size) { - -#if defined(POSIXALIGNEDALLOC) - void* mem; - return posix_memalign(&mem, alignment, size) ? nullptr : mem; -#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - return _mm_malloc(size, alignment); -#elif defined(_WIN32) - return _aligned_malloc(size, alignment); -#else - return std::aligned_alloc(alignment, size); -#endif -} - -void std_aligned_free(void* ptr) { - -#if defined(POSIXALIGNEDALLOC) - free(ptr); -#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) - _mm_free(ptr); -#elif defined(_WIN32) - _aligned_free(ptr); -#else - free(ptr); -#endif -} - -// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. - -#if defined(_WIN32) - -static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { - - #if !defined(_WIN64) - return nullptr; - #else - - HANDLE hProcessToken{}; - LUID luid{}; - void* mem = nullptr; - - const size_t largePageSize = GetLargePageMinimum(); - if (!largePageSize) - return nullptr; - - // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - - HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); - - if (!hAdvapi32) - hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - - auto OpenProcessToken_f = - OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); - if (!OpenProcessToken_f) - return nullptr; - auto LookupPrivilegeValueA_f = - LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); - if (!LookupPrivilegeValueA_f) - return nullptr; - auto AdjustTokenPrivileges_f = - AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); - if (!AdjustTokenPrivileges_f) - return nullptr; - - // We need SeLockMemoryPrivilege, so try to enable it for the process - if (!OpenProcessToken_f( // OpenProcessToken() - GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; - - if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) - { - TOKEN_PRIVILEGES tp{}; - TOKEN_PRIVILEGES prevTp{}; - DWORD prevTpLen = 0; - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, - // we still need to query GetLastError() to ensure that the privileges were actually obtained. - if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, - &prevTpLen) - && GetLastError() == ERROR_SUCCESS) - { - // Round up size to full pages and allocate - allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); - mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, - PAGE_READWRITE); - - // Privilege no longer needed, restore previous state - AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); - } - } - - CloseHandle(hProcessToken); - - return mem; - - #endif -} - -void* aligned_large_pages_alloc(size_t allocSize) { - - // Try to allocate large pages - void* mem = aligned_large_pages_alloc_windows(allocSize); - - // Fall back to regular, page-aligned, allocation if necessary - if (!mem) - mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); - - return mem; -} - -#else - -void* aligned_large_pages_alloc(size_t allocSize) { - - #if defined(__linux__) - constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size - #else - constexpr size_t alignment = 4096; // assumed small page size - #endif - - // Round up to multiples of alignment - size_t size = ((allocSize + alignment - 1) / alignment) * alignment; - void* mem = std_aligned_alloc(alignment, size); - #if defined(MADV_HUGEPAGE) - madvise(mem, size, MADV_HUGEPAGE); - #endif - return mem; -} - -#endif - - -// aligned_large_pages_free() will free the previously allocated ttmem - -#if defined(_WIN32) - -void aligned_large_pages_free(void* mem) { - - if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) - { - DWORD err = GetLastError(); - std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err - << std::dec << std::endl; - exit(EXIT_FAILURE); - } -} - -#else - -void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } - -#endif - - #ifdef _WIN32 #include #define GETCWD _getcwd diff --git a/src/misc.h b/src/misc.h index 5c0bde44eda..557a4d8c5a4 100644 --- a/src/misc.h +++ b/src/misc.h @@ -26,10 +26,9 @@ #include #include #include -#include +#include #include #include -#include #define stringify2(x) #x #define stringify(x) stringify2(x) @@ -44,39 +43,10 @@ std::string compiler_info(); // which can be quite slow. void prefetch(const void* addr); -void start_logger(const std::string& fname); -void* std_aligned_alloc(size_t alignment, size_t size); -void std_aligned_free(void* ptr); -// memory aligned by page size, min alignment: 4096 bytes -void* aligned_large_pages_alloc(size_t size); -// nop if mem == nullptr -void aligned_large_pages_free(void* mem); +void start_logger(const std::string& fname); size_t str_to_size_t(const std::string& s); -// Deleter for automating release of memory area -template -struct AlignedDeleter { - void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); - } -}; - -template -struct LargePageDeleter { - void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); - } -}; - -template -using AlignedPtr = std::unique_ptr>; - -template -using LargePagePtr = std::unique_ptr>; - #if defined(__linux__) struct PipeDeleter { @@ -141,20 +111,6 @@ std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK #define sync_endl std::endl << IO_UNLOCK - -// Get the first aligned element of an array. -// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, -// where N is the number of elements in the array. -template -T* align_ptr_up(T* ptr) { - static_assert(alignof(T) < Alignment); - - const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); - return reinterpret_cast( - reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); -} - - // True if and only if the binary is compiled on a little-endian machine static inline const union { uint32_t i; diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index db864fcd384..71c384ffc72 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include #include @@ -30,6 +29,7 @@ #include "../evaluate.h" #include "../incbin/incbin.h" +#include "../memory.h" #include "../misc.h" #include "../position.h" #include "../types.h" @@ -86,23 +86,6 @@ namespace Stockfish::Eval::NNUE { namespace Detail { -// Initialize the evaluation function parameters -template -void initialize(AlignedPtr& pointer) { - - pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - -template -void initialize(LargePagePtr& pointer) { - - static_assert(alignof(T) <= 4096, - "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); - pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - // Read evaluation function parameters template bool read_parameters(std::istream& stream, T& reference) { @@ -128,19 +111,17 @@ template Network::Network(const Network& other) : evalFile(other.evalFile), embeddedType(other.embeddedType) { + if (other.featureTransformer) - { - Detail::initialize(featureTransformer); - *featureTransformer = *other.featureTransformer; - } + featureTransformer = make_unique_large_page(*other.featureTransformer); + + network = make_unique_aligned(LayerStacks); + + if (!other.network) + return; + for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (other.network[i]) - { - Detail::initialize(network[i]); - *(network[i]) = *(other.network[i]); - } - } + network[i] = other.network[i]; } template @@ -150,18 +131,15 @@ Network::operator=(const Network& other) { embeddedType = other.embeddedType; if (other.featureTransformer) - { - Detail::initialize(featureTransformer); - *featureTransformer = *other.featureTransformer; - } + featureTransformer = make_unique_large_page(*other.featureTransformer); + + network = make_unique_aligned(LayerStacks); + + if (!other.network) + return *this; + for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (other.network[i]) - { - Detail::initialize(network[i]); - *(network[i]) = *(other.network[i]); - } - } + network[i] = other.network[i]; return *this; } @@ -253,7 +231,7 @@ Value Network::evaluate(const Position& const int bucket = (pos.count() - 1) / 4; const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + const auto positional = network[bucket].propagate(transformedFeatures); if (complexity) *complexity = std::abs(psqt - positional) / OutputScale; @@ -292,11 +270,11 @@ void Network::verify(std::string evalfilePath) const { exit(EXIT_FAILURE); } - size_t size = sizeof(*featureTransformer) + sizeof(*network) * LayerStacks; + size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks; sync_cout << "info string NNUE evaluation using " << evalfilePath << " (" << size / (1024 * 1024) << "MiB, (" << featureTransformer->InputDimensions << ", " - << network[0]->TransformedFeatureDimensions << ", " << network[0]->FC_0_OUTPUTS - << ", " << network[0]->FC_1_OUTPUTS << ", 1))" << sync_endl; + << network[0].TransformedFeatureDimensions << ", " << network[0].FC_0_OUTPUTS << ", " + << network[0].FC_1_OUTPUTS << ", 1))" << sync_endl; } @@ -333,7 +311,7 @@ Network::trace_evaluate(const Position& { const auto materialist = featureTransformer->transform(pos, cache, transformedFeatures, bucket); - const auto positional = network[bucket]->propagate(transformedFeatures); + const auto positional = network[bucket].propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); t.positional[bucket] = static_cast(positional / OutputScale); @@ -386,9 +364,8 @@ void Network::load_internal() { template void Network::initialize() { - Detail::initialize(featureTransformer); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(network[i]); + featureTransformer = make_unique_large_page(); + network = make_unique_aligned(LayerStacks); } @@ -455,7 +432,7 @@ bool Network::read_parameters(std::istream& stream, return false; for (std::size_t i = 0; i < LayerStacks; ++i) { - if (!Detail::read_parameters(stream, *(network[i]))) + if (!Detail::read_parameters(stream, network[i])) return false; } return stream && stream.peek() == std::ios::traits_type::eof(); @@ -471,7 +448,7 @@ bool Network::write_parameters(std::ostream& stream, return false; for (std::size_t i = 0; i < LayerStacks; ++i) { - if (!Detail::write_parameters(stream, *(network[i]))) + if (!Detail::write_parameters(stream, network[i])) return false; } return bool(stream); diff --git a/src/nnue/network.h b/src/nnue/network.h index f0ccfafcb4c..6ba3cfbab8d 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -25,13 +25,13 @@ #include #include -#include "../misc.h" +#include "../memory.h" #include "../position.h" #include "../types.h" +#include "nnue_accumulator.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" -#include "nnue_accumulator.h" namespace Stockfish::Eval::NNUE { @@ -91,7 +91,7 @@ class Network { LargePagePtr featureTransformer; // Evaluation function - AlignedPtr network[LayerStacks]; + AlignedPtr network; EvalFile evalFile; EmbeddedNNUEType embeddedType; diff --git a/src/numa.h b/src/numa.h index 5934a0cd8dd..a56d7142d9d 100644 --- a/src/numa.h +++ b/src/numa.h @@ -32,6 +32,7 @@ #include #include #include +#include // We support linux very well, but we explicitly do NOT support Android, because there's // no affected systems, not worth maintaining. diff --git a/src/thread.h b/src/thread.h index 102b229907b..7416271b4c1 100644 --- a/src/thread.h +++ b/src/thread.h @@ -23,15 +23,15 @@ #include #include #include +#include #include #include #include -#include +#include "numa.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" -#include "numa.h" namespace Stockfish { diff --git a/src/tt.cpp b/src/tt.cpp index f95170e9406..f808106a6e1 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -24,7 +24,7 @@ #include #include -#include "misc.h" +#include "memory.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -75,11 +75,10 @@ uint8_t TTEntry::relative_age(const uint8_t generation8) const { // measured in megabytes. Transposition table consists // of clusters and each cluster consists of ClusterSize number of TTEntry. void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) { - aligned_large_pages_free(table); - clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); - table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); + table = make_unique_large_page(clusterCount); + if (!table) { std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl; diff --git a/src/tt.h b/src/tt.h index 3b09ec4e1d9..2dcfdd44b9d 100644 --- a/src/tt.h +++ b/src/tt.h @@ -21,7 +21,9 @@ #include #include +#include +#include "memory.h" #include "misc.h" #include "types.h" @@ -94,8 +96,6 @@ class TranspositionTable { static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; public: - ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { // increment by delta to keep lower bits as is generation8 += GENERATION_DELTA; @@ -115,9 +115,9 @@ class TranspositionTable { private: friend struct TTEntry; - size_t clusterCount; - Cluster* table = nullptr; - uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 + size_t clusterCount; + LargePagePtr table; + uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 }; } // namespace Stockfish From 3d6756769cd159edf1d7eaec074c880551590c32 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 2 Jun 2024 18:40:32 +0300 Subject: [PATCH 0589/1309] Simplify continuation histories Functional simplification. Simplify continuation histories, therefore increasing the effect of stats updates and movepicker bonuses for continuation history 3 plies deep. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 229184 W: 59087 L: 59080 D: 111017 Ptnml(0-2): 554, 27248, 59002, 27213, 575 https://tests.stockfishchess.org/tests/view/665c7a09fd45fb0f907c223b Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 44532 W: 11419 L: 11223 D: 21890 Ptnml(0-2): 18, 4787, 12457, 4989, 15 https://tests.stockfishchess.org/tests/view/665c8842fd45fb0f907c23ec closes https://github.com/official-stockfish/Stockfish/pull/5339 Bench: 1326444 --- src/movepick.cpp | 2 +- src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index d333590751c..52e8c526a10 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -182,7 +182,7 @@ void MovePicker::score() { m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += 2 * (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; - m.value += (*continuationHistory[2])[pc][to] / 4; + m.value += (*continuationHistory[2])[pc][to] / 3; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; diff --git a/src/search.cpp b/src/search.cpp index a2a75af0caf..44da868366a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1811,7 +1811,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { if (ss->inCheck && i > 2) break; if (((ss - i)->currentMove).is_ok()) - (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3)); + (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + (i == 3)); } } From 924a843594743297f47edf7b0931ede8dcbb6dd8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 2 Jun 2024 23:32:58 +0300 Subject: [PATCH 0590/1309] Simplify recapture extension Simplifying the extension formula by removing the move == ttMove condition. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 47328 W: 12324 L: 12117 D: 22887 Ptnml(0-2): 134, 5532, 12097, 5795, 106 https://tests.stockfishchess.org/tests/view/665ca5e6fd45fb0f907c41be Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 72126 W: 18378 L: 18209 D: 35539 Ptnml(0-2): 36, 7841, 20130, 8030, 26 https://tests.stockfishchess.org/tests/view/665cb276fd45fb0f907c41f9 closes https://github.com/official-stockfish/Stockfish/pull/5341 Bench: 1399468 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 44da868366a..4defbadb40b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1103,7 +1103,7 @@ Value Search::Worker::search( } // Extension for capturing the previous moved piece (~0 Elo on STC, ~1 Elo on LTC) - else if (PvNode && move == ttMove && move.to_sq() == prevSq + else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] > 3988) From fe298953f89a86e7edfb0e53605d9d9c47f7ceea Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Sun, 2 Jun 2024 05:26:34 +0700 Subject: [PATCH 0591/1309] Simplify smallnet threshold Turns the quadratic threshold to a linear one STC non-reg: https://tests.stockfishchess.org/tests/view/665ba0b744e8416a9cdc188d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 330432 W: 85351 L: 85454 D: 159627 Ptnml(0-2): 888, 39643, 84283, 39488, 914 LTC non-reg: https://tests.stockfishchess.org/tests/view/665cd60ffd45fb0f907c4306 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 139146 W: 35194 L: 35093 D: 68859 Ptnml(0-2): 58, 15523, 38313, 15618, 61 closes https://github.com/official-stockfish/Stockfish/pull/5342 Bench: 1057383 --- src/evaluate.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index eaf7ab5f981..064ea027baa 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -46,8 +46,7 @@ int Eval::simple_eval(const Position& pos, Color c) { bool Eval::use_smallnet(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - int pawnCount = pos.count(); - return std::abs(simpleEval) > 992 + 6 * pawnCount * pawnCount / 16; + return std::abs(simpleEval) > 992 + 10 * pos.count(); } // Evaluate is the evaluator for the outer world. It returns a static evaluation From 397f47a7a1b7abe490d7bcb7a526d01555aed2be Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sun, 2 Jun 2024 16:27:58 -0500 Subject: [PATCH 0592/1309] Adjust lowest depth constants to the natural place Passed STC: https://tests.stockfishchess.org/tests/view/665ce3f8fd45fb0f907c537f LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 282784 W: 73032 L: 73082 D: 136670 Ptnml(0-2): 680, 31845, 76364, 31851, 652 Recently when I overhauled these comments, Disservin asked why these were so much lower: they're a relic from when we had a third QS stage at -5. Now we don't, so fix these to the obvious place. I was fairly sure it was nonfunctional but ran the nonreg to be double sure. closes https://github.com/official-stockfish/Stockfish/pull/5343 Bench: 1057383 --- src/types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.h b/src/types.h index aa4af012b12..10ad1fac9ef 100644 --- a/src/types.h +++ b/src/types.h @@ -196,8 +196,8 @@ enum : int { // For TT entries where no searching at all was done (whether regular or qsearch) we use // _UNSEARCHED, which should thus compare lower than any QS or regular depth. _ENTRY_OFFSET is used // only for the TT entry occupancy check (see tt.cpp), and should thus be lower than _UNSEARCHED. - DEPTH_UNSEARCHED = -6, - DEPTH_ENTRY_OFFSET = -7 + DEPTH_UNSEARCHED = -2, + DEPTH_ENTRY_OFFSET = -3 }; // clang-format off From 86b564055d753c49dede0b8549363f3ee11c572e Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sun, 2 Jun 2024 16:55:10 -0500 Subject: [PATCH 0593/1309] Remove delta, adjusted, complexity from nnue code ...rather they're the consumer's concern whether to tweak the result or not. Passed STC: https://tests.stockfishchess.org/tests/view/665cea9ffd45fb0f907c53bd LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 69696 W: 18101 L: 17918 D: 33677 Ptnml(0-2): 195, 8171, 17929, 8362, 191 Passed LTC: https://tests.stockfishchess.org/tests/view/665cf761fd45fb0f907c5406 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 63720 W: 16344 L: 16165 D: 31211 Ptnml(0-2): 32, 6990, 17625, 7193, 20 Non functional except for rounding issues of OutputScale changing bench. closes https://github.com/official-stockfish/Stockfish/pull/5344 Bench: 1378596 --- src/evaluate.cpp | 23 +++++++++++++++-------- src/nnue/network.cpp | 20 ++++---------------- src/nnue/network.h | 8 ++++---- src/nnue/nnue_misc.cpp | 13 ++++++++----- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 064ea027baa..248b2593333 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -24,8 +24,9 @@ #include #include #include -#include #include +#include +#include #include "nnue/network.h" #include "nnue/nnue_misc.h" @@ -60,17 +61,22 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = use_smallnet(pos); - int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) - : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) + : networks.big.evaluate(pos, &caches.big); + + constexpr int delta = 3; + Value nnue = ((128 - delta) * psqt + (128 + delta) * positional) / 128; + int nnueComplexity = std::abs(psqt - positional); // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 250)) { - nnue = networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); - smallNet = false; + std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); + nnue = ((128 - delta) * psqt + (128 + delta) * positional) / 128; + nnueComplexity = std::abs(psqt - positional); + smallNet = false; } // Blend optimism and eval with nnue complexity @@ -108,8 +114,9 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v = networks.big.evaluate(pos, &caches->big, false); - v = pos.side_to_move() == WHITE ? v : -v; + auto [psqt, positional] = networks.big.evaluate(pos, &caches->big); + Value v = psqt + positional; + v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; v = evaluate(networks, pos, *caches, VALUE_ZERO); diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 71c384ffc72..f7d2cc6ada0 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -18,7 +18,6 @@ #include "network.h" -#include #include #include #include @@ -206,15 +205,13 @@ bool Network::save(const std::optional& filename template -Value Network::evaluate(const Position& pos, - AccumulatorCaches::Cache* cache, - bool adjusted, - int* complexity) const { +NetworkOutput +Network::evaluate(const Position& pos, + AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; - constexpr int delta = 24; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType @@ -232,16 +229,7 @@ Value Network::evaluate(const Position& const int bucket = (pos.count() - 1) / 4; const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); - - if (complexity) - *complexity = std::abs(psqt - positional) / OutputScale; - - // Give more value to positional evaluation when adjusted flag is set - if (adjusted) - return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) - / (1024 * OutputScale)); - else - return static_cast((psqt + positional) / OutputScale); + return {static_cast(psqt / OutputScale), static_cast(positional / OutputScale)}; } diff --git a/src/nnue/network.h b/src/nnue/network.h index 6ba3cfbab8d..152082552c9 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "../memory.h" @@ -40,6 +41,7 @@ enum class EmbeddedNNUEType { SMALL, }; +using NetworkOutput = std::tuple; template class Network { @@ -59,10 +61,8 @@ class Network { void load(const std::string& rootDirectory, std::string evalfilePath); bool save(const std::optional& filename) const; - Value evaluate(const Position& pos, - AccumulatorCaches::Cache* cache, - bool adjusted = false, - int* complexity = nullptr) const; + NetworkOutput evaluate(const Position& pos, + AccumulatorCaches::Cache* cache) const; void hint_common_access(const Position& pos, diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index a13c717c3d8..7585cce5ab6 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "../evaluate.h" #include "../position.h" @@ -131,8 +132,9 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - Value base = networks.big.evaluate(pos, &caches.big); - base = pos.side_to_move() == WHITE ? base : -base; + auto [psqt, positional] = networks.big.evaluate(pos, &caches.big); + Value base = psqt + positional; + base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= FILE_H; ++f) for (Rank r = RANK_1; r <= RANK_8; ++r) @@ -148,9 +150,10 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat pos.remove_piece(sq); st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; - Value eval = networks.big.evaluate(pos, &caches.big); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; + std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); + Value eval = psqt + positional; + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; pos.put_piece(pc, sq); st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; From ba06671aa9df5c0a3fa5f1fa2ce17ea4aa742b7a Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 3 Jun 2024 19:47:34 +0200 Subject: [PATCH 0594/1309] Normalize some variable names and reuse existing logic closes https://github.com/official-stockfish/Stockfish/pull/5346 No functional change --- src/search.cpp | 4 ++-- src/search.h | 6 +++--- src/thread.cpp | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4defbadb40b..c03fe7811de 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -137,10 +137,10 @@ void update_all_stats(const Position& pos, Search::Worker::Worker(SharedState& sharedState, std::unique_ptr sm, - size_t thread_id, + size_t threadId, NumaReplicatedAccessToken token) : // Unpack the SharedState struct into member variables - thread_idx(thread_id), + threadIdx(threadId), numaAccessToken(token), manager(std::move(sm)), options(sharedState.options), diff --git a/src/search.h b/src/search.h index 01f7b8bdb00..a22d32004f5 100644 --- a/src/search.h +++ b/src/search.h @@ -244,7 +244,7 @@ class Worker { // It searches from the root position and outputs the "bestmove". void start_searching(); - bool is_mainthread() const { return thread_idx == 0; } + bool is_mainthread() const { return threadIdx == 0; } // Public because they need to be updatable by the stats CounterMoveHistory counterMoves; @@ -270,7 +270,7 @@ class Worker { // Get a pointer to the search manager, only allowed to be called by the // main thread. SearchManager* main_manager() const { - assert(thread_idx == 0); + assert(threadIdx == 0); return static_cast(manager.get()); } @@ -291,7 +291,7 @@ class Worker { Depth rootDepth, completedDepth; Value rootDelta; - size_t thread_idx; + size_t threadIdx; NumaReplicatedAccessToken numaAccessToken; // Reductions lookup table initialized at startup diff --git a/src/thread.cpp b/src/thread.cpp index a36c2efb7c1..0a33422acc9 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -127,9 +127,7 @@ void Thread::idle_loop() { } } -Search::SearchManager* ThreadPool::main_manager() { - return static_cast(main_thread()->worker.get()->manager.get()); -} +Search::SearchManager* ThreadPool::main_manager() { return main_thread()->worker->main_manager(); } uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); } uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); } From 7f09d06b834a5aaedbc78c5161ba91a8d6761421 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Jun 2024 07:53:25 +0200 Subject: [PATCH 0595/1309] Properly initialize the TT in a multithreaded way again --- src/tt.cpp | 4 +++- src/tt.h | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index f808106a6e1..56779b861fd 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -75,9 +75,11 @@ uint8_t TTEntry::relative_age(const uint8_t generation8) const { // measured in megabytes. Transposition table consists // of clusters and each cluster consists of ClusterSize number of TTEntry. void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) { + aligned_large_pages_free(table); + clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); - table = make_unique_large_page(clusterCount); + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); if (!table) { diff --git a/src/tt.h b/src/tt.h index 2dcfdd44b9d..974c7eb0c57 100644 --- a/src/tt.h +++ b/src/tt.h @@ -96,6 +96,7 @@ class TranspositionTable { static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; public: + ~TranspositionTable() { aligned_large_pages_free(table); } void new_search() { // increment by delta to keep lower bits as is generation8 += GENERATION_DELTA; @@ -115,9 +116,9 @@ class TranspositionTable { private: friend struct TTEntry; - size_t clusterCount; - LargePagePtr table; - uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 + size_t clusterCount; + Cluster* table = nullptr; + uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 }; } // namespace Stockfish From 4f53560d248195b172ac97d7c74e6bcfc65fe6fd Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Jun 2024 07:57:08 +0200 Subject: [PATCH 0596/1309] Accumulate nodes over all bench positions not just the last closes https://github.com/official-stockfish/Stockfish/pull/5352 No functional change --- src/tt.h | 1 - src/uci.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tt.h b/src/tt.h index 974c7eb0c57..b2e8f582b3b 100644 --- a/src/tt.h +++ b/src/tt.h @@ -21,7 +21,6 @@ #include #include -#include #include "memory.h" #include "misc.h" diff --git a/src/uci.cpp b/src/uci.cpp index 4b683116a9e..43b0e005a16 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -296,7 +296,7 @@ void UCIEngine::bench(std::istream& args) { Search::LimitsType limits = parse_limits(is); if (limits.perft) - nodes = perft(limits); + nodesSearched = perft(limits); else { engine.go(limits); From daaccd9fc9ca2dcc8ed7c72075fb1d3f504fa6ef Mon Sep 17 00:00:00 2001 From: Gahtan Nahdi <155860115+gahtan-syarif@users.noreply.github.com> Date: Tue, 4 Jun 2024 05:31:51 +0700 Subject: [PATCH 0597/1309] Simplify smallnet threshold remove pawncount Passed STC non-reg: https://tests.stockfishchess.org/tests/view/665e4548fd45fb0f907c80d5 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 60896 W: 15710 L: 15518 D: 29668 Ptnml(0-2): 149, 7145, 15660, 7353, 141 Passed LTC non-reg: https://tests.stockfishchess.org/tests/view/665e4c52fd45fb0f907c815f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 58068 W: 14773 L: 14590 D: 28705 Ptnml(0-2): 16, 6368, 16090, 6537, 23 closes https://github.com/official-stockfish/Stockfish/pull/5349 Bench: 1343156 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 248b2593333..afba6363bc6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -47,7 +47,7 @@ int Eval::simple_eval(const Position& pos, Color c) { bool Eval::use_smallnet(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 992 + 10 * pos.count(); + return std::abs(simpleEval) > 992; } // Evaluate is the evaluator for the outer world. It returns a static evaluation From 02ff76630b358e5f958793cc93df0009d2da65a5 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Tue, 4 Jun 2024 12:48:13 +0200 Subject: [PATCH 0598/1309] Add NumaPolicy "hardware" option that bypasses current processor affinity. Can be used in case a GUI (e.g. ChessBase 17 see #5307) sets affinity to a single processor group, but the user would like to use the full capabilities of the hardware. Improves affinity handling on Windows in case of multiple available APIs and existing affinities. closes https://github.com/official-stockfish/Stockfish/pull/5353 No functional change --- src/engine.cpp | 5 + src/numa.h | 394 ++++++++++++++++++++++++++++--------------------- 2 files changed, 232 insertions(+), 167 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 3fc27223a09..6980dd8341c 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -133,6 +133,11 @@ void Engine::set_numa_config_from_option(const std::string& o) { { numaContext.set_numa_config(NumaConfig::from_system()); } + else if (o == "hardware") + { + // Don't respect affinity set in the system. + numaContext.set_numa_config(NumaConfig::from_system(false)); + } else if (o == "none") { numaContext.set_numa_config(NumaConfig{}); diff --git a/src/numa.h b/src/numa.h index a56d7142d9d..c170c178e60 100644 --- a/src/numa.h +++ b/src/numa.h @@ -19,6 +19,7 @@ #ifndef NUMA_H_INCLUDED #define NUMA_H_INCLUDED +#include #include #include #include @@ -63,21 +64,9 @@ static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64; // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadselectedcpusetmasks using SetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT); -// https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-setthreadgroupaffinity -using SetThreadGroupAffinity_t = BOOL (*)(HANDLE, const GROUP_AFFINITY*, PGROUP_AFFINITY); - // https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadselectedcpusetmasks using GetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT, PUSHORT); -// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getprocessaffinitymask -using GetProcessAffinityMask_t = BOOL (*)(HANDLE, PDWORD_PTR, PDWORD_PTR); - -// https://learn.microsoft.com/en-us/windows/win32/api/processtopologyapi/nf-processtopologyapi-getprocessgroupaffinity -using GetProcessGroupAffinity_t = BOOL (*)(HANDLE, PUSHORT, PUSHORT); - -// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getactiveprocessorcount -using GetActiveProcessorCount_t = DWORD (*)(WORD); - #endif #include "misc.h" @@ -94,14 +83,7 @@ inline CpuIndex get_hardware_concurrency() { // only returns the number of processors in the first group, because only these // are available to std::thread. #ifdef _WIN64 - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto GetActiveProcessorCount_f = - GetActiveProcessorCount_t((void (*)()) GetProcAddress(k32, "GetActiveProcessorCount")); - - if (GetActiveProcessorCount_f != nullptr) - { - concurrency = GetActiveProcessorCount_f(ALL_PROCESSOR_GROUPS); - } + concurrency = std::max(concurrency, GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)); #endif return concurrency; @@ -109,6 +91,214 @@ inline CpuIndex get_hardware_concurrency() { inline const CpuIndex SYSTEM_THREADS_NB = std::max(1, get_hardware_concurrency()); +#if defined(_WIN64) + +struct WindowsAffinity { + std::optional> oldApi; + std::optional> newApi; + bool isDeterminate = true; + + std::optional> get_combined() const { + // When the affinity is not determinate we treat it as no affinity, + // because otherwise we would have to set affinity to fewer + // processors than we currently have affinity to. + if (!isDeterminate) + return std::nullopt; + + if (!oldApi.has_value()) + return newApi; + if (!newApi.has_value()) + return oldApi; + + std::set intersect; + std::set_intersection(oldApi->begin(), oldApi->end(), newApi->begin(), newApi->end(), + std::inserter(intersect, intersect.begin())); + return intersect; + } +}; + +inline std::pair> get_process_group_affinity() { + WORD numProcGroups = GetActiveProcessorGroupCount(); + + // GetProcessGroupAffinity requires the GroupArray argument to be + // aligned to 4 bytes instead of just 2. + static constexpr size_t GroupArrayMinimumAlignment = 4; + static_assert(GroupArrayMinimumAlignment >= alignof(USHORT)); + + auto GroupArray = std::make_unique( + numProcGroups + (GroupArrayMinimumAlignment / alignof(USHORT) - 1)); + + USHORT GroupCount = static_cast(numProcGroups); + const BOOL status = GetProcessGroupAffinity(GetCurrentProcess(), &GroupCount, GroupArray.get()); + + return std::make_pair(status, std::vector(GroupArray.get(), GroupArray.get() + GroupCount)); +} + +// Since Windows 11 and Windows Server 2022 thread affinities can span +// processor groups and can be set as such by a new WinAPI function. +// However, we may need to force using the old API if we detect +// that the process has affinity set by the old API already and we want to override that. +inline bool use_old_affinity_api() { + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( + (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); + + if (SetThreadSelectedCpuSetMasks_f == nullptr) + return true; + + auto [status, groupAffinity] = get_process_group_affinity(); + + // If GroupCount > 1 then we know old API was never used and we can stick + // to the new API safely. + if (status != 0 && groupAffinity.size() > 1) + return false; + + return true; +}; + +// On Windows there are two ways to set affinity, and therefore 2 ways to get it. +// These are not consistent, so we have to check both. +// In some cases it is actually not possible to determine affinity. +// For example when two different threads have affinity on different processor groups, +// set using SetThreadAffinityMask, we can't retrieve the actual affinities. +// From documentation on GetProcessAffinityMask: +// > If the calling process contains threads in multiple groups, +// > the function returns zero for both affinity masks. +// In such cases we just give up and assume we have affinity for all processors. +// nullopt means no affinity is set, that is, all processors are allowed +inline WindowsAffinity get_process_affinity() { + HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); + auto GetThreadSelectedCpuSetMasks_f = GetThreadSelectedCpuSetMasks_t( + (void (*)()) GetProcAddress(k32, "GetThreadSelectedCpuSetMasks")); + + WindowsAffinity affinity; + + if (GetThreadSelectedCpuSetMasks_f != nullptr) + { + USHORT RequiredMaskCount; + BOOL status = + GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount); + + // If RequiredMaskCount then these affinities were never set, but it's not consistent + // so GetProcessAffinityMask may still return some affinity. + if (status == 0) + { + affinity.isDeterminate = false; + return affinity; + } + + if (RequiredMaskCount > 0) + { + std::set cpus; + + auto groupAffinities = std::make_unique(RequiredMaskCount); + + GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(), + RequiredMaskCount, &RequiredMaskCount); + + for (USHORT i = 0; i < RequiredMaskCount; ++i) + { + const size_t procGroupIndex = groupAffinities[i].Group; + + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (groupAffinities[i].Mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + } + + affinity.newApi = std::move(cpus); + } + } + + DWORD_PTR proc, sys; + BOOL status = GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys); + + // If proc == 0 then we can't determine affinity because it spans processor groups. + if (status == 0 || proc == 0) + { + affinity.isDeterminate = false; + return affinity; + } + + // If SetProcessAffinityMask was never called the affinity + // must span all processor groups, but if it was called it must only span one. + auto [status2, groupAffinity] = get_process_group_affinity(); + if (status2 == 0) + { + affinity.isDeterminate = false; + return affinity; + } + + // If we have affinity for more than 1 group then at this point we + // can assume SetProcessAffinityMask has never been called and therefore + // according ot old API we do not have any affinity set. + // Otherwise we have to assume we have affinity set and gather the processor IDs. + if (groupAffinity.size() == 1) + { + std::set cpus; + + const size_t procGroupIndex = groupAffinity[0]; + + uint64_t mask = static_cast(proc); + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + + affinity.oldApi = std::move(cpus); + } + + return affinity; +} + +#endif + +#if defined(__linux__) && !defined(__ANDROID__) + +inline std::set get_process_affinity() { + + std::set cpus; + + // For unsupported systems, or in case of a soft error, we may assume all processors + // are available for use. + [[maybe_unused]] auto set_to_all_cpus = [&]() { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + cpus.insert(c); + }; + + // cpu_set_t by default holds 1024 entries. This may not be enough soon, + // but there is no easy way to determine how many threads there actually is. + // In this case we just choose a reasonable upper bound. + static constexpr CpuIndex MaxNumCpus = 1024 * 64; + + cpu_set_t* mask = CPU_ALLOC(MaxNumCpus); + if (mask == nullptr) + std::exit(EXIT_FAILURE); + + const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus); + + CPU_ZERO_S(masksize, mask); + + const int status = sched_getaffinity(0, masksize, mask); + + if (status != 0) + { + CPU_FREE(mask); + std::exit(EXIT_FAILURE); + } + + for (CpuIndex c = 0; c < MaxNumCpus; ++c) + if (CPU_ISSET_S(c, masksize, mask)) + cpus.insert(c); + + CPU_FREE(mask); + + return cpus; +} + +#endif // We want to abstract the purpose of storing the numa node index somewhat. // Whoever is using this does not need to know the specifics of the replication @@ -224,7 +414,7 @@ class NumaConfig { std::optional> allowedCpus; if (respectProcessAffinity) - allowedCpus = get_process_affinity(); + allowedCpus = get_process_affinity().get_combined(); // The affinity can't be determined in all cases on Windows, but we at least guarantee // that the number of allowed processors is >= number of processors in the affinity mask. @@ -233,15 +423,6 @@ class NumaConfig { return !allowedCpus.has_value() || allowedCpus->count(c) == 1; }; - // Since Windows 11 and Windows Server 2022 thread affinities can span - // processor groups and can be set as such by a new WinAPI function. - static const bool CanAffinitySpanProcessorGroups = []() { - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( - (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); - return SetThreadSelectedCpuSetMasks_f != nullptr; - }(); - WORD numProcGroups = GetActiveProcessorGroupCount(); for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup) { @@ -269,7 +450,8 @@ class NumaConfig { // the new NUMA allocation behaviour was introduced while there was // still no way to set thread affinity spanning multiple processor groups. // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support - if (!CanAffinitySpanProcessorGroups) + // We also do this is if need to force old API for some reason. + if (use_old_affinity_api()) { NumaConfig splitCfg = empty(); @@ -307,6 +489,12 @@ class NumaConfig { // We have to ensure no empty NUMA nodes persist. cfg.remove_empty_numa_nodes(); + // If the user explicitly opts out from respecting the current process affinity + // then it may be inconsistent with the current affinity (obviously), so we + // consider it custom. + if (!respectProcessAffinity) + cfg.customAffinity = true; + return cfg; } @@ -510,9 +698,11 @@ class NumaConfig { HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); - auto SetThreadGroupAffinity_f = - SetThreadGroupAffinity_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity")); + // We ALWAYS set affinity with the new API if available, + // because there's no downsides, and we forcibly keep it consistent + // with the old API should we need to use it. I.e. we always keep this as a superset + // of what we set with SetThreadGroupAffinity. if (SetThreadSelectedCpuSetMasks_f != nullptr) { // Only available on Windows 11 and Windows Server 2022 onwards. @@ -541,7 +731,9 @@ class NumaConfig { // This is defensive, allowed because this code is not performance critical. SwitchToThread(); } - else if (SetThreadGroupAffinity_f != nullptr) + + // Sometimes we need to force the old API, but do not use it unless necessary. + if (SetThreadSelectedCpuSetMasks_f == nullptr || use_old_affinity_api()) { // On earlier windows version (since windows 7) we can't run a single thread // on multiple processor groups, so we need to restrict the group. @@ -576,7 +768,7 @@ class NumaConfig { HANDLE hThread = GetCurrentThread(); - const BOOL status = SetThreadGroupAffinity_f(hThread, &affinity, nullptr); + const BOOL status = SetThreadGroupAffinity(hThread, &affinity, nullptr); if (status == 0) std::exit(EXIT_FAILURE); @@ -665,138 +857,6 @@ class NumaConfig { return true; } -#if defined(__linux__) && !defined(__ANDROID__) - - static std::set get_process_affinity() { - - std::set cpus; - - // For unsupported systems, or in case of a soft error, we may assume all processors - // are available for use. - [[maybe_unused]] auto set_to_all_cpus = [&]() { - for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) - cpus.insert(c); - }; - - // cpu_set_t by default holds 1024 entries. This may not be enough soon, - // but there is no easy way to determine how many threads there actually is. - // In this case we just choose a reasonable upper bound. - static constexpr CpuIndex MaxNumCpus = 1024 * 64; - - cpu_set_t* mask = CPU_ALLOC(MaxNumCpus); - if (mask == nullptr) - std::exit(EXIT_FAILURE); - - const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus); - - CPU_ZERO_S(masksize, mask); - - const int status = sched_getaffinity(0, masksize, mask); - - if (status != 0) - { - CPU_FREE(mask); - std::exit(EXIT_FAILURE); - } - - for (CpuIndex c = 0; c < MaxNumCpus; ++c) - if (CPU_ISSET_S(c, masksize, mask)) - cpus.insert(c); - - CPU_FREE(mask); - - return cpus; - } - -#elif defined(_WIN64) - - // On Windows there are two ways to set affinity, and therefore 2 ways to get it. - // These are not consistent, so we have to check both. - // In some cases it is actually not possible to determine affinity. - // For example when two different threads have affinity on different processor groups, - // set using SetThreadAffinityMask, we can't retrieve the actual affinities. - // From documentation on GetProcessAffinityMask: - // > If the calling process contains threads in multiple groups, - // > the function returns zero for both affinity masks. - // In such cases we just give up and assume we have affinity for all processors. - // nullopt means no affinity is set, that is, all processors are allowed - static std::optional> get_process_affinity() { - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto GetThreadSelectedCpuSetMasks_f = GetThreadSelectedCpuSetMasks_t( - (void (*)()) GetProcAddress(k32, "GetThreadSelectedCpuSetMasks")); - auto GetProcessAffinityMask_f = - GetProcessAffinityMask_t((void (*)()) GetProcAddress(k32, "GetProcessAffinityMask")); - auto GetProcessGroupAffinity_f = - GetProcessGroupAffinity_t((void (*)()) GetProcAddress(k32, "GetProcessGroupAffinity")); - - if (GetThreadSelectedCpuSetMasks_f != nullptr) - { - std::set cpus; - - USHORT RequiredMaskCount; - GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount); - - // If RequiredMaskCount then these affinities were never set, but it's not consistent - // so GetProcessAffinityMask may still return some affinity. - if (RequiredMaskCount > 0) - { - auto groupAffinities = std::make_unique(RequiredMaskCount); - - GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(), - RequiredMaskCount, &RequiredMaskCount); - - for (USHORT i = 0; i < RequiredMaskCount; ++i) - { - const size_t procGroupIndex = groupAffinities[i].Group; - - for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) - { - if (groupAffinities[i].Mask & (KAFFINITY(1) << j)) - cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); - } - } - - return cpus; - } - } - - if (GetProcessAffinityMask_f != nullptr && GetProcessGroupAffinity_f != nullptr) - { - std::set cpus; - - DWORD_PTR proc, sys; - BOOL status = GetProcessAffinityMask_f(GetCurrentProcess(), &proc, &sys); - if (status == 0) - return std::nullopt; - - // We can't determine affinity because it spans processor groups. - if (proc == 0) - return std::nullopt; - - // We are expecting a single group. - USHORT GroupCount = 1; - alignas(4) USHORT GroupArray[1]; - status = GetProcessGroupAffinity_f(GetCurrentProcess(), &GroupCount, GroupArray); - if (status == 0 || GroupCount != 1) - return std::nullopt; - - const size_t procGroupIndex = GroupArray[0]; - - uint64_t mask = static_cast(proc); - for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) - { - if (mask & (KAFFINITY(1) << j)) - cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); - } - - return cpus; - } - - return std::nullopt; - } - -#endif - static std::vector indices_from_shortened_string(const std::string& s) { std::vector indices; From 21ba32af6d34c367ef22384c0f87fe49764d8ef0 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 4 Jun 2024 17:59:47 -0700 Subject: [PATCH 0599/1309] Remove m512_hadd128x16_interleave() This functionality is no longer used anywhere. closes https://github.com/official-stockfish/Stockfish/pull/5357 No functional change --- src/nnue/layers/simd.h | 33 --------------------------------- src/search.cpp | 2 +- src/search.h | 2 +- 3 files changed, 2 insertions(+), 35 deletions(-) diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index cec41474bb0..55cb7df1421 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -43,39 +43,6 @@ namespace Stockfish::Simd { return _mm512_reduce_add_epi32(sum) + bias; } -/* - Parameters: - sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]] - sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]] - sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]] - sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]] - - Returns: - ret = [ - reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]), - reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]), - reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]), - reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3]) - ] - */ -[[maybe_unused]] static __m512i -m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) { - - __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1); - __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1); - - __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3); - __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3); - - __m512i sum01 = _mm512_add_epi32(sum01a, sum01b); - __m512i sum23 = _mm512_add_epi32(sum23a, sum23b); - - __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23); - __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23); - - return _mm512_add_epi32(sum0123a, sum0123b); -} - [[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { #if defined(USE_VNNI) diff --git a/src/search.cpp b/src/search.cpp index c03fe7811de..6e03b62a92c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1660,7 +1660,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, return bestValue; } -Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { +Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; return (reductionScale + 1222 - delta * 733 / rootDelta) / 1024 + (!i && reductionScale > 1231); } diff --git a/src/search.h b/src/search.h index a22d32004f5..d5210c2e072 100644 --- a/src/search.h +++ b/src/search.h @@ -265,7 +265,7 @@ class Worker { template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); - Depth reduction(bool i, Depth d, int mn, int delta); + Depth reduction(bool i, Depth d, int mn, int delta) const; // Get a pointer to the search manager, only allowed to be called by the // main thread. From a08fcacb2876ced0cb68d01e61f081449386f132 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:47:54 +0800 Subject: [PATCH 0600/1309] VVLTC search tune Parameters were tuned with 199k games of VVLTC: https://tests.stockfishchess.org/tests/view/665c67e73542f91ad1c54fe2 Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/665e9c83fd45fb0f907c837c LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 83494 W: 21546 L: 21219 D: 40729 Ptnml(0-2): 6, 7707, 25993, 8036, 5 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/665f650bfd45fb0f907cb360 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 151056 W: 38796 L: 38295 D: 73965 Ptnml(0-2): 5, 13742, 47536, 14237, 8 https://github.com/official-stockfish/Stockfish/pull/5359 Bench: 1154524 --- src/search.cpp | 94 +++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6e03b62a92c..1c0bbc4d9d7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -60,9 +60,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 129 - 43 * noTtCutNode; - Value improvingDeduction = 56 * improving * futilityMult / 32; - Value worseningDeduction = 336 * oppWorsening * futilityMult / 1024; + Value futilityMult = 124 - 43 * noTtCutNode; + Value improvingDeduction = 60 * improving * futilityMult / 32; + Value worseningDeduction = 344 * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -74,15 +74,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 5435; + v += cv * std::abs(cv) / 4990; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(205 * d - 283, 18, 1544); } +int stat_bonus(Depth d) { return std::clamp(186 * d - 285, 20, 1524); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 767 * d - 275 : 1911); } +int stat_malus(Depth d) { return (d < 4 ? 707 * d - 260 : 2073); } // 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); } @@ -314,12 +314,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 10502; + delta = 9 + avg * avg / 10182; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 122 * avg / (std::abs(avg) + 92); + optimism[us] = 127 * avg / (std::abs(avg) + 86); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -500,17 +500,17 @@ void Search::Worker::clear() { counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(0); - pawnHistory.fill(-1300); + pawnHistory.fill(-1193); correctionHistory.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-60); + h->fill(-56); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((19.90 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((19.26 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -742,7 +742,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-11 * int((ss - 1)->staticEval + ss->staticEval), -1592, 1390); + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -764,7 +764,7 @@ Value Search::Worker::search( // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 501 - 305 * depth * depth) + if (eval < alpha - 512 - 293 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -773,23 +773,23 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 12 + if (!ss->ttPv && depth < 13 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 248 + - (ss - 1)->statScore / 263 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 13999 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14369 + && eval >= beta && ss->staticEval >= beta - 21 * depth + 393 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 177, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 197, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -847,7 +847,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 185 - 60 * improving; + probCutBeta = beta + 177 - 57 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -903,7 +903,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 361; + probCutBeta = beta + 388; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -991,15 +991,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 283 + 235 * lmrDepth + Value futilityValue = ss->staticEval + 287 + 248 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -183 * depth, 162 * depth); - if (!pos.see_ge(move, -166 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -180 * depth, 163 * depth); + if (!pos.see_ge(move, -160 * depth - seeHist)) continue; } else @@ -1010,18 +1010,18 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4427 * depth) + if (lmrDepth < 6 && history < -4151 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 3670; + lmrDepth += history / 3678; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 51 ? 149 : 55) + 141 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 51 ? 138 : 54) + 140 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1032,7 +1032,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -24 * lmrDepth * lmrDepth)) continue; } } @@ -1055,11 +1055,11 @@ Value Search::Worker::search( // margins scale well. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 38) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 35) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (58 + 64 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttValue - (52 + 80 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1070,13 +1070,13 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 304 * PvNode - 203 * !ttCapture; - int tripleMargin = 117 + 259 * PvNode - 296 * !ttCapture + 97 * ss->ttPv; + int doubleMargin = 290 * PvNode - 200 * !ttCapture; + int tripleMargin = 107 + 247 * PvNode - 278 * !ttCapture + 99 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += ((!PvNode) && (depth < 16)); + depth += ((!PvNode) && (depth < 18)); } // Multi-cut pruning @@ -1106,7 +1106,7 @@ Value Search::Worker::search( else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 3988) + > 3922) extension = 1; } @@ -1162,10 +1162,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 5169; + + (*contHist[1])[movedPiece][move.to_sq()] - 4747; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 11049; + r -= ss->statScore / 11125; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1184,7 +1184,7 @@ Value Search::Worker::search( { // 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 + 36 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 35 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1345,10 +1345,10 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (116 * (depth > 5) + 115 * (PvNode || cutNode) - + 186 * ((ss - 1)->statScore < -14144) + 121 * ((ss - 1)->moveCount > 9) - + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 115) - + 137 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 81)); + int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode) + + 191 * ((ss - 1)->statScore < -14396) + 119 * ((ss - 1)->moveCount > 8) + + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 107) + + 147 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75)); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus / 100); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1520,7 +1520,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 279; + futilityBase = ss->staticEval + 294; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1592,11 +1592,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()] - <= 4181) + <= 4452) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -67)) + if (!pos.see_ge(move, -74)) continue; } @@ -1662,7 +1662,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 + 1222 - delta * 733 / rootDelta) / 1024 + (!i && reductionScale > 1231); + return (reductionScale + 1236 - delta * 746 / rootDelta) / 1024 + (!i && reductionScale > 1326); } // elapsed() returns the time elapsed since the search started. If the @@ -1765,7 +1765,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 176 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 164 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); @@ -1803,7 +1803,7 @@ void update_all_stats(const Position& pos, // by moves at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - bonus = bonus * 47 / 64; + bonus = bonus * 51 / 64; for (int i : {1, 2, 3, 4, 6}) { From 36eb9bc783d35842571d0d4313349b964892d9ca Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Wed, 5 Jun 2024 03:24:39 +0100 Subject: [PATCH 0601/1309] Use futility margin in razoring margin Uses futilityMargin * depth to set the razoring margin. This retains the quadratic depth scaling to preserve mate finding capabilities. This patch is nice because it increases the elo sensitivity of the futility margin heuristics. Passed STC: https://tests.stockfishchess.org/tests/view/665f9892fd11ae7170b4849c LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 39392 W: 10348 L: 10030 D: 19014 Ptnml(0-2): 99, 4585, 10009, 4905, 98 Passed LTC: https://tests.stockfishchess.org/tests/view/665f9d2dfd11ae7170b484a8 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 107910 W: 27521 L: 27053 D: 53336 Ptnml(0-2): 73, 11835, 29670, 12305, 72 closes https://github.com/official-stockfish/Stockfish/pull/5360 bench 1277173 --- src/search.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1c0bbc4d9d7..15cc2d8fe76 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -60,9 +60,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 124 - 43 * noTtCutNode; - Value improvingDeduction = 60 * improving * futilityMult / 32; - Value worseningDeduction = 344 * oppWorsening * futilityMult / 1024; + Value futilityMult = 109 - 40 * noTtCutNode; + Value improvingDeduction = 59 * improving * futilityMult / 32; + Value worseningDeduction = 328 * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -554,7 +554,7 @@ Value Search::Worker::search( bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount; + int moveCount, captureCount, quietCount, futilityMargin; Bound singularBound; // Step 1. Initialize node @@ -761,10 +761,12 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; + futilityMargin = futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening); + // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 512 - 293 * depth * depth) + if (eval < alpha - 465 - futilityMargin * depth * 33 / 32) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -774,9 +776,7 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. if (!ss->ttPv && depth < 13 - && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 263 - >= beta + && eval - futilityMargin - (ss - 1)->statScore / 263 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval; From fb18caae7a7906a6c9a0579c43021221c663965a Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 5 Jun 2024 18:31:11 +0200 Subject: [PATCH 0602/1309] Update clang-format to version 18 clang-format-18 is available in ubuntu noble(24.04), if you are on a version lower than that you can use the update script from llvm. https://apt.llvm.org/ Windows users should be able to download and use clang-format from their release builds https://github.com/llvm/llvm-project/releases or get the latest from msys2 https://packages.msys2.org/package/mingw-w64-x86_64-clang. macOS users can resort to "brew install clang-format". closes https://github.com/official-stockfish/Stockfish/pull/5365 No functional change --- .github/workflows/clang-format.yml | 6 +++--- CONTRIBUTING.md | 2 +- src/Makefile | 4 ++-- src/search.cpp | 10 +++++----- src/thread.cpp | 2 +- src/tune.cpp | 3 +-- 6 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index e20e0d5d623..630edbf93fe 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -25,7 +25,7 @@ jobs: id: clang-format continue-on-error: true with: - clang-format-version: "17" + clang-format-version: "18" exclude-regex: "incbin" - name: Comment on PR @@ -33,9 +33,9 @@ jobs: uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0 with: message: | - clang-format 17 needs to be run on this PR. + clang-format 18 needs to be run on this PR. If you do not have clang-format installed, the maintainer will run it when merging. - For the exact version please see https://packages.ubuntu.com/mantic/clang-format-17. + For the exact version please see https://packages.ubuntu.com/noble/clang-format-18. _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ comment_tag: execution diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf9cecda2b5..caffc916e60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ discussion._ Changes to Stockfish C++ code should respect our coding style defined by [.clang-format](.clang-format). You can format your changes by running -`make format`. This requires clang-format version 17 to be installed on your system. +`make format`. This requires clang-format version 18 to be installed on your system. ## Navigate diff --git a/src/Makefile b/src/Makefile index 29c4f879dfb..742fd195839 100644 --- a/src/Makefile +++ b/src/Makefile @@ -153,8 +153,8 @@ dotprod = no arm_version = 0 STRIP = strip -ifneq ($(shell which clang-format-17 2> /dev/null),) - CLANG-FORMAT = clang-format-17 +ifneq ($(shell which clang-format-18 2> /dev/null),) + CLANG-FORMAT = clang-format-18 else CLANG-FORMAT = clang-format endif diff --git a/src/search.cpp b/src/search.cpp index 15cc2d8fe76..2cbc7677429 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -579,9 +579,10 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate( - networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(networks[numaAccessToken], pos, refreshTable, + thisThread->optimism[us]) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -775,8 +776,7 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 13 - && eval - futilityMargin - (ss - 1)->statScore / 263 >= beta + if (!ss->ttPv && depth < 13 && eval - futilityMargin - (ss - 1)->statScore / 263 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval; diff --git a/src/thread.cpp b/src/thread.cpp index 0a33422acc9..4acb9854bd4 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -179,7 +179,7 @@ void ThreadPool::set(const NumaConfig& numaConfig, const size_t threadId = threads.size(); const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0; auto manager = threadId == 0 ? std::unique_ptr( - std::make_unique(updateContext)) + std::make_unique(updateContext)) : std::make_unique(); // When not binding threads we want to force all access to happen diff --git a/src/tune.cpp b/src/tune.cpp index 3e5ebe5e6c3..f377e59ecf1 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -118,7 +118,6 @@ void Tune::Entry::read_option() { namespace Stockfish { -void Tune::read_results() { /* ...insert your values here... */ -} +void Tune::read_results() { /* ...insert your values here... */ } } // namespace Stockfish From 5688b188cc8560e107815c83a7084220fddebdb9 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Fri, 31 May 2024 21:55:39 +0800 Subject: [PATCH 0603/1309] Simplify evaluation constants Passed STC (<0, 2> by accident): LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 346016 W: 89529 L: 88756 D: 167731 Ptnml(0-2): 1012, 41074, 88027, 41919, 976 https://tests.stockfishchess.org/tests/view/6659d6ecf426908fcc6b6929 Passed LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 89862 W: 22887 L: 22734 D: 44241 Ptnml(0-2): 45, 9999, 24694, 10144, 49 https://tests.stockfishchess.org/tests/view/665a6ebb062b2c3cf814fde8 Passed LTC (Rebased): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 325500 W: 82734 L: 82826 D: 159940 Ptnml(0-2): 193, 36409, 89665, 36263, 220 https://tests.stockfishchess.org/tests/view/665bd39f44e8416a9cdc1909 closes https://github.com/official-stockfish/Stockfish/pull/5361 Bench 961982 --- src/evaluate.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index afba6363bc6..fdf35eb17d4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -83,10 +83,8 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, optimism += optimism * nnueComplexity / 470; nnue -= nnue * nnueComplexity / 20000; - int material = 300 * pos.count() + 350 * pos.count() + 400 * pos.count() - + 640 * pos.count() + 1200 * pos.count(); - - v = (nnue * (34300 + material) + optimism * (4400 + material)) / 36672; + int material = 600 * pos.count() + pos.non_pawn_material(); + v = (nnue * (68600 + material) + optimism * (8800 + material)) / 73344; // Damp down the evaluation linearly when shuffling v -= v * pos.rule50_count() / 212; From e6c83beed12a6d3d17c69bea4bcf1a397bc60c86 Mon Sep 17 00:00:00 2001 From: R-Goc Date: Tue, 4 Jun 2024 18:06:14 +0200 Subject: [PATCH 0604/1309] Change PGO type for clang Change type of PGO in clang to IR which is recommended by LLVM/clang and could result in a speedup. https://github.com/llvm/llvm-project/issues/45668 closes https://github.com/official-stockfish/Stockfish/pull/5355 No functional change --- src/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Makefile b/src/Makefile index 742fd195839..7142b972745 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1051,14 +1051,14 @@ FORCE: clang-profile-make: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-fprofile-instr-generate ' \ - EXTRALDFLAGS=' -fprofile-instr-generate' \ + EXTRACXXFLAGS='-fprofile-generate ' \ + EXTRALDFLAGS=' -fprofile-generate' \ all clang-profile-use: $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ - EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRACXXFLAGS='-fprofile-use=stockfish.profdata' \ EXTRALDFLAGS='-fprofile-use ' \ all From 66ed4312f22a951aaa01bbb87b2d730656b8f2c1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 7 Jun 2024 18:40:47 +0200 Subject: [PATCH 0605/1309] Workaround the clang-format inconsistencies closes https://github.com/official-stockfish/Stockfish/pull/5378 No functional change --- src/nnue/nnue_misc.cpp | 10 +++++----- src/tune.cpp | 7 +++++-- src/uci.cpp | 5 +++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 7585cce5ab6..122610a749c 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -178,16 +178,16 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { - ss << "| " << bucket << " "; - ss << " | "; + ss << "| " << bucket << " " // + << " | "; format_cp_aligned_dot(t.psqt[bucket], ss, pos); - ss << " " + ss << " " // << " | "; format_cp_aligned_dot(t.positional[bucket], ss, pos); - ss << " " + ss << " " // << " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); - ss << " " + ss << " " // << " |"; if (bucket == t.correctBucket) ss << " <-- this bucket is used"; diff --git a/src/tune.cpp b/src/tune.cpp index f377e59ecf1..94c9b53ecc6 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -58,8 +58,11 @@ void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) LastOption = &((*options)[n]); // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," - << (r(v).second - r(v).first) / 20.0 << "," + std::cout << n << "," // + << v << "," // + << r(v).first << "," // + << r(v).second << "," // + << (r(v).second - r(v).first) / 20.0 << "," // << "0.0020" << std::endl; } } diff --git a/src/uci.cpp b/src/uci.cpp index 43b0e005a16..42c69cdeff2 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -324,8 +324,9 @@ void UCIEngine::bench(std::istream& args) { dbg_print(); - std::cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes + std::cerr << "\n===========================" // + << "\nTotal time (ms) : " << elapsed // + << "\nNodes searched : " << nodes // << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl; // reset callback, to not capture a dangling reference to nodesSearched From 5dda4037c73ead63b145c9a77f1dbb41422e058f Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Wed, 5 Jun 2024 18:56:25 +0200 Subject: [PATCH 0606/1309] Simplify razor changes Remove razoring changes from https://github.com/official-stockfish/Stockfish/pull/5360 The mentioned patch introduced the usage of futility_margin into razoring alongside a tune to futility_margin. It seems the elo gained in this patch comes from the tune of futility_margin and not the introduction of futility_margin to razoring, so simplify it away here. Passed Non-regression STC: https://tests.stockfishchess.org/tests/view/66606581c340c8eed7757bc8 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 169056 W: 43922 L: 43848 D: 81286 Ptnml(0-2): 438, 20288, 43034, 20298, 470 Passed Non-regression LTC: https://tests.stockfishchess.org/tests/view/66607764c340c8eed7757c58 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 157134 W: 39805 L: 39723 D: 77606 Ptnml(0-2): 74, 17444, 43461, 17502, 86 Passed rebased Non-regression LTC: https://tests.stockfishchess.org/tests/view/6660c696c340c8eed77580c0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 135984 W: 34427 L: 34324 D: 67233 Ptnml(0-2): 67, 15063, 37615, 15194, 53 closes https://github.com/official-stockfish/Stockfish/pull/5366 Bench: 1150518 --- src/search.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2cbc7677429..e0a49dba860 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -554,7 +554,7 @@ Value Search::Worker::search( bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount, futilityMargin; + int moveCount, captureCount, quietCount; Bound singularBound; // Step 1. Initialize node @@ -762,12 +762,10 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; - futilityMargin = futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening); - // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 465 - futilityMargin * depth * 33 / 32) + if (eval < alpha - 512 - 293 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -776,7 +774,10 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 13 && eval - futilityMargin - (ss - 1)->statScore / 263 >= beta + if (!ss->ttPv && depth < 13 + && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) + - (ss - 1)->statScore / 263 + >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval; From e2be0aaf67569788f0d1e726d0a86ce1604958da Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 6 Jun 2024 13:07:45 +0300 Subject: [PATCH 0607/1309] Tweak pruning formula Tweak pruning formula, including a constant. I started from an old yellow patch, if I'm not mistaken by Viz (Unfortunately I lost the link) where he tried something similar. I worked on it, trying different variations, until I came up with a good configuration to pass. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 213120 W: 55351 L: 54778 D: 102991 Ptnml(0-2): 572, 25209, 54437, 25758, 584 https://tests.stockfishchess.org/tests/view/6660c9a7c340c8eed7758195 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 315324 W: 80176 L: 79284 D: 155864 Ptnml(0-2): 155, 34711, 87030, 35619, 147 https://tests.stockfishchess.org/tests/view/6660d7bb6489614cdad13d66 closes https://github.com/official-stockfish/Stockfish/pull/5370 Bench: 1231853 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e0a49dba860..7417a4b6f5e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1580,7 +1580,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // If static exchange evaluation is much worse than what is needed to not // fall below alpha we can prune this move. - if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) + if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 2 - 30)) { bestValue = alpha; continue; From f55239b2f374a2f98717e7c361732f7c4510388b Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Thu, 6 Jun 2024 12:47:24 +0200 Subject: [PATCH 0608/1309] NumaPolicy fixes and robustness improvements 1. Fix GetProcessGroupAffinity still not getting properly aligned memory sometimes. 2. Fix a very theoretically possible heap corruption if GetActiveProcessorGroupCount changes between calls. 3. Fully determine affinity on Windows 11 and Windows Server 2022. It should only ever be indeterminate in case of an error. 4. Separate isDeterminate for old and new API, as they are &'d together we still can end up with a subset of processors even if one API is indeterminate. 5. likely_used_old_api() that is based on actual affinity that's been detected 6. IMPORTANT: Gather affinities at startup, so that we only later use the affinites set at startup. Not only does this prevent us from our own calls interfering with detection but it also means subsequent setoption NumaPolicy calls should behave as expected. 7. Fix ERROR_INSUFFICIENT_BUFFER from GetThreadSelectedCpuSetMasks being treated like an error. Should resolve https://github.com/vondele/Stockfish/commit/02ff76630b358e5f958793cc93df0009d2da65a5#commitcomment-142790025 closes https://github.com/official-stockfish/Stockfish/pull/5372 Bench: 1231853 --- src/numa.h | 272 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 197 insertions(+), 75 deletions(-) diff --git a/src/numa.h b/src/numa.h index c170c178e60..fd9abd4db7f 100644 --- a/src/numa.h +++ b/src/numa.h @@ -35,6 +35,8 @@ #include #include +#include "memory.h" + // We support linux very well, but we explicitly do NOT support Android, because there's // no affected systems, not worth maintaining. #if defined(__linux__) && !defined(__ANDROID__) @@ -96,15 +98,15 @@ inline const CpuIndex SYSTEM_THREADS_NB = std::max(1, get_hardware_con struct WindowsAffinity { std::optional> oldApi; std::optional> newApi; - bool isDeterminate = true; - std::optional> get_combined() const { - // When the affinity is not determinate we treat it as no affinity, - // because otherwise we would have to set affinity to fewer - // processors than we currently have affinity to. - if (!isDeterminate) - return std::nullopt; + // We also provide diagnostic for when the affinity is set to nullopt + // whether it was due to being indeterminate. If affinity is indeterminate + // it's best to assume it is not set at all, so consistent with the meaning + // of the nullopt affinity. + bool isNewDeterminate = true; + bool isOldDeterminate = true; + std::optional> get_combined() const { if (!oldApi.has_value()) return newApi; if (!newApi.has_value()) @@ -115,46 +117,52 @@ struct WindowsAffinity { std::inserter(intersect, intersect.begin())); return intersect; } + + // Since Windows 11 and Windows Server 2022 thread affinities can span + // processor groups and can be set as such by a new WinAPI function. + // However, we may need to force using the old API if we detect + // that the process has affinity set by the old API already and we want to override that. + // Due to the limitations of the old API we can't detect its use reliably. + // There will be cases where we detect not use but it has actually been used and vice versa. + bool likely_used_old_api() const { return oldApi.has_value() || !isOldDeterminate; } }; inline std::pair> get_process_group_affinity() { - WORD numProcGroups = GetActiveProcessorGroupCount(); - // GetProcessGroupAffinity requires the GroupArray argument to be // aligned to 4 bytes instead of just 2. static constexpr size_t GroupArrayMinimumAlignment = 4; static_assert(GroupArrayMinimumAlignment >= alignof(USHORT)); - auto GroupArray = std::make_unique( - numProcGroups + (GroupArrayMinimumAlignment / alignof(USHORT) - 1)); - - USHORT GroupCount = static_cast(numProcGroups); - const BOOL status = GetProcessGroupAffinity(GetCurrentProcess(), &GroupCount, GroupArray.get()); + // The function should succeed the second time, but it may fail if the group + // affinity has changed between GetProcessGroupAffinity calls. + // In such case we consider this a hard error, as we can't work with unstable affinities + // anyway. + static constexpr int MAX_TRIES = 2; + USHORT GroupCount = 1; + for (int i = 0; i < MAX_TRIES; ++i) + { + auto GroupArray = std::make_unique( + GroupCount + (GroupArrayMinimumAlignment / alignof(USHORT) - 1)); - return std::make_pair(status, std::vector(GroupArray.get(), GroupArray.get() + GroupCount)); -} + USHORT* GroupArrayAligned = align_ptr_up(GroupArray.get()); -// Since Windows 11 and Windows Server 2022 thread affinities can span -// processor groups and can be set as such by a new WinAPI function. -// However, we may need to force using the old API if we detect -// that the process has affinity set by the old API already and we want to override that. -inline bool use_old_affinity_api() { - HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); - auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( - (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); + const BOOL status = + GetProcessGroupAffinity(GetCurrentProcess(), &GroupCount, GroupArrayAligned); - if (SetThreadSelectedCpuSetMasks_f == nullptr) - return true; - - auto [status, groupAffinity] = get_process_group_affinity(); + if (status == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + break; + } - // If GroupCount > 1 then we know old API was never used and we can stick - // to the new API safely. - if (status != 0 && groupAffinity.size() > 1) - return false; + if (status != 0) + { + return std::make_pair(status, + std::vector(GroupArrayAligned, GroupArrayAligned + GroupCount)); + } + } - return true; -}; + return std::make_pair(0, std::vector()); +} // On Windows there are two ways to set affinity, and therefore 2 ways to get it. // These are not consistent, so we have to check both. @@ -171,83 +179,183 @@ inline WindowsAffinity get_process_affinity() { auto GetThreadSelectedCpuSetMasks_f = GetThreadSelectedCpuSetMasks_t( (void (*)()) GetProcAddress(k32, "GetThreadSelectedCpuSetMasks")); + BOOL status = 0; + WindowsAffinity affinity; if (GetThreadSelectedCpuSetMasks_f != nullptr) { USHORT RequiredMaskCount; - BOOL status = - GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount); + status = GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), nullptr, 0, &RequiredMaskCount); - // If RequiredMaskCount then these affinities were never set, but it's not consistent - // so GetProcessAffinityMask may still return some affinity. - if (status == 0) + // We expect ERROR_INSUFFICIENT_BUFFER from GetThreadSelectedCpuSetMasks, + // but other failure is an actual error. + if (status == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { - affinity.isDeterminate = false; - return affinity; + affinity.isNewDeterminate = false; } - - if (RequiredMaskCount > 0) + else if (RequiredMaskCount > 0) { - std::set cpus; - + // If RequiredMaskCount then these affinities were never set, but it's not consistent + // so GetProcessAffinityMask may still return some affinity. auto groupAffinities = std::make_unique(RequiredMaskCount); - GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(), - RequiredMaskCount, &RequiredMaskCount); + status = GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(), + RequiredMaskCount, &RequiredMaskCount); - for (USHORT i = 0; i < RequiredMaskCount; ++i) + if (status == 0) + { + affinity.isNewDeterminate = false; + } + else { - const size_t procGroupIndex = groupAffinities[i].Group; + std::set cpus; - for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + for (USHORT i = 0; i < RequiredMaskCount; ++i) { - if (groupAffinities[i].Mask & (KAFFINITY(1) << j)) - cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + const size_t procGroupIndex = groupAffinities[i].Group; + + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (groupAffinities[i].Mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } } - } - affinity.newApi = std::move(cpus); + affinity.newApi = std::move(cpus); + } } } + // NOTE: There is no way to determine full affinity using the old API if + // individual threads set affinity on different processor groups. + DWORD_PTR proc, sys; - BOOL status = GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys); + status = GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys); // If proc == 0 then we can't determine affinity because it spans processor groups. + // On Windows 11 and Server 2022 it will instead + // > If, however, hHandle specifies a handle to the current process, the function + // > always uses the calling thread's primary group (which by default is the same + // > as the process' primary group) in order to set the + // > lpProcessAffinityMask and lpSystemAffinityMask. + // So it will never be indeterminate here. We can only make assumptions later. if (status == 0 || proc == 0) { - affinity.isDeterminate = false; + affinity.isOldDeterminate = false; return affinity; } // If SetProcessAffinityMask was never called the affinity // must span all processor groups, but if it was called it must only span one. - auto [status2, groupAffinity] = get_process_group_affinity(); - if (status2 == 0) + std::vector groupAffinity; // We need to capture this later and capturing + // from structured bindings requires c++20. + std::tie(status, groupAffinity) = get_process_group_affinity(); + if (status == 0) { - affinity.isDeterminate = false; + affinity.isOldDeterminate = false; return affinity; } - // If we have affinity for more than 1 group then at this point we - // can assume SetProcessAffinityMask has never been called and therefore - // according ot old API we do not have any affinity set. - // Otherwise we have to assume we have affinity set and gather the processor IDs. if (groupAffinity.size() == 1) { - std::set cpus; + // We detect the case when affinity is set to all processors and correctly + // leave affinity.oldApi as nullopt. + if (GetActiveProcessorGroupCount() != 1 || proc != sys) + { + std::set cpus; - const size_t procGroupIndex = groupAffinity[0]; + const size_t procGroupIndex = groupAffinity[0]; - uint64_t mask = static_cast(proc); - for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) - { - if (mask & (KAFFINITY(1) << j)) - cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + const uint64_t mask = static_cast(proc); + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (mask & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + + affinity.oldApi = std::move(cpus); } + } + else + { + // If we got here it means that either SetProcessAffinityMask was never set + // or we're on Windows 11/Server 2022. + + // Since Windows 11 and Windows Server 2022 the behaviour of GetProcessAffinityMask changed + // > If, however, hHandle specifies a handle to the current process, the function + // > always uses the calling thread's primary group (which by default is the same + // > as the process' primary group) in order to set the + // > lpProcessAffinityMask and lpSystemAffinityMask. + // In which case we can actually retrieve the full affinity. + + if (GetThreadSelectedCpuSetMasks_f != nullptr) + { + std::thread th([&]() { + std::set cpus; + bool isAffinityFull = true; - affinity.oldApi = std::move(cpus); + for (auto procGroupIndex : groupAffinity) + { + const int numActiveProcessors = + GetActiveProcessorCount(static_cast(procGroupIndex)); + + // We have to schedule to 2 different processors and & the affinities we get. + // Otherwise our processor choice could influence the resulting affinity. + // We assume the processor IDs within the group are filled sequentially from 0. + uint64_t procCombined = std::numeric_limits::max(); + uint64_t sysCombined = std::numeric_limits::max(); + + for (int i = 0; i < std::min(numActiveProcessors, 2); ++i) + { + GROUP_AFFINITY GroupAffinity; + std::memset(&GroupAffinity, 0, sizeof(GROUP_AFFINITY)); + GroupAffinity.Group = static_cast(procGroupIndex); + + GroupAffinity.Mask = static_cast(1) << i; + + status = + SetThreadGroupAffinity(GetCurrentThread(), &GroupAffinity, nullptr); + if (status == 0) + { + affinity.isOldDeterminate = false; + return; + } + + SwitchToThread(); + + DWORD_PTR proc2, sys2; + status = GetProcessAffinityMask(GetCurrentProcess(), &proc2, &sys2); + if (status == 0) + { + affinity.isOldDeterminate = false; + return; + } + + procCombined &= static_cast(proc2); + sysCombined &= static_cast(sys2); + } + + if (procCombined != sysCombined) + isAffinityFull = false; + + for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + { + if (procCombined & (KAFFINITY(1) << j)) + cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); + } + } + + // We have to detect the case where the affinity was not set, or is set to all processors + // so that we correctly produce as std::nullopt result. + if (!isAffinityFull) + { + affinity.oldApi = std::move(cpus); + } + }); + + th.join(); + } } return affinity; @@ -300,6 +408,18 @@ inline std::set get_process_affinity() { #endif +#if defined(__linux__) && !defined(__ANDROID__) + +inline static const auto STARTUP_PROCESSOR_AFFINITY = get_process_affinity(); + +#elif defined(_WIN64) + +inline static const auto STARTUP_PROCESSOR_AFFINITY = get_process_affinity(); +inline static const auto STARTUP_USE_OLD_AFFINITY_API = + STARTUP_PROCESSOR_AFFINITY.likely_used_old_api(); + +#endif + // We want to abstract the purpose of storing the numa node index somewhat. // Whoever is using this does not need to know the specifics of the replication // machinery to be able to access NUMA replicated memory. @@ -326,6 +446,8 @@ class NumaReplicatedAccessToken { // It is guaranteed that NUMA nodes are NOT empty, i.e. every node exposed by NumaConfig // has at least one processor assigned. // +// We use startup affinities so as not to modify its own behaviour in time. +// // Until Stockfish doesn't support exceptions all places where an exception should be thrown // are replaced by std::exit. class NumaConfig { @@ -349,7 +471,7 @@ class NumaConfig { std::set allowedCpus; if (respectProcessAffinity) - allowedCpus = get_process_affinity(); + allowedCpus = STARTUP_PROCESSOR_AFFINITY; auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) { return !respectProcessAffinity || allowedCpus.count(c) == 1; @@ -414,7 +536,7 @@ class NumaConfig { std::optional> allowedCpus; if (respectProcessAffinity) - allowedCpus = get_process_affinity().get_combined(); + allowedCpus = STARTUP_PROCESSOR_AFFINITY.get_combined(); // The affinity can't be determined in all cases on Windows, but we at least guarantee // that the number of allowed processors is >= number of processors in the affinity mask. @@ -451,7 +573,7 @@ class NumaConfig { // still no way to set thread affinity spanning multiple processor groups. // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support // We also do this is if need to force old API for some reason. - if (use_old_affinity_api()) + if (STARTUP_USE_OLD_AFFINITY_API) { NumaConfig splitCfg = empty(); @@ -733,7 +855,7 @@ class NumaConfig { } // Sometimes we need to force the old API, but do not use it unless necessary. - if (SetThreadSelectedCpuSetMasks_f == nullptr || use_old_affinity_api()) + if (SetThreadSelectedCpuSetMasks_f == nullptr || STARTUP_USE_OLD_AFFINITY_API) { // On earlier windows version (since windows 7) we can't run a single thread // on multiple processor groups, so we need to restrict the group. From 7d4ffa175c52a425c6ebc19737586baf93f5b6ff Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 3 Jun 2024 17:47:03 -0500 Subject: [PATCH 0609/1309] Remove delta from evaluation Passed STC: https://tests.stockfishchess.org/tests/view/6660e49c6489614cdad14e29 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 188768 W: 48907 L: 48854 D: 91007 Ptnml(0-2): 584, 22571, 48005, 22656, 568 Passed LTC: https://tests.stockfishchess.org/tests/view/6660ff9791e372763104b38c LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 310680 W: 78651 L: 78727 D: 153302 Ptnml(0-2): 180, 34818, 85433, 34716, 193 closes https://github.com/official-stockfish/Stockfish/pull/5373 Bench: 1214575 --- src/evaluate.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index fdf35eb17d4..1317a01ecbd 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -47,7 +47,7 @@ int Eval::simple_eval(const Position& pos, Color c) { bool Eval::use_smallnet(const Position& pos) { int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 992; + return std::abs(simpleEval) > 962; } // Evaluate is the evaluator for the outer world. It returns a static evaluation @@ -66,25 +66,24 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) : networks.big.evaluate(pos, &caches.big); - constexpr int delta = 3; - Value nnue = ((128 - delta) * psqt + (128 + delta) * positional) / 128; - int nnueComplexity = std::abs(psqt - positional); + Value nnue = psqt + positional; + int nnueComplexity = std::abs(psqt - positional); // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 250)) + if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 227)) { std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); - nnue = ((128 - delta) * psqt + (128 + delta) * positional) / 128; + nnue = psqt + positional; nnueComplexity = std::abs(psqt - positional); smallNet = false; } // Blend optimism and eval with nnue complexity - optimism += optimism * nnueComplexity / 470; - nnue -= nnue * nnueComplexity / 20000; + optimism += optimism * nnueComplexity / 457; + nnue -= nnue * nnueComplexity / 19157; - int material = 600 * pos.count() + pos.non_pawn_material(); - v = (nnue * (68600 + material) + optimism * (8800 + material)) / 73344; + int material = 554 * pos.count() + pos.non_pawn_material(); + v = (nnue * (73921 + material) + optimism * (8112 + material)) / 73260; // Damp down the evaluation linearly when shuffling v -= v * pos.rule50_count() / 212; From 1c67b46caf91a0e6277967ea9a7e4b2f6afbc971 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Thu, 6 Jun 2024 13:10:30 -0500 Subject: [PATCH 0610/1309] Linearize corrHist Passed STC: https://tests.stockfishchess.org/tests/view/6661fff88dd8f31ed3c5d819 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 17504 W: 4651 L: 4406 D: 8447 Ptnml(0-2): 71, 1975, 4384, 2282, 40 Passed LTC: https://tests.stockfishchess.org/tests/view/666205b48dd8f31ed3c61296 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 24522 W: 6313 L: 6094 D: 12115 Ptnml(0-2): 14, 2643, 6726, 2866, 12 closes https://github.com/official-stockfish/Stockfish/pull/5374 Bench: 1237729 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 7417a4b6f5e..06adc92a3e6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -74,7 +74,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 4990; + v += cv / 10; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } From 4151c06b744a3145617200ca8f76285aae193dc2 Mon Sep 17 00:00:00 2001 From: evqsx <149484438+evqsx@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:43:55 +0800 Subject: [PATCH 0611/1309] Remove the correction history bonus in null move search Passed STC: https://tests.stockfishchess.org/tests/view/666168e191e372763104c664 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94848 W: 24708 L: 24550 D: 45590 Ptnml(0-2): 289, 11355, 24033, 11403, 344 Passed LTC: https://tests.stockfishchess.org/tests/view/6661e73591e372763104c751 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 70452 W: 17849 L: 17679 D: 34924 Ptnml(0-2): 27, 7707, 19596, 7861, 35 closes https://github.com/official-stockfish/Stockfish/pull/5375 Bench: 1174094 --- AUTHORS | 1 + src/search.cpp | 9 --------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/AUTHORS b/AUTHORS index a232e115f7a..6eefb56dd5f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -72,6 +72,7 @@ Ehsan Rashid (erashid) Elvin Liu (solarlight2) erbsenzaehler Ernesto Gatti +evqsx Fabian Beuke (madnight) Fabian Fichter (ianfab) Fanael Linithien (Fanael) diff --git a/src/search.cpp b/src/search.cpp index 06adc92a3e6..8ae12e68bf4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -805,16 +805,7 @@ Value Search::Worker::search( if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) { if (thisThread->nmpMinPly || depth < 16) - { - if (nullValue >= ss->staticEval) - { - auto bonus = std::min(int(nullValue - ss->staticEval) * depth / 32, - CORRECTION_HISTORY_LIMIT / 16); - thisThread->correctionHistory[us][pawn_structure_index(pos)] - << bonus; - } return nullValue; - } assert(!thisThread->nmpMinPly); // Recursive verification is not allowed From e271059e08c6258420af12897367ea2149220171 Mon Sep 17 00:00:00 2001 From: cj5716 <125858804+cj5716@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:30:33 +0800 Subject: [PATCH 0612/1309] Make repeated bench runs identical fixes https://github.com/official-stockfish/Stockfish/issues/5376 closes https://github.com/official-stockfish/Stockfish/pull/5377 No functional changes --- src/tt.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tt.cpp b/src/tt.cpp index 56779b861fd..5a44759e4bf 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -94,6 +94,7 @@ void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) { // Initializes the entire transposition table to zero, // in a multi-threaded way. void TranspositionTable::clear(ThreadPool& threads) { + generation8 = 0; const size_t threadCount = threads.num_threads(); for (size_t i = 0; i < threadCount; ++i) From 7e890fd048e22bfd213d46ec8eb88f7931f0315d Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 7 Jun 2024 23:53:33 +0200 Subject: [PATCH 0613/1309] Keep mate PVs intact. do not return a cutoff value in razoring if that value is in the mate/tb range. passed STC: https://tests.stockfishchess.org/tests/view/666381880ff7cb4868d1fe58 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 130848 W: 34046 L: 33931 D: 62871 Ptnml(0-2): 429, 14968, 34524, 15065, 438 passed LTC: https://tests.stockfishchess.org/tests/view/66643f120612cd151f9e7788 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 39702 W: 10157 L: 9959 D: 19586 Ptnml(0-2): 20, 4108, 11402, 4296, 25 closes https://github.com/official-stockfish/Stockfish/pull/5379 Bench: 1174094 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 8ae12e68bf4..3dbdfd47c27 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -768,7 +768,7 @@ Value Search::Worker::search( if (eval < alpha - 512 - 293 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); - if (value < alpha) + if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) return value; } From c8213ba0d047569141ed58f5eb86579d976b5614 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 10 Jun 2024 18:03:36 -0500 Subject: [PATCH 0614/1309] Simplify TT interface and avoid changing TT info This commit builds on the work and ideas of #5345, #5348, and #5364. Place as much as possible of the TT implementation in tt.cpp, rather than in the header. Some commentary is added to better document the public interface. Fix the search read-TT races, or at least contain them to within TT methods only. Passed SMP STC: https://tests.stockfishchess.org/tests/view/666134ab91e372763104b443 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 512552 W: 132387 L: 132676 D: 247489 Ptnml(0-2): 469, 58429, 138771, 58136, 471 The unmerged version has bench identical to the other PR (see also #5348) and therefore those same-functionality tests: SMP LTC: https://tests.stockfishchess.org/tests/view/665c7021fd45fb0f907c214a SMP LTC: https://tests.stockfishchess.org/tests/view/665d28a7fd45fb0f907c5495 closes https://github.com/official-stockfish/Stockfish/pull/5369 bench 1205675 --- src/search.cpp | 199 +++++++++++++++++++++--------------------- src/tt.cpp | 126 ++++++++++++++++++++++---- src/tt.h | 119 ++++++++++--------------- tests/instrumented.sh | 7 +- 4 files changed, 254 insertions(+), 197 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3dbdfd47c27..9c3f915db34 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -546,16 +546,15 @@ Value Search::Worker::search( StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - TTEntry* tte; - Key posKey; - Move ttMove, move, excludedMove, bestMove; - Depth extension, newDepth; - Value bestValue, value, ttValue, eval, maxValue, probCutBeta, singularValue; - bool givesCheck, improving, priorCapture, opponentWorsening; - bool capture, moveCountPruning, ttCapture; - Piece movedPiece; - int moveCount, captureCount, quietCount; - Bound singularBound; + Key posKey; + Move move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, eval, maxValue, probCutBeta, singularValue; + bool givesCheck, improving, priorCapture, opponentWorsening; + bool capture, moveCountPruning, ttCapture; + Piece movedPiece; + int moveCount, captureCount, quietCount; + Bound singularBound; // Step 1. Initialize node Worker* thisThread = this; @@ -605,31 +604,32 @@ Value Search::Worker::search( ss->statScore = 0; // Step 4. Transposition table lookup. - excludedMove = ss->excludedMove; - posKey = pos.key(); - tte = tt.probe(posKey, ss->ttHit); - ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] - : ss->ttHit ? tte->move() - : Move::none(); - ttCapture = ttMove && pos.capture_stage(ttMove); + excludedMove = ss->excludedMove; + posKey = pos.key(); + auto [ttHit, ttData, ttWriter] = tt.probe(posKey); + // Need further processing of the saved data + ss->ttHit = ttHit; + ttData.move = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] + : ttHit ? ttData.move + : Move::none(); + ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE; + ss->ttPv = excludedMove ? ss->ttPv : PvNode || (ttHit && ttData.is_pv); + ttCapture = ttData.move && pos.capture_stage(ttData.move); // At this point, if excluded, skip straight to step 6, static eval. However, // to save indentation, we list the condition in all code between here and there. - if (!excludedMove) - ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); // At non-PV nodes we check for an early TT cutoff - if (!PvNode && !excludedMove && tte->depth() > depth - (ttValue <= beta) - && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit - && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) + if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) + && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() + && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) - if (ttMove && ttValue >= beta) + if (ttData.move && ttData.value >= beta) { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_stats(pos, ss, *this, ttMove, stat_bonus(depth)); + update_quiet_stats(pos, ss, *this, ttData.move, stat_bonus(depth)); // Extra penalty for early quiet moves of // the previous ply (~1 Elo on STC, ~2 Elo on LTC) @@ -641,7 +641,7 @@ Value Search::Worker::search( // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) - return ttValue; + return ttData.value; } // Step 5. Tablebases probe @@ -679,9 +679,9 @@ Value Search::Worker::search( if (b == BOUND_EXACT || (b == BOUND_LOWER ? value >= beta : value <= alpha)) { - tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, - std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE, - tt.generation()); + ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, + std::min(MAX_PLY - 1, depth + 6), Move::none(), VALUE_NONE, + tt.generation()); return value; } @@ -716,7 +716,7 @@ Value Search::Worker::search( else if (ss->ttHit) { // Never assume anything about values stored in TT - unadjustedStaticEval = tte->eval(); + unadjustedStaticEval = ttData.eval; if (unadjustedStaticEval == VALUE_NONE) unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); @@ -726,8 +726,9 @@ Value Search::Worker::search( ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~7 Elo) - if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) - eval = ttValue; + if (ttData.value != VALUE_NONE + && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) + eval = ttData.value; } else { @@ -736,8 +737,8 @@ Value Search::Worker::search( ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history - tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), - unadjustedStaticEval, tt.generation()); + ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), + unadjustedStaticEval, tt.generation()); } // Use static evaluation difference to improve quiet move ordering (~9 Elo) @@ -778,7 +779,7 @@ Value Search::Worker::search( && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - (ss - 1)->statScore / 263 >= beta - && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) + && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttData.move || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval; // Step 9. Null move search with verification search (~35 Elo) @@ -824,7 +825,7 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions (~9 Elo) // For PV nodes without a ttMove, we decrease depth by 3. - if (PvNode && !ttMove) + if (PvNode && !ttData.move) depth -= 3; // Use qsearch if depth <= 0. @@ -833,8 +834,8 @@ Value Search::Worker::search( // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, or // by 1 if there is a ttMove with an upper bound. - if (cutNode && depth >= 8 && (!ttMove || tte->bound() == BOUND_UPPER)) - depth -= 1 + !ttMove; + if (cutNode && depth >= 8 && (!ttData.move || ttData.bound == BOUND_UPPER)) + depth -= 1 + !ttData.move; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value @@ -847,11 +848,11 @@ Value Search::Worker::search( // 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 - && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta)) + && !(ttData.depth >= depth - 3 && ttData.value != VALUE_NONE && ttData.value < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); - MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->captureHistory); + MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) @@ -882,8 +883,8 @@ Value Search::Worker::search( if (value >= probCutBeta) { // Save ProbCut data into transposition table - tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, - move, unadjustedStaticEval, tt.generation()); + ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, + depth - 3, move, unadjustedStaticEval, tt.generation()); return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) : value; } @@ -896,9 +897,10 @@ Value Search::Worker::search( // Step 12. A small Probcut idea, when we are in check (~4 Elo) probCutBeta = beta + 388; - if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 4 && ttValue >= probCutBeta - && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + if (ss->inCheck && !PvNode && ttCapture && (ttData.bound & BOUND_LOWER) + && ttData.depth >= depth - 4 && ttData.value >= probCutBeta + && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -911,7 +913,7 @@ Value Search::Worker::search( Move countermove = prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory, countermove, ss->killers); value = bestValue; @@ -1046,12 +1048,12 @@ Value Search::Worker::search( // Generally, higher singularBeta (i.e closer to ttValue) and lower extension // margins scale well. - if (!rootNode && move == ttMove && !excludedMove + if (!rootNode && move == ttData.move && !excludedMove && depth >= 4 - (thisThread->completedDepth > 35) + ss->ttPv - && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) - && tte->depth() >= depth - 3) + && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER) + && ttData.depth >= depth - 3) { - Value singularBeta = ttValue - (52 + 80 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttData.value - (52 + 80 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1086,7 +1088,7 @@ Value Search::Worker::search( // 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) - else if (ttValue >= 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) @@ -1126,7 +1128,7 @@ Value Search::Worker::search( // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); + r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth); // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) @@ -1136,8 +1138,8 @@ Value Search::Worker::search( // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2 - (tte->depth() >= depth && ss->ttPv) - + (!ss->ttPv && move != ttMove && move != ss->killers[0]); + r += 2 - (ttData.depth >= depth && ss->ttPv) + + (!ss->ttPv && move != ttData.move && move != ss->killers[0]); // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) @@ -1149,7 +1151,7 @@ Value Search::Worker::search( // For first picked move (ttMove) reduce reduction // but never allow it to go below 0 (~3 Elo) - else if (move == ttMove) + else if (move == ttData.move) r = std::max(0, r - 2); ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] @@ -1197,7 +1199,7 @@ Value Search::Worker::search( else if (!PvNode || moveCount > 1) { // Increase reduction if ttMove is not present (~6 Elo) - if (!ttMove) + if (!ttData.move) r += 2; // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) @@ -1287,7 +1289,7 @@ Value Search::Worker::search( if (value >= beta) { - ss->cutoffCnt += 1 + !ttMove - (extension >= 2); + ss->cutoffCnt += 1 + !ttData.move - (extension >= 2); assert(value >= beta); // Fail high break; } @@ -1363,11 +1365,11 @@ Value Search::Worker::search( // Write gathered information in transposition table // Static evaluation is saved as it was before correction history if (!excludedMove && !(rootNode && thisThread->pvIdx)) - tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, - bestValue >= beta ? BOUND_LOWER - : PvNode && bestMove ? BOUND_EXACT - : BOUND_UPPER, - depth, bestMove, unadjustedStaticEval, tt.generation()); + ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, + bestValue >= beta ? BOUND_LOWER + : PvNode && bestMove ? BOUND_EXACT + : BOUND_UPPER, + depth, bestMove, unadjustedStaticEval, tt.generation()); // Adjust correction history if (!ss->inCheck && (!bestMove || !pos.capture(bestMove)) @@ -1414,14 +1416,12 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - TTEntry* tte; - Key posKey; - Move ttMove, move, bestMove; - Depth ttDepth; - Value bestValue, value, ttValue, futilityBase; - bool pvHit, givesCheck, capture; - int moveCount; - Color us = pos.side_to_move(); + Key posKey; + Move move, bestMove; + Value bestValue, value, futilityBase; + bool pvHit, givesCheck, capture; + int moveCount; + Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) @@ -1447,23 +1447,25 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(0 <= ss->ply && ss->ply < MAX_PLY); - // Note that unlike regular search, which stores literal depth, in QS we only store the - // current movegen stage. If in check, we search all evasions and thus store - // DEPTH_QS_CHECKS. (Evasions may be quiet, and _CHECKS includes quiets.) - ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NORMAL; + // Note that unlike regular search, which stores the literal depth into the TT, from QS we + // only store the current movegen stage as "depth". If in check, we search all evasions and + // thus store DEPTH_QS_CHECKS. (Evasions may be quiet, and _CHECKS includes quiets.) + Depth qsTtDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NORMAL; // Step 3. Transposition table lookup - posKey = pos.key(); - tte = tt.probe(posKey, ss->ttHit); - ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; - ttMove = ss->ttHit ? tte->move() : Move::none(); - pvHit = ss->ttHit && tte->is_pv(); + posKey = pos.key(); + auto [ttHit, ttData, ttWriter] = tt.probe(posKey); + // Need further processing of the saved data + ss->ttHit = ttHit; + ttData.move = ttHit ? ttData.move : Move::none(); + ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE; + pvHit = ttHit && ttData.is_pv; // At non-PV nodes we check for an early TT cutoff - if (!PvNode && tte->depth() >= ttDepth - && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit - && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER))) - return ttValue; + if (!PvNode && ttData.depth >= qsTtDepth + && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() + && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))) + return ttData.value; // Step 4. Static evaluation of the position Value unadjustedStaticEval = VALUE_NONE; @@ -1474,7 +1476,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (ss->ttHit) { // Never assume anything about values stored in TT - unadjustedStaticEval = tte->eval(); + unadjustedStaticEval = ttData.eval; if (unadjustedStaticEval == VALUE_NONE) unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); @@ -1482,9 +1484,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // ttValue can be used as a better position evaluation (~13 Elo) - if (std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY - && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) - bestValue = ttValue; + if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY + && (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER))) + bestValue = ttData.value; } else { @@ -1503,9 +1505,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && !PvNode) bestValue = (3 * bestValue + beta) / 4; if (!ss->ttHit) - tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, - DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, tt.generation()); - + ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, + DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, + tt.generation()); return bestValue; } @@ -1524,7 +1526,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // (Presently, having the checks stage is worth only 1 Elo, and may be removable in the near future, // which would result in only a single stage of QS movegen.) Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, + MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs. @@ -1643,9 +1645,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Save gathered info in transposition table // Static evaluation is saved as it was before adjustment by correction history - tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, - bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, - unadjustedStaticEval, tt.generation()); + ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit, + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, qsTtDepth, bestMove, + unadjustedStaticEval, tt.generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1986,20 +1988,17 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); - bool ttHit; - assert(pv.size() == 1); if (pv[0] == Move::none()) return false; pos.do_move(pv[0], st); - TTEntry* tte = tt.probe(pos.key(), ttHit); + auto [ttHit, ttData, ttWriter] = tt.probe(pos.key()); if (ttHit) { - Move m = tte->move(); // Local copy to be SMP safe - if (MoveList(pos).contains(m)) - pv.push_back(m); + if (MoveList(pos).contains(ttData.move)) + pv.push_back(ttData.move); } pos.undo_move(pv[0]); diff --git a/src/tt.cpp b/src/tt.cpp index 5a44759e4bf..763e2c9b349 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -25,11 +25,63 @@ #include #include "memory.h" +#include "misc.h" #include "syzygy/tbprobe.h" #include "thread.h" namespace Stockfish { + +// TTEntry struct is the 10 bytes transposition table entry, defined as below: +// +// key 16 bit +// depth 8 bit +// generation 5 bit +// pv node 1 bit +// bound type 2 bit +// move 16 bit +// value 16 bit +// evaluation 16 bit +// +// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially. +// Equally, the store order in save() matches this order. + +struct TTEntry { + + // Convert internal bitfields to external types + TTData read() const { + return TTData{Move(move16), Value(value16), + Value(eval16), Depth(depth8 + DEPTH_ENTRY_OFFSET), + Bound(genBound8 & 0x3), bool(genBound8 & 0x4)}; + } + + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); + // The returned age is a multiple of TranspositionTable::GENERATION_DELTA + uint8_t relative_age(const uint8_t generation8) const; + + private: + friend class TranspositionTable; + + uint16_t key16; + uint8_t depth8; + uint8_t genBound8; + Move move16; + int16_t value16; + int16_t eval16; +}; + +// `genBound8` is where most of the details are. We use the following constants to manipulate 5 leading generation bits +// and 3 trailing miscellaneous bits. + +// These bits are reserved for other things. +static constexpr unsigned GENERATION_BITS = 3; +// increment for generation field +static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); +// cycle length +static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA; +// mask to pull out generation number +static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; + // DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but // 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits": // we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted below.) @@ -65,12 +117,34 @@ uint8_t TTEntry::relative_age(const uint8_t generation8) const { // is needed to keep the unrelated lowest n bits from affecting // the result) to calculate the entry age correctly even after // generation8 overflows into the next cycle. + return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK; +} + - return (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8) - & TranspositionTable::GENERATION_MASK; +// TTWriter is but a very thin wrapper around the pointer +TTWriter::TTWriter(TTEntry* tte) : + entry(tte) {} + +void TTWriter::write( + Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) { + entry->save(k, v, pv, b, d, m, ev, generation8); } +// A TranspositionTable is an array of Cluster, of size clusterCount. Each cluster consists of ClusterSize number +// of TTEntry. Each non-empty TTEntry contains information on exactly one position. The size of a Cluster should +// divide the size of a cache line for best performance, as the cacheline is prefetched when possible. + +static constexpr int ClusterSize = 3; + +struct Cluster { + TTEntry entry[ClusterSize]; + char padding[2]; // Pad to 32 bytes +}; + +static_assert(sizeof(Cluster) == 32, "Suboptimal Cluster size"); + + // Sets the size of the transposition table, // measured in megabytes. Transposition table consists // of clusters and each cluster consists of ClusterSize number of TTEntry. @@ -114,20 +188,46 @@ void TranspositionTable::clear(ThreadPool& threads) { } +// Returns an approximation of the hashtable +// occupation during a search. The hash is x permill full, as per UCI protocol. +// Only counts entries which match the current generation. +int TranspositionTable::hashfull() const { + + int cnt = 0; + for (int i = 0; i < 1000; ++i) + for (int j = 0; j < ClusterSize; ++j) + cnt += table[i].entry[j].depth8 + && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; + + return cnt / ClusterSize; +} + + +void TranspositionTable::new_search() { + // increment by delta to keep lower bits as is + generation8 += GENERATION_DELTA; +} + + +uint8_t TranspositionTable::generation() const { return generation8; } + + // Looks up the current position in the transposition -// table. It returns true and a pointer to the TTEntry if the position is found. +// table. It returns true if the position is found. // Otherwise, it returns false and a pointer to an empty or least valuable TTEntry // to be replaced later. The replace value of an entry is calculated as its depth // minus 8 times its relative age. TTEntry t1 is considered more valuable than // TTEntry t2 if its replace value is greater than that of t2. -TTEntry* TranspositionTable::probe(const Key key, bool& found) const { +std::tuple TranspositionTable::probe(const Key key) const { TTEntry* const tte = first_entry(key); const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster for (int i = 0; i < ClusterSize; ++i) if (tte[i].key16 == key16) - return found = bool(tte[i].depth8), &tte[i]; + // This gap is the main place for read races. + // After `read()` completes that copy is final, but may be self-inconsistent. + return {bool(tte[i].depth8), tte[i].read(), TTWriter(&tte[i])}; // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; @@ -136,22 +236,12 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const { > tte[i].depth8 - tte[i].relative_age(generation8) * 2) replace = &tte[i]; - return found = false, replace; + return {false, replace->read(), TTWriter(replace)}; } -// Returns an approximation of the hashtable -// occupation during a search. The hash is x permill full, as per UCI protocol. -// Only counts entries which match the current generation. -int TranspositionTable::hashfull() const { - - int cnt = 0; - for (int i = 0; i < 1000; ++i) - for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].depth8 - && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; - - return cnt / ClusterSize; +TTEntry* TranspositionTable::first_entry(const Key key) const { + return &table[mul_hi64(key, clusterCount)].entry[0]; } } // namespace Stockfish diff --git a/src/tt.h b/src/tt.h index b2e8f582b3b..1bece002c7b 100644 --- a/src/tt.h +++ b/src/tt.h @@ -21,103 +21,76 @@ #include #include +#include #include "memory.h" -#include "misc.h" #include "types.h" namespace Stockfish { -// TTEntry struct is the 10 bytes transposition table entry, defined as below: -// -// key 16 bit -// depth 8 bit -// generation 5 bit -// pv node 1 bit -// bound type 2 bit -// move 16 bit -// value 16 bit -// eval value 16 bit +class ThreadPool; +struct TTEntry; +struct Cluster; + +// There is only one global hash table for the engine and all its threads. For chess in particular, we even allow racy +// updates between threads to and from the TT, as taking the time to synchronize access would cost thinking time and +// thus elo. As a hash table, collisions are possible and may cause chess playing issues (bizarre blunders, faulty mate +// reports, etc). Fixing these also loses elo; however such risk decreases quickly with larger TT size. // -// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially. -// Equally, the store order in save() matches this order. -struct TTEntry { - - Move move() const { return Move(move16); } - Value value() const { return Value(value16); } - Value eval() const { return Value(eval16); } - Depth depth() const { return Depth(depth8 + DEPTH_ENTRY_OFFSET); } - bool is_pv() const { return bool(genBound8 & 0x4); } - Bound bound() const { return Bound(genBound8 & 0x3); } - void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); - // The returned age is a multiple of TranspositionTable::GENERATION_DELTA - uint8_t relative_age(const uint8_t generation8) const; +// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of: +// 1) whether the entry already has this position +// 2) a copy of the prior data (if any) (may be inconsistent due to read races) +// 3) a writer object to this entry +// The copied data and the writer are separated to maintain clear boundaries between local vs global objects. + + +// A copy of the data already in the entry (possibly collided). `probe` may be racy, resulting in inconsistent data. +struct TTData { + Move move; + Value value, eval; + Depth depth; + Bound bound; + bool is_pv; +}; + + +// This is used to make racy writes to the global TT. +struct TTWriter { + public: + void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); private: friend class TranspositionTable; - - uint16_t key16; - uint8_t depth8; - uint8_t genBound8; - Move move16; - int16_t value16; - int16_t eval16; + TTEntry* entry; + TTWriter(TTEntry* tte); }; -class ThreadPool; -// A TranspositionTable is an array of Cluster, of size clusterCount. Each -// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry -// contains information on exactly one position. The size of a Cluster should -// divide the size of a cache line for best performance, as the cacheline is -// prefetched when possible. class TranspositionTable { - static constexpr int ClusterSize = 3; - - struct Cluster { - TTEntry entry[ClusterSize]; - char padding[2]; // Pad to 32 bytes - }; - - static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); - - // Constants used to refresh the hash table periodically - - // We have 8 bits available where the lowest 3 bits are - // reserved for other things. - static constexpr unsigned GENERATION_BITS = 3; - // increment for generation field - static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); - // cycle length - static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA; - // mask to pull out generation number - static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; - public: ~TranspositionTable() { aligned_large_pages_free(table); } - void new_search() { - // increment by delta to keep lower bits as is - generation8 += GENERATION_DELTA; - } - TTEntry* probe(const Key key, bool& found) const; - int hashfull() const; - void resize(size_t mbSize, ThreadPool& threads); - void clear(ThreadPool& threads); + void resize(size_t mbSize, ThreadPool& threads); // Set TT size + void clear(ThreadPool& threads); // Re-initialize memory, multithreaded + int hashfull() + const; // Approximate what fraction of entries (permille) have been written to during this root search - TTEntry* first_entry(const Key key) const { - return &table[mul_hi64(key, clusterCount)].entry[0]; - } - - uint8_t generation() const { return generation8; } + void + new_search(); // This must be called at the beginning of each root search to track entry aging + uint8_t generation() const; // The current age, used when writing new data to the TT + std::tuple + probe(const Key key) const; // The main method, whose retvals separate local vs global objects + TTEntry* first_entry(const Key key) + const; // This is the hash function; its only external use is memory prefetching. private: friend struct TTEntry; size_t clusterCount; - Cluster* table = nullptr; - uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 + Cluster* table = nullptr; + + uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8 }; } // namespace Stockfish diff --git a/tests/instrumented.sh b/tests/instrumented.sh index 4c63fc5714e..e77ee0dd2be 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -39,13 +39,8 @@ case $1 in threads="2" cat << EOF > tsan.supp -race:Stockfish::TTEntry::move -race:Stockfish::TTEntry::depth -race:Stockfish::TTEntry::bound +race:Stockfish::TTEntry::read race:Stockfish::TTEntry::save -race:Stockfish::TTEntry::value -race:Stockfish::TTEntry::eval -race:Stockfish::TTEntry::is_pv race:Stockfish::TranspositionTable::probe race:Stockfish::TranspositionTable::hashfull From 7013a22b741b9fa937e0e027c4992c52b999283c Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Jun 2024 22:29:27 +0200 Subject: [PATCH 0615/1309] Move options into the engine Move the engine options into the engine class, also avoid duplicated initializations after startup. UCIEngine needs to register an add_listener to listen to all option changes and print these. Also avoid a double initialization of the TT, which was the case with the old state. closes https://github.com/official-stockfish/Stockfish/pull/5356 No functional change --- src/engine.cpp | 84 +++++++++++++++++++++++++++++++++++++++++++++-- src/engine.h | 11 +++++-- src/tune.cpp | 16 ++++----- src/tune.h | 2 ++ src/uci.cpp | 81 ++++++++------------------------------------- src/uci.h | 5 +-- src/ucioption.cpp | 28 ++++++++++++---- src/ucioption.h | 72 ++++++++++++++++++++++++++-------------- 8 files changed, 183 insertions(+), 116 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 6980dd8341c..233f6270110 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -44,7 +44,8 @@ namespace Stockfish { namespace NN = Eval::NNUE; -constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; Engine::Engine(std::string path) : binaryDirectory(CommandLine::get_binary_directory(path)), @@ -58,6 +59,58 @@ Engine::Engine(std::string path) : NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { pos.set(StartFEN, false, &states->back()); capSq = SQ_NONE; + + 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_binding_information_as_string(); + }); + + options["Threads"] << Option(1, 1, 1024, [this](const Option&) { + resize_threads(); + return thread_binding_information_as_string(); + }); + + options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { + set_tt_size(o); + return std::nullopt; + }); + + 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(1320, 1320, 3190); + 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; + }); + + load_networks(); + resize_threads(); } std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { @@ -212,7 +265,8 @@ void Engine::trace_eval() const { sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl; } -OptionsMap& Engine::get_options() { return options; } +const OptionsMap& Engine::get_options() const { return options; } +OptionsMap& Engine::get_options() { return options; } std::string Engine::fen() const { return pos.fen(); } @@ -241,4 +295,30 @@ std::string Engine::get_numa_config_as_string() const { return numaContext.get_numa_config().to_string(); } +std::string Engine::numa_config_information_as_string() const { + auto cfgStr = get_numa_config_as_string(); + return "Available Processors: " + cfgStr; +} + +std::string Engine::thread_binding_information_as_string() const { + auto boundThreadsByNode = get_bound_thread_count_by_numa_node(); + if (boundThreadsByNode.empty()) + return ""; + + std::stringstream ss; + ss << "NUMA Node Thread Binding: "; + + bool isFirst = true; + + for (auto&& [current, total] : boundThreadsByNode) + { + if (!isFirst) + ss << ":"; + ss << current << "/" << total; + isFirst = false; + } + + return ss.str(); +} + } diff --git a/src/engine.h b/src/engine.h index 91a8a96b0dc..0d6f0f2b826 100644 --- a/src/engine.h +++ b/src/engine.h @@ -29,13 +29,13 @@ #include #include "nnue/network.h" +#include "numa.h" #include "position.h" #include "search.h" #include "syzygy/tbprobe.h" // for Stockfish::Depth #include "thread.h" #include "tt.h" #include "ucioption.h" -#include "numa.h" namespace Stockfish { @@ -92,13 +92,18 @@ class Engine { // utility functions - void trace_eval() const; - OptionsMap& get_options(); + void trace_eval() const; + + const OptionsMap& get_options() const; + OptionsMap& get_options(); + std::string fen() const; void flip(); std::string visualize() const; std::vector> get_bound_thread_count_by_numa_node() const; std::string get_numa_config_as_string() const; + std::string numa_config_information_as_string() const; + std::string thread_binding_information_as_string() const; private: const std::string binaryDirectory; diff --git a/src/tune.cpp b/src/tune.cpp index 94c9b53ecc6..dfcd34689d4 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -33,19 +34,19 @@ namespace Stockfish { bool Tune::update_on_last; const Option* LastOption = nullptr; OptionsMap* Tune::options; - - namespace { std::map TuneResults; -void on_tune(const Option& o) { +std::optional on_tune(const Option& o) { if (!Tune::update_on_last || LastOption == &o) Tune::read_options(); -} + return std::nullopt; +} +} -void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) { +void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& r) { // Do not generate option when there is nothing to tune (ie. min = max) if (r(v).first == r(v).second) @@ -54,8 +55,8 @@ void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) if (TuneResults.count(n)) v = TuneResults[n]; - (*options)[n] << Option(v, r(v).first, r(v).second, on_tune); - LastOption = &((*options)[n]); + (*opts)[n] << Option(v, r(v).first, r(v).second, on_tune); + LastOption = &((*opts)[n]); // Print formatted parameters, ready to be copy-pasted in Fishtest std::cout << n << "," // @@ -65,7 +66,6 @@ void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) << (r(v).second - r(v).first) / 20.0 << "," // << "0.0020" << std::endl; } -} string Tune::next(string& names, bool pop) { diff --git a/src/tune.h b/src/tune.h index 079614db28a..ed4738cdc47 100644 --- a/src/tune.h +++ b/src/tune.h @@ -145,6 +145,8 @@ class Tune { return add(value, (next(names), std::move(names)), args...); } + static void make_option(OptionsMap* options, const std::string& n, int v, const SetRange& r); + std::vector> list; public: diff --git a/src/uci.cpp b/src/uci.cpp index 42c69cdeff2..75b7dfc77a4 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -30,20 +30,16 @@ #include "benchmark.h" #include "engine.h" -#include "evaluate.h" #include "movegen.h" #include "position.h" #include "score.h" #include "search.h" -#include "syzygy/tbprobe.h" #include "types.h" #include "ucioption.h" namespace Stockfish { -constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; - +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; template struct overload: Ts... { using Ts::operator()...; @@ -56,55 +52,25 @@ UCIEngine::UCIEngine(int argc, char** argv) : engine(argv[0]), cli(argc, argv) { - auto& options = engine.get_options(); - - options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); + engine.get_options().add_info_listener([](const std::optional& str) { + if (!str || (*str).empty()) + return; - options["NumaPolicy"] << Option("auto", [this](const Option& o) { - engine.set_numa_config_from_option(o); - print_numa_config_information(); - print_thread_binding_information(); - }); + // split all lines + auto ss = std::istringstream{*str}; - options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - engine.resize_threads(); - print_thread_binding_information(); + for (std::string line; std::getline(ss, line, '\n');) + sync_cout << "info string " << line << sync_endl; }); - options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { engine.set_tt_size(o); }); - - options["Clear Hash"] << Option([this](const Option&) { engine.search_clear(); }); - 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(1320, 1320, 3190); - options["UCI_ShowWDL"] << Option(false); - options["SyzygyPath"] << Option("", [](const Option& o) { Tablebases::init(o); }); - options["SyzygyProbeDepth"] << Option(1, 1, 100); - options["Syzygy50MoveRule"] << Option(true); - options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, - [this](const Option& o) { engine.load_big_network(o); }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, - [this](const Option& o) { engine.load_small_network(o); }); - - engine.set_on_iter([](const auto& i) { on_iter(i); }); engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); }); - engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); + engine.set_on_update_full( + [this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); }); engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); }); - - engine.load_networks(); - engine.resize_threads(); - engine.search_clear(); // After threads are up } void UCIEngine::loop() { - std::string token, cmd; for (int i = 1; i < cli.argc; ++i) @@ -136,8 +102,9 @@ void UCIEngine::loop() { sync_cout << "id name " << engine_info(true) << "\n" << engine.get_options() << sync_endl; - print_numa_config_information(); - print_thread_binding_information(); + sync_cout << "info string " << engine.numa_config_information_as_string() << sync_endl; + sync_cout << "info string " << engine.thread_binding_information_as_string() + << sync_endl; sync_cout << "uciok" << sync_endl; } @@ -193,28 +160,6 @@ void UCIEngine::loop() { } while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } -void UCIEngine::print_numa_config_information() const { - auto cfgStr = engine.get_numa_config_as_string(); - sync_cout << "info string Available Processors: " << cfgStr << sync_endl; -} - -void UCIEngine::print_thread_binding_information() const { - auto boundThreadsByNode = engine.get_bound_thread_count_by_numa_node(); - if (!boundThreadsByNode.empty()) - { - sync_cout << "info string NUMA Node Thread Binding: "; - bool isFirst = true; - for (auto&& [current, total] : boundThreadsByNode) - { - if (!isFirst) - std::cout << ":"; - std::cout << current << "/" << total; - isFirst = false; - } - std::cout << sync_endl; - } -} - Search::LimitsType UCIEngine::parse_limits(std::istream& is) { Search::LimitsType limits; std::string token; diff --git a/src/uci.h b/src/uci.h index bac62bb90c0..122bcc40cf4 100644 --- a/src/uci.h +++ b/src/uci.h @@ -19,10 +19,10 @@ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED +#include #include #include #include -#include #include "engine.h" #include "misc.h" @@ -42,9 +42,6 @@ class UCIEngine { void loop(); - void print_numa_config_information() const; - void print_thread_binding_information() const; - static int to_cp(Value v, const Position& pos); static std::string format_score(const Score& s); static std::string square(Square s); diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 4819a68db73..1cd028c9932 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -36,6 +36,8 @@ bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); } +void OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); } + void OptionsMap::setoption(std::istringstream& is) { std::string token, name, value; @@ -57,13 +59,20 @@ void OptionsMap::setoption(std::istringstream& is) { Option OptionsMap::operator[](const std::string& name) const { auto it = options_map.find(name); - return it != options_map.end() ? it->second : Option(); + return it != options_map.end() ? it->second : Option(this); } -Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; } +Option& OptionsMap::operator[](const std::string& name) { + if (!options_map.count(name)) + options_map[name] = Option(this); + return options_map[name]; +} std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } +Option::Option(const OptionsMap* map) : + parent(map) {} + Option::Option(const char* v, OnChange f) : type("string"), min(0), @@ -127,10 +136,12 @@ void Option::operator<<(const Option& o) { static size_t insert_order = 0; - *this = o; - idx = insert_order++; -} + 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 @@ -159,7 +170,12 @@ Option& Option::operator=(const std::string& v) { currentValue = v; if (on_change) - on_change(*this); + { + const auto ret = on_change(*this); + + if (ret && parent != nullptr && parent->info != nullptr) + parent->info(ret); + } return *this; } diff --git a/src/ucioption.h b/src/ucioption.h index 16d46696145..a47cc98de53 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -23,6 +23,7 @@ #include #include #include +#include #include namespace Stockfish { @@ -31,31 +32,14 @@ struct CaseInsensitiveLess { bool operator()(const std::string&, const std::string&) const; }; -class Option; - -class OptionsMap { - public: - void setoption(std::istringstream&); - - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); - - Option operator[](const std::string&) const; - Option& operator[](const std::string&); - - std::size_t count(const std::string&) const; - - private: - // The options container is defined as a std::map - using OptionsStore = std::map; - - OptionsStore options_map; -}; +class OptionsMap; // The Option class implements each option as specified by the UCI protocol class Option { public: - using OnChange = std::function; + using OnChange = std::function(const Option&)>; + Option(const OptionsMap*); Option(OnChange = nullptr); Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); @@ -63,7 +47,6 @@ class Option { Option(const char* v, const char* cur, OnChange = nullptr); Option& operator=(const std::string&); - void operator<<(const Option&); operator int() const; operator std::string() const; bool operator==(const char*) const; @@ -72,10 +55,49 @@ class Option { friend std::ostream& operator<<(std::ostream&, const OptionsMap&); private: - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; + friend class OptionsMap; + friend class Engine; + friend class Tune; + + void operator<<(const Option&); + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; + const OptionsMap* parent = nullptr; +}; + +class OptionsMap { + public: + using InfoListener = std::function)>; + + OptionsMap() = default; + OptionsMap(const OptionsMap&) = delete; + OptionsMap(OptionsMap&&) = delete; + OptionsMap& operator=(const OptionsMap&) = delete; + OptionsMap& operator=(OptionsMap&&) = delete; + + void add_info_listener(InfoListener&&); + + void setoption(std::istringstream&); + + Option operator[](const std::string&) const; + Option& operator[](const std::string&); + + std::size_t count(const std::string&) const; + + private: + friend class Engine; + friend class Option; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + // The options container is defined as a std::map + using OptionsStore = std::map; + + OptionsStore options_map; + InfoListener info; }; } From 025da6a0d1f96c1743c0ea6b182487bc2f78082c Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 8 Jun 2024 14:57:09 -0700 Subject: [PATCH 0616/1309] Give positional output more weight in nnue eval This effectively reverts the removal of delta in: https://github.com/official-stockfish/Stockfish/pull/5373 Passed STC: https://tests.stockfishchess.org/tests/view/6664d41922234461cef58e6b LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 56448 W: 14849 L: 14500 D: 27099 Ptnml(0-2): 227, 6481, 14457, 6834, 225 Passed LTC: https://tests.stockfishchess.org/tests/view/666587a1996b40829f4ee007 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 91686 W: 23402 L: 22963 D: 45321 Ptnml(0-2): 78, 10205, 24840, 10640, 80 closes https://github.com/official-stockfish/Stockfish/pull/5382 bench 1160467 --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1317a01ecbd..4e895fd366f 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -66,14 +66,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) : networks.big.evaluate(pos, &caches.big); - Value nnue = psqt + positional; + Value nnue = (125 * psqt + 131 * positional) / 128; int nnueComplexity = std::abs(psqt - positional); // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 227)) { std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); - nnue = psqt + positional; + nnue = (125 * psqt + 131 * positional) / 128; nnueComplexity = std::abs(psqt - positional); smallNet = false; } From 3d92950859e1d45dad60d276dd7a78fbeb097bcb Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Tue, 11 Jun 2024 21:28:11 +0200 Subject: [PATCH 0617/1309] Limit depth after extensions to avoid asserts. currently extensions can cause depth to exceed MAX_PLY. This triggers the assert near line 542 in search when running a binary compiled with `debug=yes` on a testcase like: ``` position fen 7K/P1p1p1p1/2P1P1Pk/6pP/3p2P1/1P6/3P4/8 w - - 0 1 go nodes 1000000 ``` passed STC https://tests.stockfishchess.org/tests/view/6668a56a602682471b064c8d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 143936 W: 37338 L: 37238 D: 69360 Ptnml(0-2): 514, 16335, 38149, 16477, 493 closes https://github.com/official-stockfish/Stockfish/pull/5383 Bench: 1160467 --- src/search.cpp | 3 +++ tests/instrumented.sh | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 9c3f915db34..91b3c7894fb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -528,6 +528,9 @@ Value Search::Worker::search( if (depth <= 0) return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); + // Limit the depth if extensions made it too large + depth = std::min(depth, MAX_PLY - 1); + // Check if we have an upcoming move that draws by repetition, or // if the opponent had an alternative move earlier to this position. if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index e77ee0dd2be..cb5a3a9f245 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -170,6 +170,11 @@ cat << EOF > game.exp expect "score mate -1" expect "bestmove" + send "ucinewgame\n" + send "position fen 7K/P1p1p1p1/2P1P1Pk/6pP/3p2P1/1P6/3P4/8 w - - 0 1\n" + send "go nodes 500000\n" + expect "bestmove" + send "ucinewgame\n" send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" send "go depth 18\n" From 7c0607d2d36afd7b34e686e85711aca3d77c7ecf Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Wed, 12 Jun 2024 16:54:15 +0200 Subject: [PATCH 0618/1309] Fix printing of empty info strings. Handle printing of `info string` in a single place. Fixes #5386 closes https://github.com/official-stockfish/Stockfish/pull/5387 No functional change --- src/misc.cpp | 6 ++++++ src/misc.h | 4 ++++ src/uci.cpp | 27 ++++++++++++++++----------- src/uci.h | 2 ++ 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index a8bb46ec3c1..e97d58b939f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -371,6 +371,8 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) { return os; } +void sync_cout_start() { std::cout << IO_LOCK; } +void sync_cout_end() { std::cout << IO_UNLOCK; } // Trampoline helper to avoid moving Logger to misc.h void start_logger(const std::string& fname) { Logger::start(fname); } @@ -419,6 +421,10 @@ void remove_whitespace(std::string& s) { s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return std::isspace(c); }), s.end()); } +bool is_whitespace(const std::string& s) { + return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); }); +} + std::string CommandLine::get_binary_directory(std::string argv0) { std::string pathSeparator; diff --git a/src/misc.h b/src/misc.h index 557a4d8c5a4..bdc7c864d3f 100644 --- a/src/misc.h +++ b/src/misc.h @@ -101,6 +101,7 @@ inline std::vector split(const std::string& s, const std::string& d } void remove_whitespace(std::string& s); +bool is_whitespace(const std::string& s); enum SyncCout { IO_LOCK, @@ -111,6 +112,9 @@ std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK #define sync_endl std::endl << IO_UNLOCK +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; diff --git a/src/uci.cpp b/src/uci.cpp index 75b7dfc77a4..4bc358d8acb 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -48,19 +48,25 @@ struct overload: Ts... { template overload(Ts...) -> overload; +void UCIEngine::print_info_string(const std::string& str) { + sync_cout_start(); + for (auto& line : split(str, "\n")) + { + if (!is_whitespace(line)) + { + std::cout << "info string " << line << '\n'; + } + } + sync_cout_end(); +} + UCIEngine::UCIEngine(int argc, char** argv) : engine(argv[0]), cli(argc, argv) { engine.get_options().add_info_listener([](const std::optional& str) { - if (!str || (*str).empty()) - return; - - // split all lines - auto ss = std::istringstream{*str}; - - for (std::string line; std::getline(ss, line, '\n');) - sync_cout << "info string " << line << sync_endl; + if (str.has_value()) + print_info_string(*str); }); engine.set_on_iter([](const auto& i) { on_iter(i); }); @@ -102,9 +108,8 @@ void UCIEngine::loop() { sync_cout << "id name " << engine_info(true) << "\n" << engine.get_options() << sync_endl; - sync_cout << "info string " << engine.numa_config_information_as_string() << sync_endl; - sync_cout << "info string " << engine.thread_binding_information_as_string() - << sync_endl; + print_info_string(engine.numa_config_information_as_string()); + print_info_string(engine.thread_binding_information_as_string()); sync_cout << "uciok" << sync_endl; } diff --git a/src/uci.h b/src/uci.h index 122bcc40cf4..23745f96a96 100644 --- a/src/uci.h +++ b/src/uci.h @@ -58,6 +58,8 @@ class UCIEngine { Engine engine; CommandLine cli; + static void print_info_string(const std::string& str); + void go(std::istringstream& is); void bench(std::istream& args); void position(std::istringstream& is); From 44cddbd962c738678f407a7414efa5b93f0710d9 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 13 Jun 2024 18:43:19 +0200 Subject: [PATCH 0619/1309] Add matetrack to CI verifies that all mate PVs printed for finished iterations (i.e. no lower or upper bounds), are complete, i.e. of the expected length and ending in mate, and do not contain drawing or illegal moves. based on a set of 2000 positions and the code in https://github.com/vondele/matetrack closes https://github.com/official-stockfish/Stockfish/pull/5390 No functional change --- .github/workflows/matetrack.yml | 36 +++++++++++++++++++++++++++++++++ .github/workflows/stockfish.yml | 2 ++ 2 files changed, 38 insertions(+) create mode 100644 .github/workflows/matetrack.yml diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml new file mode 100644 index 00000000000..dd81f334d05 --- /dev/null +++ b/.github/workflows/matetrack.yml @@ -0,0 +1,36 @@ +# This workflow will run matetrack on the PR + +name: Matetrack +on: + workflow_call: +jobs: + Matetrack: + name: Matetrack + runs-on: ubuntu-22.04 + steps: + - name: Checkout SF repo + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + path: Stockfish + + - name: build SF + working-directory: Stockfish/src + run: make -j profile-build + + - name: Checkout matetrack repo + uses: actions/checkout@v4 + with: + repository: vondele/matetrack + path: matetrack + ref: 20287a1a145f30a166b7ef251eddb611e4e44fbf + + - name: matetrack install deps + working-directory: matetrack + run: pip install -r requirements.txt + + - name: Run matetrack + working-directory: matetrack + run: | + python matecheck.py --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 diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 13d57f9ed9b..fcaa3f6b800 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -90,6 +90,8 @@ jobs: uses: ./.github/workflows/sanitizers.yml Tests: uses: ./.github/workflows/tests.yml + Matetrack: + uses: ./.github/workflows/matetrack.yml Binaries: if: github.repository == 'official-stockfish/Stockfish' needs: [Matrix, Prerelease, Compilation] From b01fdb596a196f966549f7132c042ab67962fbbd Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 12 Jun 2024 13:23:26 +0200 Subject: [PATCH 0620/1309] Fix upperbound/lowerbound output in multithreaded case In case a stop is received during multithreaded searches, the PV of the best thread might be printed without the correct upperbound/lowerbound indicators. This was due to the pvIdx variable being incremented after receiving the stop. passed STC: https://tests.stockfishchess.org/tests/view/666985da602682471b064d08 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 196576 W: 51039 L: 50996 D: 94541 Ptnml(0-2): 760, 22545, 51603, 22652, 728 closes https://github.com/official-stockfish/Stockfish/pull/5391 Bench: 1160467 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 91b3c7894fb..af0ab400fa1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -299,7 +299,7 @@ void Search::Worker::iterative_deepening() { searchAgainCounter++; // MultiPV loop. We perform a full root search for each PV line - for (pvIdx = 0; pvIdx < multiPV && !threads.stop; ++pvIdx) + for (pvIdx = 0; pvIdx < multiPV; ++pvIdx) { if (pvIdx == pvLast) { @@ -390,6 +390,9 @@ void Search::Worker::iterative_deepening() { // below pick a proven score/PV for this thread (from the previous iteration). && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) main_manager()->pv(*this, threads, tt, rootDepth); + + if (threads.stop) + break; } if (!threads.stop) From ff10f4ac6516d691b5a48788bc7b21d0ecd83b03 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Wed, 12 Jun 2024 03:14:55 -0500 Subject: [PATCH 0621/1309] Fix readability of TTEntry occupancy check Passed STC: https://tests.stockfishchess.org/tests/view/66695b6a602682471b064cfc LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 107520 W: 28138 L: 27998 D: 51384 Ptnml(0-2): 373, 12257, 28358, 12401, 371 closes https://github.com/official-stockfish/Stockfish/pull/5394 No functional change --- src/tt.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 763e2c9b349..30104ab7d4d 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -55,6 +55,7 @@ struct TTEntry { Bound(genBound8 & 0x3), bool(genBound8 & 0x4)}; } + bool is_occupied() const; void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8); // The returned age is a multiple of TranspositionTable::GENERATION_DELTA uint8_t relative_age(const uint8_t generation8) const; @@ -84,7 +85,8 @@ static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but // 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits": -// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted below.) +// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted in `save`.) +bool TTEntry::is_occupied() const { return bool(depth8); } // Populates the TTEntry with a new node's data, possibly // overwriting an old position. The update is not atomic and can be racy. @@ -196,7 +198,7 @@ int TranspositionTable::hashfull() const { int cnt = 0; for (int i = 0; i < 1000; ++i) for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].depth8 + cnt += table[i].entry[j].is_occupied() && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; return cnt / ClusterSize; @@ -227,7 +229,7 @@ std::tuple TranspositionTable::probe(const Key key) cons if (tte[i].key16 == key16) // This gap is the main place for read races. // After `read()` completes that copy is final, but may be self-inconsistent. - return {bool(tte[i].depth8), tte[i].read(), TTWriter(&tte[i])}; + return {tte[i].is_occupied(), tte[i].read(), TTWriter(&tte[i])}; // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; From 2046c92ad461f5e852ba62a144b53c3d3fea04b0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 12 Jun 2024 14:04:43 +0300 Subject: [PATCH 0622/1309] Tweak the reduction formula Tweak the reduction formula if position is or has been on the PV Taking inspiration from an old Viren test. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 78528 W: 20607 L: 20225 D: 37696 Ptnml(0-2): 262, 9297, 19785, 9637, 283 https://tests.stockfishchess.org/tests/view/666339c70ff7cb4868d1fe24 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 138630 W: 35666 L: 35132 D: 67832 Ptnml(0-2): 118, 15345, 37835, 15919, 98 https://tests.stockfishchess.org/tests/view/66645dec0612cd151f9e77b0 closes https://github.com/official-stockfish/Stockfish/pull/5385 Bench: 1134281 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index af0ab400fa1..8fb65fe766c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1134,7 +1134,8 @@ Value Search::Worker::search( // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth); + r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth) + - (PvNode && ttData.value < alpha && ttData.depth >= depth); // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) From 2678606e8dbeac8332909f0b3e43638936570835 Mon Sep 17 00:00:00 2001 From: xoto10 <23479932+xoto10@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:27:09 +0100 Subject: [PATCH 0623/1309] Consider wider range of moves near leaves. try to avoid missing good moves for opponent or engine, by updating bestMove also when value == bestValue (i.e. value == alpha) under certain conditions. In particular require this is at higher depth in the tree, leaving the logic near the root unchanged, and only apply randomly. Avoid doing this near mate scores, leaving mate PVs intact. Passed SMP STC 6+0.06 th7 : LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 42040 W: 10930 L: 10624 D: 20486 Ptnml(0-2): 28, 4682, 11289, 4998, 23 https://tests.stockfishchess.org/tests/view/66608b00c340c8eed7757d1d Passed SMP LTC 24+0.24 th7 : LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 73692 W: 18978 L: 18600 D: 36114 Ptnml(0-2): 9, 7421, 21614, 7787, 15 https://tests.stockfishchess.org/tests/view/666095e8c340c8eed7757d49 closes https://github.com/official-stockfish/Stockfish/pull/5367 Bench 1205168 --- src/search.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8fb65fe766c..75eea2fd932 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1283,11 +1283,17 @@ Value Search::Worker::search( rm.score = -VALUE_INFINITE; } - if (value > bestValue) + // In case we have an alternative move equal in eval to the current bestmove, + // promote it to bestmove by pretending it just exceeds alpha (but not beta). + int inc = (value == bestValue && (int(nodes) & 15) == 0 + && ss->ply + 2 + ss->ply / 32 >= thisThread->rootDepth + && std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY); + + if (value + inc > bestValue) { bestValue = value; - if (value > alpha) + if (value + inc > alpha) { bestMove = move; From 5514690f8e19631054271a6ca7e1cbfaf1b443f2 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Jun 2024 09:17:45 +0200 Subject: [PATCH 0624/1309] CI/CD: play games this action plays games under fast-chess with a `debug=yes` compiled binary. It checks for triggered asserts in the code, or generally for engine disconnects. closes https://github.com/official-stockfish/Stockfish/pull/5403 No functional change --- .github/workflows/games.yml | 41 +++++++++++++++++++++++++++++++++ .github/workflows/stockfish.yml | 2 ++ 2 files changed, 43 insertions(+) create mode 100644 .github/workflows/games.yml diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml new file mode 100644 index 00000000000..088695e57fc --- /dev/null +++ b/.github/workflows/games.yml @@ -0,0 +1,41 @@ +# This workflow will play games with a debug enabled SF using the PR + +name: Games +on: + workflow_call: +jobs: + Matetrack: + name: Games + runs-on: ubuntu-22.04 + steps: + - name: Checkout SF repo + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + path: Stockfish + + - name: build debug enabled version of SF + working-directory: Stockfish/src + run: make -j build debug=yes + + - name: Checkout fast-chess repo + uses: actions/checkout@v4 + with: + repository: Disservin/fast-chess + path: fast-chess + ref: d54af1910d5479c669dc731f1f54f9108a251951 + + - name: fast-chess build + working-directory: fast-chess + run: make -j + + - name: Run games + working-directory: fast-chess + run: | + ./fast-chess -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 + cat fast.log + ! grep "Assertion" fast.log > /dev/null + ! grep "disconnect" fast.out > /dev/null diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index fcaa3f6b800..8a1094fbdbd 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -92,6 +92,8 @@ jobs: uses: ./.github/workflows/tests.yml Matetrack: uses: ./.github/workflows/matetrack.yml + Games: + uses: ./.github/workflows/games.yml Binaries: if: github.repository == 'official-stockfish/Stockfish' needs: [Matrix, Prerelease, Compilation] From 8806a58ebf5ade73696fd1f89ac4ea12cd1eedd3 Mon Sep 17 00:00:00 2001 From: evqsx <149484438+evqsx@users.noreply.github.com> Date: Sun, 16 Jun 2024 12:34:24 +0800 Subject: [PATCH 0625/1309] Simplify static exchange evaluation pruning formula Passed STC: https://tests.stockfishchess.org/tests/view/666bda31602682471b064e1f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 141696 W: 36932 L: 36826 D: 67938 Ptnml(0-2): 510, 16880, 35989, 16932, 537 Passed LTC: https://tests.stockfishchess.org/tests/view/666e6b67602682471b064f4b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 159504 W: 40552 L: 40471 D: 78481 Ptnml(0-2): 130, 18160, 43103, 18217, 142 closes https://github.com/official-stockfish/Stockfish/pull/5400 bench: 1084115 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 75eea2fd932..9b296e7fee5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1586,7 +1586,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // If static exchange evaluation is much worse than what is needed to not // fall below alpha we can prune this move. - if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 2 - 30)) + if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) { bestValue = alpha; continue; From d5c130569b364899fc151101d069291a8934789a Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 16 Jun 2024 16:14:22 -0700 Subject: [PATCH 0626/1309] Simplify Bonus Formula In History Adjustment Inspired by a discord message [1] from Vizvezdenec, this patch simplifies the bonus adjustment bonus = bonus > 0 ? 2 * bonus : bonus / 2 to a constant addition, maintaining bonus average at around 0 in regular bench. As cj5716 pointed in discord [2], the constant bonus can also be considered as factoring tempo when calculating bonus, yielding a better value of the move. [1] https://discord.com/channels/435943710472011776/882956631514689597/1243877089443188776 [2] https://discord.com/channels/435943710472011776/813919248455827515/1252277437249622077 Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 29984 W: 7908 L: 7677 D: 14399 Ptnml(0-2): 95, 3502, 7594, 3679, 122 https://tests.stockfishchess.org/tests/view/666f7210602682471b064fa2 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 170136 W: 43214 L: 43145 D: 83777 Ptnml(0-2): 158, 19185, 46311, 19258, 156 https://tests.stockfishchess.org/tests/view/666fb32e602682471b064fb5 closes https://github.com/official-stockfish/Stockfish/pull/5401 bench 1438375 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9b296e7fee5..562bdbf9c0d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -750,8 +750,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371); - bonus = bonus > 0 ? 2 * bonus : bonus / 2; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371) + 800; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; 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] From cc992e5e4a7110b21f85168bdedad7978edad140 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 17 Jun 2024 00:03:15 +0300 Subject: [PATCH 0627/1309] Internal iterative reductions: decrease depth more For PV nodes without a ttMove, we decrease depth. But in this patch, additionally, if the current position is found in the TT, and the stored depth in the TT is greater than or equal to the current search depth, we decrease the search depth even further. Passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 84384 W: 22154 L: 21761 D: 40469 Ptnml(0-2): 292, 9972, 21315, 10277, 336 https://tests.stockfishchess.org/tests/view/666b0a4d602682471b064db6 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 92106 W: 23471 L: 23032 D: 45603 Ptnml(0-2): 79, 10155, 25154, 10578, 87 https://tests.stockfishchess.org/tests/view/666c423d602682471b064e56 closes https://github.com/official-stockfish/Stockfish/pull/5397 bench: 1038234 --- src/search.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 562bdbf9c0d..e63595c1b73 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -829,9 +829,12 @@ Value Search::Worker::search( } // Step 10. Internal iterative reductions (~9 Elo) - // For PV nodes without a ttMove, we decrease depth by 3. + // For PV nodes without a ttMove, we decrease depth. + // Additionally, if the current position is found in the TT + // and the stored depth in the TT is greater than or equal to + // current search depth, we decrease search depth even further. if (PvNode && !ttData.move) - depth -= 3; + depth -= 3 + (ss->ttHit && ttData.depth >= depth); // Use qsearch if depth <= 0. if (depth <= 0) From 5fbfd06171cadf97e6e8173216046b099ebfa43b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 23 Jun 2024 21:53:25 +0200 Subject: [PATCH 0628/1309] Move info output afer uciok fixes #5393 : an incompatibility with an older GUI (Chesspartner) fixes #5396 : an incompatibility with an older GUI (Fritz9) closes https://github.com/official-stockfish/Stockfish/pull/5404 No functional change --- src/uci.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 4bc358d8acb..3c9177ee3b3 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -108,10 +108,11 @@ void UCIEngine::loop() { sync_cout << "id name " << engine_info(true) << "\n" << engine.get_options() << sync_endl; + sync_cout << "uciok" << sync_endl; + + // keep info strings after uciok for old GUIs print_info_string(engine.numa_config_information_as_string()); print_info_string(engine.thread_binding_information_as_string()); - - sync_cout << "uciok" << sync_endl; } else if (token == "setoption") From b2a12917e2125fcd1e1c344165e840b0756201a8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 24 Jun 2024 17:12:07 +0300 Subject: [PATCH 0629/1309] Remove redundant inline constexpr implies inline anyway closes https://github.com/official-stockfish/Stockfish/pull/5406 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index e97d58b939f..26dd3a2898c 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -281,7 +281,7 @@ template struct DebugInfo { std::atomic data[N] = {0}; - constexpr inline std::atomic& operator[](int index) { return data[index]; } + constexpr std::atomic& operator[](int index) { return data[index]; } }; DebugInfo<2> hit[MaxDebugSlots]; From 66e6274d32e9a59b6d0d8c347a0f1ee8175ffcdc Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 1 Jul 2024 19:44:00 +0200 Subject: [PATCH 0630/1309] Fix typos in comments closes https://github.com/official-stockfish/Stockfish/pull/5409 No functional change --- src/thread.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/thread.h b/src/thread.h index 7416271b4c1..81ca39bbcb6 100644 --- a/src/thread.h +++ b/src/thread.h @@ -39,10 +39,10 @@ namespace Stockfish { class OptionsMap; using Value = int; -// Sometimes we don't want to actually bind the threads, but the recipent still +// Sometimes we don't want to actually bind the threads, but the recipient still // needs to think it runs on *some* NUMA node, such that it can access structures // that rely on NUMA node knowledge. This class encapsulates this optional process -// such that the recipent does not need to know whether the binding happened or not. +// such that the recipient does not need to know whether the binding happened or not. class OptionalThreadToNumaNodeBinder { public: OptionalThreadToNumaNodeBinder(NumaIndex n) : @@ -87,7 +87,7 @@ class Thread { // this name is no longer correct. However, this class (and ThreadPool) // require further work to make them properly generic while maintaining // appropriate specificity regarding search, from the point of view of an - // outside user, so renaming of this function in left for whenever that happens. + // outside user, so renaming of this function is left for whenever that happens. void wait_for_search_finished(); size_t id() const { return idx; } From 22a502ac7486576f52d7ba6cf884702162e92400 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Tue, 25 Jun 2024 10:48:50 +0200 Subject: [PATCH 0631/1309] Skip futility pruning if beta is below TB loss value Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 77024 W: 20122 L: 19946 D: 36956 Ptnml(0-2): 278, 8754, 20277, 8920, 283 https://tests.stockfishchess.org/tests/view/66752d59602682471b0652f3 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 93114 W: 23623 L: 23477 D: 46014 Ptnml(0-2): 77, 9839, 26566, 10011, 64 https://tests.stockfishchess.org/tests/view/6676b3e1602682471b065395 closes https://github.com/official-stockfish/Stockfish/pull/5413 bench: 1003441 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e63595c1b73..d04ba194d7e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -784,8 +784,9 @@ Value Search::Worker::search( && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - (ss - 1)->statScore / 263 >= beta - && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttData.move || ttCapture)) - return beta > VALUE_TB_LOSS_IN_MAX_PLY ? beta + (eval - beta) / 3 : eval; + && eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY + && eval < VALUE_TB_WIN_IN_MAX_PLY) + return beta + (eval - beta) / 3; // Step 9. Null move search with verification search (~35 Elo) if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14369 From 90eca83e7f40ca719cd49e487893f32598ae6f19 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 29 Jun 2024 17:18:39 -0700 Subject: [PATCH 0632/1309] Simplify away a useless TTEntry::read() Not needed when we don hit an entry. closes https://github.com/official-stockfish/Stockfish/pull/5416 No functional change --- src/tt.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tt.cpp b/src/tt.cpp index 30104ab7d4d..4b55e53fdfc 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -238,7 +238,7 @@ std::tuple TranspositionTable::probe(const Key key) cons > tte[i].depth8 - tte[i].relative_age(generation8) * 2) replace = &tte[i]; - return {false, replace->read(), TTWriter(replace)}; + return {false, TTData(), TTWriter(replace)}; } From 91ec31dac430e1d587f8239f2377ffb796008f8a Mon Sep 17 00:00:00 2001 From: Daniel Monroe <39802758+Ergodice@users.noreply.github.com> Date: Sat, 29 Jun 2024 21:23:41 -0400 Subject: [PATCH 0633/1309] Grade countermove bonus for low statscores Passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 338592 W: 88396 L: 87627 D: 162569 Ptnml(0-2): 1161, 40201, 85788, 41000, 1146 https://tests.stockfishchess.org/tests/view/6679d40c0c2db3fa2dcecbcc Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 83526 W: 21429 L: 21010 D: 41087 Ptnml(0-2): 54, 9173, 22913, 9546, 77 https://tests.stockfishchess.org/tests/view/667c5f2980450dba965911fc closes https://github.com/official-stockfish/Stockfish/pull/5418 bench: 1489815 --- src/search.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d04ba194d7e..81bb9a06cf7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1355,10 +1355,16 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode) - + 191 * ((ss - 1)->statScore < -14396) + 119 * ((ss - 1)->moveCount > 8) + int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode) + 119 * ((ss - 1)->moveCount > 8) + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 107) + 147 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75)); + + + // proportional to "how much damage we have to undo" + if ((ss - 1)->statScore < -8000) + bonus += std::clamp(-(ss - 1)->statScore / 100, 0, 250); + + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus / 100); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] From 7b49f9dd7091ce1d075ebdd16fff85ff1dba31fa Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 30 Jun 2024 12:47:04 +0300 Subject: [PATCH 0634/1309] Tweak multicut This patch is an original patch by author of Altair (https://github.com/Alex2262/AltairChessEngine) chess engine. It allows to produce more aggressive multicut compared to master by changing condition it needs to fulfil and also returns bigger value. Also has applied matetrack fix on top. Passed STC: https://tests.stockfishchess.org/tests/view/667223ab602682471b0650e2 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 50048 W: 13200 L: 12860 D: 23988 Ptnml(0-2): 181, 5822, 12679, 6160, 182 Passed LTC: https://tests.stockfishchess.org/tests/view/6672f777602682471b06515d LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 706380 W: 179707 L: 177981 D: 348692 Ptnml(0-2): 656, 79250, 191665, 80950, 669 closes https://github.com/official-stockfish/Stockfish/pull/5421 bench 1148966 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 81bb9a06cf7..da01f82f54c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1087,8 +1087,8 @@ Value Search::Worker::search( // and if after excluding the ttMove with a reduced search we fail high over the original beta, // we assume this expected cut-node is not singular (multiple moves fail high), // and we can prune the whole subtree by returning a softbound. - else if (singularBeta >= beta) - return singularBeta; + else if (value >= beta && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + return value; // Negative extensions // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, From 38c5fc33e493f210dc199dab7c105e84e7601b99 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" Date: Sun, 30 Jun 2024 16:32:20 +0300 Subject: [PATCH 0635/1309] Increase reduction based on correct expectation If the current node is not a cutNode then it means that the child is one in LMR and the cutoff count is expected, so more reduction when the cutoffs are expected Passed STC: https://tests.stockfishchess.org/tests/view/66815e791c5b344a34ca7090 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 64416 W: 16876 L: 16519 D: 31021 Ptnml(0-2): 150, 7670, 16264, 7921, 203 Passed LTC: https://tests.stockfishchess.org/tests/view/668162f61c5b344a34ca725c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 78186 W: 19905 L: 19499 D: 38782 Ptnml(0-2): 55, 8561, 21437, 9003, 37 closes https://github.com/official-stockfish/Stockfish/pull/5422 bench: 1161531 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index da01f82f54c..b68b30268bb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1157,7 +1157,7 @@ Value Search::Worker::search( // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) - r++; + r += 1 + !(PvNode || cutNode); // For first picked move (ttMove) reduce reduction // but never allow it to go below 0 (~3 Elo) From 5deb26239340a6a1a91d1c2050f90b7a36f9f5d1 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 30 Jun 2024 22:24:28 +0300 Subject: [PATCH 0636/1309] Simplify rm.averageScore calculation Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 485056 W: 125222 L: 125497 D: 234337 Ptnml(0-2): 1384, 58197, 123614, 57976, 1357 https://tests.stockfishchess.org/tests/view/6681816d442423e54714133f Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 56622 W: 14301 L: 14115 D: 28206 Ptnml(0-2): 31, 6259, 15538, 6459, 24 https://tests.stockfishchess.org/tests/view/6681a9a5596d543edc677490 closes https://github.com/official-stockfish/Stockfish/pull/5423 bench: 1171203 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b68b30268bb..f561b183239 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1246,7 +1246,7 @@ Value Search::Worker::search( rm.effort += nodes - nodeCount; rm.averageScore = - rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value; + rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value; // PV move or new best move? if (moveCount == 1 || value > alpha) From f6842a145cf59176abc229928b94404543daa250 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 30 Jun 2024 11:43:36 -0400 Subject: [PATCH 0637/1309] Simplify worsening deduction in futility margin Passed non-regression STC: https://tests.stockfishchess.org/tests/view/66817d46442423e547141226 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 345408 W: 89146 L: 89266 D: 166996 Ptnml(0-2): 954, 41317, 88286, 41189, 958 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/66818dbe1e90a146232d1f62 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 173214 W: 43821 L: 43755 D: 85638 Ptnml(0-2): 108, 19407, 47492, 19511, 89 closes https://github.com/official-stockfish/Stockfish/pull/5424 bench 981017 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f561b183239..52eefdc94ec 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -62,7 +62,7 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 109 - 40 * noTtCutNode; Value improvingDeduction = 59 * improving * futilityMult / 32; - Value worseningDeduction = 328 * oppWorsening * futilityMult / 1024; + Value worseningDeduction = oppWorsening * futilityMult / 3; return futilityMult * d - improvingDeduction - worseningDeduction; } From 843b6f7c9873d86742cf9b6ce3523f2c5dc69d2a Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 30 Jun 2024 17:00:49 -0400 Subject: [PATCH 0638/1309] Update some params for pruning at shallow depth Values found around 82k / 120k spsa games at 60+0.6: https://tests.stockfishchess.org/tests/view/6681aca4481148df247298bd Passed STC: https://tests.stockfishchess.org/tests/view/6681c795c1657e386d2948fa LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 145216 W: 37595 L: 37122 D: 70499 Ptnml(0-2): 375, 17122, 37185, 17507, 419 Passed LTC: https://tests.stockfishchess.org/tests/view/6681d4eec1657e386d2949e0 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 154062 W: 39117 L: 38557 D: 76388 Ptnml(0-2): 67, 16874, 42608, 17396, 86 closes https://github.com/official-stockfish/Stockfish/pull/5425 bench 996419 --- src/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 52eefdc94ec..2e8d47cf952 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -994,7 +994,7 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 287 + 248 * lmrDepth + Value futilityValue = ss->staticEval + 294 + 246 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; @@ -1002,7 +1002,7 @@ Value Search::Worker::search( // SEE based pruning for captures and checks (~11 Elo) int seeHist = std::clamp(captHist / 32, -180 * depth, 163 * depth); - if (!pos.see_ge(move, -160 * depth - seeHist)) + if (!pos.see_ge(move, -163 * depth - seeHist)) continue; } else @@ -1013,15 +1013,15 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4151 * depth) + if (lmrDepth < 6 && history < -3899 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 3678; + lmrDepth += history / 4040; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 51 ? 138 : 54) + 140 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 51 ? 135 : 56) + 140 * lmrDepth; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) From 6138a0fd0e43753a86e4a170a5f6e2b7b6752677 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sun, 30 Jun 2024 19:22:04 -0400 Subject: [PATCH 0639/1309] Probcut in check no matter if pv or capture Passed STC: https://tests.stockfishchess.org/tests/view/6681e9c8c1657e386d294cef LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 217824 W: 56149 L: 56129 D: 105546 Ptnml(0-2): 587, 25926, 55848, 25982, 569 Passed LTC: https://tests.stockfishchess.org/tests/view/6681fcb8c1657e386d294db1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 357552 W: 90546 L: 90671 D: 176335 Ptnml(0-2): 207, 40064, 98362, 39933, 210 Each half of this also passed STC+LTC separately closes https://github.com/official-stockfish/Stockfish/pull/5427 bench 1227870 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2e8d47cf952..31278241c79 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -906,9 +906,8 @@ Value Search::Worker::search( // Step 12. A small Probcut idea, when we are in check (~4 Elo) probCutBeta = beta + 388; - if (ss->inCheck && !PvNode && ttCapture && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 4 && ttData.value >= probCutBeta - && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY + if (ss->inCheck && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 + && ttData.value >= probCutBeta && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; From 69ad4667fb40cc0d7195f9fa20652903813d698c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 30 Jun 2024 22:04:51 -0700 Subject: [PATCH 0640/1309] Do Capture History Updates In Probcut This patch introduces history updates to probcut. Standard depth - 3 bonus and maluses are given to the capture that caused fail high and previously searched captures, respectively. Similar to #5243, a negative history fill is applied to compensate for an increase in capture history average, thus improving the scaling of this patch. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 84832 W: 21941 L: 21556 D: 41335 Ptnml(0-2): 226, 9927, 21688, 10386, 189 https://tests.stockfishchess.org/tests/view/6682fab9389b9ee542b1d029 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 104298 W: 26469 L: 26011 D: 51818 Ptnml(0-2): 43, 11458, 28677, 11940, 31 https://tests.stockfishchess.org/tests/view/6682ff06389b9ee542b1d0a0 closes https://github.com/official-stockfish/Stockfish/pull/5428 bench 1281351 --- src/search.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 31278241c79..188e81f4f84 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -502,7 +502,7 @@ void Search::Worker::iterative_deepening() { void Search::Worker::clear() { counterMoves.fill(Move::none()); mainHistory.fill(0); - captureHistory.fill(0); + captureHistory.fill(-700); pawnHistory.fill(-1193); correctionHistory.fill(0); @@ -862,12 +862,19 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); + Move probcutCapturesSearched[32]; + int probcutCaptureCount = 0; + Piece captured; while ((move = mp.next_move()) != Move::none()) if (move != excludedMove && pos.legal(move)) { assert(pos.capture_stage(move)); + movedPiece = pos.moved_piece(move); + captured = pos.piece_on(move.to_sq()); + + // Prefetch the TT entry for the resulting position prefetch(tt.first_entry(pos.key_after(move))); @@ -891,12 +898,28 @@ Value Search::Worker::search( if (value >= probCutBeta) { + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] + << stat_bonus(depth - 2); + + for (int i = 0; i < probcutCaptureCount; i++) + { + movedPiece = pos.moved_piece(probcutCapturesSearched[i]); + captured = pos.piece_on(probcutCapturesSearched[i].to_sq()); + + thisThread->captureHistory[movedPiece][probcutCapturesSearched[i].to_sq()] + [type_of(captured)] + << -stat_malus(depth - 3); + } + // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, unadjustedStaticEval, tt.generation()); return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) : value; } + + if (probcutCaptureCount < 32) + probcutCapturesSearched[probcutCaptureCount++] = move; } Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); From 6b7822119feffd0a27ae5b2a95d3570c9e046090 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" Date: Tue, 25 Jun 2024 01:57:35 +0300 Subject: [PATCH 0641/1309] Limit has_game_cycle() to only upcoming repetition use the original algorithm according to the paper http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf, which detects accurately if a position has an upcoming repetition. The 'no progress' part of has_game_cycle has been removed, the function has been renamed to upcoming_repetition to reflect this. As a result of this fix, to the best of our knowledge, all PVs for completed iterations that yield a mate or decisive table base score now end in mate or contain a TB position, respectively. passed non-regression STC: https://tests.stockfishchess.org/tests/view/6679fa1d0c2db3fa2dcecbf2 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 63584 W: 16666 L: 16472 D: 30446 Ptnml(0-2): 186, 7552, 16146, 7698, 210 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/667ac965e439ed1c7a9ca042 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 464574 W: 117493 L: 117729 D: 229352 Ptnml(0-2): 311, 52468, 126974, 52214, 320 closes https://github.com/official-stockfish/Stockfish/pull/5432 bench: 1209805 --- src/position.cpp | 20 ++++++++++---------- src/position.h | 2 +- src/search.cpp | 10 ++++------ 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index b46ba029985..d374b1c070a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1156,9 +1156,9 @@ bool Position::has_repeated() const { } -// Tests if the position has a move which draws by repetition, -// or an earlier position has a move that directly reaches the current position. -bool Position::has_game_cycle(int ply) const { +// Tests if the position has a move which draws by repetition. +// This function accurately matches the outcome of is_draw() over all legal moves. +bool Position::upcoming_repetition(int ply) const { int j; @@ -1169,10 +1169,16 @@ bool Position::has_game_cycle(int ply) const { Key originalKey = st->key; StateInfo* stp = st->previous; + Key other = originalKey ^ stp->key ^ Zobrist::side; for (int i = 3; i <= end; i += 2) { - stp = stp->previous->previous; + stp = stp->previous; + other ^= stp->key ^ stp->previous->key ^ Zobrist::side; + stp = stp->previous; + + if (other != 0) + continue; Key moveKey = originalKey ^ stp->key; if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) @@ -1188,12 +1194,6 @@ bool Position::has_game_cycle(int ply) const { // For nodes before or at the root, check that the move is a // repetition rather than a move to the current position. - // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in - // the same location, so we have to select which square to check. - if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) - continue; - - // For repetitions before or at the root, require one more if (stp->repetition) return true; } diff --git a/src/position.h b/src/position.h index 154ed652942..3cfb87d065f 100644 --- a/src/position.h +++ b/src/position.h @@ -156,7 +156,7 @@ class Position { int game_ply() const; bool is_chess960() const; bool is_draw(int ply) const; - bool has_game_cycle(int ply) const; + bool upcoming_repetition(int ply) const; bool has_repeated() const; int rule50_count() const; Value non_pawn_material(Color c) const; diff --git a/src/search.cpp b/src/search.cpp index 188e81f4f84..6368acc6b97 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -534,9 +534,8 @@ Value Search::Worker::search( // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); - // Check if we have an upcoming move that draws by repetition, or - // if the opponent had an alternative move earlier to this position. - if (!rootNode && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) + // Check if we have an upcoming move that draws by repetition. + if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) { alpha = value_draw(this->nodes); if (alpha >= beta) @@ -1447,9 +1446,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); - // Check if we have an upcoming move that draws by repetition, or if - // the opponent had an alternative move earlier to this position. (~1 Elo) - if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) + // Check if we have an upcoming move that draws by repetition. (~1 Elo) + if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) { alpha = value_draw(this->nodes); if (alpha >= beta) From ad0f1fecda6987b16e34807a5ebc3947ced9a866 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Tue, 2 Jul 2024 14:18:04 +0200 Subject: [PATCH 0642/1309] Move info strings once more Follow up from #5404 ... current location leads to troubles with Aquarium GUI Fixes #5430 Now prints the information on threads and available processors at the beginning of search, where info about the networks is already printed (and is known to work) closes https://github.com/official-stockfish/Stockfish/pull/5433 No functional change. --- src/engine.cpp | 14 +++++++++----- src/uci.cpp | 9 +++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 233f6270110..2bc0db6affb 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -297,16 +297,20 @@ std::string Engine::get_numa_config_as_string() const { std::string Engine::numa_config_information_as_string() const { auto cfgStr = get_numa_config_as_string(); - return "Available Processors: " + cfgStr; + return "Available processors: " + cfgStr; } std::string Engine::thread_binding_information_as_string() const { - auto boundThreadsByNode = get_bound_thread_count_by_numa_node(); + auto boundThreadsByNode = get_bound_thread_count_by_numa_node(); + std::stringstream ss; + + size_t threadsSize = threads.size(); + ss << "Using " << threadsSize << (threadsSize > 1 ? " threads" : " thread"); + if (boundThreadsByNode.empty()) - return ""; + return ss.str(); - std::stringstream ss; - ss << "NUMA Node Thread Binding: "; + ss << " with NUMA node thread binding: "; bool isFirst = true; diff --git a/src/uci.cpp b/src/uci.cpp index 3c9177ee3b3..9b60680d8c5 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -109,16 +109,17 @@ void UCIEngine::loop() { << engine.get_options() << sync_endl; sync_cout << "uciok" << sync_endl; - - // keep info strings after uciok for old GUIs - print_info_string(engine.numa_config_information_as_string()); - print_info_string(engine.thread_binding_information_as_string()); } else if (token == "setoption") setoption(is); else if (token == "go") + { + // send info strings after the go command is sent for old GUIs and python-chess + print_info_string(engine.numa_config_information_as_string()); + print_info_string(engine.thread_binding_information_as_string()); go(is); + } else if (token == "position") position(is); else if (token == "ucinewgame") From b9ff5bb93be410b418d6812d6753e64cf216057a Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 2 Jul 2024 15:06:37 -0700 Subject: [PATCH 0643/1309] Implement dbg_extremes_of An alternative to #5431, implements one function `dbg_extremes_of` to keep track of min and max. closes https://github.com/official-stockfish/Stockfish/pull/5434 No functional change --- src/misc.cpp | 35 +++++++++++++++++++++++++++++++---- src/misc.h | 2 ++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 26dd3a2898c..b68c12b97f6 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -284,10 +284,18 @@ struct DebugInfo { constexpr std::atomic& operator[](int index) { return data[index]; } }; -DebugInfo<2> hit[MaxDebugSlots]; -DebugInfo<2> mean[MaxDebugSlots]; -DebugInfo<3> stdev[MaxDebugSlots]; -DebugInfo<6> correl[MaxDebugSlots]; +struct DebugExtremes: public DebugInfo<3> { + DebugExtremes() { + data[1] = std::numeric_limits::min(); + data[2] = std::numeric_limits::max(); + } +}; + +DebugInfo<2> hit[MaxDebugSlots]; +DebugInfo<2> mean[MaxDebugSlots]; +DebugInfo<3> stdev[MaxDebugSlots]; +DebugInfo<6> correl[MaxDebugSlots]; +DebugExtremes extremes[MaxDebugSlots]; } // namespace @@ -311,6 +319,18 @@ void dbg_stdev_of(int64_t value, int slot) { stdev[slot][2] += value * value; } +void dbg_extremes_of(int64_t value, int slot) { + ++extremes[slot][0]; + + int64_t current_max = extremes[slot][1].load(); + while (current_max < value && !extremes[slot][1].compare_exchange_weak(current_max, value)) + {} + + int64_t current_min = extremes[slot][2].load(); + while (current_min > value && !extremes[slot][2].compare_exchange_weak(current_min, value)) + {} +} + void dbg_correl_of(int64_t value1, int64_t value2, int slot) { ++correl[slot][0]; @@ -345,6 +365,13 @@ void dbg_print() { std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl; } + for (int i = 0; i < MaxDebugSlots; ++i) + if ((n = extremes[i][0])) + { + std::cerr << "Extremity #" << i << ": Total " << n << " Min " << extremes[i][2] + << " Max " << extremes[i][1] << std::endl; + } + for (int i = 0; i < MaxDebugSlots; ++i) if ((n = correl[i][0])) { diff --git a/src/misc.h b/src/misc.h index bdc7c864d3f..0184ab88c00 100644 --- a/src/misc.h +++ b/src/misc.h @@ -67,6 +67,8 @@ std::optional read_file_to_string(const std::string& path); void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); void dbg_stdev_of(int64_t value, int slot = 0); +void dbg_extremes_of(int64_t value, int slot); + void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); From ee6fc7e38b4aeef44862159215a56d97122f59a0 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 3 Jul 2024 11:14:41 +0200 Subject: [PATCH 0644/1309] CI: limit artifact uploads do not upload some unneeded intermediate directories, disable running authenticated git commands with the checkout action. Thanks to Yaron A for the report. closes https://github.com/official-stockfish/Stockfish/pull/5435 No functional change --- .github/workflows/arm_compilation.yml | 6 +++++- .github/workflows/clang-format.yml | 1 + .github/workflows/codeql.yml | 2 ++ .github/workflows/compilation.yml | 7 ++++++- .github/workflows/games.yml | 2 ++ .github/workflows/iwyu.yml | 2 ++ .github/workflows/matetrack.yml | 2 ++ .github/workflows/sanitizers.yml | 2 ++ .github/workflows/stockfish.yml | 4 ++++ .github/workflows/tests.yml | 1 + .github/workflows/upload_binaries.yml | 2 ++ 11 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/arm_compilation.yml b/.github/workflows/arm_compilation.yml index 3934ac2d636..5bf2a93e552 100644 --- a/.github/workflows/arm_compilation.yml +++ b/.github/workflows/arm_compilation.yml @@ -26,6 +26,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false - name: Download required linux packages if: runner.os == 'Linux' @@ -91,4 +92,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} - path: . + path: | + . + !.git + !.output diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 630edbf93fe..637cfc0d826 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -19,6 +19,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Run clang-format style check uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d949a5a7649..d01ed41fea6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,6 +30,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 3524d5e9f2e..5878adecb5c 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -25,6 +25,8 @@ jobs: shell: ${{ matrix.config.shell }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install fixed GCC on Linux if: runner.os == 'Linux' @@ -86,4 +88,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} - path: . + path: | + . + !.git + !.output diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 088695e57fc..f0bca442fdc 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -13,6 +13,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} path: Stockfish + persist-credentials: false - name: build debug enabled version of SF working-directory: Stockfish/src @@ -24,6 +25,7 @@ jobs: repository: Disservin/fast-chess path: fast-chess ref: d54af1910d5479c669dc731f1f54f9108a251951 + persist-credentials: false - name: fast-chess build working-directory: fast-chess diff --git a/.github/workflows/iwyu.yml b/.github/workflows/iwyu.yml index 0552a598c8f..f8898b1c90e 100644 --- a/.github/workflows/iwyu.yml +++ b/.github/workflows/iwyu.yml @@ -14,6 +14,7 @@ jobs: uses: actions/checkout@v4 with: path: Stockfish + persist-credentials: false - name: Checkout include-what-you-use uses: actions/checkout@v4 @@ -21,6 +22,7 @@ jobs: repository: include-what-you-use/include-what-you-use ref: f25caa280dc3277c4086ec345ad279a2463fea0f path: include-what-you-use + persist-credentials: false - name: Download required linux packages run: | diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index dd81f334d05..de65209fb29 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -13,6 +13,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} path: Stockfish + persist-credentials: false - name: build SF working-directory: Stockfish/src @@ -24,6 +25,7 @@ jobs: repository: vondele/matetrack path: matetrack ref: 20287a1a145f30a166b7ef251eddb611e4e44fbf + persist-credentials: false - name: matetrack install deps working-directory: matetrack diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index b75c06cfbbe..55459292107 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -40,6 +40,8 @@ jobs: shell: ${{ matrix.config.shell }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Download required linux packages run: | diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 8a1094fbdbd..5589c762489 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -17,6 +17,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false # returns null if no pre-release exists - name: Get Commit SHA of Latest Pre-release @@ -66,6 +68,8 @@ jobs: arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - id: set-matrix run: | TASKS=$(echo $(cat .github/ci/matrix.json) ) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 328c9cf94b1..836555e6127 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -106,6 +106,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false - name: Download required linux packages if: runner.os == 'Linux' diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index acf91a8f331..c91824a2556 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -25,6 +25,8 @@ jobs: shell: ${{ matrix.config.shell }} steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Download artifact from compilation uses: actions/download-artifact@v4 From 74a8fc060465a822f0c047f908d5fb07ebc6ad96 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 3 Jul 2024 14:07:48 +0200 Subject: [PATCH 0645/1309] Use explicit action permissions in CI Necessary modifications according to changes in the GitHub Action settings. closes https://github.com/official-stockfish/Stockfish/pull/5437 Follow up from the report by Yaron Avital (yaronav) earlier. No functional change --- .github/workflows/stockfish.yml | 10 ++++++++++ .github/workflows/upload_binaries.yml | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 5589c762489..1f87e061be9 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -15,6 +15,8 @@ jobs: Prerelease: if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')) runs-on: ubuntu-latest + permissions: + contents: write # For deleting/creating a prerelease steps: - uses: actions/checkout@v4 with: @@ -104,9 +106,17 @@ jobs: uses: ./.github/workflows/upload_binaries.yml with: matrix: ${{ needs.Matrix.outputs.matrix }} + permissions: + contents: write # For deleting/creating a (pre)release + secrets: + token: ${{ secrets.GITHUB_TOKEN }} ARM_Binaries: if: github.repository == 'official-stockfish/Stockfish' needs: [Matrix, Prerelease, ARMCompilation] uses: ./.github/workflows/upload_binaries.yml with: matrix: ${{ needs.Matrix.outputs.arm_matrix }} + permissions: + contents: write # For deleting/creating a (pre)release + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index c91824a2556..c5a2cd105c6 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -5,6 +5,9 @@ on: matrix: type: string required: true + secrets: + token: + required: true jobs: Artifacts: @@ -80,6 +83,7 @@ jobs: uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 with: files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + token: ${{ secrets.token }} - name: Get last commit sha id: last_commit @@ -106,3 +110,4 @@ jobs: tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} prerelease: true files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }} + token: ${{ secrets.token }} From 25361e514bffb81284d4311601a9f7a4a7ced79b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 3 Jul 2024 17:39:55 +0200 Subject: [PATCH 0646/1309] Output from a fix depth onward, instead of 3s. To avoid output that depends on timing, output currmove and similar only from depth > 30 onward. Current choice of 3s makes the output of the same search depending on the system load, and doesn't always start at move 1. Depth 30 is nowadays reached in a few seconds on most systems. closes https://github.com/official-stockfish/Stockfish/pull/5436 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 6368acc6b97..5eda1217b74 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -349,10 +349,10 @@ void Search::Worker::iterative_deepening() { if (threads.stop) break; - // When failing high/low give some update (without cluttering - // the UI) before a re-search. + // When failing high/low give some update before a re-search. + // To avoid excessive output, only start at rootDepth > 30. if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && elapsed_time() > 3000) + && rootDepth > 30) main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and @@ -383,7 +383,7 @@ void Search::Worker::iterative_deepening() { std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); if (mainThread - && (threads.stop || pvIdx + 1 == multiPV || elapsed_time() > 3000) + && (threads.stop || pvIdx + 1 == multiPV || rootDepth > 30) // A thread that aborted search can have mated-in/TB-loss PV and score // that cannot be trusted, i.e. it can be delayed or refuted if we would have // had time to fully search other root-moves. Thus we suppress this output and @@ -974,7 +974,7 @@ Value Search::Worker::search( ss->moveCount = ++moveCount; - if (rootNode && is_mainthread() && elapsed_time() > 3000) + if (rootNode && is_mainthread() && rootDepth > 30) { main_manager()->updates.onIter( {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); From 3c379e55d9d92a5704632c6255e72892a4db9a2f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 1 Jul 2024 16:26:44 -0400 Subject: [PATCH 0647/1309] Update 7 stat bonus/malus params Values found around 119k / 120k spsa games at 60+0.6: https://tests.stockfishchess.org/tests/view/6683112a192114e61f92f87a Passed STC: https://tests.stockfishchess.org/tests/view/66838148c4f539faa0326897 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 40928 W: 10835 L: 10508 D: 19585 Ptnml(0-2): 139, 4802, 10254, 5131, 138 Passed LTC: https://tests.stockfishchess.org/tests/view/668448a87a1863935cee42c6 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 29208 W: 7559 L: 7253 D: 14396 Ptnml(0-2): 17, 3118, 8019, 3442, 8 closes https://github.com/official-stockfish/Stockfish/pull/5439 bench 1138753 --- src/search.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5eda1217b74..f74d4f87c5f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -79,10 +79,10 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(186 * d - 285, 20, 1524); } +int stat_bonus(Depth d) { return std::clamp(191 * d - 285, 20, 1412); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 707 * d - 260 : 2073); } +int stat_malus(Depth d) { return (d < 4 ? 727 * d - 260 : 1908); } // 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); } @@ -1380,11 +1380,9 @@ Value Search::Worker::search( + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 107) + 147 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75)); - - // proportional to "how much damage we have to undo" - if ((ss - 1)->statScore < -8000) - bonus += std::clamp(-(ss - 1)->statScore / 100, 0, 250); - + // Proportional to "how much damage we have to undo" + if ((ss - 1)->statScore < -7850) + bonus += std::clamp(-(ss - 1)->statScore / 100, 0, 224); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus / 100); @@ -1801,7 +1799,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 164 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 166 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); From 2cbc20e846e46da8bfc8e254a7703a0bfad3b850 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Tue, 25 Jun 2024 16:54:25 +0200 Subject: [PATCH 0648/1309] Correct and extend PV lines with decisive TB score Currently (after #5407), SF has the property that any PV line with a decisive TB score contains the corresponding TB position, with a score that correctly identifies the depth at which TB are entered. The PV line that follows might not preserve the game outcome, but can easily be verified and extended based on TB information. This patch provides this functionality, simply extending the PV lines on output, this doesn't affect search. Indeed, if DTZ tables are available, search based PV lines that correspond to decisive TB scores are verified to preserve game outcome, truncating the line as needed. Subsequently, such PV lines are extended with a game outcome preserving line until mate, as a possible continuation. These lines are not optimal mating lines, but are similar to what a user could produce on a website like https://syzygy-tables.info/ clicking always the top ranked move, i.e. minimizing or maximizing DTZ (with a simple tie-breaker for moves that have identical DTZ), and are thus an just an illustration of how to game can be won. A similar approach is already in established in https://github.com/joergoster/Stockfish/tree/matefish2 This also contributes to addressing #5175 where SF can give an incorrect TB win/loss for positions in TB with a movecounter that doesn't reflect optimal play. While the full solution requires either TB generated differently, or a search when ranking rootmoves, current SF will eventually find a draw in these cases, in practice quite quickly, e.g. `1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1` `8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106` Gives the same results as master on an extended set of test positions from https://github.com/mcostalba/Stockfish/commit/9173d29c414ddb8f4bec74e4db3ccbe664c66bf9 with the exception of the above mentioned fen where this commit improves. With https://github.com/vondele/matetrack using 6men TB, all generated PVs verify: ``` Using ../Stockfish/src/stockfish.syzygyExtend on matetrack.epd with --nodes 1000000 --syzygyPath /chess/syzygy/3-4-5-6/WDL:/chess/syzygy/3-4-5-6/DTZ Engine ID: Stockfish dev-20240704-ff227954 Total FENs: 6555 Found mates: 3299 Best mates: 2582 Found TB wins: 568 ``` As repeated DTZ probing could be slow a procedure (100ms+ on HDD, a few ms on SSD), the extension is only done as long as the time taken is less than half the `Move Overhead` parameter. For tournaments where these lines might be of interest to the user, a suitable `Move Overhead` might be needed (e.g. TCEC has 1000ms already). closes https://github.com/official-stockfish/Stockfish/pull/5414 No functional change --- src/search.cpp | 177 +++++++++++++++++++++++++++++++++++++---- src/search.h | 4 +- src/syzygy/tbprobe.cpp | 29 ++++--- src/syzygy/tbprobe.h | 7 +- 4 files changed, 186 insertions(+), 31 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f74d4f87c5f..023e5113460 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,9 @@ #include #include #include +#include +#include +#include #include "evaluate.h" #include "misc.h" @@ -50,6 +54,12 @@ namespace Stockfish { namespace TB = Tablebases; +void syzygy_extend_pv(const OptionsMap& options, + const Search::LimitsType& limits, + Stockfish::Position& pos, + Stockfish::Search::RootMove& rootMove, + Value& v); + using Eval::evaluate; using namespace Search; @@ -1955,18 +1965,145 @@ void SearchManager::check_time(Search::Worker& worker) { worker.threads.stop = worker.threads.abortedSearch = true; } -void SearchManager::pv(const Search::Worker& worker, +// Used to correct and extend PVs for moves that have a TB (but not a mate) score. +// Keeps the search based PV for as long as it is verified to maintain the game outcome, truncates afterwards. +// Finally, extends to mate the PV, providing a possible continuation (but not a proven mating line). +void syzygy_extend_pv(const OptionsMap& options, + const Search::LimitsType& limits, + Position& pos, + RootMove& rootMove, + Value& v) { + + auto t_start = std::chrono::steady_clock::now(); + int moveOverhead = int(options["Move Overhead"]); + + // Do not use more than moveOverhead / 2 time, if time management is active. + auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool { + auto t_end = std::chrono::steady_clock::now(); + return limits.use_time_management() + && 2 * std::chrono::duration(t_end - t_start).count() + > moveOverhead; + }; + + std::list sts; + + // Step 1, walk the PV to the last position in TB with correct decisive score + int ply = 0; + while (size_t(ply) < rootMove.pv.size()) + { + Move& pvMove = rootMove.pv[ply]; + + RootMoves legalMoves; + for (const auto& m : MoveList(pos)) + legalMoves.emplace_back(m); + + Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves); + RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); + + if (legalMoves[0].tbRank != rm.tbRank) + break; + + ply++; + + auto& st = sts.emplace_back(); + pos.do_move(pvMove, st); + + // don't allow for repetitions or drawing moves along the PV in TB regime. + if (config.rootInTB && pos.is_draw(ply)) + { + pos.undo_move(pvMove); + ply--; + break; + } + + // Full PV shown will thus be validated and end TB. + // If we can't validate the full PV in time, we don't show it. + if (config.rootInTB && time_abort()) + break; + } + + // resize the PV to the correct part + rootMove.pv.resize(ply); + + // Step 2, now extend the PV to mate, as if the user explores 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)) + { + if (time_abort()) + break; + + RootMoves legalMoves; + for (const auto& m : MoveList(pos)) + { + auto& rm = legalMoves.emplace_back(m); + StateInfo tmpSI; + pos.do_move(m, tmpSI); + // Give a score of each move to break DTZ ties + // restricting opponent mobility, but not giving the opponent a capture. + for (const auto& mOpp : MoveList(pos)) + rm.tbRank -= pos.capture(mOpp) ? 100 : 1; + pos.undo_move(m); + } + + // Mate found + if (legalMoves.size() == 0) + break; + + // sort moves according to their above assigned rank, + // This will break ties for moves with equal DTZ in rank_root_moves. + std::stable_sort( + legalMoves.begin(), legalMoves.end(), + [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); + + // The winning side tries to minimize DTZ, the losing side maximizes it. + Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true); + + // If DTZ is not available we might not find a mate, so we bail out. + if (!config.rootInTB || config.cardinality > 0) + break; + + ply++; + + Move& pvMove = legalMoves[0].pv[0]; + rootMove.pv.push_back(pvMove); + auto& st = sts.emplace_back(); + pos.do_move(pvMove, st); + } + + // Finding a draw in this function is an exceptional case, that cannot happen 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 e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106 + // which TB with dtz counter rounding cannot always correctly rank. See also + // https://github.com/official-stockfish/Stockfish/issues/5175#issuecomment-2058893495 + // We adjust the score to match the found PV. Note that a TB loss score can be displayed + // if the engine did not find a drawing move yet, but eventually search will figure it out. + // E.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1 + if (pos.is_draw(0)) + v = VALUE_DRAW; + + // Undo the PV moves. + for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it) + pos.undo_move(*it); + + // Inform if we couldn't get a full extension in time. + if (time_abort()) + sync_cout + << "info string Syzygy based PV extension requires more time, increase Move Overhead as needed." + << sync_endl; +} + +void SearchManager::pv(Search::Worker& worker, const ThreadPool& threads, const TranspositionTable& tt, - Depth depth) const { + Depth depth) { - const auto nodes = threads.nodes_searched(); - const auto& rootMoves = worker.rootMoves; - const auto& pos = worker.rootPos; - size_t pvIdx = worker.pvIdx; - TimePoint time = tm.elapsed_time() + 1; - size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); - uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); + const auto nodes = threads.nodes_searched(); + auto& rootMoves = worker.rootMoves; + auto& pos = worker.rootPos; + size_t pvIdx = worker.pvIdx; + size_t multiPV = std::min(size_t(worker.options["MultiPV"]), rootMoves.size()); + uint64_t tbHits = threads.tb_hits() + (worker.tbConfig.rootInTB ? rootMoves.size() : 0); for (size_t i = 0; i < multiPV; ++i) { @@ -1984,6 +2121,13 @@ void SearchManager::pv(const Search::Worker& worker, bool tb = worker.tbConfig.rootInTB && std::abs(v) <= VALUE_TB; v = tb ? rootMoves[i].tbScore : v; + bool isExact = i != pvIdx || tb || !updated; // tablebase- and previous-scores are exact + + // Potentially correct and extend the PV, and in exceptional cases v + if (std::abs(v) >= VALUE_TB_WIN_IN_MAX_PLY && std::abs(v) < VALUE_MATE_IN_MAX_PLY + && ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact)) + syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v); + std::string pv; for (Move m : rootMoves[i].pv) pv += UCIEngine::move(m, pos.is_chess960()) + " "; @@ -2005,15 +2149,16 @@ void SearchManager::pv(const Search::Worker& worker, info.score = {v, pos}; info.wdl = wdl; - if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact + if (!isExact) info.bound = bound; - info.timeMs = time; - info.nodes = nodes; - info.nps = nodes * 1000 / time; - info.tbHits = tbHits; - info.pv = pv; - info.hashfull = tt.hashfull(); + TimePoint time = tm.elapsed_time() + 1; + info.timeMs = time; + info.nodes = nodes; + info.nps = nodes * 1000 / time; + info.tbHits = tbHits; + info.pv = pv; + info.hashfull = tt.hashfull(); updates.onUpdateFull(info); } diff --git a/src/search.h b/src/search.h index d5210c2e072..e8e33b1a819 100644 --- a/src/search.h +++ b/src/search.h @@ -202,10 +202,10 @@ class SearchManager: public ISearchManager { void check_time(Search::Worker& worker) override; - void pv(const Search::Worker& worker, + void pv(Search::Worker& worker, const ThreadPool& threads, const TranspositionTable& tt, - Depth depth) const; + Depth depth); Stockfish::TimeManagement tm; double originalTimeAdjust; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 722dc9d3e8e..fc2a092aa54 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -66,7 +66,7 @@ namespace { constexpr int TBPIECES = 7; // Max number of supported pieces constexpr int MAX_DTZ = - 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit. + 1 << 18; // Max DTZ supported times 2, large enough to deal with the syzygy TB limit. enum { BigEndian, @@ -1574,7 +1574,10 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50) { +bool Tablebases::root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ) { ProbeState result = OK; StateInfo st; @@ -1585,7 +1588,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool ru // Check whether a position was repeated since the last zeroing move. bool rep = pos.has_repeated(); - int dtz, bound = rule50 ? (MAX_DTZ - 100) : 1; + int dtz, bound = rule50 ? (MAX_DTZ / 2 - 100) : 1; // Probe and rank each move for (auto& m : rootMoves) @@ -1624,8 +1627,10 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool ru // Better moves are ranked higher. Certain wins are ranked equally. // Losing moves are ranked equally unless a 50-move draw is in sight. - int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50)) - : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50)) + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ - (rankDTZ ? dtz : 0) + : MAX_DTZ / 2 - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ - (rankDTZ ? dtz : 0) + : -MAX_DTZ / 2 + (-dtz + cnt50)) : 0; m.tbRank = r; @@ -1633,10 +1638,11 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, bool ru // 1 cp to cursed wins and let it grow to 49 cp as the positions gets // closer to a real win. m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 - : r > 0 ? Value((std::max(3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200) - : r == 0 ? VALUE_DRAW - : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200) - : -VALUE_MATE + MAX_PLY + 1; + : r > 0 ? Value((std::max(3, r - (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200) + : r == 0 ? VALUE_DRAW + : r > -bound + ? Value((std::min(-3, r + (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200) + : -VALUE_MATE + MAX_PLY + 1; } return true; @@ -1683,7 +1689,8 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo Config Tablebases::rank_root_moves(const OptionsMap& options, Position& pos, - Search::RootMoves& rootMoves) { + Search::RootMoves& rootMoves, + bool rankDTZ) { Config config; if (rootMoves.empty()) @@ -1707,7 +1714,7 @@ Config Tablebases::rank_root_moves(const OptionsMap& options, if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { // Rank moves using DTZ tables - config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"]); + config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ); if (!config.rootInTB) { diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index e10950f4e6b..75a1858576b 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -66,9 +66,12 @@ extern int MaxCardinality; void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50); +bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); -Config rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves); +Config rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ = false); } // namespace Stockfish::Tablebases From c40dd26cbce89cf15055acac75800da6a9721307 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 6 Jul 2024 17:31:54 +0200 Subject: [PATCH 0649/1309] CI give creditials for the clang-format action following up from earlier changes closes https://github.com/official-stockfish/Stockfish/pull/5450 No functional change --- .github/workflows/clang-format.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 637cfc0d826..630edbf93fe 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -19,7 +19,6 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - name: Run clang-format style check uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 From d212e663bb00226f861f3046b36a5d8a3a127865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Sat, 6 Jul 2024 12:16:38 +0200 Subject: [PATCH 0650/1309] Introduction evaluation grain of 16 (and randomize) This patch uses an evaluation grain of 16 in order to get more cutoffs in the alpha-beta algorithm. For a discussion of the efficiency of alpha-beta related to changes in the number of discrete values of terminal nodes, see for instance section 9.1.2 of Judea Pearl's classical book "Heuristics" : https://mat.uab.cat/~alseda/MasterOpt/Judea_Pearl-Heuristics_Intelligent_Search_Strategies_for_Computer_Problem_Solving.pdf Moreover, we add a small (-1, +1) random component after the quantification to help the search exploration a little bit. This is similar in spirit to the (-1, +1) random component already present in the function draw_value() to make Stockfish more robust in draw evaluations. passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 220960 W: 57249 L: 56668 D: 107043 Ptnml(0-2): 499, 26017, 56882, 26568, 514 https://tests.stockfishchess.org/tests/view/668907fb7edfb6f233f999f8 passed LTC : LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 48966 W: 12574 L: 12233 D: 24159 Ptnml(0-2): 14, 5233, 13654, 5562, 20 https://tests.stockfishchess.org/tests/view/6689105659cb3228a47598bf closes https://github.com/official-stockfish/Stockfish/pull/5449 bench: 1336007 --- src/evaluate.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4e895fd366f..44890a361af 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -85,6 +85,9 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int material = 554 * pos.count() + pos.non_pawn_material(); v = (nnue * (73921 + material) + optimism * (8112 + material)) / 73260; + // Evaluation grain (to get more alpha-beta cuts) with randomization (for robustness) + v = (v / 16) * 16 - 1 + (pos.key() & 0x2); + // Damp down the evaluation linearly when shuffling v -= v * pos.rule50_count() / 212; From daa9e217ab59a090a6344738505edbdfcd09a700 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 6 Jul 2024 10:43:35 +0800 Subject: [PATCH 0651/1309] VVLTC search tune Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/6688af640c9d7c1ab33ed327 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 16050 W: 4200 L: 3959 D: 7891 Ptnml(0-2): 0, 1383, 5018, 1624, 0 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/6688e8900c9d7c1ab33efd60 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 44044 W: 11303 L: 10999 D: 21742 Ptnml(0-2): 1, 3973, 13772, 4273, 3 closes https://github.com/official-stockfish/Stockfish/pull/5444 Bench: 992058 --- src/search.cpp | 92 +++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 023e5113460..0863013e870 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -70,8 +70,8 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 109 - 40 * noTtCutNode; - Value improvingDeduction = 59 * improving * futilityMult / 32; + Value futilityMult = 122 - 37 * noTtCutNode; + Value improvingDeduction = 58 * improving * futilityMult / 32; Value worseningDeduction = oppWorsening * futilityMult / 3; return futilityMult * d - improvingDeduction - worseningDeduction; @@ -84,15 +84,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv / 10; + v += cv * std::abs(cv) / 5073; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(191 * d - 285, 20, 1412); } +int stat_bonus(Depth d) { return std::clamp(190 * d - 298, 20, 1596); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 727 * d - 260 : 1908); } +int stat_malus(Depth d) { return (d < 4 ? 736 * d - 268 : 2044); } // 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); } @@ -324,12 +324,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 10182; + delta = 9 + avg * avg / 10424; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 127 * avg / (std::abs(avg) + 86); + optimism[us] = 125 * avg / (std::abs(avg) + 89); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -513,17 +513,17 @@ void Search::Worker::clear() { counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(-700); - pawnHistory.fill(-1193); + pawnHistory.fill(-1188); correctionHistory.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-56); + h->fill(-58); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((19.26 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((18.62 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -759,7 +759,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1590, 1371) + 800; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1664, 1471) + 752; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; 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] @@ -780,7 +780,7 @@ Value Search::Worker::search( // Step 7. Razoring (~1 Elo) // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. - if (eval < alpha - 512 - 293 * depth * depth) + if (eval < alpha - 494 - 290 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) @@ -791,22 +791,22 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 13 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 263 + - (ss - 1)->statScore / 260 >= beta && eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY && eval < VALUE_TB_WIN_IN_MAX_PLY) return beta + (eval - beta) / 3; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14369 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 393 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14389 + && eval >= beta && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 197, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 202, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -852,13 +852,13 @@ Value Search::Worker::search( // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, or // by 1 if there is a ttMove with an upper bound. - if (cutNode && depth >= 8 && (!ttData.move || ttData.bound == BOUND_UPPER)) + if (cutNode && depth >= 7 && (!ttData.move || ttData.bound == BOUND_UPPER)) depth -= 1 + !ttData.move; // Step 11. ProbCut (~10 Elo) // 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 + 177 - 57 * improving; + probCutBeta = beta + 184 - 53 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -937,7 +937,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 388; + probCutBeta = beta + 390; if (ss->inCheck && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -1011,7 +1011,7 @@ Value Search::Worker::search( // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) moveCountPruning = moveCount >= futility_move_count(improving, depth) - - (singularBound == BOUND_UPPER && singularValue < alpha - 50); + - (singularBound == BOUND_UPPER && singularValue < alpha - 51); // Reduced depth of the next LMR search int lmrDepth = newDepth - r; @@ -1025,15 +1025,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 294 + 246 * lmrDepth + Value futilityValue = ss->staticEval + 285 + 251 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -180 * depth, 163 * depth); - if (!pos.see_ge(move, -163 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -182 * depth, 166 * depth); + if (!pos.see_ge(move, -168 * depth - seeHist)) continue; } else @@ -1044,15 +1044,15 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -3899 * depth) + if (lmrDepth < 6 && history < -4165 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 4040; + lmrDepth += history / 3853; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 51 ? 135 : 56) + 140 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 51 ? 143 : 52) + 135 * lmrDepth; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) @@ -1089,11 +1089,11 @@ Value Search::Worker::search( // margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 35) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 36) + ss->ttPv && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (52 + 80 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttData.value - (54 + 76 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1104,13 +1104,13 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 290 * PvNode - 200 * !ttCapture; - int tripleMargin = 107 + 247 * PvNode - 278 * !ttCapture + 99 * ss->ttPv; + int doubleMargin = 293 * PvNode - 195 * !ttCapture; + int tripleMargin = 107 + 259 * PvNode - 260 * !ttCapture + 98 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += ((!PvNode) && (depth < 18)); + depth += ((!PvNode) && (depth < 16)); } // Multi-cut pruning @@ -1140,7 +1140,7 @@ Value Search::Worker::search( else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 3922) + > 3994) extension = 1; } @@ -1197,10 +1197,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 4747; + + (*contHist[1])[movedPiece][move.to_sq()] - 4664; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 11125; + r -= ss->statScore / 10898; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1343,7 +1343,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 13 && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + if (depth > 2 && depth < 14 && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) depth -= 2; assert(depth > 0); @@ -1386,13 +1386,13 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (113 * (depth > 5) + 118 * (PvNode || cutNode) + 119 * ((ss - 1)->moveCount > 8) - + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 107) - + 147 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75)); + int bonus = (114 * (depth > 5) + 116 * (PvNode || cutNode) + 123 * ((ss - 1)->moveCount > 8) + + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 108) + + 153 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76)); // Proportional to "how much damage we have to undo" - if ((ss - 1)->statScore < -7850) - bonus += std::clamp(-(ss - 1)->statScore / 100, 0, 224); + if ((ss - 1)->statScore < -7865) + bonus += std::clamp(-(ss - 1)->statScore / 103, 0, 258); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus / 100); @@ -1564,7 +1564,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 294; + futilityBase = ss->staticEval + 299; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1636,11 +1636,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()] - <= 4452) + <= 4643) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -74)) + if (!pos.see_ge(move, -83)) continue; } @@ -1706,7 +1706,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 + 1236 - delta * 746 / rootDelta) / 1024 + (!i && reductionScale > 1326); + return (reductionScale + 1274 - delta * 746 / rootDelta) / 1024 + (!i && reductionScale > 1293); } // elapsed() returns the time elapsed since the search started. If the @@ -1809,7 +1809,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 166 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 172 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); @@ -1847,7 +1847,7 @@ void update_all_stats(const Position& pos, // by moves at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - bonus = bonus * 51 / 64; + bonus = bonus * 52 / 64; for (int i : {1, 2, 3, 4, 6}) { From a45c2bc34ae03dd35402e6cf26d515bae1425517 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 1 Jul 2024 17:08:22 -0700 Subject: [PATCH 0652/1309] Simplify Away Countermove Heuristic Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 977824 W: 252072 L: 252888 D: 472864 Ptnml(0-2): 2518, 117120, 250560, 116088, 2626 https://tests.stockfishchess.org/tests/view/6683452d95b0d1e881e81541 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 81048 W: 20630 L: 20470 D: 39948 Ptnml(0-2): 36, 8915, 22464, 9071, 38 https://tests.stockfishchess.org/tests/view/66886b7b0c9d7c1ab33ed281 closes https://github.com/official-stockfish/Stockfish/pull/5441 bench 1276784 --- src/movepick.cpp | 7 +------ src/movepick.h | 5 ----- src/search.cpp | 18 ++++-------------- src/search.h | 1 - 4 files changed, 5 insertions(+), 26 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 52e8c526a10..05f57ae79b6 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -91,7 +91,6 @@ MovePicker::MovePicker(const Position& p, const CapturePieceToHistory* cph, const PieceToHistory** ch, const PawnHistory* ph, - Move cm, const Move* killers) : pos(p), mainHistory(mh), @@ -99,7 +98,7 @@ MovePicker::MovePicker(const Position& p, continuationHistory(ch), pawnHistory(ph), ttMove(ttm), - refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, + refutations{{killers[0], 0}, {killers[1], 0}}, depth(d) { assert(d > 0); @@ -273,10 +272,6 @@ Move MovePicker::next_move(bool skipQuiets) { cur = std::begin(refutations); endMoves = std::end(refutations); - // If the countermove is the same as a killer, skip it - if (refutations[0] == refutations[2] || refutations[1] == refutations[2]) - --endMoves; - ++stage; [[fallthrough]]; diff --git a/src/movepick.h b/src/movepick.h index b81f76e18f2..8a2e0145d0d 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -118,10 +118,6 @@ enum StatsType { // see www.chessprogramming.org/Butterfly_Boards (~11 elo) using ButterflyHistory = Stats; -// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous -// move, see www.chessprogramming.org/Countermove_Heuristic -using CounterMoveHistory = Stats; - // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; @@ -164,7 +160,6 @@ class MovePicker { const CapturePieceToHistory*, const PieceToHistory**, const PawnHistory*, - Move, const Move*); MovePicker(const Position&, Move, diff --git a/src/search.cpp b/src/search.cpp index 0863013e870..cd29176eb2c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -125,7 +125,7 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move); +void update_refutations(Stack* ss, Move move); void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); void update_quiet_stats( @@ -510,7 +510,6 @@ void Search::Worker::iterative_deepening() { } void Search::Worker::clear() { - counterMoves.fill(Move::none()); mainHistory.fill(0); captureHistory.fill(-700); pawnHistory.fill(-1188); @@ -950,11 +949,9 @@ Value Search::Worker::search( nullptr, (ss - 6)->continuationHistory}; - Move countermove = - prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : Move::none(); MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, &thisThread->pawnHistory, countermove, ss->killers); + contHist, &thisThread->pawnHistory, ss->killers); value = bestValue; moveCountPruning = false; @@ -1860,7 +1857,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } // Updates move sorting heuristics -void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move) { +void update_refutations(Stack* ss, Move move) { // Update killers if (ss->killers[0] != move) @@ -1868,13 +1865,6 @@ void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerTh ss->killers[1] = ss->killers[0]; ss->killers[0] = move; } - - // Update countermove history - if (((ss - 1)->currentMove).is_ok()) - { - Square prevSq = ((ss - 1)->currentMove).to_sq(); - workerThread.counterMoves[pos.piece_on(prevSq)][prevSq] = move; - } } void update_quiet_histories( @@ -1893,7 +1883,7 @@ void update_quiet_histories( void update_quiet_stats( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { - update_refutations(pos, ss, workerThread, move); + update_refutations(ss, move); update_quiet_histories(pos, ss, workerThread, move, bonus); } diff --git a/src/search.h b/src/search.h index e8e33b1a819..122cd549e3c 100644 --- a/src/search.h +++ b/src/search.h @@ -247,7 +247,6 @@ class Worker { bool is_mainthread() const { return threadIdx == 0; } // Public because they need to be updatable by the stats - CounterMoveHistory counterMoves; ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; From ec8288fe0d81be31084cf4609767466b04458ec7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 6 Jul 2024 14:26:31 +0300 Subject: [PATCH 0653/1309] Simplify away eval in TM Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 40160 W: 10523 L: 10309 D: 19328 Ptnml(0-2): 129, 4543, 10524, 4753, 131 https://tests.stockfishchess.org/tests/view/6685ab8b99271ae49479dbe9 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 195672 W: 49681 L: 49639 D: 96352 Ptnml(0-2): 112, 20976, 55597, 21060, 91 https://tests.stockfishchess.org/tests/view/6686f27a7092ade1206f7889 closes https://github.com/official-stockfish/Stockfish/pull/5445 No functional change --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cd29176eb2c..2fd38e50a5c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -65,9 +65,6 @@ using namespace Search; namespace { -static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, - 0.942, 0.933, 0.890, 0.984, 0.941}; - // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 122 - 37 * noTtCutNode; @@ -463,11 +460,10 @@ void Search::Worker::iterative_deepening() { timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); - int el = std::clamp((bestValue + 750) / 150, 0, 9); double recapture = limits.capSq == rootMoves[0].pv[0].to_sq() ? 0.955 : 1.005; double totalTime = mainThread->tm.optimum() * fallingEval * reduction - * bestMoveInstability * EvalLevel[el] * recapture; + * bestMoveInstability * recapture; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) From 24ab46c5110d6f5c587f4929e23a13983babb759 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 6 Jul 2024 04:45:37 -0700 Subject: [PATCH 0654/1309] Non-functional Fixes & Updates Fixes a missing default slot for dbg_extremes of, removes a extra newline, and updates SE elo estimate from https://tests.stockfishchess.org/tests/view/664ebd1e928b1fb18de4e4b7 while we are at it. closes https://github.com/official-stockfish/Stockfish/pull/5446 No functional change --- src/misc.h | 3 +-- src/search.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/misc.h b/src/misc.h index 0184ab88c00..ce49a1f6553 100644 --- a/src/misc.h +++ b/src/misc.h @@ -67,8 +67,7 @@ std::optional read_file_to_string(const std::string& path); void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); void dbg_stdev_of(int64_t value, int slot = 0); -void dbg_extremes_of(int64_t value, int slot); - +void dbg_extremes_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); diff --git a/src/search.cpp b/src/search.cpp index 2fd38e50a5c..2bdcd25a3f7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -462,8 +462,8 @@ void Search::Worker::iterative_deepening() { double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); double recapture = limits.capSq == rootMoves[0].pv[0].to_sq() ? 0.955 : 1.005; - double totalTime = mainThread->tm.optimum() * fallingEval * reduction - * bestMoveInstability * recapture; + double totalTime = + mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * recapture; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) @@ -1068,8 +1068,8 @@ Value Search::Worker::search( // We take care to not overdo to avoid search getting stuck. if (ss->ply < thisThread->rootDepth * 2) { - // Singular extension search (~94 Elo). If all moves but one fail low on a - // search of (alpha-s, beta-s), and just one fails high on (alpha, beta), + // Singular extension search (~76 Elo, ~170 nElo). 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. From 55cb235d47afe8422cc781970ef69790387f42bc Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:07:42 +0800 Subject: [PATCH 0655/1309] Simplify internal iterative reductions This is a revert of cc992e5. This patch is based on consistent observations that decreasing depth more in IIR generally has a bad scaling behaviour (good at STC, bad at longer time controls). Simplification STC: https://tests.stockfishchess.org/tests/view/6689266659cb3228a4759b26 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 96992 W: 24977 L: 24824 D: 47191 Ptnml(0-2): 251, 11497, 24851, 11642, 255 Simplification LTC: https://tests.stockfishchess.org/tests/view/668930ffe59d990b103f6ab1 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 35808 W: 9185 L: 8980 D: 17643 Ptnml(0-2): 25, 3776, 10101, 3973, 29 closes https://github.com/official-stockfish/Stockfish/pull/5447 Bench: 1097766 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 2bdcd25a3f7..576e1f9048d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -835,11 +835,8 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions (~9 Elo) // For PV nodes without a ttMove, we decrease depth. - // Additionally, if the current position is found in the TT - // and the stored depth in the TT is greater than or equal to - // current search depth, we decrease search depth even further. if (PvNode && !ttData.move) - depth -= 3 + (ss->ttHit && ttData.depth >= depth); + depth -= 3; // Use qsearch if depth <= 0. if (depth <= 0) From 4d6e1225bd409c72a9b966c3008cf99a804c5026 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:08:28 +0800 Subject: [PATCH 0656/1309] Simplify ttPv reduction formula This is a revert of 2046c92. This patch is based on the fact that the ttPv reduction has proven non-linear scaling (as documented in the code, along with testing guidelines); however, the original patch had (erroneously) not been tested at VLTC or longer. Simplification STC: https://tests.stockfishchess.org/tests/view/6689266e59cb3228a4759b28 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 100320 W: 25913 L: 25763 D: 48644 Ptnml(0-2): 270, 11842, 25750, 12064, 234 Simplification LTC: https://tests.stockfishchess.org/tests/view/66893103e59d990b103f6ab3 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 57078 W: 14466 L: 14282 D: 28330 Ptnml(0-2): 34, 6214, 15851, 6414, 26 closes https://github.com/official-stockfish/Stockfish/pull/5448 Bench: 1124658 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 576e1f9048d..cb0340ece84 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1158,8 +1158,7 @@ Value Search::Worker::search( // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth) - - (PvNode && ttData.value < alpha && ttData.depth >= depth); + r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth); // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) From b1f522930d58118a6035870fe7d02b3d82681ec8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 4 Jul 2024 23:39:10 -0700 Subject: [PATCH 0657/1309] Simplify Away Move Count Pruning Adjustment Using Singular Search Result Passed Non-regression STC: LLR: 3.01 (-2.94,2.94) <-1.75,0.25> Total: 62688 W: 16319 L: 16121 D: 30248 Ptnml(0-2): 196, 7317, 16104, 7547, 180 https://tests.stockfishchess.org/tests/view/66879bf51b527f04dd477ff9 Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 116502 W: 29504 L: 29379 D: 57619 Ptnml(0-2): 66, 12881, 32226, 13018, 60 https://tests.stockfishchess.org/tests/view/6688629e0c9d7c1ab33ed030 closes https://github.com/official-stockfish/Stockfish/pull/5442 bench 1207930 --- src/search.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cb0340ece84..ffe6e04b1ae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -559,12 +559,11 @@ Value Search::Worker::search( Key posKey; Move move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, eval, maxValue, probCutBeta, singularValue; + Value bestValue, value, eval, maxValue, probCutBeta; bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, moveCountPruning, ttCapture; Piece movedPiece; int moveCount, captureCount, quietCount; - Bound singularBound; // Step 1. Initialize node Worker* thisThread = this; @@ -948,8 +947,6 @@ Value Search::Worker::search( value = bestValue; moveCountPruning = false; - singularValue = VALUE_INFINITE; - singularBound = BOUND_NONE; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -999,9 +996,7 @@ Value Search::Worker::search( if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - moveCountPruning = - moveCount >= futility_move_count(improving, depth) - - (singularBound == BOUND_UPPER && singularValue < alpha - 51); + moveCountPruning = moveCount >= futility_move_count(improving, depth); // Reduced depth of the next LMR search int lmrDepth = newDepth - r; @@ -1087,9 +1082,8 @@ Value Search::Worker::search( Depth singularDepth = newDepth / 2; ss->excludedMove = move; - value = singularValue = + value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - singularBound = singularValue >= singularBeta ? BOUND_LOWER : BOUND_UPPER; ss->excludedMove = Move::none(); if (value < singularBeta) From b79ac764ff1662b40d5480595bafb599b72512eb Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 1 Jul 2024 21:57:53 -0700 Subject: [PATCH 0658/1309] Simplify in-check condition for Probcut-in-check dont let your memes be dreams !? Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 512000 W: 132193 L: 132497 D: 247310 Ptnml(0-2): 1600, 61170, 130704, 60986, 1540 https://tests.stockfishchess.org/tests/view/66838911c4f539faa03268a2 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 380268 W: 95894 L: 96042 D: 188332 Ptnml(0-2): 193, 42861, 104180, 42701, 199 https://tests.stockfishchess.org/tests/view/6688d0550c9d7c1ab33ed5a8 closes https://github.com/official-stockfish/Stockfish/pull/5443 Bench: 1130282 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ffe6e04b1ae..1bce9daad2b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -927,10 +927,9 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here - // Step 12. A small Probcut idea, when we are in check (~4 Elo) + // Step 12. A small Probcut idea (~4 Elo) probCutBeta = beta + 390; - if (ss->inCheck && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 - && ttData.value >= probCutBeta && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY + if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; From 2d3ef434b4009fcc9e198b508f00957c2d05eb1e Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 6 Jul 2024 03:44:28 -0700 Subject: [PATCH 0659/1309] Tweak LMR at Root Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 328192 W: 84751 L: 84014 D: 159427 Ptnml(0-2): 758, 38802, 84253, 39511, 772 https://tests.stockfishchess.org/tests/view/6689203959cb3228a4759a49 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 56748 W: 14494 L: 14136 D: 28118 Ptnml(0-2): 19, 6089, 15803, 6441, 22 https://tests.stockfishchess.org/tests/view/66892d76e59d990b103f6626 closes https://github.com/official-stockfish/Stockfish/pull/5452 Bench 1253593 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1bce9daad2b..9d0b5627cf4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1185,7 +1185,7 @@ Value Search::Worker::search( r -= ss->statScore / 10898; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) - if (depth >= 2 && moveCount > 1 + rootNode) + if (depth >= 2 && moveCount > 1 + (rootNode && depth < 10)) { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension From bb9b65408ffe0f71eb60760e05c5d599300173da Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 6 Jul 2024 13:41:11 -0400 Subject: [PATCH 0660/1309] Simplify improving deduction in futility margin Passed non-regression STC: https://tests.stockfishchess.org/tests/view/668981d4df142e108ffc9bb4 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 312672 W: 80280 L: 80363 D: 152029 Ptnml(0-2): 729, 37198, 80529, 37187, 693 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/668988c6df142e108ffca042 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 126042 W: 31971 L: 31857 D: 62214 Ptnml(0-2): 50, 13988, 34832, 14100, 51 closes https://github.com/official-stockfish/Stockfish/pull/5454 bench 1100483 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9d0b5627cf4..153eba188c8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -68,7 +68,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 122 - 37 * noTtCutNode; - Value improvingDeduction = 58 * improving * futilityMult / 32; + Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; return futilityMult * d - improvingDeduction - worseningDeduction; From 75c8cb2c2f7d687d3ba02eac2088860b625acd47 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 6 Jul 2024 22:21:33 +0300 Subject: [PATCH 0661/1309] Adjust usage of previous statscore in bonus assignments This patch adjusts usage of previous statscore for bonus assginments - allowing it for any statscores and clamping it to wider range. Passed STC: https://tests.stockfishchess.org/tests/view/66892e76e59d990b103f6a91 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 431520 W: 111767 L: 110872 D: 208881 Ptnml(0-2): 1180, 51165, 110133, 52144, 1138 Passed LTC: https://tests.stockfishchess.org/tests/view/66897176e59d990b103f9605 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 143184 W: 36463 L: 35929 D: 70792 Ptnml(0-2): 55, 15540, 39863, 16084, 50 closes https://github.com/official-stockfish/Stockfish/pull/5455 bench 1330556 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 153eba188c8..9b0ea9df860 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1373,8 +1373,7 @@ Value Search::Worker::search( + 153 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76)); // Proportional to "how much damage we have to undo" - if ((ss - 1)->statScore < -7865) - bonus += std::clamp(-(ss - 1)->statScore / 103, 0, 258); + bonus += std::clamp(-(ss - 1)->statScore / 100, -50, 274); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus / 100); From 4e9fded5a63a2a72964f6d3518e2f66186662d05 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 6 Jul 2024 16:04:07 -0400 Subject: [PATCH 0662/1309] Larger bonus when updating quiet stats Also removes unused arguments to update_all_stats to fix compiler warnings about unused parameters. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6689a79a0fdd852d63cf52e9 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 26496 W: 6901 L: 6669 D: 12926 Ptnml(0-2): 62, 3094, 6715, 3304, 73 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6689a9960fdd852d63cf532d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 41214 W: 10373 L: 10173 D: 20668 Ptnml(0-2): 11, 4491, 11412, 4673, 20 closes https://github.com/official-stockfish/Stockfish/pull/5456 bench 1169958 --- src/search.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9b0ea9df860..3f1600474d4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -131,8 +131,6 @@ void update_all_stats(const Position& pos, Stack* ss, Search::Worker& workerThread, Move bestMove, - Value bestValue, - Value beta, Square prevSq, Move* quietsSearched, int quietCount, @@ -1362,8 +1360,8 @@ Value Search::Worker::search( // If there is a move that produces search value greater than alpha we update the stats of searched moves else if (bestMove) - update_all_stats(pos, ss, *this, bestMove, bestValue, beta, prevSq, quietsSearched, - quietCount, capturesSearched, captureCount, depth); + update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, quietCount, + capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1772,8 +1770,6 @@ void update_all_stats(const Position& pos, Stack* ss, Search::Worker& workerThread, Move bestMove, - Value bestValue, - Value beta, Square prevSq, Move* quietsSearched, int quietCount, @@ -1790,10 +1786,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 172 ? quietMoveBonus // larger bonus - : stat_bonus(depth); // smaller bonus - - update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); + update_quiet_stats(pos, ss, workerThread, bestMove, quietMoveBonus); // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) From cdb0b96e0725bb9aafc0ca9aecfebdae32eede8f Mon Sep 17 00:00:00 2001 From: MinetaS Date: Sun, 7 Jul 2024 08:27:43 +0900 Subject: [PATCH 0663/1309] Clean up refutations array in MovePicker This is a follow-up cleanup to a45c2bc34ae03dd35402e6cf26d515bae1425517. closes https://github.com/official-stockfish/Stockfish/pull/5458 No functional change --- src/movepick.cpp | 9 +++------ src/movepick.h | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 05f57ae79b6..f6f9f0dc895 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -297,9 +297,8 @@ Move MovePicker::next_move(bool skipQuiets) { [[fallthrough]]; case GOOD_QUIET : - if (!skipQuiets && select([&]() { - return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; - })) + if (!skipQuiets + && select([&]() { return *cur != refutations[0] && *cur != refutations[1]; })) { if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) return *(cur - 1); @@ -328,9 +327,7 @@ Move MovePicker::next_move(bool skipQuiets) { case BAD_QUIET : if (!skipQuiets) - return select([&]() { - return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2]; - }); + return select([&]() { return *cur != refutations[0] && *cur != refutations[1]; }); return Move::none(); diff --git a/src/movepick.h b/src/movepick.h index 8a2e0145d0d..2564f730190 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -185,7 +185,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove refutations[3], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + ExtMove refutations[2], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; int stage; int threshold; Depth depth; From 5752529cabb3270e055147709ff0847e4d59ec22 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 6 Jul 2024 09:31:35 -0400 Subject: [PATCH 0664/1309] Update default main net to nn-74f1d263ae9a.nnue Created by setting output weights (256) and biases (8) of the previous main net nn-ddcfb9224cdb.nnue to values found around 12k / 120k spsa games at 120+1.2 This used modified fishtest dev workers to construct .nnue files from spsa params, then load them with EvalFile when running tests: https://github.com/linrock/fishtest/tree/spsa-file-modified-nnue/worker Inspired by researching loading spsa params from files: https://github.com/official-stockfish/fishtest/pull/1926 Scripts for modifying nnue files and preparing params: https://github.com/linrock/nnue-pytorch/tree/no-gpu-modify-nnue spsa params: weights: [-127, 127], c_end = 6 biases: [-8192, 8192], c_end = 64 Example of reading output weights and biases from the previous main net using nnue-pytorch and printing spsa params in a format compatible with fishtest: ``` import features from serialize import NNUEReader feature_set = features.get_feature_set_from_name("HalfKAv2_hm") with open("nn-ddcfb9224cdb.nnue", "rb") as f: model = NNUEReader(f, feature_set).model c_end_weights = 6 c_end_biases = 64 for i in range(8): for j in range(32): value = round(int(model.layer_stacks.output.weight[i, j] * 600 * 16) / 127) print(f"oW[{i}][{j}],{value},-127,127,{c_end_weights},0.0020") for i in range(8): value = int(model.layer_stacks.output.bias[i] * 600 * 16) print(f"oB[{i}],{value},-8192,8192,{c_end_biases},0.0020") ``` For more info on spsa tuning params in nets: https://github.com/official-stockfish/Stockfish/pull/5149 https://github.com/official-stockfish/Stockfish/pull/5254 Passed STC: https://tests.stockfishchess.org/tests/view/66894d64e59d990b103f8a37 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 32000 W: 8443 L: 8137 D: 15420 Ptnml(0-2): 80, 3627, 8309, 3875, 109 Passed LTC: https://tests.stockfishchess.org/tests/view/6689668ce59d990b103f8b8b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 172176 W: 43822 L: 43225 D: 85129 Ptnml(0-2): 97, 18821, 47633, 19462, 75 closes https://github.com/official-stockfish/Stockfish/pull/5459 bench 1120091 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index bdef9ceb620..047c4a56bc3 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-ddcfb9224cdb.nnue" +#define EvalFileDefaultNameBig "nn-74f1d263ae9a.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 5d3517c601c64d026824251784dd44f0cbf14873 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 7 Jul 2024 11:23:50 +0200 Subject: [PATCH 0665/1309] Fix output for GUI Fritz 19 can hang with the current way to provide output, i.e. too much output in a short time for a mate / depth 245 found quickly. fallout from 25361e514bffb81284d4311601a9f7a4a7ced79b closes https://github.com/official-stockfish/Stockfish/pull/5460 No functional change --- src/search.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3f1600474d4..d22761e3ef0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -355,9 +355,10 @@ void Search::Worker::iterative_deepening() { break; // When failing high/low give some update before a re-search. - // To avoid excessive output, only start at rootDepth > 30. + // To avoid excessive output that could hang GUIs like Fritz 19, only start + // at nodes > 10M (rather than depth N, which can be reached quickly) if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && rootDepth > 30) + && nodes > 10000000) main_manager()->pv(*this, threads, tt, rootDepth); // In case of failing low/high increase aspiration window and @@ -388,7 +389,7 @@ void Search::Worker::iterative_deepening() { std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); if (mainThread - && (threads.stop || pvIdx + 1 == multiPV || rootDepth > 30) + && (threads.stop || pvIdx + 1 == multiPV || nodes > 10000000) // A thread that aborted search can have mated-in/TB-loss PV and score // that cannot be trusted, i.e. it can be delayed or refuted if we would have // had time to fully search other root-moves. Thus we suppress this output and @@ -968,7 +969,7 @@ Value Search::Worker::search( ss->moveCount = ++moveCount; - if (rootNode && is_mainthread() && rootDepth > 30) + if (rootNode && is_mainthread() && nodes > 10000000) { main_manager()->updates.onIter( {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); From eac2d080a3279358a79e35bfcecf016d01db97e4 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 7 Jul 2024 22:25:10 +0300 Subject: [PATCH 0666/1309] Further simplify stat bonuses Based on recent simplification by linrock Since it completely removed any special bonuses based on values difference and made it flat stat_bonus(depth + 1) I got an idea that we might as well remove all (depth + 1) bonuses and make them usual depth bonuses. Also removes weird negative bonus for depth 1 as a side effect. Passed STC: https://tests.stockfishchess.org/tests/view/6689d817eca84f4d25863746 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 18080 W: 4789 L: 4552 D: 8739 Ptnml(0-2): 46, 1987, 4727, 2244, 36 Passed LTC: https://tests.stockfishchess.org/tests/view/6689daa4eca84f4d258639d7 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 109062 W: 27548 L: 27417 D: 54097 Ptnml(0-2): 58, 11983, 30293, 12164, 33 Passed direct LTC vs linrock patch: https://tests.stockfishchess.org/tests/view/668a46f8eca84f4d25866fe9 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 100002 W: 25351 L: 25209 D: 49442 Ptnml(0-2): 54, 11119, 27529, 11229, 70 closes https://github.com/official-stockfish/Stockfish/pull/5461 Bench 1175744 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d22761e3ef0..ac0e59b5e7a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -86,7 +86,7 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(190 * d - 298, 20, 1596); } +int stat_bonus(Depth d) { return std::min(190 * d - 108, 1596); } // History and stats update malus, based on depth int stat_malus(Depth d) { return (d < 4 ? 736 * d - 268 : 2044); } @@ -1782,7 +1782,7 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int quietMoveBonus = stat_bonus(depth + 1); + int quietMoveBonus = stat_bonus(depth); int quietMoveMalus = stat_malus(depth); if (!pos.capture_stage(bestMove)) From acd0a933ad5beacaafeb89025b2e60802e993e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Tue, 9 Jul 2024 00:48:40 +0200 Subject: [PATCH 0667/1309] Fix compilation on Apple MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Always use the posix function posix_memalign() as aligned memory allocator on Apple computers. This should allow to compile Stockfish out of the box on all versions of Mac OS X. Patch tested on the following systems (apart from the CI) : • Mac OS 10.9.6 (arch x86-64-sse41-popcnt) with gcc-10 • Mac OS 10.13.6 (arch x86-64-bmi2) with gcc-10, gcc-14 and clang-11 • Mac OS 14.1.1 (arch apple-silicon) with clang-15 closes https://github.com/official-stockfish/Stockfish/pull/5462 No functional change --- src/memory.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/memory.cpp b/src/memory.cpp index 565b39b2061..1769a6617b9 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -68,12 +68,12 @@ namespace Stockfish { // does not guarantee the availability of aligned_alloc(). Memory allocated with // std_aligned_alloc() must be freed with std_aligned_free(). void* std_aligned_alloc(size_t alignment, size_t size) { - // Apple requires 10.15, which is enforced in the makefile -#if defined(_ISOC11_SOURCE) || defined(__APPLE__) +#if defined(_ISOC11_SOURCE) return aligned_alloc(alignment, size); #elif defined(POSIXALIGNEDALLOC) - void* mem; - return posix_memalign(&mem, alignment, size) ? nullptr : mem; + void* mem = nullptr; + posix_memalign(&mem, alignment, size); + return mem; #elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) return _mm_malloc(size, alignment); #elif defined(_WIN32) From 4880ed4ad177f2cec50cfc784a6cbb65d31ff4ef Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 6 Jul 2024 20:07:01 -0700 Subject: [PATCH 0668/1309] Simplify Probcut Malus Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 74880 W: 19261 L: 19083 D: 36536 Ptnml(0-2): 202, 8861, 19120, 9071, 186 https://tests.stockfishchess.org/tests/view/668a0966eca84f4d25864cba Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 263916 W: 66690 L: 66718 D: 130508 Ptnml(0-2): 125, 29348, 73040, 29320, 125 https://tests.stockfishchess.org/tests/view/668a17e3eca84f4d25864e91 closes https://github.com/official-stockfish/Stockfish/pull/5464 bench 1331408 --- src/search.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ac0e59b5e7a..3e6c56a696d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -861,8 +861,6 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); - Move probcutCapturesSearched[32]; - int probcutCaptureCount = 0; Piece captured; while ((move = mp.next_move()) != Move::none()) @@ -900,25 +898,12 @@ Value Search::Worker::search( thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] << stat_bonus(depth - 2); - for (int i = 0; i < probcutCaptureCount; i++) - { - movedPiece = pos.moved_piece(probcutCapturesSearched[i]); - captured = pos.piece_on(probcutCapturesSearched[i].to_sq()); - - thisThread->captureHistory[movedPiece][probcutCapturesSearched[i].to_sq()] - [type_of(captured)] - << -stat_malus(depth - 3); - } - // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, unadjustedStaticEval, tt.generation()); return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) : value; } - - if (probcutCaptureCount < 32) - probcutCapturesSearched[probcutCaptureCount++] = move; } Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); From b209f14b1ee0cda8cbd7fa3a8349e65701d1869f Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 7 Jul 2024 15:13:40 -0400 Subject: [PATCH 0669/1309] Update default main net to nn-e8bac1c07a5a.nnue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created by modifying L2 weights from the previous main net (nn-74f1d263ae9a.nnue) with params found by spsa around 9k / 120k games at 120+1.2. 370 spsa params - L2 weights in nn-74f1d263ae9a.nnue where |val| >= 50 A: 6000, alpha: 0.602, gamma: 0.101 weights: [-127, 127], c_end = 6 To print the spsa params with nnue-pytorch: ``` import features from serialize import NNUEReader feature_set = features.get_feature_set_from_name("HalfKAv2_hm") with open("nn-74f1d263ae9a.nnue", "rb") as f: model = NNUEReader(f, feature_set).model c_end = 6 for i in range(8): for j in range(32): for k in range(30): value = int(model.layer_stacks.l2.weight[32 * i + j, k] * 64) if abs(value) >= 50: print(f"twoW[{i}][{j}][{k}],{value},-127,127,{c_end},0.0020") ``` Among the 370 params, 229 weights were changed. avg change: 0.0961 ± 1.67 range: [-4, 3] The number of weights changed, grouped by layer stack index, shows more weights were modified in the lower piece count buckets: [54, 52, 29, 23, 22, 18, 14, 17] Found with the same method described in: https://github.com/official-stockfish/Stockfish/pull/5459 Passed STC: https://tests.stockfishchess.org/tests/view/668aec9a58083e5fd88239e7 LLR: 3.00 (-2.94,2.94) <0.00,2.00> Total: 52384 W: 13569 L: 13226 D: 25589 Ptnml(0-2): 127, 6141, 13335, 6440, 149 Passed LTC: https://tests.stockfishchess.org/tests/view/668af50658083e5fd8823a0b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 46974 W: 12006 L: 11668 D: 23300 Ptnml(0-2): 25, 4992, 13121, 5318, 31 closes https://github.com/official-stockfish/Stockfish/pull/5466 bench 1300471 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 047c4a56bc3..4b5f447ee32 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-74f1d263ae9a.nnue" +#define EvalFileDefaultNameBig "nn-e8bac1c07a5a.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 362a77a3450335e1940020c080bf3b7b361c594a Mon Sep 17 00:00:00 2001 From: yl25946 Date: Tue, 9 Jul 2024 00:53:04 -0500 Subject: [PATCH 0670/1309] Move Loop Consistency in Probcut In probcut move loop, everything is enclosed within a large if statement. I've changed it to guard clauses to stay consistent with other move loops. closes https://github.com/official-stockfish/Stockfish/pull/5463 No functional change --- AUTHORS | 1 + src/search.cpp | 69 +++++++++++++++++++++++++++----------------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6eefb56dd5f..6957682f494 100644 --- a/AUTHORS +++ b/AUTHORS @@ -129,6 +129,7 @@ Kojirion Krystian Kuzniarek (kuzkry) Leonardo Ljubičić (ICCF World Champion) Leonid Pechenik (lp--) +Li Ying (yl25946) Liam Keegan (lkeegan) Linmiao Xu (linrock) Linus Arver (listx) diff --git a/src/search.cpp b/src/search.cpp index 3e6c56a696d..47c5dc88242 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -864,47 +864,54 @@ Value Search::Worker::search( Piece captured; while ((move = mp.next_move()) != Move::none()) - if (move != excludedMove && pos.legal(move)) - { - assert(pos.capture_stage(move)); + { + assert(move.is_ok()); - movedPiece = pos.moved_piece(move); - captured = pos.piece_on(move.to_sq()); + if (move == excludedMove) + continue; + // Check for legality + if (!pos.legal(move)) + continue; - // Prefetch the TT entry for the resulting position - prefetch(tt.first_entry(pos.key_after(move))); + assert(pos.capture_stage(move)); - ss->currentMove = move; - ss->continuationHistory = - &this - ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; + movedPiece = pos.moved_piece(move); + captured = pos.piece_on(move.to_sq()); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - pos.do_move(move, st); - // Perform a preliminary qsearch to verify that the move holds - value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); + // Prefetch the TT entry for the resulting position + prefetch(tt.first_entry(pos.key_after(move))); - // If the qsearch held, perform the regular search - if (value >= probCutBeta) - value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4, - !cutNode); + ss->currentMove = move; + ss->continuationHistory = + &this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; - pos.undo_move(move); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + pos.do_move(move, st); - if (value >= probCutBeta) - { - thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] - << stat_bonus(depth - 2); - - // Save ProbCut data into transposition table - ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, - depth - 3, move, unadjustedStaticEval, tt.generation()); - return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) - : value; - } + // Perform a preliminary qsearch to verify that the move holds + value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); + + // If the qsearch held, perform the regular search + if (value >= probCutBeta) + value = + -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4, !cutNode); + + pos.undo_move(move); + + if (value >= probCutBeta) + { + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] + << stat_bonus(depth - 2); + + // Save ProbCut data into transposition table + ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, + depth - 3, move, unadjustedStaticEval, tt.generation()); + return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) + : value; } + } Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); } From 98a7bb4436f04505a17f37befab0207252e97897 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 10 Jul 2024 17:56:43 +0200 Subject: [PATCH 0671/1309] CI give correct permissions for the clang-format action closes https://github.com/official-stockfish/Stockfish/pull/5470 No functional change --- .github/workflows/clang-format.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 630edbf93fe..452c2f2a30f 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -11,6 +11,10 @@ on: paths: - "**.cpp" - "**.h" + +permissions: + pull-requests: write + jobs: Clang-Format: name: Clang-Format @@ -39,6 +43,7 @@ jobs: _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ comment_tag: execution + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Comment on PR if: steps.clang-format.outcome != 'failure' @@ -49,3 +54,4 @@ jobs: create_if_not_exists: false comment_tag: execution mode: delete + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 7e72b37e4ce3351399bb0ac08686bd84cdc4fba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Wed, 10 Jul 2024 14:51:48 +0200 Subject: [PATCH 0672/1309] Clean up comments in code - Capitalize comments - Reformat multi-lines comments to equalize the widths of the lines - Try to keep the width of comments around 85 characters - Remove periods at the end of single-line comments closes https://github.com/official-stockfish/Stockfish/pull/5469 No functional change --- src/engine.h | 2 +- src/memory.cpp | 26 ++- src/memory.h | 25 +-- src/misc.cpp | 19 +- src/movepick.cpp | 8 +- src/nnue/nnue_feature_transformer.h | 40 +++-- src/numa.h | 203 +++++++++++---------- src/position.h | 4 +- src/search.cpp | 270 ++++++++++++++-------------- src/search.h | 9 +- src/thread.cpp | 31 ++-- src/types.h | 30 ++-- 12 files changed, 356 insertions(+), 311 deletions(-) diff --git a/src/engine.h b/src/engine.h index 0d6f0f2b826..127f7d7c84d 100644 --- a/src/engine.h +++ b/src/engine.h @@ -49,7 +49,7 @@ class Engine { Engine(std::string path = ""); - // Can't be movable due to components holding backreferences to fields + // Cannot be movable due to components holding backreferences to fields Engine(const Engine&) = delete; Engine(Engine&&) = delete; Engine& operator=(const Engine&) = delete; diff --git a/src/memory.cpp b/src/memory.cpp index 1769a6617b9..ae303c5377a 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -49,10 +49,12 @@ #include // std::cerr #include // std::endl #include + // The needed Windows API for processor groups could be missed from old Windows // versions, so instead of calling them directly (forcing the linker to resolve // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. + extern "C" { using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); @@ -64,9 +66,10 @@ using AdjustTokenPrivileges_t = namespace Stockfish { -// Wrapper for systems where the c++17 implementation -// does not guarantee the availability of aligned_alloc(). Memory allocated with -// std_aligned_alloc() must be freed with std_aligned_free(). +// Wrappers for systems where the c++17 implementation does not guarantee the +// availability of aligned_alloc(). Memory allocated with std_aligned_alloc() +// must be freed with std_aligned_free(). + void* std_aligned_alloc(size_t alignment, size_t size) { #if defined(_ISOC11_SOURCE) return aligned_alloc(alignment, size); @@ -96,7 +99,8 @@ void std_aligned_free(void* ptr) { #endif } -// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. +// aligned_large_pages_alloc() will return suitably aligned memory, +// if possible using large pages. #if defined(_WIN32) @@ -135,6 +139,7 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize return nullptr; // We need SeLockMemoryPrivilege, so try to enable it for the process + if (!OpenProcessToken_f( // OpenProcessToken() GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) return nullptr; @@ -149,8 +154,10 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, - // we still need to query GetLastError() to ensure that the privileges were actually obtained. + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() + // succeeds, we still need to query GetLastError() to ensure that the privileges + // were actually obtained. + if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && GetLastError() == ERROR_SUCCESS) @@ -189,9 +196,9 @@ void* aligned_large_pages_alloc(size_t allocSize) { void* aligned_large_pages_alloc(size_t allocSize) { #if defined(__linux__) - constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size + constexpr size_t alignment = 2 * 1024 * 1024; // 2MB page size assumed #else - constexpr size_t alignment = 4096; // assumed small page size + constexpr size_t alignment = 4096; // small page size assumed #endif // Round up to multiples of alignment @@ -206,7 +213,8 @@ void* aligned_large_pages_alloc(size_t allocSize) { #endif -// aligned_large_pages_free() will free the previously allocated ttmem +// aligned_large_pages_free() will free the previously memory allocated +// by aligned_large_pages_alloc(). The effect is a nop if mem == nullptr. #if defined(_WIN32) diff --git a/src/memory.h b/src/memory.h index ad7ca6025b3..3155a5aab12 100644 --- a/src/memory.h +++ b/src/memory.h @@ -33,13 +33,13 @@ namespace Stockfish { void* std_aligned_alloc(size_t alignment, size_t size); void std_aligned_free(void* ptr); -// memory aligned by page size, min alignment: 4096 bytes + +// Memory aligned by page size, min alignment: 4096 bytes void* aligned_large_pages_alloc(size_t size); -// nop if mem == nullptr -void aligned_large_pages_free(void* mem); +void aligned_large_pages_free(void* mem); -// frees memory which was placed there with placement new. -// works for both single objects and arrays of unknown bound +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. template void memory_deleter(T* ptr, FREE_FUNC free_func) { if (!ptr) @@ -53,15 +53,15 @@ void memory_deleter(T* ptr, FREE_FUNC free_func) { return; } -// frees memory which was placed there with placement new. -// works for both single objects and arrays of unknown bound +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. template void memory_deleter_array(T* ptr, FREE_FUNC free_func) { if (!ptr) return; - // Move back on the pointer to where the size is allocated. + // Move back on the pointer to where the size is allocated const size_t array_offset = std::max(sizeof(size_t), alignof(T)); char* raw_memory = reinterpret_cast(ptr) - array_offset; @@ -77,7 +77,7 @@ void memory_deleter_array(T* ptr, FREE_FUNC free_func) { free_func(raw_memory); } -// Allocates memory for a single object and places it there with placement new. +// Allocates memory for a single object and places it there with placement new template inline std::enable_if_t, T*> memory_allocator(ALLOC_FUNC alloc_func, Args&&... args) { @@ -86,7 +86,7 @@ inline std::enable_if_t, T*> memory_allocator(ALLOC_FUNC all return new (raw_memory) T(std::forward(args)...); } -// Allocates memory for an array of unknown bound and places it there with placement new. +// Allocates memory for an array of unknown bound and places it there with placement new template inline std::enable_if_t, std::remove_extent_t*> memory_allocator(ALLOC_FUNC alloc_func, size_t num) { @@ -94,7 +94,7 @@ memory_allocator(ALLOC_FUNC alloc_func, size_t num) { const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType)); - // save the array size in the memory location + // Save the array size in the memory location char* raw_memory = reinterpret_cast(alloc_func(array_offset + num * sizeof(ElementType))); ASSERT_ALIGNED(raw_memory, alignof(T)); @@ -104,7 +104,8 @@ memory_allocator(ALLOC_FUNC alloc_func, size_t num) { for (size_t i = 0; i < num; ++i) new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType(); - // Need to return the pointer at the start of the array so that the indexing in unique_ptr works + // Need to return the pointer at the start of the array so that + // the indexing in unique_ptr works. return reinterpret_cast(raw_memory + array_offset); } diff --git a/src/misc.cpp b/src/misc.cpp index b68c12b97f6..664ab4b89ff 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -112,14 +112,16 @@ class Logger { // Returns the full name of the current Stockfish version. -// For local dev compiles we try to append the commit sha and commit date -// from git if that fails only the local compilation date is set and "nogit" is specified: -// Stockfish dev-YYYYMMDD-SHA -// or -// Stockfish dev-YYYYMMDD-nogit +// +// For local dev compiles we try to append the commit SHA and +// commit date from git. If that fails only the local compilation +// date is set and "nogit" is specified: +// Stockfish dev-YYYYMMDD-SHA +// or +// Stockfish dev-YYYYMMDD-nogit // // For releases (non-dev builds) we only include the version number: -// Stockfish version +// Stockfish version std::string engine_info(bool to_uci) { std::stringstream ss; ss << "Stockfish " << version << std::setfill('0'); @@ -131,8 +133,9 @@ std::string engine_info(bool to_uci) { ss << stringify(GIT_DATE); #else constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); - std::string month, day, year; - std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" + + std::string month, day, year; + std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008" date >> month >> day >> year; ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) diff --git a/src/movepick.cpp b/src/movepick.cpp index f6f9f0dc895..c21b14a9089 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -59,8 +59,8 @@ enum Stages { QCHECK }; -// Sort moves in descending order up to and including -// a given limit. The order of moves smaller than the limit is left unspecified. +// Sort moves in descending order up to and including a given limit. +// The order of moves smaller than the limit is left unspecified. void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) @@ -125,8 +125,8 @@ MovePicker::MovePicker(const Position& p, stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } -// Constructor for ProbCut: we generate captures with SEE greater -// than or equal to the given threshold. +// Constructor for ProbCut: we generate captures with SEE greater than or equal +// to the given threshold. MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 483b84a8704..ad0fb1b4d93 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -354,13 +354,18 @@ class FeatureTransformer { for (IndexType j = 0; j < NumOutputChunks; ++j) { - // What we want to do is multiply inputs in a pairwise manner (after clipping), and then shift right by 9. - // Instead, we shift left by 7, and use mulhi, stripping the bottom 16 bits, effectively shifting right by 16, - // resulting in a net shift of 9 bits. We use mulhi because it maintains the sign of the multiplication (unlike mullo), - // allowing us to make use of packus to clip 2 of the inputs, resulting in a save of 2 "vec_max_16" calls. - // A special case is when we use NEON, where we shift left by 6 instead, because the instruction "vqdmulhq_s16" - // also doubles the return value after the multiplication, adding an extra shift to the left by 1, so we - // compensate by shifting less before the multiplication. + // What we want to do is multiply inputs in a pairwise manner + // (after clipping), and then shift right by 9. Instead, we + // shift left by 7, and use mulhi, stripping the bottom 16 bits, + // effectively shifting right by 16, resulting in a net shift + // of 9 bits. We use mulhi because it maintains the sign of + // the multiplication (unlike mullo), allowing us to make use + // of packus to clip 2 of the inputs, resulting in a save of 2 + // "vec_max_16" calls. A special case is when we use NEON, + // where we shift left by 6 instead, because the instruction + // "vqdmulhq_s16" also doubles the return value after the + // multiplication, adding an extra shift to the left by 1, so + // we compensate by shifting less before the multiplication. #if defined(USE_SSE2) constexpr int shift = 7; @@ -426,10 +431,10 @@ class FeatureTransformer { } // NOTE: The parameter states_to_update is an array of position states. - // All states must be sequential, that is states_to_update[i] must either be reachable - // by repeatedly applying ->previous from states_to_update[i+1]. - // computed_st must be reachable by repeatedly applying ->previous on - // states_to_update[0]. + // All states must be sequential, that is states_to_update[i] must + // either be reachable by repeatedly applying ->previous from + // states_to_update[i+1], and computed_st must be reachable by + // repeatedly applying ->previous on states_to_update[0]. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, @@ -446,7 +451,7 @@ class FeatureTransformer { #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array - // is defined in the VECTOR code below, once in each branch + // is defined in the VECTOR code below, once in each branch. vec_t acc[NumRegs]; psqt_vec_t psqt[NumPsqtRegs]; #endif @@ -474,7 +479,8 @@ class FeatureTransformer { StateInfo* st = computed_st; - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + // Now update the accumulators listed in states_to_update[], + // where the last element is a sentinel. #ifdef VECTOR if (N == 1 && (removed[0].size() == 1 || removed[0].size() == 2) && added[0].size() == 1) @@ -794,7 +800,7 @@ class FeatureTransformer { } // The accumulator of the refresh entry has been updated. - // Now copy its content to the actual accumulator we were refreshing + // Now copy its content to the actual accumulator we were refreshing. std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, sizeof(BiasType) * HalfDimensions); @@ -827,7 +833,7 @@ class FeatureTransformer { if ((oldest_st->*accPtr).computed[Perspective]) { - // Only update current position accumulator to minimize work. + // Only update current position accumulator to minimize work StateInfo* states_to_update[1] = {pos.state()}; update_accumulator_incremental(pos, oldest_st, states_to_update); } @@ -846,8 +852,8 @@ class FeatureTransformer { if (next == nullptr) return; - // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. - // Currently we update 2 accumulators. + // Now update the accumulators listed in states_to_update[], where + // the last element is a sentinel. Currently we update two accumulators: // 1. for the current position // 2. the next accumulator after the computed one // The heuristic may change in the future. diff --git a/src/numa.h b/src/numa.h index fd9abd4db7f..3de8281d152 100644 --- a/src/numa.h +++ b/src/numa.h @@ -37,8 +37,8 @@ #include "memory.h" -// We support linux very well, but we explicitly do NOT support Android, because there's -// no affected systems, not worth maintaining. +// We support linux very well, but we explicitly do NOT support Android, +// because there is no affected systems, not worth maintaining. #if defined(__linux__) && !defined(__ANDROID__) #if !defined(_GNU_SOURCE) #define _GNU_SOURCE @@ -81,9 +81,9 @@ using NumaIndex = size_t; inline CpuIndex get_hardware_concurrency() { CpuIndex concurrency = std::thread::hardware_concurrency(); - // Get all processors across all processor groups on windows, since ::hardware_concurrency - // only returns the number of processors in the first group, because only these - // are available to std::thread. + // Get all processors across all processor groups on windows, since + // hardware_concurrency() only returns the number of processors in + // the first group, because only these are available to std::thread. #ifdef _WIN64 concurrency = std::max(concurrency, GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)); #endif @@ -101,7 +101,7 @@ struct WindowsAffinity { // We also provide diagnostic for when the affinity is set to nullopt // whether it was due to being indeterminate. If affinity is indeterminate - // it's best to assume it is not set at all, so consistent with the meaning + // it is best to assume it is not set at all, so consistent with the meaning // of the nullopt affinity. bool isNewDeterminate = true; bool isOldDeterminate = true; @@ -119,23 +119,25 @@ struct WindowsAffinity { } // Since Windows 11 and Windows Server 2022 thread affinities can span - // processor groups and can be set as such by a new WinAPI function. - // However, we may need to force using the old API if we detect - // that the process has affinity set by the old API already and we want to override that. - // Due to the limitations of the old API we can't detect its use reliably. - // There will be cases where we detect not use but it has actually been used and vice versa. + // processor groups and can be set as such by a new WinAPI function. However, + // we may need to force using the old API if we detect that the process has + // affinity set by the old API already and we want to override that. Due to the + // limitations of the old API we cannot detect its use reliably. There will be + // cases where we detect not use but it has actually been used and vice versa. + bool likely_used_old_api() const { return oldApi.has_value() || !isOldDeterminate; } }; inline std::pair> get_process_group_affinity() { + // GetProcessGroupAffinity requires the GroupArray argument to be // aligned to 4 bytes instead of just 2. static constexpr size_t GroupArrayMinimumAlignment = 4; static_assert(GroupArrayMinimumAlignment >= alignof(USHORT)); // The function should succeed the second time, but it may fail if the group - // affinity has changed between GetProcessGroupAffinity calls. - // In such case we consider this a hard error, as we can't work with unstable affinities + // affinity has changed between GetProcessGroupAffinity calls. In such case + // we consider this a hard error, as we Cannot work with unstable affinities // anyway. static constexpr int MAX_TRIES = 2; USHORT GroupCount = 1; @@ -165,10 +167,10 @@ inline std::pair> get_process_group_affinity() { } // On Windows there are two ways to set affinity, and therefore 2 ways to get it. -// These are not consistent, so we have to check both. -// In some cases it is actually not possible to determine affinity. -// For example when two different threads have affinity on different processor groups, -// set using SetThreadAffinityMask, we can't retrieve the actual affinities. +// These are not consistent, so we have to check both. In some cases it is actually +// not possible to determine affinity. For example when two different threads have +// affinity on different processor groups, set using SetThreadAffinityMask, we cannot +// retrieve the actual affinities. // From documentation on GetProcessAffinityMask: // > If the calling process contains threads in multiple groups, // > the function returns zero for both affinity masks. @@ -196,8 +198,8 @@ inline WindowsAffinity get_process_affinity() { } else if (RequiredMaskCount > 0) { - // If RequiredMaskCount then these affinities were never set, but it's not consistent - // so GetProcessAffinityMask may still return some affinity. + // If RequiredMaskCount then these affinities were never set, but it's + // not consistent so GetProcessAffinityMask may still return some affinity. auto groupAffinities = std::make_unique(RequiredMaskCount); status = GetThreadSelectedCpuSetMasks_f(GetCurrentThread(), groupAffinities.get(), @@ -233,7 +235,7 @@ inline WindowsAffinity get_process_affinity() { DWORD_PTR proc, sys; status = GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys); - // If proc == 0 then we can't determine affinity because it spans processor groups. + // If proc == 0 then we cannot determine affinity because it spans processor groups. // On Windows 11 and Server 2022 it will instead // > If, however, hHandle specifies a handle to the current process, the function // > always uses the calling thread's primary group (which by default is the same @@ -246,10 +248,12 @@ inline WindowsAffinity get_process_affinity() { return affinity; } - // If SetProcessAffinityMask was never called the affinity - // must span all processor groups, but if it was called it must only span one. + // If SetProcessAffinityMask was never called the affinity must span + // all processor groups, but if it was called it must only span one. + std::vector groupAffinity; // We need to capture this later and capturing // from structured bindings requires c++20. + std::tie(status, groupAffinity) = get_process_group_affinity(); if (status == 0) { @@ -282,11 +286,12 @@ inline WindowsAffinity get_process_affinity() { // If we got here it means that either SetProcessAffinityMask was never set // or we're on Windows 11/Server 2022. - // Since Windows 11 and Windows Server 2022 the behaviour of GetProcessAffinityMask changed - // > If, however, hHandle specifies a handle to the current process, the function - // > always uses the calling thread's primary group (which by default is the same - // > as the process' primary group) in order to set the - // > lpProcessAffinityMask and lpSystemAffinityMask. + // Since Windows 11 and Windows Server 2022 the behaviour of + // GetProcessAffinityMask changed: + // > If, however, hHandle specifies a handle to the current process, + // > the function always uses the calling thread's primary group + // > (which by default is the same as the process' primary group) + // > in order to set the lpProcessAffinityMask and lpSystemAffinityMask. // In which case we can actually retrieve the full affinity. if (GetThreadSelectedCpuSetMasks_f != nullptr) @@ -300,9 +305,11 @@ inline WindowsAffinity get_process_affinity() { const int numActiveProcessors = GetActiveProcessorCount(static_cast(procGroupIndex)); - // We have to schedule to 2 different processors and & the affinities we get. - // Otherwise our processor choice could influence the resulting affinity. - // We assume the processor IDs within the group are filled sequentially from 0. + // We have to schedule to two different processors + // and & the affinities we get. Otherwise our processor + // choice could influence the resulting affinity. + // We assume the processor IDs within the group are + // filled sequentially from 0. uint64_t procCombined = std::numeric_limits::max(); uint64_t sysCombined = std::numeric_limits::max(); @@ -346,8 +353,9 @@ inline WindowsAffinity get_process_affinity() { } } - // We have to detect the case where the affinity was not set, or is set to all processors - // so that we correctly produce as std::nullopt result. + // We have to detect the case where the affinity was not set, + // or is set to all processors so that we correctly produce as + // std::nullopt result. if (!isAffinityFull) { affinity.oldApi = std::move(cpus); @@ -369,16 +377,16 @@ inline std::set get_process_affinity() { std::set cpus; - // For unsupported systems, or in case of a soft error, we may assume all processors - // are available for use. + // For unsupported systems, or in case of a soft error, we may assume + // all processors are available for use. [[maybe_unused]] auto set_to_all_cpus = [&]() { for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) cpus.insert(c); }; // cpu_set_t by default holds 1024 entries. This may not be enough soon, - // but there is no easy way to determine how many threads there actually is. - // In this case we just choose a reasonable upper bound. + // but there is no easy way to determine how many threads there actually + // is. In this case we just choose a reasonable upper bound. static constexpr CpuIndex MaxNumCpus = 1024 * 64; cpu_set_t* mask = CPU_ALLOC(MaxNumCpus); @@ -437,19 +445,19 @@ class NumaReplicatedAccessToken { NumaIndex n; }; -// Designed as immutable, because there is no good reason to alter an already existing config -// in a way that doesn't require recreating it completely, and it would be complex and expensive -// to maintain class invariants. -// The CPU (processor) numbers always correspond to the actual numbering used by the system. -// The NUMA node numbers MAY NOT correspond to the system's numbering of the NUMA nodes. -// In particular, empty nodes may be removed, or the user may create custom nodes. -// It is guaranteed that NUMA nodes are NOT empty, i.e. every node exposed by NumaConfig -// has at least one processor assigned. +// Designed as immutable, because there is no good reason to alter an already +// existing config in a way that doesn't require recreating it completely, and +// it would be complex and expensive to maintain class invariants. +// The CPU (processor) numbers always correspond to the actual numbering used +// by the system. The NUMA node numbers MAY NOT correspond to the system's +// numbering of the NUMA nodes. In particular, empty nodes may be removed, or +// the user may create custom nodes. It is guaranteed that NUMA nodes are NOT +// empty: every node exposed by NumaConfig has at least one processor assigned. // // We use startup affinities so as not to modify its own behaviour in time. // -// Until Stockfish doesn't support exceptions all places where an exception should be thrown -// are replaced by std::exit. +// Since Stockfish doesn't support exceptions all places where an exception +// should be thrown are replaced by std::exit. class NumaConfig { public: NumaConfig() : @@ -460,9 +468,9 @@ class NumaConfig { } // This function queries the system for the mapping of processors to NUMA nodes. - // On Linux we read from standardized kernel sysfs, with a fallback to single NUMA node. - // On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see - // comment for Windows implementation of get_process_affinity + // On Linux we read from standardized kernel sysfs, with a fallback to single NUMA + // node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see + // comment for Windows implementation of get_process_affinity. static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) { NumaConfig cfg = empty(); @@ -479,7 +487,6 @@ class NumaConfig { // On Linux things are straightforward, since there's no processor groups and // any thread can be scheduled on all processors. - // We try to gather this information from the sysfs first // https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node @@ -504,9 +511,9 @@ class NumaConfig { std::string path = std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist"; auto cpuIdsStr = read_file_to_string(path); - // Now, we only bail if the file does not exist. Some nodes may be empty, that's fine. - // An empty node still has a file that appears to have some whitespace, so we need - // to handle that. + // Now, we only bail if the file does not exist. Some nodes may be + // empty, that's fine. An empty node still has a file that appears + // to have some whitespace, so we need to handle that. if (!cpuIdsStr.has_value()) { fallback(); @@ -538,9 +545,10 @@ class NumaConfig { if (respectProcessAffinity) allowedCpus = STARTUP_PROCESSOR_AFFINITY.get_combined(); - // The affinity can't be determined in all cases on Windows, but we at least guarantee - // that the number of allowed processors is >= number of processors in the affinity mask. - // In case the user is not satisfied they must set the processor numbers explicitly. + // The affinity cannot be determined in all cases on Windows, + // but we at least guarantee that the number of allowed processors + // is >= number of processors in the affinity mask. In case the user + // is not satisfied they must set the processor numbers explicitly. auto is_cpu_allowed = [&allowedCpus](CpuIndex c) { return !allowedCpus.has_value() || allowedCpus->count(c) == 1; }; @@ -711,21 +719,22 @@ class NumaConfig { } bool suggests_binding_threads(CpuIndex numThreads) const { - // If we can reasonably determine that the threads can't be contained + // If we can reasonably determine that the threads cannot be contained // by the OS within the first NUMA node then we advise distributing // and binding threads. When the threads are not bound we can only use // NUMA memory replicated objects from the first node, so when the OS - // has to schedule on other nodes we lose performance. - // We also suggest binding if there's enough threads to distribute among nodes - // with minimal disparity. - // We try to ignore small nodes, in particular the empty ones. + // has to schedule on other nodes we lose performance. We also suggest + // binding if there's enough threads to distribute among nodes with minimal + // disparity. We try to ignore small nodes, in particular the empty ones. - // If the affinity set by the user does not match the affinity given by the OS - // then binding is necessary to ensure the threads are running on correct processors. + // If the affinity set by the user does not match the affinity given by + // the OS then binding is necessary to ensure the threads are running on + // correct processors. if (customAffinity) return true; - // We obviously can't distribute a single thread, so a single thread should never be bound. + // We obviously cannot distribute a single thread, so a single thread + // should never be bound. if (numThreads <= 1) return false; @@ -754,8 +763,8 @@ class NumaConfig { if (nodes.size() == 1) { - // special case for when there's no NUMA nodes - // doesn't buy us much, but let's keep the default path simple + // Special case for when there's no NUMA nodes. This doesn't buy us + // much, but let's keep the default path simple. ns.resize(numThreads, NumaIndex{0}); } else @@ -769,9 +778,11 @@ class NumaConfig { { float fill = static_cast(occupation[n] + 1) / static_cast(nodes[n].size()); - // NOTE: Do we want to perhaps fill the first available node up to 50% first before considering other nodes? - // Probably not, because it would interfere with running multiple instances. We basically shouldn't - // favor any particular node. + // NOTE: Do we want to perhaps fill the first available node + // up to 50% first before considering other nodes? + // Probably not, because it would interfere with running + // multiple instances. We basically shouldn't favor any + // particular node. if (fill < bestNodeFill) { bestNode = n; @@ -816,18 +827,19 @@ class NumaConfig { #elif defined(_WIN64) - // Requires Windows 11. No good way to set thread affinity spanning processor groups before that. + // Requires Windows 11. No good way to set thread affinity spanning + // processor groups before that. HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll")); auto SetThreadSelectedCpuSetMasks_f = SetThreadSelectedCpuSetMasks_t( (void (*)()) GetProcAddress(k32, "SetThreadSelectedCpuSetMasks")); - // We ALWAYS set affinity with the new API if available, - // because there's no downsides, and we forcibly keep it consistent - // with the old API should we need to use it. I.e. we always keep this as a superset - // of what we set with SetThreadGroupAffinity. + // We ALWAYS set affinity with the new API if available, because + // there's no downsides, and we forcibly keep it consistent with + // the old API should we need to use it. I.e. we always keep this + // as a superset of what we set with SetThreadGroupAffinity. if (SetThreadSelectedCpuSetMasks_f != nullptr) { - // Only available on Windows 11 and Windows Server 2022 onwards. + // Only available on Windows 11 and Windows Server 2022 onwards const USHORT numProcGroups = USHORT( ((highestCpuIndex + 1) + WIN_PROCESSOR_GROUP_SIZE - 1) / WIN_PROCESSOR_GROUP_SIZE); auto groupAffinities = std::make_unique(numProcGroups); @@ -857,22 +869,25 @@ class NumaConfig { // Sometimes we need to force the old API, but do not use it unless necessary. if (SetThreadSelectedCpuSetMasks_f == nullptr || STARTUP_USE_OLD_AFFINITY_API) { - // On earlier windows version (since windows 7) we can't run a single thread + // On earlier windows version (since windows 7) we cannot run a single thread // on multiple processor groups, so we need to restrict the group. // We assume the group of the first processor listed for this node. // Processors from outside this group will not be assigned for this thread. // Normally this won't be an issue because windows used to assign NUMA nodes - // such that they can't span processor groups. However, since Windows 10 Build 20348 - // the behaviour changed, so there's a small window of versions between this and Windows 11 - // that might exhibit problems with not all processors being utilized. - // We handle this in NumaConfig::from_system by manually splitting the nodes when - // we detect that there's no function to set affinity spanning processor nodes. - // This is required because otherwise our thread distribution code may produce - // suboptimal results. + // such that they cannot span processor groups. However, since Windows 10 + // Build 20348 the behaviour changed, so there's a small window of versions + // between this and Windows 11 that might exhibit problems with not all + // processors being utilized. + // + // We handle this in NumaConfig::from_system by manually splitting the + // nodes when we detect that there is no function to set affinity spanning + // processor nodes. This is required because otherwise our thread distribution + // code may produce suboptimal results. + // // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support GROUP_AFFINITY affinity; std::memset(&affinity, 0, sizeof(GROUP_AFFINITY)); - // We use an ordered set so we're guaranteed to get the smallest cpu number here. + // We use an ordered set to be sure to get the smallest cpu number here. const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE; affinity.Group = static_cast(forcedProcGroupIndex); for (CpuIndex c : nodes[n]) @@ -894,8 +909,8 @@ class NumaConfig { if (status == 0) std::exit(EXIT_FAILURE); - // We yield this thread just to be sure it gets rescheduled. - // This is defensive, allowed because this code is not performance critical. + // We yield this thread just to be sure it gets rescheduled. This is + // defensive, allowed because this code is not performance critical. SwitchToThread(); } @@ -1013,8 +1028,8 @@ class NumaConfig { class NumaReplicationContext; -// Instances of this class are tracked by the NumaReplicationContext instance -// NumaReplicationContext informs all tracked instances whenever NUMA configuration changes. +// Instances of this class are tracked by the NumaReplicationContext instance. +// NumaReplicationContext informs all tracked instances when NUMA configuration changes. class NumaReplicatedBase { public: NumaReplicatedBase(NumaReplicationContext& ctx); @@ -1034,9 +1049,9 @@ class NumaReplicatedBase { NumaReplicationContext* context; }; -// We force boxing with a unique_ptr. If this becomes an issue due to added indirection we -// may need to add an option for a custom boxing type. -// When the NUMA config changes the value stored at the index 0 is replicated to other nodes. +// We force boxing with a unique_ptr. If this becomes an issue due to added +// indirection we may need to add an option for a custom boxing type. When the +// NUMA config changes the value stored at the index 0 is replicated to other nodes. template class NumaReplicated: public NumaReplicatedBase { public: @@ -1090,8 +1105,8 @@ class NumaReplicated: public NumaReplicatedBase { } void on_numa_config_changed() override { - // Use the first one as the source. It doesn't matter which one we use, because they all must - // be identical, but the first one is guaranteed to exist. + // Use the first one as the source. It doesn't matter which one we use, + // because they all must be identical, but the first one is guaranteed to exist. auto source = std::move(instances[0]); replicate_from(std::move(*source)); } @@ -1167,7 +1182,7 @@ class NumaReplicationContext { private: NumaConfig config; - // std::set uses std::less by default, which is required for pointer comparison to be defined. + // std::set uses std::less by default, which is required for pointer comparison std::set trackedReplicatedObjects; }; diff --git a/src/position.h b/src/position.h index 3cfb87d065f..064dd5fa918 100644 --- a/src/position.h +++ b/src/position.h @@ -315,8 +315,8 @@ inline bool Position::capture(Move m) const { } // Returns true if a move is generated from the capture stage, having also -// queen promotions covered, i.e. consistency with the capture stage move generation -// is needed to avoid the generation of duplicate moves. +// queen promotions covered, i.e. consistency with the capture stage move +// generation is needed to avoid the generation of duplicate moves. inline bool Position::capture_stage(Move m) const { assert(m.is_ok()); return capture(m) || m.promotion_type() == QUEEN; diff --git a/src/search.cpp b/src/search.cpp index 47c5dc88242..d3d95eda8f3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -78,7 +78,8 @@ constexpr int futility_move_count(bool improving, Depth depth) { return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; } -// Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range +// Add correctionHistory value to raw staticEval and guarantee evaluation +// does not hit the tablebase range. Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; v += cv * std::abs(cv) / 5073; @@ -333,8 +334,8 @@ void Search::Worker::iterative_deepening() { int failedHighCnt = 0; while (true) { - // Adjust the effective depth searched, but ensure at least one effective increment - // for every four searchAgain steps (see issue #2717). + // Adjust the effective depth searched, but ensure at least one + // effective increment for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); rootDelta = beta - alpha; @@ -354,15 +355,15 @@ void Search::Worker::iterative_deepening() { if (threads.stop) break; - // When failing high/low give some update before a re-search. - // To avoid excessive output that could hang GUIs like Fritz 19, only start + // When failing high/low give some update before a re-search. To avoid + // excessive output that could hang GUIs like Fritz 19, only start // at nodes > 10M (rather than depth N, which can be reached quickly) if (mainThread && multiPV == 1 && (bestValue <= alpha || bestValue >= beta) && nodes > 10000000) main_manager()->pv(*this, threads, tt, rootDepth); - // In case of failing low/high increase aspiration window and - // re-search, otherwise exit the loop. + // In case of failing low/high increase aspiration window and re-search, + // otherwise exit the loop. if (bestValue <= alpha) { beta = (alpha + beta) / 2; @@ -390,10 +391,11 @@ void Search::Worker::iterative_deepening() { if (mainThread && (threads.stop || pvIdx + 1 == multiPV || nodes > 10000000) - // A thread that aborted search can have mated-in/TB-loss PV and score - // that cannot be trusted, i.e. it can be delayed or refuted if we would have - // had time to fully search other root-moves. Thus we suppress this output and - // below pick a proven score/PV for this thread (from the previous iteration). + // A thread that aborted search can have mated-in/TB-loss PV and + // score that cannot be trusted, i.e. it can be delayed or refuted + // if we would have had time to fully search other root-moves. Thus + // we suppress this output and below pick a proven score/PV for this + // thread (from the previous iteration). && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) main_manager()->pv(*this, threads, tt, rootDepth); @@ -504,6 +506,7 @@ void Search::Worker::iterative_deepening() { skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); } +// Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(0); captureHistory.fill(-700); @@ -523,7 +526,7 @@ void Search::Worker::clear() { } -// Main search function for both PV and non-PV nodes. +// Main search function for both PV and non-PV nodes template Value Search::Worker::search( Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { @@ -538,7 +541,7 @@ Value Search::Worker::search( // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); - // Check if we have an upcoming move that draws by repetition. + // Check if we have an upcoming move that draws by repetition if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) { alpha = value_draw(this->nodes); @@ -611,7 +614,7 @@ Value Search::Worker::search( Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; - // Step 4. Transposition table lookup. + // Step 4. Transposition table lookup excludedMove = ss->excludedMove; posKey = pos.key(); auto [ttHit, ttData, ttWriter] = tt.probe(posKey); @@ -676,7 +679,7 @@ Value Search::Worker::search( Value tbValue = VALUE_TB - ss->ply; - // use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score + // Use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score value = wdl < -drawScore ? -tbValue : wdl > drawScore ? tbValue : VALUE_DRAW + 2 * wdl * drawScore; @@ -771,8 +774,8 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; // Step 7. Razoring (~1 Elo) - // If eval is really low check with qsearch if it can exceed alpha, if it can't, - // return a fail low. + // If eval is really low, check with qsearch if we can exceed alpha. If the + // search suggests we cannot exceed alpha, return a speculative fail low. if (eval < alpha - 494 - 290 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); @@ -836,27 +839,26 @@ Value Search::Worker::search( if (PvNode && !ttData.move) depth -= 3; - // Use qsearch if depth <= 0. + // Use qsearch if depth <= 0 if (depth <= 0) return qsearch(pos, ss, alpha, beta); - // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, or - // by 1 if there is a ttMove with an upper bound. + // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, + // or by 1 if there is a ttMove with an upper bound. if (cutNode && depth >= 7 && (!ttData.move || ttData.bound == BOUND_UPPER)) depth -= 1 + !ttData.move; // Step 11. ProbCut (~10 Elo) - // 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. + // 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 + 184 - 53 * improving; - if ( - !PvNode && depth > 3 - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - // If value from transposition table is lower than probCutBeta, don't attempt 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 && ttData.value != VALUE_NONE && ttData.value < probCutBeta)) + if (!PvNode && depth > 3 + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + // If value from transposition table is lower than probCutBeta, don't attempt + // 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 && ttData.value != VALUE_NONE && ttData.value < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); @@ -870,7 +872,6 @@ Value Search::Worker::search( if (move == excludedMove) continue; - // Check for legality if (!pos.legal(move)) continue; @@ -1050,18 +1051,18 @@ Value Search::Worker::search( // 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 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) and lower extension - // margins scale well. + // Singular extension search (~76 Elo, ~170 nElo). 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) + // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove && depth >= 4 - (thisThread->completedDepth > 36) + ss->ttPv @@ -1089,28 +1090,31 @@ Value Search::Worker::search( // Multi-cut pruning // Our ttMove is assumed to fail high based on the bound of the TT entry, - // and if after excluding the ttMove with a reduced search we fail high over the original beta, - // we assume this expected cut-node is not singular (multiple moves fail high), - // and we can prune the whole subtree by returning a softbound. + // and if after excluding the ttMove with a reduced search we fail high + // over the original beta, we assume this expected cut-node is not + // singular (multiple moves fail high), and we can prune the whole + // subtree by returning a softbound. else if (value >= beta && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) return value; // Negative extensions - // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, - // but we cannot do multi-cut because (ttValue - margin) is lower than the original beta, - // we do not know 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 other moves failed high over (ttValue - margin) without the + // ttMove on a reduced search, but we cannot do multi-cut because + // (ttValue - margin) is lower than the original beta, we do not know + // 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) 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) + // If we are on a cutNode but the ttMove is not assumed to fail high + // over current beta (~1 Elo) else if (cutNode) extension = -2; } - // Extension for capturing the previous moved piece (~0 Elo on STC, ~1 Elo on LTC) + // Extension for capturing the previous moved piece (~1 Elo at LTC) else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] @@ -1136,9 +1140,9 @@ Value Search::Worker::search( pos.do_move(move, st, givesCheck); // 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. + // 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 if position is or has been on the PV (~7 Elo) if (ss->ttPv) @@ -1148,7 +1152,7 @@ Value Search::Worker::search( if (PvNode) r--; - // These reduction adjustments have no proven non-linear scaling. + // These reduction adjustments have no proven non-linear scaling // Increase reduction for cut nodes (~4 Elo) if (cutNode) @@ -1163,8 +1167,8 @@ Value Search::Worker::search( if ((ss + 1)->cutoffCnt > 3) r += 1 + !(PvNode || cutNode); - // For first picked move (ttMove) reduce reduction - // but never allow it to go below 0 (~3 Elo) + // For first picked move (ttMove) reduce reduction, but never allow + // reduction to go below 0 (~3 Elo) else if (move == ttData.move) r = std::max(0, r - 2); @@ -1190,8 +1194,8 @@ Value Search::Worker::search( // Do a full-depth search when reduced LMR search fails high if (value > alpha && d < newDepth) { - // Adjust full-depth search based on LMR results - if the result - // was good enough search deeper, if it was bad enough search shallower. + // 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 + 35 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) @@ -1237,8 +1241,8 @@ Value Search::Worker::search( // Step 20. Check for a new best move // Finished searching the move. If a stop occurred, the return value of - // the search cannot be trusted, and we return immediately without - // updating best move, PV and TT. + // the search cannot be trusted, and we return immediately without updating + // best move, principal variation nor transposition table. if (threads.stop.load(std::memory_order_relaxed)) return VALUE_ZERO; @@ -1351,7 +1355,8 @@ Value Search::Worker::search( if (!moveCount) bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW; - // If there is a move that produces search value greater than alpha we update the stats of searched moves + // If there is a move that produces search value greater than alpha, + // we update the stats of searched moves. else if (bestMove) update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, quietCount, capturesSearched, captureCount, depth); @@ -1385,8 +1390,8 @@ Value Search::Worker::search( if (bestValue <= alpha) ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); - // Write gathered information in transposition table - // Static evaluation is saved as it was before correction history + // Write gathered information in transposition table. Note that the + // static evaluation is saved as it was before correction history. if (!excludedMove && !(rootNode && thisThread->pvIdx)) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, bestValue >= beta ? BOUND_LOWER @@ -1410,12 +1415,12 @@ Value Search::Worker::search( } -// Quiescence search function, which is called by the main search function with zero depth, or -// recursively with further decreasing depth per call. 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 only. -// See https://www.chessprogramming.org/Horizon_Effect and https://www.chessprogramming.org/Quiescence_Search -// (~155 Elo) +// 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). +// See https://www.chessprogramming.org/Horizon_Effect +// and https://www.chessprogramming.org/Quiescence_Search template Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { @@ -1426,7 +1431,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); - // Check if we have an upcoming move that draws by repetition. (~1 Elo) + // Check if we have an upcoming move that draws by repetition (~1 Elo) if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) { alpha = value_draw(this->nodes); @@ -1469,9 +1474,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(0 <= ss->ply && ss->ply < MAX_PLY); - // Note that unlike regular search, which stores the literal depth into the TT, from QS we - // only store the current movegen stage as "depth". If in check, we search all evasions and - // thus store DEPTH_QS_CHECKS. (Evasions may be quiet, and _CHECKS includes quiets.) + // Note that unlike regular search, which stores the literal depth into the + // transposition table, from qsearch we only store the current movegen stage + // as "depth". If in check, we search all evasions and thus store DEPTH_QS_CHECKS. + // Evasions may be quiet, and _CHECKS includes quiets. Depth qsTtDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NORMAL; // Step 3. Transposition table lookup @@ -1512,7 +1518,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, } else { - // In case of null move search, use previous static eval with a different sign + // In case of null move search, use previous static eval with opposite sign unadjustedStaticEval = (ss - 1)->currentMove != Move::null() ? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) @@ -1542,21 +1548,20 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, (ss - 2)->continuationHistory}; - // Initialize a MovePicker object for the current position, and prepare to search the moves. - // We presently use two stages of qs movegen, first captures+checks, then captures only. - // (When in check, we simply search all evasions.) - // (Presently, having the checks stage is worth only 1 Elo, and may be removable in the near future, - // which would result in only a single stage of QS movegen.) + // Initialize a MovePicker object for the current position, and prepare to search + // the moves. We presently use two stages of move generator in quiescence search: + // first captures+checks, then captures only (but when in check, we simply search + // all evasions). Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); - // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs. + // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta + // cutoff occurs. while ((move = mp.next_move()) != Move::none()) { assert(move.is_ok()); - // Check for legality if (!pos.legal(move)) continue; @@ -1577,24 +1582,24 @@ 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) + // If static eval + value of piece we are going to capture is + // much lower than alpha, we can prune this move. (~2 Elo) if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); continue; } - // If static eval is much lower than alpha and move is not winning material - // we can prune this move. (~2 Elo) + // If static eval is much lower than alpha and move is + // not winning material, we can prune this move. (~2 Elo) if (futilityBase <= alpha && !pos.see_ge(move, 1)) { bestValue = std::max(bestValue, futilityBase); continue; } - // If static exchange evaluation is much worse than what is needed to not - // fall below alpha we can prune this move. + // If static exchange evaluation is much worse than what + // is needed to not fall below alpha, we can prune this move. if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) { bestValue = alpha; @@ -1654,8 +1659,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, } // Step 9. Check for mate - // All legal moves have been searched. A special case: if we're in check - // and no legal moves were found, it is checkmate. + // All legal moves have been searched. A special case: if we are + // in check and no legal moves were found, it is checkmate. if (ss->inCheck && bestValue == -VALUE_INFINITE) { assert(!MoveList(pos).size()); @@ -1665,8 +1670,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && bestValue >= beta) bestValue = (3 * bestValue + beta) / 4; - // Save gathered info in transposition table - // Static evaluation is saved as it was before adjustment by correction history + // Save gathered info in transposition table. The static evaluation + // is saved as it was before adjustment by correction history. ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, qsTtDepth, bestMove, unadjustedStaticEval, tt.generation()); @@ -1697,8 +1702,8 @@ TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elaps namespace { -// Adjusts a mate or TB score from "plies to mate from the root" -// to "plies to mate from the current position". Standard scores are unchanged. +// Adjusts a mate or TB score from "plies to mate from the root" to +// "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. Value value_to_tt(Value v, int ply) { @@ -1707,11 +1712,11 @@ Value value_to_tt(Value v, int ply) { } -// Inverse of value_to_tt(): it adjusts a mate or TB score -// from the transposition table (which refers to the plies to mate/be mated from -// current position) to "plies to mate/be mated (TB win/loss) from the root". -// However, to avoid potentially false mate or TB scores related to the 50 moves rule -// and the graph history interaction, we return the highest non-TB score instead. +// Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition +// table (which refers to the plies to mate/be mated from current position) to +// "plies to mate/be mated (TB win/loss) from the root". However, to avoid +// potentially false mate or TB scores related to the 50 moves rule and the +// graph history interaction, we return the highest non-TB score instead. Value value_from_tt(Value v, int ply, int r50c) { if (v == VALUE_NONE) @@ -1810,8 +1815,8 @@ void update_all_stats(const Position& pos, } -// Updates histories of the move pairs formed -// by moves at ply -1, -2, -3, -4, and -6 with current move. +// Updates histories of the move pairs formed by moves +// at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { bonus = bonus * 52 / 64; @@ -1859,8 +1864,8 @@ void update_quiet_stats( } -// When playing with strength handicap, choose the best move among a set of RootMoves -// using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. +// When playing with strength handicap, choose the best move among a set of +// RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { static PRNG rng(now()); // PRNG sequence should be non-deterministic @@ -1891,8 +1896,8 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { } -// Used to print debug info and, more importantly, -// to detect when we are out of available time and thus stop the search. +// Used to print debug info and, more importantly, to detect +// when we are out of available time and thus stop the search. void SearchManager::check_time(Search::Worker& worker) { if (--callsCnt > 0) return; @@ -1926,8 +1931,9 @@ void SearchManager::check_time(Search::Worker& worker) { } // Used to correct and extend PVs for moves that have a TB (but not a mate) score. -// Keeps the search based PV for as long as it is verified to maintain the game outcome, truncates afterwards. -// Finally, extends to mate the PV, providing a possible continuation (but not a proven mating line). +// Keeps the search based PV for as long as it is verified to maintain the game +// outcome, truncates afterwards. Finally, extends to mate the PV, providing a +// possible continuation (but not a proven mating line). void syzygy_extend_pv(const OptionsMap& options, const Search::LimitsType& limits, Position& pos, @@ -1937,7 +1943,7 @@ void syzygy_extend_pv(const OptionsMap& options, auto t_start = std::chrono::steady_clock::now(); int moveOverhead = int(options["Move Overhead"]); - // Do not use more than moveOverhead / 2 time, if time management is active. + // Do not use more than moveOverhead / 2 time, if time management is active auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool { auto t_end = std::chrono::steady_clock::now(); return limits.use_time_management() @@ -1968,7 +1974,7 @@ void syzygy_extend_pv(const OptionsMap& options, auto& st = sts.emplace_back(); pos.do_move(pvMove, st); - // don't allow for repetitions or drawing moves along the PV in TB regime. + // Do not allow for repetitions or drawing moves along the PV in TB regime if (config.rootInTB && pos.is_draw(ply)) { pos.undo_move(pvMove); @@ -1976,17 +1982,18 @@ void syzygy_extend_pv(const OptionsMap& options, break; } - // Full PV shown will thus be validated and end TB. - // If we can't validate the full PV in time, we don't show it. + // Full PV shown will thus be validated and end in TB. + // If we cannot validate the full PV in time, we do not show it. if (config.rootInTB && time_abort()) break; } - // resize the PV to the correct part + // Resize the PV to the correct part rootMove.pv.resize(ply); - // Step 2, now extend the PV to mate, as if the user explores syzygy-tables.info using - // top ranked moves (minimal DTZ), which gives optimal mates only for simple endgames e.g. KRvK + // 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)) { if (time_abort()) @@ -1998,8 +2005,8 @@ void syzygy_extend_pv(const OptionsMap& options, auto& rm = legalMoves.emplace_back(m); StateInfo tmpSI; pos.do_move(m, tmpSI); - // Give a score of each move to break DTZ ties - // restricting opponent mobility, but not giving the opponent a capture. + // Give a score of each move to break DTZ ties restricting opponent mobility, + // but not giving the opponent a capture. for (const auto& mOpp : MoveList(pos)) rm.tbRank -= pos.capture(mOpp) ? 100 : 1; pos.undo_move(m); @@ -2009,16 +2016,16 @@ void syzygy_extend_pv(const OptionsMap& options, if (legalMoves.size() == 0) break; - // sort moves according to their above assigned rank, + // Sort moves according to their above assigned rank. // This will break ties for moves with equal DTZ in rank_root_moves. std::stable_sort( legalMoves.begin(), legalMoves.end(), [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); - // The winning side tries to minimize DTZ, the losing side maximizes it. + // The winning side tries to minimize DTZ, the losing side maximizes it Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true); - // If DTZ is not available we might not find a mate, so we bail out. + // If DTZ is not available we might not find a mate, so we bail out if (!config.rootInTB || config.cardinality > 0) break; @@ -2030,23 +2037,24 @@ 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 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 e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106 - // which TB with dtz counter rounding cannot always correctly rank. See also + // Finding a draw in this function is an exceptional case, that cannot happen + // 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 + // (e.g. 8/8/6k1/3B4/3K4/4N3/8/8 w - - 54 106 ) which TB with dtz counter rounding + // cannot always correctly rank. See also // https://github.com/official-stockfish/Stockfish/issues/5175#issuecomment-2058893495 - // We adjust the score to match the found PV. Note that a TB loss score can be displayed - // if the engine did not find a drawing move yet, but eventually search will figure it out. - // E.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1 + // We adjust the score to match the found PV. Note that a TB loss score can be + // displayed if the engine did not find a drawing move yet, but eventually search + // will figure it out (e.g. 1kq5/q2r4/5K2/8/8/8/8/7Q w - - 96 1 ) if (pos.is_draw(0)) v = VALUE_DRAW; - // Undo the PV moves. + // Undo the PV moves for (auto it = rootMove.pv.rbegin(); it != rootMove.pv.rend(); ++it) pos.undo_move(*it); - // Inform if we couldn't get a full extension in time. + // Inform if we couldn't get a full extension in time if (time_abort()) sync_cout << "info string Syzygy based PV extension requires more time, increase Move Overhead as needed." @@ -2092,7 +2100,7 @@ void SearchManager::pv(Search::Worker& worker, for (Move m : rootMoves[i].pv) pv += UCIEngine::move(m, pos.is_chess960()) + " "; - // remove last whitespace + // Remove last whitespace if (!pv.empty()) pv.pop_back(); diff --git a/src/search.h b/src/search.h index 122cd549e3c..575967540fb 100644 --- a/src/search.h +++ b/src/search.h @@ -236,8 +236,8 @@ class Worker { public: Worker(SharedState&, std::unique_ptr, size_t, NumaReplicatedAccessToken); - // Called at instantiation to initialize Reductions tables - // Reset histories, usually before a new game + // Called at instantiation to initialize reductions tables. + // Reset histories, usually before a new game. void clear(); // Called when the program receives the UCI 'go' command. @@ -256,7 +256,7 @@ class Worker { private: void iterative_deepening(); - // Main search function for both PV and non-PV nodes + // This is the main search function, for both PV and non-PV nodes template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); @@ -266,8 +266,7 @@ class Worker { Depth reduction(bool i, Depth d, int mn, int delta) const; - // Get a pointer to the search manager, only allowed to be called by the - // main thread. + // Pointer to the search manager, only allowed to be called by the main thread SearchManager* main_manager() const { assert(threadIdx == 0); return static_cast(manager.get()); diff --git a/src/thread.cpp b/src/thread.cpp index 4acb9854bd4..f17fc4a5366 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -50,8 +50,8 @@ Thread::Thread(Search::SharedState& sharedState, run_custom_job([this, &binder, &sharedState, &sm, n]() { // Use the binder to [maybe] bind the threads to a NUMA node before doing - // the Worker allocation. - // Ideally we would also allocate the SearchManager here, but that's minor. + // the Worker allocation. Ideally we would also allocate the SearchManager + // here, but that's minor. this->numaAccessToken = binder(); this->worker = std::make_unique(sharedState, std::move(sm), n, this->numaAccessToken); @@ -72,27 +72,26 @@ Thread::~Thread() { stdThread.join(); } - // Wakes up the thread that will start the search void Thread::start_searching() { assert(worker != nullptr); run_custom_job([this]() { worker->start_searching(); }); } -// Wakes up the thread that will start the search +// Clears the histories for the thread worker (usually before a new game) void Thread::clear_worker() { assert(worker != nullptr); run_custom_job([this]() { worker->clear(); }); } -// Blocks on the condition variable -// until the thread has finished searching. +// Blocks on the condition variable until the thread has finished searching void Thread::wait_for_search_finished() { std::unique_lock lk(mutex); cv.wait(lk, [&] { return !searching; }); } +// Launching a function in the thread void Thread::run_custom_job(std::function f) { { std::unique_lock lk(mutex); @@ -103,8 +102,8 @@ void Thread::run_custom_job(std::function f) { cv.notify_one(); } -// Thread gets parked here, blocked on the -// condition variable, when it has no work to do. +// Thread gets parked here, blocked on the condition variable +// when the thread has no work to do. void Thread::idle_loop() { while (true) @@ -233,8 +232,9 @@ void ThreadPool::wait_on_thread(size_t threadId) { size_t ThreadPool::num_threads() const { return threads.size(); } -// Wakes up main thread waiting in idle_loop() and -// returns immediately. Main thread will wake up other threads and start the search. + +// Wakes up main thread waiting in idle_loop() and returns immediately. +// Main thread will wake up other threads and start the search. void ThreadPool::start_thinking(const OptionsMap& options, Position& pos, StateListPtr& states, @@ -274,8 +274,8 @@ void ThreadPool::start_thinking(const OptionsMap& options, // We use Position::set() to set root position across threads. But there are // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot // be deduced from a fen string, so set() clears them and they are set from - // setupStates->back() later. The rootState is per thread, earlier states are shared - // since they are read-only. + // setupStates->back() later. The rootState is per thread, earlier states are + // shared since they are read-only. for (auto&& th : threads) { th->run_custom_job([&]() { @@ -335,7 +335,7 @@ Thread* ThreadPool::get_best_thread() const { const bool newThreadInProvenLoss = newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; - // Note that we make sure not to pick a thread with truncated-PV for better viewer experience. + // We make sure not to pick a thread with truncated principal variation const bool betterVotingValue = thread_voting_value(th.get()) * int(newThreadPV.size() > 2) > thread_voting_value(bestThread) * int(bestThreadPV.size() > 2); @@ -363,8 +363,8 @@ Thread* ThreadPool::get_best_thread() const { } -// Start non-main threads -// Will be invoked by main thread after it has started searching +// Start non-main threads. +// Will be invoked by main thread after it has started searching. void ThreadPool::start_searching() { for (auto&& th : threads) @@ -374,7 +374,6 @@ void ThreadPool::start_searching() { // Wait for non-main threads - void ThreadPool::wait_for_search_finished() const { for (auto&& th : threads) diff --git a/src/types.h b/src/types.h index 10ad1fac9ef..8a9400bb8f8 100644 --- a/src/types.h +++ b/src/types.h @@ -137,9 +137,9 @@ enum Bound { BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; -// Value is used as an alias for int16_t, this is done to differentiate between -// a search value and any other integer value. The values used in search are always -// supposed to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range. +// Value is used as an alias for int, this is done to differentiate between a search +// value and any other integer value. The values used in search are always supposed +// to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range. using Value = int; constexpr Value VALUE_ZERO = 0; @@ -187,15 +187,20 @@ constexpr Value PieceValue[PIECE_NB] = { using Depth = int; enum : int { - // The following DEPTH_ constants are used for TT entries and QS movegen stages. In regular search, - // TT depth is literal: the search depth (effort) used to make the corresponding TT value. - // In qsearch, however, TT entries only store the current QS movegen stage (which should thus compare + // The following DEPTH_ constants are used for transposition table entries + // and quiescence search move generation stages. In regular search, the + // depth stored in the transposition table is literal: the search depth + // (effort) used to make the corresponding transposition table value. In + // quiescence search, however, the transposition table entries only store + // the current quiescence move generation stage (which should thus compare // lower than any regular search depth). DEPTH_QS_CHECKS = 0, DEPTH_QS_NORMAL = -1, - // For TT entries where no searching at all was done (whether regular or qsearch) we use - // _UNSEARCHED, which should thus compare lower than any QS or regular depth. _ENTRY_OFFSET is used - // only for the TT entry occupancy check (see tt.cpp), and should thus be lower than _UNSEARCHED. + // For transposition table entries where no searching at all was done + // (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus + // compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET + // is used only for the transposition table entry occupancy check (see tt.cpp), + // and should thus be lower than DEPTH_UNSEARCHED. DEPTH_UNSEARCHED = -2, DEPTH_ENTRY_OFFSET = -3 }; @@ -356,9 +361,10 @@ enum MoveType { // bit 14-15: special move flag: promotion (1), en passant (2), castling (3) // NOTE: en passant bit is set only when a pawn can be captured // -// Special cases are Move::none() and Move::null(). We can sneak these in because in -// any normal move destination square is always different from origin square -// while Move::none() and Move::null() have the same origin and destination square. +// Special cases are Move::none() and Move::null(). We can sneak these in because +// in any normal move the destination square and origin square are always different, +// but Move::none() and Move::null() have the same origin and destination square. + class Move { public: Move() = default; From 6135a0e2f830a587d2ac7a332bb62188fa924aad Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 10 Jul 2024 21:13:59 +0200 Subject: [PATCH 0673/1309] Provide more info on found TB files now uses the following format: `info string Found 510 WDL and 510 DTZ tablebase files (up to 6-man).` this clarifies exactly what has been found, as the difference matters, e.g. for the PV extension of TB scores. closes https://github.com/official-stockfish/Stockfish/pull/5471 No functional change --- src/syzygy/tbprobe.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index fc2a092aa54..e2344fdab2e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -443,6 +443,8 @@ class TBTables { std::deque> wdlTable; std::deque> dtzTable; + size_t foundDTZFiles = 0; + size_t foundWDLFiles = 0; void insert(Key key, TBTable* wdl, TBTable* dtz) { uint32_t homeBucket = uint32_t(key) & (Size - 1); @@ -486,9 +488,16 @@ class TBTables { memset(hashTable, 0, sizeof(hashTable)); wdlTable.clear(); dtzTable.clear(); + foundDTZFiles = 0; + foundWDLFiles = 0; } - size_t size() const { return wdlTable.size(); } - void add(const std::vector& pieces); + + void info() const { + sync_cout << "info string Found " << foundWDLFiles << " WDL and " << foundDTZFiles + << " DTZ tablebase files (up to " << MaxCardinality << "-man)." << sync_endl; + } + + void add(const std::vector& pieces); }; TBTables TBTables; @@ -501,13 +510,22 @@ void TBTables::add(const std::vector& pieces) { for (PieceType pt : pieces) code += PieceToChar[pt]; + code.insert(code.find('K', 1), "v"); + + TBFile file_dtz(code + ".rtbz"); // KRK -> KRvK + if (file_dtz.is_open()) + { + file_dtz.close(); + foundDTZFiles++; + } - TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + TBFile file(code + ".rtbw"); // KRK -> KRvK if (!file.is_open()) // Only WDL file is checked return; file.close(); + foundWDLFiles++; MaxCardinality = std::max(int(pieces.size()), MaxCardinality); @@ -1466,7 +1484,7 @@ void Tablebases::init(const std::string& paths) { } } - sync_cout << "info string Found " << TBTables.size() << " tablebases" << sync_endl; + TBTables.info(); } // Probe the WDL table for a particular position. From 8d1e41458e1fd12aaf42a13fcc0676ae487531f0 Mon Sep 17 00:00:00 2001 From: yl25946 Date: Wed, 10 Jul 2024 23:49:16 -0500 Subject: [PATCH 0674/1309] removed second killer move STC with movepicker rewrite: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 46656 W: 12208 L: 11995 D: 22453 Ptnml(0-2): 203, 5461, 11777, 5694, 193 https://tests.stockfishchess.org/tests/view/668d98a15034141ae5999e68 Earlier version passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 468896 W: 120999 L: 120054 D: 227843 Ptnml(0-2): 1207, 55209, 120639, 56218, 1175 https://tests.stockfishchess.org/tests/view/668b17d2cf91c430fca58630 Earlier version passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 550524 W: 139553 L: 139877 D: 271094 Ptnml(0-2): 333, 61646, 151616, 61346, 321 https://tests.stockfishchess.org/tests/view/668b2e04cf91c430fca586b1 closes https://github.com/official-stockfish/Stockfish/pull/5472 bench 1234309 Co-authored-by: rn5f107s2 --- src/movepick.cpp | 28 ++++++++++++---------------- src/movepick.h | 4 ++-- src/search.cpp | 24 ++++++++++-------------- src/search.h | 2 +- 4 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index c21b14a9089..d54bcbc74d1 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -20,7 +20,6 @@ #include #include -#include #include #include "bitboard.h" @@ -35,7 +34,7 @@ enum Stages { MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, - REFUTATION, + KILLER, QUIET_INIT, GOOD_QUIET, BAD_CAPTURE, @@ -91,14 +90,14 @@ MovePicker::MovePicker(const Position& p, const CapturePieceToHistory* cph, const PieceToHistory** ch, const PawnHistory* ph, - const Move* killers) : + Move km) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), pawnHistory(ph), ttMove(ttm), - refutations{{killers[0], 0}, {killers[1], 0}}, + killer{km, 0}, depth(d) { assert(d > 0); @@ -268,19 +267,17 @@ Move MovePicker::next_move(bool skipQuiets) { })) return *(cur - 1); - // Prepare the pointers to loop over the refutations array - cur = std::begin(refutations); - endMoves = std::end(refutations); - ++stage; [[fallthrough]]; - case REFUTATION : - if (select([&]() { - return *cur != Move::none() && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur); - })) - return *(cur - 1); + case KILLER : + // increment it before so if we aren't stuck here indefinitely ++stage; + + if (killer != ttMove && killer != Move::none() && !pos.capture_stage(killer) + && pos.pseudo_legal(killer)) + return killer; + [[fallthrough]]; case QUIET_INIT : @@ -297,8 +294,7 @@ Move MovePicker::next_move(bool skipQuiets) { [[fallthrough]]; case GOOD_QUIET : - if (!skipQuiets - && select([&]() { return *cur != refutations[0] && *cur != refutations[1]; })) + if (!skipQuiets && select([&]() { return *cur != killer; })) { if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) return *(cur - 1); @@ -327,7 +323,7 @@ Move MovePicker::next_move(bool skipQuiets) { case BAD_QUIET : if (!skipQuiets) - return select([&]() { return *cur != refutations[0] && *cur != refutations[1]; }); + return select([&]() { return *cur != killer; }); return Move::none(); diff --git a/src/movepick.h b/src/movepick.h index 2564f730190..86a2a5834d6 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -160,7 +160,7 @@ class MovePicker { const CapturePieceToHistory*, const PieceToHistory**, const PawnHistory*, - const Move*); + Move); MovePicker(const Position&, Move, Depth, @@ -185,7 +185,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove refutations[2], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + ExtMove killer, *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; int stage; int threshold; Depth depth; diff --git a/src/search.cpp b/src/search.cpp index d3d95eda8f3..a4da8cb002b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -123,7 +123,7 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_refutations(Stack* ss, Move move); +void update_killer(Stack* ss, Move move); void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); void update_quiet_stats( @@ -608,9 +608,9 @@ Value Search::Worker::search( assert(0 <= ss->ply && ss->ply < MAX_PLY); - bestMove = Move::none(); - (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); - (ss + 2)->cutoffCnt = 0; + bestMove = Move::none(); + (ss + 1)->killer = Move::none(); + (ss + 2)->cutoffCnt = 0; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; @@ -934,7 +934,7 @@ Value Search::Worker::search( MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, &thisThread->pawnHistory, ss->killers); + contHist, &thisThread->pawnHistory, ss->killer); value = bestValue; moveCountPruning = false; @@ -1157,7 +1157,7 @@ Value Search::Worker::search( // Increase reduction for cut nodes (~4 Elo) if (cutNode) r += 2 - (ttData.depth >= depth && ss->ttPv) - + (!ss->ttPv && move != ttData.move && move != ss->killers[0]); + + (!ss->ttPv && move != ttData.move && move != ss->killer); // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) @@ -1801,7 +1801,7 @@ void update_all_stats(const Position& pos, // main killer move in previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit - || ((ss - 1)->currentMove == (ss - 1)->killers[0])) + || ((ss - 1)->currentMove == (ss - 1)->killer)) && !pos.captured_piece()) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus); @@ -1832,14 +1832,10 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } // Updates move sorting heuristics -void update_refutations(Stack* ss, Move move) { +void update_killer(Stack* ss, Move move) { // Update killers - if (ss->killers[0] != move) - { - ss->killers[1] = ss->killers[0]; - ss->killers[0] = move; - } + ss->killer = move; } void update_quiet_histories( @@ -1858,7 +1854,7 @@ void update_quiet_histories( void update_quiet_stats( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { - update_refutations(ss, move); + update_killer(ss, move); update_quiet_histories(pos, ss, workerThread, move, bonus); } diff --git a/src/search.h b/src/search.h index 575967540fb..65394bc0763 100644 --- a/src/search.h +++ b/src/search.h @@ -65,7 +65,7 @@ struct Stack { int ply; Move currentMove; Move excludedMove; - Move killers[2]; + Move killer; Value staticEval; int statScore; int moveCount; From 42aae5fe8b3f41dac7b0e080ea2e55fa3816d802 Mon Sep 17 00:00:00 2001 From: Andyson007 Date: Thu, 11 Jul 2024 10:09:57 +0200 Subject: [PATCH 0675/1309] Fixed non UCI compliance print `` and accept `` for UCI string options, accepting empty strings as well. Internally use empty strings (`""`). closes https://github.com/official-stockfish/Stockfish/pull/5474 No functional change --- AUTHORS | 1 + src/engine.cpp | 2 +- src/syzygy/tbprobe.cpp | 2 +- src/ucioption.cpp | 14 +++++++++++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6957682f494..1ac40d879df 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,6 +20,7 @@ Alexander Kure Alexander Pagel (Lolligerhans) Alfredo Menezes (lonfom169) Ali AlZhrani (Cooffe) +Andreas Jan van der Meulen (Andyson007) Andreas Matthies (Matthies) Andrei Vetrov (proukornew) Andrew Grant (AndyGrant) diff --git a/src/engine.cpp b/src/engine.cpp index 2bc0db6affb..41b19ac6859 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -93,7 +93,7 @@ Engine::Engine(std::string path) : options["UCI_LimitStrength"] << Option(false); options["UCI_Elo"] << Option(1320, 1320, 3190); options["UCI_ShowWDL"] << Option(false); - options["SyzygyPath"] << Option("", [](const Option& o) { + options["SyzygyPath"] << Option("", [](const Option& o) { Tablebases::init(o); return std::nullopt; }); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index e2344fdab2e..9b24e700b18 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1344,7 +1344,7 @@ void Tablebases::init(const std::string& paths) { MaxCardinality = 0; TBFile::Paths = paths; - if (paths.empty() || paths == "") + if (paths.empty()) return; // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 1cd028c9932..455803cfe1b 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -166,7 +166,9 @@ Option& Option::operator=(const std::string& v) { return *this; } - if (type != "button") + if (type == "string") + currentValue = v == "" ? "" : v; + else if (type != "button") currentValue = v; if (on_change) @@ -188,10 +190,16 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { const Option& o = it.second; os << "\noption name " << it.first << " type " << o.type; - if (o.type == "string" || o.type == "check" || o.type == "combo") + if (o.type == "check" || o.type == "combo") os << " default " << o.defaultValue; - if (o.type == "spin") + else if (o.type == "string") + { + std::string defaultValue = o.defaultValue.empty() ? "" : o.defaultValue; + os << " default " << defaultValue; + } + + else if (o.type == "spin") os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " << o.max; From 3df09c04d7081d341cb0c5bcc3adc498ba877f9a Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 9 Jul 2024 13:04:47 -0500 Subject: [PATCH 0676/1309] Simplify Away Refutation Stage Simplify away killer stage to a constant bonus given to the killer move during quiet move scoring. Passed Non-regression STC (Against then-pending PR #5472): LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 106176 W: 27685 L: 27539 D: 50952 Ptnml(0-2): 410, 12765, 26637, 12821, 455 https://tests.stockfishchess.org/tests/view/668dd0835034141ae5999e8f Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 92472 W: 23426 L: 23276 D: 45770 Ptnml(0-2): 55, 10376, 25215, 10544, 46 https://tests.stockfishchess.org/tests/view/669019e45034141ae5999fd2 closes https://github.com/official-stockfish/Stockfish/pull/5476 Bench 1459677 --- src/movepick.cpp | 19 +++++-------------- src/movepick.h | 12 ++++++------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index d54bcbc74d1..7619471f15b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -34,7 +34,6 @@ enum Stages { MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, - KILLER, QUIET_INIT, GOOD_QUIET, BAD_CAPTURE, @@ -97,7 +96,7 @@ MovePicker::MovePicker(const Position& p, continuationHistory(ch), pawnHistory(ph), ttMove(ttm), - killer{km, 0}, + killer(km), depth(d) { assert(d > 0); @@ -184,6 +183,8 @@ void MovePicker::score() { m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; + m.value += (m == killer) * 65536; + // bonus for checks m.value += bool(pos.check_squares(pt) & to) * 16384; @@ -270,16 +271,6 @@ Move MovePicker::next_move(bool skipQuiets) { ++stage; [[fallthrough]]; - case KILLER : - // increment it before so if we aren't stuck here indefinitely - ++stage; - - if (killer != ttMove && killer != Move::none() && !pos.capture_stage(killer) - && pos.pseudo_legal(killer)) - return killer; - - [[fallthrough]]; - case QUIET_INIT : if (!skipQuiets) { @@ -294,7 +285,7 @@ Move MovePicker::next_move(bool skipQuiets) { [[fallthrough]]; case GOOD_QUIET : - if (!skipQuiets && select([&]() { return *cur != killer; })) + if (!skipQuiets && select([]() { return true; })) { if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) return *(cur - 1); @@ -323,7 +314,7 @@ Move MovePicker::next_move(bool skipQuiets) { case BAD_QUIET : if (!skipQuiets) - return select([&]() { return *cur != killer; }); + return select([]() { return true; }); return Move::none(); diff --git a/src/movepick.h b/src/movepick.h index 86a2a5834d6..c6a5d25aecc 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -184,12 +184,12 @@ class MovePicker { const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; - Move ttMove; - ExtMove killer, *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; - int stage; - int threshold; - Depth depth; - ExtMove moves[MAX_MOVES]; + Move ttMove, killer; + ExtMove * cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + int stage; + int threshold; + Depth depth; + ExtMove moves[MAX_MOVES]; }; } // namespace Stockfish From 024eb6f453e06e37ceca81d5f759b8fe6006b03b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 11 Jul 2024 14:07:38 -0700 Subject: [PATCH 0677/1309] Unify Movepick Initializer Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 168704 W: 43524 L: 43455 D: 81725 Ptnml(0-2): 414, 17173, 49076, 17308, 381 https://tests.stockfishchess.org/tests/view/66904b7b5034141ae599a197 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 120294 W: 30473 L: 30364 D: 59457 Ptnml(0-2): 40, 10974, 38032, 11039, 62 https://tests.stockfishchess.org/tests/view/66905b235034141ae599a223 closes https://github.com/official-stockfish/Stockfish/pull/5477 bench 1459677 --- src/movepick.cpp | 25 ++++--------------------- src/movepick.h | 9 +-------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 7619471f15b..55bacf6e747 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -98,29 +98,12 @@ MovePicker::MovePicker(const Position& p, ttMove(ttm), killer(km), depth(d) { - assert(d > 0); - stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm)); -} - -// Constructor for quiescence search -MovePicker::MovePicker(const Position& p, - Move ttm, - Depth d, - const ButterflyHistory* mh, - const CapturePieceToHistory* cph, - const PieceToHistory** ch, - const PawnHistory* ph) : - pos(p), - mainHistory(mh), - captureHistory(cph), - continuationHistory(ch), - pawnHistory(ph), - ttMove(ttm), - depth(d) { - assert(d <= 0); + if (pos.checkers()) + stage = EVASION_TT + !(ttm && pos.pseudo_legal(ttm)); - stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); + else + stage = (depth > 0 ? MAIN_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } // Constructor for ProbCut: we generate captures with SEE greater than or equal diff --git a/src/movepick.h b/src/movepick.h index c6a5d25aecc..92e11de2bf3 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -160,14 +160,7 @@ class MovePicker { const CapturePieceToHistory*, const PieceToHistory**, const PawnHistory*, - Move); - MovePicker(const Position&, - Move, - Depth, - const ButterflyHistory*, - const CapturePieceToHistory*, - const PieceToHistory**, - const PawnHistory*); + Move killer = Move::none()); MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); From 563d268519885a411e9a3b784875e457aeb26929 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Sat, 13 Jul 2024 00:53:34 +0900 Subject: [PATCH 0678/1309] Simplify futility_move_count This patch reverts changes from #4032 which was introduced as a speedup. Modern compilers no longer use DIV/IDIV instructions, potentially making the explicit branch perform worse. Since evaluations spend significantly more time now, the impact of the speedup in search diminishes with old compilers as well. GCC 14.1.0 profile-build, x86-64-vnni512 ``` .text:000000014010FEA9 mov ecx, [rsp+3FB8h+var_3F5C] ... .text:000000014010FEBD mov r10d, ecx .text:000000014010FEC0 imul r10d, ecx .text:000000014010FEC4 mov ecx, dword ptr [rsp+3FB8h+var_3F44+4] .text:000000014010FEC8 add r10d, 3 .text:000000014010FECC mov r11d, r10d .text:000000014010FECF sar r11d, 1 .text:000000014010FED2 cmp [rsp+3FB8h+var_3EE7], 0 .text:000000014010FEDA cmovnz r11d, r10d ``` LLVM 18.1.18 profile-build, x86-64-vnni512 ``` .text:0000000140001EDC mov [rsp+40h+arg_E0], r13 .text:0000000140001EE4 movsxd rcx, r13d .text:0000000140001EE7 mov rax, rcx .text:0000000140001EEA mov [rsp+40h+arg_B8], rcx .text:0000000140001EF2 imul eax, eax .text:0000000140001EF5 add eax, 3 .text:0000000140001EF8 mov ecx, [rsp+40h+arg_8C] .text:0000000140001EFF shrx eax, eax, ecx .text:0000000140001F04 mov [rsp+40h+arg_190], rax ``` Passed non-regression STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 109504 W: 28420 L: 28280 D: 52804 Ptnml(0-2): 355, 12326, 29273, 12420, 378 https://tests.stockfishchess.org/tests/view/6690dc095034141ae599c5fe closes https://github.com/official-stockfish/Stockfish/pull/5478 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a4da8cb002b..26bee2c134e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -75,7 +75,7 @@ Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorseni } constexpr int futility_move_count(bool improving, Depth depth) { - return improving ? (3 + depth * depth) : (3 + depth * depth) / 2; + return (3 + depth * depth) / (2 - improving); } // Add correctionHistory value to raw staticEval and guarantee evaluation From 930915de901b89c7f7d4bf1495c7e949c0d5e546 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 13 Jul 2024 05:34:09 +0300 Subject: [PATCH 0679/1309] Decrease delta Decrease delta in aspiration windows - both initial value and quadratic function of previous best value. Passed STC: https://tests.stockfishchess.org/tests/view/6691a52ec6827afcdcee1569 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 55456 W: 14449 L: 14107 D: 26900 Ptnml(0-2): 174, 6416, 14193, 6784, 161 Passed LTC: https://tests.stockfishchess.org/tests/view/6691aac1c6827afcdcee1625 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 107940 W: 27530 L: 27065 D: 53345 Ptnml(0-2): 52, 11787, 29840, 12226, 65 closes https://github.com/official-stockfish/Stockfish/pull/5479 bench 1547707 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 26bee2c134e..d1e0b32109e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -320,7 +320,7 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 9 + avg * avg / 10424; + delta = 5 + avg * avg / 13424; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); From 558abdbe8a1262b7f15f20ccf961b335c4713364 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Fri, 12 Jul 2024 10:10:00 -0400 Subject: [PATCH 0680/1309] Set best value to futility value after pruned quiet move Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6691592f5034141ae599c68d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 278496 W: 71818 L: 71865 D: 134813 Ptnml(0-2): 865, 33311, 70978, 33194, 900 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/66918fca5034141ae599e761 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 202986 W: 51048 L: 51013 D: 100925 Ptnml(0-2): 107, 22552, 56133, 22601, 100 closes https://github.com/official-stockfish/Stockfish/pull/5480 bench 1715206 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d1e0b32109e..ebae94ef18c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1035,7 +1035,7 @@ Value Search::Worker::search( { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) - bestValue = (bestValue + futilityValue * 3) / 4; + bestValue = futilityValue; continue; } From 7395d568329f404cd4dc3f4c2fe093059ac2b391 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 13 Jul 2024 14:44:23 +0300 Subject: [PATCH 0681/1309] bonus calculation for prior countermoves Introduce a new term to the bonus calculation for prior countermoves Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 140896 W: 36545 L: 36079 D: 68272 Ptnml(0-2): 383, 16505, 36217, 16949, 394 https://tests.stockfishchess.org/tests/view/6691c73cc6827afcdcee1816 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 126660 W: 32089 L: 31587 D: 62984 Ptnml(0-2): 63, 13774, 35154, 14276, 63 https://tests.stockfishchess.org/tests/view/6691cdc4c6827afcdcee1930 closes https://github.com/official-stockfish/Stockfish/pull/5483 bench: 1250388 --- src/search.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ebae94ef18c..87310301ff6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1364,12 +1364,13 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (114 * (depth > 5) + 116 * (PvNode || cutNode) + 123 * ((ss - 1)->moveCount > 8) - + 64 * (!ss->inCheck && bestValue <= ss->staticEval - 108) - + 153 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76)); + int bonus = (138 * (depth > 5) + 58 * (PvNode || cutNode) + 160 * ((ss - 1)->moveCount > 8) + + 84 * (!ss->inCheck && bestValue <= ss->staticEval - 108) + + 153 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76) + + 32 * (!(ss - 1)->inCheck && bestValue > -(ss - 1)->staticEval + 76)); // Proportional to "how much damage we have to undo" - bonus += std::clamp(-(ss - 1)->statScore / 100, -50, 274); + bonus += std::clamp(-(ss - 1)->statScore / 100, -64, 300); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus / 100); From 2b37b151dd8c4374353d9e185bddbea1cfe300b0 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Sun, 14 Jul 2024 01:03:49 +0900 Subject: [PATCH 0682/1309] Use ValueList to represent searched moves array This PR replaces a pair of array and size with existing ValueList class. Removes two local variables in search and two parameters of update_all_stats. Passed non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 227040 W: 58472 L: 58463 D: 110105 Ptnml(0-2): 495, 23572, 65427, 23481, 545 https://tests.stockfishchess.org/tests/view/669299204ff211be9d4e98dc closes https://github.com/official-stockfish/Stockfish/pull/5484 No functional change --- src/search.cpp | 75 +++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 87310301ff6..1d709749de7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -128,16 +128,14 @@ void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); void update_quiet_stats( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); -void update_all_stats(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move bestMove, - Square prevSq, - Move* quietsSearched, - int quietCount, - Move* capturesSearched, - int captureCount, - Depth depth); +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + ValueList& quietsSearched, + ValueList& capturesSearched, + Depth depth); } // namespace @@ -554,7 +552,7 @@ Value Search::Worker::search( assert(0 < depth && depth < MAX_PLY); assert(!(PvNode && cutNode)); - Move pv[MAX_PLY + 1], capturesSearched[32], quietsSearched[32]; + Move pv[MAX_PLY + 1]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); @@ -563,18 +561,20 @@ Value Search::Worker::search( Depth extension, newDepth; Value bestValue, value, eval, maxValue, probCutBeta; bool givesCheck, improving, priorCapture, opponentWorsening; - bool capture, moveCountPruning, ttCapture; + bool capture, ttCapture; Piece movedPiece; - int moveCount, captureCount, quietCount; + + ValueList capturesSearched; + ValueList quietsSearched; // Step 1. Initialize node Worker* thisThread = this; ss->inCheck = pos.checkers(); priorCapture = pos.captured_piece(); Color us = pos.side_to_move(); - moveCount = captureCount = quietCount = ss->moveCount = 0; - bestValue = -VALUE_INFINITE; - maxValue = VALUE_INFINITE; + ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; // Check for the available remaining time if (is_mainthread()) @@ -936,8 +936,10 @@ Value Search::Worker::search( MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->killer); - value = bestValue; - moveCountPruning = false; + value = bestValue; + + int moveCount = 0; + bool moveCountPruning = false; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. @@ -1334,9 +1336,9 @@ Value Search::Worker::search( if (move != bestMove && moveCount <= 32) { if (capture) - capturesSearched[captureCount++] = move; + capturesSearched.push_back(move); else - quietsSearched[quietCount++] = move; + quietsSearched.push_back(move); } } @@ -1358,8 +1360,7 @@ Value Search::Worker::search( // If there is a move that produces search value greater than alpha, // we update the stats of searched moves. else if (bestMove) - update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, quietCount, - capturesSearched, captureCount, depth); + update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1765,16 +1766,14 @@ void update_pv(Move* pv, Move move, const Move* childPv) { // Updates stats at the end of search() when a bestMove is found -void update_all_stats(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move bestMove, - Square prevSq, - Move* quietsSearched, - int quietCount, - Move* capturesSearched, - int captureCount, - Depth depth) { +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + ValueList& quietsSearched, + ValueList& capturesSearched, + Depth depth) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); @@ -1788,8 +1787,8 @@ void update_all_stats(const Position& pos, update_quiet_stats(pos, ss, workerThread, bestMove, quietMoveBonus); // Decrease stats for all non-best quiet moves - for (int i = 0; i < quietCount; ++i) - update_quiet_histories(pos, ss, workerThread, quietsSearched[i], -quietMoveMalus); + for (Move move : quietsSearched) + update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus); } else { @@ -1807,11 +1806,11 @@ void update_all_stats(const Position& pos, update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus); // Decrease stats for all non-best capture moves - for (int i = 0; i < captureCount; ++i) + for (Move move : capturesSearched) { - moved_piece = pos.moved_piece(capturesSearched[i]); - captured = type_of(pos.piece_on(capturesSearched[i].to_sq())); - captureHistory[moved_piece][capturesSearched[i].to_sq()][captured] << -quietMoveMalus; + moved_piece = pos.moved_piece(move); + captured = type_of(pos.piece_on(move.to_sq())); + captureHistory[moved_piece][move.to_sq()][captured] << -quietMoveMalus; } } From de2bf1a186ef036a7df06b448f41b00ff62f9322 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 13 Jul 2024 22:18:38 +0300 Subject: [PATCH 0683/1309] Remove quiet history pruning depth limit This patch removes lmrDepth limit for quiet moves history based pruning. Previously removal of this type of depth limits was considered bad because it was performing bad for matetrack - but with this pruning heuristic this shouldn't be that bad because it's "naturally" depth limited by history threshold and should be completely disabled at depth >= 15 or so. Also this heuristic in previous years was known to scale non-linearly - bigger lmrDepth thresholds were better at longer time controls and removing it completely probably should scale pretty well. Passed STC: https://tests.stockfishchess.org/tests/view/6692b89b4ff211be9d4eab21 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 114464 W: 29675 L: 29545 D: 55244 Ptnml(0-2): 372, 12516, 31329, 12640, 375 Passed LTC: https://tests.stockfishchess.org/tests/view/6692c4554ff211be9d4eab3d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 67746 W: 17182 L: 17014 D: 33550 Ptnml(0-2): 28, 6993, 19652, 7183, 17 closes https://github.com/official-stockfish/Stockfish/pull/5485 Bench: 1250388 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1d709749de7..3c6617ebdf2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1022,7 +1022,7 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4165 * depth) + if (history < -4165 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; From e443b2459e973c47dbf7e46104bf3bb02ffbb6f7 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sat, 13 Jul 2024 00:40:07 -0400 Subject: [PATCH 0684/1309] Separate eval params for smallnet and main net Values found with spsa around 80% of 120k games at 60+0.6: https://tests.stockfishchess.org/tests/view/669205dac6827afcdcee3ea4 Passed STC: https://tests.stockfishchess.org/tests/view/6692928b4ff211be9d4e98a9 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 313696 W: 81107 L: 80382 D: 152207 Ptnml(0-2): 934, 36942, 80363, 37683, 926 Passed LTC: https://tests.stockfishchess.org/tests/view/6692aab54ff211be9d4e9915 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 228420 W: 57903 L: 57190 D: 113327 Ptnml(0-2): 131, 25003, 63243, 25688, 145 closes https://github.com/official-stockfish/Stockfish/pull/5486 bench 1319322 --- src/evaluate.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 44890a361af..1cff6478fb9 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -79,11 +79,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, } // Blend optimism and eval with nnue complexity - optimism += optimism * nnueComplexity / 457; - nnue -= nnue * nnueComplexity / 19157; + optimism += optimism * nnueComplexity / (smallNet ? 433 : 453); + nnue -= nnue * nnueComplexity / (smallNet ? 18815 : 17864); - int material = 554 * pos.count() + pos.non_pawn_material(); - v = (nnue * (73921 + material) + optimism * (8112 + material)) / 73260; + int material = (smallNet ? 553 : 532) * pos.count() + pos.non_pawn_material(); + v = (nnue * (73921 + material) + optimism * (8112 + material)) / (smallNet ? 68104 : 74715); // Evaluation grain (to get more alpha-beta cuts) with randomization (for robustness) v = (v / 16) * 16 - 1 + (pos.key() & 0x2); From c755bc1a73bb10ec0357ca3c98b6de2eb3d9ad63 Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Thu, 18 Jul 2024 09:38:17 +0200 Subject: [PATCH 0685/1309] Simplify improving condition if we were in check at our previous move we look back until we weren't in check and take the staticEval of that position as reference. Passed STC: https://tests.stockfishchess.org/tests/view/668ba7b65034141ae5996665 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 74784 W: 19454 L: 19274 D: 36056 Ptnml(0-2): 260, 8874, 18952, 9038, 268 Passted LTC: https://tests.stockfishchess.org/tests/view/668cb2db5034141ae599678b LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 241488 W: 61166 L: 61171 D: 119151 Ptnml(0-2): 190, 27154, 66062, 27147, 191 closes https://github.com/official-stockfish/Stockfish/pull/5492 bench: 1368313 --- src/search.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3c6617ebdf2..09918bfdce4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -713,7 +713,7 @@ Value Search::Worker::search( if (ss->inCheck) { // Skip early pruning when in check - ss->staticEval = eval = VALUE_NONE; + ss->staticEval = eval = (ss - 2)->staticEval; improving = false; goto moves_loop; } @@ -764,12 +764,9 @@ Value Search::Worker::search( // Set up the improving flag, which is true if current static evaluation is // bigger than the previous static evaluation at our turn (if we were in - // check at our previous move we look at static evaluation at move prior to it - // and if we were in check at move prior to it flag is set to true) and is + // check at our previous move we go back until we weren't in check) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = (ss - 2)->staticEval != VALUE_NONE - ? ss->staticEval > (ss - 2)->staticEval - : (ss - 4)->staticEval != VALUE_NONE && ss->staticEval > (ss - 4)->staticEval; + improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; From 7bb45d05faa62463f4c791749907f4c50ceee990 Mon Sep 17 00:00:00 2001 From: yl25946 Date: Mon, 15 Jul 2024 14:55:38 -0500 Subject: [PATCH 0686/1309] Replace ternary with std::min equivalent and more readable. closes https://github.com/official-stockfish/Stockfish/pull/5488 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 09918bfdce4..e34aabba199 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -90,7 +90,7 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { int stat_bonus(Depth d) { return std::min(190 * d - 108, 1596); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 736 * d - 268 : 2044); } +int stat_malus(Depth d) { return std::min(736 * d - 268, 2044); } // 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); } From 27042fe9497f721abbfccab50ebb6a0641e63b21 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Sat, 6 Jul 2024 22:31:45 -0400 Subject: [PATCH 0687/1309] Linearize corrHist Passed STC: https://tests.stockfishchess.org/tests/view/66919cdec6827afcdcee146f LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 130656 W: 33579 L: 33461 D: 63616 Ptnml(0-2): 394, 15548, 33318, 15682, 386 Passed VVLTC: https://tests.stockfishchess.org/tests/view/6691acb2c6827afcdcee1645 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 160314 W: 40925 L: 40854 D: 78535 Ptnml(0-2): 12, 14754, 50551, 14831, 9 closes https://github.com/official-stockfish/Stockfish/pull/5489 bench 1380295 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e34aabba199..218d1ce4bb4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -82,7 +82,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { // does not hit the tablebase range. Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 5073; + v += 66 * cv / 512; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } From c8d8e362fcf58238da07aeb31f6fa029cf9828c6 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" Date: Sun, 14 Jul 2024 21:36:19 +0300 Subject: [PATCH 0688/1309] Try nullmoves only on cutnodes since master only tries nullmoves on cutNodes already with 99.0224% of the cases running bench, We can try null moves at 100% of cutNodes and achieve such simplification, by making passing false already equivalent to passing !cutNode This is a more correct form of PR #5482 Passed non-regression STC: https://tests.stockfishchess.org/tests/view/66941c044ff211be9d4ebf5f LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 153216 W: 39856 L: 39764 D: 73596 Ptnml(0-2): 590, 18174, 38979, 18284, 581 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/6694e5cd4ff211be9d4ebfdf LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 67842 W: 17178 L: 17004 D: 33660 Ptnml(0-2): 52, 7437, 18759, 7631, 42 closes https://github.com/official-stockfish/Stockfish/pull/5490 bench: 1345400 Co-Authored-By: FauziAkram <11150271+fauziakram@users.noreply.github.com> --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 218d1ce4bb4..c03a30f5675 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -791,7 +791,7 @@ Value Search::Worker::search( return beta + (eval - beta) / 3; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14389 + if (cutNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14389 && eval >= beta && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) @@ -806,7 +806,7 @@ Value Search::Worker::search( pos.do_null_move(st, tt); - Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, !cutNode); + Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); pos.undo_null_move(); From c2837769e0d43f1195081c2aa97b7028b27dee73 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 17 Jul 2024 00:15:44 -0400 Subject: [PATCH 0689/1309] Avoid calculating nnue complexity twice Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6697459d4ff211be9d4ec236 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 146848 W: 38289 L: 38189 D: 70370 Ptnml(0-2): 503, 16665, 39046, 16649, 561 closes https://github.com/official-stockfish/Stockfish/pull/5493 No functional change --- src/evaluate.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1cff6478fb9..d0c553ffc8a 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -66,19 +66,18 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) : networks.big.evaluate(pos, &caches.big); - Value nnue = (125 * psqt + 131 * positional) / 128; - int nnueComplexity = std::abs(psqt - positional); + Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 227)) { std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); nnue = (125 * psqt + 131 * positional) / 128; - nnueComplexity = std::abs(psqt - positional); smallNet = false; } // Blend optimism and eval with nnue complexity + int nnueComplexity = std::abs(psqt - positional); optimism += optimism * nnueComplexity / (smallNet ? 433 : 453); nnue -= nnue * nnueComplexity / (smallNet ? 18815 : 17864); From a8401e803d37ec7dbf0650f4d79475214655477e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 18 Jul 2024 16:30:42 +0300 Subject: [PATCH 0690/1309] Adjust bonus to move that caused a fail low This is an elo gainer and simultaneously a minor logical fix to bonuses that caused a fail low. It increases maximum of statscore based subtraction - but disallows negative bonuses. Passed STC: https://tests.stockfishchess.org/tests/view/66955e6f4ff211be9d4ec063 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 44640 W: 11805 L: 11472 D: 21363 Ptnml(0-2): 166, 5178, 11335, 5439, 202 Passed LTC: https://tests.stockfishchess.org/tests/view/66963fde4ff211be9d4ec190 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 72288 W: 18478 L: 18082 D: 35728 Ptnml(0-2): 50, 7919, 19825, 8285, 65 closes https://github.com/official-stockfish/Stockfish/pull/5494 Bench: 1477054 --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c03a30f5675..945f8b408e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1368,7 +1368,9 @@ Value Search::Worker::search( + 32 * (!(ss - 1)->inCheck && bestValue > -(ss - 1)->staticEval + 76)); // Proportional to "how much damage we have to undo" - bonus += std::clamp(-(ss - 1)->statScore / 100, -64, 300); + bonus += std::clamp(-(ss - 1)->statScore / 100, -94, 300); + + bonus = std::max(bonus, 0); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus / 100); From 1fb4dc2e0f0dbeddff889bcd75466e4be4fe1ad6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 19 Jul 2024 21:26:51 +0200 Subject: [PATCH 0691/1309] Enable syzygy in the matetrack action now checks correctness of PV lines with TB score. uses 3-4-5 man table bases, downloaded from lichess, which are cached with the appropriate action. closes https://github.com/official-stockfish/Stockfish/pull/5500 No functional change --- .github/workflows/matetrack.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index de65209fb29..dc8dff8d57f 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -24,15 +24,31 @@ jobs: with: repository: vondele/matetrack path: matetrack - ref: 20287a1a145f30a166b7ef251eddb611e4e44fbf + ref: 814160f82e6428ed2f6522dc06c2a6fa539cd413 persist-credentials: false - name: matetrack install deps working-directory: matetrack run: pip install -r requirements.txt + - name: cache syzygy + id: cache-syzygy + uses: actions/cache@v4 + with: + path: | + matetrack/3-4-5-wdl/ + matetrack/3-4-5-dtz/ + key: key-syzygy + + - name: download syzygy 3-4-5 if needed + working-directory: matetrack + if: steps.cache-syzygy.outputs.cache-hit != 'true' + run: | + wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-wdl/ + wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-dtz/ + - name: Run matetrack working-directory: matetrack run: | - python matecheck.py --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 From e57fba7fc9be461cbb97c063b269a1e231cdd284 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 21 Jul 2024 13:15:12 +0200 Subject: [PATCH 0692/1309] Fix TB PV extension and MultiPV in the case of MultiPV, the first move of the Nth multiPV could actually turn a winning position in a losing one, so don't attempt to correct it. Instead, always perform the first move without correction. Fixes #5505 Closes https://github.com/official-stockfish/Stockfish/pull/5506 No functional change --- src/search.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 945f8b408e3..435af4b244b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1948,8 +1948,12 @@ void syzygy_extend_pv(const OptionsMap& options, std::list sts; + // Step 0, do the rootMove, no correction allowed, as needed for MultiPV in TB. + auto& stRoot = sts.emplace_back(); + pos.do_move(rootMove.pv[0], stRoot); + int ply = 1; + // Step 1, walk the PV to the last position in TB with correct decisive score - int ply = 0; while (size_t(ply) < rootMove.pv.size()) { Move& pvMove = rootMove.pv[ply]; From 703f17975bd9c29172a27f795ca6b5a7d0a32b25 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Thu, 2 May 2024 05:35:15 -0500 Subject: [PATCH 0693/1309] Remove QS_CHECKS movepick stage Passed STC: https://tests.stockfishchess.org/tests/view/669597cf4ff211be9d4ec147 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 199072 W: 52100 L: 52058 D: 94914 Ptnml(0-2): 829, 23679, 50406, 23865, 757 Passed LTC: https://tests.stockfishchess.org/tests/view/66988f5f4ff211be9d4ec33e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 119778 W: 30420 L: 30299 D: 59059 Ptnml(0-2): 106, 13293, 32957, 13440, 93 closes https://github.com/official-stockfish/Stockfish/pull/5498 Bench 1499842 --- src/movegen.cpp | 54 ++++++++++++++---------------------------------- src/movegen.h | 1 - src/movepick.cpp | 22 +------------------- src/search.cpp | 10 ++------- src/types.h | 3 +-- 5 files changed, 19 insertions(+), 71 deletions(-) diff --git a/src/movegen.cpp b/src/movegen.cpp index e6923067f88..69b8fe6ae2b 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -75,17 +75,6 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta b2 &= target; } - if constexpr (Type == QUIET_CHECKS) - { - // To make a quiet check, you either make a direct check by pushing a pawn - // or push a blocker pawn that is not on the same file as the enemy king. - // Discovered check promotion has been already generated amongst the captures. - Square ksq = pos.square(Them); - Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); - b1 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); - b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); - } - while (b1) { Square to = pop_lsb(b1); @@ -158,7 +147,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta } -template +template ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); @@ -170,10 +159,6 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) Square from = pop_lsb(bb); Bitboard b = attacks_bb(from, pos.pieces()) & target; - // To check, you either move freely a blocker or make a direct check. - if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from))) - b &= pos.check_squares(Pt); - while (b) *moveList++ = Move(from, pop_lsb(b)); } @@ -187,9 +172,8 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate_all()"); - constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations - const Square ksq = pos.square(Us); - Bitboard target; + const Square ksq = pos.square(Us); + Bitboard target; // Skip generating non-king moves when in double check if (Type != EVASIONS || !more_than_one(pos.checkers())) @@ -197,29 +181,24 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) : Type == NON_EVASIONS ? ~pos.pieces(Us) : Type == CAPTURES ? pos.pieces(~Us) - : ~pos.pieces(); // QUIETS || QUIET_CHECKS + : ~pos.pieces(); // QUIETS moveList = generate_pawn_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, target); } - if (!Checks || pos.blockers_for_king(~Us) & ksq) - { - Bitboard b = attacks_bb(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target); - if (Checks) - b &= ~attacks_bb(pos.square(~Us)); + Bitboard b = attacks_bb(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target); - while (b) - *moveList++ = Move(ksq, pop_lsb(b)); + while (b) + *moveList++ = Move(ksq, pop_lsb(b)); - if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) - for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) - if (!pos.castling_impeded(cr) && pos.can_castle(cr)) - *moveList++ = Move::make(ksq, pos.castling_rook_square(cr)); - } + if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) + for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) + if (!pos.castling_impeded(cr) && pos.can_castle(cr)) + *moveList++ = Move::make(ksq, pos.castling_rook_square(cr)); return moveList; } @@ -231,8 +210,6 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { // Generates all pseudo-legal non-captures and underpromotions // Generates all pseudo-legal check evasions // Generates all pseudo-legal captures and non-captures -// Generates all pseudo-legal non-captures giving check, -// except castling and promotions // // Returns a pointer to the end of the move list. template @@ -251,7 +228,6 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); -template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); diff --git a/src/movegen.h b/src/movegen.h index 5f650d2e36d..f067f8808b6 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -31,7 +31,6 @@ class Position; enum GenType { CAPTURES, QUIETS, - QUIET_CHECKS, EVASIONS, NON_EVASIONS, LEGAL diff --git a/src/movepick.cpp b/src/movepick.cpp index 55bacf6e747..81384328729 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -52,9 +52,7 @@ enum Stages { // generate qsearch moves QSEARCH_TT, QCAPTURE_INIT, - QCAPTURE, - QCHECK_INIT, - QCHECK + QCAPTURE }; // Sort moves in descending order up to and including a given limit. @@ -316,24 +314,6 @@ Move MovePicker::next_move(bool skipQuiets) { return select([&]() { return pos.see_ge(*cur, threshold); }); case QCAPTURE : - if (select([]() { return true; })) - return *(cur - 1); - - // If we found no move and the depth is too low to try checks, then we have finished - if (depth <= DEPTH_QS_NORMAL) - return Move::none(); - - ++stage; - [[fallthrough]]; - - case QCHECK_INIT : - cur = moves; - endMoves = generate(pos, cur); - - ++stage; - [[fallthrough]]; - - case QCHECK : return select([]() { return true; }); } diff --git a/src/search.cpp b/src/search.cpp index 435af4b244b..fd9fa6da094 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1475,12 +1475,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, assert(0 <= ss->ply && ss->ply < MAX_PLY); - // Note that unlike regular search, which stores the literal depth into the - // transposition table, from qsearch we only store the current movegen stage - // as "depth". If in check, we search all evasions and thus store DEPTH_QS_CHECKS. - // Evasions may be quiet, and _CHECKS includes quiets. - Depth qsTtDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NORMAL; - // Step 3. Transposition table lookup posKey = pos.key(); auto [ttHit, ttData, ttWriter] = tt.probe(posKey); @@ -1491,7 +1485,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, pvHit = ttHit && ttData.is_pv; // At non-PV nodes we check for an early TT cutoff - if (!PvNode && ttData.depth >= qsTtDepth + if (!PvNode && ttData.depth >= DEPTH_QS && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttData.value; @@ -1674,7 +1668,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Save gathered info in transposition table. The static evaluation // is saved as it was before adjustment by correction history. ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit, - bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, qsTtDepth, bestMove, + bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, DEPTH_QS, bestMove, unadjustedStaticEval, tt.generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); diff --git a/src/types.h b/src/types.h index 8a9400bb8f8..b12491d6cdd 100644 --- a/src/types.h +++ b/src/types.h @@ -194,8 +194,7 @@ enum : int { // quiescence search, however, the transposition table entries only store // the current quiescence move generation stage (which should thus compare // lower than any regular search depth). - DEPTH_QS_CHECKS = 0, - DEPTH_QS_NORMAL = -1, + DEPTH_QS = 0, // For transposition table entries where no searching at all was done // (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus // compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET From a2ba3e33628bed0930f50c54a5ae4f30b853b3b8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 20 Jul 2024 13:34:27 +0300 Subject: [PATCH 0694/1309] Bonus Simplification This tune removes completely a recently added term. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 61376 W: 16046 L: 15693 D: 29637 Ptnml(0-2): 207, 7132, 15665, 7469, 215 https://tests.stockfishchess.org/tests/view/669512b94ff211be9d4ebffb Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 100662 W: 25474 L: 25020 D: 50168 Ptnml(0-2): 64, 11092, 27581, 11514, 80 https://tests.stockfishchess.org/tests/view/66955f194ff211be9d4ec06a Passed LTC#2: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 28056 W: 7128 L: 6909 D: 14019 Ptnml(0-2): 18, 3084, 7620, 3273, 33 https://tests.stockfishchess.org/tests/view/669a541a4ff211be9d4ec52b closes https://github.com/official-stockfish/Stockfish/pull/5502 bench: 1619438 --- src/search.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fd9fa6da094..f51a749995b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -189,7 +189,7 @@ void Search::Worker::start_searching() { {} // Busy wait for a stop or a ponder reset // Stop the threads if not already stopped (also raise the stop if - // "ponderhit" just reset threads.ponder). + // "ponderhit" just reset threads.ponder) threads.stop = true; // Wait until all threads have finished @@ -1362,20 +1362,19 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (138 * (depth > 5) + 58 * (PvNode || cutNode) + 160 * ((ss - 1)->moveCount > 8) - + 84 * (!ss->inCheck && bestValue <= ss->staticEval - 108) - + 153 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76) - + 32 * (!(ss - 1)->inCheck && bestValue > -(ss - 1)->staticEval + 76)); + int bonus = (122 * (depth > 5) + 39 * (PvNode || cutNode) + 165 * ((ss - 1)->moveCount > 8) + + 107 * (!ss->inCheck && bestValue <= ss->staticEval - 98) + + 134 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 91)); // Proportional to "how much damage we have to undo" - bonus += std::clamp(-(ss - 1)->statScore / 100, -94, 300); + bonus += std::clamp(-(ss - 1)->statScore / 100, -94, 304); bonus = std::max(bonus, 0); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - stat_bonus(depth) * bonus / 100); + stat_bonus(depth) * bonus / 116); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << stat_bonus(depth) * bonus / 200; + << stat_bonus(depth) * bonus / 180; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) From 986173264f4c03e3750bd68f904bfdf1152437d4 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 21 Jul 2024 18:52:26 +0300 Subject: [PATCH 0695/1309] Adding LowestElo and HighestElo constants These values represent the lowest Elo rating in the skill level calculation, and the highest one, but it's not clear from the code where these values come from other than the comment. This should improve code readability and maintainability. It makes the purpose of the values clear and allows for easy modification if the Elo range for skill level calculation changes in the future. Moved the Skill struct definition from search.cpp to search.h header file to define the Search::Skill struct, making it accessible from other files. closes https://github.com/official-stockfish/Stockfish/pull/5508 No functional change --- src/engine.cpp | 4 +++- src/search.cpp | 25 ------------------------- src/search.h | 29 +++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 41b19ac6859..498b7c3e741 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -91,7 +91,9 @@ Engine::Engine(std::string path) : options["nodestime"] << Option(0, 0, 10000); options["UCI_Chess960"] << Option(false); options["UCI_LimitStrength"] << Option(false); - options["UCI_Elo"] << Option(1320, 1320, 3190); + 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); diff --git a/src/search.cpp b/src/search.cpp index f51a749995b..0d9824b7701 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,31 +94,6 @@ int stat_malus(Depth d) { return std::min(736 * d - 268, 2044); } // 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); } - -// Skill structure is used to implement strength limit. If we have a UCI_Elo, -// we convert it to an appropriate skill level, anchored to the Stash engine. -// This method is based on a fit of the Elo results for games played between -// Stockfish at various skill levels and various versions of the Stash engine. -// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately -// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 -struct Skill { - Skill(int skill_level, int uci_elo) { - if (uci_elo) - { - double e = double(uci_elo - 1320) / (3190 - 1320); - level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0); - } - else - level = double(skill_level); - } - bool enabled() const { return level < 20.0; } - bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } - Move pick_best(const RootMoves&, size_t multiPV); - - double level; - Move best = Move::none(); -}; - Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); diff --git a/src/search.h b/src/search.h index 65394bc0763..d42b5fba9b7 100644 --- a/src/search.h +++ b/src/search.h @@ -19,6 +19,7 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED +#include #include #include #include @@ -180,6 +181,34 @@ struct InfoIteration { size_t currmovenumber; }; +// Skill structure is used to implement strength limit. If we have a UCI_Elo, +// we convert it to an appropriate skill level, anchored to the Stash engine. +// This method is based on a fit of the Elo results for games played between +// Stockfish at various skill levels and various versions of the Stash engine. +// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately +// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2 +struct Skill { + // Lowest and highest Elo ratings used in the skill level calculation + constexpr static int LowestElo = 1320; + constexpr static int HighestElo = 3190; + + Skill(int skill_level, int uci_elo) { + if (uci_elo) + { + double e = double(uci_elo - LowestElo) / (HighestElo - LowestElo); + level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0); + } + else + level = double(skill_level); + } + bool enabled() const { return level < 20.0; } + bool time_to_pick(Depth depth) const { return depth == 1 + int(level); } + Move pick_best(const RootMoves&, size_t multiPV); + + double level; + Move best = Move::none(); +}; + // SearchManager manages the search from the main thread. It is responsible for // keeping track of the time, and storing data strictly related to the main thread. class SearchManager: public ISearchManager { From bb4b01e3063d5ad19679d51140d8e9f0599ac538 Mon Sep 17 00:00:00 2001 From: "Shahin M. Shahin" Date: Fri, 19 Jul 2024 13:27:30 +0300 Subject: [PATCH 0696/1309] Fix TB guard even if beta is below TB range, once we return probcutBeta with beta + 390 we can return wrong TB value, and guard against ttData.value being `VALUE_NONE` closes https://github.com/official-stockfish/Stockfish/pull/5499 bench: 1440277 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0d9824b7701..ca3465029b1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -894,7 +894,8 @@ Value Search::Worker::search( // Step 12. A small Probcut idea (~4 Elo) probCutBeta = beta + 390; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) + && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY) return probCutBeta; const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, From 1e2f0511033945d07e1c8856980ed72cdbe42822 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Wed, 17 Jul 2024 00:03:10 -0400 Subject: [PATCH 0697/1309] Replace simple eval with psqt in re-eval condition As a result, re-eval depends only on smallnet outputs so an extra call to simple eval can be removed. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/669743054ff211be9d4ec232 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 214912 W: 55801 L: 55777 D: 103334 Ptnml(0-2): 746, 24597, 56760, 24593, 760 https://github.com/official-stockfish/Stockfish/pull/5501 Bench: 1440277 --- src/evaluate.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d0c553ffc8a..221ccde8b8b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,8 +59,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); - int simpleEval = simple_eval(pos, pos.side_to_move()); - bool smallNet = use_smallnet(pos); + bool smallNet = use_smallnet(pos); int v; auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) @@ -69,7 +68,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (nnue * simpleEval < 0 || std::abs(nnue) < 227)) + if (smallNet && (nnue * psqt < 0 || std::abs(nnue) < 227)) { std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); nnue = (125 * psqt + 131 * positional) / 128; From 985b9fd7b05d1d81be7a1ac90862a5790ee56176 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 20 Jul 2024 12:40:16 -0700 Subject: [PATCH 0698/1309] Remove Killer Heuristic In Move Ordering Passed Non-regression STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 80480 W: 20979 L: 20802 D: 38699 Ptnml(0-2): 279, 9610, 20337, 9683, 331 https://tests.stockfishchess.org/tests/view/669c12c14ff211be9d4ec69b Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 77988 W: 19788 L: 19624 D: 38576 Ptnml(0-2): 66, 8605, 21481, 8783, 59 https://tests.stockfishchess.org/tests/view/669d628a4ff211be9d4ec7a8 closes https://github.com/official-stockfish/Stockfish/pull/5511 bench 1367740 --- src/movepick.cpp | 6 +----- src/movepick.h | 5 ++--- src/search.cpp | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 81384328729..7bd0252c819 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -86,15 +86,13 @@ MovePicker::MovePicker(const Position& p, const ButterflyHistory* mh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory* ph, - Move km) : + const PawnHistory* ph) : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), pawnHistory(ph), ttMove(ttm), - killer(km), depth(d) { if (pos.checkers()) @@ -164,8 +162,6 @@ void MovePicker::score() { m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; - m.value += (m == killer) * 65536; - // bonus for checks m.value += bool(pos.check_squares(pt) & to) * 16384; diff --git a/src/movepick.h b/src/movepick.h index 92e11de2bf3..671cbb9cead 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -159,8 +159,7 @@ class MovePicker { const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory*, - Move killer = Move::none()); + const PawnHistory*); MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); @@ -177,7 +176,7 @@ class MovePicker { const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; - Move ttMove, killer; + Move ttMove; ExtMove * cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; int stage; int threshold; diff --git a/src/search.cpp b/src/search.cpp index ca3465029b1..233dc4f71ca 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -907,7 +907,7 @@ Value Search::Worker::search( MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, &thisThread->pawnHistory, ss->killer); + contHist, &thisThread->pawnHistory); value = bestValue; From 836154acb5ba447a46196a64d6bbab5a5b31ea1b Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 23 Jul 2024 16:36:49 +0300 Subject: [PATCH 0699/1309] Introduce pre-qsearch ttmove extensions at pv nodes The idea is that we are about to dive into qsearch (next search depth is <= 0) but since we have the move in transposition table we should extend that move and evaluate it with more precise search - because branch seems important. Passed STC: https://tests.stockfishchess.org/tests/view/6699d2564ff211be9d4ec488 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 83104 W: 21789 L: 21401 D: 39914 Ptnml(0-2): 293, 9748, 21128, 10044, 339 Passed LTC: https://tests.stockfishchess.org/tests/view/669b3f1a4ff211be9d4ec602 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 136098 W: 34636 L: 34111 D: 67351 Ptnml(0-2): 105, 14882, 37550, 15407, 105 closes https://github.com/official-stockfish/Stockfish/pull/5512 bench 1526129 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 233dc4f71ca..5e260247ca9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1206,6 +1206,10 @@ Value Search::Worker::search( (ss + 1)->pv = pv; (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) + newDepth = std::max(newDepth, 1); + value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); } From b55217fd02d8e5bc0754e5f27bc84df7b01479a6 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 15 Jul 2024 11:39:02 -0400 Subject: [PATCH 0700/1309] Update default main net to nn-31337bea577c.nnue Created by updating output weights (256) and biases (8) of the previous main net with values found with spsa around 101k / 120k games at 140+1.4. 264 spsa params: output weights and biases in nn-e8bac1c07a5a.nnue A: 6000, alpha: 0.602, gamma: 0.101 weights: [-127, 127], c_end = 6 biases: [-8192, 8192], c_end = 64 Among the 264 params, 189 weights and all 8 biases were changed. Changes in the weights: - mean: -0.111 +/- 3.57 - range: [-8, 8] Found with the same method as: https://github.com/official-stockfish/Stockfish/pull/5459 Due to the original name (nn-ea8c9128c325.nnue) being too similar to the previous main net (nn-e8bac1c07a5a.nnue) and creating confusion, it was renamed by making non-functional changes to the .nnue file the same way as past nets with: https://github.com/linrock/nnue-namer To verify that bench is the same and view the modified non-functional bytes: ``` echo -e "setoption name EvalFile value nn-ea8c9128c325.nnue\nbench" | ./stockfish echo -e "setoption name EvalFile value nn-31337bea577c.nnue\nbench" | ./stockfish cmp -l nn-ea8c9128c325.nnue nn-31337bea577c.nnue diff <(xxd nn-ea8c9128c325.nnue) <(xxd nn-31337bea577c.nnue) ``` Passed STC: https://tests.stockfishchess.org/tests/view/669564154ff211be9d4ec080 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 57280 W: 15139 L: 14789 D: 27352 Ptnml(0-2): 209, 6685, 14522, 6995, 229 Passed LTC: https://tests.stockfishchess.org/tests/view/669694204ff211be9d4ec1b4 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 63030 W: 16093 L: 15720 D: 31217 Ptnml(0-2): 47, 6766, 17516, 7139, 47 closes https://github.com/official-stockfish/Stockfish/pull/5509 bench 1371485 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 4b5f447ee32..55838243342 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-e8bac1c07a5a.nnue" +#define EvalFileDefaultNameBig "nn-31337bea577c.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 85893ac1cd1933f9d24700026972b278f5a37b9c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 20 Jul 2024 12:41:56 -0700 Subject: [PATCH 0701/1309] Simplify Away Killer Condition in Cutnode LMR Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 42944 W: 11240 L: 11024 D: 20680 Ptnml(0-2): 159, 5056, 10825, 5274, 158 https://tests.stockfishchess.org/tests/view/669c13384ff211be9d4ec69f Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 163548 W: 41366 L: 41289 D: 80893 Ptnml(0-2): 109, 18246, 45007, 18283, 129 https://tests.stockfishchess.org/tests/view/669cb1254ff211be9d4ec73a closes https://github.com/official-stockfish/Stockfish/pull/5513 Bench: 1178570 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5e260247ca9..e2df475e9e1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1131,8 +1131,7 @@ Value Search::Worker::search( // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2 - (ttData.depth >= depth && ss->ttPv) - + (!ss->ttPv && move != ttData.move && move != ss->killer); + r += 2 - (ttData.depth >= depth && ss->ttPv) + (!ss->ttPv && move != ttData.move); // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) From 607c3e404fc706d09bd3b276ddd563d636823533 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 24 Jul 2024 18:25:08 +0300 Subject: [PATCH 0702/1309] Remove unneeded depth tracking in qsearch Since simplification of quiet checks in qsearch this depth isn't used by any function at all apart movepicker, which also doesn't use passed qsearch depth in any way, so can be removed. No functional change. closes https://github.com/official-stockfish/Stockfish/pull/5514 No functional change --- src/search.cpp | 7 +++---- src/search.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e2df475e9e1..09004ba6e7b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1401,14 +1401,13 @@ Value Search::Worker::search( // See https://www.chessprogramming.org/Horizon_Effect // and https://www.chessprogramming.org/Quiescence_Search template -Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { +Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { static_assert(nodeType != Root); constexpr bool PvNode = nodeType == PV; assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); - assert(depth <= 0); // Check if we have an upcoming move that draws by repetition (~1 Elo) if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) @@ -1526,7 +1525,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // first captures+checks, then captures only (but when in check, we simply search // all evasions). Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; - MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, + MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta @@ -1606,7 +1605,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 7. Make and search the move thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st, givesCheck); - value = -qsearch(pos, ss + 1, -beta, -alpha, depth - 1); + value = -qsearch(pos, ss + 1, -beta, -alpha); pos.undo_move(move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); diff --git a/src/search.h b/src/search.h index d42b5fba9b7..4872a58af14 100644 --- a/src/search.h +++ b/src/search.h @@ -291,7 +291,7 @@ class Worker { // Quiescence search function, which is called by the main search template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta); Depth reduction(bool i, Depth d, int mn, int delta) const; From af802da65b595f67046e97d580479ef1f7b18cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Nicolet?= Date: Fri, 26 Jul 2024 11:13:37 +0200 Subject: [PATCH 0703/1309] Clean up comments for movepicker Remove references to checks in MovePicker comments. Follow-up for https://github.com/official-stockfish/Stockfish/pull/5498 closes https://github.com/official-stockfish/Stockfish/pull/5516 No functional change --- src/movepick.cpp | 26 ++++++++++++-------------- src/movepick.h | 12 ++++++------ src/search.cpp | 14 +++++++------- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 7bd0252c819..bdc0e4affdb 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -74,12 +74,10 @@ void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { // Constructors of the MovePicker class. As arguments, we pass information -// to help it return the (presumably) good moves first, to decide which -// moves to return (in the quiescence search, for instance, we only want to -// search captures, promotions, and some checks) and how important a good -// move ordering is at the current node. +// to decide which class of moves to emit, to help sorting the (presumably) +// good moves first, and how important move ordering is at the current node. -// MovePicker constructor for the main search +// MovePicker constructor for the main search and for the quiescence search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, @@ -102,8 +100,8 @@ MovePicker::MovePicker(const Position& p, stage = (depth > 0 ? MAIN_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm)); } -// Constructor for ProbCut: we generate captures with SEE greater than or equal -// to the given threshold. +// MovePicker constructor for ProbCut: we generate captures with Static Exchange +// Evaluation (SEE) greater than or equal to the given threshold. MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) : pos(p), captureHistory(cph), @@ -115,9 +113,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } -// Assigns a numerical value to each move in a list, used -// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring -// captures with a good history. Quiets moves are ordered using the history tables. +// Assigns a numerical value to each move in a list, used for sorting. +// Captures are ordered by Most Valuable Victim (MVV), preferring captures +// with a good history. Quiets moves are ordered using the history tables. template void MovePicker::score() { @@ -191,7 +189,7 @@ void MovePicker::score() { } // Returns the next move satisfying a predicate function. -// It never returns the TT move. +// This never returns the TT move, as it was emitted before. template Move MovePicker::select(Pred filter) { @@ -208,9 +206,9 @@ Move MovePicker::select(Pred filter) { return Move::none(); } -// Most important method of the MovePicker class. It -// returns a new pseudo-legal move every time it is called until there are no more -// moves left, picking the move with the highest score from a list of generated moves. +// This is the most important method of the MovePicker class. We emit one +// new pseudo-legal move on every call until there are no more moves left, +// picking the move with the highest score from a list of generated moves. Move MovePicker::next_move(bool skipQuiets) { auto quiet_threshold = [](Depth d) { return -3560 * d; }; diff --git a/src/movepick.h b/src/movepick.h index 671cbb9cead..61f6368e6d9 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -137,12 +137,12 @@ using PawnHistory = Stats using CorrectionHistory = Stats; -// MovePicker class is used to pick one pseudo-legal move at a time from the -// current position. The most important method is next_move(), which returns a -// new pseudo-legal move each time it is called, until there are no moves left, -// when Move::none() is returned. In order to improve the efficiency of the -// alpha-beta algorithm, MovePicker attempts to return the moves which are most -// likely to get a cut-off first. +// The MovePicker class is used to pick one pseudo-legal move at a time from the +// current position. The most important method is next_move(), which emits one +// new pseudo-legal move on every call, until there are no moves left, when +// Move::none() is returned. In order to improve the efficiency of the alpha-beta +// algorithm, MovePicker attempts to return the moves which are most likely to get +// a cut-off first. class MovePicker { enum PickType { diff --git a/src/search.cpp b/src/search.cpp index 09004ba6e7b..e83034561c0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -20,18 +20,18 @@ #include #include -#include #include #include +#include #include #include #include #include -#include -#include -#include #include +#include #include +#include +#include #include "evaluate.h" #include "misc.h" @@ -1520,11 +1520,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, (ss - 2)->continuationHistory}; + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; + // Initialize a MovePicker object for the current position, and prepare to search // the moves. We presently use two stages of move generator in quiescence search: - // first captures+checks, then captures only (but when in check, we simply search - // all evasions). - Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; + // captures, or evasions only when in check. MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->captureHistory, contHist, &thisThread->pawnHistory); From 2343f71f3ff524e937f81b2922705081f8907980 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 20 Jul 2024 12:41:56 -0700 Subject: [PATCH 0704/1309] Remove Killers The removal of killers on line 1774 resulted in a substantial decrease in pre-LMR history average, so a negative history fill is applied to counter it. Passed Non-regression STC (vs #5513): LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 21984 W: 5886 L: 5645 D: 10453 Ptnml(0-2): 80, 2492, 5628, 2691, 101 https://tests.stockfishchess.org/tests/view/66a095894ff211be9d4ecb9d Passed Non-regression LTC (vs #5513): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 95430 W: 24141 L: 23995 D: 47294 Ptnml(0-2): 97, 10537, 26298, 10689, 94 https://tests.stockfishchess.org/tests/view/66a11c8d4ff211be9d4ecbf8 closes https://github.com/official-stockfish/Stockfish/pull/5517 Bench: 1660869 --- src/search.cpp | 34 +++++++--------------------------- src/search.h | 1 - 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e83034561c0..f20bd4c9379 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -98,11 +98,8 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_killer(Stack* ss, Move move); void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); -void update_quiet_stats( - const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); void update_all_stats(const Position& pos, Stack* ss, Search::Worker& workerThread, @@ -222,7 +219,7 @@ void Search::Worker::iterative_deepening() { // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2): // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6), - // (ss + 2) is needed for initialization of cutOffCnt and killers. + // (ss + 2) is needed for initialization of cutOffCnt. Stack stack[MAX_PLY + 10] = {}; Stack* ss = stack + 7; @@ -490,7 +487,7 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-58); + h->fill(-658); for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((18.62 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); @@ -584,7 +581,6 @@ Value Search::Worker::search( assert(0 <= ss->ply && ss->ply < MAX_PLY); bestMove = Move::none(); - (ss + 1)->killer = Move::none(); (ss + 2)->cutoffCnt = 0; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; @@ -615,7 +611,7 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_stats(pos, ss, *this, ttData.move, stat_bonus(depth)); + update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth)); // Extra penalty for early quiet moves of // the previous ply (~1 Elo on STC, ~2 Elo on LTC) @@ -1754,7 +1750,7 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - update_quiet_stats(pos, ss, workerThread, bestMove, quietMoveBonus); + update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) @@ -1767,12 +1763,9 @@ void update_all_stats(const Position& pos, captureHistory[moved_piece][bestMove.to_sq()][captured] << quietMoveBonus; } - // Extra penalty for a quiet early move that was not a TT move or - // main killer move in previous ply when it gets refuted. - if (prevSq != SQ_NONE - && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit - || ((ss - 1)->currentMove == (ss - 1)->killer)) - && !pos.captured_piece()) + // 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, -quietMoveMalus); // Decrease stats for all non-best capture moves @@ -1802,11 +1795,6 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } // Updates move sorting heuristics -void update_killer(Stack* ss, Move move) { - - // Update killers - ss->killer = move; -} void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { @@ -1820,14 +1808,6 @@ void update_quiet_histories( workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus / 2; } -// Updates move sorting heuristics -void update_quiet_stats( - const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { - - update_killer(ss, move); - update_quiet_histories(pos, ss, workerThread, move, bonus); -} - } // When playing with strength handicap, choose the best move among a set of diff --git a/src/search.h b/src/search.h index 4872a58af14..bdb63ffd0d8 100644 --- a/src/search.h +++ b/src/search.h @@ -66,7 +66,6 @@ struct Stack { int ply; Move currentMove; Move excludedMove; - Move killer; Value staticEval; int statScore; int moveCount; From 8e560c4fd347514a699bde1931912834047cc835 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Thu, 25 Jul 2024 14:37:08 +0200 Subject: [PATCH 0705/1309] Replicate network weights only to used NUMA nodes On a system with multiple NUMA nodes, this patch avoids unneeded replicated (e.g. 8x for a single threaded run), reducting memory use in that case. Lazy initialization forced before search. Passed STC: https://tests.stockfishchess.org/tests/view/66a28c524ff211be9d4ecdd4 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 691776 W: 179429 L: 179927 D: 332420 Ptnml(0-2): 2573, 79370, 182547, 78778, 2620 closes https://github.com/official-stockfish/Stockfish/pull/5515 No functional change --- src/engine.cpp | 5 +++ src/engine.h | 8 ++-- src/numa.h | 112 +++++++++++++++++++++++++++++++++++++++++++++++++ src/search.cpp | 6 +++ src/search.h | 26 ++++++------ src/thread.cpp | 7 ++++ src/thread.h | 4 ++ 7 files changed, 152 insertions(+), 16 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 498b7c3e741..81bb260bd88 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -204,6 +204,7 @@ void Engine::set_numa_config_from_option(const std::string& o) { // Force reallocation of threads in case affinities need to change. resize_threads(); + threads.ensure_network_replicated(); } void Engine::resize_threads() { @@ -212,6 +213,7 @@ void Engine::resize_threads() { // Reallocate the hash with the new threadpool size set_tt_size(options["Hash"]); + threads.ensure_network_replicated(); } void Engine::set_tt_size(size_t mb) { @@ -234,18 +236,21 @@ void Engine::load_networks() { networks_.small.load(binaryDirectory, options["EvalFileSmall"]); }); threads.clear(); + threads.ensure_network_replicated(); } void Engine::load_big_network(const std::string& file) { networks.modify_and_replicate( [this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); }); threads.clear(); + threads.ensure_network_replicated(); } void Engine::load_small_network(const std::string& file) { networks.modify_and_replicate( [this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); }); threads.clear(); + threads.ensure_network_replicated(); } void Engine::save_network(const std::pair, std::string> files[2]) { diff --git a/src/engine.h b/src/engine.h index 127f7d7c84d..f3c78398669 100644 --- a/src/engine.h +++ b/src/engine.h @@ -114,10 +114,10 @@ class Engine { StateListPtr states; Square capSq; - OptionsMap options; - ThreadPool threads; - TranspositionTable tt; - NumaReplicated networks; + OptionsMap options; + ThreadPool threads; + TranspositionTable tt; + LazyNumaReplicated networks; Search::SearchManager::UpdateContext updateContext; }; diff --git a/src/numa.h b/src/numa.h index 3de8281d152..20d352c91c2 100644 --- a/src/numa.h +++ b/src/numa.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -1136,6 +1137,117 @@ class NumaReplicated: public NumaReplicatedBase { } }; +// We force boxing with a unique_ptr. If this becomes an issue due to added +// indirection we may need to add an option for a custom boxing type. +template +class LazyNumaReplicated: public NumaReplicatedBase { + public: + using ReplicatorFuncType = std::function; + + LazyNumaReplicated(NumaReplicationContext& ctx) : + NumaReplicatedBase(ctx) { + prepare_replicate_from(T{}); + } + + LazyNumaReplicated(NumaReplicationContext& ctx, T&& source) : + NumaReplicatedBase(ctx) { + prepare_replicate_from(std::move(source)); + } + + LazyNumaReplicated(const LazyNumaReplicated&) = delete; + LazyNumaReplicated(LazyNumaReplicated&& other) noexcept : + NumaReplicatedBase(std::move(other)), + instances(std::exchange(other.instances, {})) {} + + LazyNumaReplicated& operator=(const LazyNumaReplicated&) = delete; + LazyNumaReplicated& operator=(LazyNumaReplicated&& other) noexcept { + NumaReplicatedBase::operator=(*this, std::move(other)); + instances = std::exchange(other.instances, {}); + + return *this; + } + + LazyNumaReplicated& operator=(T&& source) { + prepare_replicate_from(std::move(source)); + + return *this; + } + + ~LazyNumaReplicated() override = default; + + const T& operator[](NumaReplicatedAccessToken token) const { + assert(token.get_numa_index() < instances.size()); + ensure_present(token.get_numa_index()); + return *(instances[token.get_numa_index()]); + } + + const T& operator*() const { return *(instances[0]); } + + const T* operator->() const { return instances[0].get(); } + + template + void modify_and_replicate(FuncT&& f) { + auto source = std::move(instances[0]); + std::forward(f)(*source); + prepare_replicate_from(std::move(*source)); + } + + void on_numa_config_changed() override { + // Use the first one as the source. It doesn't matter which one we use, + // because they all must be identical, but the first one is guaranteed to exist. + auto source = std::move(instances[0]); + prepare_replicate_from(std::move(*source)); + } + + private: + mutable std::vector> instances; + mutable std::mutex mutex; + + void ensure_present(NumaIndex idx) const { + assert(idx < instances.size()); + + if (instances[idx] != nullptr) + return; + + assert(idx != 0); + + std::unique_lock lock(mutex); + // Check again for races. + if (instances[idx] != nullptr) + return; + + const NumaConfig& cfg = get_numa_config(); + cfg.execute_on_numa_node( + idx, [this, idx]() { instances[idx] = std::make_unique(*instances[0]); }); + } + + void prepare_replicate_from(T&& source) { + instances.clear(); + + const NumaConfig& cfg = get_numa_config(); + if (cfg.requires_memory_replication()) + { + assert(cfg.num_numa_nodes() > 0); + + // We just need to make sure the first instance is there. + // Note that we cannot move here as we need to reallocate the data + // on the correct NUMA node. + cfg.execute_on_numa_node( + 0, [this, &source]() { instances.emplace_back(std::make_unique(source)); }); + + // Prepare others for lazy init. + instances.resize(cfg.num_numa_nodes()); + } + else + { + assert(cfg.num_numa_nodes() == 1); + // We take advantage of the fact that replication is not required + // and reuse the source value, avoiding one copy operation. + instances.emplace_back(std::make_unique(std::move(source))); + } + } +}; + class NumaReplicationContext { public: NumaReplicationContext(NumaConfig&& cfg) : diff --git a/src/search.cpp b/src/search.cpp index f20bd4c9379..beafd87dd5c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -127,6 +127,12 @@ Search::Worker::Worker(SharedState& sharedState, clear(); } +void Search::Worker::ensure_network_replicated() { + // Access once to force lazy initialization. + // We do this because we want to avoid initialization during search. + (void) (networks[numaAccessToken]); +} + void Search::Worker::start_searching() { // Non-main threads go directly to iterative_deepening() diff --git a/src/search.h b/src/search.h index bdb63ffd0d8..0f635186a3b 100644 --- a/src/search.h +++ b/src/search.h @@ -131,19 +131,19 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& optionsMap, - ThreadPool& threadPool, - TranspositionTable& transpositionTable, - const NumaReplicated& nets) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const LazyNumaReplicated& nets) : options(optionsMap), threads(threadPool), tt(transpositionTable), networks(nets) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - const NumaReplicated& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const LazyNumaReplicated& networks; }; class Worker; @@ -274,6 +274,8 @@ class Worker { bool is_mainthread() const { return threadIdx == 0; } + void ensure_network_replicated(); + // Public because they need to be updatable by the stats ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; @@ -328,10 +330,10 @@ class Worker { Tablebases::Config tbConfig; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - const NumaReplicated& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const LazyNumaReplicated& networks; // Used by NNUE Eval::NNUE::AccumulatorCaches refreshTable; diff --git a/src/thread.cpp b/src/thread.cpp index f17fc4a5366..b5d51594c54 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -102,6 +102,8 @@ void Thread::run_custom_job(std::function f) { cv.notify_one(); } +void Thread::ensure_network_replicated() { worker->ensure_network_replicated(); } + // Thread gets parked here, blocked on the condition variable // when the thread has no work to do. @@ -400,4 +402,9 @@ std::vector ThreadPool::get_bound_thread_count_by_numa_node() const { return counts; } +void ThreadPool::ensure_network_replicated() { + for (auto&& th : threads) + th->ensure_network_replicated(); +} + } // namespace Stockfish diff --git a/src/thread.h b/src/thread.h index 81ca39bbcb6..43e2e1423ce 100644 --- a/src/thread.h +++ b/src/thread.h @@ -83,6 +83,8 @@ class Thread { void clear_worker(); void run_custom_job(std::function f); + void ensure_network_replicated(); + // Thread has been slightly altered to allow running custom jobs, so // this name is no longer correct. However, this class (and ThreadPool) // require further work to make them properly generic while maintaining @@ -146,6 +148,8 @@ class ThreadPool { std::vector get_bound_thread_count_by_numa_node() const; + void ensure_network_replicated(); + std::atomic_bool stop, abortedSearch, increaseDepth; auto cbegin() const noexcept { return threads.cbegin(); } From b976f0a101f80d8b80aa212e92d1cc04b12c6136 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Mon, 29 Jul 2024 12:10:34 +0900 Subject: [PATCH 0706/1309] Move DotProd code into optimized affine layer This patch moves the DotProd code into the propagation function which has sequential access optimization. To prove the speedup, the comparison is done without the sparse layer. With the sparse layer the effect is marginal (GCC 0.3%, LLVM/Clang 0.1%). For both tests, binary is compiled with GCC 14.1. Each test had 50 runs. Sparse layer included: ``` speedup = +0.0030 P(speedup > 0) = 1.0000 ``` Sparse layer excluded: ``` speedup = +0.0561 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/5520 No functional change --- src/nnue/layers/affine_transform.h | 58 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index ad9167c05c4..59a6149f0c4 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -39,25 +39,26 @@ namespace Stockfish::Eval::NNUE::Layers { +#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD) + #define ENABLE_SEQ_OPT +#endif + // Fallback implementation for older/other architectures. // Requires the input to be padded to at least 16 values. -#if !defined(USE_SSSE3) +#ifndef ENABLE_SEQ_OPT + template static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input) { - #if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON) + #if defined(USE_SSE2) || defined(USE_NEON) #if defined(USE_SSE2) // At least a multiple of 16, with SSE2. constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const __m128i Zeros = _mm_setzero_si128(); const auto inputVector = reinterpret_cast(input); - #elif defined(USE_NEON_DOTPROD) - constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; - const auto inputVector = reinterpret_cast(input); - #elif defined(USE_NEON) constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; const auto inputVector = reinterpret_cast(input); @@ -91,16 +92,8 @@ static void affine_transform_non_ssse3(std::int32_t* output, sum = _mm_add_epi32(sum, sum_second_32); output[i] = _mm_cvtsi128_si32(sum); - #elif defined(USE_NEON_DOTPROD) - int32x4_t sum = {biases[i]}; - const auto row = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumChunks; ++j) - { - sum = vdotq_s32(sum, inputVector[j], row[j]); - } - output[i] = vaddvq_s32(sum); - #elif defined(USE_NEON) + int32x4_t sum = {biases[i]}; const auto row = reinterpret_cast(&weights[offset]); for (IndexType j = 0; j < NumChunks; ++j) @@ -127,7 +120,8 @@ static void affine_transform_non_ssse3(std::int32_t* output, } #endif } -#endif + +#endif // !ENABLE_SEQ_OPT template class AffineTransform { @@ -162,7 +156,7 @@ class AffineTransform { } static constexpr IndexType get_weight_index(IndexType i) { -#if defined(USE_SSSE3) +#ifdef ENABLE_SEQ_OPT return get_weight_index_scrambled(i); #else return i; @@ -190,29 +184,28 @@ class AffineTransform { // Forward propagation void propagate(const InputType* input, OutputType* output) const { -#if defined(USE_SSSE3) +#ifdef ENABLE_SEQ_OPT if constexpr (OutputDimensions > 1) { - #if defined(USE_AVX512) using vec_t = __m512i; - #define vec_setzero _mm512_setzero_si512 #define vec_set_32 _mm512_set1_epi32 #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 - #define vec_hadd Simd::m512_hadd #elif defined(USE_AVX2) using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_hadd Simd::m128_hadd + #elif defined(USE_NEON_DOTPROD) + using vec_t = int32x4_t; + #define vec_set_32 vdupq_n_s32 + #define vec_add_dpbusd_32(acc, a, b) \ + Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + vreinterpretq_s8_s32(b)) #endif static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); @@ -242,28 +235,33 @@ class AffineTransform { for (IndexType k = 0; k < NumRegs; ++k) outptr[k] = acc[k]; - #undef vec_setzero #undef vec_set_32 #undef vec_add_dpbusd_32 - #undef vec_hadd } else if constexpr (OutputDimensions == 1) { - // We cannot use AVX512 for the last layer because there are only 32 inputs // and the buffer is not padded to 64 elements. #if defined(USE_AVX2) using vec_t = __m256i; - #define vec_setzero _mm256_setzero_si256 + #define vec_setzero() _mm256_setzero_si256() #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; - #define vec_setzero _mm_setzero_si128 + #define vec_setzero() _mm_setzero_si128() #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 #define vec_hadd Simd::m128_hadd + #elif defined(USE_NEON_DOTPROD) + using vec_t = int32x4_t; + #define vec_setzero() vdupq_n_s32(0) + #define vec_set_32 vdupq_n_s32 + #define vec_add_dpbusd_32(acc, a, b) \ + Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + vreinterpretq_s8_s32(b)) + #define vec_hadd Simd::neon_m128_hadd #endif const auto inputVector = reinterpret_cast(input); From ae9e55cf530081afea34216b86b6eb5d9b2b5661 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 29 Jul 2024 00:04:03 -0700 Subject: [PATCH 0707/1309] Simplify Cutnode Reduction Passed Non-regression STC: LR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 143968 W: 37439 L: 37333 D: 69196 Ptnml(0-2): 521, 17228, 36456, 17182, 597 https://tests.stockfishchess.org/tests/view/66a73f9f4ff211be9d4ed27f Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 198954 W: 50384 L: 50345 D: 98225 Ptnml(0-2): 201, 22360, 54347, 22337, 232 https://tests.stockfishchess.org/tests/view/66a906e94ff211be9d4ed423 closes https://github.com/official-stockfish/Stockfish/pull/5526 bench 1277466 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index beafd87dd5c..5f87f28fded 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1133,7 +1133,7 @@ Value Search::Worker::search( // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2 - (ttData.depth >= depth && ss->ttPv) + (!ss->ttPv && move != ttData.move); + r += 2 - (ttData.depth >= depth && ss->ttPv) + !ss->ttPv; // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) From d626af5c3a3781a8f63e1b9b7104ec69aaa4c726 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 17 Aug 2024 22:07:42 +0200 Subject: [PATCH 0708/1309] Fix failing CI for MacOS 13 GCC 11 closes https://github.com/official-stockfish/Stockfish/pull/5540 No functional change --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 836555e6127..8d209a4f974 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -148,7 +148,7 @@ jobs: - name: Download required macOS packages if: runner.os == 'macOS' - run: brew install coreutils + run: brew install coreutils gcc@11 - name: Setup msys and install required packages if: runner.os == 'Windows' From bc80ece6c78cafb3a89d3abcec6c71a517c29f2d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 30 Jul 2024 01:33:56 -0700 Subject: [PATCH 0709/1309] Improve Comments for Pairwise Multiplication Optimization closes https://github.com/official-stockfish/Stockfish/pull/5524 no functional change --- src/nnue/nnue_feature_transformer.h | 76 ++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 17 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index ad0fb1b4d93..2f74dcae2ed 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -352,26 +352,68 @@ class FeatureTransformer { reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); vec_t* out = reinterpret_cast(output + offset); - for (IndexType j = 0; j < NumOutputChunks; ++j) - { - // What we want to do is multiply inputs in a pairwise manner - // (after clipping), and then shift right by 9. Instead, we - // shift left by 7, and use mulhi, stripping the bottom 16 bits, - // effectively shifting right by 16, resulting in a net shift - // of 9 bits. We use mulhi because it maintains the sign of - // the multiplication (unlike mullo), allowing us to make use - // of packus to clip 2 of the inputs, resulting in a save of 2 - // "vec_max_16" calls. A special case is when we use NEON, - // where we shift left by 6 instead, because the instruction - // "vqdmulhq_s16" also doubles the return value after the - // multiplication, adding an extra shift to the left by 1, so - // we compensate by shifting less before the multiplication. - + // Per the NNUE architecture, here we want to multiply pairs of + // clipped elements and divide the product by 128. To do this, + // we can naively perform min/max operation to clip each of the + // four int16 vectors, mullo pairs together, then pack them into + // one int8 vector. However, there exists a faster way. + + // The idea here is to use the implicit clipping from packus to + // save us two vec_max_16 instructions. This clipping works due + // to the fact that any int16 integer below zero will be zeroed + // on packus. + + // Consider the case where the second element is negative. + // If we do standard clipping, that element will be zero, which + // means our pairwise product is zero. If we perform packus and + // remove the lower-side clip for the second element, then our + // product before packus will be negative, and is zeroed on pack. + // The two operation produce equivalent results, but the second + // one (using packus) saves one max operation per pair. + + // But here we run into a problem: mullo does not preserve the + // sign of the multiplication. We can get around this by doing + // mulhi, which keeps the sign. But that requires an additional + // tweak. + + // mulhi cuts off the last 16 bits of the resulting product, + // which is the same as performing a rightward shift of 16 bits. + // We can use this to our advantage. Recall that we want to + // divide the final product by 128, which is equivalent to a + // 7-bit right shift. Intuitively, if we shift the clipped + // value left by 9, and perform mulhi, which shifts the product + // right by 16 bits, then we will net a right shift of 7 bits. + // However, this won't work as intended. Since we clip the + // values to have a maximum value of 127, shifting it by 9 bits + // might occupy the signed bit, resulting in some positive + // values being interpreted as negative after the shift. + + // There is a way, however, to get around this limitation. When + // loading the network, scale accumulator weights and biases by + // 2. To get the same pairwise multiplication result as before, + // we need to divide the product by 128 * 2 * 2 = 512, which + // amounts to a right shift of 9 bits. So now we only have to + // shift left by 7 bits, perform mulhi (shifts right by 16 bits) + // and net a 9 bit right shift. Since we scaled everything by + // two, the values are clipped at 127 * 2 = 254, which occupies + // 8 bits. Shifting it by 7 bits left will no longer occupy the + // signed bit, so we are safe. + + // Note that on NEON processors, we shift left by 6 instead + // because the instruction "vqdmulhq_s16" also doubles the + // return value after the multiplication, adding an extra shift + // to the left by 1, so we compensate by shifting less before + // the multiplication. + + constexpr int shift = #if defined(USE_SSE2) - constexpr int shift = 7; + 7; #else - constexpr int shift = 6; + 6; #endif + + for (IndexType j = 0; j < NumOutputChunks; ++j) + { const vec_t sum0a = vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift); const vec_t sum0b = From a75717ede14df4526a0990466e7b10d00e89c9ff Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 2 Aug 2024 13:23:44 -0700 Subject: [PATCH 0710/1309] Simplify Post-LMR Continuation History Updates Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 55520 W: 14625 L: 14420 D: 26475 Ptnml(0-2): 247, 6522, 14007, 6747, 237 https://tests.stockfishchess.org/tests/view/66ad40874ff211be9d4ed8f7 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 216168 W: 54561 L: 54540 D: 107067 Ptnml(0-2): 196, 24212, 59244, 24239, 193 https://tests.stockfishchess.org/tests/view/66aeac954ff211be9d4eda03 closes https://github.com/official-stockfish/Stockfish/pull/5530 bench 1418263 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5f87f28fded..3c7fc2532dc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1181,9 +1181,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates (~1 Elo) - int bonus = value <= alpha ? -stat_malus(newDepth) - : value >= beta ? stat_bonus(newDepth) - : 0; + int bonus = value >= beta ? stat_bonus(newDepth) : -stat_malus(newDepth); update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } From 4995792a6c1dfca13e3fafc8e55577854b4de1dd Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 10 Aug 2024 13:06:28 +0300 Subject: [PATCH 0711/1309] Simplify cutnode reduction formula Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 137994 W: 34705 L: 34603 D: 68686 Ptnml(0-2): 124, 15371, 37903, 15477, 122 https://tests.stockfishchess.org/tests/view/66aeb74b4ff211be9d4eda10 Passed LTC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 131456 W: 34148 L: 34031 D: 63277 Ptnml(0-2): 506, 15571, 33465, 15672, 514 https://tests.stockfishchess.org/tests/view/66ae258b4ff211be9d4ed95d closes https://github.com/official-stockfish/Stockfish/pull/5531 Bench: 1261995 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 3c7fc2532dc..35f203b9342 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1133,7 +1133,7 @@ Value Search::Worker::search( // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2 - (ttData.depth >= depth && ss->ttPv) + !ss->ttPv; + r += 2 - (ttData.depth >= depth && ss->ttPv); // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) From 5d81071953bd304e57613140b694b03a8241eac9 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Thu, 8 Aug 2024 01:33:42 -0400 Subject: [PATCH 0712/1309] Update default main net to nn-1111cefa1111.nnue Created from 2 distinct spsa tunes of the latest main net (nn-31337bea577c.nnue) and applying the params to the prior main net (nn-e8bac1c07a5a.nnue). This effectively reverts the modifications to output weights and biases in https://github.com/official-stockfish/Stockfish/pull/5509 SPSA: A: 6000, alpha: 0.602, gamma: 0.101 1st - 437 feature transformer biases where values are < 25 54k / 120k games at 180+1.8 https://tests.stockfishchess.org/tests/view/66af98ac4ff211be9d4edad0 nn-808259761cca.nnue 2nd - 208 L2 weights where values are zero 112k / 120k games at 180+1.8 https://tests.stockfishchess.org/tests/view/66b0c3074ff211be9d4edbe5 nn-a56cb8c3d477.nnue When creating the above 2 nets (nn-808259761cca.nnue, nn-a56cb8c3d477.nnue), spsa params were unintentionally applied to nn-e8bac1c07a5a.nnue rather than nn-31337bea577c.nnue due to an issue in a script that creates nets by applying spsa results to base nets. Since they both passed STC and were neutral or slightly positive at LTC, they were combined to see if the elo from each set of params was additive. The 2 nets can be merged on top of nn-e8bac1c07a5a.nnue with: https://github.com/linrock/nnue-tools/blob/90942d3/spsa/combine_nnue.py ``` python3 combine_nnue.py \ nn-e8bac1c07a5a.nnue \ nn-808259761cca.nnue \ nn-a56cb8c3d477.nnue ``` Merging yields nn-87caa003fc6a.nnue which was renamed to nn-1111cefa1111.nnue with an updated nnue-namer around 10x faster than before by: - using a prefix trie for efficient prefix matches - modifying 4 non-functional bytes near the end of the file instead of 2 https://github.com/linrock/nnue-namer Thanks to @MinetaS for pointing out in #nnue-dev what the non-functional bytes are: L3 is 32, 4 bytes for biases, 32 bytes for weights. (fc_2) So -38 and -37 are technically -2 and -1 of fc_1 (type AffineTransform<30, 32>) And since InputDimension is padded to 32 there are total 32 of 2 adjacent bytes padding. So yes, it's non-functional whatever values are there. It's possible to tweak bytes at -38 - 32 * N and -37 - 32 * N given N = 0 ... 31 The net renamed with the new method passed non-regression STC vs. the original net: https://tests.stockfishchess.org/tests/view/66c0f0a821503a509c13b332 To print the spsa params with nnue-pytorch: ``` import features from serialize import NNUEReader feature_set = features.get_feature_set_from_name("HalfKAv2_hm") with open("nn-31337bea577c.nnue", "rb") as f: model = NNUEReader(f, feature_set).model c_end = 16 for i,ft_bias in enumerate(model.input.bias.data[:3072]): value = int(ft_bias * 254) if abs(value) < 25: print(f"ftB[{i}],{value},-1024,1024,{c_end},0.0020") c_end = 6 for i in range(8): for j in range(32): for k in range(30): value = int(model.layer_stacks.l2.weight.data[32 * i + j, k] * 64) if value == 0: print(f"twoW[{i}][{j}][{k}],{value},-127,127,{c_end},0.0020") ``` New params found with the same method as: https://github.com/official-stockfish/Stockfish/pull/5459 Passed STC: https://tests.stockfishchess.org/tests/view/66b4d4464ff211be9d4edf6e LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 136416 W: 35753 L: 35283 D: 65380 Ptnml(0-2): 510, 16159, 34416, 16597, 526 Passed LTC: https://tests.stockfishchess.org/tests/view/66b76e814ff211be9d4ee1cc LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 159336 W: 40753 L: 40178 D: 78405 Ptnml(0-2): 126, 17497, 43864, 18038, 143 closes https://github.com/official-stockfish/Stockfish/pull/5534 bench 1613043 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 55838243342..c9041efbf84 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-31337bea577c.nnue" +#define EvalFileDefaultNameBig "nn-1111cefa1111.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 175021721c6042896f2b35beb251edcf107d9dc2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 13 Aug 2024 19:02:02 -0700 Subject: [PATCH 0713/1309] Simplify bestMove promotion Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 216768 W: 56240 L: 56217 D: 104311 Ptnml(0-2): 794, 24900, 56956, 24957, 777 https://tests.stockfishchess.org/tests/view/66bc11324ff211be9d4ee78b Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 44970 W: 11391 L: 11199 D: 22380 Ptnml(0-2): 44, 4596, 13002, 4810, 33 https://tests.stockfishchess.org/tests/view/66bdbb1b4ff211be9d4eec5a closes https://github.com/official-stockfish/Stockfish/pull/5535 bench: 1613043 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 35f203b9342..ec8a9dd2d98 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1274,9 +1274,9 @@ Value Search::Worker::search( // In case we have an alternative move equal in eval to the current bestmove, // promote it to bestmove by pretending it just exceeds alpha (but not beta). - int inc = (value == bestValue && (int(nodes) & 15) == 0 - && ss->ply + 2 + ss->ply / 32 >= thisThread->rootDepth - && std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY); + int inc = + (value == bestValue && (int(nodes) & 15) == 0 && ss->ply + 2 >= thisThread->rootDepth + && std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY); if (value + inc > bestValue) { From 87814d2fb869166f1bdbcb23893aca57729602fe Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 13 Aug 2024 19:07:08 -0700 Subject: [PATCH 0714/1309] Simplify doShallowerSearch Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 242336 W: 62657 L: 62663 D: 117016 Ptnml(0-2): 941, 28949, 61418, 28895, 965 https://tests.stockfishchess.org/tests/view/66bc13c34ff211be9d4ee794 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 128100 W: 32503 L: 32390 D: 63207 Ptnml(0-2): 106, 14319, 35113, 14380, 132 https://tests.stockfishchess.org/tests/view/66bdbb304ff211be9d4eec5d closes https://github.com/official-stockfish/Stockfish/pull/5537 bench 1586246 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ec8a9dd2d98..34190596bfa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1173,7 +1173,7 @@ Value Search::Worker::search( // 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 + 35 + 2 * newDepth); // (~1 Elo) - const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) + const bool doShallowerSearch = value < bestValue + 8; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; From 6cf7f300acc88df277da64b754c436462f48dadf Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Fri, 16 Aug 2024 22:54:05 +0200 Subject: [PATCH 0715/1309] Simplify stand pat adjustement Remove && !PvNode condition for stand pat adjustement in quiescence search. Passed non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 108544 W: 28228 L: 28085 D: 52231 Ptnml(0-2): 389, 12902, 27554, 13031, 396 https://tests.stockfishchess.org/tests/view/66bb402e4ff211be9d4ee688 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 193014 W: 48796 L: 48751 D: 95467 Ptnml(0-2): 188, 21481, 53116, 21542, 180 https://tests.stockfishchess.org/tests/view/66bc78774ff211be9d4ee88f closes https://github.com/official-stockfish/Stockfish/pull/5538 Bench 1787360 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1ac40d879df..3201e7a8afe 100644 --- a/AUTHORS +++ b/AUTHORS @@ -171,6 +171,7 @@ Niklas Fiekas (niklasf) Nikolay Kostov (NikolayIT) Norman Schmidt (FireFather) notruck +Nour Berakdar (Nonlinear) Ofek Shochat (OfekShochat, ghostway) Ondrej Mosnáček (WOnder93) Ondřej Mišina (AndrovT) diff --git a/src/search.cpp b/src/search.cpp index 34190596bfa..b062aa46df0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1502,7 +1502,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && !PvNode) + if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) bestValue = (3 * bestValue + beta) / 4; if (!ss->ttHit) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, From d275bf9643768cbf6472977f4262220e6c1c1bb5 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 15 Aug 2024 16:24:56 -0600 Subject: [PATCH 0716/1309] Introduce Fail Low History Bonus When a node fails low, give TT move a small bonus 1/4 of normal value. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 92384 W: 24094 L: 23691 D: 44599 Ptnml(0-2): 323, 10852, 23465, 11203, 349 https://tests.stockfishchess.org/tests/view/66be80794ff211be9d4eed68 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 114660 W: 29260 L: 28778 D: 56622 Ptnml(0-2): 97, 12506, 31653, 12966, 108 https://tests.stockfishchess.org/tests/view/66bf63ee4ff211be9d4eeef0 closes https://github.com/official-stockfish/Stockfish/pull/5539 bench 1463003 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index b062aa46df0..c94d3c42222 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1361,6 +1361,10 @@ Value Search::Worker::search( << stat_bonus(depth) * bonus / 25; } + // Bonus when search fails low and there is a TT move + else if (moveCount > 1 && ttData.move && (cutNode || PvNode)) + thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) / 4; + if (PvNode) bestValue = std::min(bestValue, maxValue); From 9fb58328e363d84e3cf720b018e639b139ba95c2 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Sun, 18 Aug 2024 16:11:06 +0200 Subject: [PATCH 0717/1309] Tweak late move extensions Allow late move extensions only for PV and cut nodes. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 44512 W: 11688 L: 11355 D: 21469 Ptnml(0-2): 167, 5180, 11229, 5513, 167 https://tests.stockfishchess.org/tests/view/66c0509d4ff211be9d4ef10e Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 152970 W: 39026 L: 38466 D: 75478 Ptnml(0-2): 102, 16792, 42164, 17298, 129 https://tests.stockfishchess.org/tests/view/66c0994d21503a509c13b2b6 closes https://github.com/official-stockfish/Stockfish/pull/5541 bench: 1484730 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c94d3c42222..531fc42fe35 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1163,7 +1163,7 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r, newDepth + 1)); + Depth d = std::max(1, std::min(newDepth - r, newDepth + (PvNode || cutNode))); value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From a0597b1281f22dc90dbcc2f52f4a1a0e2bc09f96 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Mon, 26 Aug 2024 15:22:22 +0200 Subject: [PATCH 0718/1309] Forcibly split NUMA nodes on Windows split by processor groups due to Window's thread scheduler issues. fixes #5551 closes https://github.com/official-stockfish/Stockfish/pull/5552 No functional change --- src/numa.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/numa.h b/src/numa.h index 20d352c91c2..db8359222cd 100644 --- a/src/numa.h +++ b/src/numa.h @@ -582,7 +582,21 @@ class NumaConfig { // still no way to set thread affinity spanning multiple processor groups. // See https://learn.microsoft.com/en-us/windows/win32/procthread/numa-support // We also do this is if need to force old API for some reason. - if (STARTUP_USE_OLD_AFFINITY_API) + // + // 2024-08-26: It appears that we need to actually always force this behaviour. + // While Windows allows this to work now, such assignments have bad interaction + // with the scheduler - in particular it still prefers scheduling on the thread's + // "primary" node, even if it means scheduling SMT processors first. + // See https://github.com/official-stockfish/Stockfish/issues/5551 + // See https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups + // + // Each process is assigned a primary group at creation, and by default all + // of its threads' primary group is the same. Each thread's ideal processor + // is in the thread's primary group, so threads will preferentially be + // scheduled to processors on their primary group, but they are able to + // be scheduled to processors on any other group. + // + // used to be guarded by if (STARTUP_USE_OLD_AFFINITY_API) { NumaConfig splitCfg = empty(); From 54def6f7eb7c411cba9c1e31ff4074757f64e826 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:37:52 +0200 Subject: [PATCH 0719/1309] rename !(PvNode || cutNode) to allNode Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 108992 W: 28178 L: 28039 D: 52775 Ptnml(0-2): 356, 12428, 28762, 12621, 329 https://tests.stockfishchess.org/tests/view/66c73a51bf8c9d8780fda532 closes https://github.com/official-stockfish/Stockfish/pull/5549 No functional change --- src/search.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 531fc42fe35..0b6756a7e21 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -509,6 +509,7 @@ Value Search::Worker::search( constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; + const bool allNode = !(PvNode || cutNode); // Dive into quiescence search when the depth reaches zero if (depth <= 0) @@ -1141,7 +1142,7 @@ Value Search::Worker::search( // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) - r += 1 + !(PvNode || cutNode); + r += 1 + allNode; // For first picked move (ttMove) reduce reduction, but never allow // reduction to go below 0 (~3 Elo) @@ -1163,7 +1164,7 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r, newDepth + (PvNode || cutNode))); + Depth d = std::max(1, std::min(newDepth - r, newDepth + !allNode)); value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); @@ -1341,7 +1342,7 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (122 * (depth > 5) + 39 * (PvNode || cutNode) + 165 * ((ss - 1)->moveCount > 8) + int bonus = (122 * (depth > 5) + 39 * !allNode + 165 * ((ss - 1)->moveCount > 8) + 107 * (!ss->inCheck && bestValue <= ss->staticEval - 98) + 134 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 91)); @@ -1362,7 +1363,7 @@ Value Search::Worker::search( } // Bonus when search fails low and there is a TT move - else if (moveCount > 1 && ttData.move && (cutNode || PvNode)) + else if (moveCount > 1 && ttData.move && !allNode) thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) / 4; if (PvNode) From 451044202a49fbbbe908b49fab323d70fab333e7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 22 Aug 2024 18:27:16 +0300 Subject: [PATCH 0720/1309] Simpler formula for ss->cutoffCnt update closes https://github.com/official-stockfish/Stockfish/pull/5548 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0b6756a7e21..ad2c35e7708 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1292,7 +1292,7 @@ Value Search::Worker::search( if (value >= beta) { - ss->cutoffCnt += 1 + !ttData.move - (extension >= 2); + ss->cutoffCnt += !ttData.move + (extension < 2); assert(value >= beta); // Fail high break; } From ab00c24c7e547a06e3277fc5ae7f66980532224c Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 31 Aug 2024 15:42:29 +0200 Subject: [PATCH 0721/1309] Fix some of the tests due to https://github.com/official-stockfish/Stockfish/issues/5185 some CI tests are skipped. This patch fixes a few tests that need updating. closes https://github.com/official-stockfish/Stockfish/pull/5560 No functional change --- tests/instrumented.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/instrumented.sh b/tests/instrumented.sh index cb5a3a9f245..5fc6ca9a974 100755 --- a/tests/instrumented.sh +++ b/tests/instrumented.sh @@ -177,25 +177,25 @@ cat << EOF > game.exp send "ucinewgame\n" send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" - send "go depth 18\n" + send "go depth 18 searchmoves c6d7\n" expect "score mate 2 * pv c6d7 * f7f5" expect "bestmove c6d7" send "ucinewgame\n" send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" - send "go mate 2\n" + send "go mate 2 searchmoves c6d7\n" expect "score mate 2 * pv c6d7" expect "bestmove c6d7" send "ucinewgame\n" send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" - send "go nodes 10000\n" + send "go nodes 500000 searchmoves c6d7\n" expect "score mate 2 * pv c6d7 * f7f5" expect "bestmove c6d7" send "ucinewgame\n" send "position fen 1NR2B2/5p2/5p2/1p1kpp2/1P2rp2/2P1pB2/2P1P1K1/8 b - - \n" - send "go depth 18\n" + send "go depth 27\n" expect "score mate -2" expect "pv d5e6 c8d8" expect "bestmove d5e6" @@ -257,7 +257,7 @@ cat << EOF > syzygy.exp expect "Stockfish" send "uci\n" send "setoption name SyzygyPath value ../tests/syzygy/\n" - expect "info string Found 35 tablebases" + expect "info string Found 35 WDL and 35 DTZ tablebase files (up to 4-man)." send "bench 128 1 8 default depth\n" expect "Nodes searched :" send "ucinewgame\n" From 2054add23cf234f302c67709efc0d265c5a98eae Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Tue, 3 Sep 2024 08:20:06 +0200 Subject: [PATCH 0722/1309] Update the WDL model updates the internal WDL model, using data from 2.6M games played by the revisions since 9fb5832. https://github.com/official-stockfish/Stockfish/pull/5565 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 9b60680d8c5..c94f8b914cd 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -339,8 +339,8 @@ WinRateParams win_rate_params(const Position& pos) { double m = std::clamp(material, 17, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-41.25712052, 121.47473115, -124.46958843, 411.84490997}; - constexpr double bs[] = {84.92998051, -143.66658718, 80.09988253, 49.80869370}; + constexpr double as[] = {-37.45051876, 121.19101539, -132.78783573, 420.70576692}; + constexpr double bs[] = {90.26261072, -137.26549898, 71.10130540, 51.35259597}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From 38e0cc7b909796c1a71d9c07b636698b69420975 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 31 Aug 2024 16:00:59 +0200 Subject: [PATCH 0723/1309] Update Top CPU Contributors to the status as of Aug 31st 2024. closes https://github.com/official-stockfish/Stockfish/pull/5561 No functional change --- Top CPU Contributors.txt | 193 +++++++++++++++++++++------------------ 1 file changed, 104 insertions(+), 89 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 11636e840b5..3d8c52361dd 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,106 +1,109 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2024-02-24. +Contributors to Fishtest with >10,000 CPU hours, as of 2024-08-31. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 39302472 3055513453 -technologov 20845762 994893444 -linrock 8616428 560281417 +noobpwnftw 40428649 3164740143 +technologov 23581394 1076895482 +vdv 19425375 718302718 +linrock 10034115 643194527 mlang 3026000 200065824 -okrout 2332151 222639518 -pemo 1800019 60274069 +okrout 2572676 237511408 +pemo 1836785 62226157 dew 1689162 100033738 -TueRens 1474943 75121774 -grandphish2 1463002 91616949 -JojoM 1109702 72927902 -olafm 978631 71037944 -sebastronomy 939955 44920556 +TueRens 1648780 77891164 +sebastronomy 1468328 60859092 +grandphish2 1466110 91776075 +JojoM 1130625 73666098 +olafm 1067009 74807270 tvijlbrief 796125 51897690 -gvreuls 711320 49142318 +oz 781847 53910686 +rpngn 768460 49812975 +gvreuls 751085 52177668 mibere 703840 46867607 -oz 646268 46293638 -rpngn 572571 38928563 -leszek 531858 39316505 -cw 518116 34894291 +leszek 566598 42024615 +cw 519601 34988161 fastgm 503862 30260818 CSU_Dynasty 468784 31385034 -ctoks 434591 28520597 -maximmasiutin 429983 27066286 +maximmasiutin 439192 27893522 +ctoks 435148 28541909 crunchy 427414 27371625 bcross 415724 29061187 +robal 371112 24642270 +mgrabiak 367963 26464704 velislav 342588 22140902 -mgrabiak 338763 23999170 +ncfish1 329039 20624527 Fisherman 327231 21829379 -robal 299836 20213182 Dantist 296386 18031762 -ncfish1 267604 17881149 +tolkki963 262050 22049676 +Sylvain27 255595 8864404 nordlandia 249322 16420192 +Fifis 237657 13065577 marrco 234581 17714473 -tolkki963 233490 19773930 +Calis007 217537 14450582 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 -Calis007 188631 12795784 Thanar 179852 12365359 -Fifis 176209 10638245 -vdv 175544 9904472 +javran 169679 13481966 +armo9494 162863 10937118 spams 157128 10319326 -DesolatedDodo 156659 10210328 -armo9494 155355 10566898 +DesolatedDodo 156683 10211206 +Wencey 152308 8375444 sqrt2 147963 9724586 +vdbergh 140311 9225125 jcAEie 140086 10603658 -vdbergh 139746 9172061 CoffeeOne 137100 5024116 malala 136182 8002293 xoto 133759 9159372 +Dubslow 129614 8519312 davar 129023 8376525 DMBK 122960 8980062 dsmith 122059 7570238 -javran 121564 10144656 +CypressChess 120784 8672620 +sschnee 120526 7547722 +maposora 119734 10749710 amicic 119661 7938029 -sschnee 118107 7389266 -Wolfgang 114616 8070494 +Wolfgang 115713 8159062 Data 113305 8220352 BrunoBanani 112960 7436849 -Wencey 111502 5991676 -cuistot 108503 7006992 -CypressChess 108331 7759788 +markkulix 112897 9133168 +cuistot 109802 7121030 skiminki 107583 7218170 +sterni1971 104431 5938282 MaZePallas 102823 6633619 -sterni1971 100532 5880772 sunu 100167 7040199 zeryl 99331 6221261 thirdlife 99156 2245320 ElbertoOne 99028 7023771 -Dubslow 98600 6903242 -markkulix 97010 7643900 -bigpen0r 94809 6529203 +megaman7de 98456 6675076 +Goatminola 96765 8257832 +bigpen0r 94825 6529241 brabos 92118 6186135 Maxim 90818 3283364 psk 89957 5984901 -megaman7de 88822 6052132 racerschmacer 85805 6122790 -maposora 85710 7778146 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 +szupaw 82495 7151686 BRAVONE 81239 5054681 nssy 76497 5259388 +cody 76126 4492126 jromang 76106 5236025 +MarcusTullius 76103 5061991 +woutboat 76072 6022922 +Spprtr 75977 5252287 teddybaer 75125 5407666 Pking_cda 73776 5293873 -yurikvelo 73516 5036928 -MarcusTullius 71053 4803477 +yurikvelo 73611 5046822 +Mineta 71130 4711422 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 -Spprtr 69646 4806763 -Mineta 66325 4537742 manap 66273 4121774 -szupaw 65468 5669742 tinker 64333 4268790 qurashee 61208 3429862 -woutboat 59496 4906352 AGI 58195 4329580 robnjr 57262 4053117 Freja 56938 3733019 @@ -108,39 +111,45 @@ MaxKlaxxMiner 56879 3423958 ttruscott 56010 3680085 rkl 55132 4164467 jmdana 54697 4012593 +notchris 53936 4184018 renouve 53811 3501516 -notchris 52433 4044590 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 -Goatminola 51004 4432492 rap 49985 3219146 pb00067 49733 3298934 GPUex 48686 3684998 OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 -oryx 45533 3539290 +oryx 45639 3546530 VoyagerOne 45476 3452465 speedycpu 43842 3003273 jbwiebe 43305 2805433 Antihistamine 41788 2761312 mhunt 41735 2691355 +jibarbosa 41640 4145702 homyur 39893 2850481 gri 39871 2515779 +DeepnessFulled 39020 3323102 Garf 37741 2999686 SC 37299 2731694 -Sylvain27 36520 1467082 +Gaster319 37118 3279678 +naclosagc 36562 1279618 csnodgrass 36207 2688994 -Gaster319 35655 3149442 strelock 34716 2074055 +gopeto 33717 2245606 EthanOConnor 33370 2090311 slakovv 32915 2021889 -gopeto 31884 2076712 +jojo2357 32890 2826662 +shawnxu 32019 2802552 Gelma 31771 1551204 +vidar808 31560 1351810 kdave 31157 2198362 manapbk 30987 1810399 -ZacHFX 30551 2238078 +ZacHFX 30966 2272416 +TataneSan 30713 1513402 +votoanthuan 30691 2460856 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 @@ -149,14 +158,15 @@ hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 belzedar94 28846 1811530 -votoanthuan 27978 2285818 -shawnxu 27438 2465810 +mecevdimitar 27610 1721382 chriswk 26902 1868317 xwziegtm 26897 2124586 achambord 26582 1767323 +somethingintheshadows 26496 2186404 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25397 1701264 +srowen 25743 1490684 +Ulysses 25413 1702830 Jopo12321 25227 1652482 SFTUser 25182 1675689 nabildanial 25068 1531665 @@ -164,66 +174,69 @@ Sharaf_DG 24765 1786697 rodneyc 24376 1416402 jsys14 24297 1721230 agg177 23890 1395014 -srowen 23842 1342508 +AndreasKrug 23754 1890115 Ente 23752 1678188 -jojo2357 23479 2061238 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 +WoodMan777 23253 2023048 +Nullvalue 23155 2022752 cisco2015 22920 1763301 Zirie 22542 1472937 -Nullvalue 22490 1970374 -AndreasKrug 22485 1769491 team-oh 22272 1636708 Roady 22220 1465606 MazeOfGalious 21978 1629593 -sg4032 21947 1643353 +sg4032 21950 1643373 +tsim67 21747 1330880 ianh2105 21725 1632562 +Skiff84 21711 1014212 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 user213718 21454 1404128 +Serpensin 21452 1790510 sphinx 21211 1384728 -qoo_charly_cai 21135 1514907 +qoo_charly_cai 21136 1514927 +IslandLambda 21062 1220838 jjoshua2 21001 1423089 Zake9298 20938 1565848 horst.prack 20878 1465656 +fishtester 20729 1348888 0xB00B1ES 20590 1208666 -Serpensin 20487 1729674 -Dinde 20440 1292390 +ols 20477 1195945 +Dinde 20459 1292774 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 wei 19973 1745989 -fishtester 19617 1257388 +teenychess 19819 1762006 rstoesser 19569 1293588 eudhan 19274 1283717 vulcan 18871 1729392 +wizardassassin 18795 1376884 Karpovbot 18766 1053178 -WoodMan777 18556 1628264 jundery 18445 1115855 +mkstockfishtester 18350 1690676 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17414 981289 -ols 17291 1042003 iisiraider 17275 1049015 -Skiff84 17111 950248 DragonLord 17014 1162790 +Karby 17008 1013160 +pirt 16965 1271519 redstone59 16842 1461780 -Karby 16839 1010124 Alb11747 16787 1213990 -pirt 16493 1237199 Naven94 16414 951718 -wizardassassin 16392 1148672 +scuzzi 16115 994341 IgorLeMasson 16064 1147232 -scuzzi 15757 968735 ako027ako 15671 1173203 +infinigon 15285 965966 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 OssumOpossum 14857 1007129 LunaticBFF57 14525 1190310 enedene 14476 905279 -IslandLambda 14393 958196 +Hjax 14394 1005013 bpfliegel 14233 882523 YELNAMRON 14230 1128094 mpx86 14019 759568 @@ -233,54 +246,56 @@ Nesa92 13806 1116101 crocogoat 13803 1117422 joster 13710 946160 mbeier 13650 1044928 -Hjax 13535 915487 +Pablohn26 13552 1088532 +wxt9861 13550 1312306 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 Machariel 13010 863104 -infinigon 12991 943216 +nalanzeyu 12996 232590 mabichito 12903 749391 +Jackfish 12895 868928 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 +whelanh 12682 266404 mschmidt 12644 863193 korposzczur 12606 838168 -tsim67 12570 890180 -Jackfish 12553 836958 fatmurphy 12547 853210 -Oakwen 12503 853105 +Oakwen 12532 855759 +icewulf 12447 854878 SapphireBrand 12416 969604 deflectooor 12386 579392 modolief 12386 896470 -TataneSan 12358 609332 Farseer 12249 694108 +Hongildong 12201 648712 pgontarz 12151 848794 dbernier 12103 860824 -FormazChar 11989 907809 +szczur90 12035 942376 +FormazChar 12019 910409 +rensonthemove 11999 971993 stocky 11954 699440 -somethingintheshadows 11940 989472 -MooTheCow 11892 776126 +MooTheCow 11923 779432 3cho 11842 1036786 -whelanh 11557 245188 +ckaz 11792 732276 infinity 11470 727027 aga 11412 695127 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 +Def9Infinity 11345 696552 d64 11263 789184 ali-al-zhrani 11245 779246 -ckaz 11170 680866 +ImperiumAeternum 11155 952000 snicolet 11106 869170 dapper 11032 771402 Ethnikoi 10993 945906 Snuuka 10938 435504 -Karmatron 10859 678058 +Karmatron 10871 678306 basepi 10637 744851 -jibarbosa 10628 857100 Cubox 10621 826448 -mecevdimitar 10609 787318 +gerbil 10519 971688 michaelrpg 10509 739239 -Def9Infinity 10427 686978 OIVAS7572 10420 995586 -wxt9861 10412 1013864 Garruk 10365 706465 dzjp 10343 732529 +RickGroszkiewicz 10263 990798 From e0bfc4b69bbe928d6f474a46560bcc3b3f6709aa Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Tue, 3 Sep 2024 18:07:22 +0200 Subject: [PATCH 0724/1309] Stockfish 17 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 17 Bench: 1484730 --- Stockfish 17 Today we have the pleasure to announce a new major release of Stockfish. As always, you can freely download it at https://stockfishchess.org/download and use it in the GUI of your choice. Don’t forget to join our Discord server[1] to get in touch with the community of developers and users of the project! *Quality of chess play* In tests against Stockfish 16, this release brings an Elo gain of up to 46 points[2] and wins up to 4.5 times more game pairs[3] than it loses. In practice, high-quality moves are now found in less time, with a user upgrading from Stockfish 14 being able to analyze games at least 6 times[4] faster with Stockfish 17 while maintaining roughly the same quality. During this development period, Stockfish won its 9th consecutive first place in the main league of the Top Chess Engine Championship (TCEC)[5], and the 24th consecutive first place in the main events (bullet, blitz, and rapid) of the Computer Chess Championship (CCC)[6]. *Update highlights* *Improved engine lines* This release introduces principal variations (PVs) that are more informative for mate and decisive table base (TB) scores. In both cases, the PV will contain all moves up to checkmate. For mate scores, the PV shown is the best variation known to the engine at that point, while for table base wins, it follows, based on the TB, a sequence of moves that preserves the game outcome to checkmate. *NUMA performance optimization* For high-end computers with multiple CPUs (typically a dual-socket architecture with 100+ cores), this release automatically improves performance with a `NumaPolicy` setting that optimizes non-uniform memory access (NUMA). Although typical consumer hardware will not benefit, speedups of up to 2.8x[7] have been measured. *Shoutouts* *ChessDB* During the past 1.5 years, hundreds of cores have been continuously running Stockfish to grow a database of analyzed positions. This chess cloud database[8] now contains well over 45 billion positions, providing excellent coverage of all openings and commonly played lines. This database is already integrated into GUIs such as En Croissant[9] and Nibbler[10], which access it through the public API. *Leela Chess Zero* Generally considered to be the strongest GPU engine, it continues to provide open data which is essential for training our NNUE networks. They released version 0.31.1[11] of their engine a few weeks ago, check it out! *Website redesign* Our website has undergone a redesign in recent months, most notably in our home page[12], now featuring a darker color scheme and a more modern aesthetic, while still maintaining its core identity. We hope you'll like it as much as we do! *Thank you* The Stockfish project builds on a thriving community of enthusiasts (thanks everybody!) who contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We would like to express our gratitude for the 11k stars[13] that light up our GitHub project! Thank you for your support and encouragement – your recognition means a lot to us. We invite our chess fans to join the Fishtest testing framework[14] to contribute compute resources needed for development. Programmers can contribute to the project either directly to Stockfish[15] (C++), to Fishtest[16] (HTML, CSS, JavaScript, and Python), to our trainer nnue-pytorch[17] (C++ and Python), or to our website[18] (HTML, CSS/SCSS, and JavaScript). The Stockfish team [1] https://discord.gg/GWDRS3kU6R [2] https://tests.stockfishchess.org/tests/view/66d738ba9de3e7f9b33d159a [3] https://tests.stockfishchess.org/tests/view/66d738f39de3e7f9b33d15a0 [4] https://github.com/official-stockfish/Stockfish/wiki/Useful-data#equivalent-time-odds-and-normalized-game-pair-elo [5] https://en.wikipedia.org/wiki/Stockfish_(chess)#Top_Chess_Engine_Championship [6] https://en.wikipedia.org/wiki/Stockfish_(chess)#Chess.com_Computer_Chess_Championship [7] https://github.com/official-stockfish/Stockfish/pull/5285 [8] https://chessdb.cn/queryc_en/ [9] https://encroissant.org/ [10] https://github.com/rooklift/nibbler [11] https://github.com/LeelaChessZero/lc0/releases/tag/v0.31.1 [12] https://stockfishchess.org/ [13] https://github.com/official-stockfish/Stockfish/stargazers [14] https://github.com/official-stockfish/fishtest/wiki/Running-the-worker [15] https://github.com/official-stockfish/Stockfish [16] https://github.com/official-stockfish/fishtest [17] https://github.com/official-stockfish/nnue-pytorch [18] https://github.com/official-stockfish/stockfish-web --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 664ab4b89ff..91cdbc4dccc 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -38,7 +38,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "dev"; +constexpr std::string_view version = "17"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From f4ba7ce67a0a4b77d9d641b4010dfc73f3b534b2 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 9 Sep 2024 17:07:36 +0200 Subject: [PATCH 0725/1309] Restore development closes https://github.com/official-stockfish/Stockfish/pull/5580 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 91cdbc4dccc..664ab4b89ff 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -38,7 +38,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "17"; +constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 4fb04eb3df9279cd7b8c3d43dbf1916b3c74bea3 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 30 Aug 2024 14:09:24 +0300 Subject: [PATCH 0726/1309] Simplify history bonus After we recently added the disallowance for negative bonuses, it is no longer necessary to keep the max comparison in the previous step. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 72000 W: 18820 L: 18637 D: 34543 Ptnml(0-2): 267, 8489, 18276, 8730, 238 https://tests.stockfishchess.org/tests/view/66ce132cbf8c9d8780fdabe7 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 67452 W: 17136 L: 16961 D: 33355 Ptnml(0-2): 35, 7489, 18519, 7632, 51 https://tests.stockfishchess.org/tests/view/66cf6ad49de3e7f9b33d1010 closes https://github.com/official-stockfish/Stockfish/pull/5554 Bench: 1147012 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ad2c35e7708..9d950c0e755 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1347,7 +1347,7 @@ Value Search::Worker::search( + 134 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 91)); // Proportional to "how much damage we have to undo" - bonus += std::clamp(-(ss - 1)->statScore / 100, -94, 304); + bonus += std::min(-(ss - 1)->statScore / 100, 304); bonus = std::max(bonus, 0); From ddc9f48bc3e0b1d22b2d1259d5d45d4640e0374d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 23 Aug 2024 17:13:49 -0700 Subject: [PATCH 0727/1309] Introduce Material Correction History Idea from Caissa (https://github.com/Witek902/Caissa) chess engine. Add a secondary correction history indexed by the material key of a position. The material key is the zobrist hash representing the number of pieces left in a position. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 189472 W: 49360 L: 48813 D: 91299 Ptnml(0-2): 666, 22453, 47953, 22996, 668 https://tests.stockfishchess.org/tests/view/66cbddafbf8c9d8780fda9f1 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 224190 W: 57022 L: 56312 D: 110856 Ptnml(0-2): 197, 24723, 61540, 25443, 192 https://tests.stockfishchess.org/tests/view/66cd529bbf8c9d8780fdab4c closes https://github.com/official-stockfish/Stockfish/pull/5556 Bench: 1462697 --- src/movepick.h | 30 ++++++++++++++++++++++-------- src/search.cpp | 11 ++++++++--- src/search.h | 11 ++++++----- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 61f6368e6d9..651091b0829 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -34,14 +34,15 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int PAWN_CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 +constexpr int MATERIAL_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, +static_assert((PAWN_CORRECTION_HISTORY_SIZE & (PAWN_CORRECTION_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); enum PawnHistoryType { @@ -51,7 +52,11 @@ enum PawnHistoryType { template inline int pawn_structure_index(const Position& pos) { - return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); + return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : PAWN_CORRECTION_HISTORY_SIZE) - 1); +} + +inline int material_index(const Position& pos) { + return pos.material_key() & (MATERIAL_CORRECTION_HISTORY_SIZE - 1); } // StatsEntry stores the stat table value. It is usually a number but could @@ -133,9 +138,18 @@ using ContinuationHistory = Stats // PawnHistory is addressed by the pawn structure and a move's [piece][to] using PawnHistory = Stats; -// CorrectionHistory is addressed by color and pawn structure -using CorrectionHistory = - Stats; + +// Correction histories record differences between the static evaluation of +// positions and their search score. It is used to improve the static evaluation +// used by some search heuristics. + +// PawnCorrectionHistory is addressed by color and pawn structure +using PawnCorrectionHistory = + Stats; + +// MaterialCorrectionHistory is addressed by color and material configuration +using MaterialCorrectionHistory = + Stats; // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one diff --git a/src/search.cpp b/src/search.cpp index 9d950c0e755..bc85a5c3e96 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -81,7 +81,10 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { - auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; + const auto pcv = + w.pawnCorrectionHistory[pos.side_to_move()][pawn_structure_index(pos)]; + const auto mcv = w.materialCorrectionHistory[pos.side_to_move()][material_index(pos)]; + const auto cv = (2 * pcv + mcv) / 3; v += 66 * cv / 512; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -487,7 +490,8 @@ void Search::Worker::clear() { mainHistory.fill(0); captureHistory.fill(-700); pawnHistory.fill(-1188); - correctionHistory.fill(0); + pawnCorrectionHistory.fill(0); + materialCorrectionHistory.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) @@ -1390,7 +1394,8 @@ Value Search::Worker::search( { auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - thisThread->correctionHistory[us][pawn_structure_index(pos)] << bonus; + thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] << bonus; + thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus; } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); diff --git a/src/search.h b/src/search.h index 0f635186a3b..c9fe9e184ac 100644 --- a/src/search.h +++ b/src/search.h @@ -277,11 +277,12 @@ class Worker { void ensure_network_replicated(); // Public because they need to be updatable by the stats - ButterflyHistory mainHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; - PawnHistory pawnHistory; - CorrectionHistory correctionHistory; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; + PawnCorrectionHistory pawnCorrectionHistory; + MaterialCorrectionHistory materialCorrectionHistory; private: void iterative_deepening(); From e74452ae44df35aeda21e81bb2eec883a7a45c38 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 1 Sep 2024 20:56:09 -0400 Subject: [PATCH 0728/1309] Reduce on ttcaptures if not capture Tweak ttcapture reduction. Reduce on ttcaptures only if the current move is a capture Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 94912 W: 24896 L: 24492 D: 45524 Ptnml(0-2): 301, 11197, 24087, 11539, 332 https://tests.stockfishchess.org/tests/view/66cd2264bf8c9d8780fdab34 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 60738 W: 15465 L: 15096 D: 30177 Ptnml(0-2): 42, 6573, 16775, 6932, 47 https://tests.stockfishchess.org/tests/view/66cf356d9de3e7f9b33d0fde closes https://github.com/official-stockfish/Stockfish/pull/5562 Bench: 1268700 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bc85a5c3e96..aab5c743cc5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1140,8 +1140,8 @@ Value Search::Worker::search( if (cutNode) r += 2 - (ttData.depth >= depth && ss->ttPv); - // Increase reduction if ttMove is a capture (~3 Elo) - if (ttCapture) + // Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo) + if (ttCapture && !capture) r++; // Increase reduction if next ply has a lot of fail high (~5 Elo) From 66a7965b0fab4d1ae59203039b0b2262dbf2bcc0 Mon Sep 17 00:00:00 2001 From: xu-shawn <50402888+xu-shawn@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:31:40 -0700 Subject: [PATCH 0729/1309] Copy scripts directory in distributed packages closes https://github.com/official-stockfish/Stockfish/pull/5571 No functional change --- .github/workflows/upload_binaries.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index c5a2cd105c6..1067f6e7615 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -59,6 +59,7 @@ jobs: mv "${{ matrix.config.simple_name }} ${{ matrix.binaries }}" stockfish-workflow cd stockfish-workflow cp -r src ../stockfish/ + cp -r scripts ../stockfish/ cp stockfish-$NAME-$BINARY$EXT ../stockfish/ cp "Top CPU Contributors.txt" ../stockfish/ cp Copying.txt ../stockfish/ From 1b310cc87e22840621284f27f1f5873c9b9c0384 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Mon, 2 Sep 2024 19:22:18 +0900 Subject: [PATCH 0730/1309] Export and clean up net downloading script Fixes https://github.com/official-stockfish/Stockfish/issues/5564 This patch extracts the net downloading script in Makefile into an external script file. Also the script is moderately rewritten for improved readability and speed. * Use wget preferentially over curl, as curl is known to have slight overhead. * Use command instead of hash to check if command exists. Reportedly, hash always returns zero in some POSIX shells even when the command fails. * Command existence checks (wget/curl, sha256sum) are performed only once at the beginning. * Each of common patterns is encapsulated in a function (get_nnue_filename, validate_network). * Print out error/warning messages to stderr. closes https://github.com/official-stockfish/Stockfish/pull/5563 No functional change Co-authored-by: Disservin --- .github/workflows/tests.yml | 12 +++--- scripts/net.sh | 75 +++++++++++++++++++++++++++++++++++++ src/Makefile | 52 +------------------------ 3 files changed, 82 insertions(+), 57 deletions(-) create mode 100755 scripts/net.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8d209a4f974..a826e6f063e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -143,7 +143,7 @@ jobs: FROM ${{ matrix.config.base_image }} WORKDIR /app RUN apk update && apk add make g++ - CMD ["sh", "script.sh"] + CMD ["sh", "src/script.sh"] EOF - name: Download required macOS packages @@ -176,7 +176,7 @@ jobs: $COMPCXX -v else echo "$COMPCXX -v" > script.sh - docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder fi - name: Test help target @@ -342,8 +342,8 @@ jobs: - name: Test riscv64 build if: matrix.config.run_riscv64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build" > script.sh - docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder + echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build" > script.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder ../tests/signature.sh $benchref # ppc64 tests @@ -351,8 +351,8 @@ jobs: - name: Test ppc64 build if: matrix.config.run_ppc64_tests run: | - echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=ppc-64 build" > script.sh - docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder + echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=ppc-64 build" > script.sh + docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder ../tests/signature.sh $benchref # Other tests diff --git a/scripts/net.sh b/scripts/net.sh new file mode 100755 index 00000000000..168fbad66b0 --- /dev/null +++ b/scripts/net.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -q") || \ + (command -v curl > /dev/null 2>&1 && echo "curl -L -s -k")) + +if [ -z "$wget_or_curl" ]; then + >&2 printf "%s\n" "Neither wget or curl is installed." \ + "Install one of these tools to download NNUE files automatically." + exit 1 +fi + +sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \ + (command -v sha256sum > /dev/null 2>&1 && echo "sha256sum")) + +if [ -z "$sha256sum" ]; then + >&2 echo "sha256sum not found, NNUE files will be assumed valid." +fi + +get_nnue_filename() { + grep "$1" evaluate.h | grep "#define" | sed "s/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/" +} + +validate_network() { + # If no sha256sum command is available, assume the file is always valid. + if [ -n "$sha256sum" ] && [ -f "$1" ]; then + if [ "$1" != "nn-$($sha256sum "$1" | cut -c 1-12).nnue" ]; then + rm -f "$1" + return 1 + fi + fi +} + +fetch_network() { + _filename="$(get_nnue_filename "$1")" + + if [ -z "$_filename" ]; then + >&2 echo "NNUE file name not found for: $1" + return 1 + fi + + if [ -f "$_filename" ]; then + if validate_network "$_filename"; then + echo "Existing $_filename validated, skipping download" + return + else + echo "Removing invalid NNUE file: $_filename" + fi + fi + + for url in \ + "https://tests.stockfishchess.org/api/nn/$_filename" \ + "https://github.com/official-stockfish/networks/raw/master/$_filename"; do + echo "Downloading from $url ..." + if $wget_or_curl "$url"; then + if validate_network "$_filename"; then + echo "Successfully validated $_filename" + else + echo "Downloaded $_filename is invalid" + continue + fi + else + echo "Failed to download from $url" + fi + if [ -f "$_filename" ]; then + return + fi + done + + # Download was not successful in the loop, return false. + >&2 echo "Failed to download $_filename" + return 1 +} + +fetch_network EvalFileDefaultNameBig && \ +fetch_network EvalFileDefaultNameSmall diff --git a/src/Makefile b/src/Makefile index 7142b972745..042d9479cc8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -917,59 +917,9 @@ profileclean: @rm -f stockfish.res @rm -f ./-lstdc++.res -define fetch_network - @echo "Default net: $(nnuenet)" - @if [ "x$(curl_or_wget)" = "x" ]; then \ - echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \ - fi - @if [ "x$(shasum_command)" = "x" ]; then \ - echo "shasum / sha256sum not found, skipping net validation"; \ - elif test -f "$(nnuenet)"; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Removing invalid network"; rm -f $(nnuenet); \ - fi; \ - fi; - @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ - if test -f "$(nnuenet)"; then \ - echo "$(nnuenet) available : OK"; break; \ - else \ - if [ "x$(curl_or_wget)" != "x" ]; then \ - echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ - else \ - echo "No net found and download not possible"; exit 1;\ - fi; \ - fi; \ - if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Removing failed download"; rm -f $(nnuenet); \ - fi; \ - fi; \ - done - @if ! test -f "$(nnuenet)"; then \ - echo "Failed to download $(nnuenet)."; \ - fi; - @if [ "x$(shasum_command)" != "x" ]; then \ - if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ - echo "Network validated"; break; \ - fi; \ - fi; -endef - -# set up shell variables for the net stuff -define netvariables -$(eval nnuenet := $(shell grep $(1) evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) -$(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) -$(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) -$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) -$(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) -endef - # evaluation network (nnue) net: - $(call netvariables, EvalFileDefaultNameBig) - $(call fetch_network) - $(call netvariables, EvalFileDefaultNameSmall) - $(call fetch_network) + @$(SHELL) ../scripts/net.sh format: $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file From d7e3a708d456ff2793c2392c13d8d9cbea61aaba Mon Sep 17 00:00:00 2001 From: xu-shawn <50402888+xu-shawn@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:20:40 -0700 Subject: [PATCH 0731/1309] Remove ARCH=... from README.md closes https://github.com/official-stockfish/Stockfish/pull/5570 No functional change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52b123cbd25..25da319d5a2 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ descriptions. An example suitable for most Intel and AMD chips: ``` cd src -make -j profile-build ARCH=x86-64-avx2 +make -j profile-build ``` Detailed compilation instructions for all platforms can be found in our From a8cb002038bf314764a737077864a961c0e1d145 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 7 Sep 2024 18:46:09 +0300 Subject: [PATCH 0732/1309] Simplify ttmove reduction Remove condition that clamps reductions for tt move. Passed STC: https://tests.stockfishchess.org/tests/view/66d5f1239de3e7f9b33d14b0 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 91136 W: 23805 L: 23646 D: 43685 Ptnml(0-2): 334, 10328, 24066, 10525, 315 Passed LTC: https://tests.stockfishchess.org/tests/view/66d7c5889de3e7f9b33d1721 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 139242 W: 35130 L: 35030 D: 69082 Ptnml(0-2): 78, 15200, 38986, 15258, 99 closes https://github.com/official-stockfish/Stockfish/pull/5574 Bench: 1268715 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index aab5c743cc5..1ed849f2a34 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1148,10 +1148,9 @@ Value Search::Worker::search( if ((ss + 1)->cutoffCnt > 3) r += 1 + allNode; - // For first picked move (ttMove) reduce reduction, but never allow - // reduction to go below 0 (~3 Elo) + // For first picked move (ttMove) reduce reduction (~3 Elo) else if (move == ttData.move) - r = std::max(0, r - 2); + r -= 2; ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From effa2460710aef54465967796099916a5f0d13d3 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 7 Sep 2024 20:34:10 +0200 Subject: [PATCH 0733/1309] Use optional for the engine path - A small quality of file change is to change the type of engine path from a string to an optional string, skips the binary directory lookup, which is commonly disabled by people who create wasm builds or include stockfish as a library. closes https://github.com/official-stockfish/Stockfish/pull/5575 No functional change --- src/engine.cpp | 4 ++-- src/engine.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 81bb260bd88..b5cc3f832f5 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -47,8 +47,8 @@ namespace NN = Eval::NNUE; constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; -Engine::Engine(std::string path) : - binaryDirectory(CommandLine::get_binary_directory(path)), +Engine::Engine(std::optional path) : + binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), numaContext(NumaConfig::from_system()), states(new std::deque(1)), threads(), diff --git a/src/engine.h b/src/engine.h index f3c78398669..efab1c6af83 100644 --- a/src/engine.h +++ b/src/engine.h @@ -47,7 +47,7 @@ class Engine { using InfoFull = Search::InfoFull; using InfoIter = Search::InfoIteration; - Engine(std::string path = ""); + Engine(std::optional path = std::nullopt); // Cannot be movable due to components holding backreferences to fields Engine(const Engine&) = delete; From 2680c9c7992f6565e9a2f0acc52260af55e56b5a Mon Sep 17 00:00:00 2001 From: MinetaS Date: Fri, 6 Sep 2024 22:14:47 +0900 Subject: [PATCH 0734/1309] Small speedup in incremental accumulator updates Instead of updating at most two accumulators, update all accumluators during incremental updates. Tests have shown that this change yields a small speedup of at least 0.5%, and up to 1% with shorter TC. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 54368 W: 14179 L: 13842 D: 26347 Ptnml(0-2): 173, 6122, 14262, 6449, 178 https://tests.stockfishchess.org/tests/view/66db038a9de3e7f9b33d1ad9 Passed 5+0.05: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 55040 W: 14682 L: 14322 D: 26036 Ptnml(0-2): 303, 6364, 13856, 6664, 333 https://tests.stockfishchess.org/tests/view/66dbc325dc53972b68218ba7 Passed non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 57390 W: 14555 L: 14376 D: 28459 Ptnml(0-2): 37, 5876, 16683, 6069, 30 https://tests.stockfishchess.org/tests/view/66dbc30adc53972b68218ba5 closes https://github.com/official-stockfish/Stockfish/pull/5576 No functional change --- src/nnue/nnue_feature_transformer.h | 330 ++++++++++++---------------- src/position.cpp | 2 + src/position.h | 1 + 3 files changed, 140 insertions(+), 193 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 2f74dcae2ed..fa180678d89 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -453,11 +453,10 @@ class FeatureTransformer { private: template - [[nodiscard]] std::pair - try_find_computed_accumulator(const Position& pos) const { + 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(), *next = nullptr; + StateInfo* st = pos.state(); int gain = FeatureSet::refresh_cost(pos); while (st->previous && !(st->*accPtr).computed[Perspective]) { @@ -466,30 +465,17 @@ class FeatureTransformer { if (FeatureSet::requires_refresh(st, Perspective) || (gain -= FeatureSet::update_cost(st) + 1) < 0) break; - next = st; - st = st->previous; + st = st->previous; } - return {st, next}; + return st; } - // NOTE: The parameter states_to_update is an array of position states. - // All states must be sequential, that is states_to_update[i] must - // either be reachable by repeatedly applying ->previous from - // states_to_update[i+1], and computed_st must be reachable by - // repeatedly applying ->previous on states_to_update[0]. - template - void update_accumulator_incremental(const Position& pos, - StateInfo* computed_st, - StateInfo* states_to_update[N]) const { - static_assert(N > 0); - assert([&]() { - for (size_t i = 0; i < N; ++i) - { - if (states_to_update[i] == nullptr) - return false; - } - return true; - }()); + // It computes the accumulator of the next position, or updates the + // current position's accumulator if CurrentOnly is true. + 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 @@ -498,205 +484,186 @@ class FeatureTransformer { psqt_vec_t psqt[NumPsqtRegs]; #endif - // Update incrementally going back through states_to_update. - // Gather all features to be updated. 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. - FeatureSet::IndexList removed[N], added[N]; - - for (int i = N - 1; i >= 0; --i) - { - (states_to_update[i]->*accPtr).computed[Perspective] = true; - - const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + FeatureSet::IndexList removed, added; - for (StateInfo* st2 = states_to_update[i]; st2 != end_state; st2 = st2->previous) - FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, removed[i], - added[i]); - } + if constexpr (CurrentOnly) + for (StateInfo* st = pos.state(); st != computed; st = st->previous) + FeatureSet::append_changed_indices(ksq, st->dirtyPiece, removed, + added); + else + FeatureSet::append_changed_indices(ksq, computed->next->dirtyPiece, + removed, added); - StateInfo* st = computed_st; + StateInfo* next = CurrentOnly ? pos.state() : computed->next; + assert(!(next->*accPtr).computed[Perspective]); - // Now update the accumulators listed in states_to_update[], - // where the last element is a sentinel. #ifdef VECTOR - - if (N == 1 && (removed[0].size() == 1 || removed[0].size() == 2) && added[0].size() == 1) + if ((removed.size() == 1 || removed.size() == 2) && added.size() == 1) { - assert(states_to_update[0]); - auto accIn = - reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + reinterpret_cast(&(computed->*accPtr).accumulation[Perspective][0]); + auto accOut = reinterpret_cast(&(next->*accPtr).accumulation[Perspective][0]); - const IndexType offsetR0 = HalfDimensions * removed[0][0]; + const IndexType offsetR0 = HalfDimensions * removed[0]; auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; + const IndexType offsetA = HalfDimensions * added[0]; auto columnA = reinterpret_cast(&weights[offsetA]); - if (removed[0].size() == 1) + if (removed.size() == 1) { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + 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]); } else { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; + const IndexType offsetR1 = HalfDimensions * removed[1]; auto columnR1 = reinterpret_cast(&weights[offsetR1]); - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); + for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA[i]), + vec_add_16(columnR0[i], columnR1[i])); } - auto accPsqtIn = - reinterpret_cast(&(st->*accPtr).psqtAccumulation[Perspective][0]); - auto accPsqtOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).psqtAccumulation[Perspective][0]); + auto accPsqtIn = reinterpret_cast( + &(computed->*accPtr).psqtAccumulation[Perspective][0]); + auto accPsqtOut = + reinterpret_cast(&(next->*accPtr).psqtAccumulation[Perspective][0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0]; + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - const IndexType offsetPsqtA = PSQTBuckets * added[0][0]; + const IndexType offsetPsqtA = PSQTBuckets * added[0]; auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); - if (removed[0].size() == 1) + if (removed.size() == 1) { - for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); - ++k) - accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[k], columnPsqtR0[k]), - columnPsqtA[k]); + 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]); } else { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1]; + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); - for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); - ++k) - accPsqtOut[k] = - vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]), - vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k])); + 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_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); } } else { - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + for (IndexType i = 0; i < HalfDimensions / TileHeight; ++i) { // Load accumulator auto accTileIn = reinterpret_cast( - &(st->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); + &(computed->*accPtr).accumulation[Perspective][i * TileHeight]); + for (IndexType j = 0; j < NumRegs; ++j) + acc[j] = vec_load(&accTileIn[j]); + + // Difference calculation for the deactivated features + for (const auto index : removed) + { + const IndexType offset = HalfDimensions * index + i * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumRegs; ++j) + acc[j] = vec_sub_16(acc[j], column[j]); + } - for (IndexType i = 0; i < N; ++i) + // Difference calculation for the activated features + for (const auto index : added) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - auto accTileOut = reinterpret_cast( - &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); + const IndexType offset = HalfDimensions * index + i * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumRegs; ++j) + acc[j] = vec_add_16(acc[j], column[j]); } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &(next->*accPtr).accumulation[Perspective][i * TileHeight]); + for (IndexType j = 0; j < NumRegs; ++j) + vec_store(&accTileOut[j], acc[j]); } - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + for (IndexType i = 0; i < PSQTBuckets / PsqtTileHeight; ++i) { // Load accumulator auto accTilePsqtIn = reinterpret_cast( - &(st->*accPtr).psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); + &(computed->*accPtr).psqtAccumulation[Perspective][i * PsqtTileHeight]); + for (std::size_t j = 0; j < 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 * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t j = 0; j < NumPsqtRegs; ++j) + psqt[j] = vec_sub_psqt_32(psqt[j], columnPsqt[j]); + } - for (IndexType i = 0; i < N; ++i) + // Difference calculation for the activated features + for (const auto index : added) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - // Store accumulator - auto accTilePsqtOut = reinterpret_cast( - &(states_to_update[i]->*accPtr) - .psqtAccumulation[Perspective][j * PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqtOut[k], psqt[k]); + const IndexType offset = PSQTBuckets * index + i * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t j = 0; j < NumPsqtRegs; ++j) + psqt[j] = vec_add_psqt_32(psqt[j], columnPsqt[j]); } + + // Store accumulator + auto accTilePsqtOut = reinterpret_cast( + &(next->*accPtr).psqtAccumulation[Perspective][i * PsqtTileHeight]); + for (std::size_t j = 0; j < NumPsqtRegs; ++j) + vec_store_psqt(&accTilePsqtOut[j], psqt[j]); } } #else - for (IndexType i = 0; i < N; ++i) + 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) { - std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], - (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = - (st->*accPtr).psqtAccumulation[Perspective][k]; - - st = states_to_update[i]; - - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; + const IndexType offset = HalfDimensions * index; + for (IndexType i = 0; i < HalfDimensions; ++i) + (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; - for (std::size_t k = 0; k < PSQTBuckets; ++k) - (st->*accPtr).psqtAccumulation[Perspective][k] -= - psqtWeights[index * PSQTBuckets + k]; - } + 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[i]) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; + // 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 k = 0; k < PSQTBuckets; ++k) - (st->*accPtr).psqtAccumulation[Perspective][k] += - psqtWeights[index * PSQTBuckets + k]; - } + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (next->*accPtr).psqtAccumulation[Perspective][i] += + psqtWeights[index * PSQTBuckets + i]; } #endif + + (next->*accPtr).computed[Perspective] = true; + + if (!CurrentOnly && next != pos.state()) + update_accumulator_incremental(pos, next); } template @@ -871,14 +838,10 @@ class FeatureTransformer { if ((pos.state()->*accPtr).computed[Perspective]) return; - auto [oldest_st, _] = try_find_computed_accumulator(pos); + StateInfo* oldest = try_find_computed_accumulator(pos); - if ((oldest_st->*accPtr).computed[Perspective]) - { - // Only update current position accumulator to minimize work - StateInfo* states_to_update[1] = {pos.state()}; - update_accumulator_incremental(pos, oldest_st, states_to_update); - } + if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) + update_accumulator_incremental(pos, oldest); else update_accumulator_refresh_cache(pos, cache); } @@ -887,31 +850,12 @@ class FeatureTransformer { void update_accumulator(const Position& pos, AccumulatorCaches::Cache* cache) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos); - - if ((oldest_st->*accPtr).computed[Perspective]) - { - if (next == nullptr) - return; - - // Now update the accumulators listed in states_to_update[], where - // the last element is a sentinel. Currently we update two accumulators: - // 1. for the current position - // 2. the next accumulator after the computed one - // The heuristic may change in the future. - if (next == pos.state()) - { - StateInfo* states_to_update[1] = {next}; - - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - else - { - StateInfo* states_to_update[2] = {next, pos.state()}; + StateInfo* oldest = try_find_computed_accumulator(pos); - update_accumulator_incremental(pos, oldest_st, states_to_update); - } - } + 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); } diff --git a/src/position.cpp b/src/position.cpp index d374b1c070a..df95ffef380 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -671,6 +671,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // our state pointer to point to the new (ready to be updated) state. std::memcpy(&newSt, st, offsetof(StateInfo, key)); newSt.previous = st; + st->next = &newSt; st = &newSt; // Increment ply counters. In particular, rule50 will be reset to zero later on @@ -963,6 +964,7 @@ void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { std::memcpy(&newSt, st, offsetof(StateInfo, accumulatorBig)); newSt.previous = st; + st->next = &newSt; st = &newSt; st->dirtyPiece.dirty_num = 0; diff --git a/src/position.h b/src/position.h index 064dd5fa918..6cac1731951 100644 --- a/src/position.h +++ b/src/position.h @@ -53,6 +53,7 @@ struct StateInfo { Key key; Bitboard checkersBB; StateInfo* previous; + StateInfo* next; Bitboard blockersForKing[COLOR_NB]; Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; From 6de25872361de9515bdb25bf1d0391311d074012 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Sat, 31 Aug 2024 16:35:17 +0900 Subject: [PATCH 0735/1309] Remove statScore condition in NMP Eliminate the condition that is nearly 100% likelihood of being true. Passed non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 208832 W: 54053 L: 54022 D: 100757 Ptnml(0-2): 753, 24987, 52901, 25026, 749 https://tests.stockfishchess.org/tests/view/66cddb50bf8c9d8780fdabaf Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 154344 W: 39132 L: 39047 D: 76165 Ptnml(0-2): 115, 17231, 42403, 17300, 123 https://tests.stockfishchess.org/tests/view/66cfafe39de3e7f9b33d1050 closes https://github.com/official-stockfish/Stockfish/pull/5558 Bench: 1393697 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1ed849f2a34..d26f43dbc44 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -773,10 +773,9 @@ Value Search::Worker::search( return beta + (eval - beta) / 3; // Step 9. Null move search with verification search (~35 Elo) - if (cutNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 14389 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove - && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly - && beta > VALUE_TB_LOSS_IN_MAX_PLY) + if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta + && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove && pos.non_pawn_material(us) + && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); From d8e49cdbdd8076d85b137510ee5637e36db1074f Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Mon, 9 Sep 2024 22:32:00 +0200 Subject: [PATCH 0736/1309] Remove the `moveCount` increase in the LMR condition. Passed non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 87104 W: 22630 L: 22464 D: 42010 Ptnml(0-2): 316, 10295, 22132, 10525, 284 https://tests.stockfishchess.org/tests/view/66dccd00dc53972b68218c60 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94050 W: 23869 L: 23722 D: 46459 Ptnml(0-2): 49, 10400, 25985, 10537, 54 https://tests.stockfishchess.org/tests/view/66dd69c7dc53972b68218ca5 closes https://github.com/official-stockfish/Stockfish/pull/5582 Bench: 1281840 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d26f43dbc44..ac0b9c6d9dc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1159,7 +1159,7 @@ Value Search::Worker::search( r -= ss->statScore / 10898; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) - if (depth >= 2 && moveCount > 1 + (rootNode && depth < 10)) + if (depth >= 2 && moveCount > 1) { // In general we want to cap the LMR depth search at newDepth, but when // reduction is negative, we allow this move a limited search extension From f677aee28baedcab4d3110d0a5c414621ed805c4 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 11 Sep 2024 05:14:01 +0900 Subject: [PATCH 0737/1309] Fix net downloading script The recent commit introduced a bug in the net downloading script that the file is not downloaded correctly and the content is redirected to stdout. closes https://github.com/official-stockfish/Stockfish/pull/5585 No functional change --- scripts/net.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/net.sh b/scripts/net.sh index 168fbad66b0..0bc57a19e30 100755 --- a/scripts/net.sh +++ b/scripts/net.sh @@ -1,7 +1,7 @@ #!/bin/sh -wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -q") || \ - (command -v curl > /dev/null 2>&1 && echo "curl -L -s -k")) +wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \ + (command -v curl > /dev/null 2>&1 && echo "curl -skL")) if [ -z "$wget_or_curl" ]; then >&2 printf "%s\n" "Neither wget or curl is installed." \ @@ -51,7 +51,7 @@ fetch_network() { "https://tests.stockfishchess.org/api/nn/$_filename" \ "https://github.com/official-stockfish/networks/raw/master/$_filename"; do echo "Downloading from $url ..." - if $wget_or_curl "$url"; then + if $wget_or_curl "$url" > "$_filename"; then if validate_network "$_filename"; then echo "Successfully validated $_filename" else From a06e7004c1a01fb56f5db90295884eaf3b7cd0f6 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 10 Sep 2024 18:36:54 +0200 Subject: [PATCH 0738/1309] Port instrumented testing to python Since an unknown amount of time the instrumented CI has been a bit flawed, explained here https://github.com/official-stockfish/Stockfish/issues/5185. It also experiences random timeout issues where restarting the workflow fixes it or very long run times (more than other workflows) and is not very portable. The intention of this commit is to port the instrumented.sh to python which also works on other operating systems. It should also be relatively easy for beginners to add new tests to assert stockfish's output and to run it. From the source directory the following command can be run. `python3 ../tests/instrumented.py --none ./stockfish` A test runner will go over the test suites and run the test cases. All instrumented tests should have been ported over. The required python version for this is should be 3.7 (untested) + the requests package, testing.py includes some infrastructure code which setups the testing. fixes https://github.com/official-stockfish/Stockfish/issues/5185 closes https://github.com/official-stockfish/Stockfish/pull/5583 No functional change --- .github/workflows/sanitizers.yml | 2 +- .gitignore | 5 + tests/instrumented.py | 520 +++++++++++++++++++++++++++++++ tests/instrumented.sh | 301 ------------------ tests/testing.py | 378 ++++++++++++++++++++++ 5 files changed, 904 insertions(+), 302 deletions(-) create mode 100644 tests/instrumented.py delete mode 100755 tests/instrumented.sh create mode 100644 tests/testing.py diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 55459292107..946a81cec4a 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -75,4 +75,4 @@ jobs: export CXXFLAGS="-O1 -fno-inline" make clean make -j4 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null - ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }} + python3 ../tests/instrumented.py --${{ matrix.sanitizers.instrumented_option }} ./stockfish diff --git a/.gitignore b/.gitignore index 8981efcaf13..2fc80d48731 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ src/-lstdc++.res # Neural network for the NNUE evaluation **/*.nnue +# Files generated by the instrumented tests +tsan.supp +__pycache__/ +tests/syzygy +tests/bench_tmp.epd \ No newline at end of file diff --git a/tests/instrumented.py b/tests/instrumented.py new file mode 100644 index 00000000000..a3747d4e97a --- /dev/null +++ b/tests/instrumented.py @@ -0,0 +1,520 @@ +import argparse +import re +import sys +import subprocess +import pathlib +import os + +from testing import ( + EPD, + TSAN, + Stockfish as Engine, + MiniTestFramework, + OrderedClassMembers, + Valgrind, + Syzygy, +) + +PATH = pathlib.Path(__file__).parent.resolve() +CWD = os.getcwd() + + +def get_prefix(): + if args.valgrind: + return Valgrind.get_valgrind_command() + if args.valgrind_thread: + return Valgrind.get_valgrind_thread_command() + + return [] + + +def get_threads(): + if args.valgrind_thread or args.sanitizer_thread: + return 2 + return 1 + + +def get_path(): + return os.path.abspath(os.path.join(CWD, args.stockfish_path)) + + +def postfix_check(output): + if args.sanitizer_undefined: + for idx, line in enumerate(output): + if "runtime error:" in line: + # print next possible 50 lines + for i in range(50): + debug_idx = idx + i + if debug_idx < len(output): + print(output[debug_idx]) + return False + + if args.sanitizer_thread: + for idx, line in enumerate(output): + if "WARNING: ThreadSanitizer:" in line: + # print next possible 50 lines + for i in range(50): + debug_idx = idx + i + if debug_idx < len(output): + print(output[debug_idx]) + return False + + return True + + +def Stockfish(*args, **kwargs): + return Engine(get_prefix(), get_path(), *args, **kwargs) + + +class TestCLI(metaclass=OrderedClassMembers): + + def beforeAll(self): + pass + + def afterAll(self): + pass + + def beforeEach(self): + self.stockfish = None + + def afterEach(self): + assert postfix_check(self.stockfish.get_output()) == True + self.stockfish.clear_output() + + def test_eval(self): + self.stockfish = Stockfish("eval".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_go_nodes_1000(self): + self.stockfish = Stockfish("go nodes 1000".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_go_depth_10(self): + self.stockfish = Stockfish("go depth 10".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_go_perft_4(self): + self.stockfish = Stockfish("go perft 4".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_go_movetime_1000(self): + self.stockfish = Stockfish("go movetime 1000".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_go_wtime_8000_btime_8000_winc_500_binc_500(self): + self.stockfish = Stockfish( + "go wtime 8000 btime 8000 winc 500 binc 500".split(" "), + True, + ) + assert self.stockfish.process.returncode == 0 + + def test_go_wtime_1000_btime_1000_winc_0_binc_0(self): + self.stockfish = Stockfish( + "go wtime 1000 btime 1000 winc 0 binc 0".split(" "), + True, + ) + assert self.stockfish.process.returncode == 0 + + def test_go_wtime_1000_btime_1000_winc_0_binc_0_movestogo_5(self): + self.stockfish = Stockfish( + "go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5".split(" "), + True, + ) + assert self.stockfish.process.returncode == 0 + + def test_go_movetime_200(self): + self.stockfish = Stockfish("go movetime 200".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_go_nodes_20000_searchmoves_e2e4_d2d4(self): + self.stockfish = Stockfish( + "go nodes 20000 searchmoves e2e4 d2d4".split(" "), True + ) + assert self.stockfish.process.returncode == 0 + + def test_bench_128_threads_8_default_depth(self): + self.stockfish = Stockfish( + f"bench 128 {get_threads()} 8 default depth".split(" "), + True, + ) + assert self.stockfish.process.returncode == 0 + + def test_bench_128_threads_3_bench_tmp_epd_depth(self): + self.stockfish = Stockfish( + f"bench 128 {get_threads()} 3 {os.path.join(PATH,'bench_tmp.epd')} depth".split( + " " + ), + True, + ) + assert self.stockfish.process.returncode == 0 + + def test_d(self): + self.stockfish = Stockfish("d".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_compiler(self): + self.stockfish = Stockfish("compiler".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_license(self): + self.stockfish = Stockfish("license".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_uci(self): + self.stockfish = Stockfish("uci".split(" "), True) + assert self.stockfish.process.returncode == 0 + + def test_export_net_verify_nnue(self): + current_path = os.path.abspath(os.getcwd()) + self.stockfish = Stockfish( + f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + ) + assert self.stockfish.process.returncode == 0 + + # verify the generated net equals the base net + + def test_network_equals_base(self): + self.stockfish = Stockfish( + ["uci"], + True, + ) + + output = self.stockfish.process.stdout + + # find line + for line in output.split("\n"): + if "option name EvalFile type string default" in line: + network = line.split(" ")[-1] + break + + # find network file in src dir + network = os.path.join(PATH.parent.resolve(), "src", network) + + if not os.path.exists(network): + print( + f"Network file {network} not found, please download the network file over the make command." + ) + assert False + + diff = subprocess.run(["diff", network, f"verify.nnue"]) + + assert diff.returncode == 0 + + +class TestInteractive(metaclass=OrderedClassMembers): + def beforeAll(self): + self.stockfish = Stockfish() + + def afterAll(self): + self.stockfish.quit() + assert self.stockfish.close() == 0 + + def afterEach(self): + assert postfix_check(self.stockfish.get_output()) == True + self.stockfish.clear_output() + + def test_startup_output(self): + self.stockfish.starts_with("Stockfish") + + def test_uci_command(self): + self.stockfish.send_command("uci") + self.stockfish.equals("uciok") + + def test_set_threads_option(self): + self.stockfish.send_command(f"setoption name Threads value {get_threads()}") + + def test_ucinewgame_and_startpos_nodes_1000(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position startpos") + self.stockfish.send_command("go nodes 1000") + self.stockfish.starts_with("bestmove") + + def test_ucinewgame_and_startpos_moves(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position startpos moves e2e4 e7e6") + self.stockfish.send_command("go nodes 1000") + self.stockfish.starts_with("bestmove") + + def test_fen_position_1(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1") + self.stockfish.send_command("go nodes 1000") + self.stockfish.starts_with("bestmove") + + def test_fen_position_2_flip(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1") + self.stockfish.send_command("flip") + self.stockfish.send_command("go nodes 1000") + self.stockfish.starts_with("bestmove") + + def test_depth_5_with_callback(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position startpos") + self.stockfish.send_command("go depth 5") + + def callback(output): + regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + if output.startswith("info depth") and not re.match(regex, output): + assert False + if output.startswith("bestmove"): + return True + return False + + self.stockfish.check_output(callback) + + def test_ucinewgame_and_go_depth_9(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("setoption name UCI_ShowWDL value true") + self.stockfish.send_command("position startpos") + self.stockfish.send_command("go depth 9") + + depth = 1 + + def callback(output): + nonlocal depth + + regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + + if output.startswith("info depth"): + if not re.match(regex, output): + assert False + depth += 1 + + if output.startswith("bestmove"): + assert depth == 10 + return True + + return False + + self.stockfish.check_output(callback) + + def test_clear_hash(self): + self.stockfish.send_command("setoption name Clear Hash") + + def test_fen_position_mate_1(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6" + ) + self.stockfish.send_command("go depth 18") + + self.stockfish.expect("* score mate 1 * pv d5e6") + self.stockfish.equals("bestmove d5e6") + + def test_fen_position_mate_minus_1(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 2brrb2/8/p7/Q7/1p1kpPp1/1P1pN1K1/3P4/8 b - -" + ) + self.stockfish.send_command("go depth 18") + self.stockfish.expect("* score mate -1 *") + self.stockfish.starts_with("bestmove") + + def test_fen_position_fixed_node(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 5K2/8/2P1P1Pk/6pP/3p2P1/1P6/3P4/8 w - - 0 1" + ) + self.stockfish.send_command("go nodes 500000") + self.stockfish.starts_with("bestmove") + + def test_fen_position_with_mate_go_depth(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -" + ) + self.stockfish.send_command("go depth 18 searchmoves c6d7") + self.stockfish.expect("* score mate 2 * pv c6d7 * f7f5") + + self.stockfish.starts_with("bestmove") + + def test_fen_position_with_mate_go_mate(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -" + ) + self.stockfish.send_command("go mate 2 searchmoves c6d7") + self.stockfish.expect("* score mate 2 * pv c6d7 *") + + self.stockfish.starts_with("bestmove") + + def test_fen_position_with_mate_go_nodes(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -" + ) + self.stockfish.send_command("go nodes 500000 searchmoves c6d7") + self.stockfish.expect("* score mate 2 * pv c6d7 * f7f5") + + self.stockfish.starts_with("bestmove") + + def test_fen_position_depth_27(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 1NR2B2/5p2/5p2/1p1kpp2/1P2rp2/2P1pB2/2P1P1K1/8 b - -" + ) + self.stockfish.send_command("go depth 27") + self.stockfish.contains("score mate -2") + + self.stockfish.starts_with("bestmove") + + def test_fen_position_with_mate_go_depth_and_promotion(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7 f2f1q" + ) + self.stockfish.send_command("go depth 18") + self.stockfish.expect("* score mate 1 * pv f7f5") + self.stockfish.starts_with("bestmove f7f5") + + def test_fen_position_with_mate_go_depth_and_searchmoves(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -" + ) + self.stockfish.send_command("go depth 18 searchmoves c6d7") + self.stockfish.expect("* score mate 2 * pv c6d7 * f7f5") + + self.stockfish.starts_with("bestmove c6d7") + + def test_fen_position_with_moves_with_mate_go_depth_and_searchmoves(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command( + "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7" + ) + self.stockfish.send_command("go depth 18 searchmoves e3e2") + self.stockfish.expect("* score mate -1 * pv e3e2 f7f5") + self.stockfish.starts_with("bestmove e3e2") + + def test_verify_nnue_network(self): + current_path = os.path.abspath(os.getcwd()) + Stockfish( + f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + ) + + self.stockfish.send_command("setoption name EvalFile value verify.nnue") + self.stockfish.send_command("position startpos") + self.stockfish.send_command("go depth 5") + self.stockfish.starts_with("bestmove") + + def test_multipv_setting(self): + self.stockfish.send_command("setoption name MultiPV value 4") + self.stockfish.send_command("position startpos") + self.stockfish.send_command("go depth 5") + self.stockfish.starts_with("bestmove") + + def test_fen_position_with_skill_level(self): + self.stockfish.send_command("setoption name Skill Level value 10") + self.stockfish.send_command("position startpos") + self.stockfish.send_command("go depth 5") + self.stockfish.starts_with("bestmove") + + self.stockfish.send_command("setoption name Skill Level value 20") + + +class TestSyzygy(metaclass=OrderedClassMembers): + def beforeAll(self): + self.stockfish = Stockfish() + + def afterAll(self): + self.stockfish.quit() + assert self.stockfish.close() == 0 + + def afterEach(self): + assert postfix_check(self.stockfish.get_output()) == True + self.stockfish.clear_output() + + def test_syzygy_setup(self): + self.stockfish.starts_with("Stockfish") + self.stockfish.send_command("uci") + self.stockfish.send_command( + f"setoption name SyzygyPath value {os.path.join(PATH, 'syzygy')}" + ) + self.stockfish.expect( + "info string Found 35 WDL and 35 DTZ tablebase files (up to 4-man)." + ) + + def test_syzygy_bench(self): + self.stockfish.send_command("bench 128 1 8 default depth") + self.stockfish.expect("Nodes searched :*") + + def test_syzygy_position(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1") + self.stockfish.send_command("go depth 5") + + def check_output(output): + if "score cp 20000" in output or "score mate" in output: + return True + + self.stockfish.check_output(check_output) + self.stockfish.expect("bestmove *") + + def test_syzygy_position_2(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1") + self.stockfish.send_command("go depth 5") + + def check_output(output): + if "score cp 20000" in output or "score mate" in output: + return True + + self.stockfish.check_output(check_output) + self.stockfish.expect("bestmove *") + + def test_syzygy_position_3(self): + self.stockfish.send_command("ucinewgame") + self.stockfish.send_command("position fen 8/1P6/2B5/8/4K3/8/6k1/8 b - - 0 1") + self.stockfish.send_command("go depth 5") + + def check_output(output): + if "score cp -20000" in output or "score mate" in output: + return True + + self.stockfish.check_output(check_output) + self.stockfish.expect("bestmove *") + + +def parse_args(): + parser = argparse.ArgumentParser(description="Run Stockfish with testing options") + parser.add_argument("--valgrind", action="store_true", help="Run valgrind testing") + parser.add_argument( + "--valgrind-thread", action="store_true", help="Run valgrind-thread testing" + ) + parser.add_argument( + "--sanitizer-undefined", + action="store_true", + help="Run sanitizer-undefined testing", + ) + parser.add_argument( + "--sanitizer-thread", action="store_true", help="Run sanitizer-thread testing" + ) + + parser.add_argument( + "--none", action="store_true", help="Run without any testing options" + ) + parser.add_argument("stockfish_path", type=str, help="Path to Stockfish binary") + + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + + EPD.create_bench_epd() + TSAN.set_tsan_option() + Syzygy.download_syzygy() + + framework = MiniTestFramework() + + # Each test suite will be ran inside a temporary directory + framework.run([TestCLI, TestInteractive, TestSyzygy]) + + EPD.delete_bench_epd() + TSAN.unset_tsan_option() + + if framework.has_failed(): + sys.exit(1) + + sys.exit(0) diff --git a/tests/instrumented.sh b/tests/instrumented.sh deleted file mode 100755 index 5fc6ca9a974..00000000000 --- a/tests/instrumented.sh +++ /dev/null @@ -1,301 +0,0 @@ -#!/bin/bash -# check for errors under Valgrind or sanitizers. - -error() -{ - echo "instrumented testing failed on line $1" - exit 1 -} -trap 'error ${LINENO}' ERR - -# define suitable post and prefixes for testing options -case $1 in - --valgrind) - echo "valgrind testing started" - prefix='' - exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full' - postfix='' - threads="1" - ;; - --valgrind-thread) - echo "valgrind-thread testing started" - prefix='' - exeprefix='valgrind --fair-sched=try --error-exitcode=42' - postfix='' - threads="2" - ;; - --sanitizer-undefined) - echo "sanitizer-undefined testing started" - prefix='!' - exeprefix='' - postfix='2>&1 | grep -A50 "runtime error:"' - threads="1" - ;; - --sanitizer-thread) - echo "sanitizer-thread testing started" - prefix='!' - exeprefix='' - postfix='2>&1 | grep -A50 "WARNING: ThreadSanitizer:"' - threads="2" - -cat << EOF > tsan.supp -race:Stockfish::TTEntry::read -race:Stockfish::TTEntry::save - -race:Stockfish::TranspositionTable::probe -race:Stockfish::TranspositionTable::hashfull - -EOF - - export TSAN_OPTIONS="suppressions=./tsan.supp" - - ;; - *) - echo "unknown testing started" - prefix='' - exeprefix='' - postfix='' - threads="1" - ;; -esac - -cat << EOF > bench_tmp.epd -Rn6/1rbq1bk1/2p2n1p/2Bp1p2/3Pp1pP/1N2P1P1/2Q1NPB1/6K1 w - - 2 26 -rnbqkb1r/ppp1pp2/5n1p/3p2p1/P2PP3/5P2/1PP3PP/RNBQKBNR w KQkq - 0 3 -3qnrk1/4bp1p/1p2p1pP/p2bN3/1P1P1B2/P2BQ3/5PP1/4R1K1 w - - 9 28 -r4rk1/1b2ppbp/pq4pn/2pp1PB1/1p2P3/1P1P1NN1/1PP3PP/R2Q1RK1 w - - 0 13 -EOF - -# simple command line testing -for args in "eval" \ - "go nodes 1000" \ - "go depth 10" \ - "go perft 4" \ - "go movetime 1000" \ - "go wtime 8000 btime 8000 winc 500 binc 500" \ - "go wtime 1000 btime 1000 winc 0 binc 0" \ - "go wtime 1000 btime 1000 winc 0 binc 0" \ - "go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5" \ - "go movetime 200" \ - "go nodes 20000 searchmoves e2e4 d2d4" \ - "bench 128 $threads 8 default depth" \ - "bench 128 $threads 3 bench_tmp.epd depth" \ - "export_net verify.nnue" \ - "d" \ - "compiler" \ - "license" \ - "uci" -do - - echo "$prefix $exeprefix ./stockfish $args $postfix" - eval "$prefix $exeprefix ./stockfish $args $postfix" - -done - -# verify the generated net equals the base net -network=`./stockfish uci | grep 'option name EvalFile type string default' | awk '{print $NF}'` -echo "Comparing $network to the written verify.nnue" -diff $network verify.nnue - -# more general testing, following an uci protocol exchange -cat << EOF > game.exp - set timeout 240 - # to correctly catch eof we need the following line - # expect_before timeout { exit 2 } eof { exit 3 } - expect_before timeout { exit 2 } - - spawn $exeprefix ./stockfish - expect "Stockfish" - - send "uci\n" - expect "uciok" - - # send "setoption name Debug Log File value debug.log\n" - send "setoption name Threads value $threads\n" - - send "ucinewgame\n" - send "position startpos\n" - send "go nodes 1000\n" - expect "bestmove" - - send "ucinewgame\n" - send "position startpos moves e2e4 e7e6\n" - send "go nodes 1000\n" - expect "bestmove" - - send "ucinewgame\n" - send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" - send "go depth 10\n" - expect "bestmove" - - send "ucinewgame\n" - send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" - send "flip\n" - send "go depth 10\n" - expect "bestmove" - - send "ucinewgame\n" - send "position startpos\n" - send "go depth 5\n" - expect -re {info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect "bestmove" - - send "ucinewgame\n" - send "setoption name UCI_ShowWDL value true\n" - send "position startpos\n" - send "go depth 9\n" - expect -re {info depth 1 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 2 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 3 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 4 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 5 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 6 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 7 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 8 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect -re {info depth 9 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv} - expect "bestmove" - - send "setoption name Clear Hash\n" - - send "ucinewgame\n" - send "position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6\n" - send "go depth 18\n" - expect "score mate 1" - expect "pv d5e6" - expect "bestmove d5e6" - - send "ucinewgame\n" - send "position fen 2brrb2/8/p7/Q7/1p1kpPp1/1P1pN1K1/3P4/8 b - -\n" - send "go depth 18\n" - expect "score mate -1" - expect "bestmove" - - send "ucinewgame\n" - send "position fen 7K/P1p1p1p1/2P1P1Pk/6pP/3p2P1/1P6/3P4/8 w - - 0 1\n" - send "go nodes 500000\n" - expect "bestmove" - - send "ucinewgame\n" - send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" - send "go depth 18 searchmoves c6d7\n" - expect "score mate 2 * pv c6d7 * f7f5" - expect "bestmove c6d7" - - send "ucinewgame\n" - send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" - send "go mate 2 searchmoves c6d7\n" - expect "score mate 2 * pv c6d7" - expect "bestmove c6d7" - - send "ucinewgame\n" - send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" - send "go nodes 500000 searchmoves c6d7\n" - expect "score mate 2 * pv c6d7 * f7f5" - expect "bestmove c6d7" - - send "ucinewgame\n" - send "position fen 1NR2B2/5p2/5p2/1p1kpp2/1P2rp2/2P1pB2/2P1P1K1/8 b - - \n" - send "go depth 27\n" - expect "score mate -2" - expect "pv d5e6 c8d8" - expect "bestmove d5e6" - - send "ucinewgame\n" - send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7 f2f1q\n" - send "go depth 18\n" - expect "score mate 1 * pv f7f5" - expect "bestmove f7f5" - - send "ucinewgame\n" - send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n" - send "go depth 18 searchmoves c6d7\n" - expect "score mate 2 * pv c6d7 * f7f5" - expect "bestmove c6d7" - - send "ucinewgame\n" - send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7\n" - send "go depth 18 searchmoves e3e2\n" - expect "score mate -1 * pv e3e2 f7f5" - expect "bestmove e3e2" - - send "setoption name EvalFile value verify.nnue\n" - send "position startpos\n" - send "go depth 5\n" - expect "bestmove" - - send "setoption name MultiPV value 4\n" - send "position startpos\n" - send "go depth 5\n" - expect "bestmove" - - send "setoption name Skill Level value 10\n" - send "position startpos\n" - send "go depth 5\n" - expect "bestmove" - send "setoption name Skill Level value 20\n" - - send "quit\n" - expect eof - - # return error code of the spawned program, useful for Valgrind - lassign [wait] pid spawnid os_error_flag value - exit \$value -EOF - -#download TB as needed -if [ ! -d ../tests/syzygy ]; then - curl -sL https://api.github.com/repos/niklasf/python-chess/tarball/9b9aa13f9f36d08aadfabff872882f4ab1494e95 | tar -xzf - - mv niklasf-python-chess-9b9aa13 ../tests/syzygy -fi - -cat << EOF > syzygy.exp - set timeout 240 - # to correctly catch eof we need the following line - # expect_before timeout { exit 2 } eof { exit 3 } - expect_before timeout { exit 2 } - spawn $exeprefix ./stockfish - expect "Stockfish" - send "uci\n" - send "setoption name SyzygyPath value ../tests/syzygy/\n" - expect "info string Found 35 WDL and 35 DTZ tablebase files (up to 4-man)." - send "bench 128 1 8 default depth\n" - expect "Nodes searched :" - send "ucinewgame\n" - send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n" - send "go depth 5\n" - expect -re {score cp 20000|score mate} - expect "bestmove" - send "ucinewgame\n" - send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n" - send "go depth 5\n" - expect -re {score cp 20000|score mate} - expect "bestmove" - send "ucinewgame\n" - send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 b - - 0 1\n" - send "go depth 5\n" - expect -re {score cp -20000|score mate} - expect "bestmove" - send "quit\n" - expect eof - - # return error code of the spawned program, useful for Valgrind - lassign [wait] pid spawnid os_error_flag value - exit \$value -EOF - -for exp in game.exp syzygy.exp -do - - echo "======== $exp ==============" - cat $exp - echo "============================" - echo "$prefix expect $exp $postfix" - eval "$prefix expect $exp $postfix" - - rm $exp - -done - -rm -f tsan.supp bench_tmp.epd - -echo "instrumented testing OK" diff --git a/tests/testing.py b/tests/testing.py new file mode 100644 index 00000000000..d51ca89ac92 --- /dev/null +++ b/tests/testing.py @@ -0,0 +1,378 @@ +import subprocess +from typing import List +import os +import collections +import time +import sys +import traceback +import fnmatch +from functools import wraps +from contextlib import redirect_stdout +import io +import tarfile +import pathlib +import concurrent.futures +import tempfile +import shutil +import requests + +CYAN_COLOR = "\033[36m" +GRAY_COLOR = "\033[2m" +RED_COLOR = "\033[31m" +GREEN_COLOR = "\033[32m" +RESET_COLOR = "\033[0m" +WHITE_BOLD = "\033[1m" + +MAX_TIMEOUT = 60 * 5 + +PATH = pathlib.Path(__file__).parent.resolve() + + +class Valgrind: + @staticmethod + def get_valgrind_command(): + return [ + "valgrind", + "--error-exitcode=42", + "--errors-for-leak-kinds=all", + "--leak-check=full", + ] + + @staticmethod + def get_valgrind_thread_command(): + return ["valgrind", "--error-exitcode=42", "--fair-sched=try"] + + +class TSAN: + @staticmethod + def set_tsan_option(): + with open(f"tsan.supp", "w") as f: + f.write( + """ +race:Stockfish::TTEntry::read +race:Stockfish::TTEntry::save +race:Stockfish::TranspositionTable::probe +race:Stockfish::TranspositionTable::hashfull +""" + ) + + os.environ["TSAN_OPTIONS"] = "suppressions=./tsan.supp" + + @staticmethod + def unset_tsan_option(): + os.environ.pop("TSAN_OPTIONS", None) + os.remove(f"tsan.supp") + + +class EPD: + @staticmethod + def create_bench_epd(): + with open(f"{os.path.join(PATH,'bench_tmp.epd')}", "w") as f: + f.write( + """ +Rn6/1rbq1bk1/2p2n1p/2Bp1p2/3Pp1pP/1N2P1P1/2Q1NPB1/6K1 w - - 2 26 +rnbqkb1r/ppp1pp2/5n1p/3p2p1/P2PP3/5P2/1PP3PP/RNBQKBNR w KQkq - 0 3 +3qnrk1/4bp1p/1p2p1pP/p2bN3/1P1P1B2/P2BQ3/5PP1/4R1K1 w - - 9 28 +r4rk1/1b2ppbp/pq4pn/2pp1PB1/1p2P3/1P1P1NN1/1PP3PP/R2Q1RK1 w - - 0 13 +""" + ) + + @staticmethod + def delete_bench_epd(): + os.remove(f"{os.path.join(PATH,'bench_tmp.epd')}") + + +class Syzygy: + @staticmethod + def get_syzygy_path(): + return os.path.abspath("syzygy") + + @staticmethod + def download_syzygy(): + if not os.path.isdir(os.path.join(PATH, "syzygy")): + url = "https://api.github.com/repos/niklasf/python-chess/tarball/9b9aa13f9f36d08aadfabff872882f4ab1494e95" + file = "niklasf-python-chess-9b9aa13" + + with tempfile.TemporaryDirectory() as tmpdirname: + tarball_path = os.path.join(tmpdirname, f"{file}.tar.gz") + + response = requests.get(url, stream=True) + 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")) + +class OrderedClassMembers(type): + @classmethod + def __prepare__(self, name, bases): + return collections.OrderedDict() + + def __new__(self, name, bases, classdict): + classdict["__ordered__"] = [ + key for key in classdict.keys() if key not in ("__module__", "__qualname__") + ] + return type.__new__(self, name, bases, classdict) + + +class TimeoutException(Exception): + def __init__(self, message: str, timeout: int): + self.message = message + self.timeout = timeout + + +def timeout_decorator(timeout: float): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + with concurrent.futures.ThreadPoolExecutor() as executor: + future = executor.submit(func, *args, **kwargs) + try: + result = future.result(timeout=timeout) + except concurrent.futures.TimeoutError: + raise TimeoutException( + f"Function {func.__name__} timed out after {timeout} seconds", + timeout, + ) + return result + + return wrapper + + return decorator + + +class MiniTestFramework: + def __init__(self): + self.passed_test_suites = 0 + self.failed_test_suites = 0 + self.passed_tests = 0 + self.failed_tests = 0 + + def has_failed(self) -> bool: + return self.failed_test_suites > 0 + + def run(self, classes: List[type]) -> bool: + self.start_time = time.time() + + for test_class in classes: + with tempfile.TemporaryDirectory() as tmpdirname: + original_cwd = os.getcwd() + os.chdir(tmpdirname) + + try: + if self.__run(test_class): + self.failed_test_suites += 1 + else: + self.passed_test_suites += 1 + finally: + os.chdir(original_cwd) + + self.__print_summary(round(time.time() - self.start_time, 2)) + return self.has_failed() + + def __run(self, test_class) -> bool: + test_instance = test_class() + test_name = test_instance.__class__.__name__ + test_methods = [m for m in test_instance.__ordered__ if m.startswith("test_")] + + print(f"\nTest Suite: {test_name}") + + if hasattr(test_instance, "beforeAll"): + test_instance.beforeAll() + + fails = 0 + + for method in test_methods: + fails += self.__run_test_method(test_instance, method) + + if hasattr(test_instance, "afterAll"): + test_instance.afterAll() + + self.failed_tests += fails + + return fails > 0 + + def __run_test_method(self, test_instance, method: str) -> int: + print(f" Running {method}... \r", end="", flush=True) + + buffer = io.StringIO() + fails = 0 + + try: + t0 = time.time() + + with redirect_stdout(buffer): + if hasattr(test_instance, "beforeEach"): + test_instance.beforeEach() + + getattr(test_instance, method)() + + if hasattr(test_instance, "afterEach"): + test_instance.afterEach() + + duration = time.time() - t0 + + self.print_success(f" {method} ({duration * 1000:.2f}ms)") + self.passed_tests += 1 + except Exception as e: + if isinstance(e, TimeoutException): + self.print_failure( + f" {method} (hit execution limit of {e.timeout} seconds)" + ) + + if isinstance(e, AssertionError): + self.__handle_assertion_error(t0, method) + + fails += 1 + finally: + self.__print_buffer_output(buffer) + + return fails + + def __handle_assertion_error(self, start_time, method: str): + duration = time.time() - start_time + self.print_failure(f" {method} ({duration * 1000:.2f}ms)") + traceback_output = "".join(traceback.format_tb(sys.exc_info()[2])) + + colored_traceback = "\n".join( + f" {CYAN_COLOR}{line}{RESET_COLOR}" + for line in traceback_output.splitlines() + ) + + print(colored_traceback) + + def __print_buffer_output(self, buffer: io.StringIO): + output = buffer.getvalue() + if output: + indented_output = "\n".join(f" {line}" for line in output.splitlines()) + print(f" {RED_COLOR}⎯⎯⎯⎯⎯OUTPUT⎯⎯⎯⎯⎯{RESET_COLOR}") + print(f"{GRAY_COLOR}{indented_output}{RESET_COLOR}") + print(f" {RED_COLOR}⎯⎯⎯⎯⎯OUTPUT⎯⎯⎯⎯⎯{RESET_COLOR}") + + def __print_summary(self, duration: float): + print(f"\n{WHITE_BOLD}Test Summary{RESET_COLOR}\n") + print( + f" Test Suites: {GREEN_COLOR}{self.passed_test_suites} passed{RESET_COLOR}, {RED_COLOR}{self.failed_test_suites} failed{RESET_COLOR}, {self.passed_test_suites + self.failed_test_suites} total" + ) + print( + f" Tests: {GREEN_COLOR}{self.passed_tests} passed{RESET_COLOR}, {RED_COLOR}{self.failed_tests} failed{RESET_COLOR}, {self.passed_tests + self.failed_tests} total" + ) + print(f" Time: {duration}s\n") + + def print_failure(self, add: str): + print(f" {RED_COLOR}✗{RESET_COLOR}{add}", flush=True) + + def print_success(self, add: str): + print(f" {GREEN_COLOR}✓{RESET_COLOR}{add}", flush=True) + + +class Stockfish: + def __init__( + self, + prefix: List[str], + path: str, + args: List[str] = [], + cli: bool = False, + ): + self.path = path + self.process = None + self.args = args + self.cli = cli + self.prefix = prefix + self.output = [] + + self.start() + + def start(self): + if self.cli: + self.process = subprocess.run( + self.prefix + [self.path] + self.args, + capture_output=True, + text=True, + ) + + self.process.stdout + + return + + self.process = subprocess.Popen( + self.prefix + [self.path] + self.args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + bufsize=1, + ) + + def setoption(self, name: str, value: str): + self.send_command(f"setoption name {name} value {value}") + + def send_command(self, command: str): + if not self.process: + raise RuntimeError("Stockfish process is not started") + + self.process.stdin.write(command + "\n") + self.process.stdin.flush() + + @timeout_decorator(MAX_TIMEOUT) + def equals(self, expected_output: str): + for line in self.readline(): + if line == expected_output: + return + + @timeout_decorator(MAX_TIMEOUT) + def expect(self, expected_output: str): + for line in self.readline(): + if fnmatch.fnmatch(line, expected_output): + return + + @timeout_decorator(MAX_TIMEOUT) + def contains(self, expected_output: str): + for line in self.readline(): + if expected_output in line: + return + + @timeout_decorator(MAX_TIMEOUT) + def starts_with(self, expected_output: str): + for line in self.readline(): + if line.startswith(expected_output): + return + + @timeout_decorator(MAX_TIMEOUT) + def check_output(self, callback): + if not callback: + raise ValueError("Callback function is required") + + for line in self.readline(): + if callback(line) == True: + return + + def readline(self): + if not self.process: + raise RuntimeError("Stockfish process is not started") + + while True: + line = self.process.stdout.readline().strip() + self.output.append(line) + + yield line + + def clear_output(self): + self.output = [] + + def get_output(self) -> List[str]: + return self.output + + def quit(self): + self.send_command("quit") + + def close(self): + if self.process: + self.process.stdin.close() + self.process.stdout.close() + return self.process.wait() + + return 0 From 224c147bd6211d2481afd25605b07c3fc98d837c Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Tue, 17 Sep 2024 20:44:57 +0200 Subject: [PATCH 0739/1309] VVLTC Search Tune Tuned with 115k games at VVLTC: https://tests.stockfishchess.org/tests/view/66c80e09bf8c9d8780fda62a Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/66d69ade9de3e7f9b33d14f9 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 54270 W: 13935 L: 13647 D: 26688 Ptnml(0-2): 2, 4907, 17032, 5189, 5 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/66dcf9c1dc53972b68218c84 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 136696 W: 34941 L: 34462 D: 67293 Ptnml(0-2): 8, 12659, 42535, 13138, 8 closes https://github.com/official-stockfish/Stockfish/pull/5592 Bench: 1644273 --- src/search.cpp | 84 +++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ac0b9c6d9dc..4f6e75111d1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -67,7 +67,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 122 - 37 * noTtCutNode; + Value futilityMult = 118 - 33 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; @@ -85,15 +85,15 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { w.pawnCorrectionHistory[pos.side_to_move()][pawn_structure_index(pos)]; const auto mcv = w.materialCorrectionHistory[pos.side_to_move()][material_index(pos)]; const auto cv = (2 * pcv + mcv) / 3; - v += 66 * cv / 512; + v += 74 * cv / 512; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(190 * d - 108, 1596); } +int stat_bonus(Depth d) { return std::min(179 * d - 108, 1598); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(736 * d - 268, 2044); } +int stat_malus(Depth d) { return std::min(820 * d - 261, 2246); } // 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); } @@ -299,12 +299,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 5 + avg * avg / 13424; + delta = 5 + avg * avg / 11797; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 125 * avg / (std::abs(avg) + 89); + optimism[us] = 132 * avg / (std::abs(avg) + 89); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -488,8 +488,8 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(0); - captureHistory.fill(-700); - pawnHistory.fill(-1188); + captureHistory.fill(-753); + pawnHistory.fill(-1152); pawnCorrectionHistory.fill(0); materialCorrectionHistory.fill(0); @@ -497,10 +497,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-658); + h->fill(-678); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((18.62 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((18.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -737,7 +737,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1664, 1471) + 752; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1641, 1423) + 760; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; 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] @@ -755,7 +755,7 @@ Value Search::Worker::search( // Step 7. Razoring (~1 Elo) // If eval is really low, check with qsearch if we can exceed alpha. If the // search suggests we cannot exceed alpha, return a speculative fail low. - if (eval < alpha - 494 - 290 * depth * depth) + if (eval < alpha - 501 - 272 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) @@ -766,7 +766,7 @@ Value Search::Worker::search( // The depth condition is important for mate finding. if (!ss->ttPv && depth < 13 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 260 + - (ss - 1)->statScore / 272 >= beta && eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY && eval < VALUE_TB_WIN_IN_MAX_PLY) @@ -774,13 +774,13 @@ Value Search::Worker::search( // Step 9. Null move search with verification search (~35 Elo) if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 390 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 23 * depth + 400 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 202, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 209, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -829,7 +829,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 184 - 53 * improving; + probCutBeta = beta + 189 - 53 * improving; if (!PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY // If value from transposition table is lower than probCutBeta, don't attempt @@ -898,7 +898,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea (~4 Elo) - probCutBeta = beta + 390; + probCutBeta = beta + 379; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY) @@ -982,15 +982,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 285 + 251 * lmrDepth + Value futilityValue = ss->staticEval + 300 + 238 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -182 * depth, 166 * depth); - if (!pos.see_ge(move, -168 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -159 * depth, 160 * depth); + if (!pos.see_ge(move, -167 * depth - seeHist)) continue; } else @@ -1001,15 +1001,15 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (history < -4165 * depth) + if (history < -4071 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 3853; + lmrDepth += history / 3653; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 51 ? 143 : 52) + 135 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 51 ? 145 : 49) + 144 * lmrDepth; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) @@ -1050,7 +1050,7 @@ Value Search::Worker::search( && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (54 + 76 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttData.value - (54 + 77 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1060,13 +1060,13 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 293 * PvNode - 195 * !ttCapture; - int tripleMargin = 107 + 259 * PvNode - 260 * !ttCapture + 98 * ss->ttPv; + int doubleMargin = 262 * PvNode - 204 * !ttCapture; + int tripleMargin = 97 + 266 * PvNode - 255 * !ttCapture + 94 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += ((!PvNode) && (depth < 16)); + depth += ((!PvNode) && (depth < 14)); } // Multi-cut pruning @@ -1099,7 +1099,7 @@ Value Search::Worker::search( else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 3994) + > 4299) extension = 1; } @@ -1153,10 +1153,10 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 4664; + + (*contHist[1])[movedPiece][move.to_sq()] - 4410; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 10898; + r -= ss->statScore / 11016; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1) @@ -1175,7 +1175,7 @@ Value Search::Worker::search( { // 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 + 35 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 38 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + 8; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1344,19 +1344,19 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (122 * (depth > 5) + 39 * !allNode + 165 * ((ss - 1)->moveCount > 8) - + 107 * (!ss->inCheck && bestValue <= ss->staticEval - 98) - + 134 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 91)); + int bonus = (118 * (depth > 5) + 38 * !allNode + 169 * ((ss - 1)->moveCount > 8) + + 116 * (!ss->inCheck && bestValue <= ss->staticEval - 101) + + 133 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 92)); // Proportional to "how much damage we have to undo" - bonus += std::min(-(ss - 1)->statScore / 100, 304); + bonus += std::min(-(ss - 1)->statScore / 102, 305); bonus = std::max(bonus, 0); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - stat_bonus(depth) * bonus / 116); + stat_bonus(depth) * bonus / 107); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << stat_bonus(depth) * bonus / 180; + << stat_bonus(depth) * bonus / 174; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -1522,7 +1522,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 299; + futilityBase = ss->staticEval + 280; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1593,11 +1593,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()] - <= 4643) + <= 5036) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -83)) + if (!pos.see_ge(move, -82)) continue; } @@ -1663,7 +1663,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 + 1274 - delta * 746 / rootDelta) / 1024 + (!i && reductionScale > 1293); + return (reductionScale + 1239 - delta * 795 / rootDelta) / 1024 + (!i && reductionScale > 1341); } // elapsed() returns the time elapsed since the search started. If the @@ -1794,7 +1794,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) { - bonus = bonus * 52 / 64; + bonus = bonus * 53 / 64; for (int i : {1, 2, 3, 4, 6}) { From 5ce7f866a57264c38cf308152208deadc65508c8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 7 Sep 2024 15:04:28 -0700 Subject: [PATCH 0740/1309] Simplify Fail Low Bonus Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 302528 W: 78190 L: 78264 D: 146074 Ptnml(0-2): 1029, 35797, 77551, 35993, 894 https://tests.stockfishchess.org/tests/view/66dcebdedc53972b68218c7e Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 122754 W: 31025 L: 30907 D: 60822 Ptnml(0-2): 74, 13597, 33908, 13733, 65 https://tests.stockfishchess.org/tests/view/66e0c38686d5ee47d953a481 closes https://github.com/official-stockfish/Stockfish/pull/5594 Bench: 1646373 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4f6e75111d1..135db0cee67 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1365,7 +1365,7 @@ Value Search::Worker::search( } // Bonus when search fails low and there is a TT move - else if (moveCount > 1 && ttData.move && !allNode) + else if (ttData.move && !allNode) thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) / 4; if (PvNode) From 240a5b1c72af0c9fa7b2dd13d17cdef61415b4e6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 14 Sep 2024 08:22:32 +0300 Subject: [PATCH 0741/1309] Introduce separate butterfly history table for sorting root moves Idea of this patch comes from the fact that current history heuristics are mostly populated by low depth entries since our stat bonus reaches maximum value at depth 5-6 and number of low depth nodes is much bigger than number of high depth nodes. But it doesn't make a whole lost of sense to use this low-depth centered histories to sort moves at root. Current patch introduces special history table that is used exclusively at root, it remembers which quiet moves were good and which quiet moves were not good there and uses this information for move ordering. Passed STC: https://tests.stockfishchess.org/tests/view/66dda74adc53972b68218cc9 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 127680 W: 33579 L: 33126 D: 60975 Ptnml(0-2): 422, 15098, 32391, 15463, 466 Passed LTC: https://tests.stockfishchess.org/tests/view/66dead2adc53972b68218d34 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 381978 W: 96958 L: 95923 D: 189097 Ptnml(0-2): 277, 42165, 105089, 43162, 296 closes https://github.com/official-stockfish/Stockfish/pull/5595 Bench: 1611283 --- src/movepick.cpp | 11 +++++++-- src/movepick.h | 6 ++++- src/search.cpp | 59 +++++++++++++++++++++++++++++++----------------- src/search.h | 1 + 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index bdc0e4affdb..63d9e8b1ace 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -82,16 +82,20 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, + const ButterflyHistory* rh, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory* ph) : + const PawnHistory* ph, + bool rn) : pos(p), mainHistory(mh), + rootHistory(rh), captureHistory(cph), continuationHistory(ch), pawnHistory(ph), ttMove(ttm), - depth(d) { + depth(d), + rootNode(rn) { if (pos.checkers()) stage = EVASION_TT + !(ttm && pos.pseudo_legal(ttm)); @@ -174,6 +178,9 @@ void MovePicker::score() { m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 : pt == ROOK ? bool(to & threatenedByMinor) * 24335 : bool(to & threatenedByPawn) * 14900); + + if (rootNode) + m.value += 4 * (*rootHistory)[pos.side_to_move()][m.from_to()]; } else // Type == EVASIONS diff --git a/src/movepick.h b/src/movepick.h index 651091b0829..f66cdadf5bb 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -171,9 +171,11 @@ class MovePicker { Move, Depth, const ButterflyHistory*, + const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory*); + const PawnHistory*, + bool); MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); @@ -187,6 +189,7 @@ class MovePicker { const Position& pos; const ButterflyHistory* mainHistory; + const ButterflyHistory* rootHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; @@ -195,6 +198,7 @@ class MovePicker { int stage; int threshold; Depth depth; + bool rootNode; ExtMove moves[MAX_MOVES]; }; diff --git a/src/search.cpp b/src/search.cpp index 135db0cee67..3c6da163b89 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -101,16 +101,21 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_histories( - const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); -void update_all_stats(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move bestMove, - Square prevSq, - ValueList& quietsSearched, - ValueList& capturesSearched, - Depth depth); +void update_quiet_histories(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move move, + int bonus, + bool rootNode); +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + ValueList& quietsSearched, + ValueList& capturesSearched, + Depth depth, + bool rootNode); } // namespace @@ -264,6 +269,8 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; + rootHistory.fill(0); + // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop && !(limits.depth && mainThread && rootDepth > limits.depth)) @@ -488,6 +495,7 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(0); + rootHistory.fill(0); captureHistory.fill(-753); pawnHistory.fill(-1152); pawnCorrectionHistory.fill(0); @@ -622,7 +630,7 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth)); + update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth), rootNode); // Extra penalty for early quiet moves of // the previous ply (~1 Elo on STC, ~2 Elo on LTC) @@ -912,8 +920,8 @@ Value Search::Worker::search( (ss - 6)->continuationHistory}; - MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, &thisThread->pawnHistory); + MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->rootHistory, + &thisThread->captureHistory, contHist, &thisThread->pawnHistory, rootNode); value = bestValue; @@ -1339,7 +1347,8 @@ Value Search::Worker::search( // If there is a move that produces search value greater than alpha, // we update the stats of searched moves. else if (bestMove) - update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth); + update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, + rootNode); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1533,8 +1542,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Initialize a MovePicker object for the current position, and prepare to search // the moves. We presently use two stages of move generator in quiescence search: // captures, or evasions only when in check. - MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->captureHistory, - contHist, &thisThread->pawnHistory); + MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->rootHistory, + &thisThread->captureHistory, contHist, &thisThread->pawnHistory, + nodeType == Root); // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta // cutoff occurs. @@ -1751,7 +1761,8 @@ void update_all_stats(const Position& pos, Square prevSq, ValueList& quietsSearched, ValueList& capturesSearched, - Depth depth) { + Depth depth, + bool rootNode) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); @@ -1762,11 +1773,11 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus); + update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus, rootNode); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus); + update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus, rootNode); } else { @@ -1808,11 +1819,17 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Updates move sorting heuristics -void update_quiet_histories( - const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { +void update_quiet_histories(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move move, + int bonus, + bool rootNode) { Color us = pos.side_to_move(); workerThread.mainHistory[us][move.from_to()] << bonus; + if (rootNode) + workerThread.rootHistory[us][move.from_to()] << bonus; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); diff --git a/src/search.h b/src/search.h index c9fe9e184ac..b06c7c9484b 100644 --- a/src/search.h +++ b/src/search.h @@ -278,6 +278,7 @@ class Worker { // Public because they need to be updatable by the stats ButterflyHistory mainHistory; + ButterflyHistory rootHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; PawnHistory pawnHistory; From 60351b9df901ff5278f208a9cf3a40059ff54832 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 12 Sep 2024 15:53:15 -0700 Subject: [PATCH 0742/1309] Introduce Various Correction histories This patch introduces three additional correction histories, namely, Major Piece Correction History, Minor Piece Correction History, and Non-Pawn Correction History. Introduced by @mcthouacbb in Sirius (https://github.com/mcthouacbb/Sirius) chess engine. The Major Piece Correction History is indexed by side-to-move and the Zobrist key representing the position of the King, Rook, and Queen of both sides. Likewise, the Minor Piece Correction History is indexed by side-to-move and the Zobrist key representing the position of the King, Knight, and Bishop of both sides. Also See: https://github.com/mcthouacbb/Sirius/commit/97b85bbaac88ff5a0f63e28776027dd3de77164e https://github.com/mcthouacbb/Sirius/commit/3099cdef2f13e29805654b5f8153e6ecd5853195 Introduced by @zzzzz151 in Starzix (https://github.com/zzzzz151/Starzix) chess engine. Non-Pawn correction history consists of side-to-move, side of Zobrist key, and a Zobrist key representing of the position of all non-pawn pieces of **one side**. The non-pawn correction values for both key sides are then summed. Also See: https://github.com/zzzzz151/Starzix/commit/34911772f178c27b3a239dda0acb79c397c3a2f0 https://github.com/zzzzz151/Starzix/commit/33e0df8dd2db1d4775974ab12e3390154697f47a The weights on the final correction value of the above correction histories, as well as existing correction histories, are then tuned in two separate SPSA sessions, totaling 75k games. SPSA1: https://tests.stockfishchess.org/tests/view/66e5243886d5ee47d953a86b (Stopped early due to some weights reaching the maximum value) SPSA2: https://tests.stockfishchess.org/tests/view/66e6a26f86d5ee47d953a965 Also thanks to @martinnovaak, (Motor https://github.com/martinnovaak/motor author) for insights and suggestions. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 23328 W: 6197 L: 5901 D: 11230 Ptnml(0-2): 82, 2582, 6041, 2876, 83 https://tests.stockfishchess.org/tests/view/66e8787b86d5ee47d953ab6f Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 10626 W: 2826 L: 2560 D: 5240 Ptnml(0-2): 4, 1054, 2941, 1300, 14 https://tests.stockfishchess.org/tests/view/66e8ab2386d5ee47d953aba8 closes https://github.com/official-stockfish/Stockfish/pull/5598 Bench: 1011161 --- src/bitboard.cpp | 4 +-- src/movepick.h | 40 ++++++++++++++++++++++++----- src/position.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++---- src/position.h | 12 +++++++++ src/search.cpp | 24 +++++++++++++----- src/search.h | 19 +++++++++----- tests/perft.sh | 2 +- 7 files changed, 140 insertions(+), 27 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index c842ca1271e..a8b4e5f4464 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -140,8 +140,8 @@ Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { // Computes all rook and bishop attacks at startup. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see -// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so -// called "fancy" approach. +// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use +// the so called "fancy" approach. void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { // Optimal PRNG seeds to pick the correct magics in the shortest time diff --git a/src/movepick.h b/src/movepick.h index f66cdadf5bb..13b9635bb2a 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -34,10 +34,13 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 -constexpr int PAWN_CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 -constexpr int MATERIAL_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int PAWN_CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 +constexpr int MATERIAL_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int MAJOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int MINOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int NON_PAWN_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); @@ -59,6 +62,19 @@ inline int material_index(const Position& pos) { return pos.material_key() & (MATERIAL_CORRECTION_HISTORY_SIZE - 1); } +inline int major_piece_index(const Position& pos) { + return pos.major_piece_key() & (MAJOR_PIECE_CORRECTION_HISTORY_SIZE - 1); +} + +inline int minor_piece_index(const Position& pos) { + return pos.minor_piece_key() & (MINOR_PIECE_CORRECTION_HISTORY_SIZE - 1); +} + +template +inline int non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c) & (NON_PAWN_CORRECTION_HISTORY_SIZE - 1); +} + // StatsEntry stores the stat table value. It is usually a number but could // be a move or even a nested history. We use a class instead of a naked value // to directly call history update operator<<() on the entry so to use stats @@ -120,7 +136,7 @@ enum StatsType { // ButterflyHistory records how often quiet moves have been successful or unsuccessful // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, -// see www.chessprogramming.org/Butterfly_Boards (~11 elo) +// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) using ButterflyHistory = Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] @@ -138,10 +154,10 @@ using ContinuationHistory = Stats // PawnHistory is addressed by the pawn structure and a move's [piece][to] using PawnHistory = Stats; - // Correction histories record differences between the static evaluation of // positions and their search score. It is used to improve the static evaluation // used by some search heuristics. +// see https://www.chessprogramming.org/Static_Evaluation_Correction_History // PawnCorrectionHistory is addressed by color and pawn structure using PawnCorrectionHistory = @@ -151,6 +167,18 @@ using PawnCorrectionHistory = using MaterialCorrectionHistory = Stats; +// MajorPieceCorrectionHistory is addressed by color and king/major piece (Queen, Rook) positions +using MajorPieceCorrectionHistory = + Stats; + +// MinorPieceCorrectionHistory is addressed by color and king/minor piece (Knight, Bishop) positions +using MinorPieceCorrectionHistory = + Stats; + +// NonPawnCorrectionHistory is addressed by color and non-pawn material positions +using NonPawnCorrectionHistory = + Stats; + // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one // new pseudo-legal move on every call, until there are no moves left, when diff --git a/src/position.cpp b/src/position.cpp index df95ffef380..f596b015355 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -334,8 +334,10 @@ void Position::set_check_info() const { // The function is only used when a new position is set up void Position::set_state() const { - st->key = st->materialKey = 0; - st->pawnKey = Zobrist::noPawns; + st->key = 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; st->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); @@ -350,8 +352,27 @@ void Position::set_state() const { if (type_of(pc) == PAWN) st->pawnKey ^= Zobrist::psq[pc][s]; - else if (type_of(pc) != KING) - st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; + else + { + st->nonPawnKey[color_of(pc)] ^= Zobrist::psq[pc][s]; + + if (type_of(pc) != KING) + { + st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; + + if (type_of(pc) == QUEEN || type_of(pc) == ROOK) + st->majorPieceKey ^= Zobrist::psq[pc][s]; + + else + st->minorPieceKey ^= Zobrist::psq[pc][s]; + } + + else + { + st->majorPieceKey ^= Zobrist::psq[pc][s]; + st->minorPieceKey ^= Zobrist::psq[pc][s]; + } + } } if (st->epSquare != SQ_NONE) @@ -707,6 +728,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { 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; } @@ -732,7 +755,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->pawnKey ^= Zobrist::psq[captured][capsq]; } else + { st->nonPawnMaterial[them] -= PieceValue[captured]; + st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq]; + + if (type_of(pc) == QUEEN || type_of(pc) == ROOK) + st->majorPieceKey ^= Zobrist::psq[captured][capsq]; + + else + st->minorPieceKey ^= Zobrist::psq[captured][capsq]; + } dp.dirty_num = 2; // 1 piece moved, 1 piece captured dp.piece[1] = captured; @@ -790,7 +822,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { else if (m.type_of() == PROMOTION) { - Piece promotion = make_piece(us, m.promotion_type()); + Piece promotion = make_piece(us, m.promotion_type()); + PieceType promotionType = type_of(promotion); assert(relative_rank(us, to) == RANK_8); assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); @@ -811,6 +844,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; + if (promotionType == QUEEN || promotionType == ROOK) + st->majorPieceKey ^= Zobrist::psq[promotion][to]; + + else + st->minorPieceKey ^= Zobrist::psq[promotion][to]; + // Update material st->nonPawnMaterial[us] += PieceValue[promotion]; } @@ -822,6 +861,23 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->rule50 = 0; } + else + { + st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + 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) == QUEEN || type_of(pc) == ROOK) + st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + else + st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + } + // Set capture piece st->capturedPiece = captured; diff --git a/src/position.h b/src/position.h index 6cac1731951..888612da78d 100644 --- a/src/position.h +++ b/src/position.h @@ -43,6 +43,9 @@ struct StateInfo { // Copied when making a move Key materialKey; Key pawnKey; + Key majorPieceKey; + Key minorPieceKey; + Key nonPawnKey[COLOR_NB]; Value nonPawnMaterial[COLOR_NB]; int castlingRights; int rule50; @@ -151,6 +154,9 @@ class Position { Key key_after(Move m) 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; // Other properties of the position Color side_to_move() const; @@ -298,6 +304,12 @@ 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]; } + inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } inline Value Position::non_pawn_material() const { diff --git a/src/search.cpp b/src/search.cpp index 3c6da163b89..199b9355403 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -46,7 +46,6 @@ #include "thread.h" #include "timeman.h" #include "tt.h" -#include "types.h" #include "uci.h" #include "ucioption.h" @@ -81,11 +80,16 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { - const auto pcv = - w.pawnCorrectionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - const auto mcv = w.materialCorrectionHistory[pos.side_to_move()][material_index(pos)]; - const auto cv = (2 * pcv + mcv) / 3; - v += 74 * cv / 512; + const Color us = pos.side_to_move(); + const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(pos)]; + const auto mcv = w.materialCorrectionHistory[us][material_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 cv = + (98198 * pcv + 68968 * mcv + 54353 * macv + 85174 * micv + 85581 * (wnpcv + bnpcv)) / 2097152; + v += cv; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -500,6 +504,10 @@ void Search::Worker::clear() { pawnHistory.fill(-1152); pawnCorrectionHistory.fill(0); materialCorrectionHistory.fill(0); + majorPieceCorrectionHistory.fill(0); + minorPieceCorrectionHistory.fill(0); + nonPawnCorrectionHistory[WHITE].fill(0); + nonPawnCorrectionHistory[BLACK].fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) @@ -1403,6 +1411,10 @@ Value Search::Worker::search( -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] << bonus; thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus; + thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus; + thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus; + thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] << bonus; + thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] << bonus; } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); diff --git a/src/search.h b/src/search.h index b06c7c9484b..d7a909a82a8 100644 --- a/src/search.h +++ b/src/search.h @@ -277,13 +277,18 @@ class Worker { void ensure_network_replicated(); // Public because they need to be updatable by the stats - ButterflyHistory mainHistory; - ButterflyHistory rootHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; - PawnHistory pawnHistory; - PawnCorrectionHistory pawnCorrectionHistory; - MaterialCorrectionHistory materialCorrectionHistory; + ButterflyHistory mainHistory; + ButterflyHistory rootHistory; + + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; + + PawnCorrectionHistory pawnCorrectionHistory; + MaterialCorrectionHistory materialCorrectionHistory; + MajorPieceCorrectionHistory majorPieceCorrectionHistory; + MinorPieceCorrectionHistory minorPieceCorrectionHistory; + NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; private: void iterative_deepening(); diff --git a/tests/perft.sh b/tests/perft.sh index 545e750fec0..c1532c20c19 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -1,5 +1,5 @@ #!/bin/bash -# verify perft numbers (positions from www.chessprogramming.org/Perft_Results) +# verify perft numbers (positions from https://www.chessprogramming.org/Perft_Results) error() { From 93869d5d0aab2f7121bdf227def3a942c9fcde17 Mon Sep 17 00:00:00 2001 From: Wencey Wang Date: Thu, 19 Sep 2024 16:30:28 +0800 Subject: [PATCH 0743/1309] Fix native arch builds on loongarch64 Adds support for LSX and LASX closes https://github.com/official-stockfish/Stockfish/pull/5600 No functional change --- AUTHORS | 1 + scripts/get_native_properties.sh | 15 ++++++++++++ src/Makefile | 42 +++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3201e7a8afe..c0a8beebc45 100644 --- a/AUTHORS +++ b/AUTHORS @@ -237,6 +237,7 @@ Unai Corzo (unaiic) Uri Blass (uriblass) Vince Negri (cuddlestmonkey) Viren +Wencey Wang windfishballad xefoci7612 Xiang Wang (KatyushaScarlet) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index fb124021a31..dfbfac0eab6 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -26,6 +26,17 @@ check_znver_1_2() { [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true } +# Set the file CPU loongarch64 architecture +set_arch_loongarch64() { + if check_flags 'lasx'; then + true_arch='loongarch64-lasx' + elif check_flags 'lsx'; then + true_arch='lonngarch64-lsx' + else + true_arch='loongarch64' + fi +} + # Set the file CPU x86_64 architecture set_arch_x86_64() { if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then @@ -90,6 +101,10 @@ case $uname_s in true_arch="$true_arch-neon" fi ;; + 'loongarch64'*) + file_os='linux' + set_arch_loongarch64 + ;; *) # Unsupported machine type, exit with error printf 'Unsupported machine type: %s\n' "$uname_m" exit 1 diff --git a/src/Makefile b/src/Makefile index 042d9479cc8..6cb778a6822 100644 --- a/src/Makefile +++ b/src/Makefile @@ -100,6 +100,8 @@ VPATH = syzygy:nnue:nnue/features # vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 # neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture # dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions +# lsx = yes/no --- -mlsx --- Use Loongson SIMD eXtension +# lasx = yes/no --- -mlasx --- use Loongson Advanced SIMD eXtension # # Note that Makefile is space sensitive, so when adding new architectures # or modifying existing flags, you have to make sure there are no extra spaces @@ -125,7 +127,8 @@ ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ - armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 loongarch64)) + armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \ + loongarch64 loongarch64-lsx loongarch64-lasx)) SUPPORTED_ARCH=true else SUPPORTED_ARCH=false @@ -151,6 +154,8 @@ vnni512 = no neon = no dotprod = no arm_version = 0 +lsx = no +lasx = no STRIP = strip ifneq ($(shell which clang-format-18 2> /dev/null),) @@ -370,8 +375,19 @@ ifeq ($(ARCH),riscv64) arch = riscv64 endif -ifeq ($(ARCH),loongarch64) +ifeq ($(findstring loongarch64,$(ARCH)),loongarch64) arch = loongarch64 + prefetch = yes + +ifeq ($(findstring -lasx,$(ARCH)),-lasx) + lsx = yes + lasx = yes +endif + +ifeq ($(findstring -lsx,$(ARCH)),-lsx) + lsx = yes +endif + endif endif @@ -408,7 +424,7 @@ ifeq ($(COMP),gcc) ifeq ($(ARCH),riscv64) CXXFLAGS += -latomic endif - else ifeq ($(ARCH),loongarch64) + else ifeq ($(arch),loongarch64) CXXFLAGS += -latomic else CXXFLAGS += -m$(bits) @@ -480,7 +496,7 @@ ifeq ($(COMP),clang) ifeq ($(ARCH),riscv64) CXXFLAGS += -latomic endif - else ifeq ($(ARCH),loongarch64) + else ifeq ($(arch),loongarch64) CXXFLAGS += -latomic else CXXFLAGS += -m$(bits) @@ -719,6 +735,18 @@ ifeq ($(dotprod),yes) CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD endif +ifeq ($(lasx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mlasx + endif +endif + +ifeq ($(lsx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mlsx + endif +endif + ### 3.7 pext ifeq ($(pext),yes) CXXFLAGS += -DUSE_PEXT @@ -835,6 +863,8 @@ help: @echo "general-32 > unspecified 32-bit" @echo "riscv64 > RISC-V 64-bit" @echo "loongarch64 > LoongArch 64-bit" + @echo "loongarch64-lsx > LoongArch 64-bit with SIMD eXtension" + @echo "loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension" @echo "" @echo "Supported compilers:" @echo "" @@ -960,6 +990,8 @@ config-sanity: net @echo "neon: '$(neon)'" @echo "dotprod: '$(dotprod)'" @echo "arm_version: '$(arm_version)'" + @echo "lsx: '$(lsx)'" + @echo "lasx: '$(lasx)'" @echo "target_windows: '$(target_windows)'" @echo "" @echo "Flags:" @@ -989,6 +1021,8 @@ config-sanity: net @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no" @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no" @test "$(neon)" = "yes" || test "$(neon)" = "no" + @test "$(lsx)" = "yes" || test "$(lsx)" = "no" + @test "$(lasx)" = "yes" || test "$(lasx)" = "no" @test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ || test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang" From 5d0bb5976ef2da06a6386d0f5cad2f755e9b0927 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 19 Sep 2024 15:03:07 +0300 Subject: [PATCH 0744/1309] Removed ROOK threatenedByPawn Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 56608 W: 14788 L: 14588 D: 27232 Ptnml(0-2): 162, 6763, 14313, 6845, 221 https://tests.stockfishchess.org/tests/view/66e83f9c86d5ee47d953ab1d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 175758 W: 44501 L: 44438 D: 86819 Ptnml(0-2): 125, 19489, 48601, 19526, 138 https://tests.stockfishchess.org/tests/view/66e882d486d5ee47d953ab8a closes https://github.com/official-stockfish/Stockfish/pull/5601 bench: 1241271 --- src/movepick.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 63d9e8b1ace..f4ef0e5499b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -175,9 +175,9 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 - : pt == ROOK ? bool(to & threatenedByMinor) * 24335 - : bool(to & threatenedByPawn) * 14900); + m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 + : pt == ROOK && bool(to & threatenedByMinor) ? 24335 + : 0); if (rootNode) m.value += 4 * (*rootHistory)[pos.side_to_move()][m.from_to()]; From ae420e735f378bbb675dcf47598a5204f008cdd5 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 18 Sep 2024 06:26:20 +0200 Subject: [PATCH 0745/1309] Tweak Correction histories tune parameters some more, adjust scores updated for each history passed STC: https://tests.stockfishchess.org/tests/view/66ea569186d5ee47d953ae48 LLR: 2.92 (-2.94,2.94) <0.00,2.00> Total: 36288 W: 9660 L: 9344 D: 17284 Ptnml(0-2): 110, 4207, 9220, 4471, 136 passed LTC: https://tests.stockfishchess.org/tests/view/66ea9b4e86d5ee47d953ae6f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 241446 W: 61748 L: 61010 D: 118688 Ptnml(0-2): 173, 26211, 67202, 26979, 158 closes https://github.com/official-stockfish/Stockfish/pull/5606 Bench: 1677953 --- src/search.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 199b9355403..229aef9b218 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -88,7 +88,8 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& 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 cv = - (98198 * pcv + 68968 * mcv + 54353 * macv + 85174 * micv + 85581 * (wnpcv + bnpcv)) / 2097152; + (99916 * pcv + 55067 * mcv + 55530 * macv + 95324 * micv + 105056 * (wnpcv + bnpcv)) + / 2097152; v += cv; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -1409,12 +1410,15 @@ Value Search::Worker::search( { 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; - thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus; - thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus; - thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus; - thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] << bonus; - thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] << bonus; + thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] + << bonus * 101 / 128; + thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus * 99 / 128; + thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 157 / 128; + thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 153 / 128; + thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] + << bonus * 123 / 128; + thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] + << bonus * 165 / 128; } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); From aff1f67997cd2584ea7c82d967ac7bfd4cc77861 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:17:24 +0200 Subject: [PATCH 0746/1309] simplify see pruning in qsearch passed non-regression STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 34880 W: 9193 L: 8968 D: 16719 Ptnml(0-2): 103, 4047, 8935, 4232, 123 https://tests.stockfishchess.org/tests/view/66ee83bd86d5ee47d953b15b passed non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 69126 W: 17529 L: 17357 D: 34240 Ptnml(0-2): 41, 7507, 19285, 7699, 31 https://tests.stockfishchess.org/tests/view/66ef3e0386d5ee47d953b1d3 closes https://github.com/official-stockfish/Stockfish/pull/5607 Bench: 1339840 --- src/search.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 229aef9b218..d87a6b9a041 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1596,19 +1596,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; } - // If static eval is much lower than alpha and move is - // not winning material, we can prune this move. (~2 Elo) - if (futilityBase <= alpha && !pos.see_ge(move, 1)) + // if static exchange evaluation is low enough + // we can prune this move. (~2 Elo) + if (!pos.see_ge(move, alpha - futilityBase)) { - bestValue = std::max(bestValue, futilityBase); - continue; - } - - // If static exchange evaluation is much worse than what - // is needed to not fall below alpha, we can prune this move. - if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4)) - { - bestValue = alpha; + bestValue = (futilityBase > alpha) ? alpha : std::max(bestValue, futilityBase); continue; } } From 3ac75cd27d914da29280163c9d391bbca414d766 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Tue, 4 Jun 2024 17:23:56 +0200 Subject: [PATCH 0747/1309] Add a standardized benchmark command `speedtest`. `speedtest [threads] [hash_MiB] [time_s]`. `threads` default to system concurrency. `hash_MiB` defaults to `threads*128`. `time_s` defaults to 150. Intended to be used with default parameters, as a stable hardware benchmark. Example: ``` C:\dev\stockfish-master\src>stockfish.exe speedtest Stockfish dev-20240928-nogit by the Stockfish developers (see AUTHORS file) info string Using 16 threads Warmup position 3/3 Position 258/258 =========================== Version : Stockfish dev-20240928-nogit Compiled by : g++ (GNUC) 13.2.0 on MinGW64 Compilation architecture : x86-64-vnni256 Compilation settings : 64bit VNNI BMI2 AVX2 SSE41 SSSE3 SSE2 POPCNT Compiler __VERSION__ macro : 13.2.0 Large pages : yes User invocation : speedtest Filled invocation : speedtest 16 2048 150 Available processors : 0-15 Thread count : 16 Thread binding : none TT size [MiB] : 2048 Hash max, avg [per mille] : single search : 40, 21 single game : 631, 428 Total nodes searched : 2099917842 Total search time [s] : 153.937 Nodes/second : 13641410 ``` ------------------------------- Small unrelated tweaks: - Network verification output is now handled as a callback. - TT hashfull queries allow specifying maximum entry age. closes https://github.com/official-stockfish/Stockfish/pull/5354 No functional change --- src/benchmark.cpp | 349 +++++++++++++++++++++++++++++++++++++++++++ src/benchmark.h | 10 ++ src/engine.cpp | 37 +++-- src/engine.h | 7 +- src/memory.cpp | 31 ++++ src/memory.h | 2 + src/misc.cpp | 11 +- src/misc.h | 8 +- src/nnue/network.cpp | 51 ++++--- src/nnue/network.h | 4 +- src/numa.h | 11 +- src/tt.cpp | 15 +- src/tt.h | 2 +- src/uci.cpp | 174 ++++++++++++++++++++- src/uci.h | 5 +- 15 files changed, 664 insertions(+), 53 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 3622ac8afc8..35ad3c18014 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -17,6 +17,7 @@ */ #include "benchmark.h" +#include "numa.h" #include #include @@ -91,6 +92,282 @@ const std::vector Defaults = { }; // clang-format on +// clang-format off +// human-randomly picked 5 games with <60 moves from +// https://tests.stockfishchess.org/tests/view/665c71f9fd45fb0f907c21e0 +// only moves for one side +const std::vector> BenchmarkPositions = { + { + "rnbq1k1r/ppp1bppp/4pn2/8/2B5/2NP1N2/PPP2PPP/R1BQR1K1 b - - 2 8", + "rnbq1k1r/pp2bppp/4pn2/2p5/2B2B2/2NP1N2/PPP2PPP/R2QR1K1 b - - 1 9", + "r1bq1k1r/pp2bppp/2n1pn2/2p5/2B1NB2/3P1N2/PPP2PPP/R2QR1K1 b - - 3 10", + "r1bq1k1r/pp2bppp/2n1p3/2p5/2B1PB2/5N2/PPP2PPP/R2QR1K1 b - - 0 11", + "r1b2k1r/pp2bppp/2n1p3/2p5/2B1PB2/5N2/PPP2PPP/3RR1K1 b - - 0 12", + "r1b1k2r/pp2bppp/2n1p3/2p5/2B1PB2/2P2N2/PP3PPP/3RR1K1 b - - 0 13", + "r1b1k2r/1p2bppp/p1n1p3/2p5/4PB2/2P2N2/PP2BPPP/3RR1K1 b - - 1 14", + "r1b1k2r/4bppp/p1n1p3/1pp5/P3PB2/2P2N2/1P2BPPP/3RR1K1 b - - 0 15", + "r1b1k2r/4bppp/p1n1p3/1P6/2p1PB2/2P2N2/1P2BPPP/3RR1K1 b - - 0 16", + "r1b1k2r/4bppp/2n1p3/1p6/2p1PB2/1PP2N2/4BPPP/3RR1K1 b - - 0 17", + "r3k2r/3bbppp/2n1p3/1p6/2P1PB2/2P2N2/4BPPP/3RR1K1 b - - 0 18", + "r3k2r/3bbppp/2n1p3/8/1pP1P3/2P2N2/3BBPPP/3RR1K1 b - - 1 19", + "1r2k2r/3bbppp/2n1p3/8/1pPNP3/2P5/3BBPPP/3RR1K1 b - - 3 20", + "1r2k2r/3bbppp/2n1p3/8/2PNP3/2B5/4BPPP/3RR1K1 b - - 0 21", + "1r2k2r/3bb1pp/2n1pp2/1N6/2P1P3/2B5/4BPPP/3RR1K1 b - - 1 22", + "1r2k2r/3b2pp/2n1pp2/1N6/1BP1P3/8/4BPPP/3RR1K1 b - - 0 23", + "1r2k2r/3b2pp/4pp2/1N6/1nP1P3/8/3RBPPP/4R1K1 b - - 1 24", + "1r5r/3bk1pp/4pp2/1N6/1nP1PP2/8/3RB1PP/4R1K1 b - - 0 25", + "1r5r/3bk1pp/2n1pp2/1N6/2P1PP2/8/3RBKPP/4R3 b - - 2 26", + "1r5r/3bk1pp/2n2p2/1N2p3/2P1PP2/6P1/3RBK1P/4R3 b - - 0 27", + "1r1r4/3bk1pp/2n2p2/1N2p3/2P1PP2/6P1/3RBK1P/R7 b - - 2 28", + "1r1r4/N3k1pp/2n1bp2/4p3/2P1PP2/6P1/3RBK1P/R7 b - - 4 29", + "1r1r4/3bk1pp/2N2p2/4p3/2P1PP2/6P1/3RBK1P/R7 b - - 0 30", + "1r1R4/4k1pp/2b2p2/4p3/2P1PP2/6P1/4BK1P/R7 b - - 0 31", + "3r4/4k1pp/2b2p2/4P3/2P1P3/6P1/4BK1P/R7 b - - 0 32", + "3r4/R3k1pp/2b5/4p3/2P1P3/6P1/4BK1P/8 b - - 1 33", + "8/3rk1pp/2b5/R3p3/2P1P3/6P1/4BK1P/8 b - - 3 34", + "8/3r2pp/2bk4/R1P1p3/4P3/6P1/4BK1P/8 b - - 0 35", + "8/2kr2pp/2b5/R1P1p3/4P3/4K1P1/4B2P/8 b - - 2 36", + "1k6/3r2pp/2b5/RBP1p3/4P3/4K1P1/7P/8 b - - 4 37", + "8/1k1r2pp/2b5/R1P1p3/4P3/3BK1P1/7P/8 b - - 6 38", + "1k6/3r2pp/2b5/2P1p3/4P3/3BK1P1/7P/R7 b - - 8 39", + "1k6/r5pp/2b5/2P1p3/4P3/3BK1P1/7P/5R2 b - - 10 40", + "1k3R2/6pp/2b5/2P1p3/4P3/r2BK1P1/7P/8 b - - 12 41", + "5R2/2k3pp/2b5/2P1p3/4P3/r2B2P1/3K3P/8 b - - 14 42", + "5R2/2k3pp/2b5/2P1p3/4P3/3BK1P1/r6P/8 b - - 16 43", + "5R2/2k3pp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 18 44", + "5R2/2k3pp/2b5/2P1p3/4P3/3B1KP1/r6P/8 b - - 20 45", + "8/2k2Rpp/2b5/2P1p3/4P3/r2B1KP1/7P/8 b - - 22 46", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 24 47", + "3k4/5Rpp/2b5/2P1p3/4P3/3B1KP1/r6P/8 b - - 26 48", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/4K2P/8 b - - 28 49", + "3k4/5Rpp/2b5/2P1p3/4P3/3BK1P1/r6P/8 b - - 30 50", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/3K3P/8 b - - 32 51", + "3k4/5Rpp/2b5/2P1p3/4P3/2KB2P1/r6P/8 b - - 34 52", + "3k4/5Rpp/2b5/2P1p3/4P3/r2B2P1/2K4P/8 b - - 36 53", + "3k4/5Rpp/2b5/2P1p3/4P3/1K1B2P1/r6P/8 b - - 38 54", + "3k4/6Rp/2b5/2P1p3/4P3/1K1B2P1/7r/8 b - - 0 55", + "3k4/8/2b3Rp/2P1p3/4P3/1K1B2P1/7r/8 b - - 1 56", + "8/2k3R1/2b4p/2P1p3/4P3/1K1B2P1/7r/8 b - - 3 57", + "3k4/8/2b3Rp/2P1p3/4P3/1K1B2P1/7r/8 b - - 5 58", + "8/2k5/2b3Rp/2P1p3/1K2P3/3B2P1/7r/8 b - - 7 59", + "8/2k5/2b3Rp/2P1p3/4P3/2KB2P1/3r4/8 b - - 9 60", + "8/2k5/2b3Rp/2P1p3/1K2P3/3B2P1/6r1/8 b - - 11 61", + "8/2k5/2b3Rp/2P1p3/4P3/2KB2P1/3r4/8 b - - 13 62", + "8/2k5/2b3Rp/2P1p3/2K1P3/3B2P1/6r1/8 b - - 15 63", + "4b3/2k3R1/7p/2P1p3/2K1P3/3B2P1/6r1/8 b - - 17 64", + }, + { + "r1bqkbnr/npp1pppp/p7/3P4/4pB2/2N5/PPP2PPP/R2QKBNR w KQkq - 1 6", + "r1bqkb1r/npp1pppp/p4n2/3P4/4pB2/2N5/PPP1QPPP/R3KBNR w KQkq - 3 7", + "r2qkb1r/npp1pppp/p4n2/3P1b2/4pB2/2N5/PPP1QPPP/2KR1BNR w kq - 5 8", + "r2qkb1r/1pp1pppp/p4n2/1n1P1b2/4pB2/2N4P/PPP1QPP1/2KR1BNR w kq - 1 9", + "r2qkb1r/1pp1pppp/5n2/1p1P1b2/4pB2/7P/PPP1QPP1/2KR1BNR w kq - 0 10", + "r2qkb1r/1ppbpppp/5n2/1Q1P4/4pB2/7P/PPP2PP1/2KR1BNR w kq - 1 11", + "3qkb1r/1Qpbpppp/5n2/3P4/4pB2/7P/rPP2PP1/2KR1BNR w k - 0 12", + "q3kb1r/1Qpbpppp/5n2/3P4/4pB2/7P/rPP2PP1/1K1R1BNR w k - 2 13", + "r3kb1r/2pbpppp/5n2/3P4/4pB2/7P/1PP2PP1/1K1R1BNR w k - 0 14", + "r3kb1r/2Bb1ppp/4pn2/3P4/4p3/7P/1PP2PP1/1K1R1BNR w k - 0 15", + "r3kb1r/2Bb2pp/4pn2/8/4p3/7P/1PP2PP1/1K1R1BNR w k - 0 16", + "r3k2r/2Bb2pp/4pn2/2b5/4p3/7P/1PP1NPP1/1K1R1B1R w k - 2 17", + "r6r/2Bbk1pp/4pn2/2b5/3Np3/7P/1PP2PP1/1K1R1B1R w - - 4 18", + "r6r/b2bk1pp/4pn2/4B3/3Np3/7P/1PP2PP1/1K1R1B1R w - - 6 19", + "r1r5/b2bk1pp/4pn2/4B3/2BNp3/7P/1PP2PP1/1K1R3R w - - 8 20", + "r7/b2bk1pp/4pn2/2r1B3/2BNp3/1P5P/2P2PP1/1K1R3R w - - 1 21", + "rb6/3bk1pp/4pn2/2r1B3/2BNpP2/1P5P/2P3P1/1K1R3R w - - 1 22", + "1r6/3bk1pp/4pn2/2r5/2BNpP2/1P5P/2P3P1/1K1R3R w - - 0 23", + "1r6/3bk1p1/4pn1p/2r5/2BNpP2/1P5P/2P3P1/2KR3R w - - 0 24", + "8/3bk1p1/1r2pn1p/2r5/2BNpP1P/1P6/2P3P1/2KR3R w - - 1 25", + "8/3bk3/1r2pnpp/2r5/2BNpP1P/1P6/2P3P1/2K1R2R w - - 0 26", + "2b5/4k3/1r2pnpp/2r5/2BNpP1P/1P4P1/2P5/2K1R2R w - - 1 27", + "8/1b2k3/1r2pnpp/2r5/2BNpP1P/1P4P1/2P5/2K1R1R1 w - - 3 28", + "8/1b1nk3/1r2p1pp/2r5/2BNpPPP/1P6/2P5/2K1R1R1 w - - 1 29", + "8/1b2k3/1r2p1pp/2r1nP2/2BNp1PP/1P6/2P5/2K1R1R1 w - - 1 30", + "8/1b2k3/1r2p1p1/2r1nPp1/2BNp2P/1P6/2P5/2K1R1R1 w - - 0 31", + "8/1b2k3/1r2p1n1/2r3p1/2BNp2P/1P6/2P5/2K1R1R1 w - - 0 32", + "8/1b2k3/1r2p1n1/6r1/2BNp2P/1P6/2P5/2K1R3 w - - 0 33", + "8/1b2k3/1r2p3/4n1P1/2BNp3/1P6/2P5/2K1R3 w - - 1 34", + "8/1b2k3/1r2p3/4n1P1/2BN4/1P2p3/2P5/2K4R w - - 0 35", + "8/1b2k3/1r2p2R/6P1/2nN4/1P2p3/2P5/2K5 w - - 0 36", + "8/1b2k3/3rp2R/6P1/2PN4/4p3/2P5/2K5 w - - 1 37", + "8/4k3/3rp2R/6P1/2PN4/2P1p3/6b1/2K5 w - - 1 38", + "8/4k3/r3p2R/2P3P1/3N4/2P1p3/6b1/2K5 w - - 1 39", + "8/3k4/r3p2R/2P2NP1/8/2P1p3/6b1/2K5 w - - 3 40", + "8/3k4/4p2R/2P3P1/8/2P1N3/6b1/r1K5 w - - 1 41", + "8/3k4/4p2R/2P3P1/8/2P1N3/3K2b1/6r1 w - - 3 42", + "8/3k4/4p2R/2P3P1/8/2PKNb2/8/6r1 w - - 5 43", + "8/4k3/4p1R1/2P3P1/8/2PKNb2/8/6r1 w - - 7 44", + "8/4k3/4p1R1/2P3P1/3K4/2P1N3/8/6rb w - - 9 45", + "8/3k4/4p1R1/2P1K1P1/8/2P1N3/8/6rb w - - 11 46", + "8/3k4/4p1R1/2P3P1/5K2/2P1N3/8/4r2b w - - 13 47", + "8/3k4/2b1p2R/2P3P1/5K2/2P1N3/8/4r3 w - - 15 48", + "8/3k4/2b1p3/2P3P1/5K2/2P1N2R/8/6r1 w - - 17 49", + "2k5/7R/2b1p3/2P3P1/5K2/2P1N3/8/6r1 w - - 19 50", + "2k5/7R/4p3/2P3P1/b1P2K2/4N3/8/6r1 w - - 1 51", + "2k5/3bR3/4p3/2P3P1/2P2K2/4N3/8/6r1 w - - 3 52", + "3k4/3b2R1/4p3/2P3P1/2P2K2/4N3/8/6r1 w - - 5 53", + "3kb3/6R1/4p1P1/2P5/2P2K2/4N3/8/6r1 w - - 1 54", + "3kb3/6R1/4p1P1/2P5/2P2KN1/8/8/2r5 w - - 3 55", + "3kb3/6R1/4p1P1/2P1N3/2P2K2/8/8/5r2 w - - 5 56", + "3kb3/6R1/4p1P1/2P1N3/2P5/4K3/8/4r3 w - - 7 57", + }, + { + "rnbq1rk1/ppp1npb1/4p1p1/3P3p/3PP3/2N2N2/PP2BPPP/R1BQ1RK1 b - - 0 8", + "rnbq1rk1/ppp1npb1/6p1/3pP2p/3P4/2N2N2/PP2BPPP/R1BQ1RK1 b - - 0 9", + "rn1q1rk1/ppp1npb1/6p1/3pP2p/3P2b1/2N2N2/PP2BPPP/R1BQR1K1 b - - 2 10", + "r2q1rk1/ppp1npb1/2n3p1/3pP2p/3P2bN/2N5/PP2BPPP/R1BQR1K1 b - - 4 11", + "r4rk1/pppqnpb1/2n3p1/3pP2p/3P2bN/2N4P/PP2BPP1/R1BQR1K1 b - - 0 12", + "r4rk1/pppqnpb1/2n3p1/3pP2p/3P3N/7P/PP2NPP1/R1BQR1K1 b - - 0 13", + "r4rk1/pppq1pb1/2n3p1/3pPN1p/3P4/7P/PP2NPP1/R1BQR1K1 b - - 0 14", + "r4rk1/ppp2pb1/2n3p1/3pPq1p/3P1N2/7P/PP3PP1/R1BQR1K1 b - - 1 15", + "r4rk1/pppq1pb1/2n3p1/3pP2p/P2P1N2/7P/1P3PP1/R1BQR1K1 b - - 0 16", + "r2n1rk1/pppq1pb1/6p1/3pP2p/P2P1N2/R6P/1P3PP1/2BQR1K1 b - - 2 17", + "r4rk1/pppq1pb1/4N1p1/3pP2p/P2P4/R6P/1P3PP1/2BQR1K1 b - - 0 18", + "r4rk1/ppp2pb1/4q1p1/3pP1Bp/P2P4/R6P/1P3PP1/3QR1K1 b - - 1 19", + "r3r1k1/ppp2pb1/4q1p1/3pP1Bp/P2P1P2/R6P/1P4P1/3QR1K1 b - - 0 20", + "r3r1k1/ppp3b1/4qpp1/3pP2p/P2P1P1B/R6P/1P4P1/3QR1K1 b - - 1 21", + "r3r1k1/ppp3b1/4q1p1/3pP2p/P4P1B/R6P/1P4P1/3QR1K1 b - - 0 22", + "r4rk1/ppp3b1/4q1p1/3pP1Bp/P4P2/R6P/1P4P1/3QR1K1 b - - 2 23", + "r4rk1/pp4b1/4q1p1/2ppP1Bp/P4P2/3R3P/1P4P1/3QR1K1 b - - 1 24", + "r4rk1/pp4b1/4q1p1/2p1P1Bp/P2p1PP1/3R3P/1P6/3QR1K1 b - - 0 25", + "r4rk1/pp4b1/4q1p1/2p1P1B1/P2p1PP1/3R4/1P6/3QR1K1 b - - 0 26", + "r5k1/pp3rb1/4q1p1/2p1P1B1/P2p1PP1/6R1/1P6/3QR1K1 b - - 2 27", + "5rk1/pp3rb1/4q1p1/2p1P1B1/P2pRPP1/6R1/1P6/3Q2K1 b - - 4 28", + "5rk1/1p3rb1/p3q1p1/P1p1P1B1/3pRPP1/6R1/1P6/3Q2K1 b - - 0 29", + "4r1k1/1p3rb1/p3q1p1/P1p1P1B1/3pRPP1/1P4R1/8/3Q2K1 b - - 0 30", + "4r1k1/5rb1/pP2q1p1/2p1P1B1/3pRPP1/1P4R1/8/3Q2K1 b - - 0 31", + "4r1k1/5rb1/pq4p1/2p1P1B1/3pRPP1/1P4R1/4Q3/6K1 b - - 1 32", + "4r1k1/1r4b1/pq4p1/2p1P1B1/3pRPP1/1P4R1/2Q5/6K1 b - - 3 33", + "4r1k1/1r4b1/1q4p1/p1p1P1B1/3p1PP1/1P4R1/2Q5/4R1K1 b - - 1 34", + "4r1k1/3r2b1/1q4p1/p1p1P1B1/2Qp1PP1/1P4R1/8/4R1K1 b - - 3 35", + "4r1k1/3r2b1/4q1p1/p1p1P1B1/2Qp1PP1/1P4R1/5K2/4R3 b - - 5 36", + "4r1k1/3r2b1/6p1/p1p1P1B1/2Pp1PP1/6R1/5K2/4R3 b - - 0 37", + "4r1k1/3r2b1/6p1/p1p1P1B1/2P2PP1/3p2R1/5K2/3R4 b - - 1 38", + "5rk1/3r2b1/6p1/p1p1P1B1/2P2PP1/3p2R1/8/3RK3 b - - 3 39", + "5rk1/6b1/6p1/p1p1P1B1/2Pr1PP1/3R4/8/3RK3 b - - 0 40", + "5rk1/3R2b1/6p1/p1p1P1B1/2r2PP1/8/8/3RK3 b - - 1 41", + "5rk1/3R2b1/6p1/p1p1P1B1/4rPP1/8/3K4/3R4 b - - 3 42", + "1r4k1/3R2b1/6p1/p1p1P1B1/4rPP1/2K5/8/3R4 b - - 5 43", + "1r4k1/3R2b1/6p1/p1p1P1B1/2K2PP1/4r3/8/3R4 b - - 7 44", + "1r3bk1/8/3R2p1/p1p1P1B1/2K2PP1/4r3/8/3R4 b - - 9 45", + "1r3bk1/8/6R1/2p1P1B1/p1K2PP1/4r3/8/3R4 b - - 0 46", + "1r3b2/5k2/R7/2p1P1B1/p1K2PP1/4r3/8/3R4 b - - 2 47", + "5b2/1r3k2/R7/2p1P1B1/p1K2PP1/4r3/8/7R b - - 4 48", + "5b2/5k2/R7/2pKP1B1/pr3PP1/4r3/8/7R b - - 6 49", + "5b2/5k2/R1K5/2p1P1B1/p2r1PP1/4r3/8/7R b - - 8 50", + "8/R4kb1/2K5/2p1P1B1/p2r1PP1/4r3/8/7R b - - 10 51", + "8/R5b1/2K3k1/2p1PPB1/p2r2P1/4r3/8/7R b - - 0 52", + "8/6R1/2K5/2p1PPk1/p2r2P1/4r3/8/7R b - - 0 53", + "8/6R1/2K5/2p1PP2/p2r1kP1/4r3/8/5R2 b - - 2 54", + "8/6R1/2K2P2/2p1P3/p2r2P1/4r1k1/8/5R2 b - - 0 55", + "8/5PR1/2K5/2p1P3/p2r2P1/4r3/6k1/5R2 b - - 0 56", + }, + { + "rn1qkb1r/p1pbpppp/5n2/8/2pP4/2N5/1PQ1PPPP/R1B1KBNR w KQkq - 0 7", + "r2qkb1r/p1pbpppp/2n2n2/8/2pP4/2N2N2/1PQ1PPPP/R1B1KB1R w KQkq - 2 8", + "r2qkb1r/p1pbpppp/5n2/8/1npPP3/2N2N2/1PQ2PPP/R1B1KB1R w KQkq - 1 9", + "r2qkb1r/p1pb1ppp/4pn2/8/1npPP3/2N2N2/1P3PPP/R1BQKB1R w KQkq - 0 10", + "r2qk2r/p1pbbppp/4pn2/8/1nBPP3/2N2N2/1P3PPP/R1BQK2R w KQkq - 1 11", + "r2q1rk1/p1pbbppp/4pn2/8/1nBPP3/2N2N2/1P3PPP/R1BQ1RK1 w - - 3 12", + "r2q1rk1/2pbbppp/p3pn2/8/1nBPPB2/2N2N2/1P3PPP/R2Q1RK1 w - - 0 13", + "r2q1rk1/2p1bppp/p3pn2/1b6/1nBPPB2/2N2N2/1P3PPP/R2QR1K1 w - - 2 14", + "r2q1rk1/4bppp/p1p1pn2/1b6/1nBPPB2/1PN2N2/5PPP/R2QR1K1 w - - 0 15", + "r4rk1/3qbppp/p1p1pn2/1b6/1nBPPB2/1PN2N2/3Q1PPP/R3R1K1 w - - 2 16", + "r4rk1/1q2bppp/p1p1pn2/1b6/1nBPPB2/1PN2N1P/3Q1PP1/R3R1K1 w - - 1 17", + "r3r1k1/1q2bppp/p1p1pn2/1b6/1nBPPB2/1PN2N1P/4QPP1/R3R1K1 w - - 3 18", + "r3r1k1/1q1nbppp/p1p1p3/1b6/1nBPPB2/1PN2N1P/4QPP1/3RR1K1 w - - 5 19", + "r3rbk1/1q1n1ppp/p1p1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/4R1K1 w - - 7 20", + "r3rbk1/1q3ppp/pnp1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/4R2K w - - 9 21", + "2r1rbk1/1q3ppp/pnp1p3/1b6/1nBPPB2/1PN2N1P/3RQPP1/1R5K w - - 11 22", + "2r1rbk1/1q4pp/pnp1pp2/1b6/1nBPPB2/1PN2N1P/4QPP1/1R1R3K w - - 0 23", + "2r1rbk1/5qpp/pnp1pp2/1b6/1nBPP3/1PN1BN1P/4QPP1/1R1R3K w - - 2 24", + "2r1rbk1/5qp1/pnp1pp1p/1b6/1nBPP3/1PN1BN1P/4QPP1/1R1R2K1 w - - 0 25", + "2r1rbk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/n3QPP1/1R1R2K1 w - - 0 26", + "r3rbk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/Q4PP1/1R1R2K1 w - - 1 27", + "rr3bk1/5qp1/pnp1pp1p/1b6/2BPP3/1P2BN1P/Q4PP1/R2R2K1 w - - 3 28", + "rr2qbk1/6p1/pnp1pp1p/1b6/2BPP3/1P2BN1P/4QPP1/R2R2K1 w - - 5 29", + "rr2qbk1/6p1/1np1pp1p/pb6/2BPP3/1P1QBN1P/5PP1/R2R2K1 w - - 0 30", + "rr2qbk1/6p1/1n2pp1p/pp6/3PP3/1P1QBN1P/5PP1/R2R2K1 w - - 0 31", + "rr2qbk1/6p1/1n2pp1p/1p1P4/p3P3/1P1QBN1P/5PP1/R2R2K1 w - - 0 32", + "rr2qbk1/3n2p1/3Ppp1p/1p6/p3P3/1P1QBN1P/5PP1/R2R2K1 w - - 1 33", + "rr3bk1/3n2p1/3Ppp1p/1p5q/pP2P3/3QBN1P/5PP1/R2R2K1 w - - 1 34", + "rr3bk1/3n2p1/3Ppp1p/1p5q/1P2P3/p2QBN1P/5PP1/2RR2K1 w - - 0 35", + "1r3bk1/3n2p1/r2Ppp1p/1p5q/1P2P3/pQ2BN1P/5PP1/2RR2K1 w - - 2 36", + "1r2qbk1/2Rn2p1/r2Ppp1p/1p6/1P2P3/pQ2BN1P/5PP1/3R2K1 w - - 4 37", + "1r2qbk1/2Rn2p1/r2Ppp1p/1pB5/1P2P3/1Q3N1P/p4PP1/3R2K1 w - - 0 38", + "1r2q1k1/2Rn2p1/r2bpp1p/1pB5/1P2P3/1Q3N1P/p4PP1/R5K1 w - - 0 39", + "1r2q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/1Q3N1P/p4PP1/R5K1 w - - 0 40", + "2r1q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 1 41", + "1r2q1k1/1R1n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 3 42", + "2r1q1k1/2Rn2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 5 43", + "1r2q1k1/1R1n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 7 44", + "1rq3k1/R2n2p1/3rpp1p/1p6/1P2P3/5N1P/Q4PP1/R5K1 w - - 9 45", + "2q3k1/Rr1n2p1/3rpp1p/1p6/1P2P3/5N1P/4QPP1/R5K1 w - - 11 46", + "Rrq3k1/3n2p1/3rpp1p/1p6/1P2P3/5N1P/4QPP1/R5K1 w - - 13 47", + }, + { + "rn1qkb1r/1pp2ppp/p4p2/3p1b2/5P2/1P2PN2/P1PP2PP/RN1QKB1R b KQkq - 1 6", + "r2qkb1r/1pp2ppp/p1n2p2/3p1b2/3P1P2/1P2PN2/P1P3PP/RN1QKB1R b KQkq - 0 7", + "r2qkb1r/1pp2ppp/p4p2/3p1b2/1n1P1P2/1P1BPN2/P1P3PP/RN1QK2R b KQkq - 2 8", + "r2qkb1r/1pp2ppp/p4p2/3p1b2/3P1P2/1P1PPN2/P5PP/RN1QK2R b KQkq - 0 9", + "r2qk2r/1pp2ppp/p2b1p2/3p1b2/3P1P2/1PNPPN2/P5PP/R2QK2R b KQkq - 2 10", + "r2qk2r/1p3ppp/p1pb1p2/3p1b2/3P1P2/1PNPPN2/P5PP/R2Q1RK1 b kq - 1 11", + "r2q1rk1/1p3ppp/p1pb1p2/3p1b2/3P1P2/1PNPPN2/P2Q2PP/R4RK1 b - - 3 12", + "r2qr1k1/1p3ppp/p1pb1p2/3p1b2/3P1P2/1P1PPN2/P2QN1PP/R4RK1 b - - 5 13", + "r3r1k1/1p3ppp/pqpb1p2/3p1b2/3P1P2/1P1PPNN1/P2Q2PP/R4RK1 b - - 7 14", + "r3r1k1/1p3ppp/pqp2p2/3p1b2/1b1P1P2/1P1PPNN1/P1Q3PP/R4RK1 b - - 9 15", + "r3r1k1/1p1b1ppp/pqp2p2/3p4/1b1P1P2/1P1PPNN1/P4QPP/R4RK1 b - - 11 16", + "2r1r1k1/1p1b1ppp/pqp2p2/3p4/1b1PPP2/1P1P1NN1/P4QPP/R4RK1 b - - 0 17", + "2r1r1k1/1p1b1ppp/pq3p2/2pp4/1b1PPP2/PP1P1NN1/5QPP/R4RK1 b - - 0 18", + "2r1r1k1/1p1b1ppp/pq3p2/2Pp4/4PP2/PPbP1NN1/5QPP/R4RK1 b - - 0 19", + "2r1r1k1/1p1b1ppp/p4p2/2Pp4/4PP2/PqbP1NN1/5QPP/RR4K1 b - - 1 20", + "2r1r1k1/1p1b1ppp/p4p2/2Pp4/q3PP2/P1bP1NN1/R4QPP/1R4K1 b - - 3 21", + "2r1r1k1/1p3ppp/p4p2/1bPP4/q4P2/P1bP1NN1/R4QPP/1R4K1 b - - 0 22", + "2r1r1k1/1p3ppp/p4p2/2PP4/q4P2/P1bb1NN1/R4QPP/2R3K1 b - - 1 23", + "2r1r1k1/1p3ppp/p2P1p2/2P5/2q2P2/P1bb1NN1/R4QPP/2R3K1 b - - 0 24", + "2rr2k1/1p3ppp/p2P1p2/2P5/2q2P2/P1bb1NN1/R4QPP/2R4K b - - 2 25", + "2rr2k1/1p3ppp/p2P1p2/2Q5/5P2/P1bb1NN1/R5PP/2R4K b - - 0 26", + "3r2k1/1p3ppp/p2P1p2/2r5/5P2/P1bb1N2/R3N1PP/2R4K b - - 1 27", + "3r2k1/1p3ppp/p2P1p2/2r5/5P2/P1b2N2/4R1PP/2R4K b - - 0 28", + "3r2k1/1p3ppp/p2P1p2/2r5/1b3P2/P4N2/4R1PP/3R3K b - - 2 29", + "3r2k1/1p2Rppp/p2P1p2/b1r5/5P2/P4N2/6PP/3R3K b - - 4 30", + "3r2k1/1R3ppp/p1rP1p2/b7/5P2/P4N2/6PP/3R3K b - - 0 31", + "3r2k1/1R3ppp/p2R1p2/b7/5P2/P4N2/6PP/7K b - - 0 32", + "6k1/1R3ppp/p2r1p2/b7/5P2/P4NP1/7P/7K b - - 0 33", + "6k1/1R3p1p/p2r1pp1/b7/5P1P/P4NP1/8/7K b - - 0 34", + "6k1/3R1p1p/pr3pp1/b7/5P1P/P4NP1/8/7K b - - 2 35", + "6k1/5p2/pr3pp1/b2R3p/5P1P/P4NP1/8/7K b - - 1 36", + "6k1/5p2/pr3pp1/7p/5P1P/P1bR1NP1/8/7K b - - 3 37", + "6k1/5p2/p1r2pp1/7p/5P1P/P1bR1NP1/6K1/8 b - - 5 38", + "6k1/5p2/p1r2pp1/b2R3p/5P1P/P4NP1/6K1/8 b - - 7 39", + "6k1/5p2/p4pp1/b2R3p/5P1P/P4NPK/2r5/8 b - - 9 40", + "6k1/2b2p2/p4pp1/7p/5P1P/P2R1NPK/2r5/8 b - - 11 41", + "6k1/2b2p2/5pp1/p6p/3N1P1P/P2R2PK/2r5/8 b - - 1 42", + "6k1/2b2p2/5pp1/p6p/3N1P1P/P1R3PK/r7/8 b - - 3 43", + "6k1/5p2/1b3pp1/p6p/5P1P/P1R3PK/r1N5/8 b - - 5 44", + "8/5pk1/1bR2pp1/p6p/5P1P/P5PK/r1N5/8 b - - 7 45", + "3b4/5pk1/2R2pp1/p4P1p/7P/P5PK/r1N5/8 b - - 0 46", + "8/4bpk1/2R2pp1/p4P1p/6PP/P6K/r1N5/8 b - - 0 47", + "8/5pk1/2R2pP1/p6p/6PP/b6K/r1N5/8 b - - 0 48", + "8/6k1/2R2pp1/p6P/7P/b6K/r1N5/8 b - - 0 49", + "8/6k1/2R2p2/p6p/7P/b5K1/r1N5/8 b - - 1 50", + "8/8/2R2pk1/p6p/7P/b4K2/r1N5/8 b - - 3 51", + "8/8/2R2pk1/p6p/7P/4NK2/rb6/8 b - - 5 52", + "2R5/8/5pk1/7p/p6P/4NK2/rb6/8 b - - 1 53", + "6R1/8/5pk1/7p/p6P/4NK2/1b6/r7 b - - 3 54", + "R7/5k2/5p2/7p/p6P/4NK2/1b6/r7 b - - 5 55", + "R7/5k2/5p2/7p/7P/p3N3/1b2K3/r7 b - - 1 56", + "8/R4k2/5p2/7p/7P/p3N3/1b2K3/7r b - - 3 57", + "8/8/5pk1/7p/R6P/p3N3/1b2K3/7r b - - 5 58", + "8/8/5pk1/7p/R6P/p7/4K3/2bN3r b - - 7 59", + "8/8/5pk1/7p/R6P/p7/4KN1r/2b5 b - - 9 60", + "8/8/5pk1/7p/R6P/p3K3/1b3N1r/8 b - - 11 61", + "8/8/R4pk1/7p/7P/p1b1K3/5N1r/8 b - - 13 62", + "8/8/5pk1/7p/7P/2b1K3/R4N1r/8 b - - 0 63", + "8/8/5pk1/7p/3K3P/8/R4N1r/4b3 b - - 2 64", + } +}; +// clang-format on + } // namespace namespace Stockfish::Benchmark { @@ -160,4 +437,76 @@ std::vector setup_bench(const std::string& currentFen, std::istream return list; } +BenchmarkSetup setup_benchmark(std::istream& is) { + // TT_SIZE_PER_THREAD is chosen such that roughly half of the hash is used all positions + // for the current sequence have been searched. + static constexpr int TT_SIZE_PER_THREAD = 128; + + static constexpr int DEFAULT_DURATION_S = 150; + + BenchmarkSetup setup{}; + + // Assign default values to missing arguments + int desiredTimeS; + + if (!(is >> setup.threads)) + setup.threads = get_hardware_concurrency(); + else + setup.originalInvocation += std::to_string(setup.threads); + + if (!(is >> setup.ttSize)) + setup.ttSize = TT_SIZE_PER_THREAD * setup.threads; + else + setup.originalInvocation += " " + std::to_string(setup.ttSize); + + if (!(is >> desiredTimeS)) + desiredTimeS = DEFAULT_DURATION_S; + else + setup.originalInvocation += " " + std::to_string(desiredTimeS); + + setup.filledInvocation += std::to_string(setup.threads) + " " + std::to_string(setup.ttSize) + + " " + std::to_string(desiredTimeS); + + auto getCorrectedTime = [&](int ply) { + // time per move is fit roughly based on LTC games + // seconds = 50/{ply+15} + // ms = 50000/{ply+15} + // with this fit 10th move gets 2000ms + // adjust for desired 10th move time + return 50000.0 / (static_cast(ply) + 15.0); + }; + + float totalTime = 0; + for (const auto& game : BenchmarkPositions) + { + setup.commands.emplace_back("ucinewgame"); + int ply = 1; + for (int i = 0; i < static_cast(game.size()); ++i) + { + const float correctedTime = getCorrectedTime(ply); + totalTime += correctedTime; + ply += 1; + } + } + + float timeScaleFactor = static_cast(desiredTimeS * 1000) / totalTime; + + for (const auto& game : BenchmarkPositions) + { + setup.commands.emplace_back("ucinewgame"); + int ply = 1; + for (const std::string& fen : game) + { + setup.commands.emplace_back("position fen " + fen); + + const int correctedTime = static_cast(getCorrectedTime(ply) * timeScaleFactor); + setup.commands.emplace_back("go movetime " + std::to_string(correctedTime)); + + ply += 1; + } + } + + return setup; +} + } // namespace Stockfish \ No newline at end of file diff --git a/src/benchmark.h b/src/benchmark.h index b1eba40f38b..eb3a52d894d 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -27,6 +27,16 @@ namespace Stockfish::Benchmark { std::vector setup_bench(const std::string&, std::istream&); +struct BenchmarkSetup { + int ttSize; + int threads; + std::vector commands; + std::string originalInvocation; + std::string filledInvocation; +}; + +BenchmarkSetup setup_benchmark(std::istream&); + } // namespace Stockfish #endif // #ifndef BENCHMARK_H_INCLUDED diff --git a/src/engine.cpp b/src/engine.cpp index b5cc3f832f5..85c84099352 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -67,12 +67,13 @@ Engine::Engine(std::optional path) : options["NumaPolicy"] << Option("auto", [this](const Option& o) { set_numa_config_from_option(o); - return numa_config_information_as_string() + "\n" + thread_binding_information_as_string(); + return numa_config_information_as_string() + "\n" + + thread_allocation_information_as_string(); }); options["Threads"] << Option(1, 1, 1024, [this](const Option&) { resize_threads(); - return thread_binding_information_as_string(); + return thread_allocation_information_as_string(); }); options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { @@ -156,6 +157,10 @@ void Engine::set_on_bestmove(std::function&& f) { + onVerifyNetworks = std::move(f); +} + void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } void Engine::set_position(const std::string& fen, const std::vector& moves) { @@ -226,8 +231,8 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } // network related void Engine::verify_networks() const { - networks->big.verify(options["EvalFile"]); - networks->small.verify(options["EvalFileSmall"]); + networks->big.verify(options["EvalFile"], onVerifyNetworks); + networks->small.verify(options["EvalFileSmall"], onVerifyNetworks); } void Engine::load_networks() { @@ -285,6 +290,8 @@ std::string Engine::visualize() const { return ss.str(); } +int Engine::get_hashfull(int maxAge) const { return tt.hashfull(maxAge); } + std::vector> Engine::get_bound_thread_count_by_numa_node() const { auto counts = threads.get_bound_thread_count_by_numa_node(); const NumaConfig& cfg = numaContext.get_numa_config(); @@ -310,15 +317,9 @@ std::string Engine::numa_config_information_as_string() const { std::string Engine::thread_binding_information_as_string() const { auto boundThreadsByNode = get_bound_thread_count_by_numa_node(); std::stringstream ss; - - size_t threadsSize = threads.size(); - ss << "Using " << threadsSize << (threadsSize > 1 ? " threads" : " thread"); - if (boundThreadsByNode.empty()) return ss.str(); - ss << " with NUMA node thread binding: "; - bool isFirst = true; for (auto&& [current, total] : boundThreadsByNode) @@ -332,4 +333,20 @@ std::string Engine::thread_binding_information_as_string() const { return ss.str(); } +std::string Engine::thread_allocation_information_as_string() const { + std::stringstream ss; + + size_t threadsSize = threads.size(); + ss << "Using " << threadsSize << (threadsSize > 1 ? " threads" : " thread"); + + auto boundThreadsByNodeStr = thread_binding_information_as_string(); + if (boundThreadsByNodeStr.empty()) + return ss.str(); + + ss << " with NUMA node thread binding: "; + ss << boundThreadsByNodeStr; + + return ss.str(); +} + } diff --git a/src/engine.h b/src/engine.h index efab1c6af83..257826935d9 100644 --- a/src/engine.h +++ b/src/engine.h @@ -81,6 +81,7 @@ class Engine { void set_on_update_full(std::function&&); void set_on_iter(std::function&&); void set_on_bestmove(std::function&&); + void set_on_verify_networks(std::function&&); // network related @@ -97,12 +98,15 @@ class Engine { const OptionsMap& get_options() const; OptionsMap& get_options(); + int get_hashfull(int maxAge = 0) const; + std::string fen() const; void flip(); std::string visualize() const; std::vector> get_bound_thread_count_by_numa_node() const; std::string get_numa_config_as_string() const; std::string numa_config_information_as_string() const; + std::string thread_allocation_information_as_string() const; std::string thread_binding_information_as_string() const; private: @@ -119,7 +123,8 @@ class Engine { TranspositionTable tt; LazyNumaReplicated networks; - Search::SearchManager::UpdateContext updateContext; + Search::SearchManager::UpdateContext updateContext; + std::function onVerifyNetworks; }; } // namespace Stockfish diff --git a/src/memory.cpp b/src/memory.cpp index ae303c5377a..47c901b4e33 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -212,6 +212,37 @@ void* aligned_large_pages_alloc(size_t allocSize) { #endif +bool has_large_pages() { + +#if defined(_WIN32) + + constexpr size_t page_size = 2 * 1024 * 1024; // 2MB page size assumed + void* mem = aligned_large_pages_alloc_windows(page_size); + if (mem == nullptr) + { + return false; + } + else + { + aligned_large_pages_free(mem); + return true; + } + +#elif defined(__linux__) + + #if defined(MADV_HUGEPAGE) + return true; + #else + return false; + #endif + +#else + + return false; + +#endif +} + // aligned_large_pages_free() will free the previously memory allocated // by aligned_large_pages_alloc(). The effect is a nop if mem == nullptr. diff --git a/src/memory.h b/src/memory.h index 3155a5aab12..eaf0261aa2f 100644 --- a/src/memory.h +++ b/src/memory.h @@ -38,6 +38,8 @@ void std_aligned_free(void* ptr); void* aligned_large_pages_alloc(size_t size); void aligned_large_pages_free(void* mem); +bool has_large_pages(); + // Frees memory which was placed there with placement new. // Works for both single objects and arrays of unknown bound. template diff --git a/src/misc.cpp b/src/misc.cpp index 664ab4b89ff..10c86b7a6e7 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -122,7 +122,7 @@ class Logger { // // For releases (non-dev builds) we only include the version number: // Stockfish version -std::string engine_info(bool to_uci) { +std::string engine_version_info() { std::stringstream ss; ss << "Stockfish " << version << std::setfill('0'); @@ -151,11 +151,14 @@ std::string engine_info(bool to_uci) { #endif } - ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)"; - return ss.str(); } +std::string engine_info(bool to_uci) { + return engine_version_info() + (to_uci ? "\nid author " : " by ") + + "the Stockfish developers (see AUTHORS file)"; +} + // Returns a string trying to describe the compiler we use std::string compiler_info() { @@ -451,7 +454,7 @@ void remove_whitespace(std::string& s) { s.erase(std::remove_if(s.begin(), s.end(), [](char c) { return std::isspace(c); }), s.end()); } -bool is_whitespace(const std::string& s) { +bool is_whitespace(std::string_view s) { return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); }); } diff --git a/src/misc.h b/src/misc.h index ce49a1f6553..21093769b76 100644 --- a/src/misc.h +++ b/src/misc.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #define stringify2(x) #x @@ -35,6 +36,7 @@ namespace Stockfish { +std::string engine_version_info(); std::string engine_info(bool to_uci = false); std::string compiler_info(); @@ -79,8 +81,8 @@ inline TimePoint now() { .count(); } -inline std::vector split(const std::string& s, const std::string& delimiter) { - std::vector res; +inline std::vector split(std::string_view s, std::string_view delimiter) { + std::vector res; if (s.empty()) return res; @@ -102,7 +104,7 @@ inline std::vector split(const std::string& s, const std::string& d } void remove_whitespace(std::string& s); -bool is_whitespace(const std::string& s); +bool is_whitespace(std::string_view s); enum SyncCout { IO_LOCK, diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index f7d2cc6ada0..a8e901a0d32 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -234,35 +234,44 @@ Network::evaluate(const Position& pos template -void Network::verify(std::string evalfilePath) const { +void Network::verify(std::string evalfilePath, + const std::function& f) const { if (evalfilePath.empty()) evalfilePath = evalFile.defaultName; if (evalFile.current != evalfilePath) { - std::string msg1 = - "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, " - "including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: " - "https://tests.stockfishchess.org/api/nn/" - + evalFile.defaultName; - std::string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; + if (f) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; + + std::string msg = "ERROR: " + msg1 + '\n' + "ERROR: " + msg2 + '\n' + "ERROR: " + msg3 + + '\n' + "ERROR: " + msg4 + '\n' + "ERROR: " + msg5 + '\n'; + + f(msg); + } + exit(EXIT_FAILURE); } - size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks; - sync_cout << "info string NNUE evaluation using " << evalfilePath << " (" - << size / (1024 * 1024) << "MiB, (" << featureTransformer->InputDimensions << ", " - << network[0].TransformedFeatureDimensions << ", " << network[0].FC_0_OUTPUTS << ", " - << network[0].FC_1_OUTPUTS << ", 1))" << sync_endl; + if (f) + { + size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks; + f("info string NNUE evaluation using " + evalfilePath + " (" + + std::to_string(size / (1024 * 1024)) + "MiB, (" + + std::to_string(featureTransformer->InputDimensions) + ", " + + std::to_string(network[0].TransformedFeatureDimensions) + ", " + + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + + ", 1))"); + } } diff --git a/src/nnue/network.h b/src/nnue/network.h index 152082552c9..95253595a2c 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -20,9 +20,11 @@ #define NETWORK_H_INCLUDED #include +#include #include #include #include +#include #include #include @@ -68,7 +70,7 @@ class Network { void hint_common_access(const Position& pos, AccumulatorCaches::Cache* cache) const; - void verify(std::string evalfilePath) const; + void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, AccumulatorCaches::Cache* cache) const; diff --git a/src/numa.h b/src/numa.h index db8359222cd..1063721e3fc 100644 --- a/src/numa.h +++ b/src/numa.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -653,7 +654,7 @@ class NumaConfig { NumaIndex n = 0; for (auto&& nodeStr : split(s, ":")) { - auto indices = indices_from_shortened_string(nodeStr); + auto indices = indices_from_shortened_string(std::string(nodeStr)); if (!indices.empty()) { for (auto idx : indices) @@ -1015,7 +1016,7 @@ class NumaConfig { if (s.empty()) return indices; - for (const std::string& ss : split(s, ",")) + for (const auto& ss : split(s, ",")) { if (ss.empty()) continue; @@ -1023,13 +1024,13 @@ class NumaConfig { auto parts = split(ss, "-"); if (parts.size() == 1) { - const CpuIndex c = CpuIndex{str_to_size_t(parts[0])}; + const CpuIndex c = CpuIndex{str_to_size_t(std::string(parts[0]))}; indices.emplace_back(c); } else if (parts.size() == 2) { - const CpuIndex cfirst = CpuIndex{str_to_size_t(parts[0])}; - const CpuIndex clast = CpuIndex{str_to_size_t(parts[1])}; + const CpuIndex cfirst = CpuIndex{str_to_size_t(std::string(parts[0]))}; + const CpuIndex clast = CpuIndex{str_to_size_t(std::string(parts[1]))}; for (size_t c = cfirst; c <= clast; ++c) { indices.emplace_back(c); diff --git a/src/tt.cpp b/src/tt.cpp index 4b55e53fdfc..507507539e5 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -193,13 +193,20 @@ void TranspositionTable::clear(ThreadPool& threads) { // Returns an approximation of the hashtable // occupation during a search. The hash is x permill full, as per UCI protocol. // Only counts entries which match the current generation. -int TranspositionTable::hashfull() const { - +int TranspositionTable::hashfull(int maxAge) const { int cnt = 0; for (int i = 0; i < 1000; ++i) for (int j = 0; j < ClusterSize; ++j) - cnt += table[i].entry[j].is_occupied() - && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; + { + if (table[i].entry[j].is_occupied()) + { + int age = (generation8 >> GENERATION_BITS) + - ((table[i].entry[j].genBound8 & GENERATION_MASK) >> GENERATION_BITS); + if (age < 0) + age += 1 << (8 - GENERATION_BITS); + cnt += age <= maxAge; + } + } return cnt / ClusterSize; } diff --git a/src/tt.h b/src/tt.h index 1bece002c7b..e7bb5c452b4 100644 --- a/src/tt.h +++ b/src/tt.h @@ -73,7 +73,7 @@ class TranspositionTable { void resize(size_t mbSize, ThreadPool& threads); // Set TT size void clear(ThreadPool& threads); // Re-initialize memory, multithreaded - int hashfull() + int hashfull(int maxAge = 0) const; // Approximate what fraction of entries (permille) have been written to during this root search void diff --git a/src/uci.cpp b/src/uci.cpp index c94f8b914cd..cfb34db791f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,7 @@ #include "benchmark.h" #include "engine.h" +#include "memory.h" #include "movegen.h" #include "position.h" #include "score.h" @@ -39,6 +41,8 @@ namespace Stockfish { +constexpr auto BenchmarkCommand = "speedtest"; + constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; template struct overload: Ts... { @@ -48,7 +52,7 @@ struct overload: Ts... { template overload(Ts...) -> overload; -void UCIEngine::print_info_string(const std::string& str) { +void UCIEngine::print_info_string(std::string_view str) { sync_cout_start(); for (auto& line : split(str, "\n")) { @@ -69,11 +73,16 @@ UCIEngine::UCIEngine(int argc, char** argv) : print_info_string(*str); }); + init_search_update_listeners(); +} + +void UCIEngine::init_search_update_listeners() { engine.set_on_iter([](const auto& i) { on_iter(i); }); engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); }); engine.set_on_update_full( [this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); }); engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); }); + engine.set_on_verify_networks([](const auto& s) { print_info_string(s); }); } void UCIEngine::loop() { @@ -117,7 +126,7 @@ void UCIEngine::loop() { { // send info strings after the go command is sent for old GUIs and python-chess print_info_string(engine.numa_config_information_as_string()); - print_info_string(engine.thread_binding_information_as_string()); + print_info_string(engine.thread_allocation_information_as_string()); go(is); } else if (token == "position") @@ -133,6 +142,8 @@ void UCIEngine::loop() { engine.flip(); else if (token == "bench") bench(is); + else if (token == BenchmarkCommand) + benchmark(is); else if (token == "d") sync_cout << engine.visualize() << sync_endl; else if (token == "eval") @@ -285,6 +296,165 @@ void UCIEngine::bench(std::istream& args) { engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); } +void UCIEngine::benchmark(std::istream& args) { + // Probably not very important for a test this long, but include for completeness and sanity. + static constexpr int NUM_WARMUP_POSITIONS = 3; + + std::string token; + uint64_t nodes = 0, cnt = 1; + uint64_t nodesSearched = 0; + + engine.set_on_update_full([&](const Engine::InfoFull& i) { nodesSearched = i.nodes; }); + + engine.set_on_iter([](const auto&) {}); + engine.set_on_update_no_moves([](const auto&) {}); + engine.set_on_bestmove([](const auto&, const auto&) {}); + engine.set_on_verify_networks([](const auto&) {}); + + Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args); + + const int numGoCommands = count_if(setup.commands.begin(), setup.commands.end(), + [](const std::string& s) { return s.find("go ") == 0; }); + + TimePoint totalTime = 0; + + // Set options once at the start. + auto ss = std::istringstream("name Threads value " + std::to_string(setup.threads)); + setoption(ss); + ss = std::istringstream("name Hash value " + std::to_string(setup.ttSize)); + setoption(ss); + ss = std::istringstream("name UCI_Chess960 value false"); + setoption(ss); + + // Warmup + for (const auto& cmd : setup.commands) + { + std::istringstream is(cmd); + is >> std::skipws >> token; + + if (token == "go") + { + // One new line is produced by the search, so omit it here + std::cerr << "\rWarmup position " << cnt++ << '/' << NUM_WARMUP_POSITIONS; + + Search::LimitsType limits = parse_limits(is); + + TimePoint elapsed = now(); + + // Run with silenced network verification + engine.go(limits); + engine.wait_for_search_finished(); + + totalTime += now() - elapsed; + + nodes += nodesSearched; + nodesSearched = 0; + } + else if (token == "position") + position(is); + else if (token == "ucinewgame") + { + engine.search_clear(); // search_clear may take a while + } + + if (cnt > NUM_WARMUP_POSITIONS) + break; + } + + std::cerr << "\n"; + + cnt = 1; + nodes = 0; + + int numHashfullReadings = 0; + constexpr int hashfullAges[] = {0, 999}; // Only normal hashfull and touched hash. + int totalHashfull[std::size(hashfullAges)] = {0}; + int maxHashfull[std::size(hashfullAges)] = {0}; + + auto updateHashfullReadings = [&]() { + numHashfullReadings += 1; + + for (int i = 0; i < static_cast(std::size(hashfullAges)); ++i) + { + const int hashfull = engine.get_hashfull(hashfullAges[i]); + maxHashfull[i] = std::max(maxHashfull[i], hashfull); + totalHashfull[i] += hashfull; + } + }; + + engine.search_clear(); // search_clear may take a while + + for (const auto& cmd : setup.commands) + { + std::istringstream is(cmd); + is >> std::skipws >> token; + + if (token == "go") + { + // One new line is produced by the search, so omit it here + std::cerr << "\rPosition " << cnt++ << '/' << numGoCommands; + + Search::LimitsType limits = parse_limits(is); + + TimePoint elapsed = now(); + + // Run with silenced network verification + engine.go(limits); + engine.wait_for_search_finished(); + + totalTime += now() - elapsed; + + updateHashfullReadings(); + + nodes += nodesSearched; + nodesSearched = 0; + } + else if (token == "position") + position(is); + else if (token == "ucinewgame") + { + engine.search_clear(); // search_clear may take a while + } + } + + totalTime = std::max(totalTime, 1); // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); + + std::cerr << "\n"; + + static_assert( + std::size(hashfullAges) == 2 && hashfullAges[0] == 0 && hashfullAges[1] == 999, + "Hardcoded for display. Would complicate the code needlessly in the current state."); + + std::string threadBinding = engine.thread_binding_information_as_string(); + if (threadBinding.empty()) + threadBinding = "none"; + + std::cerr << "===========================" + << "\nVersion : " + << engine_version_info() + // "\nCompiled by : " + << compiler_info() + << "Large pages : " << (has_large_pages() ? "yes" : "no") + << "\nUser invocation : " << BenchmarkCommand << " " + << setup.originalInvocation << "\nFilled invocation : " << BenchmarkCommand + << " " << setup.filledInvocation + << "\nAvailable processors : " << engine.get_numa_config_as_string() + << "\nThread count : " << setup.threads + << "\nThread binding : " << threadBinding + << "\nTT size [MiB] : " << setup.ttSize + << "\nHash max, avg [per mille] : " + << "\n single search : " << maxHashfull[0] << ", " + << totalHashfull[0] / numHashfullReadings + << "\n single game : " << maxHashfull[1] << ", " + << totalHashfull[1] / numHashfullReadings + << "\nTotal nodes searched : " << nodes + << "\nTotal search time [s] : " << totalTime / 1000.0 + << "\nNodes/second : " << 1000 * nodes / totalTime << std::endl; + + init_search_update_listeners(); +} void UCIEngine::setoption(std::istringstream& is) { engine.wait_for_search_finished(); diff --git a/src/uci.h b/src/uci.h index 23745f96a96..6adf74cb85a 100644 --- a/src/uci.h +++ b/src/uci.h @@ -58,10 +58,11 @@ class UCIEngine { Engine engine; CommandLine cli; - static void print_info_string(const std::string& str); + static void print_info_string(std::string_view str); void go(std::istringstream& is); void bench(std::istream& args); + void benchmark(std::istream& args); void position(std::istringstream& is); void setoption(std::istringstream& is); std::uint64_t perft(const Search::LimitsType&); @@ -70,6 +71,8 @@ class UCIEngine { static void on_update_full(const Engine::InfoFull& info, bool showWDL); static void on_iter(const Engine::InfoIter& info); static void on_bestmove(std::string_view bestmove, std::string_view ponder); + + void init_search_update_listeners(); }; } // namespace Stockfish From 56444ce1f7e2204d69c35f5826f74130adc77b2c Mon Sep 17 00:00:00 2001 From: peregrineshahin <41402573+peregrineshahin@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:09:03 +0300 Subject: [PATCH 0748/1309] Push expected cutting late moves up in the move ordering. since the passing of the LMR verification is coming from a relatively late move this means we have wasted some time trying/picking other moves, and it would make sense to push it up in the move ordering for future positions not to be as late. Passed STC: https://tests.stockfishchess.org/tests/view/66f0f69186d5ee47d953b2aa LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 34144 W: 9024 L: 8709 D: 16411 Ptnml(0-2): 137, 3875, 8732, 4192, 136 Passed LTC: https://tests.stockfishchess.org/tests/view/66f1d84a86d5ee47d953b325 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 62808 W: 16054 L: 15684 D: 31070 Ptnml(0-2): 24, 6725, 17555, 7057, 43 closes https://github.com/official-stockfish/Stockfish/pull/5608 bench: 1452807 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d87a6b9a041..7d84bd388c0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1201,8 +1201,8 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates (~1 Elo) - int bonus = value >= beta ? stat_bonus(newDepth) : -stat_malus(newDepth); - + int bonus = value >= beta ? (1 + 2 * (moveCount > depth)) * stat_bonus(newDepth) + : -stat_malus(newDepth); update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } From d6043970bd156b1d2ab6cb51e8d5cb0c6a40797c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 17 Sep 2024 14:29:55 -0700 Subject: [PATCH 0749/1309] Make Correction History Size Uniform Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 207232 W: 53834 L: 53802 D: 99596 Ptnml(0-2): 695, 24486, 53200, 24562, 673 https://tests.stockfishchess.org/tests/view/66e9f5a886d5ee47d953ada1 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 99120 W: 25264 L: 25123 D: 48733 Ptnml(0-2): 66, 10803, 27675, 10956, 60 https://tests.stockfishchess.org/tests/view/66ed7ebc86d5ee47d953b056 Passed Non-regression LTC vs #5606: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 208950 W: 53049 L: 53019 D: 102882 Ptnml(0-2): 111, 23232, 57760, 23260, 112 https://tests.stockfishchess.org/tests/view/66f1843886d5ee47d953b2f2 closes https://github.com/official-stockfish/Stockfish/pull/5609 bench 1575189 --- src/movepick.h | 32 ++++++++++++++------------------ src/search.cpp | 3 +-- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 13b9635bb2a..c5e565fe448 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -34,18 +34,14 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 -constexpr int PAWN_CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2 -constexpr int MATERIAL_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int MAJOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int MINOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int NON_PAWN_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -static_assert((PAWN_CORRECTION_HISTORY_SIZE & (PAWN_CORRECTION_HISTORY_SIZE - 1)) == 0, +static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); enum PawnHistoryType { @@ -55,24 +51,24 @@ enum PawnHistoryType { template inline int pawn_structure_index(const Position& pos) { - return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : PAWN_CORRECTION_HISTORY_SIZE) - 1); + return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); } inline int material_index(const Position& pos) { - return pos.material_key() & (MATERIAL_CORRECTION_HISTORY_SIZE - 1); + return pos.material_key() & (CORRECTION_HISTORY_SIZE - 1); } inline int major_piece_index(const Position& pos) { - return pos.major_piece_key() & (MAJOR_PIECE_CORRECTION_HISTORY_SIZE - 1); + return pos.major_piece_key() & (CORRECTION_HISTORY_SIZE - 1); } inline int minor_piece_index(const Position& pos) { - return pos.minor_piece_key() & (MINOR_PIECE_CORRECTION_HISTORY_SIZE - 1); + return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); } template inline int non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c) & (NON_PAWN_CORRECTION_HISTORY_SIZE - 1); + return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); } // StatsEntry stores the stat table value. It is usually a number but could @@ -161,23 +157,23 @@ using PawnHistory = Stats // PawnCorrectionHistory is addressed by color and pawn structure using PawnCorrectionHistory = - Stats; + Stats; // MaterialCorrectionHistory is addressed by color and material configuration using MaterialCorrectionHistory = - Stats; + Stats; // MajorPieceCorrectionHistory is addressed by color and king/major piece (Queen, Rook) positions using MajorPieceCorrectionHistory = - Stats; + Stats; // MinorPieceCorrectionHistory is addressed by color and king/minor piece (Knight, Bishop) positions using MinorPieceCorrectionHistory = - Stats; + Stats; // NonPawnCorrectionHistory is addressed by color and non-pawn material positions using NonPawnCorrectionHistory = - Stats; + Stats; // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one diff --git a/src/search.cpp b/src/search.cpp index 7d84bd388c0..4d581a85093 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -88,8 +88,7 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& 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 cv = - (99916 * pcv + 55067 * mcv + 55530 * macv + 95324 * micv + 105056 * (wnpcv + bnpcv)) - / 2097152; + (6245 * pcv + 3442 * mcv + 3471 * macv + 5958 * micv + 6566 * (wnpcv + bnpcv)) / 131072; v += cv; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } From c85f802185dd223bae1197269d17b9b1d5e935a0 Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:58:48 +0200 Subject: [PATCH 0750/1309] Tweak ttCapture reduction More reduction at shallow depth for quiet moves when ttMove is a capture. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 365728 W: 95896 L: 95090 D: 174742 Ptnml(0-2): 1283, 43133, 93262, 43867, 1319 https://tests.stockfishchess.org/tests/view/66edd35986d5ee47d953b0d5 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 200526 W: 51197 L: 50540 D: 98789 Ptnml(0-2): 119, 21952, 55462, 22613, 117 https://tests.stockfishchess.org/tests/view/66f405dc86d5ee47d953b460 closes https://github.com/official-stockfish/Stockfish/pull/5610 bench: 1269487 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4d581a85093..a206cddab3d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1157,7 +1157,7 @@ Value Search::Worker::search( // Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo) if (ttCapture && !capture) - r++; + r += 1 + (depth < 8); // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) From 2b9154882a0e924c28d4de7b98309d889334428c Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 16 Sep 2024 01:49:04 -0400 Subject: [PATCH 0751/1309] Tweak 7 eval params Values found from 120k / 120k spsa games at 30+0.3 Passed STC: https://tests.stockfishchess.org/tests/view/66ecd7ce86d5ee47d953b003 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 241312 W: 62994 L: 62373 D: 115945 Ptnml(0-2): 754, 28684, 61280, 29063, 875 Passed LTC: https://tests.stockfishchess.org/tests/view/66f1f3a286d5ee47d953b331 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 304896 W: 77580 L: 76709 D: 150607 Ptnml(0-2): 198, 33413, 84360, 34274, 203 closes https://github.com/official-stockfish/Stockfish/pull/5611 bench 1173651 --- src/evaluate.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 221ccde8b8b..087765e36cb 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -77,11 +77,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); - optimism += optimism * nnueComplexity / (smallNet ? 433 : 453); - nnue -= nnue * nnueComplexity / (smallNet ? 18815 : 17864); + optimism += optimism * nnueComplexity / (smallNet ? 430 : 474); + nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879); int material = (smallNet ? 553 : 532) * pos.count() + pos.non_pawn_material(); - v = (nnue * (73921 + material) + optimism * (8112 + material)) / (smallNet ? 68104 : 74715); + v = (nnue * (76898 + material) + optimism * (8112 + material)) / (smallNet ? 74411 : 76256); // Evaluation grain (to get more alpha-beta cuts) with randomization (for robustness) v = (v / 16) * 16 - 1 + (pos.key() & 0x2); From 0186904f53e6b9c90935cd8fe822da795ca9d333 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 29 Sep 2024 04:22:37 -0400 Subject: [PATCH 0752/1309] Remove evaluation grain Passed non-regression STC: https://tests.stockfishchess.org/tests/view/66fa345a86d5ee47d953b86e LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 39776 W: 10528 L: 10306 D: 18942 Ptnml(0-2): 134, 4674, 10063, 4870, 147 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/66facfb886d5ee47d953b8a8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 64230 W: 16484 L: 16305 D: 31441 Ptnml(0-2): 38, 7195, 17483, 7348, 51 closes https://github.com/official-stockfish/Stockfish/pull/5613 bench 1013135 --- src/evaluate.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 087765e36cb..b1c7283e3e2 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -83,9 +83,6 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int material = (smallNet ? 553 : 532) * pos.count() + pos.non_pawn_material(); v = (nnue * (76898 + material) + optimism * (8112 + material)) / (smallNet ? 74411 : 76256); - // Evaluation grain (to get more alpha-beta cuts) with randomization (for robustness) - v = (v / 16) * 16 - 1 + (pos.key() & 0x2); - // Damp down the evaluation linearly when shuffling v -= v * pos.rule50_count() / 212; From 7ac745a736a37f69632d6612d422aa3127f85509 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 2 Oct 2024 10:49:23 +0300 Subject: [PATCH 0753/1309] Refactor root history into low ply history This patch changes root history to low ply history - butterfly history for plies < 4. Doubles weight of this history for root, latter plies have lesser effect. Passed STC: https://tests.stockfishchess.org/tests/view/66f77d2386d5ee47d953b65d LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 180992 W: 47362 L: 46830 D: 86800 Ptnml(0-2): 554, 21499, 45928, 21891, 624 Passed LTC: https://tests.stockfishchess.org/tests/view/66fb557986d5ee47d953b8e5 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 42462 W: 11013 L: 10682 D: 20767 Ptnml(0-2): 33, 4518, 11795, 4855, 30 closes https://github.com/official-stockfish/Stockfish/pull/5614 Bench 1264335 --- src/movepick.cpp | 12 +++++----- src/movepick.h | 12 ++++++---- src/search.cpp | 62 +++++++++++++++++++----------------------------- src/search.h | 2 +- 4 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index f4ef0e5499b..1d1aef0f313 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -82,20 +82,20 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const ButterflyHistory* rh, + const LowPlyHistory* lph, const CapturePieceToHistory* cph, const PieceToHistory** ch, const PawnHistory* ph, - bool rn) : + int pl) : pos(p), mainHistory(mh), - rootHistory(rh), + lowPlyHistory(lph), captureHistory(cph), continuationHistory(ch), pawnHistory(ph), ttMove(ttm), depth(d), - rootNode(rn) { + ply(pl) { if (pos.checkers()) stage = EVASION_TT + !(ttm && pos.pseudo_legal(ttm)); @@ -179,8 +179,8 @@ void MovePicker::score() { : pt == ROOK && bool(to & threatenedByMinor) ? 24335 : 0); - if (rootNode) - m.value += 4 * (*rootHistory)[pos.side_to_move()][m.from_to()]; + if (ply < 4) + m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + 2 * ply); } else // Type == EVASIONS diff --git a/src/movepick.h b/src/movepick.h index c5e565fe448..8deefd140ff 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -135,6 +135,10 @@ enum StatsType { // see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) using ButterflyHistory = Stats; +// LowPlyHistory is adressed by play and move's from and to squares, used +// to improve move ordering near the root +using LowPlyHistory = Stats; + // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; @@ -195,11 +199,11 @@ class MovePicker { Move, Depth, const ButterflyHistory*, - const ButterflyHistory*, + const LowPlyHistory*, const CapturePieceToHistory*, const PieceToHistory**, const PawnHistory*, - bool); + int); MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(bool skipQuiets = false); @@ -213,7 +217,7 @@ class MovePicker { const Position& pos; const ButterflyHistory* mainHistory; - const ButterflyHistory* rootHistory; + const LowPlyHistory* lowPlyHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; @@ -222,7 +226,7 @@ class MovePicker { int stage; int threshold; Depth depth; - bool rootNode; + int ply; ExtMove moves[MAX_MOVES]; }; diff --git a/src/search.cpp b/src/search.cpp index a206cddab3d..0ed7b6a76ef 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -105,21 +105,16 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_histories(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move move, - int bonus, - bool rootNode); -void update_all_stats(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move bestMove, - Square prevSq, - ValueList& quietsSearched, - ValueList& capturesSearched, - Depth depth, - bool rootNode); +void update_quiet_histories( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + ValueList& quietsSearched, + ValueList& capturesSearched, + Depth depth); } // namespace @@ -273,7 +268,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - rootHistory.fill(0); + lowPlyHistory.fill(0); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -499,7 +494,7 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(0); - rootHistory.fill(0); + lowPlyHistory.fill(0); captureHistory.fill(-753); pawnHistory.fill(-1152); pawnCorrectionHistory.fill(0); @@ -638,7 +633,7 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth), rootNode); + update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth)); // Extra penalty for early quiet moves of // the previous ply (~1 Elo on STC, ~2 Elo on LTC) @@ -928,8 +923,8 @@ Value Search::Worker::search( (ss - 6)->continuationHistory}; - MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->rootHistory, - &thisThread->captureHistory, contHist, &thisThread->pawnHistory, rootNode); + MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory, + &thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply); value = bestValue; @@ -1355,8 +1350,7 @@ Value Search::Worker::search( // If there is a move that produces search value greater than alpha, // we update the stats of searched moves. else if (bestMove) - update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - rootNode); + update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1557,9 +1551,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Initialize a MovePicker object for the current position, and prepare to search // the moves. We presently use two stages of move generator in quiescence search: // captures, or evasions only when in check. - MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->rootHistory, - &thisThread->captureHistory, contHist, &thisThread->pawnHistory, - nodeType == Root); + MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->lowPlyHistory, + &thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply); // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta // cutoff occurs. @@ -1768,8 +1761,7 @@ void update_all_stats(const Position& pos, Square prevSq, ValueList& quietsSearched, ValueList& capturesSearched, - Depth depth, - bool rootNode) { + Depth depth) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); @@ -1780,11 +1772,11 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus, rootNode); + update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus, rootNode); + update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus); } else { @@ -1826,17 +1818,13 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Updates move sorting heuristics -void update_quiet_histories(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move move, - int bonus, - bool rootNode) { +void update_quiet_histories( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { Color us = pos.side_to_move(); workerThread.mainHistory[us][move.from_to()] << bonus; - if (rootNode) - workerThread.rootHistory[us][move.from_to()] << bonus; + if (ss->ply < 4) + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); diff --git a/src/search.h b/src/search.h index d7a909a82a8..0761328a758 100644 --- a/src/search.h +++ b/src/search.h @@ -278,7 +278,7 @@ class Worker { // Public because they need to be updatable by the stats ButterflyHistory mainHistory; - ButterflyHistory rootHistory; + LowPlyHistory lowPlyHistory; CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; From 81c1d310844e7b41caeabda0d5351ae275d799db Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:55:59 +0200 Subject: [PATCH 0754/1309] Decrease probCutBeta based on opponentWorsening Passed STC: LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 62112 W: 16305 L: 15947 D: 29860 Ptnml(0-2): 203, 7226, 15856, 7552, 219 https://tests.stockfishchess.org/tests/view/66f85fc986d5ee47d953b71e Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 129552 W: 33223 L: 32710 D: 63619 Ptnml(0-2): 94, 14250, 35573, 14767, 92 https://tests.stockfishchess.org/tests/view/66f93fef86d5ee47d953b7d2 closes https://github.com/official-stockfish/Stockfish/pull/5615 bench: 1511354 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0ed7b6a76ef..d79b452d9b4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -840,7 +840,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 189 - 53 * improving; + probCutBeta = beta + 189 - 53 * improving - 30 * opponentWorsening; if (!PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY // If value from transposition table is lower than probCutBeta, don't attempt From 6592b13d56e43c247ac8b0d6f62564b2a4ca72a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Tutkun?= Date: Fri, 4 Oct 2024 17:46:47 +0300 Subject: [PATCH 0755/1309] Introduce Continuation Correction History Continuation correction history uses last 2 move to correct static eval. ContCorrHist first introduced by @martinnovaak in Motor(https://github.com/martinnovaak/motor/pull/162). Earlier ideas using last move to correct eval is introduced by @MinusKelvin in Ice4(https://github.com/MinusKelvin/ice4/commit/45daf7d9ea64ea4efaf0d2b4e99f53e12e08c838) Passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 310144 W: 81267 L: 80538 D: 148339 Ptnml(0-2): 1160, 36607, 78834, 37286, 1185 https://tests.stockfishchess.org/tests/view/66f96cbc86d5ee47d953b7f7 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 97470 W: 24892 L: 24447 D: 48131 Ptnml(0-2): 63, 10631, 26915, 11050, 76 https://tests.stockfishchess.org/tests/view/66fd59bc86d5ee47d953b9ea closes https://github.com/official-stockfish/Stockfish/pull/5617 Bench: 1143382 --- AUTHORS | 1 + src/movepick.h | 7 +++++++ src/search.cpp | 47 ++++++++++++++++++++++++++++++++++++----------- src/search.h | 36 +++++++++++++++++++----------------- 4 files changed, 63 insertions(+), 28 deletions(-) diff --git a/AUTHORS b/AUTHORS index c0a8beebc45..725b356909d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -176,6 +176,7 @@ Ofek Shochat (OfekShochat, ghostway) Ondrej Mosnáček (WOnder93) Ondřej Mišina (AndrovT) Oskar Werkelin Ahlin +Ömer Faruk Tutkun (OmerFarukTutkun) Pablo Vazquez Panthee Pascal Romaret diff --git a/src/movepick.h b/src/movepick.h index 8deefd140ff..9a68aaa1d7a 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -145,6 +145,9 @@ using CapturePieceToHistory = Stats; +// PieceToCorrectionHistory is addressed by a move's [piece][to] +using PieceToCorrectionHistory = Stats; + // ContinuationHistory is the combined history of a given pair of moves, usually // the current one given a previous one. The nested history table is based on // PieceToHistory instead of ButterflyBoards. @@ -179,6 +182,10 @@ using MinorPieceCorrectionHistory = using NonPawnCorrectionHistory = Stats; +// ContinuationCorrectionHistory is the combined correction history of a given pair of moves +using ContinuationCorrectionHistory = + Stats; + // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one // new pseudo-legal move on every call, until there are no moves left, when diff --git a/src/search.cpp b/src/search.cpp index d79b452d9b4..c55118ece75 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -79,16 +79,23 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. -Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { +Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, 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 mcv = w.materialCorrectionHistory[us][material_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 cv = - (6245 * pcv + 3442 * mcv + 3471 * macv + 5958 * micv + 6566 * (wnpcv + bnpcv)) / 131072; + int cntcv = 1; + + if (m.is_ok()) + cntcv = int((*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]); + + const auto cv = + (5932 * pcv + 2994 * mcv + 3269 * macv + 5660 * micv + 6237 * (wnpcv + bnpcv) + cntcv * 5555) + / 131072; v += cv; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -240,7 +247,8 @@ void Search::Worker::iterative_deepening() { { (ss - i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel - (ss - i)->staticEval = VALUE_NONE; + (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; + (ss - i)->staticEval = VALUE_NONE; } for (int i = 0; i <= MAX_PLY + 2; ++i) @@ -504,6 +512,10 @@ void Search::Worker::clear() { nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); + for (auto& to : continuationCorrectionHistory) + for (auto& h : to) + h->fill(0); + for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) @@ -727,7 +739,8 @@ Value Search::Worker::search( else if (PvNode) Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); - ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); + ss->staticEval = eval = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // ttValue can be used as a better position evaluation (~7 Elo) if (ttData.value != VALUE_NONE @@ -738,7 +751,8 @@ Value Search::Worker::search( { unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); - ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); + ss->staticEval = eval = + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // Static evaluation is saved as it was before adjustment by correction history ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), @@ -793,8 +807,9 @@ Value Search::Worker::search( // Null move dynamic reduction based on depth and eval Depth R = std::min(int(eval - beta) / 209, 6) + depth / 3 + 5; - ss->currentMove = Move::null(); - ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; + ss->currentMove = Move::null(); + ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; + ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0]; pos.do_null_move(st, tt); @@ -876,6 +891,8 @@ Value Search::Worker::search( ss->currentMove = move; ss->continuationHistory = &this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; + ss->continuationCorrectionHistory = + &this->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()]; thisThread->nodes.fetch_add(1, std::memory_order_relaxed); pos.do_move(move, st); @@ -1124,7 +1141,8 @@ Value Search::Worker::search( ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; - + ss->continuationCorrectionHistory = + &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; // Step 16. Make the move @@ -1401,6 +1419,8 @@ Value Search::Worker::search( && !(bestValue >= beta && bestValue <= ss->staticEval) && !(!bestMove && bestValue >= ss->staticEval)) { + const auto m = (ss - 1)->currentMove; + 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)] @@ -1412,6 +1432,9 @@ Value Search::Worker::search( << bonus * 123 / 128; thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] << bonus * 165 / 128; + + if (m.is_ok()) + (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1507,7 +1530,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); ss->staticEval = bestValue = - to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // ttValue can be used as a better position evaluation (~13 Elo) if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY @@ -1522,7 +1545,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) ? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) : -(ss - 1)->staticEval; ss->staticEval = bestValue = - to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); + to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); } // Stand pat. Return immediately if static value is at least beta @@ -1619,6 +1642,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) ss->continuationHistory = &thisThread ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; + ss->continuationCorrectionHistory = + &thisThread->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()]; // Step 7. Make and search the move thisThread->nodes.fetch_add(1, std::memory_order_relaxed); diff --git a/src/search.h b/src/search.h index 0761328a758..a407e10589e 100644 --- a/src/search.h +++ b/src/search.h @@ -61,18 +61,19 @@ namespace Search { // shallower and deeper in the tree during the search. Each search thread has // its own array of Stack objects, indexed by the current ply. struct Stack { - Move* pv; - PieceToHistory* continuationHistory; - int ply; - Move currentMove; - Move excludedMove; - Value staticEval; - int statScore; - int moveCount; - bool inCheck; - bool ttPv; - bool ttHit; - int cutoffCnt; + Move* pv; + PieceToHistory* continuationHistory; + PieceToCorrectionHistory* continuationCorrectionHistory; + int ply; + Move currentMove; + Move excludedMove; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; + int cutoffCnt; }; @@ -284,11 +285,12 @@ class Worker { ContinuationHistory continuationHistory[2][2]; PawnHistory pawnHistory; - PawnCorrectionHistory pawnCorrectionHistory; - MaterialCorrectionHistory materialCorrectionHistory; - MajorPieceCorrectionHistory majorPieceCorrectionHistory; - MinorPieceCorrectionHistory minorPieceCorrectionHistory; - NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; + PawnCorrectionHistory pawnCorrectionHistory; + MaterialCorrectionHistory materialCorrectionHistory; + MajorPieceCorrectionHistory majorPieceCorrectionHistory; + MinorPieceCorrectionHistory minorPieceCorrectionHistory; + NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; + ContinuationCorrectionHistory continuationCorrectionHistory; private: void iterative_deepening(); From e046c4ef0d743ce57c97c0d40f17610dc2ec3c56 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Mon, 30 Sep 2024 23:53:57 -0400 Subject: [PATCH 0756/1309] Simplify evaluation scaling Set digits in adjusted eval params all to 7. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/66fc493d86d5ee47d953b94c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 57696 W: 15098 L: 14898 D: 27700 Ptnml(0-2): 205, 6784, 14678, 6968, 213 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/66fd4b9386d5ee47d953b9d5 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 93786 W: 23868 L: 23721 D: 46197 Ptnml(0-2): 55, 10322, 25993, 10467, 56 closes https://github.com/official-stockfish/Stockfish/pull/5618 Bench: 1277182 --- src/evaluate.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index b1c7283e3e2..802913a041e 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,9 +59,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); - bool smallNet = use_smallnet(pos); - int v; - + bool smallNet = use_smallnet(pos); auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) : networks.big.evaluate(pos, &caches.big); @@ -81,7 +79,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879); int material = (smallNet ? 553 : 532) * pos.count() + pos.non_pawn_material(); - v = (nnue * (76898 + material) + optimism * (8112 + material)) / (smallNet ? 74411 : 76256); + int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; // Damp down the evaluation linearly when shuffling v -= v * pos.rule50_count() / 212; From dce72913feec523f077db8e86cc5797286c6548d Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 4 Oct 2024 19:36:02 +0200 Subject: [PATCH 0757/1309] Temporarily fix clang-format mismatch closes https://github.com/official-stockfish/Stockfish/pull/5620 No functional change --- src/uci.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/uci.cpp b/src/uci.cpp index cfb34db791f..8388cad8cee 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -431,6 +431,8 @@ void UCIEngine::benchmark(std::istream& args) { if (threadBinding.empty()) threadBinding = "none"; + // clang-format off + std::cerr << "===========================" << "\nVersion : " << engine_version_info() @@ -453,6 +455,8 @@ void UCIEngine::benchmark(std::istream& args) { << "\nTotal search time [s] : " << totalTime / 1000.0 << "\nNodes/second : " << 1000 * nodes / totalTime << std::endl; + // clang-format on + init_search_update_listeners(); } From 3348603770926e9865fc3f43baaaef8de99d3014 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 4 Oct 2024 10:39:51 -0700 Subject: [PATCH 0758/1309] Simplify previous #5608 https://github.com/official-stockfish/Stockfish/pull/5608 STC: https://tests.stockfishchess.org/tests/view/66fb1bab86d5ee47d953b8cc LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 25536 W: 6797 L: 6560 D: 12179 Ptnml(0-2): 93, 2953, 6460, 3148, 114 LTC https://tests.stockfishchess.org/tests/view/66fb690e86d5ee47d953b8eb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 225114 W: 57200 L: 57188 D: 110726 Ptnml(0-2): 197, 25076, 61995, 25096, 193 closes https://github.com/official-stockfish/Stockfish/pull/5621 Bench: 1570076 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c55118ece75..34fb5a80579 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1213,8 +1213,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates (~1 Elo) - int bonus = value >= beta ? (1 + 2 * (moveCount > depth)) * stat_bonus(newDepth) - : -stat_malus(newDepth); + int bonus = value >= beta ? 3 * stat_bonus(newDepth) : -stat_malus(newDepth); update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } From 9a21e3e9968ebdd36c24d9b2762646a76a4e448b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 5 Oct 2024 13:52:24 +0300 Subject: [PATCH 0759/1309] Simplify bestvalue formula Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 163680 W: 42689 L: 42605 D: 78386 Ptnml(0-2): 619, 19555, 41386, 19683, 597 https://tests.stockfishchess.org/tests/view/66f9451386d5ee47d953b7d9 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 96498 W: 24582 L: 24438 D: 47478 Ptnml(0-2): 62, 10642, 26718, 10744, 83 https://tests.stockfishchess.org/tests/view/66fd765786d5ee47d953ba1c closes https://github.com/official-stockfish/Stockfish/pull/5622 Bench: 1309815 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 34fb5a80579..5598b5ffb22 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1610,11 +1610,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; } - // if static exchange evaluation is low enough + // If static exchange evaluation is low enough // we can prune this move. (~2 Elo) if (!pos.see_ge(move, alpha - futilityBase)) { - bestValue = (futilityBase > alpha) ? alpha : std::max(bestValue, futilityBase); + bestValue = std::min(alpha, futilityBase); continue; } } From 76923bb6fef2982dbce201227f6a33788390ce35 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 5 Oct 2024 16:18:21 -0700 Subject: [PATCH 0760/1309] Optimize magics Reduce the size of the Magics table by half on modern cpu's and lay it out to match our access pattern. Namely we typically access the magics for the same square for both bishop and rook back to back so we want those to be in the same cache line. https://tests.stockfishchess.org/tests/view/6701c9b386d5ee47d953bcf4 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 121664 W: 31931 L: 31497 D: 58236 Ptnml(0-2): 395, 13658, 32322, 14032, 425 A similar patch minus the size reduction finished yellow https://tests.stockfishchess.org/tests/view/6695f03f4ff211be9d4ec16c LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 310688 W: 80940 L: 80746 D: 149002 Ptnml(0-2): 1119, 35032, 82846, 35230, 1117 closes https://github.com/official-stockfish/Stockfish/pull/5623 No functional change --- src/bitboard.cpp | 40 +++++++++++++++++++++++----------------- src/bitboard.h | 21 ++++++++++++--------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index a8b4e5f4464..deda6da2a5e 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -34,15 +34,14 @@ Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; -Magic RookMagics[SQUARE_NB]; -Magic BishopMagics[SQUARE_NB]; +alignas(64) Magic Magics[SQUARE_NB][2]; namespace { Bitboard RookTable[0x19000]; // To store rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks -void init_magics(PieceType pt, Bitboard table[], Magic magics[]); +void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]); // Returns the bitboard of target square for the given step // from the given square. If the step is off the board, returns empty bitboard. @@ -82,8 +81,8 @@ void Bitboards::init() { for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - init_magics(ROOK, RookTable, RookMagics); - init_magics(BISHOP, BishopTable, BishopMagics); + init_magics(ROOK, RookTable, Magics); + init_magics(BISHOP, BishopTable, Magics); for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { @@ -142,39 +141,47 @@ Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { // bitboards are used to look up attacks of sliding pieces. As a reference see // https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use // the so called "fancy" approach. -void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { +void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { +#ifndef USE_PEXT // Optimal PRNG seeds to pick the correct magics in the shortest time int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; - Bitboard occupancy[4096], reference[4096], edges, b; - int epoch[4096] = {}, cnt = 0, size = 0; + Bitboard occupancy[4096]; + int epoch[4096] = {}, cnt = 0; +#endif + Bitboard reference[4096]; + int size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { // Board edges are not considered in the relevant occupancies - edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); + Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); // Given a square 's', the mask is the bitboard of sliding attacks from // 's' computed on an empty board. The index must be big enough to contain // all the attacks for each possible subset of the mask and so is 2 power // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. - Magic& m = magics[s]; + Magic& m = magics[s][pt - BISHOP]; m.mask = sliding_attack(pt, s, 0) & ~edges; - m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); - +#ifndef USE_PEXT + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); +#endif // Set the offset for the attacks table of the square. We have individual // table sizes for each square with "Fancy Magic Bitboards". - m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size; + m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size; + size = 0; // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. - b = size = 0; + Bitboard b = 0; do { +#ifndef USE_PEXT occupancy[size] = b; +#endif reference[size] = sliding_attack(pt, s, b); if (HasPext) @@ -184,9 +191,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { b = (b - m.mask) & m.mask; } while (b); - if (HasPext) - continue; - +#ifndef USE_PEXT PRNG rng(seeds[Is64Bit][rank_of(s)]); // Find a magic for square 's' picking up an (almost) random number @@ -215,6 +220,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]) { break; } } +#endif } } } diff --git a/src/bitboard.h b/src/bitboard.h index cdff4c759bc..c4bf18b531b 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -67,27 +67,31 @@ extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square struct Magic { Bitboard mask; - Bitboard magic; Bitboard* attacks; - unsigned shift; +#ifndef USE_PEXT + Bitboard magic; + unsigned shift; +#endif // Compute the attack's index using the 'magic bitboards' approach unsigned index(Bitboard occupied) const { - if (HasPext) - return unsigned(pext(occupied, mask)); - +#ifdef USE_PEXT + return unsigned(pext(occupied, mask)); +#else if (Is64Bit) return unsigned(((occupied & mask) * magic) >> shift); unsigned lo = unsigned(occupied) & unsigned(mask); unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; +#endif } + + Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; } }; -extern Magic RookMagics[SQUARE_NB]; -extern Magic BishopMagics[SQUARE_NB]; +extern Magic Magics[SQUARE_NB][2]; constexpr Bitboard square_bb(Square s) { assert(is_ok(s)); @@ -229,9 +233,8 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) { switch (Pt) { case BISHOP : - return BishopMagics[s].attacks[BishopMagics[s].index(occupied)]; case ROOK : - return RookMagics[s].attacks[RookMagics[s].index(occupied)]; + return Magics[s][Pt - BISHOP].attacks_bb(occupied); case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); default : From d4358ddba7184aa7403d12397f2f49f5ea6364fd Mon Sep 17 00:00:00 2001 From: Mathias Parnaudeau Date: Sat, 5 Oct 2024 15:28:39 +0200 Subject: [PATCH 0761/1309] Add autodetection of ppc64 architectures That allows 'make -j profile-build' work on ppc64 architectures, setting the use of the appropriate SIMD extension, Altivec or VSX. For VSX, gcc allows to map SSE2 intrinsics and get benefit of the existing SIMD code. On PowerMac G5, using altivec provides a performance improvement of 30%. On Talos 2, using vsx provides a performance improvement of 120%. closes https://github.com/official-stockfish/Stockfish/pull/5624 No functional change --- AUTHORS | 1 + scripts/get_native_properties.sh | 18 ++++++++++++++ src/Makefile | 42 ++++++++++++++++++++++++++++++-- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 725b356909d..31a64c17e3a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -143,6 +143,7 @@ Maciej Żenczykowski (zenczykowski) Malcolm Campbell (xoto10) Mark Tenzer (31m059) marotear +Mathias Parnaudeau (mparnaudeau) Matt Ginsberg (mattginsberg) Matthew Lai (matthewlai) Matthew Sullivan (Matt14916) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index dfbfac0eab6..ed5fc9af093 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -54,6 +54,20 @@ set_arch_x86_64() { fi } +set_arch_ppc_64() { + if $(grep -q -w "altivec" /proc/cpuinfo); then + power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo) + if [ "0$power" -gt 7 ]; then + # VSX started with POWER8 + true_arch='ppc-64-vsx' + else + true_arch='ppc-64-altivec' + fi + else + true_arch='ppc-64' + fi +} + # Check the system type uname_s=$(uname -s) uname_m=$(uname -m) @@ -87,6 +101,10 @@ case $uname_s in file_os='ubuntu' true_arch='x86-32' ;; + 'ppc64'*) + file_os='ubuntu' + set_arch_ppc_64 + ;; 'aarch64') file_os='android' true_arch='armv8' diff --git a/src/Makefile b/src/Makefile index 6cb778a6822..15066781b39 100644 --- a/src/Makefile +++ b/src/Makefile @@ -98,6 +98,8 @@ VPATH = syzygy:nnue:nnue/features # avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 # vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands # vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 +# altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension +# vsx = yes/no --- -mvsx --- Use POWER VSX SIMD extension # neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture # dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions # lsx = yes/no --- -mlsx --- Use Loongson SIMD eXtension @@ -126,7 +128,7 @@ endif ifeq ($(ARCH), $(filter $(ARCH), \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ - x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \ + x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \ armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \ loongarch64 loongarch64-lsx loongarch64-lasx)) SUPPORTED_ARCH=true @@ -151,6 +153,8 @@ avxvnni = no avx512 = no vnni256 = no vnni512 = no +altivec = no +vsx = no neon = no dotprod = no arm_version = 0 @@ -360,6 +364,20 @@ ifeq ($(ARCH),ppc-64) prefetch = yes endif +ifeq ($(ARCH),ppc-64-altivec) + arch = ppc64 + popcnt = yes + prefetch = yes + altivec = yes +endif + +ifeq ($(ARCH),ppc-64-vsx) + arch = ppc64 + popcnt = yes + prefetch = yes + vsx = yes +endif + ifeq ($(findstring e2k,$(ARCH)),e2k) arch = e2k mmx = yes @@ -650,7 +668,7 @@ else endif ifeq ($(popcnt),yes) - ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64)) + ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64)) CXXFLAGS += -DUSE_POPCNT else CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT @@ -720,6 +738,20 @@ ifeq ($(mmx),yes) endif endif +ifeq ($(altivec),yes) + CXXFLAGS += -maltivec + ifeq ($(COMP),gcc) + CXXFLAGS += -mabi=altivec + endif +endif + +ifeq ($(vsx),yes) + CXXFLAGS += -mvsx + ifeq ($(COMP),gcc) + CXXFLAGS += -DNO_WARN_X86_INTRINSICS -DUSE_SSE2 + endif +endif + ifeq ($(neon),yes) CXXFLAGS += -DUSE_NEON=$(arm_version) ifeq ($(KERNEL),Linux) @@ -852,6 +884,8 @@ help: @echo "x86-32-sse2 > x86 32-bit with sse2 support" @echo "x86-32 > x86 32-bit generic (with mmx compile support)" @echo "ppc-64 > PPC 64-bit" + @echo "ppc-64-altivec > PPC 64-bit with altivec support" + @echo "ppc-64-vsx > PPC 64-bit with vsx support" @echo "ppc-32 > PPC 32-bit" @echo "armv7 > ARMv7 32-bit" @echo "armv7-neon > ARMv7 32-bit with popcnt and neon" @@ -987,6 +1021,8 @@ config-sanity: net @echo "avx512: '$(avx512)'" @echo "vnni256: '$(vnni256)'" @echo "vnni512: '$(vnni512)'" + @echo "altivec: '$(altivec)'" + @echo "vsx: '$(vsx)'" @echo "neon: '$(neon)'" @echo "dotprod: '$(dotprod)'" @echo "arm_version: '$(arm_version)'" @@ -1020,6 +1056,8 @@ config-sanity: net @test "$(avx512)" = "yes" || test "$(avx512)" = "no" @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no" @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no" + @test "$(altivec)" = "yes" || test "$(altivec)" = "no" + @test "$(vsx)" = "yes" || test "$(vsx)" = "no" @test "$(neon)" = "yes" || test "$(neon)" = "no" @test "$(lsx)" = "yes" || test "$(lsx)" = "no" @test "$(lasx)" = "yes" || test "$(lasx)" = "no" From aaadbe0572e793cea9ebdf37e32c79235e4d573b Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Wed, 9 Oct 2024 20:00:19 +0200 Subject: [PATCH 0762/1309] Introduce mean squared score for delta adjustments This patch introduces the value `meanSquaredScore`, which makes the initial delta sensitive to unstable iterative deepening scores. Passed STC: https://tests.stockfishchess.org/tests/view/66fed74286d5ee47d953bb42 LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 71104 W: 18635 L: 18262 D: 34207 Ptnml(0-2): 234, 8365, 17993, 8714, 246 Passed LTC: https://tests.stockfishchess.org/tests/view/6700088e86d5ee47d953bbe9 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 212544 W: 54238 L: 53560 D: 104746 Ptnml(0-2): 120, 23093, 59172, 23763, 124 closes https://github.com/official-stockfish/Stockfish/pull/5627 Bench: 1395505 --- src/search.cpp | 8 ++++++-- src/search.h | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5598b5ffb22..647bae764f3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -312,8 +312,8 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13797; Value avg = rootMoves[pvIdx].averageScore; - delta = 5 + avg * avg / 11797; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); @@ -1065,7 +1065,7 @@ Value Search::Worker::search( // (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. + // 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 @@ -1265,6 +1265,10 @@ Value Search::Worker::search( rm.averageScore = rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value; + rm.meanSquaredScore = rm.meanSquaredScore != -VALUE_INFINITE * VALUE_INFINITE + ? (value * std::abs(value) + rm.meanSquaredScore) / 2 + : value * std::abs(value); + // PV move or new best move? if (moveCount == 1 || value > alpha) { diff --git a/src/search.h b/src/search.h index a407e10589e..2342d9e93c4 100644 --- a/src/search.h +++ b/src/search.h @@ -91,15 +91,16 @@ struct RootMove { return m.score != score ? m.score < score : m.previousScore < previousScore; } - uint64_t effort = 0; - Value score = -VALUE_INFINITE; - Value previousScore = -VALUE_INFINITE; - Value averageScore = -VALUE_INFINITE; - Value uciScore = -VALUE_INFINITE; - bool scoreLowerbound = false; - bool scoreUpperbound = false; - int selDepth = 0; - int tbRank = 0; + uint64_t effort = 0; + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + Value averageScore = -VALUE_INFINITE; + Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE; + Value uciScore = -VALUE_INFINITE; + bool scoreLowerbound = false; + bool scoreUpperbound = false; + int selDepth = 0; + int tbRank = 0; Value tbScore; std::vector pv; }; From b261df970d5207069a06e89b48983aece1c60925 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 9 Oct 2024 20:16:14 -0700 Subject: [PATCH 0763/1309] Fix majorPieceKey Updates Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 476160 W: 124285 L: 123311 D: 228564 Ptnml(0-2): 1662, 56266, 121219, 57302, 1631 https://tests.stockfishchess.org/tests/view/66ea3dc186d5ee47d953ae07 Failed Yellow LTC: LLR: -2.94 (-2.94,2.94) <0.50,2.50> Total: 230634 W: 58525 L: 58295 D: 113814 Ptnml(0-2): 113, 25301, 64299, 25451, 153 https://tests.stockfishchess.org/tests/view/66f1825e86d5ee47d953b2ec Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 112344 W: 28590 L: 28462 D: 55292 Ptnml(0-2): 71, 12439, 31039, 12537, 86 https://tests.stockfishchess.org/tests/view/6707474486d5ee47d953bfe3 closes https://github.com/official-stockfish/Stockfish/pull/5629 Bench: 1283457 --- src/position.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/position.cpp b/src/position.cpp index f596b015355..bab7a1fcac5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -759,7 +759,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[them] -= PieceValue[captured]; st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq]; - if (type_of(pc) == QUEEN || type_of(pc) == ROOK) + if (type_of(captured) == QUEEN || type_of(captured) == ROOK) st->majorPieceKey ^= Zobrist::psq[captured][capsq]; else From 9766db8139ce8815110c15bdde8381d0564a63fa Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 12 Oct 2024 08:32:15 +0300 Subject: [PATCH 0764/1309] Make low ply history size fixed Size of low ply history should always be the same, so ensure it. closes https://github.com/official-stockfish/Stockfish/pull/5630 No functional change --- src/movepick.cpp | 2 +- src/movepick.h | 3 ++- src/search.cpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 1d1aef0f313..0649518938a 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -179,7 +179,7 @@ void MovePicker::score() { : pt == ROOK && bool(to & threatenedByMinor) ? 24335 : 0); - if (ply < 4) + if (ply < LOW_PLY_HISTORY_SIZE) m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + 2 * ply); } diff --git a/src/movepick.h b/src/movepick.h index 9a68aaa1d7a..5c312531ef1 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -37,6 +37,7 @@ namespace Stockfish { constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int LOW_PLY_HISTORY_SIZE = 4; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); @@ -137,7 +138,7 @@ using ButterflyHistory = Stats; +using LowPlyHistory = Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; diff --git a/src/search.cpp b/src/search.cpp index 647bae764f3..6e513b458ff 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1851,7 +1851,7 @@ void update_quiet_histories( Color us = pos.side_to_move(); workerThread.mainHistory[us][move.from_to()] << bonus; - if (ss->ply < 4) + if (ss->ply < LOW_PLY_HISTORY_SIZE) workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); From 656b2cb6459cf3c91f8d8ed6aa770026f77ee7b9 Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 6 Oct 2024 20:11:19 -0400 Subject: [PATCH 0765/1309] Update default main net to nn-1cedc0ffeeee.nnue Created by setting output weights (256) and biases (8) of the previous main net nn-1111cefa1111.nnue to values found with spsa after 38k / 120k games at 120+1.2 using the same method as: https://github.com/official-stockfish/Stockfish/pull/5459 nn-1111cefa1111.nnue -> nn-1cedc0ffeeee.nnue # weights changed: 185 mean: 0.0703 +/- 2.53 min: -6 max: 6 Passed STC: https://tests.stockfishchess.org/tests/view/6703589b86d5ee47d953bda1 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 101984 W: 26690 L: 26275 D: 49019 Ptnml(0-2): 375, 11944, 25926, 12385, 362 Passed LTC: https://tests.stockfishchess.org/tests/view/670542d286d5ee47d953befa LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 106224 W: 27079 L: 26618 D: 52527 Ptnml(0-2): 71, 11508, 29487, 11981, 65 closes https://github.com/official-stockfish/Stockfish/pull/5632 Bench: 1351413 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index c9041efbf84..9bd436b58c6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-1111cefa1111.nnue" +#define EvalFileDefaultNameBig "nn-1cedc0ffeeee.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 2f3e6198e878818f9f90de8cb31e287de34bed0e Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 13 Oct 2024 13:59:20 +0300 Subject: [PATCH 0766/1309] Simplify optimism divisor. Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 139360 W: 36143 L: 36033 D: 67184 Ptnml(0-2): 436, 16456, 35778, 16582, 428 https://tests.stockfishchess.org/tests/view/66fc49c786d5ee47d953b94e Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 257748 W: 65163 L: 65184 D: 127401 Ptnml(0-2): 173, 28471, 71611, 28442, 177 https://tests.stockfishchess.org/tests/view/66ff01ae86d5ee47d953bb54 Passed LTC against rebased version: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 53610 W: 13691 L: 13501 D: 26418 Ptnml(0-2): 52, 5942, 14605, 6176, 30 https://tests.stockfishchess.org/tests/view/670a9c5c86d5ee47d953c231 closes https://github.com/official-stockfish/Stockfish/pull/5633 Bench: 1282078 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 802913a041e..7c7b54a4ff8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -75,7 +75,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 / (smallNet ? 430 : 474); + optimism += optimism * nnueComplexity / 468; nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879); int material = (smallNet ? 553 : 532) * pos.count() + pos.non_pawn_material(); From bf2a0d53925da1a0d58a91ef78d577a448eb4b5a Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Mon, 14 Oct 2024 21:30:18 +0200 Subject: [PATCH 0767/1309] Simplify internal iterative reductions Passed STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 138656 W: 36182 L: 36074 D: 66400 Ptnml(0-2): 523, 16422, 35310, 16570, 503 https://tests.stockfishchess.org/tests/view/6702beb386d5ee47d953bd41 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 680844 W: 172021 L: 172480 D: 336343 Ptnml(0-2): 492, 76259, 187419, 75720, 532 https://tests.stockfishchess.org/tests/view/67042b1f86d5ee47d953be7c closes https://github.com/official-stockfish/Stockfish/pull/5634 Bench: 1169252 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 6e513b458ff..75a8c963344 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -850,7 +850,7 @@ Value Search::Worker::search( // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, // or by 1 if there is a ttMove with an upper bound. if (cutNode && depth >= 7 && (!ttData.move || ttData.bound == BOUND_UPPER)) - depth -= 1 + !ttData.move; + depth -= 2; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search From 7f386d109e1b38d530d98f81e7213a2f1b2090af Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 16 Oct 2024 03:06:58 +0300 Subject: [PATCH 0768/1309] Remove material corrHist Passed STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 80832 W: 21150 L: 20975 D: 38707 Ptnml(0-2): 283, 9531, 20598, 9736, 268 https://tests.stockfishchess.org/tests/view/670302fe86d5ee47d953bd68 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 46008 W: 11621 L: 11423 D: 22964 Ptnml(0-2): 30, 5072, 12606, 5262, 34 https://tests.stockfishchess.org/tests/view/6704074686d5ee47d953be53 Passed LTC Rebased: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 95814 W: 24340 L: 24195 D: 47279 Ptnml(0-2): 71, 10497, 26602, 10690, 47 https://tests.stockfishchess.org/tests/view/670ae1ac86d5ee47d953c262 closes https://github.com/official-stockfish/Stockfish/pull/5636 Bench: 1119774 --- src/movepick.h | 4 ---- src/search.cpp | 6 +----- src/search.h | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 5c312531ef1..6ad13397ab1 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -167,10 +167,6 @@ using PawnHistory = Stats using PawnCorrectionHistory = Stats; -// MaterialCorrectionHistory is addressed by color and material configuration -using MaterialCorrectionHistory = - Stats; - // MajorPieceCorrectionHistory is addressed by color and king/major piece (Queen, Rook) positions using MajorPieceCorrectionHistory = Stats; diff --git a/src/search.cpp b/src/search.cpp index 75a8c963344..568e147c8bf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -83,7 +83,6 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, St 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 mcv = w.materialCorrectionHistory[us][material_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)]; @@ -94,8 +93,7 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, St cntcv = int((*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]); const auto cv = - (5932 * pcv + 2994 * mcv + 3269 * macv + 5660 * micv + 6237 * (wnpcv + bnpcv) + cntcv * 5555) - / 131072; + (5932 * pcv + 3269 * macv + 5660 * micv + 6666 * (wnpcv + bnpcv) + 5555 * cntcv) / 131072; v += cv; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } @@ -506,7 +504,6 @@ void Search::Worker::clear() { captureHistory.fill(-753); pawnHistory.fill(-1152); pawnCorrectionHistory.fill(0); - materialCorrectionHistory.fill(0); majorPieceCorrectionHistory.fill(0); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); @@ -1428,7 +1425,6 @@ Value Search::Worker::search( -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] << bonus * 101 / 128; - thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus * 99 / 128; thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 157 / 128; thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 153 / 128; thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] diff --git a/src/search.h b/src/search.h index 2342d9e93c4..b599da110a2 100644 --- a/src/search.h +++ b/src/search.h @@ -287,7 +287,6 @@ class Worker { PawnHistory pawnHistory; PawnCorrectionHistory pawnCorrectionHistory; - MaterialCorrectionHistory materialCorrectionHistory; MajorPieceCorrectionHistory majorPieceCorrectionHistory; MinorPieceCorrectionHistory minorPieceCorrectionHistory; NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; From b325b2c348df02e415b6c78121e0502622d57f34 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 16 Oct 2024 13:14:13 +0300 Subject: [PATCH 0769/1309] Simplify bestValue formula Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 45888 W: 12051 L: 11841 D: 21996 Ptnml(0-2): 123, 5356, 11807, 5504, 154 https://tests.stockfishchess.org/tests/view/670bb89086d5ee47d953c2d8 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 51336 W: 13021 L: 12830 D: 25485 Ptnml(0-2): 34, 5594, 14227, 5773, 40 https://tests.stockfishchess.org/tests/view/670c587f86d5ee47d953c31b closes https://github.com/official-stockfish/Stockfish/pull/5637 Bench: 1192999 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 568e147c8bf..c398b7d2568 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1551,7 +1551,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue >= beta) { if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) - bestValue = (3 * bestValue + beta) / 4; + bestValue = (bestValue + beta) / 2; if (!ss->ttHit) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, From 2ce47573b4d3664dca4cbc4354c8c600540d16ad Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 16 Oct 2024 19:40:49 +0300 Subject: [PATCH 0770/1309] Remove -stat_malus(newDepth) Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 92544 W: 23940 L: 23778 D: 44826 Ptnml(0-2): 286, 10936, 23638, 11154, 258 https://tests.stockfishchess.org/tests/view/670c3d6986d5ee47d953c30b Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 43164 W: 10986 L: 10786 D: 21392 Ptnml(0-2): 27, 4713, 11905, 4907, 30 https://tests.stockfishchess.org/tests/view/670eda3d86d5ee47d953c51d closes https://github.com/official-stockfish/Stockfish/pull/5639 Bench: 1281912 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c398b7d2568..c78acb6c40c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1210,7 +1210,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates (~1 Elo) - int bonus = value >= beta ? 3 * stat_bonus(newDepth) : -stat_malus(newDepth); + int bonus = 2 * (value >= beta) * stat_bonus(newDepth); update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } From c15113554f53890d7944c00a70d0f2d8a78916fb Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 19 Oct 2024 16:56:02 +0200 Subject: [PATCH 0771/1309] Speedup Makefile on Windows The Makefile is notoriously slow on windows, because of new processes being spawned I believe. This pr improves it a little bit for the help and config-sanity targets, with the latter also improving `make -j build` because it depends on that. On the same machine ubuntu (wsl) is more than 3 times faster, if there are other improvements we can make I'd be happy to hear about them. Ultimately https://github.com/official-stockfish/Stockfish/pull/5543 also aims to improve this I believe, but it will take some additional time before that lands. ``` make config-sanity: patch: 6.199s master: 12.738s make help: patch: 3.1s master: 11.49s make -j build: patch: 36s master: 43.25s make -j build: master ubuntu: 10s ``` closes https://github.com/official-stockfish/Stockfish/pull/5642 No functional change --- src/Makefile | 264 ++++++++++++++++++++++++++------------------------- 1 file changed, 133 insertions(+), 131 deletions(-) diff --git a/src/Makefile b/src/Makefile index 15066781b39..4307b7c7473 100644 --- a/src/Makefile +++ b/src/Makefile @@ -851,75 +851,75 @@ endif ### ========================================================================== help: - @echo "" - @echo "To compile stockfish, type: " - @echo "" - @echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]" - @echo "" - @echo "Supported targets:" - @echo "" - @echo "help > Display architecture details" - @echo "profile-build > standard build with profile-guided optimization" - @echo "build > skip profile-guided optimization" - @echo "net > Download the default nnue nets" - @echo "strip > Strip executable" - @echo "install > Install executable" - @echo "clean > Clean up" - @echo "" - @echo "Supported archs:" - @echo "" - @echo "native > select the best architecture for the host processor (default)" - @echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" - @echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" - @echo "x86-64-avx512 > x86 64-bit with avx512 support" - @echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" - @echo "x86-64-bmi2 > x86 64-bit with bmi2 support" - @echo "x86-64-avx2 > x86 64-bit with avx2 support" - @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" - @echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" - @echo "x86-64-ssse3 > x86 64-bit with ssse3 support" - @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support" - @echo "x86-64 > x86 64-bit generic (with sse2 support)" - @echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" - @echo "x86-32-sse2 > x86 32-bit with sse2 support" - @echo "x86-32 > x86 32-bit generic (with mmx compile support)" - @echo "ppc-64 > PPC 64-bit" - @echo "ppc-64-altivec > PPC 64-bit with altivec support" - @echo "ppc-64-vsx > PPC 64-bit with vsx support" - @echo "ppc-32 > PPC 32-bit" - @echo "armv7 > ARMv7 32-bit" - @echo "armv7-neon > ARMv7 32-bit with popcnt and neon" - @echo "armv8 > ARMv8 64-bit with popcnt and neon" - @echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" - @echo "e2k > Elbrus 2000" - @echo "apple-silicon > Apple silicon ARM64" - @echo "general-64 > unspecified 64-bit" - @echo "general-32 > unspecified 32-bit" - @echo "riscv64 > RISC-V 64-bit" - @echo "loongarch64 > LoongArch 64-bit" - @echo "loongarch64-lsx > LoongArch 64-bit with SIMD eXtension" - @echo "loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension" - @echo "" - @echo "Supported compilers:" - @echo "" - @echo "gcc > GNU compiler (default)" - @echo "mingw > GNU compiler with MinGW under Windows" - @echo "clang > LLVM Clang compiler" - @echo "icx > Intel oneAPI DPC++/C++ Compiler" - @echo "ndk > Google NDK to cross-compile for Android" - @echo "" - @echo "Simple examples. If you don't know what to do, you likely want to run one of: " - @echo "" - @echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " - @echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " - @echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " - @echo "" - @echo "Advanced examples, for experienced users: " - @echo "" - @echo "make -j profile-build ARCH=x86-64-avxvnni" - @echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" - @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" - @echo "" + @echo "" && \ + echo "To compile stockfish, type: " && \ + echo "" && \ + echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]" && \ + echo "" && \ + echo "Supported targets:" && \ + echo "" && \ + echo "help > Display architecture details" && \ + echo "profile-build > standard build with profile-guided optimization" && \ + echo "build > skip profile-guided optimization" && \ + echo "net > Download the default nnue nets" && \ + echo "strip > Strip executable" && \ + echo "install > Install executable" && \ + echo "clean > Clean up" && \ + echo "" && \ + echo "Supported archs:" && \ + echo "" && \ + echo "native > select the best architecture for the host processor (default)" && \ + echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \ + echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \ + echo "x86-64-avx512 > x86 64-bit with avx512 support" && \ + echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" && \ + echo "x86-64-bmi2 > x86 64-bit with bmi2 support" && \ + echo "x86-64-avx2 > x86 64-bit with avx2 support" && \ + echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" && \ + echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" && \ + echo "x86-64-ssse3 > x86 64-bit with ssse3 support" && \ + echo "x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support" && \ + echo "x86-64 > x86 64-bit generic (with sse2 support)" && \ + echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" && \ + echo "x86-32-sse2 > x86 32-bit with sse2 support" && \ + echo "x86-32 > x86 32-bit generic (with mmx compile support)" && \ + echo "ppc-64 > PPC 64-bit" && \ + echo "ppc-64-altivec > PPC 64-bit with altivec support" && \ + echo "ppc-64-vsx > PPC 64-bit with vsx support" && \ + echo "ppc-32 > PPC 32-bit" && \ + echo "armv7 > ARMv7 32-bit" && \ + echo "armv7-neon > ARMv7 32-bit with popcnt and neon" && \ + echo "armv8 > ARMv8 64-bit with popcnt and neon" && \ + echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" && \ + echo "e2k > Elbrus 2000" && \ + echo "apple-silicon > Apple silicon ARM64" && \ + echo "general-64 > unspecified 64-bit" && \ + echo "general-32 > unspecified 32-bit" && \ + echo "riscv64 > RISC-V 64-bit" && \ + echo "loongarch64 > LoongArch 64-bit" && \ + echo "loongarch64-lsx > LoongArch 64-bit with SIMD eXtension" && \ + echo "loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension" && \ + echo "" && \ + echo "Supported compilers:" && \ + echo "" && \ + echo "gcc > GNU compiler (default)" && \ + echo "mingw > GNU compiler with MinGW under Windows" && \ + echo "clang > LLVM Clang compiler" && \ + echo "icx > Intel oneAPI DPC++/C++ Compiler" && \ + echo "ndk > Google NDK to cross-compile for Android" && \ + echo "" && \ + echo "Simple examples. If you don't know what to do, you likely want to run one of: " && \ + echo "" && \ + echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " && \ + echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " && \ + echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " && \ + echo "" && \ + echo "Advanced examples, for experienced users: " && \ + echo "" && \ + echo "make -j profile-build ARCH=x86-64-avxvnni" && \ + echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" && \ + echo "make -j build ARCH=x86-64-ssse3 COMP=clang" && \ + echo "" ifneq ($(SUPPORTED_ARCH), true) @echo "Specify a supported architecture with the ARCH option for more details" @echo "" @@ -1000,69 +1000,71 @@ all: $(EXE) .depend config-sanity: net @echo "" - @echo "Config:" - @echo "debug: '$(debug)'" - @echo "sanitize: '$(sanitize)'" - @echo "optimize: '$(optimize)'" - @echo "arch: '$(arch)'" - @echo "bits: '$(bits)'" - @echo "kernel: '$(KERNEL)'" - @echo "os: '$(OS)'" - @echo "prefetch: '$(prefetch)'" - @echo "popcnt: '$(popcnt)'" - @echo "pext: '$(pext)'" - @echo "sse: '$(sse)'" - @echo "mmx: '$(mmx)'" - @echo "sse2: '$(sse2)'" - @echo "ssse3: '$(ssse3)'" - @echo "sse41: '$(sse41)'" - @echo "avx2: '$(avx2)'" - @echo "avxvnni: '$(avxvnni)'" - @echo "avx512: '$(avx512)'" - @echo "vnni256: '$(vnni256)'" - @echo "vnni512: '$(vnni512)'" - @echo "altivec: '$(altivec)'" - @echo "vsx: '$(vsx)'" - @echo "neon: '$(neon)'" - @echo "dotprod: '$(dotprod)'" - @echo "arm_version: '$(arm_version)'" - @echo "lsx: '$(lsx)'" - @echo "lasx: '$(lasx)'" - @echo "target_windows: '$(target_windows)'" - @echo "" - @echo "Flags:" - @echo "CXX: $(CXX)" - @echo "CXXFLAGS: $(CXXFLAGS)" - @echo "LDFLAGS: $(LDFLAGS)" - @echo "" - @echo "Testing config sanity. If this fails, try 'make help' ..." - @echo "" - @test "$(debug)" = "yes" || test "$(debug)" = "no" - @test "$(optimize)" = "yes" || test "$(optimize)" = "no" - @test "$(SUPPORTED_ARCH)" = "true" - @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ + @echo "Config:" && \ + echo "debug: '$(debug)'" && \ + echo "sanitize: '$(sanitize)'" && \ + echo "optimize: '$(optimize)'" && \ + echo "arch: '$(arch)'" && \ + echo "bits: '$(bits)'" && \ + echo "kernel: '$(KERNEL)'" && \ + echo "os: '$(OS)'" && \ + echo "prefetch: '$(prefetch)'" && \ + echo "popcnt: '$(popcnt)'" && \ + echo "pext: '$(pext)'" && \ + echo "sse: '$(sse)'" && \ + echo "mmx: '$(mmx)'" && \ + echo "sse2: '$(sse2)'" && \ + echo "ssse3: '$(ssse3)'" && \ + echo "sse41: '$(sse41)'" && \ + echo "avx2: '$(avx2)'" && \ + echo "avxvnni: '$(avxvnni)'" && \ + echo "avx512: '$(avx512)'" && \ + echo "vnni256: '$(vnni256)'" && \ + echo "vnni512: '$(vnni512)'" && \ + echo "altivec: '$(altivec)'" && \ + echo "vsx: '$(vsx)'" && \ + echo "neon: '$(neon)'" && \ + echo "dotprod: '$(dotprod)'" && \ + echo "arm_version: '$(arm_version)'" && \ + echo "lsx: '$(lsx)'" && \ + echo "lasx: '$(lasx)'" && \ + echo "target_windows: '$(target_windows)'" && \ + echo "" && \ + echo "Flags:" && \ + echo "CXX: $(CXX)" && \ + echo "CXXFLAGS: $(CXXFLAGS)" && \ + echo "LDFLAGS: $(LDFLAGS)" && \ + echo "" && \ + echo "Testing config sanity. If this fails, try 'make help' ..." && \ + echo "" && \ + (test "$(debug)" = "yes" || test "$(debug)" = "no") && \ + (test "$(optimize)" = "yes" || test "$(optimize)" = "no") && \ + (test "$(SUPPORTED_ARCH)" = "true") && \ + (test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ - test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64" - @test "$(bits)" = "32" || test "$(bits)" = "64" - @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" - @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" - @test "$(pext)" = "yes" || test "$(pext)" = "no" - @test "$(sse)" = "yes" || test "$(sse)" = "no" - @test "$(mmx)" = "yes" || test "$(mmx)" = "no" - @test "$(sse2)" = "yes" || test "$(sse2)" = "no" - @test "$(ssse3)" = "yes" || test "$(ssse3)" = "no" - @test "$(sse41)" = "yes" || test "$(sse41)" = "no" - @test "$(avx2)" = "yes" || test "$(avx2)" = "no" - @test "$(avx512)" = "yes" || test "$(avx512)" = "no" - @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no" - @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no" - @test "$(altivec)" = "yes" || test "$(altivec)" = "no" - @test "$(vsx)" = "yes" || test "$(vsx)" = "no" - @test "$(neon)" = "yes" || test "$(neon)" = "no" - @test "$(lsx)" = "yes" || test "$(lsx)" = "no" - @test "$(lasx)" = "yes" || test "$(lasx)" = "no" - @test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ - || test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang" + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || \ + test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64") && \ + (test "$(bits)" = "32" || test "$(bits)" = "64") && \ + (test "$(prefetch)" = "yes" || test "$(prefetch)" = "no") && \ + (test "$(popcnt)" = "yes" || test "$(popcnt)" = "no") && \ + (test "$(pext)" = "yes" || test "$(pext)" = "no") && \ + (test "$(sse)" = "yes" || test "$(sse)" = "no") && \ + (test "$(mmx)" = "yes" || test "$(mmx)" = "no") && \ + (test "$(sse2)" = "yes" || test "$(sse2)" = "no") && \ + (test "$(ssse3)" = "yes" || test "$(ssse3)" = "no") && \ + (test "$(sse41)" = "yes" || test "$(sse41)" = "no") && \ + (test "$(avx2)" = "yes" || test "$(avx2)" = "no") && \ + (test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \ + (test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \ + (test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \ + (test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \ + (test "$(vsx)" = "yes" || test "$(vsx)" = "no") && \ + (test "$(neon)" = "yes" || test "$(neon)" = "no") && \ + (test "$(lsx)" = "yes" || test "$(lsx)" = "no") && \ + (test "$(lasx)" = "yes" || test "$(lasx)" = "no") && \ + (test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || \ + test "$(comp)" = "clang" || test "$(comp)" = "armv7a-linux-androideabi16-clang" || \ + test "$(comp)" = "aarch64-linux-android21-clang") $(EXE): $(OBJS) +$(CXX) -o $@ $(OBJS) $(LDFLAGS) From 8ef403c7869b2d3b7e480cedae97e97d3b271f56 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 21 Oct 2024 10:42:31 +0300 Subject: [PATCH 0772/1309] Small cleanup for stats adjustments After some simplifications bonuses and maluses are the same for quiet and non-quiet moves so it makes no sense to use quietMoveBonus/Malus, instead use just bonus/malus. closes https://github.com/official-stockfish/Stockfish/pull/5649 No functional change --- src/search.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c78acb6c40c..8a7bd8109f3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1791,35 +1791,35 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int quietMoveBonus = stat_bonus(depth); - int quietMoveMalus = stat_malus(depth); + int bonus = stat_bonus(depth); + int malus = stat_malus(depth); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus); + update_quiet_histories(pos, ss, workerThread, move, -malus); } 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] << quietMoveBonus; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus; } // 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, -quietMoveMalus); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus); // 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] << -quietMoveMalus; + captureHistory[moved_piece][move.to_sq()][captured] << -malus; } } From 4a9c980f3bb666648054a9710ec0346561229312 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 22 Oct 2024 14:57:07 -0700 Subject: [PATCH 0773/1309] Template Corrhist Avoids duplication of `using ... = Stats;` closes https://github.com/official-stockfish/Stockfish/pull/5650 No functional change Co-authored-by: Disservin --- src/movepick.h | 41 ++++++++++++++++++++++------------------- src/search.h | 36 ++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 6ad13397ab1..dff09f79c3e 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -146,9 +146,6 @@ using CapturePieceToHistory = Stats; -// PieceToCorrectionHistory is addressed by a move's [piece][to] -using PieceToCorrectionHistory = Stats; - // ContinuationHistory is the combined history of a given pair of moves, usually // the current one given a previous one. The nested history table is based on // PieceToHistory instead of ButterflyBoards. @@ -162,26 +159,32 @@ using PawnHistory = Stats // positions and their search score. It is used to improve the static evaluation // used by some search heuristics. // see https://www.chessprogramming.org/Static_Evaluation_Correction_History +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 + PieceTo, // By [piece][to] move + Continuation, // Combined history of move pairs +}; -// PawnCorrectionHistory is addressed by color and pawn structure -using PawnCorrectionHistory = - Stats; - -// MajorPieceCorrectionHistory is addressed by color and king/major piece (Queen, Rook) positions -using MajorPieceCorrectionHistory = - Stats; +template +struct CorrHistTypedef { + using type = Stats; +}; -// MinorPieceCorrectionHistory is addressed by color and king/minor piece (Knight, Bishop) positions -using MinorPieceCorrectionHistory = - Stats; +template<> +struct CorrHistTypedef { + using type = Stats; +}; -// NonPawnCorrectionHistory is addressed by color and non-pawn material positions -using NonPawnCorrectionHistory = - Stats; +template<> +struct CorrHistTypedef { + using type = Stats::type, NOT_USED, PIECE_NB, SQUARE_NB>; +}; -// ContinuationCorrectionHistory is the combined correction history of a given pair of moves -using ContinuationCorrectionHistory = - Stats; +template +using CorrectionHistory = typename CorrHistTypedef::type; // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one diff --git a/src/search.h b/src/search.h index b599da110a2..751a398483f 100644 --- a/src/search.h +++ b/src/search.h @@ -61,19 +61,19 @@ namespace Search { // shallower and deeper in the tree during the search. Each search thread has // its own array of Stack objects, indexed by the current ply. struct Stack { - Move* pv; - PieceToHistory* continuationHistory; - PieceToCorrectionHistory* continuationCorrectionHistory; - int ply; - Move currentMove; - Move excludedMove; - Value staticEval; - int statScore; - int moveCount; - bool inCheck; - bool ttPv; - bool ttHit; - int cutoffCnt; + Move* pv; + PieceToHistory* continuationHistory; + CorrectionHistory* continuationCorrectionHistory; + int ply; + Move currentMove; + Move excludedMove; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; + int cutoffCnt; }; @@ -286,11 +286,11 @@ class Worker { ContinuationHistory continuationHistory[2][2]; PawnHistory pawnHistory; - PawnCorrectionHistory pawnCorrectionHistory; - MajorPieceCorrectionHistory majorPieceCorrectionHistory; - MinorPieceCorrectionHistory minorPieceCorrectionHistory; - NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; - ContinuationCorrectionHistory continuationCorrectionHistory; + CorrectionHistory pawnCorrectionHistory; + CorrectionHistory majorPieceCorrectionHistory; + CorrectionHistory minorPieceCorrectionHistory; + CorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; + CorrectionHistory continuationCorrectionHistory; private: void iterative_deepening(); From 8681d3c2b38096120829c2fb47acaeb32b2fbf8b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 25 Oct 2024 15:28:10 +0300 Subject: [PATCH 0774/1309] Simplify Time Management Formula Decreasing the number of operations Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 38880 W: 10038 L: 9823 D: 19019 Ptnml(0-2): 92, 4334, 10395, 4505, 114 https://tests.stockfishchess.org/tests/view/67112bf586d5ee47d953c6be Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 242844 W: 61425 L: 61431 D: 119988 Ptnml(0-2): 145, 25175, 70797, 25151, 154 https://tests.stockfishchess.org/tests/view/6712387486d5ee47d953c737 closes https://github.com/official-stockfish/Stockfish/pull/5655 Bench: 1281912 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8a7bd8109f3..c7a8c28b474 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -443,9 +443,9 @@ void Search::Worker::iterative_deepening() { { int nodesEffort = rootMoves[0].effort * 100 / std::max(size_t(1), size_t(nodes)); - double fallingEval = (1067 + 223 * (mainThread->bestPreviousAverageScore - bestValue) - + 97 * (mainThread->iterValue[iterIdx] - bestValue)) - / 10000.0; + double fallingEval = (11 + 2 * (mainThread->bestPreviousAverageScore - bestValue) + + (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; fallingEval = std::clamp(fallingEval, 0.580, 1.667); // If the bestMove is stable over several iterations, reduce time accordingly From 24c57793e1917b2110d1e3ce8edc634f43eadc67 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 30 Oct 2024 21:30:21 +0900 Subject: [PATCH 0775/1309] Remove moveCountPruning in search.cpp The definition of moveCountPruning may cause confusion by implying that the variable is unconstrained. However, once it is set to true, it should not be reset to false, otherwise it would break the internal logic of MovePicker. Several patches have overlooked this constraint. For example: https://tests.stockfishchess.org/tests/view/671e7c0486d5ee47d953d226 https://tests.stockfishchess.org/tests/view/66a1de7b4ff211be9d4eccea The implementation approach was suggested by Disservin. Passed non-regression STC: LLR: 3.02 (-2.94,2.94) <-1.75,0.25> Total: 180672 W: 47072 L: 47006 D: 86594 Ptnml(0-2): 536, 19482, 50247, 19522, 549 https://tests.stockfishchess.org/tests/view/6720df6f86d5ee47d953d542 closes https://github.com/official-stockfish/Stockfish/pull/5661 No functional change --- src/movepick.cpp | 4 +++- src/movepick.h | 4 +++- src/search.cpp | 8 ++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0649518938a..2a1fb837348 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -216,7 +216,7 @@ Move MovePicker::select(Pred filter) { // This is the most important method of the MovePicker class. We emit one // new pseudo-legal move on every call until there are no more moves left, // picking the move with the highest score from a list of generated moves. -Move MovePicker::next_move(bool skipQuiets) { +Move MovePicker::next_move() { auto quiet_threshold = [](Depth d) { return -3560 * d; }; @@ -322,4 +322,6 @@ Move MovePicker::next_move(bool skipQuiets) { return Move::none(); // Silence warning } +void MovePicker::skip_quiet_moves() { skipQuiets = true; } + } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index dff09f79c3e..f8f84d02474 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -212,7 +212,8 @@ class MovePicker { const PawnHistory*, int); MovePicker(const Position&, Move, int, const CapturePieceToHistory*); - Move next_move(bool skipQuiets = false); + Move next_move(); + void skip_quiet_moves(); private: template @@ -234,6 +235,7 @@ class MovePicker { int threshold; Depth depth; int ply; + bool skipQuiets = false; ExtMove moves[MAX_MOVES]; }; diff --git a/src/search.cpp b/src/search.cpp index c7a8c28b474..4864057c1b1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -942,12 +942,11 @@ Value Search::Worker::search( value = bestValue; - int moveCount = 0; - bool moveCountPruning = false; + int moveCount = 0; // Step 13. Loop through all pseudo-legal moves until no moves remain // or a beta cutoff occurs. - while ((move = mp.next_move(moveCountPruning)) != Move::none()) + while ((move = mp.next_move()) != Move::none()) { assert(move.is_ok()); @@ -993,7 +992,8 @@ Value Search::Worker::search( if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) - moveCountPruning = moveCount >= futility_move_count(improving, depth); + if (moveCount >= futility_move_count(improving, depth)) + mp.skip_quiet_moves(); // Reduced depth of the next LMR search int lmrDepth = newDepth - r; From 16fee2a7da25c6d0267930eb9677862cb1f009c7 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 23 Oct 2024 03:37:32 -0700 Subject: [PATCH 0776/1309] Cleanup TT::hashfull() closes https://github.com/official-stockfish/Stockfish/pull/5651 No functional change --- src/tt.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 507507539e5..75689562d61 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -194,19 +194,12 @@ void TranspositionTable::clear(ThreadPool& threads) { // occupation during a search. The hash is x permill full, as per UCI protocol. // Only counts entries which match the current generation. int TranspositionTable::hashfull(int maxAge) const { - int cnt = 0; + int maxAgeInternal = maxAge << GENERATION_BITS; + int cnt = 0; for (int i = 0; i < 1000; ++i) for (int j = 0; j < ClusterSize; ++j) - { - if (table[i].entry[j].is_occupied()) - { - int age = (generation8 >> GENERATION_BITS) - - ((table[i].entry[j].genBound8 & GENERATION_MASK) >> GENERATION_BITS); - if (age < 0) - age += 1 << (8 - GENERATION_BITS); - cnt += age <= maxAge; - } - } + cnt += table[i].entry[j].is_occupied() + && table[i].entry[j].relative_age(generation8) <= maxAgeInternal; return cnt / ClusterSize; } From c2611efe5c317969b583a5ff81352439f905e722 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 24 Oct 2024 13:16:49 -0700 Subject: [PATCH 0777/1309] Move history code to a separate header Since no correction histories are ever used inside Movepick, and many existing histories are closely integrated into search, it might be more logical to separate them into their own header. PR based on #5650 closes https://github.com/official-stockfish/Stockfish/pull/5652 No functional change --- src/Makefile | 2 +- src/history.h | 185 +++++++++++++++++++++++++++++++++++++++++++++++ src/movepick.cpp | 2 + src/movepick.h | 163 +---------------------------------------- src/search.cpp | 1 + src/search.h | 2 +- 6 files changed, 192 insertions(+), 163 deletions(-) create mode 100644 src/history.h diff --git a/src/Makefile b/src/Makefile index 4307b7c7473..e7f8ce556bb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,7 +57,7 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp memory.cpp -HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ +HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ diff --git a/src/history.h b/src/history.h new file mode 100644 index 00000000000..8d14a7a7cb1 --- /dev/null +++ b/src/history.h @@ -0,0 +1,185 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef HISTORY_H_INCLUDED +#define HISTORY_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: keep + +#include "position.h" + +namespace Stockfish { + +constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int CORRECTION_HISTORY_LIMIT = 1024; +constexpr int LOW_PLY_HISTORY_SIZE = 4; + +static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, + "PAWN_HISTORY_SIZE has to be a power of 2"); + +static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, + "CORRECTION_HISTORY_SIZE has to be a power of 2"); + +enum PawnHistoryType { + Normal, + Correction +}; + +template +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); +} + +template +inline int non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); +} + +// StatsEntry stores the stat table value. It is usually a number but could +// be a move or even a nested history. We use a class instead of a naked value +// to directly call history update operator<<() on the entry so to use stats +// tables at caller sites as simple multi-dim arrays. +template +class StatsEntry { + + T entry; + + public: + void operator=(const T& v) { entry = v; } + T* operator&() { return &entry; } + T* operator->() { return &entry; } + operator const T&() const { return entry; } + + void operator<<(int bonus) { + static_assert(D <= std::numeric_limits::max(), "D overflows T"); + + // Make sure that bonus is in range [-D, D] + int clampedBonus = std::clamp(bonus, -D, D); + entry += clampedBonus - entry * std::abs(clampedBonus) / D; + + assert(std::abs(entry) <= D); + } +}; + +// Stats is a generic N-dimensional array used to store various statistics. +// The first template parameter T is the base type of the array, and the second +// template parameter D limits the range of updates in [-D, D] when we update +// values with the << operator, while the last parameters (Size and Sizes) +// encode the dimensions of the array. +template +struct Stats: public std::array, Size> { + using stats = Stats; + + void fill(const T& v) { + + // For standard-layout 'this' points to the first struct member + assert(std::is_standard_layout_v); + + using entry = StatsEntry; + entry* p = reinterpret_cast(this); + std::fill(p, p + sizeof(*this) / sizeof(entry), v); + } +}; + +template +struct Stats: public std::array, Size> {}; + +// In stats table, D=0 means that the template parameter is not used +enum StatsParams { + NOT_USED = 0 +}; +enum StatsType { + NoCaptures, + Captures +}; + +// ButterflyHistory records how often quiet moves have been successful or unsuccessful +// during the current search, and is used for reduction and move ordering decisions. +// It uses 2 tables (one for each color) indexed by the move's from and to squares, +// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) +using ButterflyHistory = Stats; + +// LowPlyHistory is adressed by play and move's from and to squares, used +// to improve move ordering near the root +using LowPlyHistory = Stats; + +// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +using CapturePieceToHistory = Stats; + +// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +using PieceToHistory = Stats; + +// ContinuationHistory is the combined history of a given pair of moves, usually +// the current one given a previous one. The nested history table is based on +// PieceToHistory instead of ButterflyBoards. +// (~63 elo) +using ContinuationHistory = Stats; + +// PawnHistory is addressed by the pawn structure and a move's [piece][to] +using PawnHistory = Stats; + +// Correction histories record differences between the static evaluation of +// positions and their search score. It is used to improve the static evaluation +// used by some search heuristics. +// see https://www.chessprogramming.org/Static_Evaluation_Correction_History +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 + PieceTo, // By [piece][to] move + Continuation, // Combined history of move pairs +}; + +template +struct CorrHistTypedef { + using type = Stats; +}; + +template<> +struct CorrHistTypedef { + using type = Stats; +}; + +template<> +struct CorrHistTypedef { + using type = Stats::type, NOT_USED, PIECE_NB, SQUARE_NB>; +}; + +template +using CorrectionHistory = typename CorrHistTypedef::type; + +} // namespace Stockfish + +#endif // #ifndef HISTORY_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp index 2a1fb837348..720f2e031ea 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -19,7 +19,9 @@ #include "movepick.h" #include +#include #include +#include #include #include "bitboard.h" diff --git a/src/movepick.h b/src/movepick.h index f8f84d02474..0278b70ec82 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -19,172 +19,13 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED -#include -#include -#include -#include -#include -#include -#include -#include // IWYU pragma: keep - +#include "history.h" #include "movegen.h" -#include "position.h" #include "types.h" namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_LIMIT = 1024; -constexpr int LOW_PLY_HISTORY_SIZE = 4; - -static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, - "PAWN_HISTORY_SIZE has to be a power of 2"); - -static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, - "CORRECTION_HISTORY_SIZE has to be a power of 2"); - -enum PawnHistoryType { - Normal, - Correction -}; - -template -inline int pawn_structure_index(const Position& pos) { - return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); -} - -inline int material_index(const Position& pos) { - return pos.material_key() & (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); -} - -template -inline int non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); -} - -// StatsEntry stores the stat table value. It is usually a number but could -// be a move or even a nested history. We use a class instead of a naked value -// to directly call history update operator<<() on the entry so to use stats -// tables at caller sites as simple multi-dim arrays. -template -class StatsEntry { - - T entry; - - public: - void operator=(const T& v) { entry = v; } - T* operator&() { return &entry; } - T* operator->() { return &entry; } - operator const T&() const { return entry; } - - void operator<<(int bonus) { - static_assert(D <= std::numeric_limits::max(), "D overflows T"); - - // Make sure that bonus is in range [-D, D] - int clampedBonus = std::clamp(bonus, -D, D); - entry += clampedBonus - entry * std::abs(clampedBonus) / D; - - assert(std::abs(entry) <= D); - } -}; - -// Stats is a generic N-dimensional array used to store various statistics. -// The first template parameter T is the base type of the array, and the second -// template parameter D limits the range of updates in [-D, D] when we update -// values with the << operator, while the last parameters (Size and Sizes) -// encode the dimensions of the array. -template -struct Stats: public std::array, Size> { - using stats = Stats; - - void fill(const T& v) { - - // For standard-layout 'this' points to the first struct member - assert(std::is_standard_layout_v); - - using entry = StatsEntry; - entry* p = reinterpret_cast(this); - std::fill(p, p + sizeof(*this) / sizeof(entry), v); - } -}; - -template -struct Stats: public std::array, Size> {}; - -// In stats table, D=0 means that the template parameter is not used -enum StatsParams { - NOT_USED = 0 -}; -enum StatsType { - NoCaptures, - Captures -}; - -// ButterflyHistory records how often quiet moves have been successful or unsuccessful -// during the current search, and is used for reduction and move ordering decisions. -// It uses 2 tables (one for each color) indexed by the move's from and to squares, -// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) -using ButterflyHistory = Stats; - -// LowPlyHistory is adressed by play and move's from and to squares, used -// to improve move ordering near the root -using LowPlyHistory = Stats; - -// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] -using CapturePieceToHistory = Stats; - -// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] -using PieceToHistory = Stats; - -// ContinuationHistory is the combined history of a given pair of moves, usually -// the current one given a previous one. The nested history table is based on -// PieceToHistory instead of ButterflyBoards. -// (~63 elo) -using ContinuationHistory = Stats; - -// PawnHistory is addressed by the pawn structure and a move's [piece][to] -using PawnHistory = Stats; - -// Correction histories record differences between the static evaluation of -// positions and their search score. It is used to improve the static evaluation -// used by some search heuristics. -// see https://www.chessprogramming.org/Static_Evaluation_Correction_History -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 - PieceTo, // By [piece][to] move - Continuation, // Combined history of move pairs -}; - -template -struct CorrHistTypedef { - using type = Stats; -}; - -template<> -struct CorrHistTypedef { - using type = Stats; -}; - -template<> -struct CorrHistTypedef { - using type = Stats::type, NOT_USED, PIECE_NB, SQUARE_NB>; -}; - -template -using CorrectionHistory = typename CorrHistTypedef::type; +class Position; // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one diff --git a/src/search.cpp b/src/search.cpp index 4864057c1b1..d6914da0b67 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -34,6 +34,7 @@ #include #include "evaluate.h" +#include "history.h" #include "misc.h" #include "movegen.h" #include "movepick.h" diff --git a/src/search.h b/src/search.h index 751a398483f..b618855b9fc 100644 --- a/src/search.h +++ b/src/search.h @@ -31,8 +31,8 @@ #include #include +#include "history.h" #include "misc.h" -#include "movepick.h" #include "nnue/network.h" #include "nnue/nnue_accumulator.h" #include "numa.h" From ecf5646f6e8446a3498aca04723dcee2b74f2d77 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Fri, 1 Nov 2024 02:04:35 +0300 Subject: [PATCH 0778/1309] Refine definition of improving This patch also allows improving flag to be true if static evaluation of the position is good enough. Passed STC: https://tests.stockfishchess.org/tests/view/6720906086d5ee47d953d4d0 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 34816 W: 9172 L: 8858 D: 16786 Ptnml(0-2): 113, 3988, 8887, 4312, 108 Passed LTC: https://tests.stockfishchess.org/tests/view/6721162686d5ee47d953d597 LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 145374 W: 37118 L: 36574 D: 71682 Ptnml(0-2): 91, 15875, 40212, 16417, 92 closes https://github.com/official-stockfish/Stockfish/pull/5662 Bench: 1518856 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index d6914da0b67..1b9b745cace 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -795,6 +795,8 @@ Value Search::Worker::search( && eval < VALUE_TB_WIN_IN_MAX_PLY) return beta + (eval - beta) / 3; + improving |= ss->staticEval >= beta + 100; + // Step 9. Null move search with verification search (~35 Elo) if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta && ss->staticEval >= beta - 23 * depth + 400 && !excludedMove && pos.non_pawn_material(us) From 54cf226604cfc9d17f432fa0b5bca56277e5561c Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 1 Nov 2024 13:54:50 +0300 Subject: [PATCH 0779/1309] Revert VLTC regression from #5634 https://tests.stockfishchess.org/tests/view/671bf61b86d5ee47d953cf23 And thanks to @xu-shawn for suggesting running a VLTC regress test since depth modifications affect scaling. Also, the LTC was showing a slight regress after 680+k games ~= -0.34 , for reference: https://tests.stockfishchess.org/tests/view/67042b1f86d5ee47d953be7c closes https://github.com/official-stockfish/Stockfish/pull/5663 Bench: 1307308 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1b9b745cace..5c6a62c8b47 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -850,7 +850,7 @@ Value Search::Worker::search( // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, // or by 1 if there is a ttMove with an upper bound. if (cutNode && depth >= 7 && (!ttData.move || ttData.bound == BOUND_UPPER)) - depth -= 2; + depth -= 1 + !ttData.move; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search From f77bac3dcab84a31238289ade55f9d85b650ac1a Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 3 Nov 2024 16:50:47 -0800 Subject: [PATCH 0780/1309] Remove stale Cache::clear() method closes https://github.com/official-stockfish/Stockfish/pull/5666 No functional change --- src/nnue/nnue_accumulator.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index b8dcf1e480f..b92901e4a29 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -80,11 +80,6 @@ struct AccumulatorCaches { entry.clear(network.featureTransformer->biases); } - void clear(const BiasType* biases) { - for (auto& entry : entries) - entry.clear(biases); - } - std::array& operator[](Square sq) { return entries[sq]; } std::array, SQUARE_NB> entries; From cc5c67c564f52a0611ba38d04af02636291280b6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 27 Oct 2024 14:07:03 -0700 Subject: [PATCH 0781/1309] Introduce Fractional LMR Tuning Run (90k Games): https://tests.stockfishchess.org/tests/view/67202b1c86d5ee47d953d442 Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 241024 W: 62616 L: 62001 D: 116407 Ptnml(0-2): 716, 28231, 62015, 28822, 728 https://tests.stockfishchess.org/tests/view/6725196786d5ee47d953d9f2 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 92532 W: 23678 L: 23246 D: 45608 Ptnml(0-2): 45, 9981, 25797, 10383, 60 https://tests.stockfishchess.org/tests/view/6727d3cb86d5ee47d953db9d closes https://github.com/official-stockfish/Stockfish/pull/5667 Bench: 1066071 --- src/search.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5c6a62c8b47..c807f1bdf88 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,7 +999,7 @@ Value Search::Worker::search( mp.skip_quiet_moves(); // Reduced depth of the next LMR search - int lmrDepth = newDepth - r; + int lmrDepth = newDepth - r / 1024; if (capture || givesCheck) { @@ -1156,36 +1156,36 @@ Value Search::Worker::search( // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1 + (ttData.value > alpha) + (ttData.depth >= depth); + r -= 1024 + (ttData.value > alpha) * 1024 + (ttData.depth >= depth) * 1024; // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) - r--; + r -= 1024; // These reduction adjustments have no proven non-linear scaling // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2 - (ttData.depth >= depth && ss->ttPv); + r += 2518 - (ttData.depth >= depth && ss->ttPv) * 991; // Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo) if (ttCapture && !capture) - r += 1 + (depth < 8); + r += 1043 + (depth < 8) * 999; // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) - r += 1 + allNode; + r += 938 + allNode * 960; // For first picked move (ttMove) reduce reduction (~3 Elo) else if (move == ttData.move) - r -= 2; + r -= 1879; ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - 4410; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 11016; + r -= ss->statScore * 1287 / 16384; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1) @@ -1195,7 +1195,7 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r, newDepth + !allNode)); + Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode)); value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); @@ -1223,10 +1223,11 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present (~6 Elo) if (!ttData.move) - r += 2; + r += 2037; // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) - value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3), !cutNode); + value = + -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 2983), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1700,7 +1701,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 + 1239 - delta * 795 / rootDelta) / 1024 + (!i && reductionScale > 1341); + return (reductionScale + 1239 - delta * 795 / rootDelta) + (!i && reductionScale > 1341) * 1135; } // elapsed() returns the time elapsed since the search started. If the From 3d084e9164a96bff265b3afb32f2da0aa4e97c47 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Wed, 13 Nov 2024 20:18:36 +0100 Subject: [PATCH 0782/1309] VVLTC Search Tune A single tuning run of 190k games was conducted: https://tests.stockfishchess.org/tests/view/670f3e3786d5ee47d953c554. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/672344dc86d5ee47d953d8c3 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 56768 W: 14615 L: 14323 D: 27830 Ptnml(0-2): 3, 5152, 17789, 5430, 10 Passed VVLTC 2nd sprt (rebased): https://tests.stockfishchess.org/tests/view/6726d83786d5ee47d953db03 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 57884 W: 14885 L: 14554 D: 28445 Ptnml(0-2): 5, 5300, 17999, 5635, 3 closes https://github.com/official-stockfish/Stockfish/pull/5669 Bench: 920336 --- src/search.cpp | 102 ++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c807f1bdf88..2a2331cb40e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -67,7 +67,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 118 - 33 * noTtCutNode; + Value futilityMult = 109 - 27 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; @@ -94,16 +94,16 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, St cntcv = int((*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]); const auto cv = - (5932 * pcv + 3269 * macv + 5660 * micv + 6666 * (wnpcv + bnpcv) + 5555 * cntcv) / 131072; + (6384 * pcv + 3583 * macv + 6492 * micv + 6725 * (wnpcv + bnpcv) + cntcv * 5880) / 131072; v += cv; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(179 * d - 108, 1598); } +int stat_bonus(Depth d) { return std::min(168 * d - 100, 1718); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(820 * d - 261, 2246); } +int stat_malus(Depth d) { return std::min(768 * d - 257, 2351); } // 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); } @@ -311,13 +311,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13797; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13461; 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 (~4 Elo) - optimism[us] = 132 * avg / (std::abs(avg) + 89); + optimism[us] = 150 * avg / (std::abs(avg) + 85); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -502,8 +502,8 @@ void Search::Worker::iterative_deepening() { void Search::Worker::clear() { mainHistory.fill(0); lowPlyHistory.fill(0); - captureHistory.fill(-753); - pawnHistory.fill(-1152); + captureHistory.fill(-758); + pawnHistory.fill(-1158); pawnCorrectionHistory.fill(0); majorPieceCorrectionHistory.fill(0); minorPieceCorrectionHistory.fill(0); @@ -518,10 +518,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-678); + h->fill(-645); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((18.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((19.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -760,7 +760,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1641, 1423) + 760; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1831, 1428) + 623; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; 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] @@ -778,7 +778,7 @@ Value Search::Worker::search( // Step 7. Razoring (~1 Elo) // If eval is really low, check with qsearch if we can exceed alpha. If the // search suggests we cannot exceed alpha, return a speculative fail low. - if (eval < alpha - 501 - 272 * depth * depth) + if (eval < alpha - 469 - 307 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) @@ -787,9 +787,9 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 13 + if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 272 + - (ss - 1)->statScore / 290 >= beta && eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY && eval < VALUE_TB_WIN_IN_MAX_PLY) @@ -799,13 +799,13 @@ Value Search::Worker::search( // Step 9. Null move search with verification search (~35 Elo) if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 23 * depth + 400 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 21 * depth + 421 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 209, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 235, 7) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -855,7 +855,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // 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 + 189 - 53 * improving - 30 * opponentWorsening; + probCutBeta = beta + 187 - 53 * improving - 27 * opponentWorsening; if (!PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY // If value from transposition table is lower than probCutBeta, don't attempt @@ -926,7 +926,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea (~4 Elo) - probCutBeta = beta + 379; + probCutBeta = beta + 417; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY) @@ -1010,15 +1010,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 300 + 238 * lmrDepth + Value futilityValue = ss->staticEval + 287 + 253 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 32, -159 * depth, 160 * depth); - if (!pos.see_ge(move, -167 * depth - seeHist)) + int seeHist = std::clamp(captHist / 33, -161 * depth, 156 * depth); + if (!pos.see_ge(move, -162 * depth - seeHist)) continue; } else @@ -1029,15 +1029,15 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (history < -4071 * depth) + if (history < -3884 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 3653; + lmrDepth += history / 3609; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 51 ? 145 : 49) + 144 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 45 ? 140 : 43) + 141 * lmrDepth; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) @@ -1051,7 +1051,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -24 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) continue; } } @@ -1074,11 +1074,11 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 36) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (54 + 77 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1088,8 +1088,8 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 262 * PvNode - 204 * !ttCapture; - int tripleMargin = 97 + 266 * PvNode - 255 * !ttCapture + 94 * ss->ttPv; + int doubleMargin = 249 * PvNode - 194 * !ttCapture; + int tripleMargin = 94 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1127,7 +1127,7 @@ Value Search::Worker::search( else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4299) + > 4321) extension = 1; } @@ -1182,7 +1182,7 @@ Value Search::Worker::search( ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 4410; + + (*contHist[1])[movedPiece][move.to_sq()] - 3996; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) r -= ss->statScore * 1287 / 16384; @@ -1204,8 +1204,8 @@ Value Search::Worker::search( { // 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 + 38 + 2 * newDepth); // (~1 Elo) - const bool doShallowerSearch = value < bestValue + 8; // (~2 Elo) + const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); // (~1 Elo) + const bool doShallowerSearch = value < bestValue + 10; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1377,29 +1377,29 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (118 * (depth > 5) + 38 * !allNode + 169 * ((ss - 1)->moveCount > 8) - + 116 * (!ss->inCheck && bestValue <= ss->staticEval - 101) - + 133 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 92)); + int bonus = (117 * (depth > 5) + 39 * !allNode + 168 * ((ss - 1)->moveCount > 8) + + 115 * (!ss->inCheck && bestValue <= ss->staticEval - 108) + + 119 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 83)); // Proportional to "how much damage we have to undo" - bonus += std::min(-(ss - 1)->statScore / 102, 305); + bonus += std::min(-(ss - 1)->statScore / 113, 300); bonus = std::max(bonus, 0); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - stat_bonus(depth) * bonus / 107); + stat_bonus(depth) * bonus / 93); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << stat_bonus(depth) * bonus / 174; + << stat_bonus(depth) * bonus / 179; 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] - << stat_bonus(depth) * bonus / 25; + << stat_bonus(depth) * bonus / 24; } // Bonus when search fails low and there is a TT move else if (ttData.move && !allNode) - thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) / 4; + thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) * 23 / 100; if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1428,13 +1428,13 @@ Value Search::Worker::search( 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 * 101 / 128; - thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 157 / 128; - thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 153 / 128; + << bonus * 107 / 128; + thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 162 / 128; + thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 148 / 128; thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] - << bonus * 123 / 128; + << bonus * 122 / 128; thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] - << bonus * 165 / 128; + << bonus * 185 / 128; if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; @@ -1566,7 +1566,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 280; + futilityBase = ss->staticEval + 306; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1629,11 +1629,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()] - <= 5036) + <= 5095) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -82)) + if (!pos.see_ge(move, -83)) continue; } @@ -1701,7 +1701,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 + 1239 - delta * 795 / rootDelta) + (!i && reductionScale > 1341) * 1135; + return (reductionScale + 1304 - delta * 814 / rootDelta) + (!i && reductionScale > 1423) * 1135; } // elapsed() returns the time elapsed since the search started. If the @@ -1832,7 +1832,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) { - bonus = bonus * 53 / 64; + bonus = bonus * 50 / 64; for (int i : {1, 2, 3, 4, 6}) { From 43e100ae06376d63461005422f26e5517db07c6d Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Wed, 6 Nov 2024 21:35:59 +0100 Subject: [PATCH 0783/1309] Use cutnode as TT Cutoff Condition At low enough depths, fail high with TT only when expected cutnode. Passed STC: https://tests.stockfishchess.org/tests/view/6726357b86d5ee47d953da8c LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 41184 W: 10873 L: 10551 D: 19760 Ptnml(0-2): 131, 4728, 10554, 5046, 133 Passed LTC: https://tests.stockfishchess.org/tests/view/6727326a86d5ee47d953db30 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 270888 W: 69040 L: 68243 D: 133605 Ptnml(0-2): 180, 29385, 75485, 30246, 148 closes https://github.com/official-stockfish/Stockfish/pull/5670 Bench: 805776 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2a2331cb40e..5fdfdeb2d71 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -636,7 +636,8 @@ Value Search::Worker::search( // At non-PV nodes we check for an early TT cutoff if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() - && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))) + && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) + && (cutNode == (ttData.value >= beta) || depth > 8)) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) if (ttData.move && ttData.value >= beta) From 070db8b3a1ecfb4753753a3e285578b35acd63cd Mon Sep 17 00:00:00 2001 From: Linmiao Xu Date: Sun, 3 Nov 2024 22:48:42 -0500 Subject: [PATCH 0784/1309] Update default main net to nn-1c0000000000.nnue Found by updating 489 L2 weights with values found from around 31k / 60k spsa games. Spsa was configured to use 60k games, down from 120k games in: https://github.com/official-stockfish/Stockfish/pull/5459 623 spsa params: L2 weights from `nn-1cedc0ffeeee.nnue` where 24 <= |value| <= 30 A: 3000, alpha: 0.602, gamma: 0.101 weights: [-127, 127], c_end = 6 Passed STC: https://tests.stockfishchess.org/tests/view/6728d61e86d5ee47d953dcaf LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 187168 W: 48642 L: 48107 D: 90419 Ptnml(0-2): 558, 21888, 48213, 22311, 614 Passed LTC: https://tests.stockfishchess.org/tests/view/672b018f86d5ee47d953de98 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 235074 W: 59924 L: 59202 D: 115948 Ptnml(0-2): 131, 25467, 65610, 26207, 122 closes https://github.com/official-stockfish/Stockfish/pull/5673 Bench: 898850 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 9bd436b58c6..4604321d378 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-1cedc0ffeeee.nnue" +#define EvalFileDefaultNameBig "nn-1c0000000000.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From ce2d9e27ea8b10abbd69ebd5dd73e7dcf0aa0655 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 10 Nov 2024 18:52:29 +0300 Subject: [PATCH 0785/1309] Simplify big-net reevaluation Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 37408 W: 9699 L: 9477 D: 18232 Ptnml(0-2): 130, 4326, 9577, 4534, 137 https://tests.stockfishchess.org/tests/view/672ffd8086d5ee47d953e633 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 151062 W: 38087 L: 37999 D: 74976 Ptnml(0-2): 63, 16686, 41958, 16748, 76 https://tests.stockfishchess.org/tests/view/673087aa86d5ee47d953e66b closes https://github.com/official-stockfish/Stockfish/pull/5674 Bench: 848812 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 7c7b54a4ff8..bc86a7420b8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -66,7 +66,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (nnue * psqt < 0 || std::abs(nnue) < 227)) + if (smallNet && (std::abs(nnue) < 236)) { std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); nnue = (125 * psqt + 131 * positional) / 128; From 49138b8c33ca7bacff710efba4a90630a3490c08 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 13 Nov 2024 14:56:19 +0100 Subject: [PATCH 0786/1309] Fix CI Docker Buildx closes https://github.com/official-stockfish/Stockfish/pull/5678 No functional change --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a826e6f063e..b97aaa29c5d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -139,7 +139,7 @@ jobs: - name: Build Docker container if: matrix.config.base_image run: | - docker buildx build --load -t sf_builder - << EOF + docker buildx build --platform ${{ matrix.config.platform }} --load -t sf_builder - << EOF FROM ${{ matrix.config.base_image }} WORKDIR /app RUN apk update && apk add make g++ From 82b092ca48c2efeadf2108a8351bb1309c4b7780 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 13 Nov 2024 20:43:04 +0300 Subject: [PATCH 0787/1309] Adjust statscore for captures Instead of using quiet histories use capture history with a different offset. Passed STC: https://tests.stockfishchess.org/tests/view/6731d5cc86d5ee47d953e719 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 428896 W: 111160 L: 110269 D: 207467 Ptnml(0-2): 1220, 50296, 110534, 51169, 1229 Passed LTC: https://tests.stockfishchess.org/tests/view/6733d9fd86d5ee47d953e962 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 105882 W: 26918 L: 26458 D: 52506 Ptnml(0-2): 66, 11430, 29482, 11904, 59 closes https://github.com/official-stockfish/Stockfish/pull/5679 Bench: 840721 --- src/search.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5fdfdeb2d71..94b20c85be2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1181,9 +1181,14 @@ Value Search::Worker::search( else if (move == ttData.move) r -= 1879; - ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] - + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3996; + if (capture) + ss->statScore = + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] + - 13000; + else + ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] - 3996; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) r -= ss->statScore * 1287 / 16384; From f129bf0de94f2c5a7ee19e697612a7e83ccd28ff Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 17 Nov 2024 09:19:06 +0300 Subject: [PATCH 0788/1309] Tweak statscore for captures Followup of a recent patch that separated statscore for captures and non-captures. Lower value that we subtract from statscore if a move is a capture. Passed STC: https://tests.stockfishchess.org/tests/view/67385b6786d5ee47d953eeba LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 50592 W: 13223 L: 12888 D: 24481 Ptnml(0-2): 154, 5853, 12931, 6220, 138 Passed LTC: https://tests.stockfishchess.org/tests/view/6739056e86d5ee47d953ef3f LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 23598 W: 6155 L: 5862 D: 11581 Ptnml(0-2): 16, 2466, 6543, 2757, 17 closes https://github.com/official-stockfish/Stockfish/pull/5682 Bench: 771180 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 94b20c85be2..50b31d2ad0f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1184,7 +1184,7 @@ Value Search::Worker::search( if (capture) ss->statScore = thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 13000; + - 11000; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From 0282edc0b06017b5f03971510cdb23e105fe9851 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 20 Nov 2024 01:09:39 +0300 Subject: [PATCH 0789/1309] Simplify bonus formula Give full bonus instead of half. Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 23872 W: 6254 L: 6018 D: 11600 Ptnml(0-2): 80, 2691, 6152, 2939, 74 https://tests.stockfishchess.org/tests/view/673b709686d5ee47d953f19d Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 42894 W: 10924 L: 10725 D: 21245 Ptnml(0-2): 30, 4592, 12011, 4777, 37 https://tests.stockfishchess.org/tests/view/673bb50386d5ee47d953f1eb closes https://github.com/official-stockfish/Stockfish/pull/5683 Bench: 836558 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 50b31d2ad0f..f1942a4f38b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -765,7 +765,7 @@ Value Search::Worker::search( thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; 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 / 2; + << bonus; } // Set up the improving flag, which is true if current static evaluation is From d29c8bd5d456b2a6fcee2069e1440ef82cba2b1e Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Wed, 20 Nov 2024 14:48:23 +0100 Subject: [PATCH 0790/1309] Rewrite of 'Adjust correction history' condition Current condition is convoluted and hard to understand because of several negations. Also added 2 comments to make the concept behind the condition better understandable. closes https://github.com/official-stockfish/Stockfish/pull/5685 No functional change --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f1942a4f38b..93036398d2e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1425,9 +1425,9 @@ Value Search::Worker::search( depth, bestMove, unadjustedStaticEval, tt.generation()); // Adjust correction history - if (!ss->inCheck && (!bestMove || !pos.capture(bestMove)) - && !(bestValue >= beta && bestValue <= ss->staticEval) - && !(!bestMove && bestValue >= ss->staticEval)) + if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) + && ((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; From cd3c13a883b2d1e8dc32400202f8e0bae7d8123a Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 20 Nov 2024 17:03:56 +0300 Subject: [PATCH 0791/1309] Further tweak statscore for captures Even lower offset. Passed STC: https://tests.stockfishchess.org/tests/view/673a66d786d5ee47d953f070 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 63776 W: 16570 L: 16216 D: 30990 Ptnml(0-2): 178, 7371, 16478, 7641, 220 Passed LTC: https://tests.stockfishchess.org/tests/view/673b2e2a86d5ee47d953f14b LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 156960 W: 39999 L: 39435 D: 77526 Ptnml(0-2): 96, 16965, 43803, 17511, 105 closes https://github.com/official-stockfish/Stockfish/pull/5686 Bench: 867931 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 93036398d2e..213bbdab77b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1184,7 +1184,7 @@ Value Search::Worker::search( if (capture) ss->statScore = thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 11000; + - 5454; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From 4fcd78ceb4a5cf25ee652ee7793bb0a3fa1f95df Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 18 Nov 2024 09:08:26 -0800 Subject: [PATCH 0792/1309] Simplify Probcut Bonus Passed STC: LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 172288 W: 44656 L: 44580 D: 83052 Ptnml(0-2): 507, 20650, 43782, 20670, 535 https://tests.stockfishchess.org/tests/view/673b74f986d5ee47d953f1a3 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94596 W: 24098 L: 23953 D: 46545 Ptnml(0-2): 57, 10322, 26393, 10471, 55 https://tests.stockfishchess.org/tests/view/673d191886d5ee47d953f337 closes https://github.com/official-stockfish/Stockfish/pull/5688 Bench: 1031022 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 213bbdab77b..ace6385d7f0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -910,8 +910,7 @@ Value Search::Worker::search( if (value >= probCutBeta) { - thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] - << stat_bonus(depth - 2); + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] << 1300; // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, From fb6be17ad40d321b5fff02395bc156568fce3091 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 17 Nov 2024 20:46:30 -0800 Subject: [PATCH 0793/1309] Simplify statscore at captures Simplify statscores for captures, setting them to 0 A recent tweak of Vizvezdenec finds substantial elo gain from giving captures a separate statscore, which is used mainly for reductions. The idea is that the old combination of quiet histories was inappropriate and that a value based on the capture history is more suitable. This simplification sets the statscore for captures to 0, suggesting that the elo gain came from rectifying the quiet history/capture mismatch. Passed STC (against a slightly older version of Viz's patch) https://tests.stockfishchess.org/tests/view/673ac6e286d5ee47d953f0ec LR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 57312 W: 14872 L: 14672 D: 27768 Ptnml(0-2): 152, 6761, 14649, 6923, 171 Passed LTC (against Viz's newest patch) https://tests.stockfishchess.org/tests/view/673cd00686d5ee47d953f2db LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 88236 W: 22510 L: 22358 D: 43368 Ptnml(0-2): 70, 9530, 24745, 9724, 49 closes https://github.com/official-stockfish/Stockfish/pull/5691 Bench: 959947 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ace6385d7f0..560b031bae7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -538,7 +538,7 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); + return qsearch(pos, ss, alpha, beta); // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); @@ -1181,9 +1181,7 @@ Value Search::Worker::search( r -= 1879; if (capture) - ss->statScore = - thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 5454; + ss->statScore = 0; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From b7f17346e55a9494d8fed610f613e1722da3042d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 21 Nov 2024 22:17:47 -0800 Subject: [PATCH 0794/1309] Fix Sanitizer Tests closes https://github.com/official-stockfish/Stockfish/pull/5692 No functional change --- tests/instrumented.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/instrumented.py b/tests/instrumented.py index a3747d4e97a..db5ec8e0802 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -352,10 +352,10 @@ def test_fen_position_with_mate_go_nodes(self): def test_fen_position_depth_27(self): self.stockfish.send_command("ucinewgame") self.stockfish.send_command( - "position fen 1NR2B2/5p2/5p2/1p1kpp2/1P2rp2/2P1pB2/2P1P1K1/8 b - -" + "position fen r1b2r1k/pp1p2pp/2p5/2B1q3/8/8/P1PN2PP/R4RK1 w - - 0 18" ) - self.stockfish.send_command("go depth 27") - self.stockfish.contains("score mate -2") + self.stockfish.send_command("go") + self.stockfish.contains("score mate 1") self.stockfish.starts_with("bestmove") From 55905e562a0de4aeef9c5081a9eab80e6ed4c542 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 13 Nov 2024 12:56:29 -0800 Subject: [PATCH 0795/1309] Simplify movepick coefficients This commit sets movepick weights for all continuation histories to 1 and doubles the weight for the main history, inspired by a recent tune. Passed STC https://tests.stockfishchess.org/tests/view/6735151a86d5ee47d953eaa2 LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 29984 W: 7840 L: 7612 D: 14532 Ptnml(0-2): 85, 3511, 7571, 3741, 84 Passed LTC https://tests.stockfishchess.org/tests/view/673667a986d5ee47d953ec78 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 26268 W: 6726 L: 6510 D: 13032 Ptnml(0-2): 16, 2797, 7288, 3021, 12 closes https://github.com/official-stockfish/Stockfish/pull/5680 Bench: 1130293 --- src/movepick.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 720f2e031ea..df722eceb70 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -158,11 +158,11 @@ void MovePicker::score() { Square to = m.to_sq(); // histories - m.value = (*mainHistory)[pos.side_to_move()][m.from_to()]; + m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; - m.value += 2 * (*continuationHistory[0])[pc][to]; + m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; - m.value += (*continuationHistory[2])[pc][to] / 3; + m.value += (*continuationHistory[2])[pc][to]; m.value += (*continuationHistory[3])[pc][to]; m.value += (*continuationHistory[5])[pc][to]; From 70bb317afe870c8bc1979ef955f120e4d81f504e Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 22 Nov 2024 16:56:50 -0800 Subject: [PATCH 0796/1309] Bonus for a prior capture that causes a fail low. This tweak adds a bonus equal to twice the stat_bonus for the current depth for a prior capture that caused a fail high, similar to the prior countermove bonus we currently have. Passed STC https://tests.stockfishchess.org/tests/view/673bc14b86d5ee47d953f1f2 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 105824 W: 27538 L: 27118 D: 51168 Ptnml(0-2): 358, 12370, 27024, 12814, 346 Passed LTC https://tests.stockfishchess.org/tests/view/673ccbff86d5ee47d953f2d9 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 134502 W: 34340 L: 33820 D: 66342 Ptnml(0-2): 102, 14634, 37229, 15214, 72 closes https://github.com/official-stockfish/Stockfish/pull/5695 Bench: 1107054 --- src/search.cpp | 19 +++++++++++++++---- src/search.h | 3 ++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 560b031bae7..45f0f10fc52 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -538,7 +538,7 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch(pos, ss, alpha, beta); + return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); @@ -889,7 +889,8 @@ Value Search::Worker::search( // Prefetch the TT entry for the resulting position prefetch(tt.first_entry(pos.key_after(move))); - ss->currentMove = move; + ss->currentMove = move; + ss->capturedPiece = captured; ss->continuationHistory = &this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1138,7 +1139,8 @@ Value Search::Worker::search( prefetch(tt.first_entry(pos.key_after(move))); // Update the current move (this must be done after singular extension search) - ss->currentMove = move; + ss->currentMove = move; + ss->capturedPiece = pos.piece_on(move.to_sq()); ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1400,6 +1402,14 @@ Value Search::Worker::search( << stat_bonus(depth) * bonus / 24; } + else if (priorCapture && prevSq != SQ_NONE) + { + // bonus for prior countermoves that caused the fail low + Piece capturedPiece = (ss - 1)->capturedPiece; + thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] + << stat_bonus(depth) * 2; + } + // Bonus when search fails low and there is a TT move else if (ttData.move && !allNode) thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) * 23 / 100; @@ -1644,7 +1654,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) prefetch(tt.first_entry(pos.key_after(move))); // Update the current move - ss->currentMove = move; + ss->currentMove = move; + ss->capturedPiece = pos.piece_on(move.to_sq()); ss->continuationHistory = &thisThread ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; diff --git a/src/search.h b/src/search.h index b618855b9fc..7868f607870 100644 --- a/src/search.h +++ b/src/search.h @@ -66,6 +66,7 @@ struct Stack { CorrectionHistory* continuationCorrectionHistory; int ply; Move currentMove; + Piece capturedPiece; Move excludedMove; Value staticEval; int statScore; @@ -356,4 +357,4 @@ class Worker { } // namespace Stockfish -#endif // #ifndef SEARCH_H_INCLUDED +#endif // #ifndef SEARCH_H_INCLUDED \ No newline at end of file From 57e06be71f0177a69843750a9f456462d02f23b9 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:24:43 +0100 Subject: [PATCH 0797/1309] Add functions to check for decisive scores Thanks to peregrineshahin and robbyrobbyrob for their suggestions. closes https://github.com/official-stockfish/Stockfish/pull/5696 No functional change --- src/nnue/nnue_misc.cpp | 2 +- src/score.cpp | 2 +- src/search.cpp | 80 +++++++++++++++++++----------------------- src/thread.cpp | 10 +++--- src/types.h | 15 ++++++++ 5 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 122610a749c..a2bece21df0 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -126,7 +126,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; if (pc != NO_PIECE) board[y + 1][x + 4] = PieceToChar[pc]; - if (value != VALUE_NONE) + if (is_valid(value)) format_cp_compact(value, &board[y + 2][x + 2], pos); }; diff --git a/src/score.cpp b/src/score.cpp index 292f53406e2..179796d2080 100644 --- a/src/score.cpp +++ b/src/score.cpp @@ -29,7 +29,7 @@ namespace Stockfish { Score::Score(Value v, const Position& pos) { assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY) + if (!is_decisive(v)) { score = InternalUnits{UCIEngine::to_cp(v, pos)}; } diff --git a/src/search.cpp b/src/search.cpp index 45f0f10fc52..2e904f40de2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -388,7 +388,7 @@ void Search::Worker::iterative_deepening() { // if we would have had time to fully search other root-moves. Thus // we suppress this output and below pick a proven score/PV for this // thread (from the previous iteration). - && !(threads.abortedSearch && rootMoves[0].uciScore <= VALUE_TB_LOSS_IN_MAX_PLY)) + && !(threads.abortedSearch && is_loss(rootMoves[0].uciScore))) main_manager()->pv(*this, threads, tt, rootDepth); if (threads.stop) @@ -401,7 +401,7 @@ void Search::Worker::iterative_deepening() { // We make sure not to pick an unproven mated-in score, // in case this thread prematurely stopped search (aborted-search). if (threads.abortedSearch && rootMoves[0].score != -VALUE_INFINITE - && rootMoves[0].score <= VALUE_TB_LOSS_IN_MAX_PLY) + && is_loss(rootMoves[0].score)) { // Bring the last best move to the front for best thread selection. Utility::move_to_front(rootMoves, [&lastBestPV = std::as_const(lastBestPV)]( @@ -635,7 +635,7 @@ Value Search::Worker::search( // At non-PV nodes we check for an early TT cutoff if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) - && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() + && 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 > 8)) { @@ -732,7 +732,7 @@ Value Search::Worker::search( { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; - if (unadjustedStaticEval == VALUE_NONE) + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); else if (PvNode) @@ -742,7 +742,7 @@ Value Search::Worker::search( to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // ttValue can be used as a better position evaluation (~7 Elo) - if (ttData.value != VALUE_NONE + if (is_valid(ttData.value) && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttData.value; } @@ -782,7 +782,7 @@ Value Search::Worker::search( if (eval < alpha - 469 - 307 * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); - if (value < alpha && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + if (value < alpha && !is_decisive(value)) return value; } @@ -792,8 +792,7 @@ Value Search::Worker::search( && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - (ss - 1)->statScore / 290 >= beta - && eval >= beta && (!ttData.move || ttCapture) && beta > VALUE_TB_LOSS_IN_MAX_PLY - && eval < VALUE_TB_WIN_IN_MAX_PLY) + && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; improving |= ss->staticEval >= beta + 100; @@ -801,7 +800,7 @@ Value Search::Worker::search( // Step 9. Null move search with verification search (~35 Elo) if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta && ss->staticEval >= beta - 21 * depth + 421 && !excludedMove && pos.non_pawn_material(us) - && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) + && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); @@ -819,7 +818,7 @@ Value Search::Worker::search( pos.undo_null_move(); // Do not return unproven mate or TB scores - if (nullValue >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY) + if (nullValue >= beta && !is_win(nullValue)) { if (thisThread->nmpMinPly || depth < 16) return nullValue; @@ -858,12 +857,12 @@ Value Search::Worker::search( // returns a value much above beta, we can (almost) safely prune the previous move. probCutBeta = beta + 187 - 53 * improving - 27 * opponentWorsening; if (!PvNode && depth > 3 - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt // 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 && ttData.value != VALUE_NONE && ttData.value < probCutBeta)) + && !(ttData.depth >= depth - 3 && is_valid(ttData.value) && ttData.value < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); @@ -916,8 +915,7 @@ Value Search::Worker::search( // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, unadjustedStaticEval, tt.generation()); - return std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY ? value - (probCutBeta - beta) - : value; + return is_decisive(value) ? value : value - (probCutBeta - beta); } } @@ -929,8 +927,7 @@ Value Search::Worker::search( // Step 12. A small Probcut idea (~4 Elo) probCutBeta = beta + 417; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY - && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY) + && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -993,7 +990,7 @@ Value Search::Worker::search( // Step 14. Pruning at shallow depth (~120 Elo). // Depth conditions are important for mate finding. - if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) if (moveCount >= futility_move_count(improving, depth)) @@ -1043,8 +1040,8 @@ Value Search::Worker::search( // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) { - if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY - && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) + if (bestValue <= futilityValue && !is_decisive(bestValue) + && !is_win(futilityValue)) bestValue = futilityValue; continue; } @@ -1076,8 +1073,8 @@ Value Search::Worker::search( if (!rootNode && move == ttData.move && !excludedMove && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv - && std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3) + && is_valid(ttData.value) && !is_decisive(ttData.value) + && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; @@ -1104,7 +1101,7 @@ Value Search::Worker::search( // over the original beta, we assume this expected cut-node is not // singular (multiple moves fail high), and we can prune the whole // subtree by returning a softbound. - else if (value >= beta && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + else if (value >= beta && !is_decisive(value)) return value; // Negative extensions @@ -1315,9 +1312,8 @@ Value Search::Worker::search( // In case we have an alternative move equal in eval to the current bestmove, // promote it to bestmove by pretending it just exceeds alpha (but not beta). - int inc = - (value == bestValue && (int(nodes) & 15) == 0 && ss->ply + 2 >= thisThread->rootDepth - && std::abs(value) + 1 < VALUE_TB_WIN_IN_MAX_PLY); + int inc = (value == bestValue && ss->ply + 2 >= thisThread->rootDepth + && (int(nodes) & 15) == 0 && !is_win(std::abs(value) + 1)); if (value + inc > bestValue) { @@ -1339,7 +1335,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 14 && std::abs(value) < VALUE_TB_WIN_IN_MAX_PLY) + if (depth > 2 && depth < 14 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1367,8 +1363,8 @@ Value Search::Worker::search( assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY - && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(alpha) < VALUE_TB_WIN_IN_MAX_PLY) + if (!PvNode && bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) + && !is_decisive(alpha)) bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) @@ -1528,7 +1524,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // At non-PV nodes we check for an early TT cutoff if (!PvNode && ttData.depth >= DEPTH_QS - && ttData.value != VALUE_NONE // Can happen when !ttHit or when access race in probe() + && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER))) return ttData.value; @@ -1542,14 +1538,14 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; - if (unadjustedStaticEval == VALUE_NONE) + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // ttValue can be used as a better position evaluation (~13 Elo) - if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY + if (is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttData.value; } @@ -1567,7 +1563,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { - if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY) + if (!is_decisive(bestValue)) bestValue = (bestValue + beta) / 2; if (!ss->ttHit) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, @@ -1608,10 +1604,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) moveCount++; // Step 6. Pruning - if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us)) + if (!is_loss(bestValue) && pos.non_pawn_material(us)) { // Futility pruning and moveCount pruning (~10 Elo) - if (!givesCheck && move.to_sq() != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY + if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase) && move.type_of() != PROMOTION) { if (moveCount > 2) @@ -1699,7 +1695,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return mated_in(ss->ply); // Plies to mate from the root } - if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && bestValue >= beta) + if (!is_decisive(bestValue) && bestValue >= beta) bestValue = (3 * bestValue + beta) / 4; // Save gathered info in transposition table. The static evaluation @@ -1737,11 +1733,7 @@ namespace { // Adjusts a mate or TB score from "plies to mate from the root" to // "plies to mate from the current position". Standard scores are unchanged. // The function is called before storing a value in the transposition table. -Value value_to_tt(Value v, int ply) { - - assert(v != VALUE_NONE); - return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; -} +Value value_to_tt(Value v, int ply) { return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; } // Inverse of value_to_tt(): it adjusts a mate or TB score from the transposition @@ -1751,11 +1743,11 @@ Value value_to_tt(Value v, int ply) { // graph history interaction, we return the highest non-TB score instead. Value value_from_tt(Value v, int ply, int r50c) { - if (v == VALUE_NONE) + if (!is_valid(v)) return VALUE_NONE; // handle TB win or better - if (v >= VALUE_TB_WIN_IN_MAX_PLY) + if (is_win(v)) { // Downgrade a potentially false mate score if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) @@ -1769,7 +1761,7 @@ Value value_from_tt(Value v, int ply, int r50c) { } // handle TB loss or worse - if (v <= VALUE_TB_LOSS_IN_MAX_PLY) + if (is_loss(v)) { // Downgrade a potentially false mate score. if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) @@ -2108,7 +2100,7 @@ void SearchManager::pv(Search::Worker& worker, bool isExact = i != pvIdx || tb || !updated; // tablebase- and previous-scores are exact // Potentially correct and extend the PV, and in exceptional cases v - if (std::abs(v) >= VALUE_TB_WIN_IN_MAX_PLY && std::abs(v) < VALUE_MATE_IN_MAX_PLY + if (is_decisive(v) && std::abs(v) < VALUE_MATE_IN_MAX_PLY && ((!rootMoves[i].scoreLowerbound && !rootMoves[i].scoreUpperbound) || isExact)) syzygy_extend_pv(worker.options, worker.limits, pos, rootMoves[i], v); diff --git a/src/thread.cpp b/src/thread.cpp index b5d51594c54..5f73771effa 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -329,13 +329,13 @@ Thread* ThreadPool::get_best_thread() const { const auto bestThreadMoveVote = votes[bestThreadPV[0]]; const auto newThreadMoveVote = votes[newThreadPV[0]]; - const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; - const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY; + const bool bestThreadInProvenWin = is_win(bestThreadScore); + const bool newThreadInProvenWin = is_win(newThreadScore); const bool bestThreadInProvenLoss = - bestThreadScore != -VALUE_INFINITE && bestThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore); const bool newThreadInProvenLoss = - newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY; + newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore); // We make sure not to pick a thread with truncated principal variation const bool betterVotingValue = @@ -355,7 +355,7 @@ Thread* ThreadPool::get_best_thread() const { bestThread = th.get(); } else if (newThreadInProvenWin || newThreadInProvenLoss - || (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY + || (!is_loss(newThreadScore) && (newThreadMoveVote > bestThreadMoveVote || (newThreadMoveVote == bestThreadMoveVote && betterVotingValue)))) bestThread = th.get(); diff --git a/src/types.h b/src/types.h index b12491d6cdd..5644460113f 100644 --- a/src/types.h +++ b/src/types.h @@ -155,6 +155,21 @@ constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; + +constexpr bool is_valid(Value value) { return value != VALUE_NONE; } + +constexpr bool is_win(Value value) { + assert(is_valid(value)); + return value >= VALUE_TB_WIN_IN_MAX_PLY; +} + +constexpr bool is_loss(Value value) { + assert(is_valid(value)); + return value <= VALUE_TB_LOSS_IN_MAX_PLY; +} + +constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } + // In the code, we make the assumption that these values // are such that non_pawn_material() can be used to uniquely // identify the material on the board. From da82942b541b2a6512189a9bad2284c6f45f3c44 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:24:43 +0100 Subject: [PATCH 0798/1309] Add functions to check for decisive scores Thanks to peregrineshahin and robbyrobbyrob for their suggestions. closes https://github.com/official-stockfish/Stockfish/pull/5696 No functional change --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2e904f40de2..ea43017e595 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1042,7 +1042,9 @@ Value Search::Worker::search( { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) - bestValue = futilityValue; + if (bestValue <= futilityValue && !is_decisive(bestValue) + && !is_win(futilityValue)) + bestValue = futilityValue; continue; } From d5a36a3c92533782d6a74d16c080de0c1538f65d Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 23 Nov 2024 14:37:08 +0300 Subject: [PATCH 0799/1309] Simplify probCutBeta formula After recent changes to the improving definition, seems like there is no need anymore to keep opponentWorsening in the probCutBeta formula. Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 214272 W: 55566 L: 55541 D: 103165 Ptnml(0-2): 620, 25540, 54817, 25513, 646 https://tests.stockfishchess.org/tests/view/6735243d86d5ee47d953eaea Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 126708 W: 32329 L: 32216 D: 62163 Ptnml(0-2): 68, 13986, 35123, 14119, 58 https://tests.stockfishchess.org/tests/view/67393cf686d5ee47d953ef99 closes https://github.com/official-stockfish/Stockfish/pull/5697 Bench: 983067 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ea43017e595..5209bd073f4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -855,7 +855,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 187 - 53 * improving - 27 * opponentWorsening; + probCutBeta = beta + 187 - 56 * improving; if (!PvNode && depth > 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt From 713000c517c63e6926bdbe1071e647280bc3da32 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Sun, 24 Nov 2024 16:05:04 +0100 Subject: [PATCH 0800/1309] Same weight for black and white nonPawnCorrection history Since we don't have color dependent parameters in NNUE eval, it also has no sense IMO to have color dependent parameters in correction histories. Ideally a fixed depth search on a single thread should be determistic, so delivering the same result (move) if we just flip colors on the board. Patch replaces 2 parameters (122 and 185) with just one value 154 (= the avg of the two). Passed STC-non regression https://tests.stockfishchess.org/tests/view/6740a63286d5ee47d953f656 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 122336 W: 31499 L: 31372 D: 59465 Ptnml(0-2): 336, 14535, 31301, 14658, 338 Passed LTC-non regression https://tests.stockfishchess.org/tests/view/67419bae86d5ee47d953f7b6 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 101400 W: 25870 L: 25731 D: 49799 Ptnml(0-2): 78, 11109, 28166, 11290, 57 closes https://github.com/official-stockfish/Stockfish/pull/5698 Bench: 1215483 --- src/search.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5209bd073f4..8ebbef5b146 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1434,7 +1434,8 @@ Value Search::Worker::search( && ((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; + const auto m = (ss - 1)->currentMove; + static const int nonPawnWeight = 154; auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); @@ -1443,9 +1444,9 @@ Value Search::Worker::search( thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 162 / 128; thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 148 / 128; thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] - << bonus * 122 / 128; + << bonus * nonPawnWeight / 128; thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] - << bonus * 185 / 128; + << bonus * nonPawnWeight / 128; if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; From 1f9404434dcfd1013e20266a79dfed5d0271294a Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Sun, 24 Nov 2024 16:17:42 -0800 Subject: [PATCH 0801/1309] Simplify picking of evasion moves Sort evasions before we start returning them in next_move() (just like every other kind of move) instead of looking for the biggest element on every call to next_move(). The bench number changes because the old method is not equivalent to a stable sort. Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 132064 W: 34318 L: 34204 D: 63542 Ptnml(0-2): 392, 15522, 34106, 15604, 408 https://tests.stockfishchess.org/tests/view/6743fee086d5ee47d953f9ca Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 97542 W: 24899 L: 24757 D: 47886 Ptnml(0-2): 63, 10646, 27193, 10824, 45 https://tests.stockfishchess.org/tests/view/674509cd86d5ee47d953fb96 closes https://github.com/official-stockfish/Stockfish/pull/5700 Bench: 1094825 --- AUTHORS | 1 + src/movepick.cpp | 29 ++++++++++------------------- src/movepick.h | 7 +------ 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/AUTHORS b/AUTHORS index 31a64c17e3a..ddc53ec0249 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,6 +45,7 @@ Bruno de Melo Costa (BM123499) Bruno Pellanda (pellanda) Bryan Cross (crossbr) candirufish +Carlos Esparza Sánchez (ces42) Chess13234 Chris Cain (ceebo) Ciekce diff --git a/src/movepick.cpp b/src/movepick.cpp index df722eceb70..96f031717f8 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -18,11 +18,9 @@ #include "movepick.h" -#include #include #include #include -#include #include "bitboard.h" #include "position.h" @@ -199,19 +197,13 @@ void MovePicker::score() { // Returns the next move satisfying a predicate function. // This never returns the TT move, as it was emitted before. -template +template Move MovePicker::select(Pred filter) { - while (cur < endMoves) - { - if constexpr (T == Best) - std::swap(*cur, *std::max_element(cur, endMoves)); - + for (; cur < endMoves; ++cur) if (*cur != ttMove && filter()) return *cur++; - cur++; - } return Move::none(); } @@ -245,7 +237,7 @@ Move MovePicker::next_move() { goto top; case GOOD_CAPTURE : - if (select([&]() { + if (select([&]() { // Move losing capture to endBadCaptures to be tried later return pos.see_ge(*cur, -cur->value / 18) ? true : (*endBadCaptures++ = *cur, false); @@ -269,7 +261,7 @@ Move MovePicker::next_move() { [[fallthrough]]; case GOOD_QUIET : - if (!skipQuiets && select([]() { return true; })) + if (!skipQuiets && select([]() { return true; })) { if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) return *(cur - 1); @@ -286,7 +278,7 @@ Move MovePicker::next_move() { [[fallthrough]]; case BAD_CAPTURE : - if (select([]() { return true; })) + if (select([]() { return true; })) return *(cur - 1); // Prepare the pointers to loop over the bad quiets @@ -298,7 +290,7 @@ Move MovePicker::next_move() { case BAD_QUIET : if (!skipQuiets) - return select([]() { return true; }); + return select([]() { return true; }); return Move::none(); @@ -307,17 +299,16 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); + partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); ++stage; [[fallthrough]]; case EVASION : - return select([]() { return true; }); + case QCAPTURE : + return select([]() { return true; }); case PROBCUT : - return select([&]() { return pos.see_ge(*cur, threshold); }); - - case QCAPTURE : - return select([]() { return true; }); + return select([&]() { return pos.see_ge(*cur, threshold); }); } assert(false); diff --git a/src/movepick.h b/src/movepick.h index 0278b70ec82..ab4e832fd7d 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -35,11 +35,6 @@ class Position; // a cut-off first. class MovePicker { - enum PickType { - Next, - Best - }; - public: MovePicker(const MovePicker&) = delete; MovePicker& operator=(const MovePicker&) = delete; @@ -57,7 +52,7 @@ class MovePicker { void skip_quiet_moves(); private: - template + template Move select(Pred); template void score(); From 6a8478c6adaf9fda6b885ea74e510910f5618c41 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 22 Nov 2024 17:13:00 -0800 Subject: [PATCH 0802/1309] Simplify Prior Capture Countermove Bonus Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 184032 W: 47626 L: 47568 D: 88838 Ptnml(0-2): 590, 21808, 47238, 21714, 666 https://tests.stockfishchess.org/tests/view/67412c7686d5ee47d953f743 Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 169218 W: 43395 L: 43323 D: 82500 Ptnml(0-2): 302, 18567, 46791, 18655, 294 https://tests.stockfishchess.org/tests/view/6743b7e086d5ee47d953f9a6 closes https://github.com/official-stockfish/Stockfish/pull/5701 Bench: 1130692 --- src/search.cpp | 12 +++++------- src/search.h | 3 +-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8ebbef5b146..149222fc7fa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -888,8 +888,7 @@ Value Search::Worker::search( // Prefetch the TT entry for the resulting position prefetch(tt.first_entry(pos.key_after(move))); - ss->currentMove = move; - ss->capturedPiece = captured; + ss->currentMove = move; ss->continuationHistory = &this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1138,8 +1137,7 @@ Value Search::Worker::search( prefetch(tt.first_entry(pos.key_after(move))); // Update the current move (this must be done after singular extension search) - ss->currentMove = move; - ss->capturedPiece = pos.piece_on(move.to_sq()); + ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1403,7 +1401,8 @@ Value Search::Worker::search( else if (priorCapture && prevSq != SQ_NONE) { // bonus for prior countermoves that caused the fail low - Piece capturedPiece = (ss - 1)->capturedPiece; + Piece capturedPiece = pos.captured_piece(); + assert(capturedPiece != NO_PIECE); thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << stat_bonus(depth) * 2; } @@ -1653,8 +1652,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) prefetch(tt.first_entry(pos.key_after(move))); // Update the current move - ss->currentMove = move; - ss->capturedPiece = pos.piece_on(move.to_sq()); + ss->currentMove = move; ss->continuationHistory = &thisThread ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; diff --git a/src/search.h b/src/search.h index 7868f607870..b618855b9fc 100644 --- a/src/search.h +++ b/src/search.h @@ -66,7 +66,6 @@ struct Stack { CorrectionHistory* continuationCorrectionHistory; int ply; Move currentMove; - Piece capturedPiece; Move excludedMove; Value staticEval; int statScore; @@ -357,4 +356,4 @@ class Worker { } // namespace Stockfish -#endif // #ifndef SEARCH_H_INCLUDED \ No newline at end of file +#endif // #ifndef SEARCH_H_INCLUDED From e8d2ba194a563b8c8dc1b9ae603b6b9a45c93567 Mon Sep 17 00:00:00 2001 From: xu-shawn <50402888+xu-shawn@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:17:58 -0800 Subject: [PATCH 0803/1309] Add Leela Data Attribution closes https://github.com/official-stockfish/Stockfish/pull/5705 No functional change --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 25da319d5a2..621f1d13076 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,11 @@ where the source code can be found) to generate the exact binary you are distributing. If you make any changes to the source code, these changes must also be made available under GPL v3. +## Acknowledgements + +Stockfish uses neural networks trained on [data provided by the Leela Chess Zero +project][lc0-data-link], which is made available under the [Open Database License][odbl-link] (ODbL). + [authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS [build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml @@ -144,6 +149,8 @@ also be made available under GPL v3. [wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands [wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage [worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker +[lc0-data-link]: https://storage.lczero.org/files/training_data +[odbl-link]: https://opendatacommons.org/licenses/odbl/odbl-10.txt [build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github [commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge From afaf3a0f2a06918e4c046e27743cbe71befb3216 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 3 Dec 2024 09:18:27 +0300 Subject: [PATCH 0804/1309] Refine statscore for captures Continuation of previous attempts there. Now instead of using capture history with a static offset also add the value of the captured piece in the same way at it is used in movepicker. Passed STC: https://tests.stockfishchess.org/tests/view/674aa3d386d5ee47d95404aa LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 116480 W: 30433 L: 29999 D: 56048 Ptnml(0-2): 361, 13720, 29662, 14118, 379 Passed LTC: https://tests.stockfishchess.org/tests/view/674c4b2d86d5ee47d954073f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 133542 W: 34365 L: 33847 D: 65330 Ptnml(0-2): 78, 14585, 36934, 15089, 85 closes https://github.com/official-stockfish/Stockfish/pull/5706 Bench: 934447 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 149222fc7fa..3ce30b81309 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1180,7 +1180,10 @@ Value Search::Worker::search( r -= 1879; if (capture) - ss->statScore = 0; + ss->statScore = + 7 * int(PieceValue[pos.captured_piece()]) + + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] + - 5000; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From a8b6bf1b1a978775ad15ae677d8d425ccd05304b Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 7 Dec 2024 15:28:02 -0800 Subject: [PATCH 0805/1309] Small Major/Minor piece key simplification/optimization. closes https://github.com/official-stockfish/Stockfish/pull/5710 No functional change --- src/position.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index bab7a1fcac5..1b1c0269faf 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -360,7 +360,7 @@ void Position::set_state() const { { st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; - if (type_of(pc) == QUEEN || type_of(pc) == ROOK) + if (type_of(pc) >= ROOK) st->majorPieceKey ^= Zobrist::psq[pc][s]; else @@ -759,7 +759,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[them] -= PieceValue[captured]; st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq]; - if (type_of(captured) == QUEEN || type_of(captured) == ROOK) + if (type_of(captured) >= ROOK) st->majorPieceKey ^= Zobrist::psq[captured][capsq]; else @@ -844,7 +844,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; - if (promotionType == QUEEN || promotionType == ROOK) + if (promotionType >= ROOK) st->majorPieceKey ^= Zobrist::psq[promotion][to]; else @@ -871,7 +871,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - else if (type_of(pc) == QUEEN || type_of(pc) == ROOK) + else if (type_of(pc) >= ROOK) st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; else From cf10644d6e2592e663e48b3d41dae07e7294166e Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:24:29 +0100 Subject: [PATCH 0806/1309] Fix duplicate code (#5711) closes https://github.com/official-stockfish/Stockfish/pull/5711 No functional change --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3ce30b81309..e352c96e33e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1041,9 +1041,7 @@ Value Search::Worker::search( { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) - if (bestValue <= futilityValue && !is_decisive(bestValue) - && !is_win(futilityValue)) - bestValue = futilityValue; + bestValue = futilityValue; continue; } From b822fdf2f2f00758c794cb61a25a044424d2bc0a Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 24 Nov 2024 16:29:20 -0800 Subject: [PATCH 0807/1309] Tune histories Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 268736 W: 70080 L: 69421 D: 129235 Ptnml(0-2): 831, 31795, 68460, 32448, 834 https://tests.stockfishchess.org/tests/view/6750778886d5ee47d9540e7c Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 505356 W: 129145 L: 127868 D: 248343 Ptnml(0-2): 307, 54901, 140959, 56230, 281 https://tests.stockfishchess.org/tests/view/675367de86d5ee47d9541536 closes https://github.com/official-stockfish/Stockfish/pull/5712 Bench: 1148169 --- src/history.h | 2 +- src/search.cpp | 76 ++++++++++++++++++++++++++------------------------ src/search.h | 5 ++++ 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/src/history.h b/src/history.h index 8d14a7a7cb1..17ab3481c3e 100644 --- a/src/history.h +++ b/src/history.h @@ -138,7 +138,7 @@ using LowPlyHistory = Stats; // PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] -using PieceToHistory = Stats; +using PieceToHistory = Stats; // ContinuationHistory is the combined history of a given pair of moves, usually // the current one given a previous one. The nested history table is based on diff --git a/src/search.cpp b/src/search.cpp index e352c96e33e..9a0a9d04606 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -275,7 +275,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(0); + lowPlyHistory.fill(106); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -500,10 +500,10 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(0); - lowPlyHistory.fill(0); - captureHistory.fill(-758); - pawnHistory.fill(-1158); + mainHistory.fill(61); + lowPlyHistory.fill(106); + captureHistory.fill(-598); + pawnHistory.fill(-1181); pawnCorrectionHistory.fill(0); majorPieceCorrectionHistory.fill(0); minorPieceCorrectionHistory.fill(0); @@ -518,7 +518,7 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-645); + h->fill(-427); for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int((19.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); @@ -538,7 +538,7 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); + return qsearch(pos, ss, alpha, beta); // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); @@ -644,13 +644,13 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth)); + update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 747 / 1024); // Extra penalty for early quiet moves of // the previous ply (~1 Elo on STC, ~2 Elo on LTC) if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_malus(depth + 1)); + -stat_malus(depth + 1) * 1091 / 1024); } // Partial workaround for the graph history interaction problem @@ -762,10 +762,10 @@ Value Search::Worker::search( if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1831, 1428) + 623; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1340 / 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; + << bonus * 1159 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -909,7 +909,7 @@ Value Search::Worker::search( if (value >= probCutBeta) { - thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] << 1300; + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] << 1226; // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, @@ -1216,8 +1216,8 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates (~1 Elo) - int bonus = 2 * (value >= beta) * stat_bonus(newDepth); - update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); + int bonus = (value >= beta) * stat_bonus(newDepth); + update_continuation_histories(ss, movedPiece, move.to_sq(), bonus * 1427 / 1024); } } @@ -1379,24 +1379,25 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (117 * (depth > 5) + 39 * !allNode + 168 * ((ss - 1)->moveCount > 8) - + 115 * (!ss->inCheck && bestValue <= ss->staticEval - 108) - + 119 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 83)); + int bonusScale = (117 * (depth > 5) + 39 * !allNode + 168 * ((ss - 1)->moveCount > 8) + + 115 * (!ss->inCheck && bestValue <= ss->staticEval - 108) + + 119 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 83)); // Proportional to "how much damage we have to undo" - bonus += std::min(-(ss - 1)->statScore / 113, 300); + bonusScale += std::min(-(ss - 1)->statScore / 113, 300); - bonus = std::max(bonus, 0); + bonusScale = std::max(bonusScale, 0); + + const int scaledBonus = stat_bonus(depth) * bonusScale / 32; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - stat_bonus(depth) * bonus / 93); - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << stat_bonus(depth) * bonus / 179; + scaledBonus * 416 / 1024); + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 212 / 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] - << stat_bonus(depth) * bonus / 24; + << scaledBonus * 1073 / 1024; } else if (priorCapture && prevSq != SQ_NONE) @@ -1410,7 +1411,7 @@ Value Search::Worker::search( // Bonus when search fails low and there is a TT move else if (ttData.move && !allNode) - thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) * 23 / 100; + thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) * 287 / 1024; if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1808,30 +1809,30 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1131 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1028 / 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; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1291 / 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); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 919 / 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; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1090 / 1024; } } @@ -1839,16 +1840,16 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { + static constexpr std::array conthist_bonuses = { + {{1, 1024}, {2, 571}, {3, 339}, {4, 500}, {6, 592}}}; - bonus = bonus * 50 / 64; - - for (int i : {1, 2, 3, 4, 6}) + for (const auto [i, weight] : conthist_bonuses) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (((ss - i)->currentMove).is_ok()) - (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + (i == 3)); + (*(ss - i)->continuationHistory)[pc][to] << bonus * weight / 1024; } } @@ -1858,14 +1859,15 @@ void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { Color us = pos.side_to_move(); - workerThread.mainHistory[us][move.from_to()] << bonus; + 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; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 874 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 853 / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus / 2; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 628 / 1024; } } diff --git a/src/search.h b/src/search.h index b618855b9fc..e9a7943d128 100644 --- a/src/search.h +++ b/src/search.h @@ -351,6 +351,11 @@ class Worker { friend class SearchManager; }; +struct ConthistBonus { + int index; + int weight; +}; + } // namespace Search From f414d490bc0e4013c211a066623f313883f49106 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 13 Dec 2024 16:59:34 +0100 Subject: [PATCH 0808/1309] Update Incbin Library No functional change --- src/incbin/incbin.h | 226 ++++++++++++++++++++++++++++++++----------- src/nnue/network.cpp | 4 +- 2 files changed, 170 insertions(+), 60 deletions(-) diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h index 18718b95fae..3f662e15dad 100644 --- a/src/incbin/incbin.h +++ b/src/incbin/incbin.h @@ -3,8 +3,8 @@ * @author Dale Weiler * @brief Utility for including binary files * - * Facilities for including binary files into the current translation unit - * and making use of them externally in other translation units. + * Facilities for including binary files into the current translation unit and + * making use from them externally in other translation units. */ #ifndef INCBIN_HDR #define INCBIN_HDR @@ -26,7 +26,9 @@ defined(__SSSE3__) || \ defined(__SSE4_1__) || \ defined(__SSE4_2__) || \ - defined(__neon__) + defined(__neon__) || \ + defined(__ARM_NEON) || \ + defined(__ALTIVEC__) # define INCBIN_ALIGNMENT_INDEX 4 #elif ULONG_MAX != 0xffffffffu # define INCBIN_ALIGNMENT_INDEX 3 @@ -64,6 +66,9 @@ X #define INCBIN_INVOKE(N, ...) \ INCBIN_EVAL(N(__VA_ARGS__)) +/* Variable argument count for overloading by arity */ +#define INCBIN_VA_ARG_COUNTER(_1, _2, _3, N, ...) N +#define INCBIN_VA_ARGC(...) INCBIN_VA_ARG_COUNTER(__VA_ARGS__, 3, 2, 1, 0) /* Green Hills uses a different directive for including binary data */ #if defined(__ghs__) @@ -117,29 +122,50 @@ #endif /** - * @brief Optionally override the linker section into which data is emitted. - * - * @warning If you use this facility, you'll have to deal with platform-specific linker output - * section naming on your own - * - * Overriding the default linker output section, e.g for esp8266/Arduino: - * @code - * #define INCBIN_OUTPUT_SECTION ".irom.text" - * #include "incbin.h" - * INCBIN(Foo, "foo.txt"); - * // Data is emitted into program memory that never gets copied to RAM - * @endcode + * @brief Optionally override the linker section into which size and data is + * emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. */ #if !defined(INCBIN_OUTPUT_SECTION) # if defined(__APPLE__) -# define INCBIN_OUTPUT_SECTION ".const_data" +# define INCBIN_OUTPUT_SECTION ".const_data" # else -# define INCBIN_OUTPUT_SECTION ".rodata" +# define INCBIN_OUTPUT_SECTION ".rodata" # endif #endif +/** + * @brief Optionally override the linker section into which data is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + */ +#if !defined(INCBIN_OUTPUT_DATA_SECTION) +# define INCBIN_OUTPUT_DATA_SECTION INCBIN_OUTPUT_SECTION +#endif + +/** + * @brief Optionally override the linker section into which size is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + * + * @note This is useful for Harvard architectures where program memory cannot + * be directly read from the program without special instructions. With this you + * can chose to put the size variable in RAM rather than ROM. + */ +#if !defined(INCBIN_OUTPUT_SIZE_SECTION) +# define INCBIN_OUTPUT_SIZE_SECTION INCBIN_OUTPUT_SECTION +#endif + #if defined(__APPLE__) -/* The directives are different for Apple-branded compilers */ +# include "TargetConditionals.h" +# if defined(TARGET_OS_IPHONE) && !defined(INCBIN_SILENCE_BITCODE_WARNING) +# warning "incbin is incompatible with bitcode. Using the library will break upload to App Store if you have bitcode enabled. Add `#define INCBIN_SILENCE_BITCODE_WARNING` before including this header to silence this warning." +# endif +/* The directives are different for Apple branded compilers */ # define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" # define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" # define INCBIN_INT ".long " @@ -179,27 +205,17 @@ /** * @brief Specify the prefix to use for symbol names. * - * By default this is `g', producing symbols of the form: - * @code - * #include "incbin.h" - * INCBIN(Foo, "foo.txt"); - * - * // Now you have the following symbols: - * // const unsigned char gFooData[]; - * // const unsigned char *const gFooEnd; - * // const unsigned int gFooSize; - * @endcode + * @note By default this is "g". * - * If however you specify a prefix before including: e.g: * @code * #define INCBIN_PREFIX incbin * #include "incbin.h" * INCBIN(Foo, "foo.txt"); * * // Now you have the following symbols instead: - * // const unsigned char incbinFooData[]; - * // const unsigned char *const incbinFooEnd; - * // const unsigned int incbinFooSize; + * // const unsigned char incbinFoo[]; + * // const unsigned char *const incbinFoo; + * // const unsigned int incbinFoo; * @endcode */ #if !defined(INCBIN_PREFIX) @@ -213,18 +229,8 @@ * - INCBIN_STYLE_CAMEL "CamelCase" * - INCBIN_STYLE_SNAKE "snake_case" * - * Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form: - * @code - * #include "incbin.h" - * INCBIN(Foo, "foo.txt"); - * - * // Now you have the following symbols: - * // const unsigned char FooData[]; - * // const unsigned char *const FooEnd; - * // const unsigned int FooSize; - * @endcode + * @note By default this is INCBIN_STYLE_CAMEL * - * If however you specify a style before including: e.g: * @code * #define INCBIN_STYLE INCBIN_STYLE_SNAKE * #include "incbin.h" @@ -261,8 +267,8 @@ INCBIN_STRINGIZE( \ INCBIN_STYLE_IDENT(TYPE)) \ -/* Generate the global labels by indirectly invoking the macro - * with our style type and concatenate the name against them. */ +/* Generate the global labels by indirectly invoking the macro with our style + * type and concatenating the name against them. */ #define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ INCBIN_INVOKE( \ INCBIN_GLOBAL, \ @@ -288,23 +294,38 @@ * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with * "Data", as well as "End" and "Size" after. An example is provided below. * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. * @param NAME The name given for the binary data * * @code * INCBIN_EXTERN(Foo); * * // Now you have the following symbols: - * // extern const unsigned char FooData[]; - * // extern const unsigned char *const FooEnd; - * // extern const unsigned int FooSize; + * // extern const unsigned char Foo[]; + * // extern const unsigned char *const Foo; + * // extern const unsigned int Foo; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * @code + * INCBIN_EXTERN(custom_type, Foo); + * + * // Now you have the following symbols: + * // extern const custom_type Foo[]; + * // extern const custom_type *const Foo; + * // extern const unsigned int Foo; * @endcode */ -#define INCBIN_EXTERN(NAME) \ - INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \ +#define INCBIN_EXTERN(...) \ + INCBIN_CONCATENATE(INCBIN_EXTERN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +#define INCBIN_EXTERN_1(NAME, ...) \ + INCBIN_EXTERN_2(unsigned char, NAME) +#define INCBIN_EXTERN_2(TYPE, NAME) \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE \ INCBIN_CONCATENATE( \ INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ INCBIN_STYLE_IDENT(DATA))[]; \ - INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE *const \ INCBIN_CONCATENATE( \ INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ INCBIN_STYLE_IDENT(END)); \ @@ -313,6 +334,29 @@ INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ INCBIN_STYLE_IDENT(SIZE)) +/** + * @brief Externally reference textual data included in another translation unit. + * + * Produces three external symbols that reference the textual data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name given for the textual data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const char Foo[]; + * // extern const char *const Foo; + * // extern const unsigned int Foo; + * @endcode + */ +#define INCTXT_EXTERN(NAME) \ + INCBIN_EXTERN_2(char, NAME) + /** * @brief Include a binary file into the current translation unit. * @@ -322,6 +366,7 @@ * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with * "Data", as well as "End" and "Size" after. An example is provided below. * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. * @param NAME The name to associate with this binary data (as an identifier.) * @param FILENAME The file to include (as a string literal.) * @@ -329,9 +374,20 @@ * INCBIN(Icon, "icon.png"); * * // Now you have the following symbols: - * // const unsigned char IconData[]; - * // const unsigned char *const IconEnd; - * // const unsigned int IconSize; + * // const unsigned char Icon[]; + * // const unsigned char *const Icon; + * // const unsigned int Icon; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * These macros are specialized by arity. + * @code + * INCBIN(custom_type, Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const custom_type Icon[]; + * // const custom_type *const Icon; + * // const unsigned int Icon; * @endcode * * @warning This must be used in global scope @@ -341,15 +397,28 @@ * please @see INCBIN_EXTERN. */ #ifdef _MSC_VER -#define INCBIN(NAME, FILENAME) \ - INCBIN_EXTERN(NAME) +# define INCBIN(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) #else -#define INCBIN(NAME, FILENAME) \ +# define INCBIN(...) \ + INCBIN_CONCATENATE(INCBIN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +# if defined(__GNUC__) +# define INCBIN_1(...) _Pragma("GCC error \"Single argument INCBIN not allowed\"") +# elif defined(__clang__) +# define INCBIN_1(...) _Pragma("clang error \"Single argument INCBIN not allowed\"") +# else +# define INCBIN_1(...) /* Cannot do anything here */ +# endif +# define INCBIN_2(NAME, FILENAME) \ + INCBIN_3(unsigned char, NAME, FILENAME) +# define INCBIN_3(TYPE, NAME, FILENAME) INCBIN_COMMON(TYPE, NAME, FILENAME, /* No terminator for binary data */) +# define INCBIN_COMMON(TYPE, NAME, FILENAME, TERMINATOR) \ __asm__(INCBIN_SECTION \ INCBIN_GLOBAL_LABELS(NAME, DATA) \ INCBIN_ALIGN_HOST \ INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \ INCBIN_MACRO " \"" FILENAME "\"\n" \ + TERMINATOR \ INCBIN_GLOBAL_LABELS(NAME, END) \ INCBIN_ALIGN_BYTE \ INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \ @@ -362,7 +431,46 @@ INCBIN_ALIGN_HOST \ ".text\n" \ ); \ - INCBIN_EXTERN(NAME) - + INCBIN_EXTERN(TYPE, NAME) #endif + +/** + * @brief Include a textual file into the current translation unit. + * + * This behaves the same as INCBIN except it produces char compatible arrays + * and implicitly adds a null-terminator byte, thus the size of data included + * by this is one byte larger than that of INCBIN. + * + * Includes a textual file into the current translation unit, producing three + * symbols for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCTXT(Readme, "readme.txt"); + * + * // Now you have the following symbols: + * // const char Readme[]; + * // const char *const Readme; + * // const unsigned int Readme; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#if defined(_MSC_VER) +# define INCTXT(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +# define INCTXT(NAME, FILENAME) \ + INCBIN_COMMON(char, NAME, FILENAME, INCBIN_BYTE "0\n") #endif + +#endif \ No newline at end of file diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index a8e901a0d32..0a4452f6604 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -26,8 +26,10 @@ #include #include -#include "../evaluate.h" +#define INCBIN_SILENCE_BITCODE_WARNING #include "../incbin/incbin.h" + +#include "../evaluate.h" #include "../memory.h" #include "../misc.h" #include "../position.h" From 1776448917e49b922a762d2d08c00a3f3be10205 Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 13 Dec 2024 17:00:05 +0100 Subject: [PATCH 0809/1309] Move Embedded Net Data out of Anon Namespace fixes https://github.com/official-stockfish/Stockfish/issues/5714 closes https://github.com/official-stockfish/Stockfish/pull/5715 No functional change --- src/nnue/network.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 0a4452f6604..01cf2516d4a 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -38,7 +38,6 @@ #include "nnue_common.h" #include "nnue_misc.h" -namespace { // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). // This macro invocation will declare the following three variables @@ -58,6 +57,8 @@ const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1 const unsigned int gEmbeddedNNUESmallSize = 1; #endif +namespace { + struct EmbeddedNNUE { EmbeddedNNUE(const unsigned char* embeddedData, const unsigned char* embeddedEnd, From e770b55f7f25d1aa4ce3d80f511868dc86d6b6d9 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 13 Dec 2024 11:45:11 -0800 Subject: [PATCH 0810/1309] Remove Extraneous Parenthesis No longer needed after https://github.com/official-stockfish/Stockfish/pull/5667. closes https://github.com/official-stockfish/Stockfish/pull/5717 No functional change --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9a0a9d04606..36c0b8c0349 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -538,7 +538,7 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch(pos, ss, alpha, beta); + return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); @@ -1714,7 +1714,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 + 1304 - delta * 814 / rootDelta) + (!i && reductionScale > 1423) * 1135; + return reductionScale - delta * 814 / rootDelta + (!i && reductionScale > 1423) * 1135 + 1304; } // elapsed() returns the time elapsed since the search started. If the From ba145332c9f0b8126bd940ec5afbc7769ef43174 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 13 Dec 2024 13:45:54 -0800 Subject: [PATCH 0811/1309] Remove time reduction for recaptures Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 54016 W: 14098 L: 13902 D: 26016 Ptnml(0-2): 165, 5797, 14919, 5931, 196 https://tests.stockfishchess.org/tests/view/6758a90486d5ee47d954201e Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 296940 W: 75631 L: 75689 D: 145620 Ptnml(0-2): 145, 28928, 90384, 28866, 147 https://tests.stockfishchess.org/tests/view/6758df7a86d5ee47d9542091 closes https://github.com/official-stockfish/Stockfish/pull/5719 Bench: 1148169 --- src/engine.cpp | 8 -------- src/engine.h | 3 --- src/search.cpp | 3 +-- src/search.h | 1 - 4 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 85c84099352..30ad6ba0fb7 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -58,7 +58,6 @@ Engine::Engine(std::optional path) : NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { pos.set(StartFEN, false, &states->back()); - capSq = SQ_NONE; options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); @@ -125,7 +124,6 @@ std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960 void Engine::go(Search::LimitsType& limits) { assert(limits.perft == 0); verify_networks(); - limits.capSq = capSq; threads.start_thinking(options, pos, states, limits); } @@ -168,7 +166,6 @@ void Engine::set_position(const std::string& fen, const std::vector states = StateListPtr(new std::deque(1)); pos.set(fen, options["UCI_Chess960"], &states->back()); - capSq = SQ_NONE; for (const auto& move : moves) { auto m = UCIEngine::to_move(pos, move); @@ -178,11 +175,6 @@ void Engine::set_position(const std::string& fen, const std::vector states->emplace_back(); pos.do_move(m, states->back()); - - capSq = SQ_NONE; - DirtyPiece& dp = states->back().dirtyPiece; - if (dp.dirty_num > 1 && dp.to[1] == SQ_NONE) - capSq = m.to_sq(); } } diff --git a/src/engine.h b/src/engine.h index 257826935d9..2d17fb31d1e 100644 --- a/src/engine.h +++ b/src/engine.h @@ -39,8 +39,6 @@ namespace Stockfish { -enum Square : int; - class Engine { public: using InfoShort = Search::InfoShort; @@ -116,7 +114,6 @@ class Engine { Position pos; StateListPtr states; - Square capSq; OptionsMap options; ThreadPool threads; diff --git a/src/search.cpp b/src/search.cpp index 36c0b8c0349..f42ecf7116c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -453,10 +453,9 @@ void Search::Worker::iterative_deepening() { timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); - double recapture = limits.capSq == rootMoves[0].pv[0].to_sq() ? 0.955 : 1.005; double totalTime = - mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * recapture; + mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) diff --git a/src/search.h b/src/search.h index e9a7943d128..c65267a1d60 100644 --- a/src/search.h +++ b/src/search.h @@ -126,7 +126,6 @@ struct LimitsType { int movestogo, depth, mate, perft, infinite; uint64_t nodes; bool ponderMode; - Square capSq; }; From 77ec878ffa5b33e796c01d8331d07d05838212ae Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 15 Dec 2024 01:35:15 -0800 Subject: [PATCH 0812/1309] Prevent out of bounds access of dbg info arrays closes https://github.com/official-stockfish/Stockfish/pull/5721 No functional change --- src/misc.cpp | 53 ++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 10c86b7a6e7..7db8bd59efa 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -18,7 +18,9 @@ #include "misc.h" +#include #include +#include #include #include #include @@ -287,7 +289,10 @@ template struct DebugInfo { std::atomic data[N] = {0}; - constexpr std::atomic& operator[](int index) { return data[index]; } + [[nodiscard]] constexpr std::atomic& operator[](size_t index) { + assert(index < N); + return data[index]; + } }; struct DebugExtremes: public DebugInfo<3> { @@ -297,54 +302,54 @@ struct DebugExtremes: public DebugInfo<3> { } }; -DebugInfo<2> hit[MaxDebugSlots]; -DebugInfo<2> mean[MaxDebugSlots]; -DebugInfo<3> stdev[MaxDebugSlots]; -DebugInfo<6> correl[MaxDebugSlots]; -DebugExtremes extremes[MaxDebugSlots]; +std::array, MaxDebugSlots> hit; +std::array, MaxDebugSlots> mean; +std::array, MaxDebugSlots> stdev; +std::array, MaxDebugSlots> correl; +std::array extremes; } // namespace void dbg_hit_on(bool cond, int slot) { - ++hit[slot][0]; + ++hit.at(slot)[0]; if (cond) - ++hit[slot][1]; + ++hit.at(slot)[1]; } void dbg_mean_of(int64_t value, int slot) { - ++mean[slot][0]; - mean[slot][1] += value; + ++mean.at(slot)[0]; + mean.at(slot)[1] += value; } void dbg_stdev_of(int64_t value, int slot) { - ++stdev[slot][0]; - stdev[slot][1] += value; - stdev[slot][2] += value * value; + ++stdev.at(slot)[0]; + stdev.at(slot)[1] += value; + stdev.at(slot)[2] += value * value; } void dbg_extremes_of(int64_t value, int slot) { - ++extremes[slot][0]; + ++extremes.at(slot)[0]; - int64_t current_max = extremes[slot][1].load(); - while (current_max < value && !extremes[slot][1].compare_exchange_weak(current_max, value)) + int64_t current_max = extremes.at(slot)[1].load(); + while (current_max < value && !extremes.at(slot)[1].compare_exchange_weak(current_max, value)) {} - int64_t current_min = extremes[slot][2].load(); - while (current_min > value && !extremes[slot][2].compare_exchange_weak(current_min, value)) + int64_t current_min = extremes.at(slot)[2].load(); + while (current_min > value && !extremes.at(slot)[2].compare_exchange_weak(current_min, value)) {} } void dbg_correl_of(int64_t value1, int64_t value2, int slot) { - ++correl[slot][0]; - correl[slot][1] += value1; - correl[slot][2] += value1 * value1; - correl[slot][3] += value2; - correl[slot][4] += value2 * value2; - correl[slot][5] += value1 * value2; + ++correl.at(slot)[0]; + correl.at(slot)[1] += value1; + correl.at(slot)[2] += value1 * value1; + correl.at(slot)[3] += value2; + correl.at(slot)[4] += value2 * value2; + correl.at(slot)[5] += value1 * value2; } void dbg_print() { From 2dc47e4345a7a13421e66b88168a13bd8d6bf1bf Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 15 Dec 2024 13:52:20 +0100 Subject: [PATCH 0813/1309] Cleanup Evaluate Calls Makes code a bit easier to read as well. closes https://github.com/official-stockfish/Stockfish/pull/5722 No functional change --- src/search.cpp | 30 ++++++++++++------------------ src/search.h | 2 ++ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f42ecf7116c..4bebd985076 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -60,7 +60,6 @@ void syzygy_extend_pv(const OptionsMap& options, Stockfish::Search::RootMove& rootMove, Value& v); -using Eval::evaluate; using namespace Search; namespace { @@ -592,10 +591,8 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks[numaAccessToken], pos, refreshTable, - thisThread->optimism[us]) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -732,8 +729,7 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; if (!is_valid(unadjustedStaticEval)) - unadjustedStaticEval = - evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(pos); else if (PvNode) Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); @@ -747,9 +743,8 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = - evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); - ss->staticEval = eval = + unadjustedStaticEval = evaluate(pos); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); // Static evaluation is saved as it was before adjustment by correction history @@ -1510,9 +1505,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) - ? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) - : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1542,8 +1535,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; if (!is_valid(unadjustedStaticEval)) - unadjustedStaticEval = - evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(pos); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); @@ -1556,9 +1548,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { // In case of null move search, use previous static eval with opposite sign unadjustedStaticEval = - (ss - 1)->currentMove != Move::null() - ? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]) - : -(ss - 1)->staticEval; + (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); } @@ -1730,6 +1720,10 @@ TimePoint Search::Worker::elapsed() const { TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } +Value Search::Worker::evaluate(const Position& pos) { + return Eval::evaluate(networks[numaAccessToken], pos, refreshTable, + optimism[pos.side_to_move()]); +} namespace { // Adjusts a mate or TB score from "plies to mate from the root" to diff --git a/src/search.h b/src/search.h index c65267a1d60..945c66c1cd3 100644 --- a/src/search.h +++ b/src/search.h @@ -313,6 +313,8 @@ class Worker { TimePoint elapsed() const; TimePoint elapsed_time() const; + Value evaluate(const Position&); + LimitsType limits; size_t pvIdx, pvLast; From 6075e787d05e609bd5e0c64acb0f5eb1dc623dde Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 16 Dec 2024 13:35:15 -0800 Subject: [PATCH 0814/1309] Add CI test with glibcxx assertions enabled Re: https://github.com/official-stockfish/Stockfish/pull/5721#pullrequestreview-2504542601 closes https://github.com/official-stockfish/Stockfish/pull/5723 No functional change --- .github/workflows/sanitizers.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 946a81cec4a..950435f30a1 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -21,19 +21,28 @@ jobs: sanitizers: - name: Run with thread sanitizer make_option: sanitize=thread + cxx_extra_flags: "" instrumented_option: sanitizer-thread - name: Run with UB sanitizer make_option: sanitize=undefined + cxx_extra_flags: "" instrumented_option: sanitizer-undefined - name: Run under valgrind make_option: "" + cxx_extra_flags: "" instrumented_option: valgrind - name: Run under valgrind-thread make_option: "" + cxx_extra_flags: "" instrumented_option: valgrind-thread - name: Run non-instrumented make_option: "" + cxx_extra_flags: "" instrumented_option: none + - name: Run with glibcxx assertions + make_option: "" + cxx_extra_flags: -D_GLIBCXX_ASSERTIONS + instrumented_option: non defaults: run: working-directory: src @@ -72,7 +81,7 @@ jobs: - name: ${{ matrix.sanitizers.name }} run: | - export CXXFLAGS="-O1 -fno-inline" + export CXXFLAGS="-O1 -fno-inline ${{ matrix.sanitizers.cxx_extra_flags }}" make clean make -j4 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null python3 ../tests/instrumented.py --${{ matrix.sanitizers.instrumented_option }} ./stockfish From a04b07265ff5ce8ea27bd4ecb761a56341399bde Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 18 Dec 2024 14:32:48 +0300 Subject: [PATCH 0815/1309] Make reductionScale smoother Making the second part of the formula smoother, changing it to a linear function, increasing steadily as reductionScale increases and at the same time, it should be a little bit simpler, therefore the simplification bounds. Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 83040 W: 21493 L: 21322 D: 40225 Ptnml(0-2): 252, 9848, 21209, 9899, 312 https://tests.stockfishchess.org/tests/view/6762145486d5ee47d9543242 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 230124 W: 58485 L: 58478 D: 113161 Ptnml(0-2): 175, 25620, 63484, 25589, 194 https://tests.stockfishchess.org/tests/view/6762d4ef86d5ee47d9543367 closes https://github.com/official-stockfish/Stockfish/pull/5725 Bench: 1204658 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4bebd985076..02d7b677707 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1703,7 +1703,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 * 814 / rootDelta + (!i && reductionScale > 1423) * 1135 + 1304; + return reductionScale - delta * 814 / rootDelta + !i * reductionScale / 3 + 1304; } // elapsed() returns the time elapsed since the search started. If the From e7e78aa09e190ea0fd7fed6fcff97d98c0cf5b5f Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 14 Dec 2024 15:13:32 -0800 Subject: [PATCH 0816/1309] Adjust LMR with correction history A positive constant increase in base reduction is applied to counter the decrease in average reduction from this tweak. Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 109216 W: 28415 L: 27989 D: 52812 Ptnml(0-2): 310, 12848, 27911, 13184, 355 https://tests.stockfishchess.org/tests/view/6760bb0e86d5ee47d9542f26 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 66918 W: 17073 L: 16694 D: 33151 Ptnml(0-2): 33, 7175, 18666, 7550, 35 https://tests.stockfishchess.org/tests/view/6761e10f86d5ee47d95431fa closes https://github.com/official-stockfish/Stockfish/pull/5727 Bench: 1294909 --- src/search.cpp | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 02d7b677707..f184a353942 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,9 +77,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { return (3 + depth * depth) / (2 - improving); } -// Add correctionHistory value to raw staticEval and guarantee evaluation -// does not hit the tablebase range. -Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, Stack* ss) { +int correction_value(const Worker& w, const Position& pos, 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)]; @@ -87,15 +85,17 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, St 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)]; - int cntcv = 1; + const auto cntcv = + m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + : 0; - if (m.is_ok()) - cntcv = int((*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]); + return (6384 * pcv + 3583 * macv + 6492 * micv + 6725 * (wnpcv + bnpcv) + 5880 * cntcv); +} - const auto cv = - (6384 * pcv + 3583 * macv + 6492 * micv + 6725 * (wnpcv + bnpcv) + cntcv * 5880) / 131072; - v += cv; - return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); +// 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) { + return std::clamp(v + cv / 131072, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth @@ -709,7 +709,8 @@ Value Search::Worker::search( } // Step 6. Static evaluation of the position - Value unadjustedStaticEval = VALUE_NONE; + Value unadjustedStaticEval = VALUE_NONE; + const auto correctionValue = correction_value(*thisThread, pos, ss); if (ss->inCheck) { // Skip early pruning when in check @@ -733,8 +734,7 @@ Value Search::Worker::search( else if (PvNode) Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); - ss->staticEval = eval = - to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); // ttValue can be used as a better position evaluation (~7 Elo) if (is_valid(ttData.value) @@ -744,8 +744,7 @@ Value Search::Worker::search( else { unadjustedStaticEval = evaluate(pos); - ss->staticEval = eval = - to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); // Static evaluation is saved as it was before adjustment by correction history ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), @@ -1155,6 +1154,10 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling + r += 330; + + r -= std::min(std::abs(correctionValue) / 32768, 2048); + // Increase reduction for cut nodes (~4 Elo) if (cutNode) r += 2518 - (ttData.depth >= depth && ss->ttPv) * 991; @@ -1525,7 +1528,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return ttData.value; // Step 4. Static evaluation of the position - Value unadjustedStaticEval = VALUE_NONE; + Value unadjustedStaticEval = VALUE_NONE; + const auto correctionValue = correction_value(*thisThread, pos, ss); if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; else @@ -1537,7 +1541,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(pos); ss->staticEval = bestValue = - to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); + to_corrected_static_eval(unadjustedStaticEval, correctionValue); // ttValue can be used as a better position evaluation (~13 Elo) if (is_valid(ttData.value) && !is_decisive(ttData.value) @@ -1550,7 +1554,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) unadjustedStaticEval = (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; ss->staticEval = bestValue = - to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss); + to_corrected_static_eval(unadjustedStaticEval, correctionValue); } // Stand pat. Return immediately if static value is at least beta From 4bc2a24245faa8ea8b3b8a80d03bd7436ac154a2 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 18 Dec 2024 19:47:56 +0100 Subject: [PATCH 0817/1309] Workaround for clang-format bug closes https://github.com/official-stockfish/Stockfish/pull/5728 No functional change --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f184a353942..1f19c74db1e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -536,7 +536,10 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta); + { + constexpr auto nt = PvNode ? PV : NonPV; + return qsearch(pos, ss, alpha, beta); + } // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); From 79261bec593267a79a4af9bfb08b2ee848f67dfa Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 19 Dec 2024 01:14:40 +0300 Subject: [PATCH 0818/1309] Simplify away reductions adjustment for multithreaded search Seem to no longer bring measurable benefit. Passed STC SMP simplification: https://tests.stockfishchess.org/tests/view/6753561a86d5ee47d954151f LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 38000 W: 9864 L: 9656 D: 18480 Ptnml(0-2): 53, 4177, 10320, 4409, 41 Passed LTC SMP simplification: https://tests.stockfishchess.org/tests/view/6753d75f86d5ee47d9541669 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 258674 W: 66314 L: 66335 D: 126025 Ptnml(0-2): 77, 26957, 75303, 26910, 90 Passed 16 threads LTC simplification: https://tests.stockfishchess.org/tests/view/675a066286d5ee47d9542296 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 221804 W: 56950 L: 56936 D: 107918 Ptnml(0-2): 34, 21491, 67839, 21503, 35 closes https://github.com/official-stockfish/Stockfish/pull/5729 Bench: 1294909 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1f19c74db1e..192b837c902 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -519,7 +519,7 @@ void Search::Worker::clear() { h->fill(-427); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((19.43 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int(19.43 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } From f656fdfa9a4566f9d5d7ffe86f8390a53e518b3c Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 19 Dec 2024 15:08:16 -0800 Subject: [PATCH 0819/1309] Simplify Zobrist keys for captures The Zobrist keys for NO_PIECE are 0 so no need to special case captures. Also the TranspositionTable reference passed to do_null_move() can be const. STC Simplification: https://tests.stockfishchess.org/tests/view/6764a79a86d5ee47d9544005 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 114240 W: 29654 L: 29523 D: 55063 Ptnml(0-2): 329, 12360, 31620, 12473, 338 closes https://github.com/official-stockfish/Stockfish/pull/5731 No functional change --- src/position.cpp | 7 ++----- src/position.h | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 1b1c0269faf..2bc0aa6508e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1012,7 +1012,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ // Used to do a "null move": it flips // the side to move without executing any move on the board. -void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { +void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { assert(!checkers()); assert(&newSt != st); @@ -1071,10 +1071,7 @@ Key Position::key_after(Move m) const { Piece captured = piece_on(to); Key k = st->key ^ Zobrist::side; - if (captured) - k ^= Zobrist::psq[captured][to]; - - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; + k ^= Zobrist::psq[captured][to] ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; return (captured || type_of(pc) == PAWN) ? k : adjust_key50(k); } diff --git a/src/position.h b/src/position.h index 888612da78d..a339471d260 100644 --- a/src/position.h +++ b/src/position.h @@ -143,7 +143,7 @@ class Position { void do_move(Move m, StateInfo& newSt); void do_move(Move m, StateInfo& newSt, bool givesCheck); void undo_move(Move m); - void do_null_move(StateInfo& newSt, TranspositionTable& tt); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); void undo_null_move(); // Static Exchange Evaluation From 03e4cde729bdd357afc1e0ecb40e9e7780ada978 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 23 Dec 2024 16:12:29 +0300 Subject: [PATCH 0820/1309] Allow Pv nodes at certain conditions to spawn zero window searches deeper than default In current case it's allowed if there is no best move. Passed STC: https://tests.stockfishchess.org/tests/view/67640fd586d5ee47d9543d5a LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 392480 W: 102038 L: 101192 D: 189250 Ptnml(0-2): 1303, 46287, 100253, 47055, 1342 Passed LTC: https://tests.stockfishchess.org/tests/view/67671a4686d5ee47d9544476 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 128616 W: 32941 L: 32433 D: 63242 Ptnml(0-2): 84, 13997, 35634, 14513, 80 closes https://github.com/official-stockfish/Stockfish/pull/5733 Bench: 1095871 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 192b837c902..1fe89c4ac16 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1198,7 +1198,7 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode)); + Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From 78b57339394b284ee5d1fe32e5c2c285f80df550 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 21 Dec 2024 12:06:35 -0800 Subject: [PATCH 0821/1309] Simplify post-lmr conthist bonus Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 49184 W: 12735 L: 12528 D: 23921 Ptnml(0-2): 134, 5746, 12647, 5909, 156 https://tests.stockfishchess.org/tests/view/6765cd2e86d5ee47d954420e Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 177270 W: 45227 L: 45166 D: 86877 Ptnml(0-2): 132, 19498, 49302, 19583, 120 https://tests.stockfishchess.org/tests/view/676721fd86d5ee47d9544489 closes https://github.com/official-stockfish/Stockfish/pull/5734 Bench: 1042099 --- src/search.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1fe89c4ac16..44394c1d620 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1198,7 +1198,8 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); + Depth d = std::max( + 1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); @@ -1216,8 +1217,8 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates (~1 Elo) - int bonus = (value >= beta) * stat_bonus(newDepth); - update_continuation_histories(ss, movedPiece, move.to_sq(), bonus * 1427 / 1024); + int bonus = (value >= beta) * 2048; + update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } From 5cf6f991771d4260517d28812bfaef192c42ac97 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 29 Dec 2024 09:32:29 -0800 Subject: [PATCH 0822/1309] Remove some incorrectly marked const qualifiers closes https://github.com/official-stockfish/Stockfish/pull/5744 No functional change --- src/nnue/network.cpp | 2 +- src/nnue/nnue_feature_transformer.h | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 01cf2516d4a..9191148bbc0 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -101,7 +101,7 @@ bool read_parameters(std::istream& stream, T& reference) { // Write evaluation function parameters template -bool write_parameters(std::ostream& stream, const T& reference) { +bool write_parameters(std::ostream& stream, T& reference) { write_little_endian(stream, T::get_hash_value()); return reference.write_parameters(stream); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index fa180678d89..556a114af90 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -256,21 +256,20 @@ class FeatureTransformer { #endif } - void permute_weights([[maybe_unused]] void (*order_fn)(uint64_t*)) const { + 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(const_cast(&biases[0])); + 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(const_cast(&weights[j * HalfDimensions])); + 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]); @@ -278,17 +277,16 @@ class FeatureTransformer { #endif } - inline void scale_weights(bool read) const { + inline void scale_weights(bool read) { for (IndexType j = 0; j < InputDimensions; ++j) { - WeightType* w = const_cast(&weights[j * HalfDimensions]); + WeightType* w = &weights[j * HalfDimensions]; for (IndexType i = 0; i < HalfDimensions; ++i) w[i] = read ? w[i] * 2 : w[i] / 2; } - BiasType* b = const_cast(biases); for (IndexType i = 0; i < HalfDimensions; ++i) - b[i] = read ? b[i] * 2 : b[i] / 2; + biases[i] = read ? biases[i] * 2 : biases[i] / 2; } // Read network parameters @@ -304,7 +302,7 @@ class FeatureTransformer { } // Write network parameters - bool write_parameters(std::ostream& stream) const { + bool write_parameters(std::ostream& stream) { permute_weights(order_packs); scale_weights(false); From 00da3ff4637ea982c5ca3a2a07393d3be6aaba90 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 30 Dec 2024 23:00:03 -0800 Subject: [PATCH 0823/1309] Cleanup stats entry Prevents potential issue caused by publicly inheriting from STL container types closes https://github.com/official-stockfish/Stockfish/pull/5746 No functional change --- src/history.h | 56 ++++++++++++++++++++++++++++++++++++------------ src/movepick.cpp | 1 - 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/history.h b/src/history.h index 17ab3481c3e..2ec2b79116b 100644 --- a/src/history.h +++ b/src/history.h @@ -76,9 +76,12 @@ class StatsEntry { T entry; public: - void operator=(const T& v) { entry = v; } - T* operator&() { return &entry; } - T* operator->() { return &entry; } + StatsEntry& operator=(const T& v) { + entry = v; + return *this; + } + T* operator&() { return &entry; } + T* operator->() { return &entry; } operator const T&() const { return entry; } void operator<<(int bonus) { @@ -92,28 +95,53 @@ class StatsEntry { } }; +template +struct StatsHelper; + // Stats is a generic N-dimensional array used to store various statistics. // The first template parameter T is the base type of the array, and the second // template parameter D limits the range of updates in [-D, D] when we update // values with the << operator, while the last parameters (Size and Sizes) // encode the dimensions of the array. -template -struct Stats: public std::array, Size> { - using stats = Stats; +template +class Stats { + using child_type = typename StatsHelper::child_type; + using array_type = std::array; + array_type data; - void fill(const T& v) { + public: + using size_type = typename array_type::size_type; + + auto& operator[](size_type index) { return data[index]; } + const auto& operator[](size_type index) const { return data[index]; } - // For standard-layout 'this' points to the first struct member - assert(std::is_standard_layout_v); + auto begin() { return data.begin(); } + auto end() { return data.end(); } + auto begin() const { return data.cbegin(); } + auto end() const { return data.cend(); } + auto cbegin() const { return data.cbegin(); } + auto cend() const { return data.cend(); } - using entry = StatsEntry; - entry* p = reinterpret_cast(this); - std::fill(p, p + sizeof(*this) / sizeof(entry), v); + void fill(const T& v) { + for (auto& ele : data) + { + if constexpr (sizeof...(Sizes) == 0) + ele = v; + else + ele.fill(v); + } } }; -template -struct Stats: public std::array, Size> {}; +template +struct StatsHelper { + using child_type = Stats; +}; + +template +struct StatsHelper { + using child_type = StatsEntry; +}; // In stats table, D=0 means that the template parameter is not used enum StatsParams { diff --git a/src/movepick.cpp b/src/movepick.cpp index 96f031717f8..540024b7dda 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -18,7 +18,6 @@ #include "movepick.h" -#include #include #include From 5488dd2f91226f73d93ab8f892d4aaa9b2582eed Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 31 Dec 2024 22:28:16 -0800 Subject: [PATCH 0824/1309] Update Copyright Year closes https://github.com/official-stockfish/Stockfish/pull/5747 No functional change --- src/Makefile | 2 +- src/benchmark.cpp | 2 +- src/benchmark.h | 2 +- src/bitboard.cpp | 2 +- src/bitboard.h | 2 +- src/engine.cpp | 2 +- src/engine.h | 2 +- src/evaluate.cpp | 2 +- src/evaluate.h | 2 +- src/history.h | 2 +- src/main.cpp | 2 +- src/memory.cpp | 2 +- src/memory.h | 2 +- src/misc.cpp | 2 +- src/misc.h | 2 +- src/movegen.cpp | 2 +- src/movegen.h | 2 +- src/movepick.cpp | 2 +- src/movepick.h | 2 +- src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/features/half_ka_v2_hm.h | 2 +- src/nnue/layers/affine_transform.h | 2 +- src/nnue/layers/affine_transform_sparse_input.h | 2 +- src/nnue/layers/clipped_relu.h | 2 +- src/nnue/layers/simd.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/network.cpp | 2 +- src/nnue/network.h | 2 +- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_architecture.h | 2 +- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 2 +- src/nnue/nnue_misc.cpp | 2 +- src/nnue/nnue_misc.h | 2 +- src/numa.h | 2 +- src/perft.h | 2 +- src/position.cpp | 2 +- src/position.h | 2 +- src/score.cpp | 2 +- src/score.h | 2 +- src/search.cpp | 2 +- src/search.h | 2 +- src/syzygy/tbprobe.cpp | 2 +- src/syzygy/tbprobe.h | 2 +- src/thread.cpp | 2 +- src/thread.h | 2 +- src/thread_win32_osx.h | 2 +- src/timeman.cpp | 2 +- src/timeman.h | 2 +- src/tt.cpp | 2 +- src/tt.h | 2 +- src/tune.cpp | 2 +- src/tune.h | 2 +- src/types.h | 2 +- src/uci.cpp | 2 +- src/uci.h | 2 +- src/ucioption.cpp | 2 +- src/ucioption.h | 2 +- 58 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/Makefile b/src/Makefile index e7f8ce556bb..39cfce8bf19 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) +# Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 35ad3c18014..a9f70f10d1a 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.h b/src/benchmark.h index eb3a52d894d..d6bdc275df9 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.cpp b/src/bitboard.cpp index deda6da2a5e..8798c5701d5 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.h b/src/bitboard.h index c4bf18b531b..6f9cca0bdd4 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/engine.cpp b/src/engine.cpp index 30ad6ba0fb7..d835fc8e461 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/engine.h b/src/engine.h index 2d17fb31d1e..d26844f4ce4 100644 --- a/src/engine.h +++ b/src/engine.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.cpp b/src/evaluate.cpp index bc86a7420b8..9a523ae4475 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.h b/src/evaluate.h index 4604321d378..aad358321eb 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/history.h b/src/history.h index 2ec2b79116b..edf7fdc6716 100644 --- a/src/history.h +++ b/src/history.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index a6a3d1c4e85..e262f387576 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/memory.cpp b/src/memory.cpp index 47c901b4e33..92cb650f26f 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/memory.h b/src/memory.h index eaf0261aa2f..4dc23287835 100644 --- a/src/memory.h +++ b/src/memory.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.cpp b/src/misc.cpp index 7db8bd59efa..06a8c624a10 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.h b/src/misc.h index 21093769b76..48c49bafb34 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.cpp b/src/movegen.cpp index 69b8fe6ae2b..8653a828fde 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.h b/src/movegen.h index f067f8808b6..7c6cceb7c3d 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.cpp b/src/movepick.cpp index 540024b7dda..4c5cc11dec1 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.h b/src/movepick.h index ab4e832fd7d..71078bdcf3d 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 71782a7b731..5bb0296e297 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 96349704745..ca940c54ed4 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 59a6149f0c4..f5c640fb948 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 0ac557abac2..cbeb507f083 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 2ee378ad881..2ad5a86a12c 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h index 55cb7df1421..70ca68a0c70 100644 --- a/src/nnue/layers/simd.h +++ b/src/nnue/layers/simd.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 9c20df9d6f5..d14f1e0aba3 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 9191148bbc0..b96625a799e 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/network.h b/src/nnue/network.h index 95253595a2c..f99fa11827d 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index b92901e4a29..0d3d9413556 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 7f73f87fd5e..0c9f097dc60 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 4bc3408f18a..f21a8dec7df 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 556a114af90..6192cd51e32 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index a2bece21df0..1e269050391 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index 27a93f88435..a7647f84609 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/numa.h b/src/numa.h index 1063721e3fc..5cadbc726fc 100644 --- a/src/numa.h +++ b/src/numa.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/perft.h b/src/perft.h index e907742da05..f0d38ab7256 100644 --- a/src/perft.h +++ b/src/perft.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.cpp b/src/position.cpp index 2bc0aa6508e..60b7d7d3f20 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.h b/src/position.h index a339471d260..7dc83f251be 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/score.cpp b/src/score.cpp index 179796d2080..561bc23c48e 100644 --- a/src/score.cpp +++ b/src/score.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/score.h b/src/score.h index 2eb40f7e08e..eda90af35c0 100644 --- a/src/score.h +++ b/src/score.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.cpp b/src/search.cpp index 44394c1d620..2453679ec53 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.h b/src/search.h index 945c66c1cd3..dee759410ba 100644 --- a/src/search.h +++ b/src/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 9b24e700b18..120e64885a6 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 75a1858576b..c34338fe3c9 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.cpp b/src/thread.cpp index 5f73771effa..43ba7d9c08d 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.h b/src/thread.h index 43e2e1423ce..912d4433528 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index 1d9a834f600..fb4b2ec9718 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.cpp b/src/timeman.cpp index 9de70fdc613..d0b0d0a9492 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.h b/src/timeman.h index 10207a8a730..e8602bb7ce8 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.cpp b/src/tt.cpp index 75689562d61..50f5ca45af8 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.h b/src/tt.h index e7bb5c452b4..f0936fd282c 100644 --- a/src/tt.h +++ b/src/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.cpp b/src/tune.cpp index dfcd34689d4..aff96c8cbe9 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.h b/src/tune.h index ed4738cdc47..4dab6bf457e 100644 --- a/src/tune.h +++ b/src/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/types.h b/src/types.h index 5644460113f..6465dfd6b4f 100644 --- a/src/types.h +++ b/src/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.cpp b/src/uci.cpp index 8388cad8cee..4b8e8b7f508 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.h b/src/uci.h index 6adf74cb85a..5c1c07f7b18 100644 --- a/src/uci.h +++ b/src/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 455803cfe1b..56cf41edcc9 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/ucioption.h b/src/ucioption.h index a47cc98de53..c9f6787d3dd 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From 1611b9c940f784619d6cbf01ff8b1b3ab28405a4 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 2 Jan 2025 14:58:53 -0500 Subject: [PATCH 0825/1309] Corrplexity for futility pruning Add corrhist-based term to futility margin Inspired by a recent patch of Shawn Xu, this tweak increases the margin over beta needed to futility prune based on the correction history, with an offset. Passed STC LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 545504 W: 141957 L: 140885 D: 262662 Ptnml(0-2): 1829, 64226, 139551, 65336, 1810 https://tests.stockfishchess.org/tests/view/67634a8386d5ee47d95439db Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 125994 W: 32199 L: 31695 D: 62100 Ptnml(0-2): 97, 13742, 34798, 14280, 80 https://tests.stockfishchess.org/tests/view/6765cf9986d5ee47d9544217 closes https://github.com/official-stockfish/Stockfish/pull/5748 Bench: 999324 --- src/search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/search.cpp b/src/search.cpp index 2453679ec53..f53c116c816 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -787,6 +787,7 @@ Value Search::Worker::search( if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - (ss - 1)->statScore / 290 + + (ss->staticEval == eval) * (40 - std::abs(correctionValue) / 131072) >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; From c76c1793615b17b97dd504f8c81c1ff2c63c232a Mon Sep 17 00:00:00 2001 From: Daniel Monroe <39802758+Ergodice@users.noreply.github.com> Date: Sun, 5 Jan 2025 15:18:09 -0800 Subject: [PATCH 0826/1309] Remove non-functional std::min() closes https://github.com/official-stockfish/Stockfish/pull/5749 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f53c116c816..d6748c7696e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1160,7 +1160,7 @@ Value Search::Worker::search( r += 330; - r -= std::min(std::abs(correctionValue) / 32768, 2048); + r -= std::abs(correctionValue) / 32768; // Increase reduction for cut nodes (~4 Elo) if (cutNode) From 7858f9dfdcc26fc4f1bf2fc0975803c5fcee2968 Mon Sep 17 00:00:00 2001 From: sscg13 Date: Fri, 27 Dec 2024 12:15:10 +0800 Subject: [PATCH 0827/1309] Use same pawn value in both nets when doing material scaling of eval Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 221312 W: 57291 L: 57274 D: 106747 Ptnml(0-2): 760, 26152, 56841, 26117, 786 https://tests.stockfishchess.org/tests/view/676e2a101a2f267f20548453 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 176808 W: 45084 L: 45023 D: 86701 Ptnml(0-2): 112, 19418, 49286, 19473, 115 https://tests.stockfishchess.org/tests/view/676f424d1a2f267f2054857f closes https://github.com/official-stockfish/Stockfish/pull/5741 Bench: 1121800 --- AUTHORS | 1 + src/evaluate.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index ddc53ec0249..f6468c561ec 100644 --- a/AUTHORS +++ b/AUTHORS @@ -47,6 +47,7 @@ Bryan Cross (crossbr) candirufish Carlos Esparza Sánchez (ces42) Chess13234 +Chris Bao (sscg13) Chris Cain (ceebo) Ciekce clefrks diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 9a523ae4475..4fce86e3a9b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -78,7 +78,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, optimism += optimism * nnueComplexity / 468; nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879); - int material = (smallNet ? 553 : 532) * pos.count() + pos.non_pawn_material(); + int material = 535 * pos.count() + pos.non_pawn_material(); int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; // Damp down the evaluation linearly when shuffling From d1a1ff4f17e91d77856f10f7e0f79b6d56e8179c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 14 Dec 2024 15:02:06 -0800 Subject: [PATCH 0828/1309] Simplify Razoring Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 95584 W: 24906 L: 24750 D: 45928 Ptnml(0-2): 285, 11227, 24632, 11343, 305 https://tests.stockfishchess.org/tests/view/675e0ed286d5ee47d95429ee Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 191292 W: 48637 L: 48589 D: 94066 Ptnml(0-2): 97, 21061, 53276, 21121, 91 https://tests.stockfishchess.org/tests/view/675f08c686d5ee47d9542be3 closes https://github.com/official-stockfish/Stockfish/pull/5724 Bench: 1286274 --- src/search.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d6748c7696e..226506616c9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -775,12 +775,9 @@ Value Search::Worker::search( // Step 7. Razoring (~1 Elo) // If eval is really low, check with qsearch if we can exceed alpha. If the // search suggests we cannot exceed alpha, return a speculative fail low. - if (eval < alpha - 469 - 307 * depth * depth) - { - value = qsearch(pos, ss, alpha - 1, alpha); - if (value < alpha && !is_decisive(value)) - return value; - } + // For PvNodes, we must have a guard against mates being returned. + if (!PvNode && eval < alpha - 469 - 307 * depth * depth) + return qsearch(pos, ss, alpha - 1, alpha); // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. From c47e6fcf84fecda5973cebeeb766aba43ac3cda3 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 26 Dec 2024 23:11:41 -0800 Subject: [PATCH 0829/1309] Small cleanup of nnue_feature_transformer.h Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 285760 W: 73716 L: 73768 D: 138276 Ptnml(0-2): 777, 30775, 79851, 30677, 800 https://tests.stockfishchess.org/tests/view/676f78681a2f267f205485aa closes https://github.com/official-stockfish/Stockfish/pull/5745 No functional change --- src/nnue/nnue_feature_transformer.h | 235 ++++++++++++++-------------- 1 file changed, 120 insertions(+), 115 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 6192cd51e32..b047f62c409 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -146,10 +146,10 @@ using psqt_vec_t = int32x4_t; #endif +// Compute optimal SIMD register count for feature transformer accumulation. +template +class SIMDTiling { #ifdef VECTOR - - // Compute optimal SIMD register count for feature transformer accumulation. - // We use __m* types as template arguments, which causes GCC to emit warnings // about losing some attribute information. This is irrelevant to us as we // only take their size, so the following pragma are harmless. @@ -158,33 +158,47 @@ using psqt_vec_t = int32x4_t; #pragma GCC diagnostic ignored "-Wignored-attributes" #endif -template -static constexpr int BestRegisterCount() { - #define RegisterSize sizeof(SIMDRegisterType) - #define LaneSize sizeof(LaneType) - - static_assert(RegisterSize >= LaneSize); - static_assert(MaxRegisters <= NumRegistersSIMD); - static_assert(MaxRegisters > 0); - static_assert(NumRegistersSIMD > 0); - static_assert(RegisterSize % LaneSize == 0); - static_assert((NumLanes * LaneSize) % RegisterSize == 0); - - const int ideal = (NumLanes * LaneSize) / RegisterSize; - if (ideal <= MaxRegisters) - return ideal; - - // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters - for (int divisor = MaxRegisters; divisor > 1; --divisor) - if (ideal % divisor == 0) - return divisor; - - return 1; -} + template + static constexpr int BestRegisterCount() { + constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType); + constexpr std::size_t LaneSize = sizeof(LaneType); + + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); + + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; + + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; + + return 1; + } + #if defined(__GNUC__) #pragma GCC diagnostic pop #endif + + public: + static constexpr int NumRegs = + BestRegisterCount(); + static constexpr int NumPsqtRegs = + BestRegisterCount(); + + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; + static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; + + static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); + static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); #endif +}; // Input feature converter @@ -196,17 +210,7 @@ class FeatureTransformer { static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; private: -#ifdef VECTOR - static constexpr int NumRegs = - BestRegisterCount(); - static constexpr int NumPsqtRegs = - BestRegisterCount(); - - static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; - static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; - static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); - static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); -#endif + using Tiling = SIMDTiling; public: // Output type @@ -478,8 +482,8 @@ class FeatureTransformer { #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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; #endif const Square ksq = pos.square(Perspective); @@ -504,14 +508,14 @@ class FeatureTransformer { #ifdef VECTOR if ((removed.size() == 1 || removed.size() == 2) && added.size() == 1) { - auto accIn = + auto* accIn = reinterpret_cast(&(computed->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast(&(next->*accPtr).accumulation[Perspective][0]); + auto* accOut = reinterpret_cast(&(next->*accPtr).accumulation[Perspective][0]); const IndexType offsetR0 = HalfDimensions * removed[0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); + auto* columnR0 = reinterpret_cast(&weights[offsetR0]); const IndexType offsetA = HalfDimensions * added[0]; - auto columnA = reinterpret_cast(&weights[offsetA]); + auto* columnA = reinterpret_cast(&weights[offsetA]); if (removed.size() == 1) { @@ -521,22 +525,22 @@ class FeatureTransformer { else { const IndexType offsetR1 = HalfDimensions * removed[1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); + 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]), vec_add_16(columnR0[i], columnR1[i])); } - auto accPsqtIn = reinterpret_cast( + auto* accPsqtIn = reinterpret_cast( &(computed->*accPtr).psqtAccumulation[Perspective][0]); - auto accPsqtOut = + auto* accPsqtOut = reinterpret_cast(&(next->*accPtr).psqtAccumulation[Perspective][0]); const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; - auto columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); + auto* columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); const IndexType offsetPsqtA = PSQTBuckets * added[0]; - auto columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); + auto* columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); if (removed.size() == 1) { @@ -548,7 +552,8 @@ class FeatureTransformer { else { const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto columnPsqtR1 = reinterpret_cast(&psqtWeights[offsetPsqtR1]); + auto* columnPsqtR1 = + reinterpret_cast(&psqtWeights[offsetPsqtR1]); for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) @@ -559,69 +564,69 @@ class FeatureTransformer { } else { - for (IndexType i = 0; i < HalfDimensions / TileHeight; ++i) + for (IndexType i = 0; i < HalfDimensions / Tiling::TileHeight; ++i) { // Load accumulator - auto accTileIn = reinterpret_cast( - &(computed->*accPtr).accumulation[Perspective][i * TileHeight]); - for (IndexType j = 0; j < NumRegs; ++j) + 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]); // Difference calculation for the deactivated features for (const auto index : removed) { - const IndexType offset = HalfDimensions * index + i * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumRegs; ++j) + 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 * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < NumRegs; ++j) + 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 * TileHeight]); - for (IndexType j = 0; j < NumRegs; ++j) + 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 (IndexType i = 0; i < PSQTBuckets / PsqtTileHeight; ++i) + for (IndexType i = 0; i < PSQTBuckets / Tiling::PsqtTileHeight; ++i) { // Load accumulator - auto accTilePsqtIn = reinterpret_cast( - &(computed->*accPtr).psqtAccumulation[Perspective][i * PsqtTileHeight]); - for (std::size_t j = 0; j < NumPsqtRegs; ++j) + 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 * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t j = 0; j < NumPsqtRegs; ++j) + 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 * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t j = 0; j < NumPsqtRegs; ++j) + 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 * PsqtTileHeight]); - for (std::size_t j = 0; j < NumPsqtRegs; ++j) + 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]); } } @@ -700,88 +705,88 @@ class FeatureTransformer { accumulator.computed[Perspective] = true; #ifdef VECTOR - vec_t acc[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + for (IndexType j = 0; j < HalfDimensions / Tiling::TileHeight; ++j) { - auto accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); - auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); + auto* accTile = reinterpret_cast( + &accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = entryTile[k]; - int i = 0; - for (; i < int(std::min(removed.size(), added.size())); ++i) + std::size_t i = 0; + for (; i < std::min(removed.size(), added.size()); ++i) { IndexType indexR = removed[i]; - const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; - auto columnR = reinterpret_cast(&weights[offsetR]); + const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&weights[offsetR]); IndexType indexA = added[i]; - const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; - auto columnA = reinterpret_cast(&weights[offsetA]); + const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&weights[offsetA]); - for (unsigned k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); } - for (; i < int(removed.size()); ++i) + for (; i < removed.size(); ++i) { IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); - for (unsigned k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } - for (; i < int(added.size()); ++i) + for (; i < added.size(); ++i) { IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); - for (unsigned k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_add_16(acc[k], column[k]); } - for (IndexType k = 0; k < NumRegs; k++) + for (IndexType k = 0; k < Tiling::NumRegs; k++) vec_store(&entryTile[k], acc[k]); - for (IndexType k = 0; k < NumRegs; k++) + for (IndexType k = 0; k < Tiling::NumRegs; k++) vec_store(&accTile[k], acc[k]); } - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); - auto entryTilePsqt = - reinterpret_cast(&entry.psqtAccumulation[j * PsqtTileHeight]); + auto* accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + auto* entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; - for (int i = 0; i < int(removed.size()); ++i) + for (std::size_t i = 0; i < removed.size(); ++i) { IndexType index = removed[i]; - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } - for (int i = 0; i < int(added.size()); ++i) + for (std::size_t i = 0; i < added.size(); ++i) { IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } - for (std::size_t k = 0; k < NumPsqtRegs; ++k) + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) vec_store_psqt(&entryTilePsqt[k], psqt[k]); - for (std::size_t k = 0; k < NumPsqtRegs; ++k) + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } From 5370c3035e17a163376d83c49e7c927174e587a7 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 5 Jan 2025 17:11:22 -0800 Subject: [PATCH 0830/1309] Refactor Stats Array * Limit use of `StatsEntry` wrapper to arithmetic types * Generalize `Stats` to `MultiArray` by discarding the template parameter `D` * Allow `MultiArray::fill` to take any type assignable to element type * Remove now-unused operator overloads on `StatsEntry` closes https://github.com/official-stockfish/Stockfish/pull/5750 No functional change --- src/history.h | 97 ++++++++++++++---------------------------------- src/misc.h | 87 +++++++++++++++++++++++++++++++++++++++++++ src/movepick.cpp | 1 + src/search.cpp | 4 +- 4 files changed, 117 insertions(+), 72 deletions(-) diff --git a/src/history.h b/src/history.h index edf7fdc6716..15095cd0bbc 100644 --- a/src/history.h +++ b/src/history.h @@ -28,6 +28,7 @@ #include #include // IWYU pragma: keep +#include "misc.h" #include "position.h" namespace Stockfish { @@ -66,13 +67,17 @@ inline int non_pawn_index(const Position& pos) { return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); } -// StatsEntry stores the stat table value. It is usually a number but could -// be a move or even a nested history. We use a class instead of a naked value -// to directly call history update operator<<() on the entry so to use stats -// tables at caller sites as simple multi-dim arrays. +// StatsEntry is the container of various numerical statistics. We use a class +// instead of a naked value to directly call history update operator<<() on +// the entry. The first template parameter T is the base type of the array, +// and the second template parameter D limits the range of updates in [-D, D] +// when we update values with the << operator template class StatsEntry { + static_assert(std::is_arithmetic::value, "Not an arithmetic type"); + static_assert(D <= std::numeric_limits::max(), "D overflows T"); + T entry; public: @@ -80,13 +85,9 @@ class StatsEntry { entry = v; return *this; } - T* operator&() { return &entry; } - T* operator->() { return &entry; } operator const T&() const { return entry; } void operator<<(int bonus) { - static_assert(D <= std::numeric_limits::max(), "D overflows T"); - // Make sure that bonus is in range [-D, D] int clampedBonus = std::clamp(bonus, -D, D); entry += clampedBonus - entry * std::abs(clampedBonus) / D; @@ -95,87 +96,39 @@ class StatsEntry { } }; -template -struct StatsHelper; - -// Stats is a generic N-dimensional array used to store various statistics. -// The first template parameter T is the base type of the array, and the second -// template parameter D limits the range of updates in [-D, D] when we update -// values with the << operator, while the last parameters (Size and Sizes) -// encode the dimensions of the array. -template -class Stats { - using child_type = typename StatsHelper::child_type; - using array_type = std::array; - array_type data; - - public: - using size_type = typename array_type::size_type; - - auto& operator[](size_type index) { return data[index]; } - const auto& operator[](size_type index) const { return data[index]; } - - auto begin() { return data.begin(); } - auto end() { return data.end(); } - auto begin() const { return data.cbegin(); } - auto end() const { return data.cend(); } - auto cbegin() const { return data.cbegin(); } - auto cend() const { return data.cend(); } - - void fill(const T& v) { - for (auto& ele : data) - { - if constexpr (sizeof...(Sizes) == 0) - ele = v; - else - ele.fill(v); - } - } -}; - -template -struct StatsHelper { - using child_type = Stats; -}; - -template -struct StatsHelper { - using child_type = StatsEntry; -}; - -// In stats table, D=0 means that the template parameter is not used -enum StatsParams { - NOT_USED = 0 -}; enum StatsType { NoCaptures, Captures }; +template +using Stats = MultiArray, Sizes...>; + // ButterflyHistory records how often quiet moves have been successful or unsuccessful // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, // see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) -using ButterflyHistory = Stats; +using ButterflyHistory = Stats; // LowPlyHistory is adressed by play and move's from and to squares, used // to improve move ordering near the root -using LowPlyHistory = Stats; +using LowPlyHistory = + Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] -using CapturePieceToHistory = Stats; +using CapturePieceToHistory = Stats; // PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] -using PieceToHistory = Stats; +using PieceToHistory = Stats; // ContinuationHistory is the combined history of a given pair of moves, usually // the current one given a previous one. The nested history table is based on // PieceToHistory instead of ButterflyBoards. // (~63 elo) -using ContinuationHistory = Stats; +using ContinuationHistory = MultiArray; // PawnHistory is addressed by the pawn structure and a move's [piece][to] -using PawnHistory = Stats; +using PawnHistory = Stats; // Correction histories record differences between the static evaluation of // positions and their search score. It is used to improve the static evaluation @@ -190,23 +143,27 @@ enum CorrHistType { Continuation, // Combined history of move pairs }; +namespace Detail { + template struct CorrHistTypedef { - using type = Stats; + using type = Stats; }; template<> struct CorrHistTypedef { - using type = Stats; + using type = Stats; }; template<> struct CorrHistTypedef { - using type = Stats::type, NOT_USED, PIECE_NB, SQUARE_NB>; + using type = MultiArray::type, PIECE_NB, SQUARE_NB>; }; +} + template -using CorrectionHistory = typename CorrHistTypedef::type; +using CorrectionHistory = typename Detail::CorrHistTypedef::type; } // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index 48c49bafb34..81c7b17fe59 100644 --- a/src/misc.h +++ b/src/misc.h @@ -20,6 +20,7 @@ #define MISC_H_INCLUDED #include +#include #include #include #include @@ -142,6 +143,92 @@ class ValueList { }; +template +class MultiArray; + +namespace Detail { + +template +struct MultiArrayHelper { + using ChildType = MultiArray; +}; + +template +struct MultiArrayHelper { + using ChildType = T; +}; + +} + +// MultiArray is a generic N-dimensional array. +// The template parameters (Size and Sizes) encode the dimensions of the array. +template +class MultiArray { + using ChildType = typename Detail::MultiArrayHelper::ChildType; + using ArrayType = std::array; + ArrayType data_; + + public: + using value_type = typename ArrayType::value_type; + using size_type = typename ArrayType::size_type; + using difference_type = typename ArrayType::difference_type; + using reference = typename ArrayType::reference; + using const_reference = typename ArrayType::const_reference; + using pointer = typename ArrayType::pointer; + using const_pointer = typename ArrayType::const_pointer; + using iterator = typename ArrayType::iterator; + using const_iterator = typename ArrayType::const_iterator; + using reverse_iterator = typename ArrayType::reverse_iterator; + using const_reverse_iterator = typename ArrayType::const_reverse_iterator; + + constexpr auto& at(size_type index) noexcept { return data_.at(index); } + constexpr const auto& at(size_type index) const noexcept { return data_.at(index); } + + constexpr auto& operator[](size_type index) noexcept { return data_[index]; } + constexpr const auto& operator[](size_type index) const noexcept { return data_[index]; } + + constexpr auto& front() noexcept { return data_.front(); } + constexpr const auto& front() const noexcept { return data_.front(); } + constexpr auto& back() noexcept { return data_.back(); } + constexpr const auto& back() const noexcept { return data_.back(); } + + auto* data() { return data_.data(); } + const auto* data() const { return data_.data(); } + + constexpr auto begin() noexcept { return data_.begin(); } + constexpr auto end() noexcept { return data_.end(); } + constexpr auto begin() const noexcept { return data_.begin(); } + constexpr auto end() const noexcept { return data_.end(); } + constexpr auto cbegin() const noexcept { return data_.cbegin(); } + constexpr auto cend() const noexcept { return data_.cend(); } + + constexpr auto rbegin() noexcept { return data_.rbegin(); } + constexpr auto rend() noexcept { return data_.rend(); } + constexpr auto rbegin() const noexcept { return data_.rbegin(); } + constexpr auto rend() const noexcept { return data_.rend(); } + constexpr auto crbegin() const noexcept { return data_.crbegin(); } + constexpr auto crend() const noexcept { return data_.crend(); } + + constexpr bool empty() const noexcept { return data_.empty(); } + constexpr size_type size() const noexcept { return data_.size(); } + constexpr size_type max_size() const noexcept { return data_.max_size(); } + + template + void fill(const U& v) { + static_assert(std::is_assignable_v, "Cannot assign fill value to entry type"); + for (auto& ele : data_) + { + if constexpr (sizeof...(Sizes) == 0) + ele = v; + else + ele.fill(v); + } + } + + constexpr void swap(MultiArray& other) noexcept { data_.swap(other.data_); } +}; + + // xorshift64star Pseudo-Random Number Generator // This class is based on original code written and dedicated // to the public domain by Sebastiano Vigna (2014). diff --git a/src/movepick.cpp b/src/movepick.cpp index 4c5cc11dec1..8448616949d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -22,6 +22,7 @@ #include #include "bitboard.h" +#include "misc.h" #include "position.h" namespace Stockfish { diff --git a/src/search.cpp b/src/search.cpp index 226506616c9..d937c399a85 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -510,13 +510,13 @@ void Search::Worker::clear() { for (auto& to : continuationCorrectionHistory) for (auto& h : to) - h->fill(0); + h.fill(0); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-427); + h.fill(-427); for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int(19.43 * std::log(i)); From 403a5e100b006b46d3b4001ae9a3ef53ad1b7558 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 31 Dec 2024 21:14:16 -0800 Subject: [PATCH 0831/1309] Simplify Fail-Low Bonus Passed Non-regression STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 66592 W: 17426 L: 17239 D: 31927 Ptnml(0-2): 208, 7812, 17109, 7919, 248 https://tests.stockfishchess.org/tests/view/6774e1711a2f267f20548b22 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 179616 W: 45764 L: 45706 D: 88146 Ptnml(0-2): 125, 19665, 50162, 19739, 117 https://tests.stockfishchess.org/tests/view/677590531a2f267f20548b82 closes https://github.com/official-stockfish/Stockfish/pull/5751 Bench: 1310158 --- src/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d937c399a85..42a7bb971cf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1408,10 +1408,6 @@ Value Search::Worker::search( << stat_bonus(depth) * 2; } - // Bonus when search fails low and there is a TT move - else if (ttData.move && !allNode) - thisThread->mainHistory[us][ttData.move.from_to()] << stat_bonus(depth) * 287 / 1024; - if (PvNode) bestValue = std::min(bestValue, maxValue); From c88a5b395021a0020b417faf897e8ad3b71512e7 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Tue, 7 Jan 2025 10:06:03 +0100 Subject: [PATCH 0832/1309] Simplify away hint for common parent position at probcut MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since it's introduction at probcut step the nnue network has changed substantially and now it no longer seems useful. Passed non-regression test at STC https://tests.stockfishchess.org/tests/view/675fe27986d5ee47d9542d86 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 118656 W: 30732 L: 30609 D: 57315 Ptnml(0-2): 319, 12767, 33049, 12858, 335 N.B.: It may be useful to reintroduce it here at probcut if we know that a node that was cut away previously now has to be explored. Exploring new ground will deliver no tt-hits so in this case the hint for common parent position might be useful. No functional change --- src/search.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 42a7bb971cf..4a72a41620e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -911,8 +911,6 @@ Value Search::Worker::search( return is_decisive(value) ? value : value - (probCutBeta - beta); } } - - Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); } moves_loop: // When in check, search starts here From d49fd9090bc3668638f5686cb0ae39bdadc342cf Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Tue, 7 Jan 2025 15:30:05 +0200 Subject: [PATCH 0833/1309] Add .gitattributes for script LE closes https://github.com/official-stockfish/Stockfish/pull/5753 No functional change --- scripts/.gitattributes | 1 + tests/.gitattributes | 1 + 2 files changed, 2 insertions(+) create mode 100644 scripts/.gitattributes create mode 100644 tests/.gitattributes diff --git a/scripts/.gitattributes b/scripts/.gitattributes new file mode 100644 index 00000000000..dfdb8b771ce --- /dev/null +++ b/scripts/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/tests/.gitattributes b/tests/.gitattributes new file mode 100644 index 00000000000..dfdb8b771ce --- /dev/null +++ b/tests/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf From ea71a088435d4f1e51433c0a321f2afdff7814b1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 7 Jan 2025 21:16:12 +0100 Subject: [PATCH 0834/1309] Improve Instrumented Python Testing Script For betting debugging and earlier stop in case of termination, like in https://github.com/official-stockfish/Stockfish/pull/5754#issuecomment-2576120357 closes https://github.com/official-stockfish/Stockfish/pull/5755 No functional change --- tests/testing.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/testing.py b/tests/testing.py index d51ca89ac92..bc1f6b15bf9 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -150,6 +150,7 @@ def __init__(self): self.failed_test_suites = 0 self.passed_tests = 0 self.failed_tests = 0 + self.stop_on_failure = True def has_failed(self) -> bool: return self.failed_test_suites > 0 @@ -167,6 +168,9 @@ def run(self, classes: List[type]) -> bool: self.failed_test_suites += 1 else: self.passed_test_suites += 1 + except Exception as e: + self.failed_test_suites += 1 + print(f"\n{RED_COLOR}Error: {e}{RESET_COLOR}") finally: os.chdir(original_cwd) @@ -226,6 +230,10 @@ def __run_test_method(self, test_instance, method: str) -> int: if isinstance(e, AssertionError): self.__handle_assertion_error(t0, method) + if self.stop_on_failure: + self.__print_buffer_output(buffer) + raise e + fails += 1 finally: self.__print_buffer_output(buffer) @@ -286,6 +294,11 @@ def __init__( self.start() + def _check_process_alive(self): + if not self.process or self.process.poll() is not None: + print("\n".join(self.output)) + raise RuntimeError("Stockfish process has terminated") + def start(self): if self.cli: self.process = subprocess.run( @@ -314,6 +327,8 @@ def send_command(self, command: str): if not self.process: raise RuntimeError("Stockfish process is not started") + self._check_process_alive() + self.process.stdin.write(command + "\n") self.process.stdin.flush() @@ -355,6 +370,7 @@ def readline(self): raise RuntimeError("Stockfish process is not started") while True: + self._check_process_alive() line = self.process.stdout.readline().strip() self.output.append(line) From 28c07fb456855c4e082571ed7dd723a3e71fdcff Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 28 Dec 2024 03:29:18 -0800 Subject: [PATCH 0835/1309] Simplify Probcut Condition Rebased and properly guarded #5720 Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 179616 W: 45764 L: 45706 D: 88146 Ptnml(0-2): 125, 19665, 50162, 19739, 117 https://tests.stockfishchess.org/tests/view/677590531a2f267f20548b82 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 445728 W: 113467 L: 113682 D: 218579 Ptnml(0-2): 331, 49226, 123900, 49141, 266 https://tests.stockfishchess.org/tests/view/67734f351a2f267f205489d9 closes https://github.com/official-stockfish/Stockfish/pull/5758 Bench: 1180421 --- src/search.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4a72a41620e..61425acdcbe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -850,7 +850,7 @@ Value Search::Worker::search( // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. probCutBeta = beta + 187 - 56 * improving; - if (!PvNode && depth > 3 + if (depth > 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt // probCut there and in further interactions with transposition table cutoff @@ -908,7 +908,9 @@ Value Search::Worker::search( // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, unadjustedStaticEval, tt.generation()); - return is_decisive(value) ? value : value - (probCutBeta - beta); + + if (!is_decisive(value)) + return value - (probCutBeta - beta); } } } From 8d517bddfffffd9d292fb5362ce452e5eb056ce9 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 9 Jan 2025 20:22:41 -0800 Subject: [PATCH 0836/1309] Simplify accumulator updates AMD Ryzen 5 7600X ``` sf_base = 1902646 +/- 2114 (95%) sf_test = 1920873 +/- 2515 (95%) diff = 18227 +/- 3067 (95%) speedup = 0.95800% +/- 0.161% (95%) ``` Ryzen 9 5950X ``` sf_base = 1413387 +/- 3592 (95%) sf_test = 1437893 +/- 3355 (95%) diff = 24505 +/- 4669 (95%) speedup = 1.73380% +/- 0.330% (95%) ``` Intel Core i7-6700K ``` sf_base = 912476 +/- 1863 (95%) sf_test = 921864 +/- 2042 (95%) diff = 9388 +/- 3333 (95%) speedup = 1.02893% +/- 0.365% (95%) ``` Raspberry Pi 5 ``` sf_base = 260993 +/- 1508 (95%) sf_test = 262912 +/- 1746 (95%) diff = 1918 +/- 1221 (95%) speedup = 0.73504% +/- 0.468% (95%) ``` Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 23072 W: 6041 L: 5813 D: 11218 Ptnml(0-2): 61, 2435, 6319, 2657, 64 https://tests.stockfishchess.org/tests/view/6780a0ca9168c8bf30927757 closes https://github.com/official-stockfish/Stockfish/pull/5759 No functional change --- src/nnue/nnue_feature_transformer.h | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index b047f62c409..8649d9521bb 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -472,9 +472,8 @@ class FeatureTransformer { return st; } - // It computes the accumulator of the next position, or updates the - // current position's accumulator if CurrentOnly is true. - template + // 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); @@ -493,16 +492,10 @@ class FeatureTransformer { // feature set's update cost calculation to be correct and never allow // updates with more added/removed features than MaxActiveDimensions. FeatureSet::IndexList removed, added; + FeatureSet::append_changed_indices(ksq, computed->next->dirtyPiece, removed, + added); - if constexpr (CurrentOnly) - for (StateInfo* st = pos.state(); st != computed; st = st->previous) - FeatureSet::append_changed_indices(ksq, st->dirtyPiece, removed, - added); - else - FeatureSet::append_changed_indices(ksq, computed->next->dirtyPiece, - removed, added); - - StateInfo* next = CurrentOnly ? pos.state() : computed->next; + StateInfo* next = computed->next; assert(!(next->*accPtr).computed[Perspective]); #ifdef VECTOR @@ -665,8 +658,8 @@ class FeatureTransformer { (next->*accPtr).computed[Perspective] = true; - if (!CurrentOnly && next != pos.state()) - update_accumulator_incremental(pos, next); + if (next != pos.state()) + update_accumulator_incremental(pos, next); } template @@ -844,7 +837,7 @@ class FeatureTransformer { StateInfo* oldest = try_find_computed_accumulator(pos); if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) - update_accumulator_incremental(pos, oldest); + update_accumulator_incremental(pos, oldest); else update_accumulator_refresh_cache(pos, cache); } @@ -858,7 +851,7 @@ class FeatureTransformer { 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); + update_accumulator_incremental(pos, oldest); else update_accumulator_refresh_cache(pos, cache); } From 921361829a1c09d289e973a507652ec7be9dc796 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 10 Jan 2025 09:11:31 -0800 Subject: [PATCH 0837/1309] Simplify away capthist bonus in Probcut The explicit bonus has been obsoleted with the introduction of #5695 Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 132832 W: 34519 L: 34403 D: 63910 Ptnml(0-2): 430, 15754, 33931, 15872, 429 https://tests.stockfishchess.org/tests/view/678158c49168c8bf30927834 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 123492 W: 31426 L: 31309 D: 60757 Ptnml(0-2): 79, 13705, 34051, 13842, 69 https://tests.stockfishchess.org/tests/view/6782b07e6ddf09c0b4b6dbb7 closes https://github.com/official-stockfish/Stockfish/pull/5761 Bench: 1180439 --- src/search.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 61425acdcbe..47e7f4bc383 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -861,7 +861,6 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); - Piece captured; while ((move = mp.next_move()) != Move::none()) { @@ -875,10 +874,6 @@ Value Search::Worker::search( assert(pos.capture_stage(move)); - movedPiece = pos.moved_piece(move); - captured = pos.piece_on(move.to_sq()); - - // Prefetch the TT entry for the resulting position prefetch(tt.first_entry(pos.key_after(move))); @@ -903,8 +898,6 @@ Value Search::Worker::search( if (value >= probCutBeta) { - thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] << 1226; - // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3, move, unadjustedStaticEval, tt.generation()); From b84c8807a381cd48f5c3dfe1e649a0a727dc56fa Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 11 Jan 2025 14:49:09 -0800 Subject: [PATCH 0838/1309] Optimize attackers_to() https://tests.stockfishchess.org/tests/view/6782decb6ddf09c0b4b6e1b0 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 105920 W: 27571 L: 27181 D: 51168 Ptnml(0-2): 284, 10808, 30403, 11164, 301 - If we only need to know if attackers exist we can skip some calculations. - Also calculating slider/magic attackers first is better because the double lookup is slow due to memory latency. - I also included a couple of very minor cleanups in search that probably don't warrant their own PR but I can open separately if that's better. closes https://github.com/official-stockfish/Stockfish/pull/5762 No functional change --- src/position.cpp | 27 ++++++++++++++++++--------- src/position.h | 1 + src/search.cpp | 8 ++++---- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 60b7d7d3f20..9d883c92b47 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -493,14 +493,23 @@ void Position::update_slider_blockers(Color c) const { // Slider attacks use the occupied bitboard to indicate occupancy. Bitboard Position::attackers_to(Square s, Bitboard occupied) const { - return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) - | (attacks_bb(s) & pieces(KNIGHT)) - | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + return (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (attacks_bb(s) & pieces(KING)); + | (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) + | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING)); } +bool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const { + + return ((attacks_bb(s) & pieces(c, ROOK, QUEEN)) + && (attacks_bb(s, occupied) & pieces(c, ROOK, QUEEN))) + || ((attacks_bb(s) & pieces(c, BISHOP, QUEEN)) + && (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN))) + || (((pawn_attacks_bb(~c, s) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) + | (attacks_bb(s) & pieces(KING))) + & pieces(c)); +} // Tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { @@ -542,7 +551,7 @@ bool Position::legal(Move m) const { Direction step = to > from ? WEST : EAST; for (Square s = to; s != from; s += step) - if (attackers_to(s) & pieces(~us)) + if (attackers_to_exist(s, pieces(), ~us)) return false; // In case of Chess960, verify if the Rook blocks some checks. @@ -553,7 +562,7 @@ bool Position::legal(Move m) const { // If the moving piece is a king, check whether the destination square is // attacked by the opponent. if (type_of(piece_on(from)) == KING) - return !(attackers_to(to, pieces() ^ from) & pieces(~us)); + return !(attackers_to_exist(to, pieces() ^ from, ~us)); // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. @@ -622,7 +631,7 @@ bool Position::pseudo_legal(const Move m) const { } // In case of king moves under check we have to remove the king so as to catch // invalid moves like b1a1 when opposite queen is on c1. - else if (attackers_to(to, pieces() ^ from) & pieces(~us)) + else if (attackers_to_exist(to, pieces() ^ from, ~us)) return false; } @@ -1308,7 +1317,7 @@ bool Position::pos_is_ok() const { return true; if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1 - || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + || attackers_to_exist(square(~sideToMove), pieces(), sideToMove)) assert(0 && "pos_is_ok: Kings"); if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8) diff --git a/src/position.h b/src/position.h index 7dc83f251be..7fdaf84d5fe 100644 --- a/src/position.h +++ b/src/position.h @@ -126,6 +126,7 @@ class Position { // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occupied) const; + bool attackers_to_exist(Square s, Bitboard occupied, Color c) const; void update_slider_blockers(Color c) const; template Bitboard attacks_by(Color c) const; diff --git a/src/search.cpp b/src/search.cpp index 47e7f4bc383..d35121304df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,7 +77,7 @@ constexpr int futility_move_count(bool improving, Depth depth) { return (3 + depth * depth) / (2 - improving); } -int correction_value(const Worker& w, const Position& pos, Stack* ss) { +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)]; @@ -1140,7 +1140,7 @@ Value Search::Worker::search( // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1024 + (ttData.value > alpha) * 1024 + (ttData.depth >= depth) * 1024; + r -= 1024 + ((ttData.value > alpha) + (ttData.depth >= depth)) * 1024; // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) @@ -1423,8 +1423,8 @@ Value Search::Worker::search( && ((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; - static const int nonPawnWeight = 154; + const auto m = (ss - 1)->currentMove; + constexpr int nonPawnWeight = 154; auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); From 8b32e4825fb86b4409a266e3612d86790da9be36 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 12 Jan 2025 02:38:58 +0300 Subject: [PATCH 0839/1309] Make IIR less aggressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch is an elo gaining simplification which gains elo at longer time controls. Patch disallows IIR for cutNodes with existing tt moves as well as makes IIR for pv nodes less aggressive, basiclally confirming suspected scaling patterns for this heuristic. Result of 50k games STC run: https://tests.stockfishchess.org/tests/view/678304676ddf09c0b4b6f9f9 Elo: -2.93 ± 1.6 (95%) LOS: 0.0% Total: 50000 W: 12718 L: 13140 D: 24142 Ptnml(0-2): 189, 6087, 12835, 5735, 154 nElo: -5.71 ± 3.0 (95%) PairsRatio: 0.94 Passed VVLTC SPRT with STC bounds: https://tests.stockfishchess.org/tests/view/6782eb1a6ddf09c0b4b6e6b0 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 150292 W: 38868 L: 38458 D: 72966 Ptnml(0-2): 19, 13890, 46907, 14322, 8 Passed VVLTC SPRT with LTC bounds: https://tests.stockfishchess.org/tests/view/6782d8d96ddf09c0b4b6df18 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 153388 W: 39791 L: 39285 D: 74312 Ptnml(0-2): 13, 13924, 48311, 14436, 10 closes https://github.com/official-stockfish/Stockfish/pull/5763 Bench: 1507606 --- src/search.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d35121304df..1e2497f709e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -833,19 +833,17 @@ Value Search::Worker::search( } // Step 10. Internal iterative reductions (~9 Elo) - // For PV nodes without a ttMove, we decrease depth. - if (PvNode && !ttData.move) - depth -= 3; + // 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. + if ((PvNode || (cutNode && depth >= 7)) && !ttData.move) + depth -= 2; // Use qsearch if depth <= 0 if (depth <= 0) return qsearch(pos, ss, alpha, beta); - // For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove, - // or by 1 if there is a ttMove with an upper bound. - if (cutNode && depth >= 7 && (!ttData.move || ttData.bound == BOUND_UPPER)) - depth -= 1 + !ttData.move; - // Step 11. ProbCut (~10 Elo) // 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 93edf7a74cdb6871e43f66716a8a07912eedc14f Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Sun, 12 Jan 2025 10:54:59 +0800 Subject: [PATCH 0840/1309] VVLTC Search Tune Values were tuned with 118k VVLTC games. Tested against #5764. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/678331226ddf09c0b4b6fd78 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 43556 W: 11219 L: 10942 D: 21395 Ptnml(0-2): 2, 3975, 13549, 4248, 4 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/67834aa06ddf09c0b4b6fe34 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 37150 W: 9577 L: 9285 D: 18288 Ptnml(0-2): 2, 3344, 11593, 3632, 4 closes https://github.com/official-stockfish/Stockfish/pull/5765 Bench: 1258128 --- src/search.cpp | 150 ++++++++++++++++++++++++------------------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1e2497f709e..9ee9fb791c1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -66,7 +66,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 109 - 27 * noTtCutNode; + Value futilityMult = 112 - 26 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; @@ -89,7 +89,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 (6384 * pcv + 3583 * macv + 6492 * micv + 6725 * (wnpcv + bnpcv) + 5880 * cntcv); + return (6922 * pcv + 3837 * macv + 6238 * micv + 7490 * (wnpcv + bnpcv) + 6270 * cntcv); } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -99,10 +99,10 @@ Value to_corrected_static_eval(Value v, const int cv) { } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(168 * d - 100, 1718); } +int stat_bonus(Depth d) { return std::min(154 * d - 102, 1661); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(768 * d - 257, 2351); } +int stat_malus(Depth d) { return std::min(831 * d - 269, 2666); } // 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); } @@ -274,7 +274,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(106); + lowPlyHistory.fill(97); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -310,13 +310,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13461; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 12991; 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 (~4 Elo) - optimism[us] = 150 * avg / (std::abs(avg) + 85); + optimism[us] = 141 * avg / (std::abs(avg) + 83); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -498,10 +498,10 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(61); - lowPlyHistory.fill(106); - captureHistory.fill(-598); - pawnHistory.fill(-1181); + mainHistory.fill(63); + lowPlyHistory.fill(108); + captureHistory.fill(-631); + pawnHistory.fill(-1210); pawnCorrectionHistory.fill(0); majorPieceCorrectionHistory.fill(0); minorPieceCorrectionHistory.fill(0); @@ -516,10 +516,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-427); + h.fill(-479); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(19.43 * std::log(i)); + reductions[i] = int(2143 / 100.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -636,20 +636,20 @@ 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 > 8)) + && (cutNode == (ttData.value >= beta) || depth > 9)) { // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) if (ttData.move && ttData.value >= beta) { // Bonus for a quiet ttMove that fails high (~2 Elo) if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 747 / 1024); + 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) if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_malus(depth + 1) * 1091 / 1024); + -stat_malus(depth + 1) * 1042 / 1024); } // Partial workaround for the graph history interaction problem @@ -757,11 +757,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1831, 1428) + 623; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1340 / 1024; + 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; 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 * 1159 / 1024; + << bonus * 1107 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -776,30 +776,30 @@ Value Search::Worker::search( // If eval is really low, check with qsearch if we can exceed alpha. If the // search suggests we cannot exceed alpha, return a speculative fail low. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 469 - 307 * depth * depth) + if (!PvNode && eval < alpha - 462 - 297 * depth * depth) return qsearch(pos, ss, alpha - 1, alpha); // Step 8. Futility pruning: child node (~40 Elo) // 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 / 290 + - (ss - 1)->statScore / 310 + (ss->staticEval == eval) * (40 - std::abs(correctionValue) / 131072) >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; - improving |= ss->staticEval >= beta + 100; + improving |= ss->staticEval >= beta + 97; // Step 9. Null move search with verification search (~35 Elo) if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 421 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 20 * depth + 440 && !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) / 235, 7) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 215, 7) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -847,7 +847,7 @@ Value Search::Worker::search( // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 187 - 56 * improving; + probCutBeta = beta + 174 - 56 * improving; if (depth > 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -909,7 +909,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea (~4 Elo) - probCutBeta = beta + 417; + 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)) return probCutBeta; @@ -992,15 +992,15 @@ Value Search::Worker::search( // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 287 + 253 * lmrDepth + Value futilityValue = ss->staticEval + 271 + 243 * lmrDepth + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - int seeHist = std::clamp(captHist / 33, -161 * depth, 156 * depth); - if (!pos.see_ge(move, -162 * depth - seeHist)) + int seeHist = std::clamp(captHist / 37, -152 * depth, 141 * depth); + if (!pos.see_ge(move, -156 * depth - seeHist)) continue; } else @@ -1011,15 +1011,15 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (history < -3884 * depth) + if (history < -3901 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 3609; + lmrDepth += history / 3459; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 45 ? 140 : 43) + 141 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 47 ? 137 : 47) + 142 * lmrDepth; // Futility pruning: parent node (~13 Elo) if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) @@ -1060,7 +1060,7 @@ Value Search::Worker::search( && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttData.value - (52 + 74 * (ss->ttPv && !PvNode)) * depth / 64; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1070,13 +1070,13 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 249 * PvNode - 194 * !ttCapture; - int tripleMargin = 94 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv; + int doubleMargin = 259 * PvNode - 194 * !ttCapture; + int tripleMargin = 90 + 266 * PvNode - 272 * !ttCapture + 107 * ss->ttPv; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += ((!PvNode) && (depth < 14)); + depth += ((!PvNode) && (depth < 15)); } // Multi-cut pruning @@ -1109,7 +1109,7 @@ Value Search::Worker::search( else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 4321) + > 4126) extension = 1; } @@ -1138,46 +1138,46 @@ Value Search::Worker::search( // Decrease reduction if position is or has been on the PV (~7 Elo) if (ss->ttPv) - r -= 1024 + ((ttData.value > alpha) + (ttData.depth >= depth)) * 1024; + r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) - r -= 1024; + r -= 1018; // These reduction adjustments have no proven non-linear scaling - r += 330; + r += 307; - r -= std::abs(correctionValue) / 32768; + r -= std::abs(correctionValue) / 34112; // Increase reduction for cut nodes (~4 Elo) if (cutNode) - r += 2518 - (ttData.depth >= depth && ss->ttPv) * 991; + 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) if (ttCapture && !capture) - r += 1043 + (depth < 8) * 999; + r += 1087 + (depth < 8) * 990; // Increase reduction if next ply has a lot of fail high (~5 Elo) if ((ss + 1)->cutoffCnt > 3) - r += 938 + allNode * 960; + r += 940 + allNode * 887; // For first picked move (ttMove) reduce reduction (~3 Elo) else if (move == ttData.move) - r -= 1879; + r -= 1960; if (capture) ss->statScore = 7 * int(PieceValue[pos.captured_piece()]) + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 5000; + - 4666; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3996; + + (*contHist[1])[movedPiece][move.to_sq()] - 3874; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore * 1287 / 16384; + r -= ss->statScore * 1451 / 16384; // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1) @@ -1197,7 +1197,7 @@ Value Search::Worker::search( { // 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 + 42 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + 10; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1216,11 +1216,11 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present (~6 Elo) if (!ttData.move) - r += 2037; + r += 2111; // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) value = - -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 2983), !cutNode); + -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3444), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1369,25 +1369,25 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (117 * (depth > 5) + 39 * !allNode + 168 * ((ss - 1)->moveCount > 8) - + 115 * (!ss->inCheck && bestValue <= ss->staticEval - 108) - + 119 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 83)); + 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)); // Proportional to "how much damage we have to undo" - bonusScale += std::min(-(ss - 1)->statScore / 113, 300); + bonusScale += std::min(-(ss - 1)->statScore / 106, 318); bonusScale = std::max(bonusScale, 0); const int scaledBonus = stat_bonus(depth) * bonusScale / 32; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 416 / 1024); + scaledBonus * 436 / 1024); - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 212 / 1024; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 207 / 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] - << scaledBonus * 1073 / 1024; + << scaledBonus * 1195 / 1024; } else if (priorCapture && prevSq != SQ_NONE) @@ -1422,14 +1422,14 @@ Value Search::Worker::search( || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { const auto m = (ss - 1)->currentMove; - constexpr int nonPawnWeight = 154; + 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 * 107 / 128; - thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 162 / 128; - thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 148 / 128; + << 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)] << bonus * nonPawnWeight / 128; thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] @@ -1561,7 +1561,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 306; + futilityBase = ss->staticEval + 301; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1624,11 +1624,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()] - <= 5095) + <= 5228) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -83)) + if (!pos.see_ge(move, -80)) continue; } @@ -1696,7 +1696,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 * 814 / rootDelta + !i * reductionScale / 3 + 1304; + return reductionScale - delta * 768 / rootDelta + !i * reductionScale * 108 / 300 + 1168; } // elapsed() returns the time elapsed since the search started. If the @@ -1795,30 +1795,30 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1131 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1216 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1028 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1062 / 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 * 1291 / 1024; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1272 / 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 * 919 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 966 / 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 * 1090 / 1024; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1205 / 1024; } } @@ -1827,7 +1827,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, 1024}, {2, 571}, {3, 339}, {4, 500}, {6, 592}}}; + {{1, 1025}, {2, 621}, {3, 325}, {4, 512}, {6, 534}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1848,12 +1848,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 * 874 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 879 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 853 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 888 / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 628 / 1024; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 634 / 1024; } } From e2612f9a294a2d9ee2f961ecd52a6c8b9043e989 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Sun, 12 Jan 2025 13:00:45 +0000 Subject: [PATCH 0841/1309] Introduce Correction History Quad Extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also modifies the double and triple extension margins with the correction history adjustment. STC Elo Estimate: Elo: -4.40 ± 1.4 (95%) LOS: 0.0% Total: 60000 W: 15230 L: 15990 D: 28780 Ptnml(0-2): 264, 7495, 15168, 6883, 190 nElo: -8.48 ± 2.8 (95%) PairsRatio: 0.91 https://tests.stockfishchess.org/tests/view/6783a3786ddf09c0b4b703a1 Passed 1st VVLTC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 35736 W: 9354 L: 9088 D: 17294 Ptnml(0-2): 4, 3191, 11212, 3457, 4 https://tests.stockfishchess.org/tests/view/6783a3336ddf09c0b4b7039b Passed 2nd VVLTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 36394 W: 9515 L: 9225 D: 17654 Ptnml(0-2): 1, 3271, 11364, 3559, 2 https://tests.stockfishchess.org/tests/view/678395e26ddf09c0b4b70345 closes https://github.com/official-stockfish/Stockfish/pull/5767 Bench: 1567166 --- src/search.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9ee9fb791c1..f20e4f023e2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1070,11 +1070,16 @@ Value Search::Worker::search( if (value < singularBeta) { - int doubleMargin = 259 * PvNode - 194 * !ttCapture; - int tripleMargin = 90 + 266 * PvNode - 272 * !ttCapture + 107 * ss->ttPv; + int corrValAdj = std::abs(correctionValue) / 262144; + 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 - tripleMargin) + + (value < singularBeta - quadMargin); depth += ((!PvNode) && (depth < 15)); } From c085670b8474dd2137446ff278f6b73f4374cc68 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 12 Jan 2025 20:20:56 +0300 Subject: [PATCH 0842/1309] Increase the depth margin Tested at VVLTC against the passed patches. Test 1 against PR5764 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 167260 W: 43053 L: 42521 D: 81686 Ptnml(0-2): 7, 15272, 52542, 15800, 9 https://tests.stockfishchess.org/tests/view/6782ef196ddf09c0b4b6e780 Test 2 against PR5765 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 300012 W: 77364 L: 76771 D: 145877 Ptnml(0-2): 22, 27555, 94256, 28154, 19 https://tests.stockfishchess.org/tests/view/678366446ddf09c0b4b7028c closes https://github.com/official-stockfish/Stockfish/pull/5768 Bench: 1379150 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f20e4f023e2..c27d93d2565 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1056,7 +1056,7 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv + && depth >= 5 - (thisThread->completedDepth > 33) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { From aaafaaecf2aa15806b1c1bd10f6a74379ba094b6 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Sun, 12 Jan 2025 23:22:01 +0100 Subject: [PATCH 0843/1309] prefetch in do_move() this allows removing Position::key_after() STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 24960 W: 6556 L: 6336 D: 12068 Ptnml(0-2): 59, 2554, 7056, 2730, 81 LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 115080 W: 29319 L: 29204 D: 56557 Ptnml(0-2): 51, 10736, 35864, 10825, 64 STC with 2MB hash LLR: 3.04 (-2.94,2.94) <-1.75,0.25> Total: 182176 W: 46998 L: 46932 D: 88246 Ptnml(0-2): 526, 19711, 50544, 19785, 522 LTC with 8MB hash LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 441180 W: 111557 L: 111746 D: 217877 Ptnml(0-2): 229, 39698, 140929, 39501, 233 closes https://github.com/official-stockfish/Stockfish/pull/5770 bench: 1379150 --- src/position.cpp | 32 +++++++++++--------------------- src/position.h | 9 +++++---- src/search.cpp | 41 ++++++++++++++++++----------------------- 3 files changed, 34 insertions(+), 48 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 9d883c92b47..49f520e0118 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -689,7 +689,12 @@ bool Position::gives_check(Move m) const { // Makes a move, and saves all information necessary // to a StateInfo object. The move is assumed to be legal. Pseudo-legal // moves should be filtered out before this function is called. -void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { +// If a pointer to the TT table is passed, the entry for the new position +// will be prefetched +void Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -887,11 +892,13 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - // Set capture piece - st->capturedPiece = captured; - // Update the key with the final value st->key = k; + if (tt) + prefetch(tt->first_entry(key())); + + // Set capture piece + st->capturedPiece = captured; // Calculate checkers bitboard (if move gives check) st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; @@ -1069,23 +1076,6 @@ void Position::undo_null_move() { } -// Computes the new hash key after the given move. Needed -// for speculative prefetch. It doesn't recognize special moves like castling, -// en passant and promotions. -Key Position::key_after(Move m) const { - - Square from = m.from_sq(); - Square to = m.to_sq(); - Piece pc = piece_on(from); - Piece captured = piece_on(to); - Key k = st->key ^ Zobrist::side; - - k ^= Zobrist::psq[captured][to] ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; - - return (captured || type_of(pc) == PAWN) ? k : adjust_key50(k); -} - - // Tests if the SEE (Static Exchange Evaluation) // value of move is greater or equal to the given threshold. We'll use an // algorithm similar to alpha-beta pruning with a null window. diff --git a/src/position.h b/src/position.h index 7fdaf84d5fe..0d49a60a9fb 100644 --- a/src/position.h +++ b/src/position.h @@ -141,8 +141,8 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt); - void do_move(Move m, StateInfo& newSt, bool givesCheck); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + void do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); void undo_move(Move m); void do_null_move(StateInfo& newSt, const TranspositionTable& tt); void undo_null_move(); @@ -152,7 +152,6 @@ class Position { // Accessing hash keys Key key() const; - Key key_after(Move m) const; Key material_key() const; Key pawn_key() const; Key major_piece_key() const; @@ -369,7 +368,9 @@ inline void Position::move_piece(Square from, Square to) { board[to] = pc; } -inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); } +inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { + do_move(m, newSt, gives_check(m), tt); +} inline StateInfo* Position::state() const { return st; } diff --git a/src/search.cpp b/src/search.cpp index c27d93d2565..7f064bfb331 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -872,17 +872,16 @@ Value Search::Worker::search( assert(pos.capture_stage(move)); - // Prefetch the TT entry for the resulting position - prefetch(tt.first_entry(pos.key_after(move))); + movedPiece = pos.moved_piece(move); + + pos.do_move(move, st, &tt); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; ss->continuationHistory = - &this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()]; + &this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = - &this->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()]; - - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - pos.do_move(move, st); + &this->continuationCorrectionHistory[movedPiece][move.to_sq()]; // Perform a preliminary qsearch to verify that the move holds value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); @@ -1118,12 +1117,13 @@ Value Search::Worker::search( extension = 1; } + // Step 16. Make the move + pos.do_move(move, st, givesCheck, &tt); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); + // Add extension to new depth newDepth += extension; - // Speculative prefetch as early as possible - prefetch(tt.first_entry(pos.key_after(move))); - // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = @@ -1132,10 +1132,6 @@ Value Search::Worker::search( &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; - // Step 16. Make the move - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - pos.do_move(move, st, givesCheck); - // 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 @@ -1637,20 +1633,19 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; } - // Speculative prefetch as early as possible - prefetch(tt.first_entry(pos.key_after(move))); + // Step 7. Make and search the move + Piece movedPiece = pos.moved_piece(move); + + pos.do_move(move, st, givesCheck, &tt); + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Update the current move ss->currentMove = move; ss->continuationHistory = - &thisThread - ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()]; + &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = - &thisThread->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()]; + &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; - // Step 7. Make and search the move - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); - pos.do_move(move, st, givesCheck); value = -qsearch(pos, ss + 1, -beta, -alpha); pos.undo_move(move); @@ -2148,7 +2143,7 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po if (pv[0] == Move::none()) return false; - pos.do_move(pv[0], st); + pos.do_move(pv[0], st, &tt); auto [ttHit, ttData, ttWriter] = tt.probe(pos.key()); if (ttHit) From 3104cd72d531dd4908bbaa01d3206a62112c3c74 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 12 Jan 2025 16:31:59 -0800 Subject: [PATCH 0844/1309] Fix initialization of TTData. https://tests.stockfishchess.org/tests/view/6757757686d5ee47d9541de9 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 151200 W: 39396 L: 39306 D: 72498 Ptnml(0-2): 445, 16404, 41781, 16556, 414 Discussed in more detail here #5766 closes https://github.com/official-stockfish/Stockfish/pull/5773 No functional change --- src/tt.cpp | 4 +++- src/tt.h | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/tt.cpp b/src/tt.cpp index 50f5ca45af8..5d8457611dd 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -238,7 +238,9 @@ std::tuple TranspositionTable::probe(const Key key) cons > tte[i].depth8 - tte[i].relative_age(generation8) * 2) replace = &tte[i]; - return {false, TTData(), TTWriter(replace)}; + return {false, + TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false}, + TTWriter(replace)}; } diff --git a/src/tt.h b/src/tt.h index f0936fd282c..065380ca8ba 100644 --- a/src/tt.h +++ b/src/tt.h @@ -51,6 +51,15 @@ struct TTData { Depth depth; Bound bound; bool is_pv; + + TTData() = delete; + TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) : + move(m), + value(v), + eval(ev), + depth(d), + bound(b), + is_pv(pv) {}; }; From 5868b4cb584f7fcf55e4f901fc059af86c53d89e Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 13 Jan 2025 18:54:40 -0800 Subject: [PATCH 0845/1309] remove eval== staticeval check in fut pruning Simplify corrplexity in futility margin Don't check that staticEval == eval when applying the corrplexity-based adjustment in futility pruning. Passed Simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 121760 W: 31640 L: 31512 D: 58608 Ptnml(0-2): 349, 14400, 31289, 14458, 384 https://tests.stockfishchess.org/tests/view/6780c4109168c8bf30927777 Passed Simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134772 W: 34245 L: 34140 D: 66387 Ptnml(0-2): 94, 14869, 37350, 14984, 89 https://tests.stockfishchess.org/tests/view/6782d6ea6ddf09c0b4b6dd36 closes https://github.com/official-stockfish/Stockfish/pull/5776 Bench: 1487627 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7f064bfb331..3776e84b0ae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -783,8 +783,7 @@ Value Search::Worker::search( // 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 - + (ss->staticEval == eval) * (40 - std::abs(correctionValue) / 131072) + - (ss - 1)->statScore / 310 + 40 - std::abs(correctionValue) / 131072 >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; From 675319b45d8b420f33d23587f5c23d9bd01096e8 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 14 Jan 2025 08:22:48 +0100 Subject: [PATCH 0846/1309] Fix Path to AUTHORS in CONTRIBUTING closes https://github.com/official-stockfish/Stockfish/pull/5777 No functional change --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index caffc916e60..0b6fbce0d07 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ further discussion._ - Provide a clear and concise description of the changes in the pull request description. -_First time contributors should add their name to [AUTHORS](../AUTHORS)._ +_First time contributors should add their name to [AUTHORS](./AUTHORS)._ _Stockfish's development is not focused on adding new features. Thus any pull request introducing new features will potentially be closed without further @@ -86,7 +86,6 @@ more details. Thank you for contributing to Stockfish and helping us make it even better! - [copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt [discord-link]: https://discord.gg/GWDRS3kU6R [discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new From 4c2241089d4650e55a143659fe8002a392a91320 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 14 Jan 2025 08:34:37 +0100 Subject: [PATCH 0847/1309] Remove addition of 1ms to all timestamps The +1 was a quick fix to avoid the division by zero, a more correct approach is to use 1ms as the minimum reported timestamp to avoid a division by zero. Later timestamps no longer include an additional 1ms. closes https://github.com/official-stockfish/Stockfish/pull/5778 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 3776e84b0ae..66c5ff439fb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -2117,7 +2117,7 @@ void SearchManager::pv(Search::Worker& worker, if (!isExact) info.bound = bound; - TimePoint time = tm.elapsed_time() + 1; + TimePoint time = std::max(TimePoint(1), tm.elapsed_time()); info.timeMs = time; info.nodes = nodes; info.nps = nodes * 1000 / time; From 56000827af401563737e7be5cf97061133d00f79 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 13 Jan 2025 09:07:09 +0100 Subject: [PATCH 0848/1309] Simplify common hint for parent position Removes function hint_common_access_for_perspective together with it's comments, which weren't accurate anymore since merge of #5576 https://tests.stockfishchess.org/tests/view/6784c9cd460e2910c51dde39 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 295104 W: 76702 L: 76765 D: 141637 Ptnml(0-2): 1031, 32135, 81249, 32140, 997 closes https://github.com/official-stockfish/Stockfish/pull/5780 No functional change --- src/nnue/nnue_feature_transformer.h | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 8649d9521bb..14fdecd720f 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -449,8 +449,8 @@ class FeatureTransformer { void hint_common_access(const Position& pos, AccumulatorCaches::Cache* cache) const { - hint_common_access_for_perspective(pos, cache); - hint_common_access_for_perspective(pos, cache); + update_accumulator(pos, cache); + update_accumulator(pos, cache); } private: @@ -821,31 +821,12 @@ class FeatureTransformer { entry.byTypeBB[pt] = pos.pieces(pt); } - template - void hint_common_access_for_perspective(const Position& pos, - AccumulatorCaches::Cache* cache) const { - - // Works like update_accumulator, but performs less work. - // Updates ONLY the accumulator for pos. - - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - // Fast early exit. - if ((pos.state()->*accPtr).computed[Perspective]) - return; - - StateInfo* oldest = try_find_computed_accumulator(pos); - - if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) - update_accumulator_incremental(pos, oldest); - else - update_accumulator_refresh_cache(pos, cache); - } 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); if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) From 69ec5dcbfcec498e378297d0383c79ba7804a8cf Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 16 Jan 2025 14:42:27 +0300 Subject: [PATCH 0849/1309] Remove the type of moved piece from the evasion capture movepick formula In the move generation the moves are generated in the order pawns, knight, bishops, rooks, queens and king. This follows increasing type_of(pos.moved_piece(m)) term, so in master a capturing was sorted after a capturing rook if the same piece was captured in evasion. Because we use a stable sorting method (stable means the order of elements with the same value are not changed) and generate the moves in the above order we do'nt need the removed term. Passed STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 170560 W: 44222 L: 44148 D: 82190 Ptnml(0-2): 569, 18792, 46488, 18858, 573 https://tests.stockfishchess.org/tests/view/678530ee460e2910c51de21d closes https://github.com/official-stockfish/Stockfish/pull/5784 No functional change --- src/movepick.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 8448616949d..bee5ef5b7bd 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -186,8 +186,7 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = - PieceValue[pos.piece_on(m.to_sq())] - type_of(pos.moved_piece(m)) + (1 << 28); + m.value = PieceValue[pos.piece_on(m.to_sq())] + (1 << 28); else m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] From a944f082256efcb4d7ce181a472f9d674941f949 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 16 Jan 2025 11:06:52 -0800 Subject: [PATCH 0850/1309] retroactive reduction decrease if eval improves If the previous reduction was large but the static eval improves then increase the search depth. This patch looks at the next node when calculating the reduction, something I don't think has been done before and which can probably used for further elo gaining patches. Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 55936 W: 14813 L: 14462 D: 26661 Ptnml(0-2): 220, 6565, 14094, 6822, 267 https://tests.stockfishchess.org/tests/view/67845b70460e2910c51ddcff Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 189468 W: 48411 L: 47773 D: 93284 Ptnml(0-2): 180, 20801, 52131, 21445, 177 https://tests.stockfishchess.org/tests/view/6784e2cb460e2910c51ddf86 closes https://github.com/official-stockfish/Stockfish/pull/5785 bench: 1512884 --- src/search.cpp | 21 +++++++++++++++++++-- src/search.h | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 66c5ff439fb..eea48a6f7d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -247,10 +247,14 @@ void Search::Worker::iterative_deepening() { &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; (ss - i)->staticEval = VALUE_NONE; + (ss - i)->reduction = 0; } for (int i = 0; i <= MAX_PLY + 2; ++i) - (ss + i)->ply = i; + { + (ss + i)->ply = i; + (ss + i)->reduction = 0; + } ss->pv = pv; @@ -567,6 +571,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; Piece movedPiece; ValueList capturesSearched; @@ -772,6 +778,11 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; + if (priorReduction >= 3 && ss->staticEval + (ss - 1)->staticEval < 0) + { + depth++; + } + // Step 7. Razoring (~1 Elo) // If eval is really low, check with qsearch if we can exceed alpha. If the // search suggests we cannot exceed alpha, return a speculative fail low. @@ -1187,10 +1198,16 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. + + Depth d = std::max( 1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); - value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + (ss + 1)->reduction = newDepth - d; + + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + (ss + 1)->reduction = 0; + // Do a full-depth search when reduced LMR search fails high if (value > alpha && d < newDepth) diff --git a/src/search.h b/src/search.h index dee759410ba..3983e0f33c6 100644 --- a/src/search.h +++ b/src/search.h @@ -74,6 +74,7 @@ struct Stack { bool ttPv; bool ttHit; int cutoffCnt; + int reduction; }; From 132b90df041fcb14d604bc29442f7067620c52ec Mon Sep 17 00:00:00 2001 From: Disservin Date: Fri, 17 Jan 2025 10:28:21 +0100 Subject: [PATCH 0851/1309] Update CI to Ubuntu 22.04 from 20.04 fixes #5756 closes https://github.com/official-stockfish/Stockfish/pull/5786 No functional change --- .github/ci/matrix.json | 8 ++++---- .github/workflows/clang-format.yml | 2 +- .github/workflows/tests.yml | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index c6563eadf2f..44e0596eab0 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -1,8 +1,8 @@ { "config": [ { - "name": "Ubuntu 20.04 GCC", - "os": "ubuntu-20.04", + "name": "Ubuntu 22.04 GCC", + "os": "ubuntu-22.04", "simple_name": "ubuntu", "compiler": "g++", "comp": "gcc", @@ -111,7 +111,7 @@ { "binaries": "x86-64-avxvnni", "config": { - "ubuntu-20.04": null + "ubuntu-22.04": null } }, { @@ -153,7 +153,7 @@ { "binaries": "apple-silicon", "config": { - "os": "ubuntu-20.04" + "os": "ubuntu-22.04" } } ] diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 452c2f2a30f..ab6b4350ec2 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -18,7 +18,7 @@ permissions: jobs: Clang-Format: name: Clang-Format - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b97aaa29c5d..57d0d53f0e2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,15 +13,15 @@ jobs: fail-fast: false matrix: config: - - name: Ubuntu 20.04 GCC - os: ubuntu-20.04 + - name: Ubuntu 22.04 GCC + os: ubuntu-22.04 compiler: g++ comp: gcc run_32bit_tests: true run_64bit_tests: true shell: bash - - name: Ubuntu 20.04 Clang - os: ubuntu-20.04 + - name: Ubuntu 22.04 Clang + os: ubuntu-22.04 compiler: clang++ comp: clang run_32bit_tests: true From ccbd060b01b7aea5373729c06882e2c4d6d53291 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 15 Jan 2025 14:44:01 -0800 Subject: [PATCH 0852/1309] simplify razoring Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 93056 W: 24215 L: 24054 D: 44787 Ptnml(0-2): 364, 11085, 23470, 11244, 365 https://tests.stockfishchess.org/tests/view/67883a5d3b8f206a2696b804 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 66564 W: 16971 L: 16794 D: 32799 Ptnml(0-2): 56, 7403, 18192, 7570, 61 https://tests.stockfishchess.org/tests/view/6789ffa78082388fa0cbfe95 closes https://github.com/official-stockfish/Stockfish/pull/5788 bench 1500649 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index eea48a6f7d9..a080daf3088 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -784,11 +784,10 @@ Value Search::Worker::search( } // Step 7. Razoring (~1 Elo) - // If eval is really low, check with qsearch if we can exceed alpha. If the - // search suggests we cannot exceed alpha, return a speculative fail low. + // 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 - 1, alpha); + return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. From 4423c8eefa9ca59053199347118574573a4bdca8 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Fri, 17 Jan 2025 21:03:11 +0100 Subject: [PATCH 0853/1309] Return stockfish-macos-m1-apple-silicon.tar https://github.com/[ppigazzini/stockfish-downloader now uses the official SF script for POSIX systems. closes https://github.com/official-stockfish/Stockfish/pull/5789 No functional change --- scripts/get_native_properties.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index ed5fc9af093..132bd6f484f 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -76,7 +76,7 @@ case $uname_s in case $uname_m in 'arm64') true_arch='apple-silicon' - file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2 + file_arch='m1-apple-silicon' ;; 'x86_64') flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') From 165ace194f589abb3e506d36b268255ada2ae3b6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 17 Jan 2025 23:53:39 +0300 Subject: [PATCH 0854/1309] Remove the cap from maxscale for x moves in y seconds TC Passed STC 40/10: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 48800 W: 13044 L: 12835 D: 22921 Ptnml(0-2): 229, 5457, 12863, 5578, 273 https://tests.stockfishchess.org/tests/view/67862dae460e2910c51de7c9 Passed LTC 40/40: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 141296 W: 36110 L: 36014 D: 69172 Ptnml(0-2): 222, 14350, 41440, 14382, 254 https://tests.stockfishchess.org/tests/view/678799903b8f206a2696b6f8 Passed STC 80/8: LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 155120 W: 41442 L: 41346 D: 72332 Ptnml(0-2): 953, 17232, 41102, 17312, 961 https://tests.stockfishchess.org/tests/view/678aca4dc00c743bc9e9fc47 Passed LTC 80/60: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 93950 W: 24042 L: 23904 D: 46004 Ptnml(0-2): 80, 9020, 28627, 9178, 70 https://tests.stockfishchess.org/tests/view/678af705c00c743bc9e9fe94 closes https://github.com/official-stockfish/Stockfish/pull/5790 No functional change --- src/timeman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index d0b0d0a9492..2aaf96680d4 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -125,7 +125,7 @@ void TimeManagement::init(Search::LimitsType& limits, else { optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft); - maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + maxScale = 1.3 + 0.11 * mtg; } // Limit the maximum possible time for this move From b392ac76db4d2334746a3621dd0c851679a08ca8 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:45:10 +0100 Subject: [PATCH 0855/1309] Increase history bonus of TT moves Passed STC: https://tests.stockfishchess.org/tests/view/678807653b8f206a2696b78b LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 42208 W: 11113 L: 10783 D: 20312 Ptnml(0-2): 148, 4919, 10651, 5227, 159 Passed LTC: https://tests.stockfishchess.org/tests/view/6788a8463b8f206a2696b956 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 122886 W: 31454 L: 30952 D: 60480 Ptnml(0-2): 105, 13567, 33619, 14025, 127 closes https://github.com/official-stockfish/Stockfish/pull/5791 Bench: 1760081 --- src/search.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a080daf3088..5b44154626b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -119,7 +119,8 @@ void update_all_stats(const Position& pos, Square prevSq, ValueList& quietsSearched, ValueList& capturesSearched, - Depth depth); + Depth depth, + bool isTTMove); } // namespace @@ -1380,7 +1381,8 @@ Value Search::Worker::search( // If there is a move that produces search value greater than alpha, // we update the stats of searched moves. else if (bestMove) - update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth); + update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, + bestMove == ttData.move); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1799,13 +1801,14 @@ void update_all_stats(const Position& pos, Square prevSq, ValueList& quietsSearched, ValueList& capturesSearched, - Depth depth) { + Depth depth, + bool isTTMove) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = stat_bonus(depth); + int bonus = stat_bonus(depth) + 300 * isTTMove; int malus = stat_malus(depth); if (!pos.capture_stage(bestMove)) From 7701d0b3c4600423bfaf42f582f32e478fa88532 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 18 Jan 2025 03:23:50 +0300 Subject: [PATCH 0856/1309] Introduce one more continuation history This one is counter counter counter history - with really low update value and divided by 3 in movepicker unlike the other ones. Passed STC: https://tests.stockfishchess.org/tests/view/67861495460e2910c51de720 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 44352 W: 11699 L: 11370 D: 21283 Ptnml(0-2): 156, 5098, 11361, 5383, 178 Passed LTC: https://tests.stockfishchess.org/tests/view/6786e89e3b8f206a2696b646 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 432660 W: 110355 L: 109207 D: 213098 Ptnml(0-2): 381, 48214, 118039, 49268, 428 closes https://github.com/official-stockfish/Stockfish/pull/5792 Bench: 1491837 --- src/movepick.cpp | 1 + src/search.cpp | 13 +++++-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index bee5ef5b7bd..c762e7e453c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -162,6 +162,7 @@ void MovePicker::score() { m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to]; m.value += (*continuationHistory[3])[pc][to]; + m.value += (*continuationHistory[4])[pc][to] / 3; m.value += (*continuationHistory[5])[pc][to]; // bonus for checks diff --git a/src/search.cpp b/src/search.cpp index 5b44154626b..983f387a857 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -923,12 +923,9 @@ Value Search::Worker::search( && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, - (ss - 2)->continuationHistory, - (ss - 3)->continuationHistory, - (ss - 4)->continuationHistory, - nullptr, - (ss - 6)->continuationHistory}; + const PieceToHistory* contHist[] = { + (ss - 1)->continuationHistory, (ss - 2)->continuationHistory, (ss - 3)->continuationHistory, + (ss - 4)->continuationHistory, (ss - 5)->continuationHistory, (ss - 6)->continuationHistory}; MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory, @@ -1844,8 +1841,8 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - static constexpr std::array conthist_bonuses = { - {{1, 1025}, {2, 621}, {3, 325}, {4, 512}, {6, 534}}}; + static constexpr std::array conthist_bonuses = { + {{1, 1025}, {2, 621}, {3, 325}, {4, 512}, {5, 122}, {6, 534}}}; for (const auto [i, weight] : conthist_bonuses) { From 329c267e253e6119a43066fa3481e9c509e8c34a Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 17 Jan 2025 21:18:48 -0800 Subject: [PATCH 0857/1309] Optimize find_nnz() by reducing the size of lookup_indices https://tests.stockfishchess.org/tests/view/67896b688082388fa0cbfdee LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 452800 W: 118213 L: 117300 D: 217287 Ptnml(0-2): 1638, 50255, 121864, 50842, 1801 It's faster to shrink lookup_indices[] to 8 bit and zero extend to 16 bit using _mm_cvtepu8_epi16() than to read the larger 16 bit version. I suspect that having the constants available at compile time isn't too valuable and can be simplified back to generating at initialization time since this version also almost passed. https://tests.stockfishchess.org/tests/view/67863057460e2910c51de7e0 I will try that as a follow up. closes https://github.com/official-stockfish/Stockfish/pull/5793 No functional change --- .../layers/affine_transform_sparse_input.h | 110 ++++++++++++++++-- 1 file changed, 98 insertions(+), 12 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index cbeb507f083..248e19dd09a 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -38,17 +38,99 @@ namespace Stockfish::Eval::NNUE::Layers { #if (USE_SSSE3 | (USE_NEON >= 8)) -alignas(CacheLineSize) static inline const - std::array, 256> lookup_indices = []() { - std::array, 256> v{}; - for (unsigned i = 0; i < 256; ++i) - { - std::uint64_t j = i, k = 0; - while (j) - v[i][k++] = pop_lsb(j); - } - return v; - }(); + + #if (USE_SSE41) +alignas(CacheLineSize) static constexpr std::uint8_t + #else +alignas(CacheLineSize) static constexpr std::uint16_t + #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}}; // Find indices of nonzero numbers in an int32_t array template @@ -74,7 +156,11 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou using vec128_t = __m128i; #define vec128_zero _mm_setzero_si128() #define vec128_set_16(a) _mm_set1_epi16(a) - #define vec128_load(a) _mm_load_si128(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) #elif defined(USE_NEON) From c94bcf62e4dac6b92455f2701fda794883044b8b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 18 Jan 2025 13:38:45 +0300 Subject: [PATCH 0858/1309] Moving up the if position is or has been on the PV reduction Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 29664 W: 7880 L: 7570 D: 14214 Ptnml(0-2): 93, 3487, 7390, 3741, 121 https://tests.stockfishchess.org/tests/view/678ac957c00c743bc9e9fc3f Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 81354 W: 20903 L: 20487 D: 39964 Ptnml(0-2): 66, 9003, 22123, 9419, 66 https://tests.stockfishchess.org/tests/view/678ad359c00c743bc9e9fcfa closes https://github.com/official-stockfish/Stockfish/pull/5794 Bench: 1414638 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 983f387a857..249ac56bc51 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -978,6 +978,10 @@ Value Search::Worker::search( Depth r = reduction(improving, depth, moveCount, delta); + // Decrease reduction if position is or has been on the PV (~7 Elo) + if (ss->ttPv) + r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + // Step 14. Pruning at shallow depth (~120 Elo). // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) @@ -1144,10 +1148,6 @@ Value Search::Worker::search( // so changing them or adding conditions that are similar requires // tests at these types of time controls. - // Decrease reduction if position is or has been on the PV (~7 Elo) - if (ss->ttPv) - r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; - // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) if (PvNode) r -= 1018; From 738ac2a10025ca58198e3d2d7f0bc70d83c2cb7f Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 13 Jan 2025 15:22:00 -0800 Subject: [PATCH 0859/1309] tuned TM values Tuned 70k games at 240+2.4 th 2: https://tests.stockfishchess.org/tests/view/6783b1b16ddf09c0b4b703f5 Failed STC: LLR: -2.93 (-2.94,2.94) <0.00,2.00> Total: 491872 W: 128260 L: 127804 D: 235808 Ptnml(0-2): 1579, 55449, 131572, 55609, 1727 https://tests.stockfishchess.org/tests/view/6785a045460e2910c51de4b8 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 154824 W: 39315 L: 38874 D: 76635 Ptnml(0-2): 110, 15809, 45147, 16222, 124 https://tests.stockfishchess.org/tests/view/678ac722c00c743bc9e9fc35 Passed VLTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 77404 W: 19825 L: 19452 D: 38127 Ptnml(0-2): 18, 7262, 23765, 7643, 14 https://tests.stockfishchess.org/tests/view/678b2a98c00c743bc9ea048c closes https://github.com/official-stockfish/Stockfish/pull/5796 No functional change --- src/search.cpp | 22 ++++++++++++---------- src/timeman.cpp | 31 +++++++++++++++++-------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 249ac56bc51..1d2604c2648 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -446,17 +446,19 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - int nodesEffort = rootMoves[0].effort * 100 / std::max(size_t(1), size_t(nodes)); + int nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); - double fallingEval = (11 + 2 * (mainThread->bestPreviousAverageScore - bestValue) - + (mainThread->iterValue[iterIdx] - bestValue)) - / 100.0; - fallingEval = std::clamp(fallingEval, 0.580, 1.667); + double fallingEval = + (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.968 * (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; + fallingEval = std::clamp(fallingEval, 0.5786, 1.6752); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.495 : 0.687; - double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction); - double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size(); + timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.4857 : 0.7046; + double reduction = + (1.4540 + mainThread->previousTimeReduction) / (2.1593 * timeReduction); + double bestMoveInstability = 0.9929 + 1.8519 * totBestMoveChanges / threads.size(); double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; @@ -467,7 +469,7 @@ void Search::Worker::iterative_deepening() { auto elapsedTime = elapsed(); - if (completedDepth >= 10 && nodesEffort >= 97 && elapsedTime > totalTime * 0.739 + if (completedDepth >= 10 && nodesEffort >= 97056 && elapsedTime > totalTime * 0.6540 && !mainThread->ponder) threads.stop = true; @@ -482,7 +484,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.506; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.5138; } mainThread->iterValue[iterIdx] = bestValue; diff --git a/src/timeman.cpp b/src/timeman.cpp index 2aaf96680d4..d073a84a963 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -88,17 +88,19 @@ void TimeManagement::init(Search::LimitsType& limits, const TimePoint scaledInc = limits.inc[us] / scaleFactor; // Maximum move horizon of 50 moves - int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; // If less than one second, gradually reduce mtg - if (scaledTime < 1000 && double(mtg) / scaledInc > 0.05) + if (scaledTime < 1000 && double(centiMTG) / scaledInc > 5.051) { - mtg = scaledTime * 0.05; + centiMTG = scaledTime * 5.051; } // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1) - - moveOverhead * (2 + mtg)); + TimePoint timeLeft = + std::max(TimePoint(1), + limits.time[us] + + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); // x basetime (+ z increment) // If there is a healthy increment, timeLeft can exceed the actual available @@ -107,31 +109,32 @@ void TimeManagement::init(Search::LimitsType& limits, { // Extra time according to timeLeft if (originalTimeAdjust < 0) - originalTimeAdjust = 0.3285 * std::log10(timeLeft) - 0.4830; + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; // Calculate time constants based on current time left. double logTimeInSec = std::log10(scaledTime / 1000.0); - double optConstant = std::min(0.00308 + 0.000319 * logTimeInSec, 0.00506); - double maxConstant = std::max(3.39 + 3.01 * logTimeInSec, 2.93); + double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); - optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant, - 0.213 * limits.time[us] / timeLeft) + optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, + 0.213035 * limits.time[us] / timeLeft) * originalTimeAdjust; - maxScale = std::min(6.64, maxConstant + ply / 12.0); + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); } // x moves in y seconds (+ z increment) else { - optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft); - maxScale = 1.3 + 0.11 * mtg; + optScale = + std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); } // Limit the maximum possible time for this move optimumTime = TimePoint(optScale * timeLeft); maximumTime = - TimePoint(std::min(0.825 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; + TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; if (options["Ponder"]) optimumTime += optimumTime / 4; From f00d91f8ac72de8d201f8b50968bb66b1235dc9a Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 12 Jan 2025 13:59:38 -0800 Subject: [PATCH 0860/1309] Refine probcut Allow probcut for depth 3 and disable deeper verification search when it would simply repeat the qsearch. Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 283232 W: 74450 L: 73760 D: 135022 Ptnml(0-2): 1052, 33780, 71349, 34296, 1139 https://tests.stockfishchess.org/tests/view/67843b58460e2910c51ddcc6 Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 339654 W: 86845 L: 85893 D: 166916 Ptnml(0-2): 298, 37734, 92802, 38704, 289 https://tests.stockfishchess.org/tests/view/678aa722c00c743bc9e9face closes https://github.com/official-stockfish/Stockfish/pull/5797 bench 1288648 --- src/search.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1d2604c2648..d5e86ca9444 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -860,7 +860,7 @@ Value Search::Worker::search( // 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; - if (depth > 3 + if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt // probCut there and in further interactions with transposition table cutoff @@ -871,6 +871,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); + Depth probCutDepth = std::max(depth - 4, 0); while ((move = mp.next_move()) != Move::none()) { @@ -899,9 +900,9 @@ Value Search::Worker::search( value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); // If the qsearch held, perform the regular search - if (value >= probCutBeta) - value = - -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4, !cutNode); + if (value >= probCutBeta && probCutDepth > 0) + value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth, + !cutNode); pos.undo_move(move); @@ -909,7 +910,7 @@ Value Search::Worker::search( { // Save ProbCut data into transposition table ttWriter.write(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, - depth - 3, move, unadjustedStaticEval, tt.generation()); + probCutDepth + 1, move, unadjustedStaticEval, tt.generation()); if (!is_decisive(value)) return value - (probCutBeta - beta); From 8e3e22b3d4f214e12aa83771be543aac1196d713 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 18 Jan 2025 21:32:18 +0100 Subject: [PATCH 0861/1309] 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 d5e86ca9444..e99d5d3fc9f 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 0862/1309] 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 14fdecd720f..7a37cda8208 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]); - - // 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]); - } + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&psqtWeights[offsetPsqtA1]); + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&psqtWeights[offsetPsqtR1]); - // 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) +#else + std::memcpy((next->*accPtr).accumulation[Perspective], + (computed->*accPtr).accumulation[Perspective], + HalfDimensions * sizeof(BiasType)); + std::memcpy((next->*accPtr).psqtAccumulation[Perspective], + (computed->*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + + // Difference calculation for the deactivated features + for (const auto index : removed) { - // 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]); - } + const IndexType offset = HalfDimensions * index; + for (IndexType i = 0; i < HalfDimensions; ++i) + (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; - // 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]); + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (next->*accPtr).psqtAccumulation[Perspective][i] -= + psqtWeights[index * PSQTBuckets + i]; } - } -#else - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] -= - psqtWeights[index * PSQTBuckets + i]; - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] += weights[offset + i]; + // 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 0863/1309] 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 248e19dd09a..be5e30b5e2f 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 0864/1309] 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 7a37cda8208..4f0ce6cf605 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 0865/1309] 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 e99d5d3fc9f..de431843706 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1155,7 +1155,7 @@ Value Search::Worker::search( // 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 0866/1309] 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 de431843706..951d197d277 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 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( { 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 3983e0f33c6..3a1b3a77d0a 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 0867/1309] 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 951d197d277..3c8f33b417f 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( + (*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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( &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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( + (*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 @@ Value Search::Worker::search( { // 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 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( } 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( // 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 d073a84a963..29ebffcaa29 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 0868/1309] 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 6f9cca0bdd4..df15113bda3 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 81c7b17fe59..8adbac68ae0 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 0869/1309] 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 f6468c561ec..1e5e51e63da 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 3c8f33b417f..9f29f82853e 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 0870/1309] 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 49f520e0118..02614d13fb9 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 0d49a60a9fb..d955d9f987d 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 9f29f82853e..f236316233d 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 120e64885a6..cbf8dce5ea2 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 0871/1309] 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 f236316233d..990cbae3373 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -982,10 +982,6 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( 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 0872/1309] 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 15095cd0bbc..4543fc55ed2 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 990cbae3373..e11da912896 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 @@ Value Search::Worker::search( << 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 0873/1309] 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 e11da912896..3a161fee12c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1417,7 +1417,7 @@ Value Search::Worker::search( // 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 0874/1309] 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 3a161fee12c..ab8825f837e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1028,8 +1028,7 @@ Value Search::Worker::search( 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 0875/1309] 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 ab8825f837e..3162018868f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1328,7 +1328,7 @@ Value Search::Worker::search( 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 0876/1309] 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 4543fc55ed2..654ee19f2af 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 d955d9f987d..53269c197ee 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 3162018868f..e07c719eb34 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 @@ Value Search::Worker::search( -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 3a1b3a77d0a..326480e4dcf 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 0877/1309] 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 e07c719eb34..7317b7e4378 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 @@ Value Search::Worker::search( // 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 0878/1309] 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 7317b7e4378..5eb5049a73a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1381,23 +1381,21 @@ Value Search::Worker::search( 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 0879/1309] 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 f0bca442fdc..a50ca594be8 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 0880/1309] 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 5eb5049a73a..9a5425fc7ef 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1239,7 +1239,7 @@ Value Search::Worker::search( (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 0881/1309] 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 654ee19f2af..bec055d52ec 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 9a5425fc7ef..89233e85b17 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 @@ Value Search::Worker::search( && ((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 0882/1309] 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 e5df1a9ec26..fdaec13ad96 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 0883/1309] 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 89233e85b17..fbc97bb41a9 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 @@ Value Search::Worker::search( 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 0884/1309] 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 fbc97bb41a9..2f0b164ab03 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 0885/1309] 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 2f0b164ab03..d6e9bb75d16 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 0886/1309] 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 bec055d52ec..9ae7bdadc20 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 d6e9bb75d16..a35aad45a39 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 0887/1309] 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 a35aad45a39..9af7a115f1c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1097,12 +1097,9 @@ Value Search::Worker::search( 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 0888/1309] 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 9af7a115f1c..60f716cd36e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,6 +999,12 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( // 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 0889/1309] 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 065380ca8ba..26b63c1adb4 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 0890/1309] 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 1e5e51e63da..a345bb69f1b 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 60f716cd36e..5b26b66575b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1107,7 +1107,7 @@ Value Search::Worker::search( 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 0891/1309] 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 dc8dff8d57f..85c2be3e7e8 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 0892/1309] 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 439d4ae9def..37871aa2468 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 0893/1309] 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 5b26b66575b..1b5463117d2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1107,7 +1107,7 @@ Value Search::Worker::search( 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 0894/1309] 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 d835fc8e461..6c8799a15d1 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 aff96c8cbe9..f53a0eb52d6 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 56cf41edcc9..a76bd3ace96 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 c9f6787d3dd..3d7386c30a4 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 0895/1309] 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 1b5463117d2..d23e91ef829 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1248,8 +1248,8 @@ Value Search::Worker::search( 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 0896/1309] 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 d23e91ef829..a331508106d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1399,10 +1399,10 @@ Value Search::Worker::search( // 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 0897/1309] 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 a331508106d..f98098701b8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1348,7 +1348,7 @@ Value Search::Worker::search( 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 0898/1309] 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 f98098701b8..cf7c7715e72 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,6 +999,8 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( // 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 0899/1309] 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 cf7c7715e72..c9832c8787b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1350,7 +1350,8 @@ Value Search::Worker::search( 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 0900/1309] 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 c9832c8787b..b82217157a5 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 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( + 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( { // 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( { // 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( // 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 0901/1309] 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 b82217157a5..99aff5e9658 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1241,7 +1241,7 @@ Value Search::Worker::search( { // 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 0902/1309] 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 99aff5e9658..f5d2f5cb7fa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1165,7 +1165,7 @@ Value Search::Worker::search( // 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 0903/1309] 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 bc1f6b15bf9..3a4b537e9ab 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -97,14 +97,17 @@ def download_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 @@ def start(self): 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 0904/1309] 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 f5d2f5cb7fa..c68b3b7acd4 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 0905/1309] 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 c68b3b7acd4..36823a08e2a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1399,7 +1399,8 @@ Value Search::Worker::search( 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 0906/1309] 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 37871aa2468..37e9a2eb5b8 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 0907/1309] 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 36823a08e2a..be511791675 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 0908/1309] 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 be511791675..4dfdbaaf0b0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1158,10 +1158,8 @@ Value Search::Worker::search( // 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 @@ Value Search::Worker::search( // 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 0909/1309] 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 4dfdbaaf0b0..4bc6109ede0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1169,7 +1169,7 @@ Value Search::Worker::search( // 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 0910/1309] 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 4bc6109ede0..02323d1e0e4 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 0911/1309] 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 02323d1e0e4..cd90bbfd237 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 0912/1309] 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 cd90bbfd237..67e28d3e598 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1055,7 +1055,10 @@ Value Search::Worker::search( 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 0913/1309] 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 | 143 ++++++++++++++++++---------- src/nnue/nnue_misc.cpp | 10 -- src/nnue/nnue_misc.h | 3 - src/position.h | 2 +- src/search.cpp | 4 - 7 files changed, 93 insertions(+), 78 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index b96625a799e..5ac8b8d9850 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 f99fa11827d..764481d9442 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 4f0ce6cf605..931d9aed5a1 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); - - 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); + StateInfo* st = pos.state(); + if ((st->*accPtr).computed[Perspective]) + return; // nothing to do + + [[maybe_unused]] // only used when !Big + int gain = FeatureSet::refresh_cost(pos); + // Look for a usable already computed accumulator of an earlier position. + // When computing the small accumulator, we keep track of the estimated gain in + // terms of features to be added/subtracted. + // When computing the big accumulator, we expect to be able to reuse any + // accumulators, so we always try to do an incremental update. + 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 1e269050391..2220684da69 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 a7647f84609..02212160a12 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 53269c197ee..eeea1b74c5a 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 67e28d3e598..f292118ed90 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 0914/1309] 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 f292118ed90..da1ea3497b6 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 0915/1309] 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 9ae7bdadc20..fd9b98b98bc 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 8adbac68ae0..d2cbb699dbb 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 0916/1309] 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 f5c640fb948..dac727e23bc 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 0917/1309] 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 4fce86e3a9b..dddb5686070 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 0918/1309] 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 da1ea3497b6..d2c77365b4c 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 @@ Value Search::Worker::search( 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 @@ Value Search::Worker::search( 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 0919/1309] 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 d2c77365b4c..7736c412198 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,6 +1053,8 @@ Value Search::Worker::search( 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) From 291a429cdd54f5298b8dc3d19cd08c3a64de4d10 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 17 Feb 2025 21:24:57 +0100 Subject: [PATCH 0920/1309] Remove duplicated info string printing Currently "info string" is being printed twice for the network arch ``` info string info string NNUE evaluation using nn-1c0000000000.nnue (133MiB, (22528, 3072, 15, 32, 1)) info string info string NNUE evaluation using nn-37f18f62d772.nnue (6MiB, (22528, 128, 15, 32, 1)) ``` closes https://github.com/official-stockfish/Stockfish/pull/5889 No functional change --- src/nnue/network.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 5ac8b8d9850..fe312fcb8fc 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -268,9 +268,8 @@ void Network::verify(std::string if (f) { size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks; - f("info string NNUE evaluation using " + evalfilePath + " (" - + std::to_string(size / (1024 * 1024)) + "MiB, (" - + std::to_string(featureTransformer->InputDimensions) + ", " + f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024)) + + "MiB, (" + std::to_string(featureTransformer->InputDimensions) + ", " + std::to_string(network[0].TransformedFeatureDimensions) + ", " + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + ", 1))"); From 57f0fe08c0a65ffc8e2e366de85985428e6e6ba5 Mon Sep 17 00:00:00 2001 From: Myself <63040919+AliceRoselia@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:45:35 +0700 Subject: [PATCH 0921/1309] Add risk tolerance calculation https://tests.stockfishchess.org/tests/view/67b1db2188b11e2400eb06ae Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 123552 W: 32388 L: 31938 D: 59226 Ptnml(0-2): 487, 14520, 31345, 14904, 520 Passed LTC: https://tests.stockfishchess.org/tests/view/67b3d53f154c4df4fc4b1f43 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 206928 W: 52916 L: 52246 D: 101766 Ptnml(0-2): 159, 22546, 57394, 23196, 169 closes https://github.com/official-stockfish/Stockfish/pull/5893 Bench: 2705449 --- src/search.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 7736c412198..5ec0def6a3a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -96,6 +96,32 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; } +int risk_tolerance(const Position& pos, Value v) { + // Returns (some constant of) second derivative of sigmoid. + static constexpr auto sigmoid_d2 = [](int x, int y) { + return -345600 * x / (x * x + 3 * y * y); + }; + + int material = pos.count() + 3 * pos.count() + 3 * pos.count() + + 5 * pos.count() + 9 * pos.count(); + + int m = std::clamp(material, 17, 78); + + // a and b are the crude approximation of the wdl model. + // The win rate is: 1/(1+exp((a-v)/b)) + // The loss rate is 1/(1+exp((v+a)/b)) + int a = ((-m * 3220 / 256 + 2361) * m / 256 - 586) * m / 256 + 421; + int b = ((m * 7761 / 256 - 2674) * m / 256 + 314) * m / 256 + 51; + + + // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) + // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) + int winning_risk = sigmoid_d2(v - a, b); + int losing_risk = -sigmoid_d2(-v - a, b); + + return (winning_risk + losing_risk) * 60 / b; +} + // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. Value to_corrected_static_eval(const Value v, const int cv) { @@ -1166,6 +1192,9 @@ Value Search::Worker::search( r -= std::abs(correctionValue) / 31568; + if (PvNode && !is_decisive(bestValue)) + r -= risk_tolerance(pos, bestValue); + // Increase reduction for cut nodes if (cutNode) r += 2608 + 1024 * !ttData.move; From c19a6ea53cd715af97717ba687c3ad4c9c2a98c8 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 23 Feb 2025 19:52:37 +0300 Subject: [PATCH 0922/1309] Make Pv search shallower in some cases Conditions are the same as they are for doShallowerSearch, just that they also apply for cases where LMR wasn't reducing anything - if result is bad enough reduce search depth of PV search by 1. Passed STC: https://tests.stockfishchess.org/tests/view/67b9d2aca49c651c2caac818 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 29216 W: 7731 L: 7424 D: 14061 Ptnml(0-2): 87, 3345, 7473, 3580, 123 Passed LTC: https://tests.stockfishchess.org/tests/view/67ba538c01f3463ae1d35e69 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 33168 W: 8529 L: 8219 D: 16420 Ptnml(0-2): 12, 3505, 9262, 3771, 34 closes https://github.com/official-stockfish/Stockfish/pull/5895 Bench: 2290732 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 5ec0def6a3a..6a2f8f2d287 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1260,6 +1260,8 @@ Value Search::Worker::search( int bonus = (value >= beta) * 2010; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } + else if (value > alpha && value < bestValue + 9) + newDepth--; } // Step 18. Full-depth search when LMR is skipped From 0f9ae0d11cd034288a49ef3892c580dfed025091 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Feb 2025 10:50:41 +0100 Subject: [PATCH 0923/1309] Check maximum time every iteration This fixes a TCEC timeloss, where slow DTZ TB7 access, in combination with syzygy PV extension, led to a timeloss. While the extension code correctly aborted after spending moveOverhead/2 time, the mainThread did not search sufficient nodes (512 in > 1s) to trigger the stop in check_time. At the same time, totalTime exceeded tm.maximum() due to the factors multiplying tm.optimum(). This corner case is fixed by checking also against the tm.maximum() at each iteration. Even though this problem can't be triggered on fishtest, the patch was verified there. Passed STC: https://tests.stockfishchess.org/tests/view/67b99e1be99f8640b318810d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 136832 W: 35625 L: 35518 D: 65689 Ptnml(0-2): 499, 14963, 37431, 14978, 545 closes https://github.com/official-stockfish/Stockfish/pull/5896 No functional change --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6a2f8f2d287..edd8d9eb59e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -520,8 +520,8 @@ void Search::Worker::iterative_deepening() { && !mainThread->ponder) threads.stop = true; - // Stop the search if we have exceeded the totalTime - if (elapsedTime > totalTime) + // Stop the search if we have exceeded the totalTime or maximum + if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum()))) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". From 93b966829bd7d2d9d9dce49f11e20125f48d0cfd Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 24 Feb 2025 14:13:24 +0300 Subject: [PATCH 0924/1309] Simplify bestvalue update formula Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 124960 W: 32598 L: 32472 D: 59890 Ptnml(0-2): 480, 14852, 31694, 14970, 484 https://tests.stockfishchess.org/tests/view/67b348bae00eea114cdba37d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 150306 W: 38220 L: 38132 D: 73954 Ptnml(0-2): 98, 16430, 42005, 16526, 94 https://tests.stockfishchess.org/tests/view/67b5e37918a66624a7a3f751 closes https://github.com/official-stockfish/Stockfish/pull/5898 Bench: 2146010 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index edd8d9eb59e..21b328fbe52 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1720,8 +1720,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return mated_in(ss->ply); // Plies to mate from the root } - if (!is_decisive(bestValue) && bestValue >= beta) - bestValue = (3 * bestValue + beta) / 4; + if (!is_decisive(bestValue) && bestValue > beta) + bestValue = (bestValue + beta) / 2; // Save gathered info in transposition table. The static evaluation // is saved as it was before adjustment by correction history. From d330b48e21f688e80b44001a912168097646671d Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Feb 2025 11:32:27 -0800 Subject: [PATCH 0925/1309] Handle updating the small accumulator the same way as the big one https://tests.stockfishchess.org/tests/view/67abfe1ca04df5eb8dbebf0b LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 153088 W: 40072 L: 39979 D: 73037 Ptnml(0-2): 619, 16728, 41764, 16807, 626 closes https://github.com/official-stockfish/Stockfish/pull/5901 No functional change --- src/nnue/features/half_ka_v2_hm.cpp | 4 ---- src/nnue/features/half_ka_v2_hm.h | 5 ----- src/nnue/nnue_feature_transformer.h | 15 ++++----------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5bb0296e297..81eddb06018 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -77,10 +77,6 @@ template void HalfKAv2_hm::append_changed_indices(Square ksq, IndexList& removed, IndexList& added); -int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; } - -int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count(); } - bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { return st->dirtyPiece.piece[0] == make_piece(perspective, KING); } diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index ca940c54ed4..0a420cd1e58 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -135,11 +135,6 @@ class HalfKAv2_hm { static void append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - // Returns the cost of updating one perspective, the most costly one. - // Assumes no refresh needed. - static int update_cost(const StateInfo* st); - static int refresh_cost(const Position& pos); - // Returns whether the change stored in this StateInfo means // that a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 931d9aed5a1..60a044158fb 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -254,7 +254,6 @@ class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - static constexpr bool Big = TransformedFeatureDimensions == TransformedFeatureDimensionsBig; private: using Tiling = SIMDTiling; @@ -836,23 +835,17 @@ class FeatureTransformer { if ((st->*accPtr).computed[Perspective]) return; // nothing to do - [[maybe_unused]] // only used when !Big - int gain = FeatureSet::refresh_cost(pos); // Look for a usable already computed accumulator of an earlier position. - // When computing the small accumulator, we keep track of the estimated gain in - // terms of features to be added/subtracted. - // When computing the big accumulator, we expect to be able to reuse any - // accumulators, so we always try to do an incremental update. + // Always try to do an incremental update as most accumulators will be reusable. do { - if (FeatureSet::requires_refresh(st, Perspective) - || (!Big && (gain -= FeatureSet::update_cost(st) < 0)) || !st->previous + if (FeatureSet::requires_refresh(st, Perspective) || !st->previous || st->previous->next != st) { // compute accumulator from scratch for this position update_accumulator_refresh_cache(pos, cache); - if (Big && st != pos.state()) - // when computing a big accumulator from scratch we can use it to + if (st != pos.state()) + // when computing an accumulator from scratch we can use it to // efficiently compute the accumulator backwards, until we get to a king // move. We expect that we will need these accumulators later anyway, so // computing them now will save some work. From e4d7136042f46d0002b91ac5a49602369dbe3d05 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Feb 2025 11:42:03 -0800 Subject: [PATCH 0926/1309] Combine last 3 add/remove operations https://tests.stockfishchess.org/tests/view/67ad587052879dfd14d7e8a5 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 45856 W: 12177 L: 11855 D: 21824 Ptnml(0-2): 176, 4845, 12588, 5119, 200 The two most common cases are when added and removed counts are equal and when they are off by 1. When they are off by 1 we currently do a pass combining 2 and then an extra pass for the last 1. This patch does a single combined pass on the final 3 instead. Tested on top of the simplification in https://github.com/official-stockfish/Stockfish/pull/5901 closes https://github.com/official-stockfish/Stockfish/pull/5902 No functional change --- src/nnue/nnue_feature_transformer.h | 61 +++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 60a044158fb..7e4c669ae3e 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -704,6 +704,8 @@ class FeatureTransformer { accumulator.computed[Perspective] = true; #ifdef VECTOR + const bool combineLast3 = std::abs((int) removed.size() - (int) added.size()) == 1 + && removed.size() + added.size() > 2; vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; @@ -717,7 +719,7 @@ class FeatureTransformer { acc[k] = entryTile[k]; std::size_t i = 0; - for (; i < std::min(removed.size(), added.size()); ++i) + for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) { IndexType indexR = removed[i]; const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; @@ -729,23 +731,56 @@ class FeatureTransformer { for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); } - for (; i < removed.size(); ++i) + if (combineLast3) { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); + IndexType indexR = removed[i]; + const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&weights[offsetA]); - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); + if (removed.size() > added.size()) + { + IndexType indexR2 = removed[i + 1]; + const IndexType offsetR2 = HalfDimensions * indexR2 + j * Tiling::TileHeight; + auto* columnR2 = reinterpret_cast(&weights[offsetR2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), + vec_add_16(columnR[k], columnR2[k])); + } + else + { + IndexType indexA2 = added[i + 1]; + const IndexType offsetA2 = HalfDimensions * indexA2 + j * Tiling::TileHeight; + auto* columnA2 = reinterpret_cast(&weights[offsetA2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), + vec_add_16(columnA[k], columnA2[k])); + } } - for (; i < added.size(); ++i) + else { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } } for (IndexType k = 0; k < Tiling::NumRegs; k++) From 09faa626210e8f72cacc35887823c4929c36a86b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 23 Feb 2025 00:40:06 -0800 Subject: [PATCH 0927/1309] Simplify NMP Conditions Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142400 W: 36883 L: 36779 D: 68738 Ptnml(0-2): 467, 16804, 36571, 16874, 484 https://tests.stockfishchess.org/tests/view/67bd1898e4a8d7152b974ef1 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 251868 W: 63905 L: 63920 D: 124043 Ptnml(0-2): 133, 27480, 70708, 27495, 118 https://tests.stockfishchess.org/tests/view/67bd1898e4a8d7152b974ef1 closes https://github.com/official-stockfish/Stockfish/pull/5906 Bench: 2188400 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 21b328fbe52..89a44b930d3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -846,8 +846,8 @@ Value Search::Worker::search( // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 455 - 60 * improving && !excludedMove - && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) + && ss->staticEval >= beta - 21 * depth + 395 && !excludedMove && pos.non_pawn_material(us) + && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); From a730b4d08b6429b5ec345f7bc607ec9df0988b71 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 26 Feb 2025 03:06:47 +0300 Subject: [PATCH 0928/1309] Remove two unnecessary divisions Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 280768 W: 72187 L: 72236 D: 136345 Ptnml(0-2): 815, 33131, 72550, 33064, 824 https://tests.stockfishchess.org/tests/view/67bcf7afe670525923b8a101 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 108684 W: 27666 L: 27536 D: 53482 Ptnml(0-2): 40, 11768, 30606, 11878, 50 https://tests.stockfishchess.org/tests/view/67be472ed8d5c2c657c52cb8 closes https://github.com/official-stockfish/Stockfish/pull/5908 Bench: 2400689 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 89a44b930d3..7e5cf5f92be 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -699,13 +699,12 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(117600 * depth - 71344, 1244992) / 1024); + std::min(115 * depth - 70, 1216)); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(779788 * (depth + 1) - 271806, 2958308) - / 1024); + -std::min(762 * (depth + 1) - 266, 2889)); } // Partial workaround for the graph history interaction problem From 6d9c6f99b9439cf083175d8c4c36ccb77fd31789 Mon Sep 17 00:00:00 2001 From: Jake Senne Date: Thu, 20 Feb 2025 22:53:16 -0600 Subject: [PATCH 0929/1309] Replace aligned() function with line_bb() and simplify king piece detection From https://discord.com/channels/435943710472011776/813919248455827515/1342267241168900228 Saves 6 instructions closes https://github.com/official-stockfish/Stockfish/pull/5909 No functional change --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37e9a2eb5b8..1e19e3c8a1c 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; + return !(line_bb(from, to) & pieces(~sideToMove, KING) || m.type_of() == CASTLING); switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From 5c617e579cb44215a8904b879b24d3e7bbd78412 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 27 Feb 2025 18:31:40 +0300 Subject: [PATCH 0930/1309] VVLTC Search Tune Passed VVLTC with STC bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 15788 W: 4106 L: 3868 D: 7814 Ptnml(0-2): 0, 1324, 5009, 1560, 1 https://tests.stockfishchess.org/tests/view/67bf2ddd6e569f6234102ade Passed VVLTC with LTC bounds: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 13622 W: 3620 L: 3368 D: 6634 Ptnml(0-2): 3, 1190, 4170, 1448, 0 https://tests.stockfishchess.org/tests/view/67c04308c8f7c4c0632d8055 closes https://github.com/official-stockfish/Stockfish/pull/5910 Bench: 1823605 --- src/search.cpp | 189 +++++++++++++++++++++++++------------------------ 1 file changed, 95 insertions(+), 94 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7e5cf5f92be..bbd43ed6989 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -71,7 +71,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 112 - 26 * noTtCutNode; + Value futilityMult = 110 - 25 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; @@ -93,25 +93,26 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; - return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; + return 7685 * pcv + 7495 * micv + 9144 * (wnpcv + bnpcv) + 6469 * cntcv; } int risk_tolerance(const Position& pos, Value v) { // Returns (some constant of) second derivative of sigmoid. static constexpr auto sigmoid_d2 = [](int x, int y) { - return -345600 * x / (x * x + 3 * y * y); + return -355752 * x / (x * x + 3 * y * y); }; - int material = pos.count() + 3 * pos.count() + 3 * pos.count() - + 5 * pos.count() + 9 * pos.count(); + int material = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() + + 337 * pos.count() + 553 * pos.count()) + / 64; int m = std::clamp(material, 17, 78); // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) // The loss rate is 1/(1+exp((v+a)/b)) - int a = ((-m * 3220 / 256 + 2361) * m / 256 - 586) * m / 256 + 421; - int b = ((m * 7761 / 256 - 2674) * m / 256 + 314) * m / 256 + 51; + int a = ((-m * 3037 / 256 + 2270) * m / 256 - 637) * m / 256 + 413; + int b = ((m * 7936 / 256 - 2255) * m / 256 + 319) * m / 256 + 83; // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) @@ -119,7 +120,7 @@ int risk_tolerance(const Position& pos, Value v) { int winning_risk = sigmoid_d2(v - a, b); int losing_risk = -sigmoid_d2(-v - a, b); - return (winning_risk + losing_risk) * 60 / b; + return (winning_risk + losing_risk) * 58 / b; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -135,11 +136,11 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - static constexpr int nonPawnWeight = 165; + static constexpr int nonPawnWeight = 162; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 109 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 141 / 128; + << bonus * 111 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] @@ -147,7 +148,7 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 138 / 128; + << bonus * 143 / 128; } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -326,7 +327,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(95); + lowPlyHistory.fill(92); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -362,13 +363,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13000; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11834; Value avg = rootMoves[pvIdx].averageScore; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 138 * avg / (std::abs(avg) + 81); + optimism[us] = 138 * avg / (std::abs(avg) + 84); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -552,27 +553,27 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(65); - lowPlyHistory.fill(107); - captureHistory.fill(-655); - pawnHistory.fill(-1215); - pawnCorrectionHistory.fill(4); + mainHistory.fill(66); + lowPlyHistory.fill(105); + captureHistory.fill(-646); + pawnHistory.fill(-1262); + pawnCorrectionHistory.fill(6); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); for (auto& to : continuationCorrectionHistory) for (auto& h : to) - h.fill(0); + h.fill(5); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-493); + h.fill(-468); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2937 / 128.0 * std::log(i)); + reductions[i] = int(2954 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -699,12 +700,12 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(115 * depth - 70, 1216)); + std::min(120 * depth - 75, 1241)); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(762 * (depth + 1) - 266, 2889)); + -std::min(809 * (depth + 1) - 249, 3052)); } // Partial workaround for the graph history interaction problem @@ -808,11 +809,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1906, 1450) + 638; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1136 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1950, 1416) + 655; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1124 / 1024; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1195 / 1024; + << bonus * 1196 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -825,33 +826,33 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 200) + if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 188) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 446 - 303 * depth * depth) + if (!PvNode && eval < alpha - 461 - 315 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 326 + 37 - std::abs(correctionValue) / 132821 + - (ss - 1)->statScore / 301 + 37 - std::abs(correctionValue) / 139878 >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 395 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 19 * depth + 418 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 237, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 232, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -884,7 +885,7 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta + 97; + improving |= ss->staticEval >= beta + 94; // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. @@ -895,7 +896,7 @@ Value Search::Worker::search( // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 187 - 55 * improving; + probCutBeta = beta + 185 - 58 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -958,7 +959,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 413; + probCutBeta = beta + 415; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; @@ -1024,7 +1025,7 @@ Value Search::Worker::search( // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 1031; + r += 979; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1046,15 +1047,15 @@ Value Search::Worker::search( // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 242 + 238 * lmrDepth - + PieceValue[capturedPiece] + 95 * captHist / 700; + Value futilityValue = ss->staticEval + 242 + 230 * lmrDepth + + PieceValue[capturedPiece] + 133 * captHist / 1024; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 36, -153 * depth, 134 * depth); - if (!pos.see_ge(move, -157 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -138 * depth, 135 * depth); + if (!pos.see_ge(move, -154 * depth - seeHist)) continue; } else @@ -1065,17 +1066,17 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4107 * depth) + if (history < -4348 * depth) continue; history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3576; + lmrDepth += history / 3593; - Value futilityValue = ss->staticEval + (bestMove ? 49 : 143) + 116 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth; - if (bestValue < ss->staticEval - 150 && lmrDepth < 7) - futilityValue += 108; + if (bestValue < ss->staticEval - 128 && lmrDepth < 8) + futilityValue += 103; // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning @@ -1091,7 +1092,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1111,11 +1112,11 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 5 - (thisThread->completedDepth > 32) + ss->ttPv + && depth >= 6 - (thisThread->completedDepth > 29) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (55 + 81 * (ss->ttPv && !PvNode)) * depth / 58; + Value singularBeta = ttData.value - (59 + 77 * (ss->ttPv && !PvNode)) * depth / 54; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1125,11 +1126,11 @@ Value Search::Worker::search( if (value < singularBeta) { - int corrValAdj1 = std::abs(correctionValue) / 265083; - int corrValAdj2 = std::abs(correctionValue) / 253680; - int doubleMargin = 267 * PvNode - 181 * !ttCapture - corrValAdj1; + int corrValAdj1 = std::abs(correctionValue) / 248873; + int corrValAdj2 = std::abs(correctionValue) / 255331; + int doubleMargin = 262 * PvNode - 188 * !ttCapture - corrValAdj1; int tripleMargin = - 96 + 282 * PvNode - 250 * !ttCapture + 103 * ss->ttPv - corrValAdj2; + 88 + 265 * PvNode - 256 * !ttCapture + 93 * ss->ttPv - corrValAdj2; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1182,46 +1183,46 @@ Value Search::Worker::search( // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2230 + PvNode * 1013 + (ttData.value > alpha) * 925 - + (ttData.depth >= depth) * (971 + cutNode * 1159); + r -= 2381 + PvNode * 1008 + (ttData.value > alpha) * 880 + + (ttData.depth >= depth) * (1022 + cutNode * 1140); // These reduction adjustments have no proven non-linear scaling - r += 316 - moveCount * 32; + r += 306 - moveCount * 34; - r -= std::abs(correctionValue) / 31568; + r -= std::abs(correctionValue) / 29696; if (PvNode && !is_decisive(bestValue)) r -= risk_tolerance(pos, bestValue); // Increase reduction for cut nodes if (cutNode) - r += 2608 + 1024 * !ttData.move; + r += 2784 + 1038 * !ttData.move; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) - r += 1123 + (depth < 8) * 982; + r += 1171 + (depth < 8) * 985; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 3) - r += 981 + allNode * 833; + r += 1042 + allNode * 864; // For first picked move (ttMove) reduce reduction else if (move == ttData.move) - r -= 1982; + r -= 1937; if (capture) ss->statScore = - 688 * int(PieceValue[pos.captured_piece()]) / 100 + 846 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 4653; + - 4822; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3591; + + (*contHist[1])[movedPiece][move.to_sq()] - 3271; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 1407 / 16384; + r -= ss->statScore * 1582 / 16384; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1247,7 +1248,7 @@ Value Search::Worker::search( { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); + const bool doDeeperSearch = value > (bestValue + 43 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1256,7 +1257,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = (value >= beta) * 2010; + int bonus = (value >= beta) * 1800; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } else if (value > alpha && value < bestValue + 9) @@ -1268,11 +1269,11 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1111; + r += 1156; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3554) - (r > 5373 && newDepth > 2), !cutNode); + newDepth - (r > 3495) - (r > 5510 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1379,7 +1380,7 @@ Value Search::Worker::search( else { // Reduce other moves if we have found at least one score improvement - if (depth > 2 && depth < 15 && !is_decisive(value)) + if (depth > 2 && depth < 16 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1423,25 +1424,25 @@ Value Search::Worker::search( // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (118 * (depth > 5) + 36 * !allNode + 161 * ((ss - 1)->moveCount > 8) - + 133 * (!ss->inCheck && bestValue <= ss->staticEval - 107) - + 120 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 84) - + 81 * ((ss - 1)->isTTMove) + 100 * (ss->cutoffCnt <= 3) - + std::min(-(ss - 1)->statScore / 108, 320)); + int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 112, 303)); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(160 * depth - 106, 1523) * bonusScale; + const int scaledBonus = std::min(160 * depth - 99, 1492) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 416 / 32768); + scaledBonus * 388 / 32768); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << scaledBonus * 219 / 32768; + << scaledBonus * 212 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1103 / 32768; + << scaledBonus * 1055 / 32768; } else if (priorCapture && prevSq != SQ_NONE) @@ -1450,7 +1451,7 @@ Value Search::Worker::search( Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] - << std::min(330 * depth - 198, 3320); + << std::min(300 * depth - 182, 2995); } if (PvNode) @@ -1601,7 +1602,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 325; + futilityBase = ss->staticEval + 359; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1664,7 +1665,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 5389) + <= 5923) continue; // Do not search moves with bad enough SEE values @@ -1735,7 +1736,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 735 / rootDelta + !i * reductionScale * 191 / 512 + 1132; + return reductionScale - delta * 764 / rootDelta + !i * reductionScale * 191 / 512 + 1087; } // elapsed() returns the time elapsed since the search started. If the @@ -1831,35 +1832,35 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = std::min(162 * depth - 92, 1587) + 298 * isTTMove; - int malus = std::min(694 * depth - 230, 2503) - 32 * (moveCount - 1); + int bonus = std::min(141 * depth - 89, 1613) + 311 * isTTMove; + int malus = std::min(695 * depth - 215, 2808) - 31 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1202 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1129 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1152 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1246 / 1024); } else { // Increase stats for the best move in case it was a capture move captured = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1236 / 1024; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1187 / 1024; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 976 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 987 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { moved_piece = pos.moved_piece(move); captured = type_of(pos.piece_on(move.to_sq())); - captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1224 / 1024; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1377 / 1024; } } @@ -1868,7 +1869,7 @@ void update_all_stats(const Position& pos, // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { static constexpr std::array conthist_bonuses = { - {{1, 1029}, {2, 656}, {3, 326}, {4, 536}, {5, 120}, {6, 537}}}; + {{1, 1103}, {2, 659}, {3, 323}, {4, 533}, {5, 121}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1889,12 +1890,12 @@ void update_quiet_histories( workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 844 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 829 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 964 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 1004 / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 615 / 1024; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 587 / 1024; } } From f3bfce353168b03e4fedce515de1898c691f81ec Mon Sep 17 00:00:00 2001 From: xu-shawn <50402888+xu-shawn@users.noreply.github.com> Date: Fri, 28 Feb 2025 00:50:59 -0800 Subject: [PATCH 0931/1309] Revert "Replace aligned() function with line_bb() and simplify king piece detection" (#5915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/official-stockfish/Stockfish/pull/5915 No functional change Co-authored-by: Robert Nürnberg --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 1e19e3c8a1c..37e9a2eb5b8 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); + return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !(line_bb(from, to) & pieces(~sideToMove, KING) || m.type_of() == CASTLING); + return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From 99d32e395ee0509578553d75580234e90de89d66 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 28 Feb 2025 00:55:29 -0800 Subject: [PATCH 0932/1309] Reapply #5909 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This version fixes the logic of `gives_check`, which was identified to be the cause of illegal moves. closes https://github.com/official-stockfish/Stockfish/pull/5914 No functional change Co-authored-by: Robert Nürnberg Co-authored-by: gab8192 --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37e9a2eb5b8..14599a76d3e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; + return !(line_bb(from, to) & pieces(~sideToMove, KING)) || m.type_of() == CASTLING; switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From b825ea6e57f224d7f468b1562a71fa4c22fe2fd8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 28 Feb 2025 09:30:43 -0800 Subject: [PATCH 0933/1309] Improve Perft Testing Added #5909 problematic position and chess-library DFRC positions to perft testing. Additional work done by @Disservin to clean up and improve error reporting. closes https://github.com/official-stockfish/Stockfish/pull/5917 No functional change Co-authored-by: disservin --- tests/perft.sh | 93 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/tests/perft.sh b/tests/perft.sh index c1532c20c19..97c462c5721 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -1,6 +1,8 @@ #!/bin/bash # verify perft numbers (positions from https://www.chessprogramming.org/Perft_Results) +TESTS_FAILED=0 + error() { echo "perft testing failed on line $1" @@ -10,23 +12,82 @@ trap 'error ${LINENO}' ERR echo "perft testing started" -cat << EOF > perft.exp - set timeout 10 - lassign \$argv pos depth result - spawn ./stockfish - send "position \$pos\\ngo perft \$depth\\n" - expect "Nodes searched? \$result" {} timeout {exit 1} - send "quit\\n" - expect eof +EXPECT_SCRIPT=$(mktemp) + +cat << 'EOF' > $EXPECT_SCRIPT +#!/usr/bin/expect -f +set timeout 30 +lassign [lrange $argv 0 4] pos depth result chess960 logfile +log_file -noappend $logfile +spawn ./stockfish +if {$chess960 == "true"} { + send "setoption name UCI_Chess960 value true\n" +} +send "position $pos\ngo perft $depth\n" +expect { + "Nodes searched: $result" {} + timeout {puts "TIMEOUT: Expected $result nodes"; exit 1} + eof {puts "EOF: Stockfish crashed"; exit 2} +} +send "quit\n" +expect eof EOF -expect perft.exp startpos 5 4865609 > /dev/null -expect perft.exp "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 > /dev/null -expect perft.exp "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 6 11030083 > /dev/null -expect perft.exp "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 5 15833292 > /dev/null -expect perft.exp "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 > /dev/null -expect perft.exp "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 > /dev/null +chmod +x $EXPECT_SCRIPT -rm perft.exp +run_test() { + local pos="$1" + local depth="$2" + local expected="$3" + local chess960="$4" + local tmp_file=$(mktemp) + + echo -n "Testing depth $depth: ${pos:0:40}... " + + if $EXPECT_SCRIPT "$pos" "$depth" "$expected" "$chess960" "$tmp_file" > /dev/null 2>&1; then + echo "OK" + rm -f "$tmp_file" + else + local exit_code=$? + echo "FAILED (exit code: $exit_code)" + echo "===== Output for failed test =====" + cat "$tmp_file" + echo "==================================" + rm -f "$tmp_file" + TESTS_FAILED=1 + fi +} -echo "perft testing OK" +# standard positions + +run_test "startpos" 7 3195901860 "false" +run_test "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 "false" +run_test "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 7 178633661 "false" +run_test "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 6 706045033 "false" +run_test "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 "false" +run_test "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 "false" +run_test "fen r7/4p3/5p1q/3P4/4pQ2/4pP2/6pp/R3K1kr w Q - 1 3" 5 11609488 "false" + +# chess960 positions + +run_test "fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1" 6 119060324 "true" +run_test "fen 1rqbkrbn/1ppppp1p/1n6/p1N3p1/8/2P4P/PP1PPPP1/1RQBKRBN w FBfb - 0 9" 6 191762235 "true" +run_test "fen rbbqn1kr/pp2p1pp/6n1/2pp1p2/2P4P/P7/BP1PPPP1/R1BQNNKR w HAha - 0 9" 6 924181432 "true" +run_test "fen rqbbknr1/1ppp2pp/p5n1/4pp2/P7/1PP5/1Q1PPPPP/R1BBKNRN w GAga - 0 9" 6 308553169 "true" +run_test "fen 4rrb1/1kp3b1/1p1p4/pP1Pn2p/5p2/1PR2P2/2P1NB1P/2KR1B2 w D - 0 21" 6 872323796 "true" +run_test "fen 1rkr3b/1ppn3p/3pB1n1/6q1/R2P4/4N1P1/1P5P/2KRQ1B1 b Dbd - 0 14" 6 2678022813 "true" +run_test "fen qbbnrkr1/p1pppppp/1p4n1/8/2P5/6N1/PPNPPPPP/1BRKBRQ1 b FCge - 1 3" 6 521301336 "true" +run_test "fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2R2R b E - 1 20" 2 1438 "true" +run_test "fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 w E - 0 20" 3 37340 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p2Q1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 b E - 2 19" 4 2237725 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 4 2098209 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 5 79014522 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 6 2998685421 "true" + +rm -f $EXPECT_SCRIPT +echo "perft testing completed" + +if [ $TESTS_FAILED -ne 0 ]; then + echo "Some tests failed" + exit 1 +fi From e407a4f269ba4389f31e9bb71fd8b944e3056ced Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Fri, 28 Feb 2025 17:54:46 -0800 Subject: [PATCH 0934/1309] Simplify risk_tolerance + avoid overflow passed simplification STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 73984 W: 19058 L: 18879 D: 36047 Ptnml(0-2): 232, 8735, 18866, 8940, 219 https://tests.stockfishchess.org/tests/view/67c269a38200cf1034c9baf9 passed simplification LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 39288 W: 10033 L: 9833 D: 19422 Ptnml(0-2): 14, 4168, 11086, 4356, 20 https://tests.stockfishchess.org/tests/view/67c34f8c8200cf1034c9bda1 closes https://github.com/official-stockfish/Stockfish/pull/5919 Bench: 2050046 --- src/search.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bbd43ed6989..bacd63c9554 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -99,28 +100,28 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss int risk_tolerance(const Position& pos, Value v) { // Returns (some constant of) second derivative of sigmoid. static constexpr auto sigmoid_d2 = [](int x, int y) { - return -355752 * x / (x * x + 3 * y * y); + return 644800 * x / ((x * x + 3 * y * y) * y); }; - int material = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() - + 337 * pos.count() + 553 * pos.count()) - / 64; - - int m = std::clamp(material, 17, 78); + int m = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() + + 337 * pos.count() + 553 * pos.count()) + / 64; // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) // The loss rate is 1/(1+exp((v+a)/b)) - int a = ((-m * 3037 / 256 + 2270) * m / 256 - 637) * m / 256 + 413; - int b = ((m * 7936 / 256 - 2255) * m / 256 + 319) * m / 256 + 83; + int a = 356; + int b = ((65 * m - 3172) * m + 240578) / 2048; + // guard against overflow + assert(abs(v) + a <= std::numeric_limits::max() / 644800); // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) int winning_risk = sigmoid_d2(v - a, b); - int losing_risk = -sigmoid_d2(-v - a, b); + int losing_risk = sigmoid_d2(v + a, b); - return (winning_risk + losing_risk) * 58 / b; + return -(winning_risk + losing_risk) * 32; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -1192,7 +1193,7 @@ Value Search::Worker::search( r -= std::abs(correctionValue) / 29696; - if (PvNode && !is_decisive(bestValue)) + if (PvNode && std::abs(bestValue) <= 2000) r -= risk_tolerance(pos, bestValue); // Increase reduction for cut nodes From e3660b47bdd8249c3e647b11f506058a99167a69 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 2 Mar 2025 21:38:11 -0800 Subject: [PATCH 0935/1309] Add dbg_clear helper function closes https://github.com/official-stockfish/Stockfish/pull/5921 No functional change --- src/misc.cpp | 15 ++++++++++++++- src/misc.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 06a8c624a10..f85356c59f2 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -287,12 +287,18 @@ namespace { template struct DebugInfo { - std::atomic data[N] = {0}; + std::array, N> data = {0}; [[nodiscard]] constexpr std::atomic& operator[](size_t index) { assert(index < N); return data[index]; } + + constexpr DebugInfo& operator=(const DebugInfo& other) { + for (size_t i = 0; i < N; i++) + data[i].store(other.data[i].load()); + return *this; + } }; struct DebugExtremes: public DebugInfo<3> { @@ -393,6 +399,13 @@ void dbg_print() { } } +void dbg_clear() { + hit.fill({}); + mean.fill({}); + stdev.fill({}); + correl.fill({}); + extremes.fill({}); +} // Used to serialize access to std::cout // to avoid multiple threads writing at the same time. diff --git a/src/misc.h b/src/misc.h index d2cbb699dbb..84f11d6de38 100644 --- a/src/misc.h +++ b/src/misc.h @@ -73,6 +73,7 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_extremes_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); +void dbg_clear(); using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); From f9a6d4328654f31cca5414be988b1158e555e09b Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 6 Mar 2025 19:04:56 -0800 Subject: [PATCH 0936/1309] Simplify condition in futility pruning Passed STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 427040 W: 111061 L: 111271 D: 204708 Ptnml(0-2): 1709, 48524, 113171, 48500, 1616 https://tests.stockfishchess.org/tests/view/67af01d01a4c73ae1f930ea4 Passed rebased LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 28704 W: 7330 L: 7120 D: 14254 Ptnml(0-2): 8, 3000, 8138, 3186, 20 https://tests.stockfishchess.org/tests/view/67ca629a45214989aa0a123e closes https://github.com/official-stockfish/Stockfish/pull/5924 Bench: 2050046 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bacd63c9554..440cdc8e36c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1074,10 +1074,8 @@ Value Search::Worker::search( lmrDepth += history / 3593; - Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth; - - if (bestValue < ss->staticEval - 128 && lmrDepth < 8) - futilityValue += 103; + Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth + + 103 * (bestValue < ss->staticEval - 128); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning From 66aee01bb1430ee25ba4df96e0c4c4a931759e4c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 2 Mar 2025 01:29:38 -0800 Subject: [PATCH 0937/1309] Simplify Return Value Adjustment Condition Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82112 W: 21281 L: 21110 D: 39721 Ptnml(0-2): 258, 9630, 21112, 9795, 261 https://tests.stockfishchess.org/tests/view/67c42528b7226b5d8a2dd3a0 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 182652 W: 46295 L: 46240 D: 90117 Ptnml(0-2): 103, 20025, 51003, 20104, 91 https://tests.stockfishchess.org/tests/view/67c4d56b685e87e15e7c43d8 closes https://github.com/official-stockfish/Stockfish/pull/5925 Bench: 1711791 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 440cdc8e36c..baf99c0c687 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1406,9 +1406,8 @@ Value Search::Worker::search( assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); - // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) - && !is_decisive(alpha)) + // Adjust best value for fail high cases + if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) && !is_decisive(alpha)) bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) From fc0e0a44d407dfa440c83e86de9b338b7e2d092d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 9 Mar 2025 19:33:30 -0700 Subject: [PATCH 0938/1309] Refactor accumulator storage/updates Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 115840 W: 29983 L: 29854 D: 56003 Ptnml(0-2): 338, 12990, 31149, 13091, 352 https://tests.stockfishchess.org/tests/view/67d0a044166a3e8781d84223 closes https://github.com/official-stockfish/Stockfish/pull/5927 No functional change --- src/Makefile | 3 +- src/evaluate.cpp | 16 +- src/evaluate.h | 2 + src/nnue/features/half_ka_v2_hm.cpp | 4 +- src/nnue/features/half_ka_v2_hm.h | 5 +- src/nnue/network.cpp | 15 +- src/nnue/network.h | 13 +- src/nnue/nnue_accumulator.cpp | 601 ++++++++++++++++++++++++++++ src/nnue/nnue_accumulator.h | 89 +++- src/nnue/nnue_common.h | 5 + src/nnue/nnue_feature_transformer.h | 449 +-------------------- src/nnue/nnue_misc.cpp | 15 +- src/perft.h | 1 - src/position.cpp | 47 +-- src/position.h | 24 +- src/search.cpp | 44 +- src/search.h | 7 + 17 files changed, 813 insertions(+), 527 deletions(-) create mode 100644 src/nnue/nnue_accumulator.cpp diff --git a/src/Makefile b/src/Makefile index 39cfce8bf19..76b94785ecb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,8 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp memory.cpp + nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \ + engine.cpp score.cpp memory.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ diff --git a/src/evaluate.cpp b/src/evaluate.cpp index dddb5686070..ccb089d9777 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -54,21 +54,22 @@ bool Eval::use_smallnet(const Position& pos) { // of the position from the point of view of the side to move. Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, int optimism) { assert(!pos.checkers()); bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) - : networks.big.evaluate(pos, &caches.big); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) + : networks.big.evaluate(pos, accumulators, &caches.big); Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (std::abs(nnue) < 236)) { - std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); nnue = (125 * psqt + 131 * positional) / 128; smallNet = false; } @@ -99,7 +100,10 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pos.checkers()) return "Final evaluation: none (in check)"; - auto caches = std::make_unique(networks); + Eval::NNUE::AccumulatorStack accumulators; + auto caches = std::make_unique(networks); + + accumulators.reset(pos, networks, *caches); std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); @@ -107,12 +111,12 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqt, positional] = networks.big.evaluate(pos, &caches->big); + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches->big); Value v = psqt + positional; v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; - v = evaluate(networks, pos, *caches, VALUE_ZERO); + v = evaluate(networks, pos, accumulators, *caches, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index aad358321eb..07b914007ea 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,6 +39,7 @@ namespace Eval { namespace NNUE { struct Networks; struct AccumulatorCaches; +class AccumulatorStack; } std::string trace(Position& pos, const Eval::NNUE::Networks& networks); @@ -47,6 +48,7 @@ int simple_eval(const Position& pos, Color c); bool use_smallnet(const Position& pos); Value evaluate(const NNUE::Networks& networks, const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, int optimism); } // namespace Eval diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 81eddb06018..eb3c7e6a701 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -77,8 +77,8 @@ template void HalfKAv2_hm::append_changed_indices(Square ksq, IndexList& removed, IndexList& added); -bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { - return st->dirtyPiece.piece[0] == make_piece(perspective, KING); +bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) { + return dirtyPiece.piece[0] == make_piece(perspective, KING); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 0a420cd1e58..ba122adc8da 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -28,7 +28,6 @@ #include "../nnue_common.h" namespace Stockfish { -struct StateInfo; class Position; } @@ -135,9 +134,9 @@ class HalfKAv2_hm { static void append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - // Returns whether the change stored in this StateInfo means + // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. - static bool requires_refresh(const StateInfo* st, Color perspective); + static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective); }; } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index fe312fcb8fc..cba3abc6301 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -210,6 +210,7 @@ bool Network::save(const std::optional& filename template NetworkOutput Network::evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -229,8 +230,9 @@ Network::evaluate(const Position& pos ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); return {static_cast(psqt / OutputScale), static_cast(positional / OutputScale)}; } @@ -280,6 +282,7 @@ void Network::verify(std::string template NnueEvalTrace Network::trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -303,7 +306,7 @@ Network::trace_evaluate(const Position& for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, cache, transformedFeatures, bucket); + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); @@ -447,14 +450,14 @@ bool Network::write_parameters(std::ostream& stream, return bool(stream); } -// Explicit template instantiation +// Explicit template instantiations template class Network< NetworkArchitecture, - FeatureTransformer>; + FeatureTransformer>; template class Network< NetworkArchitecture, - FeatureTransformer>; + FeatureTransformer>; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h index 764481d9442..21df4b0a14e 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -29,13 +29,16 @@ #include #include "../memory.h" -#include "../position.h" #include "../types.h" #include "nnue_accumulator.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Eval::NNUE { enum class EmbeddedNNUEType { @@ -64,11 +67,13 @@ class Network { bool save(const std::optional& filename) const; NetworkOutput evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const; void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const; private: @@ -100,16 +105,18 @@ class Network { template friend struct AccumulatorCaches::Cache; + + friend class AccumulatorStack; }; // Definitions of the network types using SmallFeatureTransformer = - FeatureTransformer; + FeatureTransformer; using SmallNetworkArchitecture = NetworkArchitecture; using BigFeatureTransformer = - FeatureTransformer; + FeatureTransformer; using BigNetworkArchitecture = NetworkArchitecture; using NetworkBig = Network; diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp new file mode 100644 index 00000000000..0a3d95ad496 --- /dev/null +++ b/src/nnue/nnue_accumulator.cpp @@ -0,0 +1,601 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "nnue_accumulator.h" + +#include +#include +#include + +#include "../bitboard.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "network.h" +#include "nnue_common.h" +#include "nnue_feature_transformer.h" + +namespace Stockfish::Eval::NNUE { + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed); + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache); + +} + +void AccumulatorState::reset(const DirtyPiece& dp) noexcept { + dirtyPiece = dp; + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); +} + +const AccumulatorState& AccumulatorStack::latest() const noexcept { + return m_accumulators[m_current_idx - 1]; +} + +AccumulatorState& AccumulatorStack::mut_latest() noexcept { + return m_accumulators[m_current_idx - 1]; +} + +void AccumulatorStack::reset(const Position& rootPos, + const Networks& networks, + AccumulatorCaches& caches) noexcept { + m_current_idx = 1; + + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); +} + +void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { + assert(m_current_idx + 1 < m_accumulators.size()); + m_accumulators[m_current_idx].reset(dirtyPiece); + m_current_idx++; +} + +void AccumulatorStack::pop() noexcept { + assert(m_current_idx > 1); + m_current_idx--; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + evaluate_side(pos, featureTransformer, cache); + evaluate_side(pos, featureTransformer, cache); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate_side( + const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + const auto last_usable_accum = find_last_usable_accumulator(); + + if ((m_accumulators[last_usable_accum].*accPtr).computed[Perspective]) + forward_update_incremental(pos, featureTransformer, last_usable_accum); + + else + { + update_accumulator_refresh_cache(featureTransformer, pos, mut_latest(), cache); + backward_update_incremental(pos, featureTransformer, last_usable_accum); + } +} + +// Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator +// state just before a change that requires full refresh. +template AccumulatorState::*accPtr> +std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { + + for (std::size_t curr_idx = m_current_idx - 1; curr_idx > 0; curr_idx--) + { + if ((m_accumulators[curr_idx].*accPtr).computed[Perspective]) + return curr_idx; + + if (FeatureSet::requires_refresh(m_accumulators[curr_idx].dirtyPiece, Perspective)) + return curr_idx; + } + + return 0; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::forward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept { + + assert(begin < m_accumulators.size()); + assert((m_accumulators[begin].*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = begin + 1; next < m_current_idx; next++) + update_accumulator_incremental(featureTransformer, ksq, m_accumulators[next], + m_accumulators[next - 1]); + + assert((latest().*accPtr).computed[Perspective]); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::backward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept { + + assert(end < m_accumulators.size()); + assert(end < m_current_idx); + assert((latest().*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = m_current_idx - 2; next >= end; next--) + update_accumulator_incremental( + featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); + + assert((m_accumulators[end].*accPtr).computed[Perspective]); +} + +// Explicit template instantiations +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed) { + [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; + [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; + + assert(Forward != Backwards); + + assert((computed.*accPtr).computed[Perspective]); + assert(!(target_state.*accPtr).computed[Perspective]); + + // The size must be enough to contain the largest possible update. + // That might depend on the feature set and generally relies on the + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. + // In this case, the maximum size of both feature addition and removal + // is 2, since we are incrementally updating one move at a time. + FeatureSet::IndexList removed, added; + if constexpr (Forward) + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, + added); + else + FeatureSet::append_changed_indices(ksq, computed.dirtyPiece, added, removed); + + if (removed.size() == 0 && added.size() == 0) + { + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + } + else + { + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); + +#ifdef VECTOR + auto* accIn = + reinterpret_cast(&(computed.*accPtr).accumulation[Perspective][0]); + auto* accOut = + reinterpret_cast(&(target_state.*accPtr).accumulation[Perspective][0]); + + const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; + auto* columnA0 = reinterpret_cast(&featureTransformer.weights[offsetA0]); + const IndexType offsetR0 = TransformedFeatureDimensions * removed[0]; + auto* columnR0 = reinterpret_cast(&featureTransformer.weights[offsetR0]); + + if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) + { + assert(added.size() == 1 && removed.size() == 1); + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), + vec_add_16(columnR0[i], columnR1[i])); + } + else if (Backwards && removed.size() == 1) + { + assert(added.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), + vec_sub_16(columnA1[i], columnR0[i])); + } + else + { + assert(added.size() == 2 && removed.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), + vec_add_16(columnR0[i], columnR1[i]))); + } + + auto* accPsqtIn = + reinterpret_cast(&(computed.*accPtr).psqtAccumulation[Perspective][0]); + auto* accPsqtOut = + reinterpret_cast(&(target_state.*accPtr).psqtAccumulation[Perspective][0]); + + const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; + auto* columnPsqtA0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA0]); + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; + auto* columnPsqtR0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR0]); + + if ((Forward && removed.size() == 1) + || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 + { + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = + vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); + } + else if (Forward && added.size() == 1) + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); + } + else if (Backwards && removed.size() == 1) + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); + } + else + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32( + accPsqtIn[i], vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); + } +#else + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + + // Difference calculation for the deactivated features + for (const auto index : removed) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] -= + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] -= + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } + + // Difference calculation for the activated features + for (const auto index : added) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] += + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] += + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } +#endif + } + + (target_state.*accPtr).computed[Perspective] = true; +} + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache) { + using Tiling [[maybe_unused]] = SIMDTiling; + + const Square ksq = pos.square(Perspective); + auto& entry = cache[ksq][Perspective]; + FeatureSet::IndexList removed, added; + + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + } + } + + auto& accumulator = accumulatorState.*accPtr; + accumulator.computed[Perspective] = true; + +#ifdef VECTOR + const bool combineLast3 = + std::abs((int) removed.size() - (int) added.size()) == 1 && removed.size() + added.size() > 2; + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; + + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) + { + auto* accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = entryTile[k]; + + std::size_t i = 0; + for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); + } + if (combineLast3) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + if (removed.size() > added.size()) + { + IndexType indexR2 = removed[i + 1]; + const IndexType offsetR2 = Dimensions * indexR2 + j * Tiling::TileHeight; + auto* columnR2 = + reinterpret_cast(&featureTransformer.weights[offsetR2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), + vec_add_16(columnR[k], columnR2[k])); + } + else + { + IndexType indexA2 = added[i + 1]; + const IndexType offsetA2 = Dimensions * indexA2 + j * Tiling::TileHeight; + auto* columnA2 = + reinterpret_cast(&featureTransformer.weights[offsetA2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), + vec_add_16(columnA[k], columnA2[k])); + } + } + else + { + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + } + + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) + { + auto* accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + auto* entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = entryTilePsqt[k]; + + for (std::size_t i = 0; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + for (std::size_t i = 0; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&entryTilePsqt[k], psqt[k]); + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + + for (const auto index : removed) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] -= featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] -= featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + for (const auto index : added) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] += featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] += featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + + // The accumulator of the refresh entry has been updated. + // Now copy its content to the actual accumulator we were refreshing. + + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + sizeof(BiasType) * Dimensions); + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, + sizeof(int32_t) * PSQTBuckets); +#endif + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[pt] = pos.pieces(pt); +} + +} + +} diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 0d3d9413556..362ea83e30a 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -21,23 +21,43 @@ #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED +#include +#include #include +#include +#include +#include "../types.h" #include "nnue_architecture.h" #include "nnue_common.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Eval::NNUE { using BiasType = std::int16_t; using PSQTWeightType = std::int32_t; using IndexType = std::uint32_t; +struct Networks; + +template +struct alignas(CacheLineSize) Accumulator; + +struct AccumulatorState; + +template AccumulatorState::*accPtr> +class FeatureTransformer; + // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[COLOR_NB][Size]; - std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - bool computed[COLOR_NB]; + std::int16_t accumulation[COLOR_NB][Size]; + std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; + std::array computed; }; @@ -95,6 +115,69 @@ struct AccumulatorCaches { Cache small; }; + +struct AccumulatorState { + Accumulator accumulatorBig; + Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; + + void reset(const DirtyPiece& dp) noexcept; +}; + + +class AccumulatorStack { + public: + AccumulatorStack() : + m_accumulators(MAX_PLY + 1), + m_current_idx{} {} + + [[nodiscard]] const AccumulatorState& latest() const noexcept; + + void + reset(const Position& rootPos, const Networks& networks, AccumulatorCaches& caches) noexcept; + void push(const DirtyPiece& dirtyPiece) noexcept; + void pop() noexcept; + + template AccumulatorState::*accPtr> + void evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + private: + [[nodiscard]] AccumulatorState& mut_latest() noexcept; + + template AccumulatorState::*accPtr> + void evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + template AccumulatorState::*accPtr> + [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + + template AccumulatorState::*accPtr> + void + forward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept; + + template AccumulatorState::*accPtr> + void + backward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept; + + std::vector m_accumulators; + std::size_t m_current_idx; +}; + } // namespace Stockfish::Eval::NNUE #endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f21a8dec7df..b217c35839f 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -279,6 +279,11 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size flush(); } +enum IncUpdateDirection { + FORWARD, + BACKWARDS +}; + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7e4c669ae3e..20e85be3c99 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -22,12 +22,9 @@ #define NNUE_FEATURE_TRANSFORMER_H_INCLUDED #include -#include #include #include #include -#include -#include #include "../position.h" #include "../types.h" @@ -41,11 +38,6 @@ using BiasType = std::int16_t; using WeightType = std::int16_t; using PSQTWeightType = std::int32_t; -enum IncUpdateDirection { - FORWARD, - BACKWARDS -}; - // If vector instructions are enabled, we update and refresh the // accumulator tile by tile such that each tile fits in the CPU's // vector registers. @@ -249,15 +241,12 @@ class SIMDTiling { // Input feature converter template StateInfo::*accPtr> + Accumulator AccumulatorState::*accPtr> class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - private: - using Tiling = SIMDTiling; - public: // Output type using OutputType = TransformedFeatureType; @@ -348,19 +337,21 @@ class FeatureTransformer { // Convert input features std::int32_t transform(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache, OutputType* output, int bucket) const { - update_accumulator(pos, cache); - update_accumulator(pos, cache); + + accumulatorStack.evaluate(pos, *this, *cache); + const auto& accumulatorState = accumulatorStack.latest(); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; + const auto& psqtAccumulation = (accumulatorState.*accPtr).psqtAccumulation; const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; - const auto& accumulation = (pos.state()->*accPtr).accumulation; + const auto& accumulation = (accumulatorState.*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) { @@ -473,432 +464,6 @@ class FeatureTransformer { return psqt; } // end of function transform() - private: - // Given a computed accumulator, computes the accumulator of another position. - template - void update_accumulator_incremental(const Square ksq, - StateInfo* target_state, - const StateInfo* computed) const { - [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; - [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; - assert((computed->*accPtr).computed[Perspective]); - - StateInfo* next = Forward ? computed->next : computed->previous; - - assert(next != nullptr); - assert(!(next->*accPtr).computed[Perspective]); - - // The size must be enough to contain the largest possible update. - // That might depend on the feature set and generally relies on the - // feature set's update cost calculation to be correct and never allow - // updates with more added/removed features than MaxActiveDimensions. - // In this case, the maximum size of both feature addition and removal - // is 2, since we are incrementally updating one move at a time. - FeatureSet::IndexList removed, added; - if constexpr (Forward) - FeatureSet::append_changed_indices(ksq, next->dirtyPiece, removed, added); - else - FeatureSet::append_changed_indices(ksq, computed->dirtyPiece, added, - removed); - - if (removed.size() == 0 && added.size() == 0) - { - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - } - else - { - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); - if (Forward) - assert(added.size() <= removed.size()); - else - assert(removed.size() <= added.size()); - -#ifdef VECTOR - auto* accIn = - reinterpret_cast(&(computed->*accPtr).accumulation[Perspective][0]); - auto* accOut = reinterpret_cast(&(next->*accPtr).accumulation[Perspective][0]); - - const IndexType offsetA0 = HalfDimensions * added[0]; - auto* columnA0 = reinterpret_cast(&weights[offsetA0]); - const IndexType offsetR0 = HalfDimensions * removed[0]; - auto* columnR0 = reinterpret_cast(&weights[offsetR0]); - - if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) - { - assert(added.size() == 1 && removed.size() == 1); - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); - } - else if (Forward && added.size() == 1) - { - assert(removed.size() == 2); - const IndexType offsetR1 = HalfDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), - vec_add_16(columnR0[i], columnR1[i])); - } - else if (Backwards && removed.size() == 1) - { - assert(added.size() == 2); - const IndexType offsetA1 = HalfDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&weights[offsetA1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), - vec_sub_16(columnA1[i], columnR0[i])); - } - else - { - assert(added.size() == 2 && removed.size() == 2); - const IndexType offsetA1 = HalfDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&weights[offsetA1]); - const IndexType offsetR1 = HalfDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = - vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), - vec_add_16(columnR0[i], columnR1[i]))); - } - - auto* accPsqtIn = reinterpret_cast( - &(computed->*accPtr).psqtAccumulation[Perspective][0]); - auto* accPsqtOut = - reinterpret_cast(&(next->*accPtr).psqtAccumulation[Perspective][0]); - - const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; - auto* columnPsqtA0 = reinterpret_cast(&psqtWeights[offsetPsqtA0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; - auto* columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - - if ((Forward && removed.size() == 1) - || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 - { - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), - columnPsqtA0[i]); - } - else if (Forward && added.size() == 1) - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = - vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); - } - else if (Backwards && removed.size() == 1) - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&psqtWeights[offsetPsqtA1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = - vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); - } - else - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&psqtWeights[offsetPsqtA1]); - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = vec_add_psqt_32( - accPsqtIn[i], - vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); - } -#else - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] -= - psqtWeights[index * PSQTBuckets + i]; - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] += weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] += - psqtWeights[index * PSQTBuckets + i]; - } -#endif - } - - (next->*accPtr).computed[Perspective] = true; - - if (next != target_state) - update_accumulator_incremental(ksq, target_state, next); - } - - - template - void update_accumulator_refresh_cache(const Position& pos, - AccumulatorCaches::Cache* cache) const { - assert(cache != nullptr); - - Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq][Perspective]; - FeatureSet::IndexList removed, added; - - for (Color c : {WHITE, BLACK}) - { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; - - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - } - } - - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; - -#ifdef VECTOR - const bool combineLast3 = std::abs((int) removed.size() - (int) added.size()) == 1 - && removed.size() + added.size() > 2; - vec_t acc[Tiling::NumRegs]; - psqt_vec_t psqt[Tiling::NumPsqtRegs]; - - for (IndexType j = 0; j < HalfDimensions / Tiling::TileHeight; ++j) - { - auto* accTile = reinterpret_cast( - &accumulator.accumulation[Perspective][j * Tiling::TileHeight]); - auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = entryTile[k]; - - std::size_t i = 0; - for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) - { - IndexType indexR = removed[i]; - const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&weights[offsetA]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); - } - if (combineLast3) - { - IndexType indexR = removed[i]; - const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&weights[offsetA]); - - if (removed.size() > added.size()) - { - IndexType indexR2 = removed[i + 1]; - const IndexType offsetR2 = HalfDimensions * indexR2 + j * Tiling::TileHeight; - auto* columnR2 = reinterpret_cast(&weights[offsetR2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), - vec_add_16(columnR[k], columnR2[k])); - } - else - { - IndexType indexA2 = added[i + 1]; - const IndexType offsetA2 = HalfDimensions * indexA2 + j * Tiling::TileHeight; - auto* columnA2 = reinterpret_cast(&weights[offsetA2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), - vec_add_16(columnA[k], columnA2[k])); - } - } - else - { - for (; i < removed.size(); ++i) - { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - for (; i < added.size(); ++i) - { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - } - - for (IndexType k = 0; k < Tiling::NumRegs; k++) - vec_store(&entryTile[k], acc[k]); - for (IndexType k = 0; k < Tiling::NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) - { - auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); - auto* entryTilePsqt = - reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = entryTilePsqt[k]; - - for (std::size_t i = 0; i < removed.size(); ++i) - { - IndexType index = removed[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } - for (std::size_t i = 0; i < added.size(); ++i) - { - IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - vec_store_psqt(&entryTilePsqt[k], psqt[k]); - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - -#else - - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] -= weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; - } - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; - } - - // The accumulator of the refresh entry has been updated. - // Now copy its content to the actual accumulator we were refreshing. - - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, - sizeof(BiasType) * HalfDimensions); - - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, - sizeof(int32_t) * PSQTBuckets); -#endif - - for (Color c : {WHITE, BLACK}) - entry.byColorBB[c] = pos.pieces(c); - - for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[pt] = pos.pieces(pt); - } - - - template - void update_accumulator(const Position& pos, - AccumulatorCaches::Cache* cache) const { - StateInfo* st = pos.state(); - if ((st->*accPtr).computed[Perspective]) - return; // nothing to do - - // Look for a usable already computed accumulator of an earlier position. - // Always try to do an incremental update as most accumulators will be reusable. - do - { - if (FeatureSet::requires_refresh(st, Perspective) || !st->previous - || st->previous->next != st) - { - // compute accumulator from scratch for this position - update_accumulator_refresh_cache(pos, cache); - if (st != pos.state()) - // when computing an accumulator from scratch we can use it to - // efficiently compute the accumulator backwards, until we get to a king - // move. We expect that we will need these accumulators later anyway, so - // computing them now will save some work. - update_accumulator_incremental( - pos.square(Perspective), st, pos.state()); - return; - } - st = st->previous; - } while (!(st->*accPtr).computed[Perspective]); - - // Start from the oldest computed accumulator, update all the - // accumulators up to the current position. - update_accumulator_incremental(pos.square(Perspective), pos.state(), st); - } - - template - friend struct AccumulatorCaches::Cache; - alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 2220684da69..809d454b5ff 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -120,9 +120,12 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat format_cp_compact(value, &board[y + 2][x + 2], pos); }; + AccumulatorStack accumulators; + accumulators.reset(pos, networks, caches); + // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - auto [psqt, positional] = networks.big.evaluate(pos, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -135,18 +138,15 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat if (pc != NO_PIECE && type_of(pc) != KING) { - auto st = pos.state(); - pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; - std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); + accumulators.reset(pos, networks, caches); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); @@ -157,7 +157,8 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << board[row] << '\n'; ss << '\n'; - auto t = networks.big.trace_evaluate(pos, &caches.big); + accumulators.reset(pos, networks, caches); + auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl diff --git a/src/perft.h b/src/perft.h index f0d38ab7256..229debd4078 100644 --- a/src/perft.h +++ b/src/perft.h @@ -34,7 +34,6 @@ template uint64_t perft(Position& pos, Depth depth) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); uint64_t cnt, nodes = 0; const bool leaf = (depth == 2); diff --git a/src/position.cpp b/src/position.cpp index 14599a76d3e..52e1004e8fc 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -34,7 +34,6 @@ #include "bitboard.h" #include "misc.h" #include "movegen.h" -#include "nnue/nnue_common.h" #include "syzygy/tbprobe.h" #include "tt.h" #include "uci.h" @@ -83,7 +82,6 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Position p; p.set(pos.fen(), pos.is_chess960(), &st); @@ -685,10 +683,10 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position // will be prefetched -void Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +DirtyPiece Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -709,11 +707,7 @@ void Position::do_move(Move m, ++st->rule50; ++st->pliesFromNull; - // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - - auto& dp = st->dirtyPiece; + DirtyPiece dp; dp.dirty_num = 1; Color us = sideToMove; @@ -733,7 +727,7 @@ void Position::do_move(Move m, assert(captured == make_piece(us, ROOK)); Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); + do_castling(us, from, to, rfrom, rto, &dp); k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; @@ -906,6 +900,8 @@ void Position::do_move(Move m, } assert(pos_is_ok()); + + return dp; } @@ -975,23 +971,25 @@ void Position::undo_move(Move m) { // Helper used to do/undo a castling move. This is a bit // tricky in Chess960 where from/to squares can overlap. template -void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { +void Position::do_castling( + Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) { bool kingSide = to > from; rfrom = to; // Castling is encoded as "king captures friendly rook" rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + assert(!Do || dp); + if (Do) { - auto& dp = st->dirtyPiece; - dp.piece[0] = make_piece(us, KING); - dp.from[0] = from; - dp.to[0] = to; - dp.piece[1] = make_piece(us, ROOK); - dp.from[1] = rfrom; - dp.to[1] = rto; - dp.dirty_num = 2; + dp->piece[0] = make_piece(us, KING); + dp->from[0] = from; + dp->to[0] = to; + dp->piece[1] = make_piece(us, ROOK); + dp->from[1] = rfrom; + dp->to[1] = rto; + dp->dirty_num = 2; } // Remove both pieces first since squares could overlap in Chess960 @@ -1011,7 +1009,7 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { assert(!checkers()); assert(&newSt != st); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulatorBig)); + std::memcpy(&newSt, st, sizeof(StateInfo)); newSt.previous = st; st->next = &newSt; @@ -1026,11 +1024,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { st->key ^= Zobrist::side; prefetch(tt.first_entry(key())); - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - st->pliesFromNull = 0; sideToMove = ~sideToMove; diff --git a/src/position.h b/src/position.h index eeea1b74c5a..75f22c7df58 100644 --- a/src/position.h +++ b/src/position.h @@ -26,8 +26,6 @@ #include #include "bitboard.h" -#include "nnue/nnue_accumulator.h" -#include "nnue/nnue_architecture.h" #include "types.h" namespace Stockfish { @@ -61,11 +59,6 @@ struct StateInfo { Bitboard checkSquares[PIECE_TYPE_NB]; Piece capturedPiece; int repetition; - - // Used by NNUE - DirtyPiece dirtyPiece; - Eval::NNUE::Accumulator accumulatorBig; - Eval::NNUE::Accumulator accumulatorSmall; }; @@ -140,11 +133,11 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - void do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); - void undo_move(Move m); - void do_null_move(StateInfo& newSt, const TranspositionTable& tt); - void undo_null_move(); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + DirtyPiece do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -187,7 +180,12 @@ class Position { // Other helpers void move_piece(Square from, Square to); template - void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + void do_castling(Color us, + Square from, + Square& to, + Square& rfrom, + Square& rto, + DirtyPiece* const dp = nullptr); template Key adjust_key50(Key k) const; diff --git a/src/search.cpp b/src/search.cpp index baf99c0c687..8bc73b710e5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -41,7 +41,6 @@ #include "movepick.h" #include "nnue/network.h" #include "nnue/nnue_accumulator.h" -#include "nnue/nnue_common.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -197,6 +196,8 @@ void Search::Worker::ensure_network_replicated() { void Search::Worker::start_searching() { + accumulatorStack.reset(rootPos, networks[numaAccessToken], refreshTable); + // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -552,6 +553,26 @@ void Search::Worker::iterative_deepening() { skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); } + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st) { + do_move(pos, move, st, pos.gives_check(move)); +} + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck) { + DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); + accumulatorStack.push(dp); +} + +void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } + +void Search::Worker::undo_move(Position& pos, const Move move) { + pos.undo_move(move); + accumulatorStack.pop(); +} + +void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } + + // Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(66); @@ -614,7 +635,6 @@ Value Search::Worker::search( Move pv[MAX_PLY + 1]; StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Key posKey; Move move, excludedMove, bestMove; @@ -859,11 +879,11 @@ Value Search::Worker::search( ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0]; - pos.do_null_move(st, tt); + do_null_move(pos, st); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); - pos.undo_null_move(); + undo_null_move(pos); // Do not return unproven mate or TB scores if (nullValue >= beta && !is_win(nullValue)) @@ -925,7 +945,7 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); - pos.do_move(move, st, &tt); + do_move(pos, move, st); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; @@ -943,7 +963,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth, !cutNode); - pos.undo_move(move); + undo_move(pos, move); if (value >= probCutBeta) { @@ -1165,7 +1185,7 @@ Value Search::Worker::search( } // Step 16. Make the move - pos.do_move(move, st, givesCheck, &tt); + do_move(pos, move, st, givesCheck); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Add extension to new depth @@ -1290,7 +1310,7 @@ Value Search::Worker::search( } // Step 19. Undo move - pos.undo_move(move); + undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1510,7 +1530,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Move pv[MAX_PLY + 1]; StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Key posKey; Move move, bestMove; @@ -1674,7 +1693,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Step 7. Make and search the move Piece movedPiece = pos.moved_piece(move); - pos.do_move(move, st, givesCheck, &tt); + do_move(pos, move, st, givesCheck); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Update the current move @@ -1685,7 +1704,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; value = -qsearch(pos, ss + 1, -beta, -alpha); - pos.undo_move(move); + undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1752,7 +1771,7 @@ TimePoint Search::Worker::elapsed() const { TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } Value Search::Worker::evaluate(const Position& pos) { - return Eval::evaluate(networks[numaAccessToken], pos, refreshTable, + return Eval::evaluate(networks[numaAccessToken], pos, accumulatorStack, refreshTable, optimism[pos.side_to_move()]); } @@ -2178,7 +2197,6 @@ void SearchManager::pv(Search::Worker& worker, bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); assert(pv.size() == 1); if (pv[0] == Move::none()) diff --git a/src/search.h b/src/search.h index 326480e4dcf..cd3b6ad980c 100644 --- a/src/search.h +++ b/src/search.h @@ -295,6 +295,12 @@ class Worker { private: void iterative_deepening(); + void do_move(Position& pos, const Move move, StateInfo& st); + void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck); + void do_null_move(Position& pos, StateInfo& st); + void undo_move(Position& pos, const Move move); + void undo_null_move(Position& pos); + // This is the main search function, for both PV and non-PV nodes template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); @@ -347,6 +353,7 @@ class Worker { const LazyNumaReplicated& networks; // Used by NNUE + Eval::NNUE::AccumulatorStack accumulatorStack; Eval::NNUE::AccumulatorCaches refreshTable; friend class Stockfish::ThreadPool; From 4afd7f1a7b2379f9094a973676f1f03b2058428b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 9 Mar 2025 18:35:14 +0300 Subject: [PATCH 0939/1309] Removing contHist[1] from pruning formula Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 51552 W: 13297 L: 13091 D: 25164 Ptnml(0-2): 166, 6009, 13215, 6225, 161 https://tests.stockfishchess.org/tests/view/67c4de79685e87e15e7c43f5 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 244554 W: 62135 L: 62142 D: 120277 Ptnml(0-2): 137, 26612, 68794, 26589, 145 https://tests.stockfishchess.org/tests/view/67c50982685e87e15e7c443d closes https://github.com/official-stockfish/Stockfish/pull/5928 Bench: 1980385 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8bc73b710e5..bb0156c5d1a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1679,10 +1679,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Continuation history based pruning if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 5923) + <= 6290) continue; // Do not search moves with bad enough SEE values From 652a8874b523360a3b19c5003c8ba9894ac54d0f Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Fri, 14 Mar 2025 20:32:38 +0100 Subject: [PATCH 0940/1309] Allow more than 1024 threads on high-end machines closes https://github.com/official-stockfish/Stockfish/pull/5929 No functional change --- src/engine.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 6c8799a15d1..a4c0bb1ebeb 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -18,6 +18,7 @@ #include "engine.h" +#include #include #include #include @@ -32,6 +33,7 @@ #include "misc.h" #include "nnue/network.h" #include "nnue/nnue_common.h" +#include "numa.h" #include "perft.h" #include "position.h" #include "search.h" @@ -44,8 +46,9 @@ namespace Stockfish { namespace NN = Eval::NNUE; -constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency())); Engine::Engine(std::optional path) : binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), @@ -74,7 +77,7 @@ Engine::Engine(std::optional path) : })); options.add( // - "Threads", Option(1, 1, 1024, [this](const Option&) { + "Threads", Option(1, 1, MaxThreads, [this](const Option&) { resize_threads(); return thread_allocation_information_as_string(); })); From 8fc5e92005aad8f952817161818ec0082f9642e6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 16 Mar 2025 01:48:49 +0300 Subject: [PATCH 0941/1309] Use slightly different formula for stat score when position is in check Use formula that is closer to movepicker one. Passed STC: https://tests.stockfishchess.org/tests/view/67cffb337be98c1ad9b021ee LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 250432 W: 64978 L: 64343 D: 121111 Ptnml(0-2): 795, 29390, 64159, 30129, 743 Passed LTC: https://tests.stockfishchess.org/tests/view/67d3905d517865b4a2dfce8a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 140004 W: 35742 L: 35215 D: 69047 Ptnml(0-2): 60, 15111, 39151, 15602, 78 closes https://github.com/official-stockfish/Stockfish/pull/5930 Bench: 2147336 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index bb0156c5d1a..3eab34be41c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1235,6 +1235,9 @@ Value Search::Worker::search( 846 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - 4822; + else if (ss->inCheck) + ss->statScore = thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] - 2771; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From 9045f17c3f19a7b779e4baf71689ddd650f5d64c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 14 Mar 2025 02:21:48 -0700 Subject: [PATCH 0942/1309] Simplify captures PCM Passed Non-regression STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 229856 W: 59258 L: 59252 D: 111346 Ptnml(0-2): 746, 27330, 58714, 27448, 690 https://tests.stockfishchess.org/tests/view/67d3fdac517865b4a2dfcef4 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 107652 W: 27470 L: 27338 D: 52844 Ptnml(0-2): 56, 11646, 30280, 11798, 46 https://tests.stockfishchess.org/tests/view/67d5f972517865b4a2dfd2ec closes https://github.com/official-stockfish/Stockfish/pull/5931 Bench: 1842520 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3eab34be41c..4b6ad52e49f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1442,7 +1442,7 @@ Value Search::Worker::search( update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, bestMove == ttData.move, moveCount); - // Bonus for prior countermove that caused the fail low + // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) @@ -1466,13 +1466,12 @@ Value Search::Worker::search( << scaledBonus * 1055 / 32768; } + // Bonus for prior capture countermove that caused the fail low else if (priorCapture && prevSq != SQ_NONE) { - // bonus for prior countermoves that caused the fail low Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] - << std::min(300 * depth - 182, 2995); + thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1100; } if (PvNode) From 43d8ccf85641addd49a5c7e295919fa16c88b72c Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:02:16 +0100 Subject: [PATCH 0943/1309] change the bonusScale depth component Passed STC: https://tests.stockfishchess.org/tests/view/67d35f66517865b4a2dfc801 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 110816 W: 28875 L: 28449 D: 53492 Ptnml(0-2): 329, 13064, 28231, 13420, 364 Passed LTC: https://tests.stockfishchess.org/tests/view/67d4bdf0517865b4a2dfd131 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 121824 W: 31047 L: 30559 D: 60218 Ptnml(0-2): 52, 13056, 34214, 13532, 58 closes https://github.com/official-stockfish/Stockfish/pull/5932 Bench: 2128807 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4b6ad52e49f..a91fb3c1b57 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1445,7 +1445,8 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + int bonusScale = (std::clamp(160 * (depth - 4) / 2, 0, 200) + 34 * !allNode + + 164 * ((ss - 1)->moveCount > 8) + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) From 0dabf4f3fab2356111cbcf058be167bd43ac2479 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 16 Mar 2025 15:37:42 +0300 Subject: [PATCH 0944/1309] Removing the conditional bonus calculation The new value is just a guessed value. Passed STC: https://tests.stockfishchess.org/tests/view/67d5ee09517865b4a2dfd2df LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 52128 W: 13516 L: 13312 D: 25300 Ptnml(0-2): 157, 6044, 13451, 6262, 150 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 42384 W: 10855 L: 10657 D: 20872 Ptnml(0-2): 19, 4554, 11852, 4744, 23 https://tests.stockfishchess.org/tests/view/67d5f9d3517865b4a2dfd2ef closes https://github.com/official-stockfish/Stockfish/pull/5933 Bench: 2030154 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a91fb3c1b57..2da19f32eec 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1279,7 +1279,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = (value >= beta) * 1800; + int bonus = 1600; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } else if (value > alpha && value < bestValue + 9) From 6ceaca4c7b2cc1fa87617b1b9e83d38d8e880924 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 20 Mar 2025 12:40:27 -0700 Subject: [PATCH 0945/1309] Change layout of CorrectionHistory https://tests.stockfishchess.org/tests/view/67da5b158c7f315cc372a9d2 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 150368 W: 38874 L: 38401 D: 73093 Ptnml(0-2): 424, 16821, 40262, 17212, 465 Make CorrectionHistory\ handle both black and white internally. A follow up to https://github.com/official-stockfish/Stockfish/pull/5816 closes https://github.com/official-stockfish/Stockfish/pull/5934 No functional change --- src/history.h | 6 ++++++ src/search.cpp | 11 +++++------ src/search.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/history.h b/src/history.h index fd9b98b98bc..ec245230a76 100644 --- a/src/history.h +++ b/src/history.h @@ -155,6 +155,12 @@ struct CorrHistTypedef { using type = MultiArray::type, PIECE_NB, SQUARE_NB>; }; +template<> +struct CorrHistTypedef { + using type = + Stats; +}; + } template diff --git a/src/search.cpp b/src/search.cpp index 2da19f32eec..0c543c30895 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -87,8 +87,8 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss const auto m = (ss - 1)->currentMove; const auto pcv = w.pawnCorrectionHistory[pawn_structure_index(pos)][us]; const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; - const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; - const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; + const auto wnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us]; + const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; @@ -141,9 +141,9 @@ void update_correction_history(const Position& pos, workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] << bonus * 111 / 128; workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; - workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; - workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) @@ -581,8 +581,7 @@ void Search::Worker::clear() { pawnHistory.fill(-1262); pawnCorrectionHistory.fill(6); minorPieceCorrectionHistory.fill(0); - nonPawnCorrectionHistory[WHITE].fill(0); - nonPawnCorrectionHistory[BLACK].fill(0); + nonPawnCorrectionHistory.fill(0); for (auto& to : continuationCorrectionHistory) for (auto& h : to) diff --git a/src/search.h b/src/search.h index cd3b6ad980c..071773f81b6 100644 --- a/src/search.h +++ b/src/search.h @@ -289,7 +289,7 @@ class Worker { CorrectionHistory pawnCorrectionHistory; CorrectionHistory minorPieceCorrectionHistory; - CorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; + CorrectionHistory nonPawnCorrectionHistory; CorrectionHistory continuationCorrectionHistory; private: From 12d023ed0635e7dd4ada28ad764ea975de55103b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Mar 2025 09:28:58 +0100 Subject: [PATCH 0946/1309] Update CPU contributors closes https://github.com/official-stockfish/Stockfish/pull/5937 No functional change --- Top CPU Contributors.txt | 217 +++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 98 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 3d8c52361dd..4e598ecfc5b 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,118 +1,126 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2024-08-31. +Contributors to Fishtest with >10,000 CPU hours, as of 2025-03-22. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 40428649 3164740143 -technologov 23581394 1076895482 -vdv 19425375 718302718 -linrock 10034115 643194527 +noobpwnftw 41712226 3294628533 +vdv 28993864 954145232 +technologov 24984442 1115931964 +linrock 11463033 741692823 mlang 3026000 200065824 -okrout 2572676 237511408 -pemo 1836785 62226157 +okrout 2726068 248285678 +olafm 2420096 161297116 +pemo 1838361 62294199 +TueRens 1804847 80170868 dew 1689162 100033738 -TueRens 1648780 77891164 -sebastronomy 1468328 60859092 -grandphish2 1466110 91776075 +sebastronomy 1655637 67294942 +grandphish2 1474752 92156319 JojoM 1130625 73666098 -olafm 1067009 74807270 +rpngn 973590 59996557 +oz 921203 60370346 tvijlbrief 796125 51897690 -oz 781847 53910686 -rpngn 768460 49812975 -gvreuls 751085 52177668 +gvreuls 792215 55184194 mibere 703840 46867607 -leszek 566598 42024615 -cw 519601 34988161 +leszek 599745 44681421 +cw 519602 34988289 fastgm 503862 30260818 -CSU_Dynasty 468784 31385034 -maximmasiutin 439192 27893522 -ctoks 435148 28541909 +CSU_Dynasty 474794 31654170 +maximmasiutin 441753 28129452 +robal 437950 28869118 +ctoks 435150 28542141 crunchy 427414 27371625 bcross 415724 29061187 -robal 371112 24642270 -mgrabiak 367963 26464704 +mgrabiak 380202 27586936 velislav 342588 22140902 ncfish1 329039 20624527 Fisherman 327231 21829379 +Sylvain27 317021 11494912 +marrco 310446 19587107 Dantist 296386 18031762 -tolkki963 262050 22049676 -Sylvain27 255595 8864404 +Fifis 289595 14969251 +tolkki963 286043 23596996 +Calis007 272677 17281620 +cody 258835 13301710 nordlandia 249322 16420192 -Fifis 237657 13065577 -marrco 234581 17714473 -Calis007 217537 14450582 +javran 212141 16507618 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 +Wencey 198537 9606420 Thanar 179852 12365359 -javran 169679 13481966 -armo9494 162863 10937118 +sschnee 170521 10891112 +armo9494 168141 11177514 +DesolatedDodo 160605 10392474 spams 157128 10319326 -DesolatedDodo 156683 10211206 -Wencey 152308 8375444 +maposora 155839 13963260 sqrt2 147963 9724586 -vdbergh 140311 9225125 +vdbergh 140514 9242985 jcAEie 140086 10603658 CoffeeOne 137100 5024116 malala 136182 8002293 +Goatminola 134893 11640524 xoto 133759 9159372 -Dubslow 129614 8519312 +markkulix 132104 11000548 +naclosagc 131472 4660806 +Dubslow 129685 8527664 davar 129023 8376525 DMBK 122960 8980062 dsmith 122059 7570238 -CypressChess 120784 8672620 -sschnee 120526 7547722 -maposora 119734 10749710 +Wolfgang 120919 8619168 +CypressChess 120902 8683904 amicic 119661 7938029 -Wolfgang 115713 8159062 +cuistot 116864 7828864 +sterni1971 113754 6054022 Data 113305 8220352 BrunoBanani 112960 7436849 -markkulix 112897 9133168 -cuistot 109802 7121030 +megaman7de 109139 7360928 skiminki 107583 7218170 -sterni1971 104431 5938282 +zeryl 104523 6618969 MaZePallas 102823 6633619 sunu 100167 7040199 -zeryl 99331 6221261 -thirdlife 99156 2245320 +thirdlife 99178 2246544 ElbertoOne 99028 7023771 -megaman7de 98456 6675076 -Goatminola 96765 8257832 +TataneSan 97257 4239502 +romangol 95662 7784954 bigpen0r 94825 6529241 brabos 92118 6186135 Maxim 90818 3283364 psk 89957 5984901 +szupaw 89775 7800606 +jromang 87260 5988073 racerschmacer 85805 6122790 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 -szupaw 82495 7151686 +Spprtr 82103 5663635 BRAVONE 81239 5054681 +MarcusTullius 78930 5189659 +Mineta 78731 4947996 +Torom 77978 2651656 nssy 76497 5259388 -cody 76126 4492126 -jromang 76106 5236025 -MarcusTullius 76103 5061991 -woutboat 76072 6022922 -Spprtr 75977 5252287 +woutboat 76379 6031688 teddybaer 75125 5407666 Pking_cda 73776 5293873 +Viren6 73664 1356502 yurikvelo 73611 5046822 -Mineta 71130 4711422 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 manap 66273 4121774 tinker 64333 4268790 qurashee 61208 3429862 -AGI 58195 4329580 +DanielMiao1 60181 1317252 +AGI 58316 4336328 +jojo2357 57435 4944212 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 ttruscott 56010 3680085 rkl 55132 4164467 -jmdana 54697 4012593 +jmdana 54988 4041917 notchris 53936 4184018 renouve 53811 3501516 +CounterFlow 52536 3203740 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 @@ -122,33 +130,36 @@ GPUex 48686 3684998 OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 -oryx 45639 3546530 +oryx 46141 3583236 +jibarbosa 45890 4541218 +DeepnessFulled 45734 3944282 +abdicj 45577 2631772 VoyagerOne 45476 3452465 +mecevdimitar 44240 2584396 speedycpu 43842 3003273 jbwiebe 43305 2805433 +gopeto 43046 2821514 +YvesKn 42628 2177630 Antihistamine 41788 2761312 mhunt 41735 2691355 -jibarbosa 41640 4145702 +somethingintheshadows 41502 3330418 homyur 39893 2850481 gri 39871 2515779 -DeepnessFulled 39020 3323102 +vidar808 39774 1656372 Garf 37741 2999686 SC 37299 2731694 -Gaster319 37118 3279678 -naclosagc 36562 1279618 +Gaster319 37229 3289674 csnodgrass 36207 2688994 +ZacHFX 35528 2486328 +icewulf 34782 2415146 strelock 34716 2074055 -gopeto 33717 2245606 EthanOConnor 33370 2090311 slakovv 32915 2021889 -jojo2357 32890 2826662 -shawnxu 32019 2802552 +shawnxu 32144 2814668 Gelma 31771 1551204 -vidar808 31560 1351810 +srowen 31181 1732120 kdave 31157 2198362 manapbk 30987 1810399 -ZacHFX 30966 2272416 -TataneSan 30713 1513402 votoanthuan 30691 2460856 Prcuvu 30377 2170122 anst 30301 2190091 @@ -157,145 +168,155 @@ spcc 29925 1901692 hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 +WoodMan777 29300 2579864 belzedar94 28846 1811530 -mecevdimitar 27610 1721382 chriswk 26902 1868317 xwziegtm 26897 2124586 +Jopo12321 26818 1816482 achambord 26582 1767323 -somethingintheshadows 26496 2186404 Patrick_G 26276 1801617 yorkman 26193 1992080 -srowen 25743 1490684 -Ulysses 25413 1702830 -Jopo12321 25227 1652482 +Ulysses 25517 1711634 SFTUser 25182 1675689 nabildanial 25068 1531665 Sharaf_DG 24765 1786697 rodneyc 24376 1416402 jsys14 24297 1721230 +AndreasKrug 24235 1934711 agg177 23890 1395014 -AndreasKrug 23754 1890115 Ente 23752 1678188 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 -WoodMan777 23253 2023048 Nullvalue 23155 2022752 +fishtester 23115 1581502 +wizardassassin 23073 1789536 +Skiff84 22984 1053680 cisco2015 22920 1763301 +ols 22914 1322047 +Hjax 22561 1566151 Zirie 22542 1472937 team-oh 22272 1636708 +mkstockfishtester 22253 2029566 Roady 22220 1465606 MazeOfGalious 21978 1629593 sg4032 21950 1643373 -tsim67 21747 1330880 +tsim67 21939 1343944 ianh2105 21725 1632562 -Skiff84 21711 1014212 +Serpensin 21704 1809188 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 +IslandLambda 21468 1239756 user213718 21454 1404128 -Serpensin 21452 1790510 sphinx 21211 1384728 qoo_charly_cai 21136 1514927 -IslandLambda 21062 1220838 jjoshua2 21001 1423089 Zake9298 20938 1565848 horst.prack 20878 1465656 -fishtester 20729 1348888 0xB00B1ES 20590 1208666 -ols 20477 1195945 Dinde 20459 1292774 +t3hf1sht3ster 20456 670646 j3corre 20405 941444 +0x539 20332 1039516 Adrian.Schmidt123 20316 1281436 +malfoy 20313 1350694 +purpletree 20019 1461026 wei 19973 1745989 teenychess 19819 1762006 rstoesser 19569 1293588 eudhan 19274 1283717 +nalanzeyu 19211 396674 vulcan 18871 1729392 -wizardassassin 18795 1376884 Karpovbot 18766 1053178 jundery 18445 1115855 -mkstockfishtester 18350 1690676 +Farseer 18281 1074642 +sebv15 18267 1262588 +whelanh 17887 347974 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17414 981289 iisiraider 17275 1049015 +Karby 17177 1030688 DragonLord 17014 1162790 -Karby 17008 1013160 -pirt 16965 1271519 +pirt 16991 1274215 redstone59 16842 1461780 Alb11747 16787 1213990 Naven94 16414 951718 -scuzzi 16115 994341 +scuzzi 16155 995347 IgorLeMasson 16064 1147232 ako027ako 15671 1173203 +xuhdev 15516 1528278 infinigon 15285 965966 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 OssumOpossum 14857 1007129 LunaticBFF57 14525 1190310 enedene 14476 905279 -Hjax 14394 1005013 +YELNAMRON 14475 1141330 +RickGroszkiewicz 14272 1385984 +joendter 14269 982014 bpfliegel 14233 882523 -YELNAMRON 14230 1128094 mpx86 14019 759568 jpulman 13982 870599 getraideBFF 13871 1172846 +crocogoat 13817 1119086 Nesa92 13806 1116101 -crocogoat 13803 1117422 joster 13710 946160 mbeier 13650 1044928 Pablohn26 13552 1088532 wxt9861 13550 1312306 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 +Jackfish 13177 894206 +MooTheCow 13091 892304 Machariel 13010 863104 -nalanzeyu 12996 232590 mabichito 12903 749391 -Jackfish 12895 868928 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 -whelanh 12682 266404 +szczur90 12684 977536 +Kyrega 12661 456438 mschmidt 12644 863193 korposzczur 12606 838168 fatmurphy 12547 853210 Oakwen 12532 855759 -icewulf 12447 854878 SapphireBrand 12416 969604 deflectooor 12386 579392 modolief 12386 896470 -Farseer 12249 694108 +ckaz 12273 754644 Hongildong 12201 648712 pgontarz 12151 848794 dbernier 12103 860824 -szczur90 12035 942376 -FormazChar 12019 910409 +FormazChar 12051 913497 +shreven 12044 884734 rensonthemove 11999 971993 stocky 11954 699440 -MooTheCow 11923 779432 3cho 11842 1036786 -ckaz 11792 732276 +ImperiumAeternum 11482 979142 infinity 11470 727027 aga 11412 695127 +Def9Infinity 11408 700682 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 -Def9Infinity 11345 696552 d64 11263 789184 ali-al-zhrani 11245 779246 -ImperiumAeternum 11155 952000 +vaskoul 11144 953906 snicolet 11106 869170 dapper 11032 771402 Ethnikoi 10993 945906 Snuuka 10938 435504 Karmatron 10871 678306 +gerbil 10871 1005842 +OliverClarke 10696 942654 basepi 10637 744851 +michaelrpg 10624 748179 Cubox 10621 826448 -gerbil 10519 971688 -michaelrpg 10509 739239 +dragon123118 10421 936506 OIVAS7572 10420 995586 +GBx3TV 10388 339952 Garruk 10365 706465 dzjp 10343 732529 -RickGroszkiewicz 10263 990798 +borinot 10026 902130 From 6028264cb991b99b3a51cf2fd01b6f1f6ce24cc3 Mon Sep 17 00:00:00 2001 From: Michel Van den Bergh Date: Sat, 22 Mar 2025 09:58:47 +0100 Subject: [PATCH 0947/1309] Delay check for curl/wget until really needed closes https://github.com/official-stockfish/Stockfish/pull/5938 No functional change --- scripts/net.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/net.sh b/scripts/net.sh index 0bc57a19e30..1aa1fbfb188 100755 --- a/scripts/net.sh +++ b/scripts/net.sh @@ -3,11 +3,6 @@ wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \ (command -v curl > /dev/null 2>&1 && echo "curl -skL")) -if [ -z "$wget_or_curl" ]; then - >&2 printf "%s\n" "Neither wget or curl is installed." \ - "Install one of these tools to download NNUE files automatically." - exit 1 -fi sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \ (command -v sha256sum > /dev/null 2>&1 && echo "sha256sum")) @@ -47,6 +42,12 @@ fetch_network() { fi fi + if [ -z "$wget_or_curl" ]; then + >&2 printf "%s\n" "Neither wget or curl is installed." \ + "Install one of these tools to download NNUE files automatically." + exit 1 + fi + for url in \ "https://tests.stockfishchess.org/api/nn/$_filename" \ "https://github.com/official-stockfish/networks/raw/master/$_filename"; do From 4a869f41c6113f1ccdd0f11551858fdc849a245a Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 22 Mar 2025 11:09:33 +0100 Subject: [PATCH 0948/1309] Update the WDL model This PR updates the internal WDL model, using data from 2.6M games played by the revisions since f3bfce353168b03e4fedce515de1898c691f81ec. Note that the normalizing constant increases only moderately from 372 to 377. ``` > ./updateWDL.sh --firstrev f3bfce353168b03e4fedce515de1898c691f81ec Running: ./updateWDL.sh --firstrev f3bfce353168b03e4fedce515de1898c691f81ec --lasttrev HEAD --materialMin 17 --EloDiffMax 5 started at: Sat 22 Mar 11:02:14 CET 2025 Look recursively in directory pgns for games with max nElo difference 5 using books matching "UHO_Lichess_4852_v..epd" for SF revisions between f3bfce353168b03e4fedce515de1898c691f81ec (from 2025-02-28 09:50:59 +0100) and HEAD (from 2025-03-21 11:22:59 +0100). Based on 138253430 positions from 2579360 games, NormalizeToPawnValue should change from 372 to 377. ended at: Sat 22 Mar 11:04:00 CET 2025 ``` ``` > cat scoreWDL.log Converting evals with NormalizeData = {'momType': 'material', 'momMin': 17, 'momMax': 78, 'momTarget': 58, 'as': [-37.45051876, 121.19101539, -132.78783573, 420.70576692]}. Reading eval stats from updateWDL.json. Retained (W,D,L) = (33794348, 69943262, 34515820) positions. Saved distribution plot to updateWDLdistro.png. Fit WDL model based on material. Initial objective function: 0.3648260131692729 Final objective function: 0.36482338611818094 Optimization terminated successfully. const int NormalizeToPawnValue = 377; Corresponding spread = 71; Corresponding normalized spread = 0.1879431202530567; Draw rate at 0.0 eval at material 58 = 0.9902694872976331; Parameters in internal value units: p_a = ((-13.500 * x / 58 + 40.928) * x / 58 + -36.828) * x / 58 + 386.830 p_b = ((96.534 * x / 58 + -165.791) * x / 58 + 90.897) * x / 58 + 49.296 constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; Preparing plots. Saved graphics to updateWDL.png. Total elapsed time = 46.92s. ``` Only affects displayed `cp` and `wdl` values. closes https://github.com/official-stockfish/Stockfish/pull/5939 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 4b8e8b7f508..500e881843c 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -513,8 +513,8 @@ WinRateParams win_rate_params(const Position& pos) { double m = std::clamp(material, 17, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-37.45051876, 121.19101539, -132.78783573, 420.70576692}; - constexpr double bs[] = {90.26261072, -137.26549898, 71.10130540, 51.35259597}; + constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; + constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From aafc732bcbfbf847e2ce15bb5371902c1801de36 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 29 Mar 2025 11:42:26 +0100 Subject: [PATCH 0949/1309] Silence "may be used uninitialized" GCC warning Helps gcc silence the warning about ``` warning: 'added' may be used uninitialized [-Wmaybe-uninitialized] const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; ``` closes https://github.com/official-stockfish/Stockfish/pull/5950 No functional change --- src/nnue/nnue_accumulator.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 0a3d95ad496..d693cc03129 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -25,13 +25,25 @@ #include "../bitboard.h" #include "../position.h" #include "../types.h" -#include "nnue_architecture.h" #include "network.h" +#include "nnue_architecture.h" #include "nnue_common.h" #include "nnue_feature_transformer.h" namespace Stockfish::Eval::NNUE { +#if defined(__GNUC__) && !defined(__clang__) + #define sf_assume(cond) \ + do \ + { \ + if (!(cond)) \ + __builtin_unreachable(); \ + } while (0) +#else + // do nothing for other compilers + #define sf_assume(cond) +#endif + namespace { template(&(computed.*accPtr).accumulation[Perspective][0]); From 03e27488f3d21d8ff4dbf3065603afa21dbd0ef3 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 29 Mar 2025 12:44:36 +0100 Subject: [PATCH 0950/1309] Stockfish 17.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 17.1 Bench: 2030154 --- Stockfish 17.1 Today, we have the pleasure to announce Stockfish 17.1. As always, you can **freely** download it at [stockfishchess.org/download][1] and use it in the [GUI of your choice][2]. Join our [Discord server][3] to get in touch with the community of developers and users of the project! Quality of chess play In our testing against its predecessor, Stockfish 17.1 shows a consistent improvement in performance, with an [Elo gain of up to 20 points][4] and winning close to 2 times more game pairs than it loses. Update highlights New speedtest command The new `speedtest` command benchmarks your computer's performance (measured in nodes per second) using a realistic and stable test. To run it, you'll need [command line access][5]—give it a try and share your results with the community! Improved hardware support Stockfish is [no longer limited to 1024 threads][6] and will allow users to specify whatever their system is capable of. Additionally, hardware such as ppc64 and Loongson is now better supported at build time. Bug fixes for tablebase support Our previous release introduced improved engine lines (principal variations) ending in mate as soon as a mate score is announced. A side effect of this improvement was a [rare corner case][7] involving cursed tablebase wins, only relevant in correspondence chess when the 50-move rule does not apply, which has now been fixed. Relatedly, [time management][8] has also been improved to avoid potential time losses. Shoutouts Download page redesign We've redesigned the [download page][1] to address unclear wording and simplify a previously cluttered experience. The page now features a modernized layout, streamlined navigation, and clearer guidance to help you select the right binary for your system. Fishtest framework Our testing framework has been improved in various ways, both on the worker side, including the adoption of [Fastchess][9] as a new game manager, and on the server side, such as streamlined configuration. The reliable availability of testing resources is key for the progress of the engine. Thank you The Stockfish project builds on a thriving community of enthusiasts (thanks everybody!) who contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We would like to express our gratitude for the [12k stars][10] that light up our GitHub project! Thank you for your support and encouragement – your recognition means a lot to us. We invite our chess fans to [join the Fishtest testing framework][11] to contribute compute resources needed for development. Programmers can contribute to the project either directly to [Stockfish][12] (C++), to [Fishtest][13] (HTML, CSS, JavaScript, and Python), to our trainer [nnue-pytorch][14] (C++ and Python), or to our [website][15] (HTML, CSS/SCSS, and JavaScript). The Stockfish team [1]: https://stockfishchess.org/download [2]: https://official-stockfish.github.io/docs/stockfish-wiki/Download-and-usage.html#download-a-chess-gui [3]: https://discord.gg/GWDRS3kU6R [4]: https://tests.stockfishchess.org/tests/view/67e7d2fd6682f97da2178fbd [5]: https://official-stockfish.github.io/docs/stockfish-wiki/UCI-&-Commands.html#speedtest [6]: https://github.com/official-stockfish/Stockfish/commit/652a8874b523360a3b19c5003c8ba9894ac54d0f [7]: https://github.com/official-stockfish/Stockfish/commit/6c7c5c7e471c16f14518229428e51a3e00c0f1dd [8]: https://github.com/official-stockfish/Stockfish/commit/0f9ae0d11cd034288a49ef3892c580dfed025091 [9]: https://github.com/Disservin/fastchess [10]: https://github.com/official-stockfish/Stockfish/stargazers [11]: https://official-stockfish.github.io/docs/fishtest-wiki/Running-the-Worker.html [12]: https://github.com/official-stockfish/Stockfish [13]: https://github.com/official-stockfish/fishtest [14]: https://github.com/official-stockfish/nnue-pytorch [15]: https://github.com/official-stockfish/stockfish-web --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index f85356c59f2..c5ac45f5cb5 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "dev"; +constexpr std::string_view version = "17.1"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 0475c8653f6d7d6940918120d3e994845906b6d1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 30 Mar 2025 14:47:31 +0200 Subject: [PATCH 0951/1309] Restore development closes https://github.com/official-stockfish/Stockfish/pull/5956 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index c5ac45f5cb5..f85356c59f2 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "17.1"; +constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From c2ff7a95c3ffee1c964735a0a4bd0e34cf76cab8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 21 Mar 2025 11:24:11 -0700 Subject: [PATCH 0952/1309] Cleanup fused updates Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 70656 W: 18257 L: 18077 D: 34322 Ptnml(0-2): 217, 7912, 18879, 8114, 206 https://tests.stockfishchess.org/tests/view/67e23ae78888403457d876d4 closes https://github.com/official-stockfish/Stockfish/pull/5941 No functional change --- src/nnue/nnue_accumulator.cpp | 280 +++++++++++----------------- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 54 ++++++ src/types.h | 9 + 4 files changed, 169 insertions(+), 176 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index d693cc03129..efa8df90533 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -21,8 +21,10 @@ #include #include #include +#include #include "../bitboard.h" +#include "../misc.h" #include "../position.h" #include "../types.h" #include "network.h" @@ -185,7 +187,7 @@ void AccumulatorStack::backward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = m_current_idx - 2; next >= end; next--) - update_accumulator_incremental( + update_accumulator_incremental( featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); assert((m_accumulators[end].*accPtr).computed[Perspective]); @@ -208,6 +210,67 @@ AccumulatorStack::evaluate, bool> = true> +void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const... rows) { + constexpr IndexType size = Width * sizeof(ElementType) / sizeof(typename VectorWrapper::type); + + auto* vecIn = reinterpret_cast(in); + auto* vecOut = reinterpret_cast(out); + + for (IndexType i = 0; i < size; ++i) + vecOut[i] = fused( + vecIn[i], reinterpret_cast(rows)[i]...); +} + +template AccumulatorState::*accPtr> +struct AccumulatorUpdateContext { + const FeatureTransformer& featureTransformer; + const AccumulatorState& from; + AccumulatorState& to; + + AccumulatorUpdateContext(const FeatureTransformer& ft, + const AccumulatorState& accF, + AccumulatorState& accT) noexcept : + featureTransformer{ft}, + from{accF}, + to{accT} {} + + template, bool> = true> + void apply(const Ts... indices) { + auto to_weight_vector = [&](const IndexType index) { + return &featureTransformer.weights[index * Dimensions]; + }; + + auto to_psqt_weight_vector = [&](const IndexType index) { + return &featureTransformer.psqtWeights[index * PSQTBuckets]; + }; + + fused_row_reduce((from.*accPtr).accumulation[Perspective], + (to.*accPtr).accumulation[Perspective], + to_weight_vector(indices)...); + + fused_row_reduce( + (from.*accPtr).psqtAccumulation[Perspective], (to.*accPtr).psqtAccumulation[Perspective], + to_psqt_weight_vector(indices)...); + } +}; + +template AccumulatorState::*accPtr> +auto make_accumulator_update_context( + const FeatureTransformer& featureTransformer, + const AccumulatorState& accumulatorFrom, + AccumulatorState& accumulatorTo) noexcept { + return AccumulatorUpdateContext{ + featureTransformer, accumulatorFrom, accumulatorTo}; +} + template(ksq, computed.dirtyPiece, added, removed); - if (removed.size() == 0 && added.size() == 0) - { - std::memcpy((target_state.*accPtr).accumulation[Perspective], - (computed.*accPtr).accumulation[Perspective], - TransformedFeatureDimensions * sizeof(BiasType)); - std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], - (computed.*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - } - else - { - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); - if (Forward) - assert(added.size() <= removed.size()); - else - assert(removed.size() <= added.size()); - - // Workaround compiler warning for uninitialized variables, replicated on - // profile builds on windows with gcc 14.2.0. - // TODO remove once unneeded - sf_assume(added.size() == 1 || added.size() == 2); - sf_assume(removed.size() == 1 || removed.size() == 2); - -#ifdef VECTOR - auto* accIn = - reinterpret_cast(&(computed.*accPtr).accumulation[Perspective][0]); - auto* accOut = - reinterpret_cast(&(target_state.*accPtr).accumulation[Perspective][0]); - - const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; - auto* columnA0 = reinterpret_cast(&featureTransformer.weights[offsetA0]); - const IndexType offsetR0 = TransformedFeatureDimensions * removed[0]; - auto* columnR0 = reinterpret_cast(&featureTransformer.weights[offsetR0]); - - if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) - { - assert(added.size() == 1 && removed.size() == 1); - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); - } - else if (Forward && added.size() == 1) - { - assert(removed.size() == 2); - const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), - vec_add_16(columnR0[i], columnR1[i])); - } - else if (Backwards && removed.size() == 1) - { - assert(added.size() == 2); - const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), - vec_sub_16(columnA1[i], columnR0[i])); - } - else - { - assert(added.size() == 2 && removed.size() == 2); - const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); - const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), - vec_add_16(columnR0[i], columnR1[i]))); - } - - auto* accPsqtIn = - reinterpret_cast(&(computed.*accPtr).psqtAccumulation[Perspective][0]); - auto* accPsqtOut = - reinterpret_cast(&(target_state.*accPtr).psqtAccumulation[Perspective][0]); + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); - const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; - auto* columnPsqtA0 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; - auto* columnPsqtR0 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR0]); + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1 || added.size() == 2); + sf_assume(removed.size() == 1 || removed.size() == 2); - if ((Forward && removed.size() == 1) - || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 - { - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = - vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); - } - else if (Forward && added.size() == 1) - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); - } - else if (Backwards && removed.size() == 1) - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); - } - else - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_add_psqt_32( - accPsqtIn[i], vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); - } -#else - std::memcpy((target_state.*accPtr).accumulation[Perspective], - (computed.*accPtr).accumulation[Perspective], - TransformedFeatureDimensions * sizeof(BiasType)); - std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], - (computed.*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = TransformedFeatureDimensions * index; - for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) - (target_state.*accPtr).accumulation[Perspective][i] -= - featureTransformer.weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (target_state.*accPtr).psqtAccumulation[Perspective][i] -= - featureTransformer.psqtWeights[index * PSQTBuckets + i]; - } + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = TransformedFeatureDimensions * index; - for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) - (target_state.*accPtr).accumulation[Perspective][i] += - featureTransformer.weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (target_state.*accPtr).psqtAccumulation[Perspective][i] += - featureTransformer.psqtWeights[index * PSQTBuckets + i]; - } -#endif + if ((Forward && removed.size() == 1) || (Backward && added.size() == 1)) + { + assert(added.size() == 1 && removed.size() == 1); + updateContext.template apply(added[0], removed[0]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + updateContext.template apply(added[0], removed[0], removed[1]); + } + else if (Backward && removed.size() == 1) + { + assert(added.size() == 2); + updateContext.template apply(added[0], added[1], removed[0]); + } + else + { + assert(added.size() == 2 && removed.size() == 2); + updateContext.template apply(added[0], added[1], removed[0], + removed[1]); } (target_state.*accPtr).computed[Perspective] = true; @@ -477,7 +407,7 @@ void update_accumulator_refresh_cache( auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); + acc[k] = fused(acc[k], columnA[k], columnR[k]); } if (combineLast3) { @@ -496,8 +426,8 @@ void update_accumulator_refresh_cache( reinterpret_cast(&featureTransformer.weights[offsetR2]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), - vec_add_16(columnR[k], columnR2[k])); + acc[k] = fused(acc[k], columnA[k], columnR[k], + columnR2[k]); } else { @@ -507,8 +437,8 @@ void update_accumulator_refresh_cache( reinterpret_cast(&featureTransformer.weights[offsetA2]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), - vec_add_16(columnA[k], columnA2[k])); + acc[k] = fused(acc[k], columnA[k], columnA2[k], + columnR[k]); } } else diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index b217c35839f..e6e3017d22e 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -281,7 +281,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size enum IncUpdateDirection { FORWARD, - BACKWARDS + BACKWARD }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 20e85be3c99..9dee29c19b2 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -143,6 +143,60 @@ using psqt_vec_t = int32x4_t; #endif +struct Vec16Wrapper { +#ifdef VECTOR + using type = vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); } +#else + using type = BiasType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +struct Vec32Wrapper { +#ifdef VECTOR + using type = psqt_vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); } +#else + using type = PSQTWeightType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +enum UpdateOperation { + Add, + Sub +}; + +template = true> +typename VecWrapper::type fused(const typename VecWrapper::type& in) { + return in; +} + +template, bool> = true, + std::enable_if_t = true> +typename VecWrapper::type +fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) { + switch (update_op) + { + case Add : + return fused(VecWrapper::add(in, operand), operands...); + case Sub : + return fused(VecWrapper::sub(in, operand), operands...); + } +} + // Returns the inverse of a permutation template constexpr std::array diff --git a/src/types.h b/src/types.h index 6465dfd6b4f..d6af929e5e3 100644 --- a/src/types.h +++ b/src/types.h @@ -38,6 +38,7 @@ #include #include + #include #if defined(_MSC_VER) // Disable some silly and noisy warnings from MSVC compiler @@ -429,6 +430,14 @@ class Move { std::uint16_t data; }; +template +struct is_all_same { + static constexpr bool value = (std::is_same_v && ...); +}; + +template +constexpr auto is_all_same_v = is_all_same::value; + } // namespace Stockfish #endif // #ifndef TYPES_H_INCLUDED From ee35a51c40eff7fb237f217d87d9ca7c73874924 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 26 Mar 2025 16:19:43 -0500 Subject: [PATCH 0953/1309] Remove extra division closes https://github.com/official-stockfish/Stockfish/pull/5943 No functional change --- src/search.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0c543c30895..8e8e935386f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1444,12 +1444,12 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (std::clamp(160 * (depth - 4) / 2, 0, 200) + 34 * !allNode - + 164 * ((ss - 1)->moveCount > 8) - + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) - + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) - + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) - + std::min(-(ss - 1)->statScore / 112, 303)); + int bonusScale = + (std::clamp(80 * depth - 320, 0, 200) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 112, 303)); bonusScale = std::max(bonusScale, 0); From dfef7e75209a6d14f16618dd6040c0898522d89d Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 28 Mar 2025 17:57:04 +0300 Subject: [PATCH 0954/1309] Remove redundant assignment closes https://github.com/official-stockfish/Stockfish/pull/5944 No functional change --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8e8e935386f..99bf47825cf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -298,14 +298,10 @@ void Search::Worker::iterative_deepening() { &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; (ss - i)->staticEval = VALUE_NONE; - (ss - i)->reduction = 0; } for (int i = 0; i <= MAX_PLY + 2; ++i) - { - (ss + i)->ply = i; - (ss + i)->reduction = 0; - } + (ss + i)->ply = i; ss->pv = pv; From d2cb927a04829e5bfa3e91ce3c1da327ed65d520 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 19 Mar 2025 17:06:11 -0700 Subject: [PATCH 0955/1309] Simplify TT cutoff conthist updates Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 86304 W: 22251 L: 22084 D: 41969 Ptnml(0-2): 250, 10214, 22123, 10249, 316 https://tests.stockfishchess.org/tests/view/67db60cd8c7f315cc372aae7 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 199158 W: 50655 L: 50617 D: 97886 Ptnml(0-2): 103, 21579, 56182, 21607, 108 https://tests.stockfishchess.org/tests/view/67dcdc5b8c7f315cc372ac12 closes https://github.com/official-stockfish/Stockfish/pull/5945 Bench: 2069191 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 99bf47825cf..ee4e895563a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -720,8 +720,7 @@ Value Search::Worker::search( // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(809 * (depth + 1) - 249, 3052)); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2200); } // Partial workaround for the graph history interaction problem From 1a395f1b565e4140a3f15bdd7b8add92fd11537a Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 28 Mar 2025 14:39:11 -0700 Subject: [PATCH 0956/1309] Remove pawn_attacks_bb() Generalize attacks_bb() to handle pawns and remove pawn_attacks_bb() https://tests.stockfishchess.org/tests/view/67e9496231d7cf8afdc44a2e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134688 W: 34660 L: 34553 D: 65475 Ptnml(0-2): 298, 14619, 37462, 14608, 357 closes https://github.com/official-stockfish/Stockfish/pull/5947 No functional change --- src/bitboard.cpp | 5 ++--- src/bitboard.h | 12 +++--------- src/movegen.cpp | 2 +- src/position.cpp | 18 +++++++++--------- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 8798c5701d5..9b1d674c097 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -32,7 +32,6 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; alignas(64) Magic Magics[SQUARE_NB][2]; @@ -86,8 +85,8 @@ void Bitboards::init() { for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { - PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + PseudoAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PseudoAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) PseudoAttacks[KING][s1] |= safe_destination(s1, step); diff --git a/src/bitboard.h b/src/bitboard.h index df15113bda3..941299c0dcc 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -62,7 +62,6 @@ extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square @@ -155,11 +154,6 @@ constexpr Bitboard pawn_attacks_bb(Bitboard b) { : shift(b) | shift(b); } -inline Bitboard pawn_attacks_bb(Color c, Square s) { - - assert(is_ok(s)); - return PawnAttacks[c][s]; -} // Returns a bitboard representing an entire line (from board edge // to board edge) that intersects the two given squares. If the given squares @@ -216,10 +210,10 @@ inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } // Returns the pseudo attacks of the given piece type // assuming an empty board. template -inline Bitboard attacks_bb(Square s) { +inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) { - assert((Pt != PAWN) && (is_ok(s))); - return PseudoAttacks[Pt][s]; + assert((Pt != PAWN || c < COLOR_NB) && (is_ok(s))); + return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s]; } diff --git a/src/movegen.cpp b/src/movegen.cpp index 8653a828fde..a73bd8501de 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -134,7 +134,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta if (Type == EVASIONS && (target & (pos.ep_square() + Up))) return moveList; - b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); + b1 = pawnsNotOn7 & attacks_bb(pos.ep_square(), Them); assert(b1); diff --git a/src/position.cpp b/src/position.cpp index 52e1004e8fc..02fd6c7a91e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -270,7 +270,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { // a) side to move have a pawn threatening epSquare // b) there is an enemy pawn in front of epSquare // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + enpassant = attacks_bb(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN) && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); } @@ -321,7 +321,7 @@ void Position::set_check_info() const { Square ksq = square(~sideToMove); - st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[PAWN] = attacks_bb(ksq, ~sideToMove); st->checkSquares[KNIGHT] = attacks_bb(ksq); st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); @@ -487,8 +487,8 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) + | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING)); } @@ -498,7 +498,7 @@ bool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const { && (attacks_bb(s, occupied) & pieces(c, ROOK, QUEEN))) || ((attacks_bb(s) & pieces(c, BISHOP, QUEEN)) && (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN))) - || (((pawn_attacks_bb(~c, s) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) + || (((attacks_bb(s, ~c) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING))) & pieces(c)); } @@ -597,9 +597,9 @@ bool Position::pseudo_legal(const Move m) const { if ((Rank8BB | Rank1BB) & to) return false; - if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !((from + 2 * pawn_push(us) == to) // Not a double push + if (!(attacks_bb(from, us) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !((from + 2 * pawn_push(us) == to) // Not a double push && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) return false; } @@ -812,7 +812,7 @@ DirtyPiece Position::do_move(Move m, { // Set en passant square if the moved pawn can be captured if ((int(to) ^ int(from)) == 16 - && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + && (attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN))) { st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; From ed89817f626d4abad0b2e90d8ccd29f68377dff2 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 28 Mar 2025 18:09:38 -0700 Subject: [PATCH 0957/1309] Various cleanups Various simplifications, cleanups, consistancy improvements, and warning mitigations. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/67e7dd2d6682f97da2178fd8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 386848 W: 99593 L: 99751 D: 187504 Ptnml(0-2): 1024, 41822, 107973, 41498, 1107 closes https://github.com/official-stockfish/Stockfish/pull/5948 No functional change --- src/bitboard.h | 18 +++++++++--------- src/evaluate.cpp | 10 ++++------ src/evaluate.h | 2 +- src/movepick.cpp | 2 +- src/nnue/layers/affine_transform.h | 4 ---- src/position.cpp | 7 +++---- src/search.cpp | 17 ++++++++--------- src/types.h | 14 +++++++------- 8 files changed, 33 insertions(+), 41 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 941299c0dcc..f959bcb860c 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -102,17 +102,17 @@ constexpr Bitboard square_bb(Square s) { // Overloads of bitwise operators between a Bitboard and a Square for testing // whether a given bit is set in a bitboard, and for setting and clearing bits. -inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } -inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } -inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } -inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } -inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } +constexpr Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } +constexpr Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } +constexpr Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } +constexpr Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } +constexpr Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } -inline Bitboard operator&(Square s, Bitboard b) { return b & s; } -inline Bitboard operator|(Square s, Bitboard b) { return b | s; } -inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } +constexpr Bitboard operator&(Square s, Bitboard b) { return b & s; } +constexpr Bitboard operator|(Square s, Bitboard b) { return b | s; } +constexpr Bitboard operator^(Square s, Bitboard b) { return b ^ s; } -inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } +constexpr Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } diff --git a/src/evaluate.cpp b/src/evaluate.cpp index ccb089d9777..92b03af370c 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -38,17 +38,15 @@ namespace Stockfish { // Returns a static, purely materialistic evaluation of the position from -// the point of view of the given color. It can be divided by PawnValue to get +// the point of view of the side to move. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. -int Eval::simple_eval(const Position& pos, Color c) { +int Eval::simple_eval(const Position& pos) { + Color c = pos.side_to_move(); return PawnValue * (pos.count(c) - pos.count(~c)) + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } -bool Eval::use_smallnet(const Position& pos) { - int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 962; -} +bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; } // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. diff --git a/src/evaluate.h b/src/evaluate.h index 07b914007ea..2a6c9afa90e 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -44,7 +44,7 @@ class AccumulatorStack; std::string trace(Position& pos, const Eval::NNUE::Networks& networks); -int simple_eval(const Position& pos, Color c); +int simple_eval(const Position& pos); bool use_smallnet(const Position& pos); Value evaluate(const NNUE::Networks& networks, const Position& pos, diff --git a/src/movepick.cpp b/src/movepick.cpp index c762e7e453c..6ee5fad7c4d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -176,7 +176,7 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 + m.value -= (pt == QUEEN && bool(to & threatenedByRook) ? 49000 : pt == ROOK && bool(to & threatenedByMinor) ? 24335 : 0); diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index dac727e23bc..3920efb1722 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -245,19 +245,16 @@ class AffineTransform { #if defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero() _mm256_setzero_si256() - #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero() _mm_setzero_si128() - #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 #define vec_hadd Simd::m128_hadd #elif defined(USE_NEON_DOTPROD) using vec_t = int32x4_t; #define vec_setzero() vdupq_n_s32(0) - #define vec_set_32 vdupq_n_s32 #define vec_add_dpbusd_32(acc, a, b) \ Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ vreinterpretq_s8_s32(b)) @@ -282,7 +279,6 @@ class AffineTransform { output[0] = vec_hadd(sum0, biases[0]); #undef vec_setzero - #undef vec_set_32 #undef vec_add_dpbusd_32 #undef vec_hadd } diff --git a/src/position.cpp b/src/position.cpp index 02fd6c7a91e..0e5748e6a2e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -54,8 +54,8 @@ namespace { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; +static constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; } // namespace @@ -733,8 +733,7 @@ DirtyPiece Position::do_move(Move m, st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; captured = NO_PIECE; } - - if (captured) + else if (captured) { Square capsq = to; diff --git a/src/search.cpp b/src/search.cpp index ee4e895563a..10e5047f13f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -492,7 +492,8 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - int nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); + uint64_t nodesEffort = + rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); double fallingEval = (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) @@ -929,10 +930,7 @@ Value Search::Worker::search( { assert(move.is_ok()); - if (move == excludedMove) - continue; - - if (!pos.legal(move)) + if (move == excludedMove || !pos.legal(move)) continue; assert(pos.capture_stage(move)); @@ -1572,12 +1570,13 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return ttData.value; // Step 4. Static evaluation of the position - Value unadjustedStaticEval = VALUE_NONE; - const auto correctionValue = correction_value(*thisThread, pos, ss); + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; else { + const auto correctionValue = correction_value(*thisThread, pos, ss); + if (ss->ttHit) { // Never assume anything about values stored in TT @@ -1930,8 +1929,8 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = (weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) + int push = int(weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) / 128; if (rootMoves[i].score + push >= maxScore) diff --git a/src/types.h b/src/types.h index d6af929e5e3..5f9cb421e1e 100644 --- a/src/types.h +++ b/src/types.h @@ -290,8 +290,8 @@ struct DirtyPiece { }; #define ENABLE_INCR_OPERATORS_ON(T) \ - inline T& operator++(T& d) { return d = T(int(d) + 1); } \ - inline T& operator--(T& d) { return d = T(int(d) - 1); } + constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \ + constexpr T& operator--(T& d) { return d = T(int(d) - 1); } ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) @@ -304,10 +304,10 @@ constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } // Additional operators to add a Direction to a Square -constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } -constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } -inline Square& operator+=(Square& s, Direction d) { return s = s + d; } -inline Square& operator-=(Square& s, Direction d) { return s = s - d; } +constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +constexpr Square& operator+=(Square& s, Direction d) { return s = s + d; } +constexpr Square& operator-=(Square& s, Direction d) { return s = s - d; } // Toggle color constexpr Color operator~(Color c) { return Color(c ^ BLACK); } @@ -335,7 +335,7 @@ constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } -inline Color color_of(Piece pc) { +constexpr Color color_of(Piece pc) { assert(pc != NO_PIECE); return Color(pc >> 3); } From 3d61f932cbdbcca8f4a5f20459e706cfa2415648 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 29 Mar 2025 15:34:48 -0400 Subject: [PATCH 0958/1309] Squash out post-lmr bonus variable Squash out bonus variable for post-lmr now that the bonus is constant. closes https://github.com/official-stockfish/Stockfish/pull/5953 No functional change --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 10e5047f13f..7a1a36100c7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1271,8 +1271,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = 1600; - update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); + update_continuation_histories(ss, movedPiece, move.to_sq(), 1600); } else if (value > alpha && value < bestValue + 9) newDepth--; From d942e13398aa5de55224c7d81bfad6b0f5b9e488 Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Mon, 31 Mar 2025 18:27:36 +0200 Subject: [PATCH 0959/1309] Less fail high cnt in the condition Passed STC: https://tests.stockfishchess.org/tests/view/67e027538888403457d87535 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 164000 W: 42535 L: 42034 D: 79431 Ptnml(0-2): 478, 19228, 42113, 19677, 504 Passed LTC: https://tests.stockfishchess.org/tests/view/67e3c21b8888403457d87808 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 139176 W: 35500 L: 34975 D: 68701 Ptnml(0-2): 54, 15038, 38898, 15525, 73 closes https://github.com/official-stockfish/Stockfish/pull/5960 Bench: 1921404 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index a345bb69f1b..092980fe593 100644 --- a/AUTHORS +++ b/AUTHORS @@ -58,6 +58,7 @@ Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) Daniel Monroe (Ergodice) Dan Schmidt (dfannius) +DanSamek Dariusz Orzechowski (dorzechowski) David (dav1312) David Zar diff --git a/src/search.cpp b/src/search.cpp index 7a1a36100c7..5f5934a4997 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1215,7 +1215,7 @@ Value Search::Worker::search( r += 1171 + (depth < 8) * 985; // Increase reduction if next ply has a lot of fail high - if ((ss + 1)->cutoffCnt > 3) + if ((ss + 1)->cutoffCnt > 2) r += 1042 + allNode * 864; // For first picked move (ttMove) reduce reduction From d7c04a942950f1fe3f655bf8b608e8ef21c07628 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 31 Mar 2025 21:15:56 -0700 Subject: [PATCH 0960/1309] Introduce TT Move History Double Extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passed VVLTC w/ STC bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 74918 W: 19436 L: 19120 D: 36362 Ptnml(0-2): 6, 6890, 23354, 7200, 9 https://tests.stockfishchess.org/tests/view/67e4a1088888403457d878bb Passed VVLTC w/ LTC bounds: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 111706 W: 29050 L: 28619 D: 54037 Ptnml(0-2): 13, 10218, 34959, 10651, 12 https://tests.stockfishchess.org/tests/view/67d6877c517865b4a2dfd36b STC Elo Estimate: Elo: 1.26 ± 2.0 (95%) LOS: 88.8% Total: 30000 W: 7855 L: 7746 D: 14399 Ptnml(0-2): 104, 3531, 7630, 3622, 113 nElo: 2.44 ± 3.9 (95%) PairsRatio: 1.03 https://tests.stockfishchess.org/tests/view/67eacb8c31d7cf8afdc44b99 closes https://github.com/official-stockfish/Stockfish/pull/5961 Bench: 1887897 --- src/history.h | 2 ++ src/search.cpp | 12 +++++++++++- src/search.h | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/history.h b/src/history.h index ec245230a76..f2ef7661f80 100644 --- a/src/history.h +++ b/src/history.h @@ -166,6 +166,8 @@ struct CorrHistTypedef { template using CorrectionHistory = typename Detail::CorrHistTypedef::type; +using TTMoveHistory = Stats; + } // namespace Stockfish #endif // #ifndef HISTORY_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 5f5934a4997..291b99bd193 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -580,6 +580,8 @@ void Search::Worker::clear() { minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory.fill(0); + ttMoveHistory.fill(0); + for (auto& to : continuationCorrectionHistory) for (auto& h : to) h.fill(5); @@ -1139,7 +1141,8 @@ Value Search::Worker::search( { int corrValAdj1 = std::abs(correctionValue) / 248873; int corrValAdj2 = std::abs(correctionValue) / 255331; - int doubleMargin = 262 * PvNode - 188 * !ttCapture - corrValAdj1; + int doubleMargin = 262 * PvNode - 188 * !ttCapture - corrValAdj1 + - ttMoveHistory[pawn_structure_index(pos)][us] / 128; int tripleMargin = 88 + 265 * PvNode - 256 * !ttCapture + 93 * ss->ttPv - corrValAdj2; @@ -1430,8 +1433,15 @@ Value Search::Worker::search( // If there is a move that produces search value greater than alpha, // we update the stats of searched moves. else if (bestMove) + { update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, bestMove == ttData.move, moveCount); + if (!PvNode) + { + int bonus = (ttData.move == move) ? 800 : -600 * moveCount; + ttMoveHistory[pawn_structure_index(pos)][us] << bonus; + } + } // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) diff --git a/src/search.h b/src/search.h index 071773f81b6..6ef8322a859 100644 --- a/src/search.h +++ b/src/search.h @@ -292,6 +292,8 @@ class Worker { CorrectionHistory nonPawnCorrectionHistory; CorrectionHistory continuationCorrectionHistory; + TTMoveHistory ttMoveHistory; + private: void iterative_deepening(); From 7beff18ef0b131277cdd695fc01c0632a47c8540 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 25 Mar 2025 22:21:48 +0100 Subject: [PATCH 0961/1309] Retire Acc Pointer Since @xu-shawn's refactor the acc pointer has become a bit unnecessary, we can achieve the same behavior by using the dimension size to get the right accumulator from the `AccumulatorState`. I think the acc pointer has become a bit of a burden required to be passed through multiple different functions together with the necessary templating required. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/67ed600f31d7cf8afdc45183 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 279744 W: 72037 L: 72082 D: 135625 Ptnml(0-2): 673, 29918, 78767, 29809, 705 closes https://github.com/official-stockfish/Stockfish/pull/5942 No functional change --- src/nnue/network.cpp | 22 ++-- src/nnue/network.h | 6 +- src/nnue/nnue_accumulator.cpp | 174 +++++++++++++--------------- src/nnue/nnue_accumulator.h | 73 +++++++----- src/nnue/nnue_feature_transformer.h | 7 +- 5 files changed, 136 insertions(+), 146 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index cba3abc6301..e23294e4fa1 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -219,13 +219,13 @@ Network::evaluate(const Position& pos #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); @@ -290,13 +290,13 @@ Network::trace_evaluate(const Position& #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize + transformedFeaturesUnaligned[FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); #else - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); @@ -452,12 +452,10 @@ bool Network::write_parameters(std::ostream& stream, // Explicit template instantiations -template class Network< - NetworkArchitecture, - FeatureTransformer>; +template class Network, + FeatureTransformer>; -template class Network< - NetworkArchitecture, - FeatureTransformer>; +template class Network, + FeatureTransformer>; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h index 21df4b0a14e..cd32c5312fc 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -110,13 +110,11 @@ class Network { }; // Definitions of the network types -using SmallFeatureTransformer = - FeatureTransformer; +using SmallFeatureTransformer = FeatureTransformer; using SmallNetworkArchitecture = NetworkArchitecture; -using BigFeatureTransformer = - FeatureTransformer; +using BigFeatureTransformer = FeatureTransformer; using BigNetworkArchitecture = NetworkArchitecture; using NetworkBig = Network; diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index efa8df90533..37af7a0fa12 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -48,22 +48,20 @@ namespace Stockfish::Eval::NNUE { namespace { -template AccumulatorState::*accPtr> +template void update_accumulator_incremental( - const FeatureTransformer& featureTransformer, - const Square ksq, - AccumulatorState& target_state, - const AccumulatorState& computed); - -template AccumulatorState::*accPtr> -void update_accumulator_refresh_cache( - const FeatureTransformer& featureTransformer, - const Position& pos, - AccumulatorState& accumulatorState, - AccumulatorCaches::Cache& cache); + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed); + +template +void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache); } @@ -86,18 +84,14 @@ void AccumulatorStack::reset(const Position& rootPos, AccumulatorCaches& caches) noexcept { m_current_idx = 1; - update_accumulator_refresh_cache( + update_accumulator_refresh_cache( *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); - update_accumulator_refresh_cache( + update_accumulator_refresh_cache( *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); - update_accumulator_refresh_cache( + update_accumulator_refresh_cache( *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); - update_accumulator_refresh_cache( + update_accumulator_refresh_cache( *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); } @@ -112,24 +106,23 @@ void AccumulatorStack::pop() noexcept { m_current_idx--; } -template AccumulatorState::*accPtr> -void AccumulatorStack::evaluate(const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept { +template +void AccumulatorStack::evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { evaluate_side(pos, featureTransformer, cache); evaluate_side(pos, featureTransformer, cache); } -template AccumulatorState::*accPtr> -void AccumulatorStack::evaluate_side( - const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept { +template +void AccumulatorStack::evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { - const auto last_usable_accum = find_last_usable_accumulator(); + const auto last_usable_accum = find_last_usable_accumulator(); - if ((m_accumulators[last_usable_accum].*accPtr).computed[Perspective]) + if ((m_accumulators[last_usable_accum].template acc()).computed[Perspective]) forward_update_incremental(pos, featureTransformer, last_usable_accum); else @@ -141,12 +134,12 @@ void AccumulatorStack::evaluate_side( // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator // state just before a change that requires full refresh. -template AccumulatorState::*accPtr> +template std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { for (std::size_t curr_idx = m_current_idx - 1; curr_idx > 0; curr_idx--) { - if ((m_accumulators[curr_idx].*accPtr).computed[Perspective]) + if ((m_accumulators[curr_idx].template acc()).computed[Perspective]) return curr_idx; if (FeatureSet::requires_refresh(m_accumulators[curr_idx].dirtyPiece, Perspective)) @@ -156,14 +149,14 @@ std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { return 0; } -template AccumulatorState::*accPtr> +template void AccumulatorStack::forward_update_incremental( - const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t begin) noexcept { + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept { assert(begin < m_accumulators.size()); - assert((m_accumulators[begin].*accPtr).computed[Perspective]); + assert((m_accumulators[begin].acc()).computed[Perspective]); const Square ksq = pos.square(Perspective); @@ -171,18 +164,18 @@ void AccumulatorStack::forward_update_incremental( update_accumulator_incremental(featureTransformer, ksq, m_accumulators[next], m_accumulators[next - 1]); - assert((latest().*accPtr).computed[Perspective]); + assert((latest().acc()).computed[Perspective]); } -template AccumulatorState::*accPtr> +template void AccumulatorStack::backward_update_incremental( - const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t end) noexcept { + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept { assert(end < m_accumulators.size()); assert(end < m_current_idx); - assert((latest().*accPtr).computed[Perspective]); + assert((latest().acc()).computed[Perspective]); const Square ksq = pos.square(Perspective); @@ -190,21 +183,17 @@ void AccumulatorStack::backward_update_incremental( update_accumulator_incremental( featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); - assert((m_accumulators[end].*accPtr).computed[Perspective]); + assert((m_accumulators[end].acc()).computed[Perspective]); } // Explicit template instantiations -template void -AccumulatorStack::evaluate( - const Position& pos, - const FeatureTransformer& - featureTransformer, +template void AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; -template void -AccumulatorStack::evaluate( - const Position& pos, - const FeatureTransformer& - featureTransformer, +template void AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; @@ -227,15 +216,15 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const.. vecIn[i], reinterpret_cast(rows)[i]...); } -template AccumulatorState::*accPtr> +template struct AccumulatorUpdateContext { - const FeatureTransformer& featureTransformer; - const AccumulatorState& from; - AccumulatorState& to; + const FeatureTransformer& featureTransformer; + const AccumulatorState& from; + AccumulatorState& to; - AccumulatorUpdateContext(const FeatureTransformer& ft, - const AccumulatorState& accF, - AccumulatorState& accT) noexcept : + AccumulatorUpdateContext(const FeatureTransformer& ft, + const AccumulatorState& accF, + AccumulatorState& accT) noexcept : featureTransformer{ft}, from{accF}, to{accT} {} @@ -252,41 +241,37 @@ struct AccumulatorUpdateContext { return &featureTransformer.psqtWeights[index * PSQTBuckets]; }; - fused_row_reduce((from.*accPtr).accumulation[Perspective], - (to.*accPtr).accumulation[Perspective], - to_weight_vector(indices)...); + fused_row_reduce( + (from.acc()).accumulation[Perspective], + (to.acc()).accumulation[Perspective], to_weight_vector(indices)...); fused_row_reduce( - (from.*accPtr).psqtAccumulation[Perspective], (to.*accPtr).psqtAccumulation[Perspective], - to_psqt_weight_vector(indices)...); + (from.acc()).psqtAccumulation[Perspective], + (to.acc()).psqtAccumulation[Perspective], to_psqt_weight_vector(indices)...); } }; -template AccumulatorState::*accPtr> -auto make_accumulator_update_context( - const FeatureTransformer& featureTransformer, - const AccumulatorState& accumulatorFrom, - AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - featureTransformer, accumulatorFrom, accumulatorTo}; +template +auto make_accumulator_update_context(const FeatureTransformer& featureTransformer, + const AccumulatorState& accumulatorFrom, + AccumulatorState& accumulatorTo) noexcept { + return AccumulatorUpdateContext{featureTransformer, accumulatorFrom, + accumulatorTo}; } -template AccumulatorState::*accPtr> +template void update_accumulator_incremental( - const FeatureTransformer& featureTransformer, - const Square ksq, - AccumulatorState& target_state, - const AccumulatorState& computed) { + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed) { [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; [[maybe_unused]] constexpr bool Backward = Direction == BACKWARD; assert(Forward != Backward); - assert((computed.*accPtr).computed[Perspective]); - assert(!(target_state.*accPtr).computed[Perspective]); + assert((computed.acc()).computed[Perspective]); + assert(!(target_state.acc()).computed[Perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -340,15 +325,14 @@ void update_accumulator_incremental( removed[1]); } - (target_state.*accPtr).computed[Perspective] = true; + (target_state.acc()).computed[Perspective] = true; } -template AccumulatorState::*accPtr> -void update_accumulator_refresh_cache( - const FeatureTransformer& featureTransformer, - const Position& pos, - AccumulatorState& accumulatorState, - AccumulatorCaches::Cache& cache) { +template +void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache) { using Tiling [[maybe_unused]] = SIMDTiling; const Square ksq = pos.square(Perspective); @@ -378,7 +362,7 @@ void update_accumulator_refresh_cache( } } - auto& accumulator = accumulatorState.*accPtr; + auto& accumulator = accumulatorState.acc(); accumulator.computed[Perspective] = true; #ifdef VECTOR diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 362ea83e30a..d83a5a4464b 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -46,10 +46,7 @@ struct Networks; template struct alignas(CacheLineSize) Accumulator; -struct AccumulatorState; - -template AccumulatorState::*accPtr> +template class FeatureTransformer; // Class that holds the result of affine transformation of input features @@ -121,6 +118,30 @@ struct AccumulatorState { Accumulator accumulatorSmall; DirtyPiece dirtyPiece; + template + auto& acc() noexcept { + static_assert(Size == TransformedFeatureDimensionsBig + || Size == TransformedFeatureDimensionsSmall, + "Invalid size for accumulator"); + + if constexpr (Size == TransformedFeatureDimensionsBig) + return accumulatorBig; + else if constexpr (Size == TransformedFeatureDimensionsSmall) + return accumulatorSmall; + } + + template + const auto& acc() const noexcept { + static_assert(Size == TransformedFeatureDimensionsBig + || Size == TransformedFeatureDimensionsSmall, + "Invalid size for accumulator"); + + if constexpr (Size == TransformedFeatureDimensionsBig) + return accumulatorBig; + else if constexpr (Size == TransformedFeatureDimensionsSmall) + return accumulatorSmall; + } + void reset(const DirtyPiece& dp) noexcept; }; @@ -138,41 +159,31 @@ class AccumulatorStack { void push(const DirtyPiece& dirtyPiece) noexcept; void pop() noexcept; - template AccumulatorState::*accPtr> - void evaluate(const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept; + template + void evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; private: [[nodiscard]] AccumulatorState& mut_latest() noexcept; - template AccumulatorState::*accPtr> - void evaluate_side(const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept; + template + void evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; - template AccumulatorState::*accPtr> + template [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; - template AccumulatorState::*accPtr> - void - forward_update_incremental(const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t begin) noexcept; + template + void forward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept; - template AccumulatorState::*accPtr> - void - backward_update_incremental(const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t end) noexcept; + template + void backward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept; std::vector m_accumulators; std::size_t m_current_idx; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 9dee29c19b2..d2abd40fa09 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -294,8 +294,7 @@ class SIMDTiling { // Input feature converter -template AccumulatorState::*accPtr> +template class FeatureTransformer { // Number of output dimensions for one side @@ -400,12 +399,12 @@ class FeatureTransformer { const auto& accumulatorState = accumulatorStack.latest(); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& psqtAccumulation = (accumulatorState.*accPtr).psqtAccumulation; + const auto& psqtAccumulation = (accumulatorState.acc()).psqtAccumulation; const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; - const auto& accumulation = (accumulatorState.*accPtr).accumulation; + const auto& accumulation = (accumulatorState.acc()).accumulation; for (IndexType p = 0; p < 2; ++p) { From 15f34560f2adf955cac7ae5a19d6c7ffdf36e4a3 Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Wed, 2 Apr 2025 18:40:56 +0200 Subject: [PATCH 0962/1309] Update AUTHORS closes https://github.com/official-stockfish/Stockfish/pull/5964 No functional change --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 092980fe593..dadff1422a8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -57,8 +57,8 @@ Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) Daniel Monroe (Ergodice) +Daniel Samek (DanSamek) Dan Schmidt (dfannius) -DanSamek Dariusz Orzechowski (dorzechowski) David (dav1312) David Zar From fb6a3e04ec04a500d4b7c64158e5a74c2196e7ca Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 27 Mar 2025 19:16:45 -0400 Subject: [PATCH 0963/1309] Simply use non_pawn_material rather than summing tuned terms Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 136576 W: 35285 L: 35175 D: 66116 Ptnml(0-2): 410, 16179, 34997, 16295, 407 https://tests.stockfishchess.org/tests/view/67e5dc736682f97da2178da6 Passed rebased simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 85482 W: 21812 L: 21658 D: 42012 Ptnml(0-2): 34, 9260, 24022, 9368, 57 https://tests.stockfishchess.org/tests/view/67e852cb31d7cf8afdc44966 closes https://github.com/official-stockfish/Stockfish/pull/5965 Bench: 2006483 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 291b99bd193..1c362288409 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -102,9 +102,7 @@ int risk_tolerance(const Position& pos, Value v) { return 644800 * x / ((x * x + 3 * y * y) * y); }; - int m = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() - + 337 * pos.count() + 553 * pos.count()) - / 64; + int m = pos.count() + pos.non_pawn_material() / 300; // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) From 1577fa04702b9a2a2c4ed9c4be0cfbae38b64cf0 Mon Sep 17 00:00:00 2001 From: mstembera Date: Wed, 2 Apr 2025 21:38:15 -0700 Subject: [PATCH 0964/1309] Simplify Forward and Backward Forward and Backward are not independent so simplify to a bool. Cleanup some MSVC warnings like "warning C4267: 'argument': conversion from 'size_t' to 'int', possible loss of data" Other minor formatting stuff. This is a rebase of https://github.com/official-stockfish/Stockfish/pull/5912 closes https://github.com/official-stockfish/Stockfish/pull/5967 No functional change --- src/nnue/nnue_accumulator.cpp | 40 +++++++++++++---------------------- src/nnue/nnue_common.h | 5 ----- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 37af7a0fa12..8b2585b9182 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -29,7 +29,6 @@ #include "../types.h" #include "network.h" #include "nnue_architecture.h" -#include "nnue_common.h" #include "nnue_feature_transformer.h" namespace Stockfish::Eval::NNUE { @@ -48,9 +47,7 @@ namespace Stockfish::Eval::NNUE { namespace { -template +template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, const Square ksq, @@ -161,8 +158,8 @@ void AccumulatorStack::forward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = begin + 1; next < m_current_idx; next++) - update_accumulator_incremental(featureTransformer, ksq, m_accumulators[next], - m_accumulators[next - 1]); + update_accumulator_incremental( + featureTransformer, ksq, m_accumulators[next], m_accumulators[next - 1]); assert((latest().acc()).computed[Perspective]); } @@ -180,7 +177,7 @@ void AccumulatorStack::backward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = m_current_idx - 2; next >= end; next--) - update_accumulator_incremental( + update_accumulator_incremental( featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); assert((m_accumulators[end].acc()).computed[Perspective]); @@ -259,16 +256,12 @@ auto make_accumulator_update_context(const FeatureTransformer& featu accumulatorTo}; } -template +template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed) { - [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; - [[maybe_unused]] constexpr bool Backward = Direction == BACKWARD; - - assert(Forward != Backward); assert((computed.acc()).computed[Perspective]); assert(!(target_state.acc()).computed[Perspective]); @@ -288,11 +281,8 @@ void update_accumulator_incremental( assert(added.size() == 1 || added.size() == 2); assert(removed.size() == 1 || removed.size() == 2); - - if (Forward) - assert(added.size() <= removed.size()); - else - assert(removed.size() <= added.size()); + assert((Forward && added.size() <= removed.size()) + || (!Forward && added.size() >= removed.size())); // Workaround compiler warning for uninitialized variables, replicated on // profile builds on windows with gcc 14.2.0. @@ -303,7 +293,7 @@ void update_accumulator_incremental( auto updateContext = make_accumulator_update_context(featureTransformer, computed, target_state); - if ((Forward && removed.size() == 1) || (Backward && added.size() == 1)) + if ((Forward && removed.size() == 1) || (!Forward && added.size() == 1)) { assert(added.size() == 1 && removed.size() == 1); updateContext.template apply(added[0], removed[0]); @@ -313,7 +303,7 @@ void update_accumulator_incremental( assert(removed.size() == 2); updateContext.template apply(added[0], removed[0], removed[1]); } - else if (Backward && removed.size() == 1) + else if (!Forward && removed.size() == 1) { assert(added.size() == 2); updateContext.template apply(added[0], added[1], removed[0]); @@ -380,7 +370,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = entryTile[k]; - std::size_t i = 0; + IndexType i = 0; for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) { IndexType indexR = removed[i]; @@ -460,10 +450,10 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat auto* entryTilePsqt = reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; - for (std::size_t i = 0; i < removed.size(); ++i) + for (IndexType i = 0; i < removed.size(); ++i) { IndexType index = removed[i]; const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; @@ -473,7 +463,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } - for (std::size_t i = 0; i < added.size(); ++i) + for (IndexType i = 0; i < added.size(); ++i) { IndexType index = added[i]; const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; @@ -484,9 +474,9 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) vec_store_psqt(&entryTilePsqt[k], psqt[k]); - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index e6e3017d22e..f21a8dec7df 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -279,11 +279,6 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size flush(); } -enum IncUpdateDirection { - FORWARD, - BACKWARD -}; - } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED From bb3eaf8defec018ae932ec24b6854966c8f83701 Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 3 Apr 2025 20:10:17 +0200 Subject: [PATCH 0965/1309] Add cstddef header Fix missing header for std::size_t reported on discord. closes https://github.com/official-stockfish/Stockfish/pull/5968 No functional change --- src/types.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types.h b/src/types.h index 5f9cb421e1e..d7f9dfb7445 100644 --- a/src/types.h +++ b/src/types.h @@ -37,6 +37,7 @@ // | only in 64-bit mode and requires hardware with pext support. #include + #include #include #include From cf8b3637a0fe3a026a338cb9215283baffd3ded2 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 4 Apr 2025 03:58:02 +0300 Subject: [PATCH 0966/1309] Improve futility pruning Adding a small term to the futility calculation that depends on eval - beta. Refactored to a simpler form. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 74176 W: 19323 L: 18954 D: 35899 Ptnml(0-2): 226, 8576, 19117, 8941, 228 https://tests.stockfishchess.org/tests/view/67e6b0946682f97da2178eaf Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 135090 W: 34499 L: 33983 D: 66608 Ptnml(0-2): 79, 14403, 38040, 14969, 54 https://tests.stockfishchess.org/tests/view/67e757cc6682f97da2178f62 closes https://github.com/official-stockfish/Stockfish/pull/5970 Bench: 1875196 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1c362288409..7b8fbb144a6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -855,7 +855,8 @@ Value Search::Worker::search( // 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 / 301 + 37 - std::abs(correctionValue) / 139878 + - (ss - 1)->statScore / 301 + 37 + ((eval - beta) / 8) + - std::abs(correctionValue) / 139878 >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; From 2af64d581b91ace5a299ebfadf4a9558aa637d02 Mon Sep 17 00:00:00 2001 From: AliceRoselia <63040919+AliceRoselia@users.noreply.github.com> Date: Fri, 4 Apr 2025 08:12:55 +0700 Subject: [PATCH 0967/1309] Update AUTHORS closes https://github.com/official-stockfish/Stockfish/pull/5972 No functional change --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index dadff1422a8..54c5f8a2124 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,6 +20,7 @@ Alexander Kure Alexander Pagel (Lolligerhans) Alfredo Menezes (lonfom169) Ali AlZhrani (Cooffe) +AliceRoselia Andreas Jan van der Meulen (Andyson007) Andreas Matthies (Matthies) Andrei Vetrov (proukornew) From 8d2eef2b1e0075b70bf6afa74fddb01c4b5e48f1 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 3 Apr 2025 19:50:23 -0700 Subject: [PATCH 0968/1309] Fix fused() all controll paths should return a value. closes https://github.com/official-stockfish/Stockfish/pull/5973 No functional change --- src/nnue/nnue_feature_transformer.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index d2abd40fa09..b9b422a65a7 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -194,6 +194,10 @@ fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operan return fused(VecWrapper::add(in, operand), operands...); case Sub : return fused(VecWrapper::sub(in, operand), operands...); + default : + static_assert(update_op == Add || update_op == Sub, + "Only Add and Sub are currently supported."); + return typename VecWrapper::type(); } } From 5f8e67a544e0427696e2e7f950f221ef0d1c6ed4 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 2 Apr 2025 19:43:55 +0300 Subject: [PATCH 0969/1309] Remove combineLast3 optimization Passed non-reg STC 1st: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 67328 W: 17296 L: 17118 D: 32914 Ptnml(0-2): 158, 7095, 19011, 7211, 189 https://tests.stockfishchess.org/tests/view/67e6c2796682f97da2178ebe Passed non-reg STC 2nd: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 92288 W: 23885 L: 23734 D: 44669 Ptnml(0-2): 213, 10039, 25518, 10132, 242 https://tests.stockfishchess.org/tests/view/67ed6a2d31d7cf8afdc45190 closes https://github.com/official-stockfish/Stockfish/pull/5975 Bench: 1875196 --- src/nnue/nnue_accumulator.cpp | 63 ++++++++--------------------------- 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 8b2585b9182..2153cd4a67b 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -356,8 +356,6 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat accumulator.computed[Perspective] = true; #ifdef VECTOR - const bool combineLast3 = - std::abs((int) removed.size() - (int) added.size()) == 1 && removed.size() + added.size() > 2; vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; @@ -371,7 +369,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat acc[k] = entryTile[k]; IndexType i = 0; - for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) + for (; i < std::min(removed.size(), added.size()); ++i) { IndexType indexR = removed[i]; const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; @@ -383,58 +381,23 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = fused(acc[k], columnA[k], columnR[k]); } - if (combineLast3) + for (; i < removed.size(); ++i) { - IndexType indexR = removed[i]; - const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + IndexType index = removed[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); - if (removed.size() > added.size()) - { - IndexType indexR2 = removed[i + 1]; - const IndexType offsetR2 = Dimensions * indexR2 + j * Tiling::TileHeight; - auto* columnR2 = - reinterpret_cast(&featureTransformer.weights[offsetR2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = fused(acc[k], columnA[k], columnR[k], - columnR2[k]); - } - else - { - IndexType indexA2 = added[i + 1]; - const IndexType offsetA2 = Dimensions * indexA2 + j * Tiling::TileHeight; - auto* columnA2 = - reinterpret_cast(&featureTransformer.weights[offsetA2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = fused(acc[k], columnA[k], columnA2[k], - columnR[k]); - } + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); } - else + for (; i < added.size(); ++i) { - for (; i < removed.size(); ++i) - { - IndexType index = removed[i]; - const IndexType offset = Dimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&featureTransformer.weights[offset]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - for (; i < added.size(); ++i) - { - IndexType index = added[i]; - const IndexType offset = Dimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + IndexType index = added[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); } for (IndexType k = 0; k < Tiling::NumRegs; k++) From 904a016396013cacdf36d9c7fe4c562a330b33b4 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 28 Mar 2025 12:38:59 -0400 Subject: [PATCH 0970/1309] Don't use 5th continuation history in move ordering Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 136960 W: 35374 L: 35262 D: 66324 Ptnml(0-2): 420, 16214, 35049, 16428, 369 https://tests.stockfishchess.org/tests/view/67e6d0ae6682f97da2178ee5 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 233016 W: 59063 L: 59059 D: 114894 Ptnml(0-2): 113, 25430, 65421, 25428, 116 https://tests.stockfishchess.org/tests/view/67e9f7fb31d7cf8afdc44ad4 closes https://github.com/official-stockfish/Stockfish/pull/5978 Bench: 1842721 --- src/movepick.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6ee5fad7c4d..11317f113ac 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -162,7 +162,6 @@ void MovePicker::score() { m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to]; m.value += (*continuationHistory[3])[pc][to]; - m.value += (*continuationHistory[4])[pc][to] / 3; m.value += (*continuationHistory[5])[pc][to]; // bonus for checks From 44efbaddea909e146c6c41afaf458da8c9e4b4e4 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 5 Apr 2025 14:23:33 +0300 Subject: [PATCH 0971/1309] Simplify bonusScale calculation Allowing this specific term to potentially become negative for low depth values is ok, because the overall `bonusScale` is explicitly ensured to be non-negative a few lines later: bonusScale = std::max(bonusScale, 0); Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 164928 W: 42446 L: 42368 D: 80114 Ptnml(0-2): 497, 19551, 42306, 19597, 513 https://tests.stockfishchess.org/tests/view/67debf0b8888403457d8736c Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 234942 W: 59539 L: 59537 D: 115866 Ptnml(0-2): 113, 25639, 65964, 25643, 112 https://tests.stockfishchess.org/tests/view/67e2e1c48888403457d87768 closes https://github.com/official-stockfish/Stockfish/pull/5979 Bench: 1933843 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 7b8fbb144a6..7efd8499fb2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1446,7 +1446,7 @@ Value Search::Worker::search( else if (!priorCapture && prevSq != SQ_NONE) { int bonusScale = - (std::clamp(80 * depth - 320, 0, 200) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + (std::min(78 * depth - 312, 194) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) From d2d046c2a497b2d70debde07ccc414ca633d550b Mon Sep 17 00:00:00 2001 From: pb00067 Date: Tue, 15 Apr 2025 09:18:37 +0200 Subject: [PATCH 0972/1309] Improve stalemate detection during search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently SF’s quiescence search like most alpha-beta based engines doesn’t verify for stalemate because doing it each leaf position is to expensive and costs elo. However in certain positions this creates a blindspot for SF, not recognizing soon enough that the opponent can reach a stalemate by sacrifycing his last mobile heavy piece(s). This tactical motif & it’s measure are similar to zugzwang & verification search: the measure itself does not gain elo, but prevents SF from loosing/drawing games in an awkward way. The fix consists of 3 measures: 1. Make qsearch verify for stalemate on transitions to pure KP-material for the side to move with our last Rook/Queen just been captured. In fact this is the scenario where stalemate happens with highest frequency. The stalemate-verification itself is optimized by merely checking for pawn pushes & king mobility (captures were already tried by qssearch) 2. Another culprit for the issue figured out to be SEE based pruning for checks in step 14. Here often the move forcing the stalemate (or forcing the opponent to not retake) get pruned away and it need to much time to reach enough depth. To encounter this we verify following conditions: - a) side to move is happy with a draw (alpha < 0) - b) we are about to sacrify our last heavy & unique mobile piece in this position. - c) this piece doesn’t move away from our kingring giving the king a new square to move. When all 3 conditions meet we don’t prune the move, because there is a good chance that capturing the piece means stalemate. 3. Store terminal nodes (mates & stalemates) in TT with higher depth than searched depth. This prevents SF from: - reanalyzing the node (=trying to generate legal moves) in vain at each iterative deepening step. - overwriting an already correct draw-evaluation from a previous shallow normal search by a qsearch which doesn’t recognize stalemate and might store a verry erratic evaluation. This is due to the 4 constant in the TT-overwrite condition: d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 – 4 which allows qs to override entries made by normal searches with depth <=4. This 3hrd measure however is not essential for fixing the issue, but tests (one of vdv & one of mine) seem to suggest that this measure brings some small benefit. Another other position where SF benefits from this fix is for instance Position FEN 8/8/8/1B6/6p1/8/3K1Ppp/3N2kr w - - 0 1 bm f4 +M9 P.S.: Also this issue higly depends on the used net, how good the net is at evaluate such mobility restricted positions. SF16 was pretty good in solving 2rr4/5pBk/PqP3p1/1N3pPp/1PQ1bP1P/8/3R4/R4K2 b - - 0 40 bm Rxc6 (< 1 second) while SF16_1 with introduction of the dual net needs about 1,5 minutes and SF17.1 requires 3 minutes to find the drawing move Rxc6. P.S.2: Using more threads produces indeterminism & using high hash pressure makes SF reevaluate explored positions more often which makes it more likely to solve the position. To have stable meaningful results I tested therfore with one single thread and low hash pressure. Preliminary LTC test at 30k games https://tests.stockfishchess.org/tests/view/67ece7a931d7cf8afdc44e18 Elo: 0.04 ± 2.0 (95%) LOS: 51.7% Total: 24416 W: 6226 L: 6223 D: 11967 Ptnml(0-2): 12, 2497, 7185, 2504, 10 nElo: 0.09 ± 4.4 (95%) PairsRatio: 1.00 Passed LTC no-regression sprt https://tests.stockfishchess.org/tests/view/67ee8e4631d7cf8afdc452fb LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 401556 W: 101612 L: 101776 D: 198168 Ptnml(0-2): 152, 42241, 116170, 42049, 166 closes https://github.com/official-stockfish/Stockfish/pull/5983 fixes https://github.com/official-stockfish/Stockfish/issues/5899 Bench: 1721673 --- src/movepick.cpp | 27 +++++++++++++++++++++++++++ src/movepick.h | 6 ++++++ src/search.cpp | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 11317f113ac..cc6d47901b6 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -19,6 +19,7 @@ #include "movepick.h" #include +#include #include #include "bitboard.h" @@ -316,4 +317,30 @@ Move MovePicker::next_move() { void MovePicker::skip_quiet_moves() { skipQuiets = true; } +bool MovePicker::otherPieceTypesMobile(PieceType pt, ValueList& capturesSearched) { + if (stage != GOOD_QUIET && stage != BAD_QUIET) + return true; + + // verify good captures + for (std::size_t i = 0; i < capturesSearched.size(); i++) + if (type_of(pos.moved_piece(capturesSearched[i])) != pt) + { + if (type_of(pos.moved_piece(capturesSearched[i])) != KING) + return true; + if (pos.legal(capturesSearched[i])) + return true; + } + + // now verify bad captures and quiets + for (ExtMove* c = moves; c < endBadQuiets; ++c) + if (type_of(pos.moved_piece(*c)) != pt) + { + if (type_of(pos.moved_piece(*c)) != KING) + return true; + if (pos.legal(*c)) + return true; + } + return false; +} + } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 71078bdcf3d..dfafe69a58e 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -19,6 +19,8 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED +#include + #include "history.h" #include "movegen.h" #include "types.h" @@ -27,6 +29,9 @@ namespace Stockfish { class Position; +template +class ValueList; + // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one // new pseudo-legal move on every call, until there are no moves left, when @@ -50,6 +55,7 @@ class MovePicker { MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(); void skip_quiet_moves(); + bool otherPieceTypesMobile(PieceType pt, ValueList& capturesSearched); private: template diff --git a/src/search.cpp b/src/search.cpp index 7efd8499fb2..3f24f86c5e9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -34,6 +34,7 @@ #include #include +#include "bitboard.h" #include "evaluate.h" #include "history.h" #include "misc.h" @@ -1070,7 +1071,19 @@ Value Search::Worker::search( // SEE based pruning for captures and checks int seeHist = std::clamp(captHist / 32, -138 * depth, 135 * depth); if (!pos.see_ge(move, -154 * depth - seeHist)) - continue; + { + bool skip = true; + if (depth > 2 && !capture && givesCheck && alpha < 0 + && pos.non_pawn_material(us) == PieceValue[movedPiece] + && PieceValue[movedPiece] >= RookValue + && !(PseudoAttacks[KING][pos.square(us)] & move.from_sq())) + skip = mp.otherPieceTypesMobile( + type_of(movedPiece), + capturesSearched); // if the opponent captures last mobile piece it might be stalemate + + if (skip) + continue; + } } else { @@ -1490,7 +1503,8 @@ Value Search::Worker::search( bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, unadjustedStaticEval, tt.generation()); + moveCount != 0 ? depth : std::min(MAX_PLY - 1, depth + 6), bestMove, + unadjustedStaticEval, tt.generation()); // Adjust correction history if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) @@ -1743,6 +1757,22 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!is_decisive(bestValue) && bestValue > beta) bestValue = (bestValue + beta) / 2; + + Color us = pos.side_to_move(); + if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us) + && type_of(pos.captured_piece()) >= ROOK) + { + if (!((us == WHITE ? shift(pos.pieces(us, PAWN)) + : shift(pos.pieces(us, PAWN))) + & ~pos.pieces())) // no pawn pushes available + { + pos.state()->checkersBB = Rank1BB; // search for legal king-moves only + if (!MoveList(pos).size()) // stalemate + bestValue = VALUE_DRAW; + pos.state()->checkersBB = 0; + } + } + // Save gathered info in transposition table. The static evaluation // is saved as it was before adjustment by correction history. ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), pvHit, From 2b4926e091cb423d34447b9f995578b4da74906b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 31 Mar 2025 21:33:40 -0700 Subject: [PATCH 0973/1309] Simplify TT Move History Part 1 passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 195552 W: 50394 L: 50349 D: 94809 Ptnml(0-2): 581, 23222, 50122, 23273, 578 https://tests.stockfishchess.org/tests/view/67eb6ea831d7cf8afdc44c30 Part 2 passed Non-regression STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 181664 W: 46786 L: 46727 D: 88151 Ptnml(0-2): 517, 21403, 46974, 21380, 558 https://tests.stockfishchess.org/tests/view/67eb6f3331d7cf8afdc44c33 Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 155454 W: 39496 L: 39412 D: 76546 Ptnml(0-2): 77, 16950, 43580, 17052, 68 https://tests.stockfishchess.org/tests/view/67eee76531d7cf8afdc45358 Passed Non-regression VLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 118266 W: 30263 L: 30148 D: 57855 Ptnml(0-2): 11, 11844, 35309, 11957, 12 https://tests.stockfishchess.org/tests/view/67f2414a31d7cf8afdc45760 closes https://github.com/official-stockfish/Stockfish/pull/5987 Bench: 1792850 --- src/history.h | 2 +- src/search.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/history.h b/src/history.h index f2ef7661f80..259d6eefb22 100644 --- a/src/history.h +++ b/src/history.h @@ -166,7 +166,7 @@ struct CorrHistTypedef { template using CorrectionHistory = typename Detail::CorrHistTypedef::type; -using TTMoveHistory = Stats; +using TTMoveHistory = StatsEntry; } // namespace Stockfish diff --git a/src/search.cpp b/src/search.cpp index 3f24f86c5e9..0fecbce65c4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -579,7 +579,7 @@ void Search::Worker::clear() { minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory.fill(0); - ttMoveHistory.fill(0); + ttMoveHistory = 0; for (auto& to : continuationCorrectionHistory) for (auto& h : to) @@ -1151,10 +1151,10 @@ Value Search::Worker::search( if (value < singularBeta) { - int corrValAdj1 = std::abs(correctionValue) / 248873; - int corrValAdj2 = std::abs(correctionValue) / 255331; - int doubleMargin = 262 * PvNode - 188 * !ttCapture - corrValAdj1 - - ttMoveHistory[pawn_structure_index(pos)][us] / 128; + int corrValAdj1 = std::abs(correctionValue) / 248873; + int corrValAdj2 = std::abs(correctionValue) / 255331; + int doubleMargin = + 262 * PvNode - 188 * !ttCapture - corrValAdj1 - ttMoveHistory / 128; int tripleMargin = 88 + 265 * PvNode - 256 * !ttCapture + 93 * ss->ttPv - corrValAdj2; @@ -1450,8 +1450,8 @@ Value Search::Worker::search( bestMove == ttData.move, moveCount); if (!PvNode) { - int bonus = (ttData.move == move) ? 800 : -600 * moveCount; - ttMoveHistory[pawn_structure_index(pos)][us] << bonus; + int bonus = (ttData.move == move) ? 800 : -870; + ttMoveHistory << bonus; } } From 698c069bba69912d576075bf3d63e32ac5032d9d Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Mon, 14 Apr 2025 15:00:39 +0200 Subject: [PATCH 0974/1309] Move node increment inside do_move function Move node increment inside do_move function so that we can centralize the increment into a single point. closes https://github.com/official-stockfish/Stockfish/pull/5989 No functional change --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0fecbce65c4..fcbac307803 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -556,6 +556,7 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st) { void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck) { DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); + nodes.fetch_add(1, std::memory_order_relaxed); accumulatorStack.push(dp); } @@ -940,7 +941,6 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); do_move(pos, move, st); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; ss->isTTMove = (move == ttData.move); @@ -1193,7 +1193,6 @@ Value Search::Worker::search( // Step 16. Make the move do_move(pos, move, st, givesCheck); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Add extension to new depth newDepth += extension; @@ -1711,7 +1710,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Piece movedPiece = pos.moved_piece(move); do_move(pos, move, st, givesCheck); - thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Update the current move ss->currentMove = move; From 3d18ad719b2e7103e27936561f8276fbed9a354b Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 2 Apr 2025 11:57:06 -0700 Subject: [PATCH 0975/1309] Skip 5th continuation history Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 70208 W: 18098 L: 17914 D: 34196 Ptnml(0-2): 199, 8300, 17907, 8514, 184 https://tests.stockfishchess.org/tests/view/67ed889b31d7cf8afdc451cb Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 485004 W: 122703 L: 122956 D: 239345 Ptnml(0-2): 288, 53162, 135805, 53009, 238 https://tests.stockfishchess.org/tests/view/67ee810231d7cf8afdc452ea closes https://github.com/official-stockfish/Stockfish/pull/5992 Bench: 1715901 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fcbac307803..92998b02640 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1917,7 +1917,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, 1103}, {2, 659}, {3, 323}, {4, 533}, {5, 121}, {6, 474}}}; + {{1, 1103}, {2, 659}, {3, 323}, {4, 533}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { From f9459e4c8e5b595db0fa76c3ca7b475bf03858a9 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 31 Mar 2025 08:15:10 +0200 Subject: [PATCH 0976/1309] Use matching llvm-profdata when using multiple clang compilers in parallel, it is necessary to use a matching llvm-profdata, as the profile data format may change between versions. To use the proper llvm-profdata binary, the one in the same directory as the compiler is used. This allows for code like: ```bash echo "Compiling clang" for comp in clang++-11 clang++-12 clang++-13 clang++-14 clang++-15 clang++-16 clang++-17 clang++-18 clang++-19 clang++-20 do make -j profile-build CXX=$comp COMP=clang >& out.compile.$comp mv stockfish stockfish.$comp done ``` after installing the required versions with the automatic installation script (https://apt.llvm.org/) closes https://github.com/official-stockfish/Stockfish/pull/5958 No functional change --- src/Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 76b94785ecb..17badefde80 100644 --- a/src/Makefile +++ b/src/Makefile @@ -567,6 +567,15 @@ ifeq ($(COMP),ndk) LDFLAGS += -static-libstdc++ -pie -lm -latomic endif +# llvm-profdata must be version compatible with the specified CXX (be it clang, or the gcc alias) +# make -j profile-build CXX=clang++-20 COMP=clang +# Locate the version in the same directory as the compiler used, +# with fallback to a generic one if it can't be located + LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX))))llvm-profdata +ifeq ($(wildcard $(LLVM_PROFDATA)),) + LLVM_PROFDATA := llvm-profdata +endif + ifeq ($(comp),icx) profile_make = icx-profile-make profile_use = icx-profile-use @@ -1081,7 +1090,7 @@ clang-profile-make: all clang-profile-use: - $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw + $(XCRUN) $(LLVM_PROFDATA) merge -output=stockfish.profdata *.profraw $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-fprofile-use=stockfish.profdata' \ EXTRALDFLAGS='-fprofile-use ' \ From f273eea71fc8ec030d0f4279c03c4e1fc2af4584 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 11 Apr 2025 20:58:11 -0700 Subject: [PATCH 0977/1309] Remove non-functional accumulator reset Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 219360 W: 56600 L: 56583 D: 106177 Ptnml(0-2): 582, 23419, 61620, 23518, 541 https://tests.stockfishchess.org/tests/view/67fad20dcd501869c669780f closes https://github.com/official-stockfish/Stockfish/pull/5986 no functional change --- src/evaluate.cpp | 2 -- src/nnue/nnue_accumulator.cpp | 65 ++++++++++++++--------------------- src/nnue/nnue_accumulator.h | 13 +++---- src/nnue/nnue_misc.cpp | 5 ++- src/search.cpp | 2 +- 5 files changed, 33 insertions(+), 54 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 92b03af370c..23f4b8c2b2f 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -101,8 +101,6 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { Eval::NNUE::AccumulatorStack accumulators; auto caches = std::make_unique(networks); - accumulators.reset(pos, networks, *caches); - std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 2153cd4a67b..2bf76f530b3 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -19,15 +19,14 @@ #include "nnue_accumulator.h" #include +#include #include -#include #include #include "../bitboard.h" #include "../misc.h" #include "../position.h" #include "../types.h" -#include "network.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" @@ -68,39 +67,24 @@ void AccumulatorState::reset(const DirtyPiece& dp) noexcept { accumulatorSmall.computed.fill(false); } -const AccumulatorState& AccumulatorStack::latest() const noexcept { - return m_accumulators[m_current_idx - 1]; -} - -AccumulatorState& AccumulatorStack::mut_latest() noexcept { - return m_accumulators[m_current_idx - 1]; -} +const AccumulatorState& AccumulatorStack::latest() const noexcept { return accumulators[size - 1]; } -void AccumulatorStack::reset(const Position& rootPos, - const Networks& networks, - AccumulatorCaches& caches) noexcept { - m_current_idx = 1; +AccumulatorState& AccumulatorStack::mut_latest() noexcept { return accumulators[size - 1]; } - update_accumulator_refresh_cache( - *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); - update_accumulator_refresh_cache( - *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); - - update_accumulator_refresh_cache( - *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); - update_accumulator_refresh_cache( - *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); +void AccumulatorStack::reset() noexcept { + accumulators[0].reset({}); + size = 1; } void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { - assert(m_current_idx + 1 < m_accumulators.size()); - m_accumulators[m_current_idx].reset(dirtyPiece); - m_current_idx++; + assert(size + 1 < accumulators.size()); + accumulators[size].reset(dirtyPiece); + size++; } void AccumulatorStack::pop() noexcept { - assert(m_current_idx > 1); - m_current_idx--; + assert(size > 1); + size--; } template @@ -119,7 +103,7 @@ void AccumulatorStack::evaluate_side(const Position& pos, const auto last_usable_accum = find_last_usable_accumulator(); - if ((m_accumulators[last_usable_accum].template acc()).computed[Perspective]) + if ((accumulators[last_usable_accum].template acc()).computed[Perspective]) forward_update_incremental(pos, featureTransformer, last_usable_accum); else @@ -134,12 +118,12 @@ void AccumulatorStack::evaluate_side(const Position& pos, template std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { - for (std::size_t curr_idx = m_current_idx - 1; curr_idx > 0; curr_idx--) + for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) { - if ((m_accumulators[curr_idx].template acc()).computed[Perspective]) + if ((accumulators[curr_idx].template acc()).computed[Perspective]) return curr_idx; - if (FeatureSet::requires_refresh(m_accumulators[curr_idx].dirtyPiece, Perspective)) + if (FeatureSet::requires_refresh(accumulators[curr_idx].dirtyPiece, Perspective)) return curr_idx; } @@ -152,14 +136,14 @@ void AccumulatorStack::forward_update_incremental( const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { - assert(begin < m_accumulators.size()); - assert((m_accumulators[begin].acc()).computed[Perspective]); + assert(begin < accumulators.size()); + assert((accumulators[begin].acc()).computed[Perspective]); const Square ksq = pos.square(Perspective); - for (std::size_t next = begin + 1; next < m_current_idx; next++) + for (std::size_t next = begin + 1; next < size; next++) update_accumulator_incremental( - featureTransformer, ksq, m_accumulators[next], m_accumulators[next - 1]); + featureTransformer, ksq, accumulators[next], accumulators[next - 1]); assert((latest().acc()).computed[Perspective]); } @@ -170,17 +154,17 @@ void AccumulatorStack::backward_update_incremental( const FeatureTransformer& featureTransformer, const std::size_t end) noexcept { - assert(end < m_accumulators.size()); - assert(end < m_current_idx); + assert(end < accumulators.size()); + assert(end < size); assert((latest().acc()).computed[Perspective]); const Square ksq = pos.square(Perspective); - for (std::size_t next = m_current_idx - 2; next >= end; next--) + for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--) update_accumulator_incremental( - featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); + featureTransformer, ksq, accumulators[next], accumulators[next + 1]); - assert((m_accumulators[end].acc()).computed[Perspective]); + assert((accumulators[end].acc()).computed[Perspective]); } // Explicit template instantiations @@ -323,6 +307,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache) { + using Tiling [[maybe_unused]] = SIMDTiling; const Square ksq = pos.square(Perspective); diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index d83a5a4464b..aa9e2a676da 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -41,8 +41,6 @@ using BiasType = std::int16_t; using PSQTWeightType = std::int32_t; using IndexType = std::uint32_t; -struct Networks; - template struct alignas(CacheLineSize) Accumulator; @@ -149,13 +147,12 @@ struct AccumulatorState { class AccumulatorStack { public: AccumulatorStack() : - m_accumulators(MAX_PLY + 1), - m_current_idx{} {} + accumulators(MAX_PLY + 1), + size{1} {} [[nodiscard]] const AccumulatorState& latest() const noexcept; - void - reset(const Position& rootPos, const Networks& networks, AccumulatorCaches& caches) noexcept; + void reset() noexcept; void push(const DirtyPiece& dirtyPiece) noexcept; void pop() noexcept; @@ -185,8 +182,8 @@ class AccumulatorStack { const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; - std::vector m_accumulators; - std::size_t m_current_idx; + std::vector accumulators; + std::size_t size; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 809d454b5ff..c99874076db 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -121,7 +121,6 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat }; AccumulatorStack accumulators; - accumulators.reset(pos, networks, caches); // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. @@ -140,7 +139,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat { pos.remove_piece(sq); - accumulators.reset(pos, networks, caches); + accumulators.reset(); std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; @@ -157,7 +156,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << board[row] << '\n'; ss << '\n'; - accumulators.reset(pos, networks, caches); + accumulators.reset(); auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big); ss << " NNUE network contributions " diff --git a/src/search.cpp b/src/search.cpp index 92998b02640..fe0e340b4d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -195,7 +195,7 @@ void Search::Worker::ensure_network_replicated() { void Search::Worker::start_searching() { - accumulatorStack.reset(rootPos, networks[numaAccessToken], refreshTable); + accumulatorStack.reset(); // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) From f2507d05625434a1377c603bc2530ffdd4900e58 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 14 Apr 2025 22:04:51 +0200 Subject: [PATCH 0978/1309] Add x86-64-avxvnni in CI will result in the corresponding binaries being available for download closes https://github.com/official-stockfish/Stockfish/pull/5990 No functional change --- .github/ci/matrix.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index 44e0596eab0..fa720a1c38f 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -84,12 +84,6 @@ "os": "macos-14" } }, - { - "binaries": "x86-64-avxvnni", - "config": { - "os": "macos-14" - } - }, { "binaries": "x86-64-avx512", "config": { @@ -108,12 +102,6 @@ "os": "macos-14" } }, - { - "binaries": "x86-64-avxvnni", - "config": { - "ubuntu-22.04": null - } - }, { "binaries": "x86-64-avxvnni", "config": { From b915ed702aa4ac6bec948cb2baf3021f38762102 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 18 Apr 2025 09:08:39 -0700 Subject: [PATCH 0979/1309] remove StateInfo::next unused. closes https://github.com/official-stockfish/Stockfish/pull/5997 no functional change --- src/position.cpp | 2 -- src/position.h | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 0e5748e6a2e..85ade69a928 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -698,7 +698,6 @@ DirtyPiece Position::do_move(Move m, // our state pointer to point to the new (ready to be updated) state. std::memcpy(&newSt, st, offsetof(StateInfo, key)); newSt.previous = st; - st->next = &newSt; st = &newSt; // Increment ply counters. In particular, rule50 will be reset to zero later on @@ -1011,7 +1010,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { std::memcpy(&newSt, st, sizeof(StateInfo)); newSt.previous = st; - st->next = &newSt; st = &newSt; if (st->epSquare != SQ_NONE) diff --git a/src/position.h b/src/position.h index 75f22c7df58..724165b003a 100644 --- a/src/position.h +++ b/src/position.h @@ -53,7 +53,6 @@ struct StateInfo { Key key; Bitboard checkersBB; StateInfo* previous; - StateInfo* next; Bitboard blockersForKing[COLOR_NB]; Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; @@ -165,7 +164,6 @@ class Position { bool pos_is_ok() const; void flip(); - // Used by NNUE StateInfo* state() const; void put_piece(Piece pc, Square s); From 16cd38dba1c168e23982ee6d521a10bafc82ca14 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 10 Apr 2025 10:00:27 -0700 Subject: [PATCH 0980/1309] Tweak TT Move Reduction by TT Move History Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 62496 W: 16197 L: 15844 D: 30455 Ptnml(0-2): 200, 7234, 16011, 7619, 184 https://tests.stockfishchess.org/tests/view/67f7fa9b31d7cf8afdc4609c Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 400470 W: 102068 L: 101012 D: 197390 Ptnml(0-2): 201, 43207, 112347, 44295, 185 https://tests.stockfishchess.org/tests/view/67f927b0cd501869c66975e0 Passed VVLTC Non-regression: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 125394 W: 32408 L: 32304 D: 60682 Ptnml(0-2): 3, 11702, 39188, 11796, 8 https://tests.stockfishchess.org/tests/view/6804c215cd501869c66986b9 closes https://github.com/official-stockfish/Stockfish/pull/5998 bench 1760988 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index fe0e340b4d9..9a106e67386 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1298,6 +1298,8 @@ Value Search::Worker::search( if (!ttData.move) r += 1156; + r -= ttMoveHistory / 8; + // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3495) - (r > 5510 && newDepth > 2), !cutNode); From 4176ad7b0a176a615f77c91b78b8b74e113a0a88 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 20 Apr 2025 09:21:01 -0700 Subject: [PATCH 0981/1309] simplify risk tolerance Passed Non-regression STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 73408 W: 19028 L: 18844 D: 35536 Ptnml(0-2): 201, 8709, 18743, 8807, 244 https://tests.stockfishchess.org/tests/view/68051f3698cd372e3ae9f63a Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 91236 W: 23193 L: 23045 D: 44998 Ptnml(0-2): 34, 9908, 25599, 10030, 47 https://tests.stockfishchess.org/tests/view/6805239498cd372e3ae9fa41 closes https://github.com/official-stockfish/Stockfish/pull/6000 bench 1864632 --- src/search.cpp | 50 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9a106e67386..071a9d18a7b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -97,31 +97,6 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss return 7685 * pcv + 7495 * micv + 9144 * (wnpcv + bnpcv) + 6469 * cntcv; } -int risk_tolerance(const Position& pos, Value v) { - // Returns (some constant of) second derivative of sigmoid. - static constexpr auto sigmoid_d2 = [](int x, int y) { - return 644800 * x / ((x * x + 3 * y * y) * y); - }; - - int m = pos.count() + pos.non_pawn_material() / 300; - - // a and b are the crude approximation of the wdl model. - // The win rate is: 1/(1+exp((a-v)/b)) - // The loss rate is 1/(1+exp((v+a)/b)) - int a = 356; - int b = ((65 * m - 3172) * m + 240578) / 2048; - - // guard against overflow - assert(abs(v) + a <= std::numeric_limits::max() / 644800); - - // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) - // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) - int winning_risk = sigmoid_d2(v - a, b); - int losing_risk = sigmoid_d2(v + a, b); - - return -(winning_risk + losing_risk) * 32; -} - // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. Value to_corrected_static_eval(const Value v, const int cv) { @@ -150,6 +125,29 @@ void update_correction_history(const Position& pos, << bonus * 143 / 128; } +int risk_tolerance(Value v) { + // Returns (some constant of) second derivative of sigmoid. + static constexpr auto sigmoid_d2 = [](int x, int y) { + return 644800 * x / ((x * x + 3 * y * y) * y); + }; + + // a and b are the crude approximation of the wdl model. + // The win rate is: 1/(1+exp((a-v)/b)) + // The loss rate is 1/(1+exp((v+a)/b)) + int a = 356; + int b = 123; + + // guard against overflow + assert(abs(v) + a <= std::numeric_limits::max() / 644800); + + // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) + // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) + int winning_risk = sigmoid_d2(v - a, b); + int losing_risk = sigmoid_d2(v + a, b); + + return -(winning_risk + losing_risk) * 32; +} + // 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); @@ -1218,7 +1216,7 @@ Value Search::Worker::search( r -= std::abs(correctionValue) / 29696; if (PvNode && std::abs(bestValue) <= 2000) - r -= risk_tolerance(pos, bestValue); + r -= risk_tolerance(bestValue); // Increase reduction for cut nodes if (cutNode) From 449a8b017eb9556e562eb1e067d346a55d99f4e9 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 20 Apr 2025 23:39:00 +0300 Subject: [PATCH 0982/1309] Do second step of shallower search Specifically if lmr depth is not lower than new depth and value is really bad. Passed STC: https://tests.stockfishchess.org/tests/view/6805444898cd372e3aea0494 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 77664 W: 20136 L: 19762 D: 37766 Ptnml(0-2): 214, 9006, 20039, 9338, 235 Passed LTC: https://tests.stockfishchess.org/tests/view/680549b298cd372e3aea05ac LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 115458 W: 29447 L: 28971 D: 57040 Ptnml(0-2): 62, 12357, 32421, 12821, 68 closes https://github.com/official-stockfish/Stockfish/pull/6001 bench: 1515501 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 071a9d18a7b..2a5bf1b91cd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1286,7 +1286,11 @@ Value Search::Worker::search( update_continuation_histories(ss, movedPiece, move.to_sq(), 1600); } else if (value > alpha && value < bestValue + 9) + { newDepth--; + if (value < bestValue + 3) + newDepth--; + } } // Step 18. Full-depth search when LMR is skipped From f6b0d53a995da2d3fdcdb9402420b30a058662da Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 21 Apr 2025 00:19:02 +0300 Subject: [PATCH 0983/1309] Re-adding the 5th continuation history the two simplifications (#5992 and #5978) are strongly correlated. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 98016 W: 25415 L: 25008 D: 47593 Ptnml(0-2): 291, 11509, 25005, 11908, 295 https://tests.stockfishchess.org/tests/view/6802774fcd501869c6698168 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 588588 W: 150073 L: 148633 D: 289882 Ptnml(0-2): 281, 63615, 165078, 65023, 297 https://tests.stockfishchess.org/tests/view/6804cbdacd501869c66987dc closes https://github.com/official-stockfish/Stockfish/pull/6002 Bench: 1783315 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2a5bf1b91cd..54d220cd113 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1921,7 +1921,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, 1103}, {2, 659}, {3, 323}, {4, 533}, {6, 474}}}; + {{1, 1103}, {2, 659}, {3, 323}, {4, 533}, {5, 121}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { From 7988de4aa302e65dd4453651d65a6fbc095a66aa Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 20 Apr 2025 23:32:01 +0200 Subject: [PATCH 0984/1309] Prefer discovered and double checks in capture ordering. Increase weight for captured piece value if also a discovered or double check. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 142144 W: 36632 L: 36164 D: 69348 Ptnml(0-2): 429, 16470, 36788, 16974, 411 https://tests.stockfishchess.org/tests/view/68052d7498cd372e3ae9faaa Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 668328 W: 170837 L: 169238 D: 328253 Ptnml(0-2): 308, 72010, 187990, 73487, 369 https://tests.stockfishchess.org/tests/view/68053c9398cd372e3aea043b closes https://github.com/official-stockfish/Stockfish/pull/6003 Bench: 1636625 --- src/movepick.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index cc6d47901b6..6c8eb09c71c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -146,7 +146,11 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = - 7 * int(PieceValue[pos.piece_on(m.to_sq())]) + (2 + * (pos.blockers_for_king(~pos.side_to_move()) & m.from_sq() + && !aligned(m.from_sq(), m.to_sq(), pos.square(~pos.side_to_move()))) + + 7) + * int(PieceValue[pos.piece_on(m.to_sq())]) + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]; else if constexpr (Type == QUIETS) From 88a524c55244b3827747c0cb7c8de490b6119d23 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 20 Apr 2025 14:29:29 -0700 Subject: [PATCH 0985/1309] Tweak futility formula Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 248448 W: 64344 L: 63718 D: 120386 Ptnml(0-2): 750, 29172, 63783, 29740, 779 https://tests.stockfishchess.org/tests/view/68056f5598cd372e3aea2901 Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 118824 W: 30358 L: 29874 D: 58592 Ptnml(0-2): 59, 12797, 33228, 13257, 71 https://tests.stockfishchess.org/tests/view/6805675698cd372e3aea20d0 closes https://github.com/official-stockfish/Stockfish/pull/6004 bench 1839796 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 54d220cd113..b8a4b32a6b3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1099,7 +1099,7 @@ Value Search::Worker::search( lmrDepth += history / 3593; Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth - + 103 * (bestValue < ss->staticEval - 128); + + 103 * (bestValue < ss->staticEval - 128 && ss->staticEval > alpha - 50); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning From 0dcfe096d6223ea8c5663cf72f714280a38d8506 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:13:30 +0200 Subject: [PATCH 0986/1309] Increase full depth search reduction when cutNode In addition to the core patch, improve the use of `isTTMove`: - this name was used to mean both `bestMove == ttData.move` and `move == ttData.move`, so i replaced the argument `isTTMove` of `update_all_stats` with `TTMove` directly. - `ttData.move == move` was still used in some places instead of `ss->isTTMove`. I replaced these to be more consistent. Passed STC: https://tests.stockfishchess.org/tests/view/68057b8f98cd372e3aea3472 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 38400 W: 10048 L: 9734 D: 18618 Ptnml(0-2): 102, 4360, 9956, 4686, 96 Passed LTC: https://tests.stockfishchess.org/tests/view/68057f7c98cd372e3aea3842 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 312666 W: 79494 L: 78616 D: 154556 Ptnml(0-2): 144, 33809, 87563, 34659, 158 closes https://github.com/official-stockfish/Stockfish/pull/6007 Bench: 1623376 --- src/search.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b8a4b32a6b3..85e4d00774e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -164,7 +164,7 @@ void update_all_stats(const Position& pos, ValueList& quietsSearched, ValueList& capturesSearched, Depth depth, - bool isTTMove, + Move TTMove, int moveCount); } // namespace @@ -1098,8 +1098,9 @@ Value Search::Worker::search( lmrDepth += history / 3593; - Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth - + 103 * (bestValue < ss->staticEval - 128 && ss->staticEval > alpha - 50); + Value futilityValue = + ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth + + 103 * (bestValue < ss->staticEval - 128 && ss->staticEval > alpha - 50); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning @@ -1231,7 +1232,7 @@ Value Search::Worker::search( r += 1042 + allNode * 864; // For first picked move (ttMove) reduce reduction - else if (move == ttData.move) + else if (ss->isTTMove) r -= 1937; if (capture) @@ -1302,6 +1303,9 @@ Value Search::Worker::search( r -= ttMoveHistory / 8; + if (cutNode) + r += 520; + // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3495) - (r > 5510 && newDepth > 2), !cutNode); @@ -1315,7 +1319,7 @@ Value Search::Worker::search( (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && thisThread->rootDepth > 8) + if (ss->isTTMove && thisThread->rootDepth > 8) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); @@ -1450,10 +1454,10 @@ Value Search::Worker::search( else if (bestMove) { update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - bestMove == ttData.move, moveCount); + ttData.move, moveCount); if (!PvNode) { - int bonus = (ttData.move == move) ? 800 : -870; + int bonus = ss->isTTMove ? 800 : -870; ttMoveHistory << bonus; } } @@ -1877,14 +1881,14 @@ void update_all_stats(const Position& pos, ValueList& quietsSearched, ValueList& capturesSearched, Depth depth, - bool isTTMove, + Move TTMove, int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = std::min(141 * depth - 89, 1613) + 311 * isTTMove; + int bonus = std::min(141 * depth - 89, 1613) + 311 * (bestMove == TTMove); int malus = std::min(695 * depth - 215, 2808) - 31 * (moveCount - 1); if (!pos.capture_stage(bestMove)) From 8b85290313a7cb9beed6d11a0af8d38cbfd2bdd3 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 21 Apr 2025 18:39:13 +0300 Subject: [PATCH 0987/1309] Remove manual stack alignment workaround for GCC < 9.3 consequently using these old compilers will now error out. closes https://github.com/official-stockfish/Stockfish/pull/6008 No functional change. --- src/nnue/network.cpp | 21 +-------------------- src/types.h | 6 +++--- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index e23294e4fa1..957dc7bffcc 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -212,21 +212,11 @@ NetworkOutput Network::evaluate(const Position& pos, AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; - - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; -#endif ASSERT_ALIGNED(transformedFeatures, alignment); @@ -284,20 +274,11 @@ NnueEvalTrace Network::trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. - constexpr uint64_t alignment = CacheLineSize; -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType - transformedFeaturesUnaligned[FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; + constexpr uint64_t alignment = CacheLineSize; - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; -#endif ASSERT_ALIGNED(transformedFeatures, alignment); diff --git a/src/types.h b/src/types.h index d7f9dfb7445..a76d00336f3 100644 --- a/src/types.h +++ b/src/types.h @@ -57,9 +57,9 @@ // _WIN32 Building on Windows (any) // _WIN64 Building on Windows 64 bit - #if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \ - && defined(_WIN32) && !defined(__clang__) - #define ALIGNAS_ON_STACK_VARIABLES_BROKEN + #if defined(__GNUC__) && !defined(__clang__) \ + && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ < 3)) + #error "Stockfish requires GCC 9.3 or later for correct compilation" #endif #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) From f590767b91f6e7b95ee5797b3cf32c143e50d3c2 Mon Sep 17 00:00:00 2001 From: breatn Date: Tue, 22 Apr 2025 18:54:04 +0100 Subject: [PATCH 0988/1309] Adaptive beta cut passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 197088 W: 51084 L: 50533 D: 95471 Ptnml(0-2): 547, 23201, 50577, 23592, 627 https://tests.stockfishchess.org/tests/view/680604d798cd372e3aea58fe passed LTC LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 127950 W: 32719 L: 32214 D: 63017 Ptnml(0-2): 69, 13825, 35673, 14348, 60 https://tests.stockfishchess.org/tests/view/6805eae498cd372e3aea588a closes https://github.com/official-stockfish/Stockfish/pull/6012 bench 1579003 --- AUTHORS | 1 + src/search.cpp | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 54c5f8a2124..f1675860944 100644 --- a/AUTHORS +++ b/AUTHORS @@ -34,6 +34,7 @@ Artem Solopiy (EntityFX) Auguste Pop Balazs Szilagyi Balint Pfliegel +Baptiste Rech (breatn) Ben Chaney (Chaneybenjamini) Ben Koshy (BKSpurgeon) Bill Henry (VoyagerOne) diff --git a/src/search.cpp b/src/search.cpp index 85e4d00774e..d08418813df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -103,6 +103,18 @@ 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); } +int adaptive_probcut_margin(Depth depth) { + // Base margin + constexpr int base = 180; + + // Approximate log2(depth) using a fast lookup table + static constexpr int logTable[32] = {0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}; + + int logDepth = logTable[std::min(depth, 31)]; + return base + logDepth * 60 + std::min(10, (depth - 16) * 2); +}; + void update_correction_history(const Position& pos, Stack* const ss, Search::Worker& workerThread, @@ -972,7 +984,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 415; + probCutBeta = beta + adaptive_probcut_margin(depth); 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; From 5f32b3ed4b6df4039b1db131bb1db40a8e378ef1 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 23 Apr 2025 03:40:16 +0300 Subject: [PATCH 0989/1309] Remove unneeded return statement closes https://github.com/official-stockfish/Stockfish/pull/6013 No functional change --- src/memory.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/memory.h b/src/memory.h index 4dc23287835..e4a59fec5e0 100644 --- a/src/memory.h +++ b/src/memory.h @@ -52,7 +52,6 @@ void memory_deleter(T* ptr, FREE_FUNC free_func) { ptr->~T(); free_func(ptr); - return; } // Frees memory which was placed there with placement new. From 7e6a0c464bd0fce5b5d94f7d6bd4af3d4d282504 Mon Sep 17 00:00:00 2001 From: Myself <63040919+AliceRoselia@users.noreply.github.com> Date: Sat, 26 Apr 2025 22:00:36 +0700 Subject: [PATCH 0990/1309] Check only if good see. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 106400 W: 27863 L: 27444 D: 51093 Ptnml(0-2): 320, 12431, 27275, 12858, 316 https://tests.stockfishchess.org/tests/view/6806239498cd372e3aea5949 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 420534 W: 107594 L: 106494 D: 206446 Ptnml(0-2): 197, 45541, 117722, 46579, 228 https://tests.stockfishchess.org/tests/view/6806b4b3878abf56f9a0d4fc closes https://github.com/official-stockfish/Stockfish/pull/6020 bench: 1675758 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6c8eb09c71c..cac4abe4ad5 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -170,7 +170,7 @@ void MovePicker::score() { m.value += (*continuationHistory[5])[pc][to]; // bonus for checks - m.value += bool(pos.check_squares(pt) & to) * 16384; + m.value += (bool(pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384; // bonus for escaping from capture m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700 From 4b58079485dd1f7ee69959ec4e8c9f05502a32e6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 26 Apr 2025 19:36:08 +0300 Subject: [PATCH 0991/1309] Simplify and cleanup futility pruning for child nodes This patch removes (eval - beta) / 8 addition and adjusts constants accordingly, also moves every calculation into futility_margin function. Passed STC: https://tests.stockfishchess.org/tests/view/6806d00f878abf56f9a0d524 LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 483456 W: 124592 L: 124860 D: 234004 Ptnml(0-2): 1419, 57640, 123927, 57274, 1468 Passed LTC: https://tests.stockfishchess.org/tests/view/680cceb33629b02d74b1554c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 263868 W: 67076 L: 67104 D: 129688 Ptnml(0-2): 155, 28893, 73846, 28905, 135 closes https://github.com/official-stockfish/Stockfish/pull/6021 bench: 1618439 --- src/search.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d08418813df..79bb44c63e9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -71,12 +71,20 @@ namespace { // tests at these types of time controls. // Futility margin -Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 110 - 25 * noTtCutNode; +Value futility_margin(Depth d, + bool noTtCutNode, + bool improving, + bool oppWorsening, + int statScore, + int correctionValue) { + Value futilityMult = 98 - 22 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; + Value statScoreAddition = statScore / 339; + Value correctionAddition = correctionValue / 157363; - return futilityMult * d - improvingDeduction - worseningDeduction; + return futilityMult * d - improvingDeduction - worseningDeduction + statScoreAddition + + correctionAddition; } constexpr int futility_move_count(bool improving, Depth depth) { @@ -866,9 +874,9 @@ Value Search::Worker::search( // 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 / 301 + 37 + ((eval - beta) / 8) - - std::abs(correctionValue) / 139878 + && eval + - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening, + (ss - 1)->statScore, std::abs(correctionValue)) >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; From 27428a61c2fb6fd1ff1f8f0e88746e0680771730 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 26 Apr 2025 19:48:20 +0300 Subject: [PATCH 0992/1309] Allow some nodes to spawn even deeper lmr searches This includes nodes that were PvNode on a previous ply and only for non cutNodes and movecounts < 8. Passed STC: https://tests.stockfishchess.org/tests/view/680cdf2e3629b02d74b15576 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 123552 W: 31979 L: 31539 D: 60034 Ptnml(0-2): 322, 14449, 31803, 14871, 331 Passed LTC: https://tests.stockfishchess.org/tests/view/680cf09a3629b02d74b15599 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 114306 W: 29310 L: 28837 D: 56159 Ptnml(0-2): 51, 12247, 32090, 12708, 57 closes https://github.com/official-stockfish/Stockfish/pull/6022 bench: 1585741 --- src/search.cpp | 6 ++++-- src/search.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 79bb44c63e9..01f0c2c23f9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -708,6 +708,7 @@ Value Search::Worker::search( (ss + 2)->cutoffCnt = 0; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; + ss->isPvNode = PvNode; // Step 4. Transposition table lookup excludedMove = ss->excludedMove; @@ -1281,8 +1282,9 @@ Value Search::Worker::search( // std::clamp has been replaced by a more robust implementation. - Depth d = std::max( - 1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); + Depth d = std::max(1, std::min(newDepth - r / 1024, + newDepth + !allNode + (PvNode && !bestMove))) + + (!cutNode && (ss - 1)->isPvNode && moveCount < 8); ss->reduction = newDepth - d; diff --git a/src/search.h b/src/search.h index 6ef8322a859..6ab98f7c45e 100644 --- a/src/search.h +++ b/src/search.h @@ -76,6 +76,7 @@ struct Stack { int cutoffCnt; int reduction; bool isTTMove; + bool isPvNode; }; From 4e49f8dff99dfa3a7cfb8b8fd8c1d7c54e1291d2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 27 Feb 2025 12:43:58 -0800 Subject: [PATCH 0993/1309] Clean up search * Correct IIR scaling comments * Replace `(PvNode || cutNode)` with `!allNode` * Consistent formatting for scaler tags * Add comments to some recently-introduced LMR terms * Add comments on PCM bonus tweaks Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 389472 W: 102457 L: 102622 D: 184393 Ptnml(0-2): 1676, 41887, 107798, 41676, 1699 https://tests.stockfishchess.org/tests/view/67a0ea670774dfd78deb23cd closes https://github.com/official-stockfish/Stockfish/pull/5854 Bench: 1585741 --- src/search.cpp | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 01f0c2c23f9..bafccd0f5c1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -658,8 +658,7 @@ Value Search::Worker::search( Value bestValue, value, eval, maxValue, probCutBeta; bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, ttCapture; - int priorReduction = (ss - 1)->reduction; - (ss - 1)->reduction = 0; + int priorReduction; Piece movedPiece; ValueList capturesSearched; @@ -704,11 +703,13 @@ Value Search::Worker::search( assert(0 <= ss->ply && ss->ply < MAX_PLY); - bestMove = Move::none(); + Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; + bestMove = Move::none(); + priorReduction = (ss - 1)->reduction; + (ss - 1)->reduction = 0; + ss->statScore = 0; + ss->isPvNode = PvNode; (ss + 2)->cutoffCnt = 0; - Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; - ss->statScore = 0; - ss->isPvNode = PvNode; // Step 4. Transposition table lookup excludedMove = ss->excludedMove; @@ -927,8 +928,8 @@ 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 - 3 * PvNode) && !ttData.move) + // (*Scaler) Especially if they make IIR less aggressive. + if (depth >= 7 - 3 * PvNode && !allNode && !ttData.move) depth--; // Step 11. ProbCut @@ -1153,7 +1154,7 @@ Value Search::Worker::search( // and if the result is lower than ttValue minus a margin, then we will // extend the ttMove. Recursive singular search is avoided. - // (* Scaler) 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 @@ -1233,8 +1234,8 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling - r += 306 - moveCount * 34; - + r += 306; // Base reduction offset to compensate for other tweaks + r -= moveCount * 34; r -= std::abs(correctionValue) / 29696; if (PvNode && std::abs(bestValue) <= 2000) @@ -1280,18 +1281,14 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - - Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))) + (!cutNode && (ss - 1)->isPvNode && moveCount < 8); ss->reduction = newDepth - d; - value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); ss->reduction = 0; - // Do a full-depth search when reduced LMR search fails high if (value > alpha && d < newDepth) { @@ -1487,12 +1484,14 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = - (std::min(78 * depth - 312, 194) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) - + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) - + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) - + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) - + std::min(-(ss - 1)->statScore / 112, 303)); + int bonusScale = std::min(-(ss - 1)->statScore / 112, 303); + bonusScale += std::min(78 * depth - 312, 194); + bonusScale += 34 * !allNode; + bonusScale += 164 * ((ss - 1)->moveCount > 8); + bonusScale += 86 * (ss - 1)->isTTMove; + bonusScale += 86 * (ss->cutoffCnt <= 3); + bonusScale += 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100); + bonusScale += 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75); bonusScale = std::max(bonusScale, 0); @@ -1903,14 +1902,14 @@ void update_all_stats(const Position& pos, ValueList& quietsSearched, ValueList& capturesSearched, Depth depth, - Move TTMove, + Move ttMove, int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = std::min(141 * depth - 89, 1613) + 311 * (bestMove == TTMove); + int bonus = std::min(141 * depth - 89, 1613) + 311 * (bestMove == ttMove); int malus = std::min(695 * depth - 215, 2808) - 31 * (moveCount - 1); if (!pos.capture_stage(bestMove)) From fda269a2997033a01ed49d83337a2e0405cec805 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Wed, 9 Apr 2025 16:09:47 -0700 Subject: [PATCH 0994/1309] Introduce double incremental accumulator updates when we need to update an accumulator by two moves and the second move captures the piece moved in the first move, we can skip computing the middle accumulator and cancel a feature add with a feature remove to save work. Passed STC https://tests.stockfishchess.org/tests/view/67f70b1c31d7cf8afdc45f51 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 72800 W: 18878 L: 18529 D: 35393 Ptnml(0-2): 160, 7711, 20374, 7930, 225 closes https://github.com/official-stockfish/Stockfish/pull/5988 No functional change --- src/nnue/nnue_accumulator.cpp | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 2bf76f530b3..5c1128539f2 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -46,6 +46,13 @@ namespace Stockfish::Eval::NNUE { namespace { +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed); + template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, @@ -142,8 +149,27 @@ void AccumulatorStack::forward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = begin + 1; next < size; next++) + { + if (next + 1 < size) + { + auto& dp1 = accumulators[next].dirtyPiece; + auto& dp2 = accumulators[next + 1].dirtyPiece; + + if (dp2.dirty_num >= 2 && dp1.piece[0] == dp2.piece[1] && dp1.to[0] == dp2.from[1]) + { + const Square captureSq = dp1.to[0]; + dp1.to[0] = dp2.from[1] = SQ_NONE; + double_inc_update(featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); + dp1.to[0] = dp2.from[1] = captureSq; + + next++; + continue; + } + } update_accumulator_incremental( featureTransformer, ksq, accumulators[next], accumulators[next - 1]); + } assert((latest().acc()).computed[Perspective]); } @@ -240,6 +266,49 @@ auto make_accumulator_update_context(const FeatureTransformer& featu accumulatorTo}; } +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed) { + + assert(computed.acc().computed[Perspective]); + assert(!middle_state.acc().computed[Perspective]); + assert(!target_state.acc().computed[Perspective]); + + FeatureSet::IndexList removed, added; + FeatureSet::append_changed_indices(ksq, middle_state.dirtyPiece, removed, added); + // you can't capture a piece that was just involved in castling since the rook ends up + // in a square that the king passed + assert(added.size() < 2); + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, added); + + assert(added.size() == 1); + assert(removed.size() == 2 || removed.size() == 3); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1); + sf_assume(removed.size() == 2 || removed.size() == 3); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + if (removed.size() == 2) + { + updateContext.template apply(added[0], removed[0], removed[1]); + } + else + { + updateContext.template apply(added[0], removed[0], removed[1], + removed[2]); + } + + target_state.acc().computed[Perspective] = true; +} + template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, From f0de8dc0349bac56021a900910f14a00a729dbc6 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Mon, 21 Apr 2025 18:26:46 -0700 Subject: [PATCH 0995/1309] Simplify move ordering bonuses for putting piece en prise and escaping capture Now there is also a penalty for exposing knights and bishops to capture by a pawn. Passed STC: https://tests.stockfishchess.org/tests/view/68074379878abf56f9a0d5b1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 96512 W: 24841 L: 24687 D: 46984 Ptnml(0-2): 294, 11336, 24835, 11504, 287 Passed LTC: https://tests.stockfishchess.org/tests/view/6808954a878abf56f9a0d76d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 221328 W: 56271 L: 56255 D: 108802 Ptnml(0-2): 131, 24149, 62071, 24199, 114 closes https://github.com/official-stockfish/Stockfish/pull/6023 Bench: 1778227 --- src/movepick.cpp | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index cac4abe4ad5..71a5f9959e3 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -126,21 +126,20 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, - threatenedPieces; + [[maybe_unused]] Bitboard threatenedPieces, threatByLesser[4]; if constexpr (Type == QUIETS) { Color us = pos.side_to_move(); - threatenedByPawn = pos.attacks_by(~us); - threatenedByMinor = - pos.attacks_by(~us) | pos.attacks_by(~us) | threatenedByPawn; - threatenedByRook = pos.attacks_by(~us) | threatenedByMinor; + threatByLesser[0] = threatByLesser[1] = pos.attacks_by(~us); + threatByLesser[2] = + pos.attacks_by(~us) | pos.attacks_by(~us) | threatByLesser[0]; + threatByLesser[3] = pos.attacks_by(~us) | threatByLesser[2]; // Pieces threatened by pieces of lesser material value - threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook) - | (pos.pieces(us, ROOK) & threatenedByMinor) - | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn); + threatenedPieces = (pos.pieces(us, QUEEN) & threatByLesser[3]) + | (pos.pieces(us, ROOK) & threatByLesser[2]) + | (pos.pieces(us, KNIGHT, BISHOP) & threatByLesser[0]); } for (auto& m : *this) @@ -172,17 +171,15 @@ void MovePicker::score() { // bonus for checks m.value += (bool(pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384; - // bonus for escaping from capture - m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700 - : pt == ROOK && !(to & threatenedByMinor) ? 25600 - : !(to & threatenedByPawn) ? 14450 - : 0) - : 0; - - // malus for putting piece en prise - m.value -= (pt == QUEEN && bool(to & threatenedByRook) ? 49000 - : pt == ROOK && bool(to & threatenedByMinor) ? 24335 - : 0); + // penalty for moving to a square threatened by a lesser piece + // or bonus for escaping an attack by a lesser piece. + constexpr int bonus[4] = {144, 144, 256, 517}; + if (KNIGHT <= pt && pt <= QUEEN) + { + auto i = pt - 2; + int v = (threatByLesser[i] & to ? -95 : 100 * bool(threatByLesser[i] & from)); + m.value += bonus[i] * v; + } if (ply < LOW_PLY_HISTORY_SIZE) m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + 2 * ply); From b0a7a34d3fd9b7024abd64150d47405d1be1dd69 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 26 Apr 2025 14:38:21 -0700 Subject: [PATCH 0996/1309] Simplify malus calculation closes https://github.com/official-stockfish/Stockfish/pull/6024 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index bafccd0f5c1..bd2cb1cbcf1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1910,7 +1910,7 @@ void update_all_stats(const Position& pos, PieceType captured; int bonus = std::min(141 * depth - 89, 1613) + 311 * (bestMove == ttMove); - int malus = std::min(695 * depth - 215, 2808) - 31 * (moveCount - 1); + int malus = std::min(695 * depth - 184, 2839) - 31 * moveCount; if (!pos.capture_stage(bestMove)) { From 37cc2293efdc1c2b00f97d1a10f85f54e9c8ec64 Mon Sep 17 00:00:00 2001 From: Myself <63040919+AliceRoselia@users.noreply.github.com> Date: Sun, 27 Apr 2025 14:52:01 +0700 Subject: [PATCH 0997/1309] Replace complex probcut function with a precomputed table. Passed non-regression STC: https://tests.stockfishchess.org/tests/view/680de2683629b02d74b15b46 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 116000 W: 29864 L: 29742 D: 56394 Ptnml(0-2): 215, 11811, 33854, 11877, 243 closes https://github.com/official-stockfish/Stockfish/pull/6026 No functional change --- src/search.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bd2cb1cbcf1..e9dfb71723a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -111,17 +111,11 @@ 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); } -int adaptive_probcut_margin(Depth depth) { - // Base margin - constexpr int base = 180; - // Approximate log2(depth) using a fast lookup table - static constexpr int logTable[32] = {0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}; +constexpr int adaptiveProbcutMargin[32] = {148, 150, 212, 214, 276, 278, 280, 282, 344, 346, 348, + 350, 352, 354, 356, 358, 420, 422, 424, 426, 428, 430, + 430, 430, 430, 430, 430, 430, 430, 430, 430, 430}; - int logDepth = logTable[std::min(depth, 31)]; - return base + logDepth * 60 + std::min(10, (depth - 16) * 2); -}; void update_correction_history(const Position& pos, Stack* const ss, @@ -994,7 +988,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + adaptive_probcut_margin(depth); + probCutBeta = beta + adaptiveProbcutMargin[std::min(depth, 31)]; 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; From f98c178960a26e80bd7ffc9a0a280610fdaf92fd Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Apr 2025 14:42:35 +0300 Subject: [PATCH 0998/1309] Improve quiet moves bonus Inspired by an old test by Peregrine. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 453024 W: 117244 L: 116316 D: 219464 Ptnml(0-2): 1336, 53355, 116258, 54171, 1392 https://tests.stockfishchess.org/tests/view/680ccacc3629b02d74b15532 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 140550 W: 35990 L: 35462 D: 69098 Ptnml(0-2): 65, 15152, 39319, 15668, 71 https://tests.stockfishchess.org/tests/view/680d2ed73629b02d74b15691 closes https://github.com/official-stockfish/Stockfish/pull/6028 Bench: 1708152 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e9dfb71723a..fab0fd61741 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -839,7 +839,8 @@ Value Search::Worker::search( } // Use static evaluation difference to improve quiet move ordering - if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture + && (ttData.depth - 2) <= depth) { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1950, 1416) + 655; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1124 / 1024; From e5aa4b48c633eccdd6effda94583c52bfe436bb1 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Apr 2025 14:48:29 +0300 Subject: [PATCH 0999/1309] Simplify Evasion Move Scoring Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 160256 W: 41432 L: 41348 D: 77476 Ptnml(0-2): 485, 19034, 41028, 19074, 507 https://tests.stockfishchess.org/tests/view/680d242c3629b02d74b15662 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 103086 W: 26388 L: 26252 D: 50446 Ptnml(0-2): 41, 11174, 28982, 11300, 46 https://tests.stockfishchess.org/tests/view/680d47f83629b02d74b1571e closes https://github.com/official-stockfish/Stockfish/pull/6029 Bench: 1937261 --- src/movepick.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 71a5f9959e3..c8738c568a5 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -191,8 +191,7 @@ void MovePicker::score() { m.value = PieceValue[pos.piece_on(m.to_sq())] + (1 << 28); else m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] - + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()] - + (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()]; + + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()]; } } From 267fd8a3d5a19939e4026edabbe59db4f1966a10 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Apr 2025 15:03:33 +0300 Subject: [PATCH 1000/1309] Tweak History Bonus Inspired by @Ergodice , who came up first with the idea. Passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 18400 W: 4867 L: 4576 D: 8957 Ptnml(0-2): 52, 2052, 4714, 2317, 65 https://tests.stockfishchess.org/tests/view/68062a3c98cd372e3aea5959 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 454338 W: 116461 L: 115294 D: 222583 Ptnml(0-2): 198, 49139, 127346, 50270, 216 https://tests.stockfishchess.org/tests/view/6806347c98cd372e3aea5967 Passed VLTC non-reg: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 385970 W: 98401 L: 98546 D: 189023 Ptnml(0-2): 51, 38958, 115105, 38827, 44 https://tests.stockfishchess.org/tests/view/680cfe873629b02d74b155cf closes https://github.com/official-stockfish/Stockfish/pull/6030 Bench: 1715817 --- src/search.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fab0fd61741..a6a189fcab8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1962,12 +1962,14 @@ 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 * 829 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 800 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 1004 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), + bonus * (bonus > 0 ? 1094 : 790) / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 587 / 1024; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] + << bonus * (bonus > 0 ? 725 : 460) / 1024; } } From 3e26d3acc7ae2dba0ad9a47dc7bd3e36c109bcec Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 27 Apr 2025 16:31:54 +0300 Subject: [PATCH 1001/1309] Do more pruning in moves loop Effectively reverts one commit from some months ago. Passed VVLTC SPRT with STC bounds https://tests.stockfishchess.org/tests/view/680d39373629b02d74b156d7 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 405058 W: 104843 L: 104111 D: 196104 Ptnml(0-2): 35, 38029, 125672, 38755, 38 Passed VVLTC SPRT with LTC bounds https://tests.stockfishchess.org/tests/view/680d1a3b3629b02d74b1563d LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 57032 W: 14917 L: 14588 D: 27527 Ptnml(0-2): 6, 5202, 17768, 5537, 3 closes https://github.com/official-stockfish/Stockfish/pull/6031 Bench: 1643819 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a6a189fcab8..35a2b5de23f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1049,8 +1049,6 @@ Value Search::Worker::search( 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 @@ -1230,7 +1228,7 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling r += 306; // Base reduction offset to compensate for other tweaks - r -= moveCount * 34; + r -= moveCount * 66; r -= std::abs(correctionValue) / 29696; if (PvNode && std::abs(bestValue) <= 2000) From af3692b2d05201e3e4ba3c7c6a8dc5a8700e0ae4 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 27 Apr 2025 01:55:17 -0700 Subject: [PATCH 1002/1309] Simplify second probcut to linear function of depth Passed non-regression STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 57472 W: 14962 L: 14765 D: 27745 Ptnml(0-2): 140, 6715, 14817, 6936, 128 https://tests.stockfishchess.org/tests/view/680df1063629b02d74b15b69 Passed non-regression LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 88416 W: 22499 L: 22348 D: 43569 Ptnml(0-2): 31, 9565, 24874, 9698, 40 https://tests.stockfishchess.org/tests/view/680df3a93629b02d74b15b7d closes https://github.com/official-stockfish/Stockfish/pull/6035 Bench: 1792000 --- src/search.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 35a2b5de23f..b370f7ca96b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -111,12 +111,6 @@ 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); } - -constexpr int adaptiveProbcutMargin[32] = {148, 150, 212, 214, 276, 278, 280, 282, 344, 346, 348, - 350, 352, 354, 356, 358, 420, 422, 424, 426, 428, 430, - 430, 430, 430, 430, 430, 430, 430, 430, 430, 430}; - - void update_correction_history(const Position& pos, Stack* const ss, Search::Worker& workerThread, @@ -989,7 +983,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + adaptiveProbcutMargin[std::min(depth, 31)]; + probCutBeta = beta + 180 + depth * 20; 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; From 94e6c0498ff24d0a66fd0817fcbb88855a9b6116 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Apr 2025 21:40:45 +0300 Subject: [PATCH 1003/1309] VVLTC Tune Passed VVLTC with LTC bounds: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 12800 W: 3432 L: 3184 D: 6184 Ptnml(0-2): 1, 1098, 3954, 1346, 1 https://tests.stockfishchess.org/tests/view/680e255e3629b02d74b15d5e Passed VVLTC with STC bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 14402 W: 3865 L: 3625 D: 6912 Ptnml(0-2): 0, 1236, 4490, 1474, 1 https://tests.stockfishchess.org/tests/view/680e4dfb3629b02d74b15da6 Passed VVLTC third test (removing an unrelated change): LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 25584 W: 6670 L: 6398 D: 12516 Ptnml(0-2): 4, 2290, 7932, 2562, 4 https://tests.stockfishchess.org/tests/view/680e74223629b02d74b15def closes https://github.com/official-stockfish/Stockfish/pull/6036 Bench: 1857323 --- src/search.cpp | 190 ++++++++++++++++++++++++------------------------- 1 file changed, 95 insertions(+), 95 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b370f7ca96b..f673ba34044 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,11 +77,11 @@ Value futility_margin(Depth d, bool oppWorsening, int statScore, int correctionValue) { - Value futilityMult = 98 - 22 * noTtCutNode; + Value futilityMult = 105 - 23 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; - Value statScoreAddition = statScore / 339; - Value correctionAddition = correctionValue / 157363; + Value statScoreAddition = statScore / 335; + Value correctionAddition = correctionValue / 149902; return futilityMult * d - improvingDeduction - worseningDeduction + statScoreAddition + correctionAddition; @@ -102,7 +102,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 7685 * pcv + 7495 * micv + 9144 * (wnpcv + bnpcv) + 6469 * cntcv; + return 7696 * pcv + 7689 * micv + 9708 * (wnpcv + bnpcv) + 6978 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -118,11 +118,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 = 162; + static constexpr int nonPawnWeight = 172; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] << bonus * 111 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 151 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] @@ -130,20 +130,20 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 143 / 128; + << bonus * 141 / 128; } int risk_tolerance(Value v) { // Returns (some constant of) second derivative of sigmoid. static constexpr auto sigmoid_d2 = [](int x, int y) { - return 644800 * x / ((x * x + 3 * y * y) * y); + return 631760 * x / ((x * x + 3 * y * y) * y); }; // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) // The loss rate is 1/(1+exp((v+a)/b)) - int a = 356; - int b = 123; + int a = 340; + int b = 122; // guard against overflow assert(abs(v) + a <= std::numeric_limits::max() / 644800); @@ -330,7 +330,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(92); + lowPlyHistory.fill(86); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -366,13 +366,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11834; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11134; Value avg = rootMoves[pvIdx].averageScore; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 138 * avg / (std::abs(avg) + 84); + optimism[us] = 137 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -578,11 +578,11 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(66); - lowPlyHistory.fill(105); - captureHistory.fill(-646); - pawnHistory.fill(-1262); - pawnCorrectionHistory.fill(6); + mainHistory.fill(67); + lowPlyHistory.fill(107); + captureHistory.fill(-688); + pawnHistory.fill(-1287); + pawnCorrectionHistory.fill(5); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory.fill(0); @@ -590,16 +590,16 @@ void Search::Worker::clear() { for (auto& to : continuationCorrectionHistory) for (auto& h : to) - h.fill(5); + h.fill(8); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-468); + h.fill(-473); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2954 / 128.0 * std::log(i)); + reductions[i] = int(2796 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -727,11 +727,11 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(120 * depth - 75, 1241)); + std::min(125 * depth - 77, 1157)); // 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, -2200); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2301); } // Partial workaround for the graph history interaction problem @@ -836,11 +836,11 @@ Value Search::Worker::search( if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && (ttData.depth - 2) <= depth) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1950, 1416) + 655; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1124 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1858, 1492) + 661; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1057 / 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 * 1196 / 1024; + << bonus * 1266 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -853,13 +853,13 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 188) + if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 175) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 461 - 315 * depth * depth) + if (!PvNode && eval < alpha - 486 - 325 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node @@ -874,13 +874,13 @@ Value Search::Worker::search( // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 19 * depth + 418 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 19 * depth + 389 && !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) / 232, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 213, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -918,13 +918,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 less aggressive. - if (depth >= 7 - 3 * PvNode && !allNode && !ttData.move) + if ((!allNode && depth >= (PvNode ? 5 : 7)) && !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 + 185 - 58 * improving; + probCutBeta = beta + 201 - 58 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -1047,7 +1047,7 @@ Value Search::Worker::search( // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 979; + r += 968; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1069,15 +1069,15 @@ Value Search::Worker::search( // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 242 + 230 * lmrDepth - + PieceValue[capturedPiece] + 133 * captHist / 1024; + Value futilityValue = ss->staticEval + 232 + 224 * lmrDepth + + PieceValue[capturedPiece] + 131 * captHist / 1024; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 32, -138 * depth, 135 * depth); - if (!pos.see_ge(move, -154 * depth - seeHist)) + int seeHist = std::clamp(captHist / 31, -137 * depth, 125 * depth); + if (!pos.see_ge(move, -158 * depth - seeHist)) { bool skip = true; if (depth > 2 && !capture && givesCheck && alpha < 0 @@ -1100,16 +1100,16 @@ Value Search::Worker::search( + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4348 * depth) + if (history < -4229 * depth) continue; history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3593; + lmrDepth += history / 3388; Value futilityValue = - ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth - + 103 * (bestValue < ss->staticEval - 128 && ss->staticEval > alpha - 50); + ss->staticEval + (bestMove ? 46 : 138) + 117 * lmrDepth + + 102 * (bestValue < ss->staticEval - 127 && ss->staticEval > alpha - 50); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning @@ -1145,11 +1145,11 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 6 - (thisThread->completedDepth > 29) + ss->ttPv + && depth >= 6 - (thisThread->completedDepth > 27) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (59 + 77 * (ss->ttPv && !PvNode)) * depth / 54; + Value singularBeta = ttData.value - (58 + 76 * (ss->ttPv && !PvNode)) * depth / 57; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1159,12 +1159,12 @@ Value Search::Worker::search( if (value < singularBeta) { - int corrValAdj1 = std::abs(correctionValue) / 248873; - int corrValAdj2 = std::abs(correctionValue) / 255331; - int doubleMargin = - 262 * PvNode - 188 * !ttCapture - corrValAdj1 - ttMoveHistory / 128; + int corrValAdj1 = std::abs(correctionValue) / 248400; + int corrValAdj2 = std::abs(correctionValue) / 249757; + int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj1 + - 997 * ttMoveHistory / 131072; int tripleMargin = - 88 + 265 * PvNode - 256 * !ttCapture + 93 * ss->ttPv - corrValAdj2; + 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - corrValAdj2; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1216,49 +1216,49 @@ Value Search::Worker::search( // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2381 + PvNode * 1008 + (ttData.value > alpha) * 880 - + (ttData.depth >= depth) * (1022 + cutNode * 1140); + r -= 2437 + PvNode * 926 + (ttData.value > alpha) * 901 + + (ttData.depth >= depth) * (943 + cutNode * 1180); // These reduction adjustments have no proven non-linear scaling - r += 306; // Base reduction offset to compensate for other tweaks + r += 316; // Base reduction offset to compensate for other tweaks r -= moveCount * 66; - r -= std::abs(correctionValue) / 29696; + r -= std::abs(correctionValue) / 28047; - if (PvNode && std::abs(bestValue) <= 2000) + if (PvNode && std::abs(bestValue) <= 2078) r -= risk_tolerance(bestValue); // Increase reduction for cut nodes if (cutNode) - r += 2784 + 1038 * !ttData.move; + r += 2864 + 966 * !ttData.move; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) - r += 1171 + (depth < 8) * 985; + r += 1210 + (depth < 8) * 963; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1042 + allNode * 864; + r += 1036 + allNode * 848; // For first picked move (ttMove) reduce reduction else if (ss->isTTMove) - r -= 1937; + r -= 2006; if (capture) ss->statScore = - 846 * int(PieceValue[pos.captured_piece()]) / 128 + 826 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 4822; + - 5030; else if (ss->inCheck) ss->statScore = thisThread->mainHistory[us][move.from_to()] - + (*contHist[0])[movedPiece][move.to_sq()] - 2771; + + (*contHist[0])[movedPiece][move.to_sq()] - 2766; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3271; + + (*contHist[1])[movedPiece][move.to_sq()] - 3206; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 1582 / 16384; + r -= ss->statScore * 826 / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1281,7 +1281,7 @@ Value Search::Worker::search( { // 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 + 43 + 2 * newDepth); + const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1290,12 +1290,12 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - update_continuation_histories(ss, movedPiece, move.to_sq(), 1600); + update_continuation_histories(ss, movedPiece, move.to_sq(), 1508); } else if (value > alpha && value < bestValue + 9) { newDepth--; - if (value < bestValue + 3) + if (value < bestValue + 4) newDepth--; } } @@ -1305,7 +1305,7 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1156; + r += 1128; r -= ttMoveHistory / 8; @@ -1314,7 +1314,7 @@ Value Search::Worker::search( // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3495) - (r > 5510 && newDepth > 2), !cutNode); + newDepth - (r > 3564) - (r > 4969 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1463,7 +1463,7 @@ Value Search::Worker::search( ttData.move, moveCount); if (!PvNode) { - int bonus = ss->isTTMove ? 800 : -870; + int bonus = ss->isTTMove ? 800 : -879; ttMoveHistory << bonus; } } @@ -1471,28 +1471,28 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = std::min(-(ss - 1)->statScore / 112, 303); - bonusScale += std::min(78 * depth - 312, 194); - bonusScale += 34 * !allNode; - bonusScale += 164 * ((ss - 1)->moveCount > 8); + int bonusScale = std::min(-(ss - 1)->statScore / 113, 293); + bonusScale += std::min(73 * depth - 347, 184); + bonusScale += 33 * !allNode; + bonusScale += 174 * ((ss - 1)->moveCount > 8); bonusScale += 86 * (ss - 1)->isTTMove; - bonusScale += 86 * (ss->cutoffCnt <= 3); - bonusScale += 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100); - bonusScale += 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75); + bonusScale += 90 * (ss->cutoffCnt <= 3); + bonusScale += 144 * (!ss->inCheck && bestValue <= ss->staticEval - 104); + bonusScale += 128 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(160 * depth - 99, 1492) * bonusScale; + const int scaledBonus = std::min(159 * depth - 94, 1501) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 388 / 32768); + scaledBonus * 412 / 32768); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << scaledBonus * 212 / 32768; + << scaledBonus * 203 / 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 * 1055 / 32768; + << scaledBonus * 1040 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1500,7 +1500,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1100; + thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1080; } if (PvNode) @@ -1652,7 +1652,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 359; + futilityBase = ss->staticEval + 376; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1714,11 +1714,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 6290) + <= 6218) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -75)) + if (!pos.see_ge(move, -74)) continue; } @@ -1800,7 +1800,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 * 764 / rootDelta + !i * reductionScale * 191 / 512 + 1087; + return reductionScale - delta * 794 / rootDelta + !i * reductionScale * 205 / 512 + 1086; } // elapsed() returns the time elapsed since the search started. If the @@ -1896,35 +1896,35 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = std::min(141 * depth - 89, 1613) + 311 * (bestMove == ttMove); - int malus = std::min(695 * depth - 184, 2839) - 31 * moveCount; + int bonus = std::min(143 * depth - 89, 1496) + 302 * (bestMove == ttMove); + int malus = std::min(737 * depth - 179, 3141) - 30 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1129 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1059 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1246 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1310 / 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 * 1187 / 1024; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1213 / 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 * 987 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 980 / 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 * 1377 / 1024; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1388 / 1024; } } @@ -1933,7 +1933,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, 1103}, {2, 659}, {3, 323}, {4, 533}, {5, 121}, {6, 474}}}; + {{1, 1092}, {2, 631}, {3, 294}, {4, 517}, {5, 126}, {6, 445}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1954,14 +1954,14 @@ 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 * 800 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 792 / 1024; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), - bonus * (bonus > 0 ? 1094 : 790) / 1024); + bonus * (bonus > 0 ? 1082 : 784) / 1024); int pIndex = pawn_structure_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << bonus * (bonus > 0 ? 725 : 460) / 1024; + << bonus * (bonus > 0 ? 705 : 450) / 1024; } } From 0f905b4e88dba8dac26d7c457707507eb8479477 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Mon, 21 Apr 2025 08:25:18 +0200 Subject: [PATCH 1004/1309] Preserve all moves in movepicker Simplifies method otherPieceTypesMobile quite a bit and makes it more precise. More precise because capturesSearched list not always contains all processed captures: although extremely rare, it can happen that a 'good' capture get pruned at step 14 and doesn't make it to capturesSearched. (functional change at higher depths) passed STC-simplification bounds https://tests.stockfishchess.org/tests/view/68025974cd501869c6698153 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 273664 W: 15658 L: 15681 D: 242325 Ptnml(0-2): 166, 10368, 115802, 10315, 181 passed LTC-simplification bounds https://tests.stockfishchess.org/tests/view/6804b86acd501869c6698673 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 96780 W: 24547 L: 24419 D: 47814 Ptnml(0-2): 30, 8466, 31286, 8562, 46 Applied changes requested by disservin & retested to be on the safe side STC: https://tests.stockfishchess.org/tests/view/6806110698cd372e3aea5919 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 107392 W: 27739 L: 27606 D: 52047 Ptnml(0-2): 266, 10867, 31306, 10982, 275 LTC: https://tests.stockfishchess.org/tests/view/6806346198cd372e3aea5965 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 233484 W: 59106 L: 59103 D: 115275 Ptnml(0-2): 116, 22787, 70939, 22778, 122 closes https://github.com/official-stockfish/Stockfish/pull/6005 Bench: 1857323 --- src/movepick.cpp | 42 +++++++++++++++++++----------------------- src/movepick.h | 8 ++------ src/search.cpp | 9 +++++---- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index c8738c568a5..0360c2bde7c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -19,8 +19,8 @@ #include "movepick.h" #include -#include #include +#include #include "bitboard.h" #include "misc.h" @@ -223,6 +223,7 @@ Move MovePicker::next_move() { case QSEARCH_TT : case PROBCUT_TT : ++stage; + cur = moves + 1; return ttMove; case CAPTURE_INIT : @@ -238,9 +239,12 @@ Move MovePicker::next_move() { case GOOD_CAPTURE : if (select([&]() { - // Move losing capture to endBadCaptures to be tried later - return pos.see_ge(*cur, -cur->value / 18) ? true - : (*endBadCaptures++ = *cur, false); + if (!pos.see_ge(*cur, -cur->value / 18)) + { + std::swap(*endBadCaptures++, *cur); + return false; + } + return true; })) return *(cur - 1); @@ -250,7 +254,6 @@ Move MovePicker::next_move() { case QUIET_INIT : if (!skipQuiets) { - cur = endBadCaptures; endMoves = beginBadQuiets = endBadQuiets = generate(pos, cur); score(); @@ -317,30 +320,23 @@ Move MovePicker::next_move() { void MovePicker::skip_quiet_moves() { skipQuiets = true; } -bool MovePicker::otherPieceTypesMobile(PieceType pt, ValueList& capturesSearched) { - if (stage != GOOD_QUIET && stage != BAD_QUIET) - return true; +bool MovePicker::other_piece_types_mobile(PieceType pt) { + assert(stage == GOOD_QUIET || stage == BAD_QUIET || stage == EVASION); - // verify good captures - for (std::size_t i = 0; i < capturesSearched.size(); i++) - if (type_of(pos.moved_piece(capturesSearched[i])) != pt) - { - if (type_of(pos.moved_piece(capturesSearched[i])) != KING) - return true; - if (pos.legal(capturesSearched[i])) - return true; - } - - // now verify bad captures and quiets - for (ExtMove* c = moves; c < endBadQuiets; ++c) - if (type_of(pos.moved_piece(*c)) != pt) + // verify all generated captures and quiets + for (ExtMove* m = moves; m < endMoves; ++m) + { + if (*m && type_of(pos.moved_piece(*m)) != pt) { - if (type_of(pos.moved_piece(*c)) != KING) + if (type_of(pos.moved_piece(*m)) != KING) return true; - if (pos.legal(*c)) + if (pos.legal(*m)) return true; } + } return false; } +void MovePicker::mark_current_illegal() { *(cur - 1) = Move::none(); } + } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index dfafe69a58e..72a6f4e1cb2 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -19,8 +19,6 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED -#include - #include "history.h" #include "movegen.h" #include "types.h" @@ -29,9 +27,6 @@ namespace Stockfish { class Position; -template -class ValueList; - // The MovePicker class is used to pick one pseudo-legal move at a time from the // current position. The most important method is next_move(), which emits one // new pseudo-legal move on every call, until there are no moves left, when @@ -55,7 +50,8 @@ class MovePicker { MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(); void skip_quiet_moves(); - bool otherPieceTypesMobile(PieceType pt, ValueList& capturesSearched); + bool other_piece_types_mobile(PieceType pt); + void mark_current_illegal(); private: template diff --git a/src/search.cpp b/src/search.cpp index f673ba34044..05d522b1915 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1011,8 +1011,10 @@ Value Search::Worker::search( // Check for legality if (!pos.legal(move)) + { + mp.mark_current_illegal(); continue; - + } // At root obey the "searchmoves" option and skip moves not listed in Root // Move List. In MultiPV mode we also skip PV moves that have been already // searched and those of lower "TB rank" if we are in a TB root position. @@ -1084,9 +1086,8 @@ Value Search::Worker::search( && pos.non_pawn_material(us) == PieceValue[movedPiece] && PieceValue[movedPiece] >= RookValue && !(PseudoAttacks[KING][pos.square(us)] & move.from_sq())) - skip = mp.otherPieceTypesMobile( - type_of(movedPiece), - capturesSearched); // if the opponent captures last mobile piece it might be stalemate + // if the opponent captures last mobile piece it might be stalemate + skip = mp.other_piece_types_mobile(type_of(movedPiece)); if (skip) continue; From ed6b8d179a42414151fa1da69cebe1f2c8cfdd20 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 28 Apr 2025 16:46:30 +0300 Subject: [PATCH 1005/1309] Refactor futility_margin a small term to the futility calculation that depends on eval - beta. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 203136 W: 52797 L: 52239 D: 98100 Ptnml(0-2): 549, 23827, 52255, 24391, 546 https://tests.stockfishchess.org/tests/view/680e84a43629b02d74b15e2e Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 100356 W: 25950 L: 25507 D: 48899 Ptnml(0-2): 35, 10683, 28302, 11120, 38 https://tests.stockfishchess.org/tests/view/680ebcb03629b02d74b16040 closes https://github.com/official-stockfish/Stockfish/pull/6009 closes https://github.com/official-stockfish/Stockfish/pull/6041 Bench: 1815939 Co-authored-by: Michael Chaly Co-authored-by: xu-shawn <50402888+xu-shawn@users.noreply.github.com> --- src/search.cpp | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 05d522b1915..870cff54b7b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -70,23 +70,6 @@ namespace { // 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, - int statScore, - int correctionValue) { - Value futilityMult = 105 - 23 * noTtCutNode; - Value improvingDeduction = improving * futilityMult * 2; - Value worseningDeduction = oppWorsening * futilityMult / 3; - Value statScoreAddition = statScore / 335; - Value correctionAddition = correctionValue / 149902; - - return futilityMult * d - improvingDeduction - worseningDeduction + statScoreAddition - + correctionAddition; -} - constexpr int futility_move_count(bool improving, Depth depth) { return (3 + depth * depth) / (2 - improving); } @@ -864,13 +847,23 @@ Value Search::Worker::search( // 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, std::abs(correctionValue)) - >= beta - && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) - return beta + (eval - beta) / 3; + { + auto futility_margin = [&](Depth d) { + Value futilityMult = 105 - 23 * (cutNode && !ss->ttHit); + Value improvingDeduction = improving * futilityMult * 2; + Value worseningDeduction = opponentWorsening * futilityMult / 3; + + return futilityMult * d // + - improvingDeduction // + - worseningDeduction // + + (ss - 1)->statScore / 335 // + + std::abs(correctionValue) / 149902; + }; + + if (!ss->ttPv && depth < 14 && eval + (eval - beta) / 8 - futility_margin(depth) >= 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 From 63c6f22627136dc1d900b5fef1c2cb415ab855e1 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Fri, 11 Apr 2025 11:58:20 -0700 Subject: [PATCH 1006/1309] Simplify DirtyPiece Simplifies the DirtyPiece struct. passed STC: https://tests.stockfishchess.org/tests/view/68054c9798cd372e3aea05d7 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 36608 W: 9641 L: 9437 D: 17530 Ptnml(0-2): 89, 3630, 10668, 3822, 95 passed LTC: https://tests.stockfishchess.org/tests/view/6805514598cd372e3aea0783 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 127620 W: 31993 L: 31894 D: 63733 Ptnml(0-2): 42, 10983, 41665, 11074, 46 closes https://github.com/official-stockfish/Stockfish/pull/6016 No functional change --- src/nnue/features/half_ka_v2_hm.cpp | 18 ++++++------ src/nnue/nnue_accumulator.cpp | 12 ++++---- src/position.cpp | 44 ++++++++++++++--------------- src/types.h | 20 ++++++------- 4 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index eb3c7e6a701..c91da2cc80b 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -58,13 +58,15 @@ void HalfKAv2_hm::append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added) { - for (int i = 0; i < dp.dirty_num; ++i) - { - if (dp.from[i] != SQ_NONE) - removed.push_back(make_index(dp.from[i], dp.piece[i], ksq)); - if (dp.to[i] != SQ_NONE) - added.push_back(make_index(dp.to[i], dp.piece[i], ksq)); - } + removed.push_back(make_index(dp.from, dp.pc, ksq)); + if (dp.to != SQ_NONE) + added.push_back(make_index(dp.to, dp.pc, ksq)); + + if (dp.remove_sq != SQ_NONE) + removed.push_back(make_index(dp.remove_sq, dp.remove_pc, ksq)); + + if (dp.add_sq != SQ_NONE) + added.push_back(make_index(dp.add_sq, dp.add_pc, ksq)); } // Explicit template instantiations @@ -78,7 +80,7 @@ template void HalfKAv2_hm::append_changed_indices(Square ksq, IndexList& added); bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) { - return dirtyPiece.piece[0] == make_piece(perspective, KING); + return dirtyPiece.pc == make_piece(perspective, KING); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 5c1128539f2..83128436486 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -152,16 +152,16 @@ void AccumulatorStack::forward_update_incremental( { if (next + 1 < size) { - auto& dp1 = accumulators[next].dirtyPiece; - auto& dp2 = accumulators[next + 1].dirtyPiece; + DirtyPiece& dp1 = accumulators[next].dirtyPiece; + DirtyPiece& dp2 = accumulators[next + 1].dirtyPiece; - if (dp2.dirty_num >= 2 && dp1.piece[0] == dp2.piece[1] && dp1.to[0] == dp2.from[1]) + if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq) { - const Square captureSq = dp1.to[0]; - dp1.to[0] = dp2.from[1] = SQ_NONE; + const Square captureSq = dp1.to; + dp1.to = dp2.remove_sq = SQ_NONE; double_inc_update(featureTransformer, ksq, accumulators[next], accumulators[next + 1], accumulators[next - 1]); - dp1.to[0] = dp2.from[1] = captureSq; + dp1.to = dp2.remove_sq = captureSq; next++; continue; diff --git a/src/position.cpp b/src/position.cpp index 85ade69a928..9f023b5fe41 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -706,9 +706,6 @@ DirtyPiece Position::do_move(Move m, ++st->rule50; ++st->pliesFromNull; - DirtyPiece dp; - dp.dirty_num = 1; - Color us = sideToMove; Color them = ~us; Square from = m.from_sq(); @@ -716,6 +713,12 @@ DirtyPiece Position::do_move(Move m, Piece pc = piece_on(from); Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + DirtyPiece dp; + dp.pc = pc; + dp.from = from; + dp.to = to; + dp.add_sq = SQ_NONE; + assert(color_of(pc) == us); assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us)); assert(type_of(captured) != KING); @@ -762,10 +765,8 @@ DirtyPiece Position::do_move(Move m, st->minorPieceKey ^= Zobrist::psq[captured][capsq]; } - dp.dirty_num = 2; // 1 piece moved, 1 piece captured - dp.piece[1] = captured; - dp.from[1] = capsq; - dp.to[1] = SQ_NONE; + dp.remove_pc = captured; + dp.remove_sq = capsq; // Update board and piece lists remove_piece(capsq); @@ -776,6 +777,8 @@ DirtyPiece Position::do_move(Move m, // Reset rule 50 counter st->rule50 = 0; } + else + dp.remove_sq = SQ_NONE; // Update hash key k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; @@ -798,9 +801,6 @@ DirtyPiece Position::do_move(Move m, // Move the piece. The tricky Chess960 castling is handled earlier if (m.type_of() != CASTLING) { - dp.piece[0] = pc; - dp.from[0] = from; - dp.to[0] = to; move_piece(from, to); } @@ -827,12 +827,9 @@ DirtyPiece Position::do_move(Move m, remove_piece(to); put_piece(promotion, to); - // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE - dp.to[0] = SQ_NONE; - dp.piece[dp.dirty_num] = promotion; - dp.from[dp.dirty_num] = SQ_NONE; - dp.to[dp.dirty_num] = to; - dp.dirty_num++; + dp.add_pc = promotion; + dp.add_sq = to; + dp.to = SQ_NONE; // Update hash keys // Zobrist::psq[pc][to] is zero, so we don't need to clear it @@ -899,6 +896,10 @@ DirtyPiece Position::do_move(Move m, assert(pos_is_ok()); + assert(dp.pc != NO_PIECE); + assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE)); + assert(dp.from != SQ_NONE); + assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING)); return dp; } @@ -981,13 +982,10 @@ void Position::do_castling( if (Do) { - dp->piece[0] = make_piece(us, KING); - dp->from[0] = from; - dp->to[0] = to; - dp->piece[1] = make_piece(us, ROOK); - dp->from[1] = rfrom; - dp->to[1] = rto; - dp->dirty_num = 2; + dp->to = to; + dp->remove_pc = dp->add_pc = make_piece(us, ROOK); + dp->remove_sq = rfrom; + dp->add_sq = rto; } // Remove both pieces first since squares could overlap in Chess960 diff --git a/src/types.h b/src/types.h index a76d00336f3..6cd6ee74446 100644 --- a/src/types.h +++ b/src/types.h @@ -276,18 +276,14 @@ enum Rank : int { // Keep track of what a move changes on the board (used by NNUE) struct DirtyPiece { - - // Number of changed pieces - int dirty_num; - - // Max 3 pieces can change in one move. A promotion with capture moves - // both the pawn and the captured piece to SQ_NONE and the piece promoted - // to from SQ_NONE to the capture square. - Piece piece[3]; - - // From and to squares, which may be SQ_NONE - Square from[3]; - Square to[3]; + Piece pc; // this is never allowed to be NO_PIECE + Square from, to; // to should be SQ_NONE for promotions + + // if {add,remove}_sq is SQ_NONE, {add,remove}_pc is allowed to be + // uninitialized + // castling uses add_sq and remove_sq to remove and add the rook + Square remove_sq, add_sq; + Piece remove_pc, add_pc; }; #define ENABLE_INCR_OPERATORS_ON(T) \ From 81cc004060fd8e2b32286203e0366d0b5abdf8c8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 27 Apr 2025 01:49:43 -0700 Subject: [PATCH 1007/1309] Remove risk tolerance Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 379328 W: 97567 L: 97724 D: 184037 Ptnml(0-2): 909, 44861, 98314, 44638, 942 https://tests.stockfishchess.org/tests/view/680defc63629b02d74b15b62 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 160752 W: 40762 L: 40685 D: 79305 Ptnml(0-2): 60, 17548, 45091, 17609, 68 https://tests.stockfishchess.org/tests/view/680e8ff43629b02d74b15e65 closes https://github.com/official-stockfish/Stockfish/pull/6037 Bench: 1897340 --- src/search.cpp | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 870cff54b7b..12de7a679ed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -116,29 +115,6 @@ void update_correction_history(const Position& pos, << bonus * 141 / 128; } -int risk_tolerance(Value v) { - // Returns (some constant of) second derivative of sigmoid. - static constexpr auto sigmoid_d2 = [](int x, int y) { - return 631760 * x / ((x * x + 3 * y * y) * y); - }; - - // a and b are the crude approximation of the wdl model. - // The win rate is: 1/(1+exp((a-v)/b)) - // The loss rate is 1/(1+exp((v+a)/b)) - int a = 340; - int b = 122; - - // guard against overflow - assert(abs(v) + a <= std::numeric_limits::max() / 644800); - - // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) - // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) - int winning_risk = sigmoid_d2(v - a, b); - int losing_risk = sigmoid_d2(v + a, b); - - return -(winning_risk + losing_risk) * 32; -} - // 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); @@ -1219,9 +1195,6 @@ Value Search::Worker::search( r -= moveCount * 66; r -= std::abs(correctionValue) / 28047; - if (PvNode && std::abs(bestValue) <= 2078) - r -= risk_tolerance(bestValue); - // Increase reduction for cut nodes if (cutNode) r += 2864 + 966 * !ttData.move; From b73c8982df3da4a811845212040654342886a79e Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 28 Apr 2025 02:48:31 +0300 Subject: [PATCH 1008/1309] Another scaling revert Revert recent passer because it seems to not scale for longer time controls. Adjust comments accordingly. Passed VVLTC SPRT with STC bounds: https://tests.stockfishchess.org/tests/view/680ddff73629b02d74b15b3f LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 198316 W: 51317 L: 50846 D: 96153 Ptnml(0-2): 17, 18459, 61737, 18926, 19 Passed VVLTC SPRT with LTC bounds: https://tests.stockfishchess.org/tests/view/680d5b7e3629b02d74b15a21 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 123274 W: 31738 L: 31283 D: 60253 Ptnml(0-2): 7, 11444, 38282, 11895, 9 closes https://github.com/official-stockfish/Stockfish/pull/6038 Bench: 2173845 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 12de7a679ed..b7c6d8e3bd7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1244,6 +1244,8 @@ Value Search::Worker::search( ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high + // (*Scaler) Usually doing more shallower searches + // doesn't scale well to longer TCs if (value > alpha && d < newDepth) { // Adjust full-depth search based on LMR results - if the result was @@ -1260,11 +1262,7 @@ Value Search::Worker::search( update_continuation_histories(ss, movedPiece, move.to_sq(), 1508); } else if (value > alpha && value < bestValue + 9) - { newDepth--; - if (value < bestValue + 4) - newDepth--; - } } // Step 18. Full-depth search when LMR is skipped From 7afd9e859df87625e3474415dcf49ef0959d238e Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 27 Apr 2025 18:13:58 -0700 Subject: [PATCH 1009/1309] Simplify MovePicker::score() by using piece types for clarity STC https://tests.stockfishchess.org/tests/view/680ed6cc3629b02d74b160bf LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 200128 W: 51838 L: 51802 D: 96488 Ptnml(0-2): 457, 19956, 59247, 19902, 502 closes https://github.com/official-stockfish/Stockfish/pull/6039 No functional change --- src/movepick.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0360c2bde7c..39caf8e67c3 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -126,20 +126,20 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); - [[maybe_unused]] Bitboard threatenedPieces, threatByLesser[4]; + [[maybe_unused]] Bitboard threatenedPieces, threatByLesser[QUEEN + 1]; if constexpr (Type == QUIETS) { Color us = pos.side_to_move(); - threatByLesser[0] = threatByLesser[1] = pos.attacks_by(~us); - threatByLesser[2] = - pos.attacks_by(~us) | pos.attacks_by(~us) | threatByLesser[0]; - threatByLesser[3] = pos.attacks_by(~us) | threatByLesser[2]; + threatByLesser[KNIGHT] = threatByLesser[BISHOP] = pos.attacks_by(~us); + threatByLesser[ROOK] = + pos.attacks_by(~us) | pos.attacks_by(~us) | threatByLesser[KNIGHT]; + threatByLesser[QUEEN] = pos.attacks_by(~us) | threatByLesser[ROOK]; // Pieces threatened by pieces of lesser material value - threatenedPieces = (pos.pieces(us, QUEEN) & threatByLesser[3]) - | (pos.pieces(us, ROOK) & threatByLesser[2]) - | (pos.pieces(us, KNIGHT, BISHOP) & threatByLesser[0]); + threatenedPieces = (pos.pieces(us, QUEEN) & threatByLesser[QUEEN]) + | (pos.pieces(us, ROOK) & threatByLesser[ROOK]) + | (pos.pieces(us, KNIGHT, BISHOP) & threatByLesser[KNIGHT]); } for (auto& m : *this) @@ -173,12 +173,11 @@ void MovePicker::score() { // penalty for moving to a square threatened by a lesser piece // or bonus for escaping an attack by a lesser piece. - constexpr int bonus[4] = {144, 144, 256, 517}; if (KNIGHT <= pt && pt <= QUEEN) { - auto i = pt - 2; - int v = (threatByLesser[i] & to ? -95 : 100 * bool(threatByLesser[i] & from)); - m.value += bonus[i] * v; + static constexpr int bonus[QUEEN + 1] = {0, 0, 144, 144, 256, 517}; + int v = (threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from)); + m.value += bonus[pt] * v; } if (ply < LOW_PLY_HISTORY_SIZE) From e9925b122f2704c9fdb719110bfaaae7253c3f4c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 27 Apr 2025 20:12:25 -0700 Subject: [PATCH 1010/1309] Simplify ttCapture LMR Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 51104 W: 13389 L: 13184 D: 24531 Ptnml(0-2): 182, 5940, 13068, 6215, 147 https://tests.stockfishchess.org/tests/view/680ef2503629b02d74b16498 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 73350 W: 18804 L: 18638 D: 35908 Ptnml(0-2): 30, 7906, 20639, 8068, 32 https://tests.stockfishchess.org/tests/view/6810510e3629b02d74b1668a closes https://github.com/official-stockfish/Stockfish/pull/6044 Bench: 1805151 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b7c6d8e3bd7..81c25feccdc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1200,7 +1200,7 @@ Value Search::Worker::search( r += 2864 + 966 * !ttData.move; // Increase reduction if ttMove is a capture but the current move is not a capture - if (ttCapture && !capture) + if (ttCapture) r += 1210 + (depth < 8) * 963; // Increase reduction if next ply has a lot of fail high From 63a2ab1510d81b68614d268234291982249bbe77 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 3 May 2025 14:21:47 -0700 Subject: [PATCH 1011/1309] Simplify Captures scoring STC https://tests.stockfishchess.org/tests/view/680ee3b43629b02d74b160f3 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 200896 W: 52089 L: 52048 D: 96759 Ptnml(0-2): 592, 23821, 51589, 23846, 600 LTC https://tests.stockfishchess.org/tests/view/68106adf3629b02d74b166b1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 128856 W: 33026 L: 32917 D: 62913 Ptnml(0-2): 54, 13916, 36384, 14015, 59 Replaces discovered checks by direct checks which are simpler and more common. It's also what's used for scoring quiets. closes https://github.com/official-stockfish/Stockfish/pull/6047 Bench: 1886966 --- src/movepick.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 39caf8e67c3..882ca67d5af 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -145,11 +145,8 @@ void MovePicker::score() { for (auto& m : *this) if constexpr (Type == CAPTURES) m.value = - (2 - * (pos.blockers_for_king(~pos.side_to_move()) & m.from_sq() - && !aligned(m.from_sq(), m.to_sq(), pos.square(~pos.side_to_move()))) - + 7) - * int(PieceValue[pos.piece_on(m.to_sq())]) + 7 * int(PieceValue[pos.piece_on(m.to_sq())]) + + 1024 * bool(pos.check_squares(type_of(pos.moved_piece(m))) & m.to_sq()) + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]; else if constexpr (Type == QUIETS) From d4b405a5a6ab47610d7a1b93645ea4a85f941086 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 4 May 2025 20:02:58 +0300 Subject: [PATCH 1012/1309] Remove a cutNode condition Allow some nodes to spawn even deeper lmr searches, also for cutNodes Passed STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 59072 W: 15387 L: 15190 D: 28495 Ptnml(0-2): 145, 6956, 15166, 7095, 174 https://tests.stockfishchess.org/tests/view/6810b4c13629b02d74b16714 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 93294 W: 23737 L: 23591 D: 45966 Ptnml(0-2): 47, 10116, 26169, 10274, 41 https://tests.stockfishchess.org/tests/view/681170613629b02d74b167f3 closes https://github.com/official-stockfish/Stockfish/pull/6052 Bench: 1937565 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 81c25feccdc..2cab7bd9d6a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1237,7 +1237,7 @@ Value Search::Worker::search( // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))) - + (!cutNode && (ss - 1)->isPvNode && moveCount < 8); + + ((ss - 1)->isPvNode && moveCount < 8); ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From 05e39527a89c9de535c7d3eab67accb3b307c9bd Mon Sep 17 00:00:00 2001 From: gab8192 Date: Wed, 30 Apr 2025 15:44:34 +0200 Subject: [PATCH 1013/1309] Simplify low-ply history weight Passed Non-regression STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 116064 W: 30095 L: 29959 D: 56010 Ptnml(0-2): 333, 13753, 29731, 13875, 340 https://tests.stockfishchess.org/tests/view/681229f53629b02d74b168d3 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 172824 W: 44096 L: 44031 D: 84697 Ptnml(0-2): 91, 18868, 48410, 18971, 72 https://tests.stockfishchess.org/tests/view/681474243629b02d74b16b44 closes https://github.com/official-stockfish/Stockfish/pull/6053 Bench: 2064179 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 882ca67d5af..1c278147f71 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -178,7 +178,7 @@ void MovePicker::score() { } if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + 2 * ply); + m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); } else // Type == EVASIONS From 40ef7b1212c5a055c20a9c184f0bdf999c33c944 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 8 May 2025 19:43:55 +0300 Subject: [PATCH 1014/1309] Simplify probcut Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 80800 W: 20947 L: 20774 D: 39079 Ptnml(0-2): 217, 9446, 20906, 9609, 222 https://tests.stockfishchess.org/tests/view/680e83163629b02d74b15e2a Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 359004 W: 91362 L: 91486 D: 176156 Ptnml(0-2): 177, 39133, 101007, 39007, 178 https://tests.stockfishchess.org/tests/view/680e95db3629b02d74b15e7a closes https://github.com/official-stockfish/Stockfish/pull/6054 Bench: 2060860 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2cab7bd9d6a..45515555e72 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -952,7 +952,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 180 + depth * 20; + probCutBeta = beta + 400; 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; From 1f9af9966fd5be2aefd33ab99d1cb07b8415c3d1 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 8 May 2025 12:10:51 -0700 Subject: [PATCH 1015/1309] Simplify futility pruning term Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 49920 W: 12933 L: 12727 D: 24260 Ptnml(0-2): 141, 5865, 12752, 6051, 151 https://tests.stockfishchess.org/tests/view/681d01d33629b02d74b1756e Passed simplification LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 68808 W: 17573 L: 17402 D: 33833 Ptnml(0-2): 23, 7343, 19511, 7494, 33 https://tests.stockfishchess.org/tests/view/681e33843629b02d74b176b1 closes https://github.com/official-stockfish/Stockfish/pull/6058 Bench: 2041086 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 45515555e72..7c83cae24bd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1077,9 +1077,8 @@ Value Search::Worker::search( lmrDepth += history / 3388; - Value futilityValue = - ss->staticEval + (bestMove ? 46 : 138) + 117 * lmrDepth - + 102 * (bestValue < ss->staticEval - 127 && ss->staticEval > alpha - 50); + Value futilityValue = ss->staticEval + (bestMove ? 46 : 138) + 117 * lmrDepth + + 102 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning From b5f11085dd49d1132a9c807a458156d2f3dd199d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 27 Apr 2025 12:16:19 -0700 Subject: [PATCH 1016/1309] Do more extensions in nodes far from the root MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parameters found by @FauziAkram in the latest tune. Passed VVLTC w/ LTC Bound: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 243264 W: 63452 L: 62774 D: 117038 Ptnml(0-2): 18, 22494, 75934, 23164, 22 https://tests.stockfishchess.org/tests/view/680e82b23629b02d74b15e27 Passed VVLTC w/ STC Bound: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 37838 W: 9935 L: 9667 D: 18236 Ptnml(0-2): 6, 3383, 11873, 3651, 6 https://tests.stockfishchess.org/tests/view/681e707a3629b02d74b176e8 STC Elo Estimate: Elo: -0.49 ± 2.4 (95%) LOS: 34.8% Total: 20000 W: 5118 L: 5146 D: 9736 Ptnml(0-2): 55, 2365, 5182, 2349, 49 nElo: -0.96 ± 4.8 (95%) PairsRatio: 0.99 https://tests.stockfishchess.org/tests/view/6822af4b3629b02d74b17be6 closes https://github.com/official-stockfish/Stockfish/pull/6059 Bench: 2135382 --- src/search.cpp | 120 ++++++++++++++++++++++++------------------------- 1 file changed, 58 insertions(+), 62 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7c83cae24bd..d5e58eb5b22 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1100,72 +1100,68 @@ Value Search::Worker::search( } // Step 15. Extensions - // We take care to not overdo to avoid search getting stuck. - if (ss->ply < thisThread->rootDepth * 2) + // 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. + + // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) + // and lower extension margins scale well. + + if (!rootNode && move == ttData.move && !excludedMove + && depth >= 6 - (thisThread->completedDepth > 27) + ss->ttPv && is_valid(ttData.value) + && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) + && ttData.depth >= depth - 3) { - // 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. - - // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) - // and lower extension margins scale well. - - if (!rootNode && move == ttData.move && !excludedMove - && depth >= 6 - (thisThread->completedDepth > 27) + ss->ttPv - && is_valid(ttData.value) && !is_decisive(ttData.value) - && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) - { - Value singularBeta = ttData.value - (58 + 76 * (ss->ttPv && !PvNode)) * depth / 57; - Depth singularDepth = newDepth / 2; - - ss->excludedMove = move; - value = - search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = Move::none(); - - if (value < singularBeta) - { - int corrValAdj1 = std::abs(correctionValue) / 248400; - int corrValAdj2 = std::abs(correctionValue) / 249757; - int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj1 - - 997 * ttMoveHistory / 131072; - int tripleMargin = - 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - corrValAdj2; - - extension = 1 + (value < singularBeta - doubleMargin) - + (value < singularBeta - tripleMargin); + Value singularBeta = ttData.value - (58 + 76 * (ss->ttPv && !PvNode)) * depth / 57; + Depth singularDepth = newDepth / 2; - depth++; - } - - // Multi-cut pruning - // Our ttMove is assumed to fail high based on the bound of the TT entry, - // and if after excluding the ttMove with a reduced search we fail high - // over the original beta, we assume this expected cut-node is not - // singular (multiple moves fail high), and we can prune the whole - // subtree by returning a softbound. - else if (value >= beta && !is_decisive(value)) - return value; + ss->excludedMove = move; + value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = Move::none(); - // Negative extensions - // If other moves failed high over (ttValue - margin) without the - // ttMove on a reduced search, but we cannot do multi-cut because - // (ttValue - margin) is lower than the original beta, we do not know - // 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 - 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 - else if (cutNode) - extension = -2; + if (value < singularBeta) + { + int corrValAdj1 = std::abs(correctionValue) / 248400; + int corrValAdj2 = std::abs(correctionValue) / 249757; + int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj1 + - 997 * ttMoveHistory / 131072 + - (ss->ply * 2 > thisThread->rootDepth * 3) * 47; + int tripleMargin = 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv + - corrValAdj2 - (ss->ply * 2 > thisThread->rootDepth * 3) * 54; + + extension = + 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); + + depth++; } + + // Multi-cut pruning + // Our ttMove is assumed to fail high based on the bound of the TT entry, + // and if after excluding the ttMove with a reduced search we fail high + // over the original beta, we assume this expected cut-node is not + // singular (multiple moves fail high), and we can prune the whole + // subtree by returning a softbound. + else if (value >= beta && !is_decisive(value)) + return value; + + // Negative extensions + // If other moves failed high over (ttValue - margin) without the + // ttMove on a reduced search, but we cannot do multi-cut because + // (ttValue - margin) is lower than the original beta, we do not know + // 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 + 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 + else if (cutNode) + extension = -2; } // Step 16. Make the move From c4e2479a75ae34a620f68cfb686d04ce36709ab6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 13 May 2025 04:06:51 +0300 Subject: [PATCH 1017/1309] Introducing a depth component to the penalty. Passed STC: LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 31648 W: 8358 L: 8050 D: 15240 Ptnml(0-2): 78, 3596, 8182, 3876, 92 https://tests.stockfishchess.org/tests/view/680fa73d3629b02d74b165a9 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 177720 W: 45524 L: 44920 D: 87276 Ptnml(0-2): 91, 19130, 49813, 19736, 90 https://tests.stockfishchess.org/tests/view/68109e2c3629b02d74b166ee closes https://github.com/official-stockfish/Stockfish/pull/6060 Bench: 2251724 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d5e58eb5b22..6827f512742 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1877,7 +1877,8 @@ void update_all_stats(const Position& pos, // 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 * 980 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + -malus * (512 + depth * 16) / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) From 07f6edf93426fab8edb274232f9a40e46ea3d961 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 2 May 2025 18:32:55 +0300 Subject: [PATCH 1018/1309] Refactor Position::pseudo_legal Pawn Move Check use intermediate variables to make the statement easier to read closes https://github.com/official-stockfish/Stockfish/pull/6045 No functional change --- src/position.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 9f023b5fe41..5e2c2782269 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -597,10 +597,14 @@ bool Position::pseudo_legal(const Move m) const { if ((Rank8BB | Rank1BB) & to) return false; - if (!(attacks_bb(from, us) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !((from + 2 * pawn_push(us) == to) // Not a double push - && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) + // Check if it's a valid capture, single push, or double push + const bool isCapture = bool(attacks_bb(from, us) & pieces(~us) & to); + const bool isSinglePush = (from + pawn_push(us) == to) && empty(to); + const bool isDoublePush = (from + 2 * pawn_push(us) == to) + && (relative_rank(us, from) == RANK_2) && empty(to) + && empty(to - pawn_push(us)); + + if (!(isCapture || isSinglePush || isDoublePush)) return false; } else if (!(attacks_bb(type_of(pc), from, pieces()) & to)) From 6b7e05f0c5287179de25a0722f9eea0f87f64648 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 9 May 2025 15:34:36 -0700 Subject: [PATCH 1019/1309] Simplify PCM TTMove Bonus Passed Non-regression STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 114048 W: 29597 L: 29459 D: 54992 Ptnml(0-2): 315, 13619, 29045, 13703, 342 https://tests.stockfishchess.org/tests/view/681e83533629b02d74b17701 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 61014 W: 15582 L: 15405 D: 30027 Ptnml(0-2): 25, 6485, 17307, 6668, 22 https://tests.stockfishchess.org/tests/view/68226b523629b02d74b17b89 closes https://github.com/official-stockfish/Stockfish/pull/6061 bench 2016566 --- src/search.cpp | 14 ++++++-------- src/search.h | 1 - 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6827f512742..4fca84f95a8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -921,7 +921,6 @@ Value Search::Worker::search( do_move(pos, move, st); ss->currentMove = move; - ss->isTTMove = (move == ttData.move); ss->continuationHistory = &this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1172,7 +1171,6 @@ Value Search::Worker::search( // 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 = @@ -1203,7 +1201,7 @@ Value Search::Worker::search( r += 1036 + allNode * 848; // For first picked move (ttMove) reduce reduction - else if (ss->isTTMove) + else if (move == ttData.move) r -= 2006; if (capture) @@ -1285,7 +1283,7 @@ Value Search::Worker::search( (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (ss->isTTMove && thisThread->rootDepth > 8) + if (move == ttData.move && thisThread->rootDepth > 8) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); @@ -1423,7 +1421,7 @@ Value Search::Worker::search( ttData.move, moveCount); if (!PvNode) { - int bonus = ss->isTTMove ? 800 : -879; + int bonus = bestMove == ttData.move ? 800 : -879; ttMoveHistory << bonus; } } @@ -1431,11 +1429,11 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = std::min(-(ss - 1)->statScore / 113, 293); - bonusScale += std::min(73 * depth - 347, 184); + int bonusScale = -324; + bonusScale += std::min(-(ss - 1)->statScore / 103, 323); + bonusScale += std::min(73 * depth, 531); bonusScale += 33 * !allNode; bonusScale += 174 * ((ss - 1)->moveCount > 8); - bonusScale += 86 * (ss - 1)->isTTMove; bonusScale += 90 * (ss->cutoffCnt <= 3); bonusScale += 144 * (!ss->inCheck && bestValue <= ss->staticEval - 104); bonusScale += 128 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82); diff --git a/src/search.h b/src/search.h index 6ab98f7c45e..0c041c82615 100644 --- a/src/search.h +++ b/src/search.h @@ -75,7 +75,6 @@ struct Stack { bool ttHit; int cutoffCnt; int reduction; - bool isTTMove; bool isPvNode; }; From e4b0f374933543efb42baaa03b89c2d479e894cb Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 13 May 2025 22:15:24 -0700 Subject: [PATCH 1020/1309] Shrink Enum Sizes Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 110848 W: 28974 L: 28564 D: 53310 Ptnml(0-2): 302, 12118, 30132, 12612, 260 https://tests.stockfishchess.org/tests/view/68242770a527315e07ccca38 closes https://github.com/official-stockfish/Stockfish/pull/6063 no functional change --- src/timeman.cpp | 2 ++ src/timeman.h | 2 +- src/types.h | 50 ++++++++++++++++++++++++------------------------- src/uci.h | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 29ebffcaa29..f0894a262ce 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -28,6 +28,8 @@ namespace Stockfish { +enum Color : int8_t; + TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } diff --git a/src/timeman.h b/src/timeman.h index e8602bb7ce8..a2d1a4364d0 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -22,11 +22,11 @@ #include #include "misc.h" -#include "types.h" namespace Stockfish { class OptionsMap; +enum Color : int8_t; namespace Search { struct LimitsType; diff --git a/src/types.h b/src/types.h index 6cd6ee74446..6c797580718 100644 --- a/src/types.h +++ b/src/types.h @@ -110,13 +110,13 @@ using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -enum Color { +enum Color : int8_t { WHITE, BLACK, COLOR_NB = 2 }; -enum CastlingRights { +enum CastlingRights : int8_t { NO_CASTLING, WHITE_OO, WHITE_OOO = WHITE_OO << 1, @@ -132,7 +132,7 @@ enum CastlingRights { CASTLING_RIGHT_NB = 16 }; -enum Bound { +enum Bound : int8_t { BOUND_NONE, BOUND_UPPER, BOUND_LOWER, @@ -183,13 +183,13 @@ constexpr Value QueenValue = 2538; // clang-format off -enum PieceType { +enum PieceType : std::int8_t { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, ALL_PIECES = 0, PIECE_TYPE_NB = 8 }; -enum Piece { +enum Piece : std::int8_t { NO_PIECE, W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, @@ -203,26 +203,24 @@ constexpr Value PieceValue[PIECE_NB] = { using Depth = int; -enum : int { - // The following DEPTH_ constants are used for transposition table entries - // and quiescence search move generation stages. In regular search, the - // depth stored in the transposition table is literal: the search depth - // (effort) used to make the corresponding transposition table value. In - // quiescence search, however, the transposition table entries only store - // the current quiescence move generation stage (which should thus compare - // lower than any regular search depth). - DEPTH_QS = 0, - // For transposition table entries where no searching at all was done - // (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus - // compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET - // is used only for the transposition table entry occupancy check (see tt.cpp), - // and should thus be lower than DEPTH_UNSEARCHED. - DEPTH_UNSEARCHED = -2, - DEPTH_ENTRY_OFFSET = -3 -}; +// The following DEPTH_ constants are used for transposition table entries +// and quiescence search move generation stages. In regular search, the +// depth stored in the transposition table is literal: the search depth +// (effort) used to make the corresponding transposition table value. In +// quiescence search, however, the transposition table entries only store +// the current quiescence move generation stage (which should thus compare +// lower than any regular search depth). +constexpr Depth DEPTH_QS = 0; +// For transposition table entries where no searching at all was done +// (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus +// compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET +// is used only for the transposition table entry occupancy check (see tt.cpp), +// and should thus be lower than DEPTH_UNSEARCHED. +constexpr Depth DEPTH_UNSEARCHED = -2; +constexpr Depth DEPTH_ENTRY_OFFSET = -3; // clang-format off -enum Square : int { +enum Square : int8_t { SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, @@ -238,7 +236,7 @@ enum Square : int { }; // clang-format on -enum Direction : int { +enum Direction : int8_t { NORTH = 8, EAST = 1, SOUTH = -NORTH, @@ -250,7 +248,7 @@ enum Direction : int { NORTH_WEST = NORTH + WEST }; -enum File : int { +enum File : int8_t { FILE_A, FILE_B, FILE_C, @@ -262,7 +260,7 @@ enum File : int { FILE_NB }; -enum Rank : int { +enum Rank : int8_t { RANK_1, RANK_2, RANK_3, diff --git a/src/uci.h b/src/uci.h index 5c1c07f7b18..1686b3a7208 100644 --- a/src/uci.h +++ b/src/uci.h @@ -33,7 +33,7 @@ namespace Stockfish { class Position; class Move; class Score; -enum Square : int; +enum Square : int8_t; using Value = int; class UCIEngine { From 6f445631ab40fcdca41fb7ccc94ac842796b89c3 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 28 Apr 2025 14:55:56 -0700 Subject: [PATCH 1021/1309] Simplify Futility Margin Passed STC Non-regression: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 159008 W: 41500 L: 41414 D: 76094 Ptnml(0-2): 501, 18821, 40759, 18937, 486 https://tests.stockfishchess.org/tests/view/680ff9e23629b02d74b1663a Passed LTC Non-regression: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 163572 W: 41617 L: 41543 D: 80412 Ptnml(0-2): 90, 17755, 46024, 17825, 92 https://tests.stockfishchess.org/tests/view/6814dd973629b02d74b16bac closes https://github.com/official-stockfish/Stockfish/pull/6065 Bench: 2018775 --- src/search.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4fca84f95a8..857a8d47e14 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -825,19 +825,17 @@ Value Search::Worker::search( // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 105 - 23 * (cutNode && !ss->ttHit); - Value improvingDeduction = improving * futilityMult * 2; - Value worseningDeduction = opponentWorsening * futilityMult / 3; - - return futilityMult * d // - - improvingDeduction // - - worseningDeduction // - + (ss - 1)->statScore / 335 // - + std::abs(correctionValue) / 149902; + Value futilityMult = 93 - 20 * (cutNode && !ss->ttHit); + + return futilityMult * d // + - improving * futilityMult * 2 // + - opponentWorsening * futilityMult / 3 // + + (ss - 1)->statScore / 376 // + + std::abs(correctionValue) / 168639; }; - if (!ss->ttPv && depth < 14 && eval + (eval - beta) / 8 - futility_margin(depth) >= beta - && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) + if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta + && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; } From 1b6975ac4151d503ca48c9d5d4bd18d5fa11f4b9 Mon Sep 17 00:00:00 2001 From: Mapika Date: Sat, 17 May 2025 18:01:25 +0200 Subject: [PATCH 1022/1309] Add quiet move streak tracking to search stack Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 109344 W: 28473 L: 28053 D: 52818 Ptnml(0-2): 320, 12756, 28085, 13206, 305 https://tests.stockfishchess.org/tests/view/6828c43e6ec7634154f99a10 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 76308 W: 19721 L: 19323 D: 37264 Ptnml(0-2): 39, 8145, 21386, 8547, 37 https://tests.stockfishchess.org/tests/view/6828f65a6ec7634154f99b72 closes https://github.com/official-stockfish/Stockfish/pull/6066 Bench: 2161814 --- AUTHORS | 1 + src/search.cpp | 5 +++++ src/search.h | 1 + 3 files changed, 7 insertions(+) diff --git a/AUTHORS b/AUTHORS index f1675860944..8caa2285835 100644 --- a/AUTHORS +++ b/AUTHORS @@ -148,6 +148,7 @@ Lucas Braesch (lucasart) Lyudmil Antonov (lantonov) Maciej Żenczykowski (zenczykowski) Malcolm Campbell (xoto10) +Mark Marosi (Mapika) Mark Tenzer (31m059) marotear Mathias Parnaudeau (mparnaudeau) diff --git a/src/search.cpp b/src/search.cpp index 857a8d47e14..0af54b4f51b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1004,6 +1004,8 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); + (ss + 1)->quietMoveStreak = (!capture && !givesCheck) ? (ss->quietMoveStreak + 1) : 0; + // Calculate new depth for this move newDepth = depth - 1; @@ -1198,6 +1200,9 @@ Value Search::Worker::search( if ((ss + 1)->cutoffCnt > 2) r += 1036 + allNode * 848; + if (!capture && !givesCheck && ss->quietMoveStreak >= 2) + r += (ss->quietMoveStreak - 1) * 50; + // For first picked move (ttMove) reduce reduction else if (move == ttData.move) r -= 2006; diff --git a/src/search.h b/src/search.h index 0c041c82615..5caff10e80f 100644 --- a/src/search.h +++ b/src/search.h @@ -76,6 +76,7 @@ struct Stack { int cutoffCnt; int reduction; bool isPvNode; + int quietMoveStreak; }; From 4f76768fcf1aa8a8c576086210875e84cda40705 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 18 May 2025 16:12:59 +0300 Subject: [PATCH 1023/1309] Remove a moveCount condition Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 70816 W: 18315 L: 18134 D: 34367 Ptnml(0-2): 210, 8213, 18360, 8436, 189 https://tests.stockfishchess.org/tests/view/68248197a527315e07cccb2d Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 121770 W: 31248 L: 31130 D: 59392 Ptnml(0-2): 61, 13338, 33995, 13404, 87 https://tests.stockfishchess.org/tests/view/68272ff46ec7634154f998ad closes https://github.com/official-stockfish/Stockfish/pull/6067 bench: 2319161 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 0af54b4f51b..5d92e1b1d79 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1233,7 +1233,7 @@ Value Search::Worker::search( // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))) - + ((ss - 1)->isPvNode && moveCount < 8); + + ((ss - 1)->isPvNode); ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From 6e9b5af0f002ff1175998631e07a1c92735cae62 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 18 May 2025 13:31:35 -0700 Subject: [PATCH 1024/1309] Check evaluation after ttMove before doing a tt cut Passed STC LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 239136 W: 62222 L: 61608 D: 115306 Ptnml(0-2): 675, 28046, 61525, 28634, 688 https://tests.stockfishchess.org/tests/view/681053293629b02d74b1668f Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 448770 W: 115237 L: 114088 D: 219445 Ptnml(0-2): 177, 48128, 126619, 49291, 170 https://tests.stockfishchess.org/tests/view/681902de3629b02d74b16f6d closes https://github.com/official-stockfish/Stockfish/pull/6069 Bench: 2035432 --- src/search.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5d92e1b1d79..d71f78ac187 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -680,6 +680,8 @@ Value Search::Worker::search( && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) && (cutNode == (ttData.value >= beta) || depth > 5)) { + + // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) { @@ -696,7 +698,30 @@ Value Search::Worker::search( // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) - return ttData.value; + { + if (depth >= 8 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move) + && !is_decisive(ttData.value)) + { + do_move(pos, ttData.move, st); + Key nextPosKey = pos.key(); + auto [ttHitNext, ttDataNext, ttWriterNext] = tt.probe(nextPosKey); + ttDataNext.value = + ttHitNext ? value_from_tt(ttDataNext.value, ss->ply + 1, pos.rule50_count()) + : VALUE_NONE; + undo_move(pos, ttData.move); + + if (!is_valid(ttDataNext.value)) + return ttData.value; + if (ttData.value >= beta && -ttDataNext.value >= beta) + return ttData.value; + if (ttData.value <= alpha && -ttDataNext.value <= alpha) + return ttData.value; + } + else + { + return ttData.value; + } + } } // Step 5. Tablebases probe @@ -1233,7 +1258,7 @@ Value Search::Worker::search( // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))) - + ((ss - 1)->isPvNode); + + ((ss - 1)->isPvNode); ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From 39942db3ff4af2c2135775d5ac3cd3e77a883636 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 6 May 2025 15:05:24 -0700 Subject: [PATCH 1025/1309] Simplify In-Check Statscore Passed Non-regression STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 129760 W: 33701 L: 33580 D: 62479 Ptnml(0-2): 359, 15248, 33575, 15309, 389 https://tests.stockfishchess.org/tests/view/681a88193629b02d74b17123 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 519612 W: 132224 L: 132512 D: 254876 Ptnml(0-2): 246, 56823, 145960, 56527, 250 https://tests.stockfishchess.org/tests/view/681f9ed43629b02d74b177c3 closes https://github.com/official-stockfish/Stockfish/pull/6070 bench: 2046462 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d71f78ac187..bb9ce68ba8f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1237,13 +1237,10 @@ Value Search::Worker::search( 826 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - 5030; - else if (ss->inCheck) - ss->statScore = thisThread->mainHistory[us][move.from_to()] - + (*contHist[0])[movedPiece][move.to_sq()] - 2766; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3206; + + (*contHist[1])[movedPiece][move.to_sq()] + 1000 * ss->inCheck - 3206; // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 826 / 8192; From 009632c465ef8204884b01c0307f7e15106fd94e Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 16 May 2025 13:04:10 -0700 Subject: [PATCH 1026/1309] Simplify handling of good/bad quiets Simplify the handling of good/bad quiets and make it more similar to the way we handle good/bad captures. The good quiet limit was adjusted from -7998 to -14000 to keep the ratio of good/bad quiets about the same as master. This also fixes a "bug" that previously returned some bad quiets during the GOOD_QUIET stage when some qood quiets weren't sorted at low depths. STC https://tests.stockfishchess.org/tests/view/6827a68c6ec7634154f9992b LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 75936 W: 19722 L: 19547 D: 36667 Ptnml(0-2): 186, 8937, 19589, 9028, 228 LTC https://tests.stockfishchess.org/tests/view/6828f8096ec7634154f99b82 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 104112 W: 26773 L: 26638 D: 50701 Ptnml(0-2): 51, 11363, 29098, 11488, 56 closes https://github.com/official-stockfish/Stockfish/pull/6071 Bench: 2007023 --- src/movepick.cpp | 22 +++++++++------------- src/movepick.h | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 1c278147f71..b0059bd31fa 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -208,8 +208,6 @@ Move MovePicker::select(Pred filter) { // picking the move with the highest score from a list of generated moves. Move MovePicker::next_move() { - auto quiet_threshold = [](Depth d) { return -3560 * d; }; - top: switch (stage) { @@ -250,24 +248,22 @@ Move MovePicker::next_move() { case QUIET_INIT : if (!skipQuiets) { - endMoves = beginBadQuiets = endBadQuiets = generate(pos, cur); + cur = endBadQuiets = endBadCaptures; + endMoves = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, quiet_threshold(depth)); + partial_insertion_sort(cur, endMoves, -3560 * depth); } ++stage; [[fallthrough]]; case GOOD_QUIET : - if (!skipQuiets && select([]() { return true; })) - { - if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth)) - return *(cur - 1); - - // Remaining quiets are bad - beginBadQuiets = cur - 1; - } + if (!skipQuiets && select([&]() { + return cur->value > -14000 ? true + : (*endBadQuiets++ = *cur, false); + })) + return *(cur - 1); // Prepare the pointers to loop over the bad captures cur = moves; @@ -281,7 +277,7 @@ Move MovePicker::next_move() { return *(cur - 1); // Prepare the pointers to loop over the bad quiets - cur = beginBadQuiets; + cur = endBadCaptures; endMoves = endBadQuiets; ++stage; diff --git a/src/movepick.h b/src/movepick.h index 72a6f4e1cb2..7da7c3a7e78 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -68,7 +68,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove * cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets; + ExtMove * cur, *endMoves, *endBadCaptures, *endBadQuiets; int stage; int threshold; Depth depth; From 0f102f3692f3b21a2c8add92ba867bc8de362c8d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 18 May 2025 13:44:29 -0700 Subject: [PATCH 1027/1309] Simplify Quiet Early Move Penalty Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 185344 W: 47995 L: 47939 D: 89410 Ptnml(0-2): 527, 21898, 47754, 21978, 515 https://tests.stockfishchess.org/tests/view/682a47536ec7634154f9a3bc Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 101706 W: 26050 L: 25912 D: 49744 Ptnml(0-2): 53, 11056, 28499, 11190, 55 https://tests.stockfishchess.org/tests/view/682a61736ec7634154f9a50e closes https://github.com/official-stockfish/Stockfish/pull/6072 Bench: 2012032 --- src/movepick.cpp | 5 ++--- src/search.cpp | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b0059bd31fa..1df0b0ccfbf 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -249,7 +249,7 @@ Move MovePicker::next_move() { if (!skipQuiets) { cur = endBadQuiets = endBadCaptures; - endMoves = generate(pos, cur); + endMoves = generate(pos, cur); score(); partial_insertion_sort(cur, endMoves, -3560 * depth); @@ -260,8 +260,7 @@ Move MovePicker::next_move() { case GOOD_QUIET : if (!skipQuiets && select([&]() { - return cur->value > -14000 ? true - : (*endBadQuiets++ = *cur, false); + return cur->value > -14000 ? true : (*endBadQuiets++ = *cur, false); })) return *(cur - 1); diff --git a/src/search.cpp b/src/search.cpp index bb9ce68ba8f..5f20e593858 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1900,8 +1900,7 @@ void update_all_stats(const Position& pos, // 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 * (512 + depth * 16) / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 580 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) From ccfa6519680ce2cb602fab33d5605606616218ce Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Mon, 19 May 2025 10:12:50 +0200 Subject: [PATCH 1028/1309] Remove full depth search reduction when cutNode Passed STC-simplification bounds: https://tests.stockfishchess.org/tests/view/6829dd6d6ec7634154f99fd3 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67872 W: 17629 L: 17443 D: 32800 Ptnml(0-2): 167, 7988, 17451, 8152, 178 Passed LTC-simplification bounds: https://tests.stockfishchess.org/tests/view/6829f2176ec7634154f9a01c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94818 W: 24328 L: 24184 D: 46306 Ptnml(0-2): 52, 10246, 26667, 10394, 50 closes https://github.com/official-stockfish/Stockfish/pull/6074 bench: 2245168 --- src/search.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5f20e593858..cce01867b49 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1292,9 +1292,6 @@ Value Search::Worker::search( r -= ttMoveHistory / 8; - if (cutNode) - r += 520; - // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3564) - (r > 4969 && newDepth > 2), !cutNode); From 56ea1fadf16142c830161503d49c9ae4ea23b1ef Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 19 May 2025 11:42:45 +0300 Subject: [PATCH 1029/1309] Tweak low ply history Increase low ply history maximum ply by 1 and also allow to use it for check evasions scoring. Pased STC: https://tests.stockfishchess.org/tests/view/682a2db36ec7634154f9a358 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 66464 W: 17440 L: 17081 D: 31943 Ptnml(0-2): 191, 7717, 17056, 8078, 190 Passed LTC: https://tests.stockfishchess.org/tests/view/682a3d406ec7634154f9a39c LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 384030 W: 98476 L: 97452 D: 188102 Ptnml(0-2): 180, 41564, 107522, 42550, 199 closes https://github.com/official-stockfish/Stockfish/pull/6075 Bench: 2422771 --- src/history.h | 2 +- src/movepick.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/history.h b/src/history.h index 259d6eefb22..46914789e59 100644 --- a/src/history.h +++ b/src/history.h @@ -36,7 +36,7 @@ namespace Stockfish { constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 constexpr int CORRECTION_HISTORY_LIMIT = 1024; -constexpr int LOW_PLY_HISTORY_SIZE = 4; +constexpr int LOW_PLY_HISTORY_SIZE = 5; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); diff --git a/src/movepick.cpp b/src/movepick.cpp index 1df0b0ccfbf..45bb8afe87e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -186,8 +186,12 @@ void MovePicker::score() { if (pos.capture_stage(m)) m.value = PieceValue[pos.piece_on(m.to_sq())] + (1 << 28); else + { m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()]; + if (ply < LOW_PLY_HISTORY_SIZE) + m.value += 2 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); + } } } From 347e328fdbe8104747a258db00a9f034e09d3cb8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 18 May 2025 18:36:06 -0700 Subject: [PATCH 1030/1309] Simplify TT Replacement Strategy Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 50528 W: 13160 L: 12958 D: 24410 Ptnml(0-2): 132, 5681, 13439, 5877, 135 https://tests.stockfishchess.org/tests/view/682a8b296ec7634154f9a785 Passed Non-regression STC w/ High Hash Pressure: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 30048 W: 7849 L: 7621 D: 14578 Ptnml(0-2): 75, 3390, 7884, 3582, 93 https://tests.stockfishchess.org/tests/view/682a9caf6ec7634154f9a7ae Passed Non-regression LTC w/ High Hash Pressure: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 17610 W: 4584 L: 4362 D: 8664 Ptnml(0-2): 7, 1799, 4974, 2015, 10 https://tests.stockfishchess.org/tests/view/682ab3966ec7634154f9a8c8 closes https://github.com/official-stockfish/Stockfish/pull/6079 Bench: 2422771 --- src/tt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 5d8457611dd..d7f7dbdef6b 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -234,8 +234,8 @@ std::tuple TranspositionTable::probe(const Key key) cons // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) - if (replace->depth8 - replace->relative_age(generation8) * 2 - > tte[i].depth8 - tte[i].relative_age(generation8) * 2) + if (replace->depth8 - replace->relative_age(generation8) + > tte[i].depth8 - tte[i].relative_age(generation8)) replace = &tte[i]; return {false, From 54fb42ddf8b242e46f5ef2d2a0d481ed5aa5fe7b Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sat, 17 May 2025 22:50:47 +0200 Subject: [PATCH 1031/1309] clean up code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Non functional changes:** in search.cpp: - an unnecessary pair of parenthesis in the IIR condition has been removed. - refactored the stalemate trap detection code in movepick.cpp: - use the variables `from`, `to`, `piece`, `pieceType` and `capturedPiece` instead of calling the same functions multiple times in `MovePicker::score()`. - rename `MovePicker::other_piece_types_mobile()`. **Functional changes:** - make sure the processed move is always legal in `MovePicker::other_piece_types_mobile()`. passed non regression STC: https://tests.stockfishchess.org/tests/view/6829da686ec7634154f99faf LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 95680 W: 24962 L: 24820 D: 45898 Ptnml(0-2): 221, 9622, 28025, 9738, 234 Passed non regression LTC: https://tests.stockfishchess.org/tests/view/682a102c6ec7634154f9a086 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 117666 W: 30065 L: 29957 D: 57644 Ptnml(0-2): 45, 10173, 38291, 10277, 47 Run of 10k games on the stalemate opening book: https://tests.stockfishchess.org/tests/view/682b114e6ec7634154f9aa2d Elo: 0.76 ± 0.9 (95%) LOS: 95.3% Total: 10000 W: 4637 L: 4615 D: 748 Ptnml(0-2): 0, 75, 4828, 97, 0 nElo: 5.83 ± 6.8 (95%) PairsRatio: 1.29 closes https://github.com/official-stockfish/Stockfish/pull/6080 Bench: 2422771 --- src/movepick.cpp | 57 ++++++++++++++++++++---------------------------- src/movepick.h | 3 +-- src/search.cpp | 40 ++++++++++++++++----------------- 3 files changed, 45 insertions(+), 55 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 45bb8afe87e..6d00dd941c8 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -126,11 +126,11 @@ void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + Color us = pos.side_to_move(); + [[maybe_unused]] Bitboard threatenedPieces, threatByLesser[QUEEN + 1]; if constexpr (Type == QUIETS) { - Color us = pos.side_to_move(); - threatByLesser[KNIGHT] = threatByLesser[BISHOP] = pos.attacks_by(~us); threatByLesser[ROOK] = pos.attacks_by(~us) | pos.attacks_by(~us) | threatByLesser[KNIGHT]; @@ -143,21 +143,21 @@ void MovePicker::score() { } for (auto& m : *this) + { + const Square from = m.from_sq(); + const Square to = m.to_sq(); + const Piece pc = pos.moved_piece(m); + const PieceType pt = type_of(pc); + const Piece capturedPiece = pos.piece_on(to); + if constexpr (Type == CAPTURES) - m.value = - 7 * int(PieceValue[pos.piece_on(m.to_sq())]) - + 1024 * bool(pos.check_squares(type_of(pos.moved_piece(m))) & m.to_sq()) - + (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))]; + m.value = (*captureHistory)[pc][to][type_of(capturedPiece)] + + 7 * int(PieceValue[capturedPiece]) + 1024 * bool(pos.check_squares(pt) & to); else if constexpr (Type == QUIETS) { - Piece pc = pos.moved_piece(m); - PieceType pt = type_of(pc); - Square from = m.from_sq(); - Square to = m.to_sq(); - // histories - m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()]; + m.value = 2 * (*mainHistory)[us][m.from_to()]; m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -184,15 +184,15 @@ void MovePicker::score() { else // Type == EVASIONS { if (pos.capture_stage(m)) - m.value = PieceValue[pos.piece_on(m.to_sq())] + (1 << 28); + m.value = PieceValue[capturedPiece] + (1 << 28); else { - m.value = (*mainHistory)[pos.side_to_move()][m.from_to()] - + (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()]; + m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) m.value += 2 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); } } + } } // Returns the next move satisfying a predicate function. @@ -221,7 +221,6 @@ Move MovePicker::next_move() { case QSEARCH_TT : case PROBCUT_TT : ++stage; - cur = moves + 1; return ttMove; case CAPTURE_INIT : @@ -237,12 +236,10 @@ Move MovePicker::next_move() { case GOOD_CAPTURE : if (select([&]() { - if (!pos.see_ge(*cur, -cur->value / 18)) - { - std::swap(*endBadCaptures++, *cur); - return false; - } - return true; + if (pos.see_ge(*cur, -cur->value / 18)) + return true; + std::swap(*endBadCaptures++, *cur); + return false; })) return *(cur - 1); @@ -315,23 +312,17 @@ Move MovePicker::next_move() { void MovePicker::skip_quiet_moves() { skipQuiets = true; } -bool MovePicker::other_piece_types_mobile(PieceType pt) { +// this function must be called after all quiet moves and captures have been generated +bool MovePicker::can_move_king_or_pawn() { assert(stage == GOOD_QUIET || stage == BAD_QUIET || stage == EVASION); - // verify all generated captures and quiets for (ExtMove* m = moves; m < endMoves; ++m) { - if (*m && type_of(pos.moved_piece(*m)) != pt) - { - if (type_of(pos.moved_piece(*m)) != KING) - return true; - if (pos.legal(*m)) - return true; - } + PieceType movedPieceType = type_of(pos.moved_piece(*m)); + if ((movedPieceType == PAWN || movedPieceType == KING) && pos.legal(*m)) + return true; } return false; } -void MovePicker::mark_current_illegal() { *(cur - 1) = Move::none(); } - } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 7da7c3a7e78..4218ab5a3e0 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -50,8 +50,7 @@ class MovePicker { MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(); void skip_quiet_moves(); - bool other_piece_types_mobile(PieceType pt); - void mark_current_illegal(); + bool can_move_king_or_pawn(); private: template diff --git a/src/search.cpp b/src/search.cpp index cce01867b49..f5935818233 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -910,7 +910,7 @@ 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 less aggressive. - if ((!allNode && depth >= (PvNode ? 5 : 7)) && !ttData.move) + if (!allNode && depth >= (PvNode ? 5 : 7) && !ttData.move) depth--; // Step 11. ProbCut @@ -1002,10 +1002,8 @@ Value Search::Worker::search( // Check for legality if (!pos.legal(move)) - { - mp.mark_current_illegal(); continue; - } + // At root obey the "searchmoves" option and skip moves not listed in Root // Move List. In MultiPV mode we also skip PV moves that have been already // searched and those of lower "TB rank" if we are in a TB root position. @@ -1074,15 +1072,17 @@ Value Search::Worker::search( int seeHist = std::clamp(captHist / 31, -137 * depth, 125 * depth); if (!pos.see_ge(move, -158 * depth - seeHist)) { - bool skip = true; - if (depth > 2 && !capture && givesCheck && alpha < 0 - && pos.non_pawn_material(us) == PieceValue[movedPiece] - && PieceValue[movedPiece] >= RookValue - && !(PseudoAttacks[KING][pos.square(us)] & move.from_sq())) - // if the opponent captures last mobile piece it might be stalemate - skip = mp.other_piece_types_mobile(type_of(movedPiece)); - - if (skip) + bool mayStalemateTrap = + depth > 2 && givesCheck && alpha < 0 + && !capture // we consider that captures will likely destroy the stalemate configuration + && pos.non_pawn_material(us) == PieceValue[movedPiece] + && PieceValue[movedPiece] >= RookValue + // it can't be stalemate if we moved a piece adjacent to the king + && !(attacks_bb(pos.square(us)) & move.from_sq()) + && !mp.can_move_king_or_pawn(); + + // avoid pruning sacrifices of our last piece for stalemate + if (!mayStalemateTrap) continue; } } @@ -1873,8 +1873,8 @@ void update_all_stats(const Position& pos, int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; - Piece moved_piece = pos.moved_piece(bestMove); - PieceType captured; + Piece movedPiece = pos.moved_piece(bestMove); + PieceType capturedPiece; int bonus = std::min(143 * depth - 89, 1496) + 302 * (bestMove == ttMove); int malus = std::min(737 * depth - 179, 3141) - 30 * moveCount; @@ -1890,8 +1890,8 @@ void update_all_stats(const Position& pos, 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 * 1213 / 1024; + capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1213 / 1024; } // Extra penalty for a quiet early move that was not a TT move in @@ -1902,9 +1902,9 @@ void update_all_stats(const Position& pos, // 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 * 1388 / 1024; + movedPiece = pos.moved_piece(move); + capturedPiece = type_of(pos.piece_on(move.to_sq())); + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1388 / 1024; } } From e03898b57cbed0a6e5c830d4693887f067e64629 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Mon, 19 May 2025 16:44:59 +0200 Subject: [PATCH 1032/1309] ci: add tests and artifacts for windows-11-arm integrate armv8 and armv8-dotprod builds on windows-11-arm in ci, creating the corresponding artifacts. Correct Makefile to drop warnings when providing a CXX, add MINGW ARM64 to get_native_properties.sh fixes https://github.com/official-stockfish/Stockfish/issues/5640 closes https://github.com/official-stockfish/Stockfish/pull/6078 No functional change --- .github/ci/matrix.json | 138 +++++++++++++++++++++++++++++- .github/workflows/compilation.yml | 14 +-- .github/workflows/tests.yml | 20 ++++- scripts/get_native_properties.sh | 8 +- src/Makefile | 12 +-- 5 files changed, 172 insertions(+), 20 deletions(-) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index fa720a1c38f..c414c51fcce 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -40,6 +40,18 @@ "ext": ".exe", "sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future --", "archive_ext": "zip" + }, + { + "name": "Windows 11 Mingw-w64 Clang arm64", + "os": "windows-11-arm", + "simple_name": "windows", + "compiler": "clang++", + "comp": "clang", + "msys_sys": "clangarm64", + "msys_env": "clang-aarch64-clang", + "shell": "msys2 {0}", + "ext": ".exe", + "archive_ext": "zip" } ], "binaries": [ @@ -51,7 +63,9 @@ "x86-64-avx512", "x86-64-vnni256", "x86-64-vnni512", - "apple-silicon" + "apple-silicon", + "armv8", + "armv8-dotprod" ], "exclude": [ { @@ -126,6 +140,54 @@ "os": "macos-13" } }, + { + "binaries": "x86-64", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "x86-64-sse41-popcnt", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "x86-64-avx2", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "x86-64-bmi2", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "x86-64-avxvnni", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "x86-64-avx512", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "x86-64-vnni256", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "x86-64-vnni512", + "config": { + "os": "windows-11-arm" + } + }, { "binaries": "apple-silicon", "config": { @@ -135,7 +197,13 @@ { "binaries": "apple-silicon", "config": { - "os": "macos-13" + "os": "windows-11-arm" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "ubuntu-20.04" } }, { @@ -143,6 +211,72 @@ "config": { "os": "ubuntu-22.04" } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "armv8", + "config": { + "os": "windows-2022" + } + }, + { + "binaries": "armv8", + "config": { + "os": "ubuntu-20.04" + } + }, + { + "binaries": "armv8", + "config": { + "os": "ubuntu-22.04" + } + }, + { + "binaries": "armv8", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "armv8", + "config": { + "os": "macos-14" + } + }, + { + "binaries": "armv8-dotprod", + "config": { + "os": "windows-2022" + } + }, + { + "binaries": "armv8-dotprod", + "config": { + "os": "ubuntu-20.04" + } + }, + { + "binaries": "armv8-dotprod", + "config": { + "os": "ubuntu-22.04" + } + }, + { + "binaries": "armv8-dotprod", + "config": { + "os": "macos-13" + } + }, + { + "binaries": "armv8-dotprod", + "config": { + "os": "macos-14" + } } ] } diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 5878adecb5c..67b2e1c5528 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -63,13 +63,13 @@ jobs: - name: Check compiler run: $COMPCXX -v - - name: Show g++ cpu info - if: runner.os != 'macOS' - run: g++ -Q -march=native --help=target - - - name: Show clang++ cpu info - if: runner.os == 'macOS' - run: clang++ -E - -march=native -### + - name: Show compiler cpu info + run: | + if [[ "$COMPCXX" == clang* ]]; then + $COMPCXX -E - -march=native -### + else + $COMPCXX -Q -march=native --help=target + fi # x86-64 with newer extensions tests diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 57d0d53f0e2..6d35a183d1f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -98,6 +98,14 @@ jobs: msys_sys: clang64 msys_env: clang-x86_64-clang shell: msys2 {0} + - name: Windows 11 Mingw-w64 Clang arm64 + os: windows-11-arm + compiler: clang++ + comp: clang + run_armv8_tests: true + msys_sys: clangarm64 + msys_env: clang-aarch64-clang + shell: msys2 {0} defaults: run: working-directory: src @@ -302,8 +310,10 @@ jobs: - name: Test armv8 build if: matrix.config.run_armv8_tests run: | - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH - export LDFLAGS="-static -Wno-unused-command-line-argument" + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + fi make clean make -j4 ARCH=armv8 build ../tests/signature.sh $benchref @@ -311,8 +321,10 @@ jobs: - name: Test armv8-dotprod build if: matrix.config.run_armv8_tests run: | - export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH - export LDFLAGS="-static -Wno-unused-command-line-argument" + if [ $COMP == ndk ]; then + export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH + export LDFLAGS="-static -Wno-unused-command-line-argument" + fi make clean make -j4 ARCH=armv8-dotprod build ../tests/signature.sh $benchref diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index 132bd6f484f..773d6c2717d 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -130,7 +130,13 @@ case $uname_s in esac file_ext='tar' ;; - 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows system with POSIX compatibility layer + 'MINGW'*'ARM64'*) # Windows ARM64 system with POSIX compatibility layer + # TODO: older chips might be armv8, but we have no good way to detect, /proc/cpuinfo shows x86 info + file_os='windows' + true_arch='armv8-dotprod' + file_ext='zip' + ;; + 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows x86_64system with POSIX compatibility layer get_flags check_znver_1_2 set_arch_x86_64 diff --git a/src/Makefile b/src/Makefile index 17badefde80..fc27044f8b3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -567,11 +567,16 @@ ifeq ($(COMP),ndk) LDFLAGS += -static-libstdc++ -pie -lm -latomic endif +### Allow overwriting CXX from command line +ifdef COMPCXX + CXX=$(COMPCXX) +endif + # llvm-profdata must be version compatible with the specified CXX (be it clang, or the gcc alias) # make -j profile-build CXX=clang++-20 COMP=clang # Locate the version in the same directory as the compiler used, # with fallback to a generic one if it can't be located - LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX))))llvm-profdata + LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))llvm-profdata ifeq ($(wildcard $(LLVM_PROFDATA)),) LLVM_PROFDATA := llvm-profdata endif @@ -590,11 +595,6 @@ else endif endif -### Allow overwriting CXX from command line -ifdef COMPCXX - CXX=$(COMPCXX) -endif - ### Sometimes gcc is really clang ifeq ($(COMP),gcc) gccversion := $(shell $(CXX) --version 2>/dev/null) From 4f021cab3b87ac2d2e8214a03750945ec184e6ca Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 18 May 2025 14:53:15 -0700 Subject: [PATCH 1033/1309] Simplify allNode term in prior countermove Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 53632 W: 14008 L: 13805 D: 25819 Ptnml(0-2): 136, 6253, 13869, 6388, 170 https://tests.stockfishchess.org/tests/view/6828f2b26ec7634154f99b5e Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82482 W: 21202 L: 21045 D: 40235 Ptnml(0-2): 37, 8986, 23052, 9115, 51 https://tests.stockfishchess.org/tests/view/6829010a6ec7634154f99db3 closes https://github.com/official-stockfish/Stockfish/pull/6068 Bench: 2302782 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f5935818233..775aaceff67 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1451,10 +1451,9 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -324; + int bonusScale = -302; bonusScale += std::min(-(ss - 1)->statScore / 103, 323); bonusScale += std::min(73 * depth, 531); - bonusScale += 33 * !allNode; bonusScale += 174 * ((ss - 1)->moveCount > 8); bonusScale += 90 * (ss->cutoffCnt <= 3); bonusScale += 144 * (!ss->inCheck && bestValue <= ss->staticEval - 104); @@ -2245,4 +2244,4 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po } -} // namespace Stockfish +} // namespace Stockfish \ No newline at end of file From c13c1d2c30b3b2f9068f1a3b7c239e2264f329e0 Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 22 May 2025 18:41:53 +0200 Subject: [PATCH 1034/1309] Silence "may be used uninitialized" GCC 15 warning closes https://github.com/official-stockfish/Stockfish/pull/6083 No functional change --- src/nnue/nnue_accumulator.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 83128436486..83b09637a35 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -33,12 +33,16 @@ namespace Stockfish::Eval::NNUE { #if defined(__GNUC__) && !defined(__clang__) - #define sf_assume(cond) \ - do \ - { \ - if (!(cond)) \ - __builtin_unreachable(); \ - } while (0) + #if __GNUC__ >= 13 + #define sf_assume(cond) __attribute__((assume(cond))) + #else + #define sf_assume(cond) \ + do \ + { \ + if (!(cond)) \ + __builtin_unreachable(); \ + } while (0) + #endif #else // do nothing for other compilers #define sf_assume(cond) From 2662d6bf3544493fef718f05cb9a2c3ff1324d3d Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 22 May 2025 21:19:46 +0200 Subject: [PATCH 1035/1309] Update clang-format to v20 closes https://github.com/official-stockfish/Stockfish/pull/6085 No functional change --- .clang-format | 4 ++-- .github/workflows/clang-format.yml | 8 ++++---- src/Makefile | 4 ++-- src/nnue/nnue_feature_transformer.h | 6 ++---- src/thread.h | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.clang-format b/.clang-format index c71f0368ed5..a470cc0b88d 100644 --- a/.clang-format +++ b/.clang-format @@ -9,14 +9,14 @@ AllowAllParametersOfDeclarationOnNextLine: true AllowShortCaseLabelsOnASingleLine: false AllowShortEnumsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false -AlwaysBreakTemplateDeclarations: Yes +BreakTemplateDeclarations: Yes BasedOnStyle: WebKit BitFieldColonSpacing: After BinPackParameters: false BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Custom BraceWrapping: - AfterFunction: false + AfterFunction: false AfterClass: false AfterControlStatement: true BeforeElse: true diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index ab6b4350ec2..46607c1e91c 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -25,11 +25,11 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Run clang-format style check - uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0 + uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # @v4.15.0 id: clang-format continue-on-error: true with: - clang-format-version: "18" + clang-format-version: "20" exclude-regex: "incbin" - name: Comment on PR @@ -37,9 +37,9 @@ jobs: uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0 with: message: | - clang-format 18 needs to be run on this PR. + clang-format 20 needs to be run on this PR. If you do not have clang-format installed, the maintainer will run it when merging. - For the exact version please see https://packages.ubuntu.com/noble/clang-format-18. + For the exact version please see https://packages.ubuntu.com/plucky/clang-format-20. _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ comment_tag: execution diff --git a/src/Makefile b/src/Makefile index fc27044f8b3..72d06f09e58 100644 --- a/src/Makefile +++ b/src/Makefile @@ -163,8 +163,8 @@ lsx = no lasx = no STRIP = strip -ifneq ($(shell which clang-format-18 2> /dev/null),) - CLANG-FORMAT = clang-format-18 +ifneq ($(shell which clang-format-20 2> /dev/null),) + CLANG-FORMAT = clang-format-20 else CLANG-FORMAT = clang-format endif diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index b9b422a65a7..beb0c7f1c0e 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -122,8 +122,7 @@ using psqt_vec_t = int32x4_t; #define vec_add_16(a, b) vaddq_s16(a, b) #define vec_sub_16(a, b) vsubq_s16(a, b) #define vec_mulhi_16(a, b) vqdmulhq_s16(a, b) - #define vec_zero() \ - vec_t { 0 } + #define vec_zero() vec_t{0} #define vec_set_16(a) vdupq_n_s16(a) #define vec_max_16(a, b) vmaxq_s16(a, b) #define vec_min_16(a, b) vminq_s16(a, b) @@ -133,8 +132,7 @@ using psqt_vec_t = int32x4_t; #define vec_store_psqt(a, b) *(a) = (b) #define vec_add_psqt_32(a, b) vaddq_s32(a, b) #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) - #define vec_zero_psqt() \ - psqt_vec_t { 0 } + #define vec_zero_psqt() psqt_vec_t{0} #define NumRegistersSIMD 16 #define MaxChunkSize 16 diff --git a/src/thread.h b/src/thread.h index 912d4433528..00616097a7b 100644 --- a/src/thread.h +++ b/src/thread.h @@ -164,7 +164,7 @@ class ThreadPool { std::vector> threads; std::vector boundThreadToNumaNode; - uint64_t accumulate(std::atomic Search::Worker::*member) const { + uint64_t accumulate(std::atomic Search::Worker::* member) const { uint64_t sum = 0; for (auto&& th : threads) From 472cc764be8b46f1f5c30ed879e957a86369fb5c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 19 May 2025 14:47:59 -0700 Subject: [PATCH 1036/1309] Move SIMD code to a separate header Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 115328 W: 29903 L: 29777 D: 55648 Ptnml(0-2): 265, 12293, 32444, 12375, 287 https://tests.stockfishchess.org/tests/view/68300e086ec7634154f9b1d1 closes https://github.com/official-stockfish/Stockfish/pull/6086 no functional change --- src/Makefile | 4 +- src/misc.h | 16 + src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/layers/affine_transform.h | 24 +- .../layers/affine_transform_sparse_input.h | 68 +-- src/nnue/layers/simd.h | 134 ------ src/nnue/network.h | 1 + src/nnue/nnue_accumulator.cpp | 21 +- src/nnue/nnue_accumulator.h | 4 - src/nnue/nnue_architecture.h | 6 + src/nnue/nnue_common.h | 5 + src/nnue/nnue_feature_transformer.h | 223 +--------- src/nnue/simd.h | 418 ++++++++++++++++++ 13 files changed, 480 insertions(+), 446 deletions(-) delete mode 100644 src/nnue/layers/simd.h create mode 100644 src/nnue/simd.h diff --git a/src/Makefile b/src/Makefile index 72d06f09e58..9ea3e01b725 100644 --- a/src/Makefile +++ b/src/Makefile @@ -60,9 +60,9 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ - nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ + nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ - nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ + nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h diff --git a/src/misc.h b/src/misc.h index 84f11d6de38..756433290ee 100644 --- a/src/misc.h +++ b/src/misc.h @@ -317,6 +317,22 @@ void move_to_front(std::vector& vec, Predicate pred) { } } +#if defined(__GNUC__) && !defined(__clang__) + #if __GNUC__ >= 13 + #define sf_assume(cond) __attribute__((assume(cond))) + #else + #define sf_assume(cond) \ + do \ + { \ + if (!(cond)) \ + __builtin_unreachable(); \ + } while (0) + #endif +#else + // do nothing for other compilers + #define sf_assume(cond) +#endif + } // namespace Stockfish #endif // #ifndef MISC_H_INCLUDED diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index c91da2cc80b..70e9beeb15e 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -23,7 +23,7 @@ #include "../../bitboard.h" #include "../../position.h" #include "../../types.h" -#include "../nnue_accumulator.h" +#include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Features { diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 3920efb1722..b4440c1e3fb 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -25,7 +25,7 @@ #include #include "../nnue_common.h" -#include "simd.h" +#include "../simd.h" /* This file contains the definition for a fully connected layer (aka affine transform). @@ -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] = Simd::neon_m128_reduce_add_epi32(sum); + output[i] = SIMD::neon_m128_reduce_add_epi32(sum); #endif } @@ -191,20 +191,20 @@ class AffineTransform { #if defined(USE_AVX512) using vec_t = __m512i; #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32 #elif defined(USE_AVX2) using vec_t = __m256i; #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32 #elif defined(USE_NEON_DOTPROD) using vec_t = int32x4_t; #define vec_set_32 vdupq_n_s32 #define vec_add_dpbusd_32(acc, a, b) \ - Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ vreinterpretq_s8_s32(b)) #endif @@ -245,20 +245,20 @@ class AffineTransform { #if defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero() _mm256_setzero_si256() - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 - #define vec_hadd Simd::m256_hadd + #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 + #define vec_hadd SIMD::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero() _mm_setzero_si128() - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 - #define vec_hadd Simd::m128_hadd + #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32 + #define vec_hadd SIMD::m128_hadd #elif defined(USE_NEON_DOTPROD) using vec_t = int32x4_t; #define vec_setzero() vdupq_n_s32(0) #define vec_add_dpbusd_32(acc, a, b) \ - Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ vreinterpretq_s8_s32(b)) - #define vec_hadd Simd::neon_m128_hadd + #define vec_hadd SIMD::neon_m128_hadd #endif const auto inputVector = reinterpret_cast(input); diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index be5e30b5e2f..7c74d5e6402 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -22,14 +22,12 @@ #define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED #include -#include #include #include #include "../../bitboard.h" +#include "../simd.h" #include "../nnue_common.h" -#include "affine_transform.h" -#include "simd.h" /* This file contains the definition for a fully connected layer (aka affine transform) with block sparse input. @@ -77,53 +75,16 @@ alignas(CacheLineSize) static constexpr struct OffsetIndices { // Find indices of nonzero numbers in an int32_t array template void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { - #if defined(USE_SSSE3) - #if defined(USE_AVX512) - using vec_t = __m512i; - #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) - #elif defined(USE_AVX2) - using vec_t = __m256i; - #if defined(USE_VNNI) && !defined(USE_AVXVNNI) - #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) - #else - #define vec_nnz(a) \ - _mm256_movemask_ps( \ - _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) - #endif - #elif defined(USE_SSSE3) - using vec_t = __m128i; - #define vec_nnz(a) \ - _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) - #endif - using vec128_t = __m128i; - #define vec128_zero _mm_setzero_si128() - #define vec128_set_16(a) _mm_set1_epi16(a) - #if (USE_SSE41) - #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) - #else - #define vec128_load(a) _mm_load_si128(a) - #endif - #define vec128_storeu(a, b) _mm_storeu_si128(a, b) - #define vec128_add(a, b) _mm_add_epi16(a, b) - #elif defined(USE_NEON) - using vec_t = uint32x4_t; - static const std::uint32_t Mask[4] = {1, 2, 4, 8}; - #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) - using vec128_t = uint16x8_t; - #define vec128_zero vdupq_n_u16(0) - #define vec128_set_16(a) vdupq_n_u16(a) - #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) - #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) - #define vec128_add(a, b) vaddq_u16(a, b) - #endif - constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t); + using namespace SIMD; + + constexpr IndexType InputSimdWidth = sizeof(vec_uint_t) / sizeof(std::int32_t); // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); constexpr IndexType NumChunks = InputDimensions / ChunkSize; constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; constexpr IndexType OutputsPerChunk = ChunkSize / 8; - const auto inputVector = reinterpret_cast(input); + const auto inputVector = reinterpret_cast(input); IndexType count = 0; vec128_t base = vec128_zero; const vec128_t increment = vec128_set_16(8); @@ -133,7 +94,7 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou unsigned nnz = 0; for (IndexType j = 0; j < InputsPerChunk; ++j) { - const vec_t inputChunk = inputVector[i * InputsPerChunk + j]; + const vec_uint_t inputChunk = inputVector[i * InputsPerChunk + j]; nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); } for (IndexType j = 0; j < OutputsPerChunk; ++j) @@ -148,12 +109,7 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou } count_out = count; } - #undef vec_nnz - #undef vec128_zero - #undef vec128_set_16 - #undef vec128_load - #undef vec128_storeu - #undef vec128_add + #endif // Sparse input implementation @@ -232,27 +188,27 @@ class AffineTransformSparseInput { using invec_t = __m512i; using outvec_t = __m512i; #define vec_set_32 _mm512_set1_epi32 - #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32 #elif defined(USE_AVX2) using invec_t = __m256i; using outvec_t = __m256i; #define vec_set_32 _mm256_set1_epi32 - #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 #elif defined(USE_SSSE3) using invec_t = __m128i; using outvec_t = __m128i; #define vec_set_32 _mm_set1_epi32 - #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32 #elif defined(USE_NEON_DOTPROD) using invec_t = int8x16_t; using outvec_t = int32x4_t; #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) - #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::dotprod_m128_add_dpbusd_epi32 #elif defined(USE_NEON) using invec_t = int8x16_t; using outvec_t = int32x4_t; #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) - #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32 + #define vec_add_dpbusd_32 SIMD::neon_m128_add_dpbusd_epi32 #endif static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); diff --git a/src/nnue/layers/simd.h b/src/nnue/layers/simd.h deleted file mode 100644 index 70ca68a0c70..00000000000 --- a/src/nnue/layers/simd.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#ifndef STOCKFISH_SIMD_H_INCLUDED -#define STOCKFISH_SIMD_H_INCLUDED - -#if defined(USE_AVX2) - #include - -#elif defined(USE_SSE41) - #include - -#elif defined(USE_SSSE3) - #include - -#elif defined(USE_SSE2) - #include - -#elif defined(USE_NEON) - #include -#endif - -namespace Stockfish::Simd { - -#if defined(USE_AVX512) - -[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { - return _mm512_reduce_add_epi32(sum) + bias; -} - -[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { - - #if defined(USE_VNNI) - acc = _mm512_dpbusd_epi32(acc, a, b); - #else - __m512i product0 = _mm512_maddubs_epi16(a, b); - product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); - acc = _mm512_add_epi32(acc, product0); - #endif -} - -#endif - -#if defined(USE_AVX2) - -[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { - __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); - sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); - return _mm_cvtsi128_si32(sum128) + bias; -} - -[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) { - - #if defined(USE_VNNI) - acc = _mm256_dpbusd_epi32(acc, a, b); - #else - __m256i product0 = _mm256_maddubs_epi16(a, b); - product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); - acc = _mm256_add_epi32(acc, product0); - #endif -} - -#endif - -#if defined(USE_SSSE3) - -[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC - sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB - return _mm_cvtsi128_si32(sum) + bias; -} - -[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) { - - __m128i product0 = _mm_maddubs_epi16(a, b); - product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); - acc = _mm_add_epi32(acc, product0); -} - -#endif - -#if defined(USE_NEON_DOTPROD) - -[[maybe_unused]] static void -dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { - - acc = vdotq_s32(acc, a, b); -} -#endif - -#if defined(USE_NEON) - -[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { - #if USE_NEON >= 8 - return vaddvq_s32(s); - #else - return s[0] + s[1] + s[2] + s[3]; - #endif -} - -[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { - return neon_m128_reduce_add_epi32(sum) + bias; -} - -#endif - -#if USE_NEON >= 8 -[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { - - int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); - int16x8_t product1 = vmull_high_s8(a, b); - int16x8_t sum = vpaddq_s16(product0, product1); - acc = vpadalq_s16(acc, sum); -} -#endif -} - -#endif // STOCKFISH_SIMD_H_INCLUDED diff --git a/src/nnue/network.h b/src/nnue/network.h index cd32c5312fc..c9358823bdb 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -32,6 +32,7 @@ #include "../types.h" #include "nnue_accumulator.h" #include "nnue_architecture.h" +#include "nnue_common.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 83b09637a35..d13105aa47e 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -28,25 +28,12 @@ #include "../position.h" #include "../types.h" #include "nnue_architecture.h" -#include "nnue_feature_transformer.h" +#include "nnue_feature_transformer.h" // IWYU pragma: keep +#include "simd.h" namespace Stockfish::Eval::NNUE { -#if defined(__GNUC__) && !defined(__clang__) - #if __GNUC__ >= 13 - #define sf_assume(cond) __attribute__((assume(cond))) - #else - #define sf_assume(cond) \ - do \ - { \ - if (!(cond)) \ - __builtin_unreachable(); \ - } while (0) - #endif -#else - // do nothing for other compilers - #define sf_assume(cond) -#endif +using namespace SIMD; namespace { @@ -381,7 +368,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache) { - using Tiling [[maybe_unused]] = SIMDTiling; + using Tiling [[maybe_unused]] = SIMDTiling; const Square ksq = pos.square(Perspective); auto& entry = cache[ksq][Perspective]; diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index aa9e2a676da..10aadc91776 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -37,10 +37,6 @@ class Position; namespace Stockfish::Eval::NNUE { -using BiasType = std::int16_t; -using PSQTWeightType = std::int32_t; -using IndexType = std::uint32_t; - template struct alignas(CacheLineSize) Accumulator; diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 0c9f097dc60..c020ce05b38 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -49,6 +49,12 @@ constexpr int L3Small = 32; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; +// 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. +static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); + template struct NetworkArchitecture { static constexpr IndexType TransformedFeatureDimensions = L1; diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f21a8dec7df..35cc8a5fead 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -48,6 +48,11 @@ namespace Stockfish::Eval::NNUE { +using BiasType = std::int16_t; +using WeightType = std::int16_t; +using PSQTWeightType = std::int32_t; +using IndexType = std::uint32_t; + // Version of the evaluation file constexpr std::uint32_t Version = 0x7AF32F20u; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index beb0c7f1c0e..37634cbc6b7 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -31,174 +31,10 @@ #include "nnue_accumulator.h" #include "nnue_architecture.h" #include "nnue_common.h" +#include "simd.h" namespace Stockfish::Eval::NNUE { -using BiasType = std::int16_t; -using WeightType = std::int16_t; -using PSQTWeightType = std::int32_t; - -// 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. -#define VECTOR - -static_assert(PSQTBuckets % 8 == 0, - "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); - -#ifdef USE_AVX512 -using vec_t = __m512i; -using psqt_vec_t = __m256i; - #define vec_load(a) _mm512_load_si512(a) - #define vec_store(a, b) _mm512_store_si512(a, b) - #define vec_add_16(a, b) _mm512_add_epi16(a, b) - #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) - #define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b) - #define vec_zero() _mm512_setzero_epi32() - #define vec_set_16(a) _mm512_set1_epi16(a) - #define vec_max_16(a, b) _mm512_max_epi16(a, b) - #define vec_min_16(a, b) _mm512_min_epi16(a, b) - #define vec_slli_16(a, b) _mm512_slli_epi16(a, b) - // Inverse permuted at load time - #define vec_packus_16(a, b) _mm512_packus_epi16(a, b) - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a, b) _mm256_store_si256(a, b) - #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) - #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 16 - #define MaxChunkSize 64 - -#elif USE_AVX2 -using vec_t = __m256i; -using psqt_vec_t = __m256i; - #define vec_load(a) _mm256_load_si256(a) - #define vec_store(a, b) _mm256_store_si256(a, b) - #define vec_add_16(a, b) _mm256_add_epi16(a, b) - #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) - #define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b) - #define vec_zero() _mm256_setzero_si256() - #define vec_set_16(a) _mm256_set1_epi16(a) - #define vec_max_16(a, b) _mm256_max_epi16(a, b) - #define vec_min_16(a, b) _mm256_min_epi16(a, b) - #define vec_slli_16(a, b) _mm256_slli_epi16(a, b) - // Inverse permuted at load time - #define vec_packus_16(a, b) _mm256_packus_epi16(a, b) - #define vec_load_psqt(a) _mm256_load_si256(a) - #define vec_store_psqt(a, b) _mm256_store_si256(a, b) - #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) - #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) - #define vec_zero_psqt() _mm256_setzero_si256() - #define NumRegistersSIMD 16 - #define MaxChunkSize 32 - -#elif USE_SSE2 -using vec_t = __m128i; -using psqt_vec_t = __m128i; - #define vec_load(a) (*(a)) - #define vec_store(a, b) *(a) = (b) - #define vec_add_16(a, b) _mm_add_epi16(a, b) - #define vec_sub_16(a, b) _mm_sub_epi16(a, b) - #define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b) - #define vec_zero() _mm_setzero_si128() - #define vec_set_16(a) _mm_set1_epi16(a) - #define vec_max_16(a, b) _mm_max_epi16(a, b) - #define vec_min_16(a, b) _mm_min_epi16(a, b) - #define vec_slli_16(a, b) _mm_slli_epi16(a, b) - #define vec_packus_16(a, b) _mm_packus_epi16(a, b) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a, b) *(a) = (b) - #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) - #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b) - #define vec_zero_psqt() _mm_setzero_si128() - #define NumRegistersSIMD (Is64Bit ? 16 : 8) - #define MaxChunkSize 16 - -#elif USE_NEON -using vec_t = int16x8_t; -using psqt_vec_t = int32x4_t; - #define vec_load(a) (*(a)) - #define vec_store(a, b) *(a) = (b) - #define vec_add_16(a, b) vaddq_s16(a, b) - #define vec_sub_16(a, b) vsubq_s16(a, b) - #define vec_mulhi_16(a, b) vqdmulhq_s16(a, b) - #define vec_zero() vec_t{0} - #define vec_set_16(a) vdupq_n_s16(a) - #define vec_max_16(a, b) vmaxq_s16(a, b) - #define vec_min_16(a, b) vminq_s16(a, b) - #define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b)) - #define vec_packus_16(a, b) reinterpret_cast(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b))) - #define vec_load_psqt(a) (*(a)) - #define vec_store_psqt(a, b) *(a) = (b) - #define vec_add_psqt_32(a, b) vaddq_s32(a, b) - #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) - #define vec_zero_psqt() psqt_vec_t{0} - #define NumRegistersSIMD 16 - #define MaxChunkSize 16 - -#else - #undef VECTOR - -#endif - -struct Vec16Wrapper { -#ifdef VECTOR - using type = vec_t; - static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); } - static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); } -#else - using type = BiasType; - static type add(const type& lhs, const type& rhs) { return lhs + rhs; } - static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } -#endif -}; - -struct Vec32Wrapper { -#ifdef VECTOR - using type = psqt_vec_t; - static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); } - static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); } -#else - using type = PSQTWeightType; - static type add(const type& lhs, const type& rhs) { return lhs + rhs; } - static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } -#endif -}; - -enum UpdateOperation { - Add, - Sub -}; - -template = true> -typename VecWrapper::type fused(const typename VecWrapper::type& in) { - return in; -} - -template, bool> = true, - std::enable_if_t = true> -typename VecWrapper::type -fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) { - switch (update_op) - { - case Add : - return fused(VecWrapper::add(in, operand), operands...); - case Sub : - return fused(VecWrapper::sub(in, operand), operands...); - default : - static_assert(update_op == Add || update_op == Sub, - "Only Add and Sub are currently supported."); - return typename VecWrapper::type(); - } -} - // Returns the inverse of a permutation template constexpr std::array @@ -240,61 +76,6 @@ void permute(T (&data)[N], const std::array& order) { } } -// Compute optimal SIMD register count for feature transformer accumulation. -template -class SIMDTiling { -#ifdef VECTOR - // We use __m* types as template arguments, which causes GCC to emit warnings - // about losing some attribute information. This is irrelevant to us as we - // only take their size, so the following pragma are harmless. - #if defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wignored-attributes" - #endif - - template - static constexpr int BestRegisterCount() { - constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType); - constexpr std::size_t LaneSize = sizeof(LaneType); - - static_assert(RegisterSize >= LaneSize); - static_assert(MaxRegisters <= NumRegistersSIMD); - static_assert(MaxRegisters > 0); - static_assert(NumRegistersSIMD > 0); - static_assert(RegisterSize % LaneSize == 0); - static_assert((NumLanes * LaneSize) % RegisterSize == 0); - - const int ideal = (NumLanes * LaneSize) / RegisterSize; - if (ideal <= MaxRegisters) - return ideal; - - // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters - for (int divisor = MaxRegisters; divisor > 1; --divisor) - if (ideal % divisor == 0) - return divisor; - - return 1; - } - - #if defined(__GNUC__) - #pragma GCC diagnostic pop - #endif - - public: - static constexpr int NumRegs = - BestRegisterCount(); - static constexpr int NumPsqtRegs = - BestRegisterCount(); - - static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; - static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; - - static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); - static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); -#endif -}; - - // Input feature converter template class FeatureTransformer { @@ -397,6 +178,8 @@ class FeatureTransformer { OutputType* output, int bucket) const { + using namespace SIMD; + accumulatorStack.evaluate(pos, *this, *cache); const auto& accumulatorState = accumulatorStack.latest(); diff --git a/src/nnue/simd.h b/src/nnue/simd.h new file mode 100644 index 00000000000..56022300002 --- /dev/null +++ b/src/nnue/simd.h @@ -0,0 +1,418 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_SIMD_H_INCLUDED +#define NNUE_SIMD_H_INCLUDED + +#if defined(USE_AVX2) + #include + +#elif defined(USE_SSE41) + #include + +#elif defined(USE_SSSE3) + #include + +#elif defined(USE_SSE2) + #include + +#elif defined(USE_NEON) + #include +#endif + +#include "../types.h" +#include "nnue_common.h" + +namespace Stockfish::Eval::NNUE::SIMD { + +// 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. +#define VECTOR + +#ifdef USE_AVX512 +using vec_t = __m512i; +using vec128_t = __m128i; +using psqt_vec_t = __m256i; +using vec_uint_t = __m512i; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a, b) _mm512_store_si512(a, b) + #define vec_add_16(a, b) _mm512_add_epi16(a, b) + #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b) + #define vec_zero() _mm512_setzero_epi32() + #define vec_set_16(a) _mm512_set1_epi16(a) + #define vec_max_16(a, b) _mm512_max_epi16(a, b) + #define vec_min_16(a, b) _mm512_min_epi16(a, b) + #define vec_slli_16(a, b) _mm512_slli_epi16(a, b) + // Inverse permuted at load time + #define vec_packus_16(a, b) _mm512_packus_epi16(a, b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + + #ifdef USE_SSSE3 + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #endif + + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + #define NumRegistersSIMD 16 + #define MaxChunkSize 64 + +#elif USE_AVX2 +using vec_t = __m256i; +using vec128_t = __m128i; +using psqt_vec_t = __m256i; +using vec_uint_t = __m256i; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a, b) _mm256_store_si256(a, b) + #define vec_add_16(a, b) _mm256_add_epi16(a, b) + #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b) + #define vec_zero() _mm256_setzero_si256() + #define vec_set_16(a) _mm256_set1_epi16(a) + #define vec_max_16(a, b) _mm256_max_epi16(a, b) + #define vec_min_16(a, b) _mm256_min_epi16(a, b) + #define vec_slli_16(a, b) _mm256_slli_epi16(a, b) + // Inverse permuted at load time + #define vec_packus_16(a, b) _mm256_packus_epi16(a, b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + + #ifdef USE_SSSE3 + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) \ + _mm256_movemask_ps( \ + _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #endif + + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + + #define NumRegistersSIMD 16 + #define MaxChunkSize 32 + +#elif USE_SSE2 +using vec_t = __m128i; +using vec128_t = __m128i; +using psqt_vec_t = __m128i; +using vec_uint_t = __m128i; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) _mm_add_epi16(a, b) + #define vec_sub_16(a, b) _mm_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b) + #define vec_zero() _mm_setzero_si128() + #define vec_set_16(a) _mm_set1_epi16(a) + #define vec_max_16(a, b) _mm_max_epi16(a, b) + #define vec_min_16(a, b) _mm_min_epi16(a, b) + #define vec_slli_16(a, b) _mm_slli_epi16(a, b) + #define vec_packus_16(a, b) _mm_packus_epi16(a, b) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b) + #define vec_zero_psqt() _mm_setzero_si128() + + #ifdef USE_SSSE3 + #define vec_nnz(a) \ + _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) + #endif + + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + + #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define MaxChunkSize 16 + +#elif USE_NEON +using vec_t = int16x8_t; +using psqt_vec_t = int32x4_t; +using vec128_t = uint16x8_t; +using vec_uint_t = uint32x4_t; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) vaddq_s16(a, b) + #define vec_sub_16(a, b) vsubq_s16(a, b) + #define vec_mulhi_16(a, b) vqdmulhq_s16(a, b) + #define vec_zero() vec_t{0} + #define vec_set_16(a) vdupq_n_s16(a) + #define vec_max_16(a, b) vmaxq_s16(a, b) + #define vec_min_16(a, b) vminq_s16(a, b) + #define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b)) + #define vec_packus_16(a, b) reinterpret_cast(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b))) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) vaddq_s32(a, b) + #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) + #define vec_zero_psqt() psqt_vec_t{0} + +static constexpr std::uint32_t Mask[4] = {1, 2, 4, 8}; + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) + + #define NumRegistersSIMD 16 + #define MaxChunkSize 16 + +#else + #undef VECTOR + +#endif + +struct Vec16Wrapper { +#ifdef VECTOR + using type = vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); } +#else + using type = BiasType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +struct Vec32Wrapper { +#ifdef VECTOR + using type = psqt_vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); } +#else + using type = PSQTWeightType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +enum UpdateOperation { + Add, + Sub +}; + +template = true> +typename VecWrapper::type fused(const typename VecWrapper::type& in) { + return in; +} + +template, bool> = true, + std::enable_if_t = true> +typename VecWrapper::type +fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) { + switch (update_op) + { + case Add : + return fused(VecWrapper::add(in, operand), operands...); + case Sub : + return fused(VecWrapper::sub(in, operand), operands...); + default : + static_assert(update_op == Add || update_op == Sub, + "Only Add and Sub are currently supported."); + return typename VecWrapper::type(); + } +} + +#if defined(USE_AVX512) + +[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { + return _mm512_reduce_add_epi32(sum) + bias; +} + +[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { + + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); + #else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_AVX2) + +[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + return _mm_cvtsi128_si32(sum128) + bias; +} + +[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) { + + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); + #else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_SSSE3) + +[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + return _mm_cvtsi128_si32(sum) + bias; +} + +[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) { + + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, product0); +} + +#endif + +#if defined(USE_NEON_DOTPROD) + +[[maybe_unused]] static void +dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + acc = vdotq_s32(acc, a, b); +} +#endif + +#if defined(USE_NEON) + +[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { + #if USE_NEON >= 8 + return vaddvq_s32(s); + #else + return s[0] + s[1] + s[2] + s[3]; + #endif +} + +[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { + return neon_m128_reduce_add_epi32(sum) + bias; +} + +#endif + +#if USE_NEON >= 8 +[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); +} +#endif + + +// Compute optimal SIMD register count for feature transformer accumulation. +template +class SIMDTiling { +#ifdef VECTOR + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + #endif + + template + static constexpr int BestRegisterCount() { + constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType); + constexpr std::size_t LaneSize = sizeof(LaneType); + + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); + + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; + + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; + + return 1; + } + + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + + public: + static constexpr int NumRegs = + BestRegisterCount(); + static constexpr int NumPsqtRegs = + BestRegisterCount(); + + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; + static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; + + static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); + static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); +#endif +}; +} + +#endif From f58d923fe0e8cb4545237c00ffebd264fe7fecaf Mon Sep 17 00:00:00 2001 From: pb00067 Date: Fri, 23 May 2025 09:29:32 +0200 Subject: [PATCH 1037/1309] Simplify & improve stalemate detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change is functional because now we verify for stalemate also on captures and when not giving check. Green STC test on stalemate-book https://tests.stockfishchess.org/tests/view/682d878f6ec7634154f9ad2f Elo: 2.29 ± 1.3 (95%) LOS: 100.0% Total: 10000 W: 4637 L: 4571 D: 792 Ptnml(0-2): 2, 132, 4664, 202, 0 nElo: 12.42 ± 6.8 (95%) PairsRatio: 1.51 Green LTC test on stalemate-book https://tests.stockfishchess.org/tests/view/682daa2d6ec7634154f9ad67 Elo: 0.80 ± 0.8 (95%) LOS: 96.9% Total: 10000 W: 4727 L: 4704 D: 569 Ptnml(0-2): 0, 64, 4849, 87, 0 nElo: 6.51 ± 6.8 (95%) PairsRatio: 1.36 Passed non-regression test @ LTC https://tests.stockfishchess.org/tests/view/682dd10d6ec7634154f9adb3 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 148512 W: 38135 L: 38046 D: 72331 Ptnml(0-2): 55, 15759, 42558, 15810, 74 N.B.: The unique concern I have, is that due changes in future a negative SEE capture see might be returned in GOOD_CAPTURE stage. In this case the assert in can_move_king_or_pawn() will trigger since we must guarantee that all moves (also quiets) are generated in movepicker when calling can_move_king_or_pawn(). closes https://github.com/official-stockfish/Stockfish/pull/6088 bench: 2178135 --- src/movepick.cpp | 3 ++- src/search.cpp | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 6d00dd941c8..f3d6fef8b2c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -314,7 +314,8 @@ void MovePicker::skip_quiet_moves() { skipQuiets = true; } // this function must be called after all quiet moves and captures have been generated bool MovePicker::can_move_king_or_pawn() { - assert(stage == GOOD_QUIET || stage == BAD_QUIET || stage == EVASION); + // SEE negative captures shouldn't be returned in GOOD_CAPTURE stage + assert(stage > GOOD_CAPTURE && stage != EVASION_INIT); for (ExtMove* m = moves; m < endMoves; ++m) { diff --git a/src/search.cpp b/src/search.cpp index 775aaceff67..ee876f85558 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1073,9 +1073,7 @@ Value Search::Worker::search( if (!pos.see_ge(move, -158 * depth - seeHist)) { bool mayStalemateTrap = - depth > 2 && givesCheck && alpha < 0 - && !capture // we consider that captures will likely destroy the stalemate configuration - && pos.non_pawn_material(us) == PieceValue[movedPiece] + depth > 2 && alpha < 0 && pos.non_pawn_material(us) == PieceValue[movedPiece] && PieceValue[movedPiece] >= RookValue // it can't be stalemate if we moved a piece adjacent to the king && !(attacks_bb(pos.square(us)) & move.from_sq()) From b1b5893a8e344155d01a1658b79670a8ed1fd160 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Fri, 23 May 2025 18:33:01 +0200 Subject: [PATCH 1038/1309] Minor code improvements - Remove / add empty lines - fix the `ttcapture` comment - remove the `bonus` variable for `ttMoveHistory` - remove unnecessary parentheses / brackets - refactor the movepick good quiet stage - rename `endMoves` to `endCur`, as the previous name suggests that it points to the end of all generated moves, which it does not. closes https://github.com/official-stockfish/Stockfish/pull/6089 No functional change. Co-Authored-By: xu-shawn <50402888+xu-shawn@users.noreply.github.com> --- src/movepick.cpp | 33 ++++++++++++++++++--------------- src/movepick.h | 4 ++-- src/search.cpp | 15 +++++---------- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index f3d6fef8b2c..faef753be2b 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -173,7 +173,7 @@ void MovePicker::score() { if (KNIGHT <= pt && pt <= QUEEN) { static constexpr int bonus[QUEEN + 1] = {0, 0, 144, 144, 256, 517}; - int v = (threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from)); + int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); m.value += bonus[pt] * v; } @@ -200,7 +200,7 @@ void MovePicker::score() { template Move MovePicker::select(Pred filter) { - for (; cur < endMoves; ++cur) + for (; cur < endCur; ++cur) if (*cur != ttMove && filter()) return *cur++; @@ -227,10 +227,10 @@ Move MovePicker::next_move() { case PROBCUT_INIT : case QCAPTURE_INIT : cur = endBadCaptures = moves; - endMoves = generate(pos, cur); + endCur = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); + partial_insertion_sort(cur, endCur, std::numeric_limits::min()); ++stage; goto top; @@ -250,10 +250,10 @@ Move MovePicker::next_move() { if (!skipQuiets) { cur = endBadQuiets = endBadCaptures; - endMoves = generate(pos, cur); + endCur = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, -3560 * depth); + partial_insertion_sort(cur, endCur, -3560 * depth); } ++stage; @@ -261,13 +261,16 @@ Move MovePicker::next_move() { case GOOD_QUIET : if (!skipQuiets && select([&]() { - return cur->value > -14000 ? true : (*endBadQuiets++ = *cur, false); + if (cur->value > -14000) + return true; + *endBadQuiets++ = *cur; + return false; })) return *(cur - 1); // Prepare the pointers to loop over the bad captures - cur = moves; - endMoves = endBadCaptures; + cur = moves; + endCur = endBadCaptures; ++stage; [[fallthrough]]; @@ -277,8 +280,8 @@ Move MovePicker::next_move() { return *(cur - 1); // Prepare the pointers to loop over the bad quiets - cur = endBadCaptures; - endMoves = endBadQuiets; + cur = endBadCaptures; + endCur = endBadQuiets; ++stage; [[fallthrough]]; @@ -290,11 +293,11 @@ Move MovePicker::next_move() { return Move::none(); case EVASION_INIT : - cur = moves; - endMoves = generate(pos, cur); + cur = moves; + endCur = generate(pos, cur); score(); - partial_insertion_sort(cur, endMoves, std::numeric_limits::min()); + partial_insertion_sort(cur, endCur, std::numeric_limits::min()); ++stage; [[fallthrough]]; @@ -317,7 +320,7 @@ bool MovePicker::can_move_king_or_pawn() { // SEE negative captures shouldn't be returned in GOOD_CAPTURE stage assert(stage > GOOD_CAPTURE && stage != EVASION_INIT); - for (ExtMove* m = moves; m < endMoves; ++m) + for (ExtMove* m = moves; m < endCur; ++m) { PieceType movedPieceType = type_of(pos.moved_piece(*m)); if ((movedPieceType == PAWN || movedPieceType == KING) && pos.legal(*m)) diff --git a/src/movepick.h b/src/movepick.h index 4218ab5a3e0..2634fdd780f 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -58,7 +58,7 @@ class MovePicker { template void score(); ExtMove* begin() { return cur; } - ExtMove* end() { return endMoves; } + ExtMove* end() { return endCur; } const Position& pos; const ButterflyHistory* mainHistory; @@ -67,7 +67,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove * cur, *endMoves, *endBadCaptures, *endBadQuiets; + ExtMove * cur, *endCur, *endBadCaptures, *endBadQuiets; int stage; int threshold; Depth depth; diff --git a/src/search.cpp b/src/search.cpp index ee876f85558..673caacb9ad 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -680,8 +680,6 @@ Value Search::Worker::search( && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) && (cutNode == (ttData.value >= beta) || depth > 5)) { - - // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) { @@ -712,15 +710,15 @@ Value Search::Worker::search( if (!is_valid(ttDataNext.value)) return ttData.value; + if (ttData.value >= beta && -ttDataNext.value >= beta) return ttData.value; + if (ttData.value <= alpha && -ttDataNext.value <= alpha) return ttData.value; } else - { return ttData.value; - } } } @@ -1215,7 +1213,7 @@ Value Search::Worker::search( if (cutNode) r += 2864 + 966 * !ttData.move; - // Increase reduction if ttMove is a capture but the current move is not a capture + // Increase reduction if ttMove is a capture if (ttCapture) r += 1210 + (depth < 8) * 963; @@ -1253,7 +1251,7 @@ Value Search::Worker::search( // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))) - + ((ss - 1)->isPvNode); + + (ss - 1)->isPvNode; ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); @@ -1440,10 +1438,7 @@ Value Search::Worker::search( update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, ttData.move, moveCount); if (!PvNode) - { - int bonus = bestMove == ttData.move ? 800 : -879; - ttMoveHistory << bonus; - } + ttMoveHistory << (bestMove == ttData.move ? 800 : -879); } // Bonus for prior quiet countermove that caused the fail low From e6ec4705a868f904fd4237703e4c2d0a7ae1ad6b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 23 May 2025 11:56:20 -0700 Subject: [PATCH 1039/1309] Remove deprecated arch from codeql closes https://github.com/official-stockfish/Stockfish/pull/6090 no functional change --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d01ed41fea6..a338083cfe9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: - name: Build working-directory: src - run: make -j build ARCH=x86-64-modern + run: make -j build - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 From fe7b9b14d22bb96a0f6b4dd5aa256e4d02bd84d0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 24 May 2025 14:39:32 +0300 Subject: [PATCH 1040/1309] Implement smoother reduction in time management Implement smoother time reduction in time management by replacing a conditional assignment with a continuous sigmoid-based function. The updated logic employs a sigmoid-like function for a more gradual adjustment. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 64448 W: 16838 L: 16492 D: 31118 Ptnml(0-2): 145, 7214, 17207, 7466, 192 https://tests.stockfishchess.org/tests/view/6829dc046ec7634154f99fba Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 407340 W: 104458 L: 103408 D: 199474 Ptnml(0-2): 196, 42281, 117664, 43335, 194 https://tests.stockfishchess.org/tests/view/6829fe1b6ec7634154f9a036 closes https://github.com/official-stockfish/Stockfish/pull/6091 No functional change --- src/search.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 673caacb9ad..8d2d5720de1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -466,7 +466,9 @@ void Search::Worker::iterative_deepening() { fallingEval = std::clamp(fallingEval, 0.5786, 1.6752); // If the bestMove is stable over several iterations, reduce time accordingly - timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.4857 : 0.7046; + double k = 0.527; + double center = lastBestMoveDepth + 11; + timeReduction = 0.8 + 0.84 / (1.077 + std::exp(-k * (completedDepth - center))); double reduction = (1.4540 + mainThread->previousTimeReduction) / (2.1593 * timeReduction); double bestMoveInstability = 0.9929 + 1.8519 * totBestMoveChanges / threads.size(); @@ -2237,4 +2239,4 @@ bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& po } -} // namespace Stockfish \ No newline at end of file +} // namespace Stockfish From eb27d9420f3c85efc1affe661465224932c541ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=97=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=BF=D0=BE=D0=B2?= <67618307+kokodio@users.noreply.github.com> Date: Sun, 25 May 2025 13:17:35 +0500 Subject: [PATCH 1041/1309] Make ProbCut search shallower in cutNode Passed STC: https://tests.stockfishchess.org/tests/view/6832d2436ec7634154f9b4fc LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 455072 W: 118162 L: 117237 D: 219673 Ptnml(0-2): 1233, 53409, 117362, 54264, 1268 Passed LTC: https://tests.stockfishchess.org/tests/view/6833323e6ec7634154f9ba17 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 128436 W: 32916 L: 32415 D: 63105 Ptnml(0-2): 50, 13737, 36137, 14250, 44 closes https://github.com/official-stockfish/Stockfish/pull/6093 Bench: 2232447 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 8d2d5720de1..8c31fb3a7b7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -928,7 +928,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); - Depth probCutDepth = std::max(depth - 4, 0); + Depth probCutDepth = std::max(depth - (4 + cutNode), 0); while ((move = mp.next_move()) != Move::none()) { From 805a2c1672e080106bc9eee00cc7195732c50fa1 Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Sun, 25 May 2025 20:19:28 +0200 Subject: [PATCH 1042/1309] Simplify FutilityMoveCount Inlined condition, instead of a function. closes https://github.com/official-stockfish/Stockfish/pull/6096 no functional change --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8c31fb3a7b7..d7abef3d25e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -69,10 +69,6 @@ namespace { // so changing them or adding conditions that are similar requires // tests at these types of time controls. -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* const ss) { const Color us = pos.side_to_move(); const auto m = (ss - 1)->currentMove; @@ -1047,7 +1043,7 @@ Value Search::Worker::search( if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold - if (moveCount >= futility_move_count(improving, depth)) + if (moveCount >= (3 + depth * depth) / (2 - improving)) mp.skip_quiet_moves(); // Reduced depth of the next LMR search From 00b1540e01465ca39184fd33f28164c0803a34db Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 17 May 2025 18:01:25 +0200 Subject: [PATCH 1043/1309] Always Decrease Reduction on TTMove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passed VVLTC w/ LTC Bounds: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 57792 W: 15005 L: 14676 D: 28111 Ptnml(0-2): 2, 5241, 18082, 5568, 3 https://tests.stockfishchess.org/tests/view/682a0e3c6ec7634154f9a07e Passed VVLTC w/ STC Bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 372298 W: 96342 L: 95655 D: 180301 Ptnml(0-2): 37, 34598, 116181, 35307, 26 https://tests.stockfishchess.org/tests/view/682a45b16ec7634154f9a3b3 STC Elo Estimate: Elo: 0.15 ± 1.4 (95%) LOS: 58.3% Total: 59612 W: 15414 L: 15388 D: 28810 Ptnml(0-2): 166, 6959, 15527, 6991, 163 nElo: 0.30 ± 2.8 (95%) PairsRatio: 1.00 https://tests.stockfishchess.org/tests/view/68335d276ec7634154f9c25c closes https://github.com/official-stockfish/Stockfish/pull/6095 bench 2634355 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d7abef3d25e..5b16b269d75 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1223,7 +1223,7 @@ Value Search::Worker::search( r += (ss->quietMoveStreak - 1) * 50; // For first picked move (ttMove) reduce reduction - else if (move == ttData.move) + if (move == ttData.move) r -= 2006; if (capture) From bebffc5622d8dba4c8db1fbc9ad7bac5b1d9012a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=97=D0=B0=D1=80?= =?UTF-8?q?=D0=B8=D0=BF=D0=BE=D0=B2?= <67618307+kokodio@users.noreply.github.com> Date: Sun, 25 May 2025 17:13:01 +0500 Subject: [PATCH 1044/1309] Adjust futility pruning thresholds using history Passed STC: https://tests.stockfishchess.org/tests/view/6833095a6ec7634154f9b5b3 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 56896 W: 14946 L: 14604 D: 27346 Ptnml(0-2): 117, 6674, 14561, 6942, 154 Passed LTC: https://tests.stockfishchess.org/tests/view/6833179d6ec7634154f9b5da LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 200742 W: 51660 L: 51012 D: 98070 Ptnml(0-2): 96, 21520, 56473, 22204, 78 Passed Non-regression SMP STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 29080 W: 7591 L: 7373 D: 14116 Ptnml(0-2): 38, 3178, 7881, 3414, 29 https://tests.stockfishchess.org/tests/view/6833689d6ec7634154f9c2ba closes https://github.com/official-stockfish/Stockfish/pull/6092 Bench: 2305697 --- AUTHORS | 1 + src/search.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8caa2285835..be88a8e967d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -132,6 +132,7 @@ Kenneth Lee (kennethlee33) Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) +Kirill Zaripov (kokodio) Kojirion Krisztián Peőcz Krystian Kuzniarek (kuzkry) diff --git a/src/search.cpp b/src/search.cpp index 5b16b269d75..10e2448299a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1095,8 +1095,9 @@ Value Search::Worker::search( lmrDepth += history / 3388; - Value futilityValue = ss->staticEval + (bestMove ? 46 : 138) + 117 * lmrDepth - + 102 * (ss->staticEval > alpha); + Value baseFutility = (bestMove ? 46 : 138 + std::abs(history / 300)); + Value futilityValue = + ss->staticEval + baseFutility + 117 * lmrDepth + 102 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning From e3adfaf8fcb47653d3442f47ea6c3faab161b785 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Thu, 22 May 2025 01:26:02 +0200 Subject: [PATCH 1045/1309] build & ci: update to NDK r27c API level 29 Update to the latest LTS version NDK r27c (27.2.12479018), the previous NDK are unsupported by Google, see: https://developer.android.com/ndk/downloads A build with NDK r27c and API level < 29 returns this error: "executable's TLS segment is underaligned: alignment is 8 (skew 0), needs to be at least 64 for ARM64 Bionic" Update the API level to 29 to use the native ELF LTS and avoid the error: https://android.googlesource.com/platform/bionic/+/HEAD/docs/elf-tls.md https://android.googlesource.com/platform/bionic/+/HEAD/android-changes-for-ndk-developers.md#elf-tls-available-for-api-level-29 A dynamic link build of Stockfish uses these libraries: ldd stockfish-android-armv8-dynamic-api35 libm.so => /system/lib64/libm.so libdl.so => /system/lib64/libdl.so libc.so => /system/lib64/libc.so ld-android.so => /system/lib64/ld-android.so ld-android.so : the dynamic linker used by Android (on Linux is named ld-linux.so), responsible for loading and linking shared libraries into an executable at runtime. libdl.so : interface/library layer that provides function for dynamic loading, relies on the underlying functionality provided by the dynamic linker libm.so : math library for Android libc.so : standard C library for Android References: Doc for native (C/C++) API https://developer.android.com/ndk/guides/stable_apis C libraries (libc, libm, libdl): https://developer.android.com/ndk/guides/stable_apis#c_library Bionic changes with API levels: https://android.googlesource.com/platform/bionic/+/HEAD/docs/status.md NDK r27c build system: https://android.googlesource.com/platform/ndk/+/ndk-r27-release/docs/BuildSystemMaintainers.md CI: Update to NDK r27c (27.2.12479018), the default version in GitHub runner, to switch to a recent clang 18. A PGO build requires static linking, because the NDK doesn't ship the Android loaders (linker/linker64), see: https://groups.google.com/g/android-ndk/c/3Ep6zD3xxSY The API level should not be an issue when distributing a static build, use the API 29, the oldest one not affected by the LTS alignement issue. closes https://github.com/official-stockfish/Stockfish/pull/6081 No functional change --- .github/ci/arm_matrix.json | 12 ++++++------ .github/workflows/arm_compilation.yml | 2 +- .github/workflows/tests.yml | 6 +++--- src/Makefile | 14 ++++++-------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/.github/ci/arm_matrix.json b/.github/ci/arm_matrix.json index 70f2efaa21e..b53fe03af4c 100644 --- a/.github/ci/arm_matrix.json +++ b/.github/ci/arm_matrix.json @@ -4,7 +4,7 @@ "name": "Android NDK aarch64", "os": "ubuntu-22.04", "simple_name": "android", - "compiler": "aarch64-linux-android21-clang++", + "compiler": "aarch64-linux-android29-clang++", "emu": "qemu-aarch64", "comp": "ndk", "shell": "bash", @@ -14,7 +14,7 @@ "name": "Android NDK arm", "os": "ubuntu-22.04", "simple_name": "android", - "compiler": "armv7a-linux-androideabi21-clang++", + "compiler": "armv7a-linux-androideabi29-clang++", "emu": "qemu-arm", "comp": "ndk", "shell": "bash", @@ -26,25 +26,25 @@ { "binaries": "armv8-dotprod", "config": { - "compiler": "armv7a-linux-androideabi21-clang++" + "compiler": "armv7a-linux-androideabi29-clang++" } }, { "binaries": "armv8", "config": { - "compiler": "armv7a-linux-androideabi21-clang++" + "compiler": "armv7a-linux-androideabi29-clang++" } }, { "binaries": "armv7", "config": { - "compiler": "aarch64-linux-android21-clang++" + "compiler": "aarch64-linux-android29-clang++" } }, { "binaries": "armv7-neon", "config": { - "compiler": "aarch64-linux-android21-clang++" + "compiler": "aarch64-linux-android29-clang++" } } ] diff --git a/.github/workflows/arm_compilation.yml b/.github/workflows/arm_compilation.yml index 5bf2a93e552..781bd807024 100644 --- a/.github/workflows/arm_compilation.yml +++ b/.github/workflows/arm_compilation.yml @@ -38,7 +38,7 @@ jobs: if: runner.os == 'Linux' run: | if [ $COMP == ndk ]; then - NDKV="21.4.7075529" + NDKV="27.2.12479018" ANDROID_ROOT=/usr/local/lib/android ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6d35a183d1f..b269bd74243 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,13 +29,13 @@ jobs: shell: bash - name: Android NDK aarch64 os: ubuntu-22.04 - compiler: aarch64-linux-android21-clang++ + compiler: aarch64-linux-android29-clang++ comp: ndk run_armv8_tests: true shell: bash - name: Android NDK arm os: ubuntu-22.04 - compiler: armv7a-linux-androideabi21-clang++ + compiler: armv7a-linux-androideabi29-clang++ comp: ndk run_armv7_tests: true shell: bash @@ -126,7 +126,7 @@ jobs: if: runner.os == 'Linux' run: | if [ $COMP == ndk ]; then - NDKV="21.4.7075529" + NDKV="27.2.12479018" ANDROID_ROOT=/usr/local/lib/android ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager diff --git a/src/Makefile b/src/Makefile index 9ea3e01b725..87bf4d69787 100644 --- a/src/Makefile +++ b/src/Makefile @@ -533,14 +533,12 @@ ifeq ($(KERNEL),Darwin) XCRUN = xcrun endif -# To cross-compile for Android, NDK version r21 or later is recommended. -# In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. -# Currently we don't know how to make PGO builds with the NDK yet. +# To cross-compile for Android, use NDK version r27c or later. ifeq ($(COMP),ndk) - CXXFLAGS += -stdlib=libc++ -fPIE + CXXFLAGS += -stdlib=libc++ comp=clang ifeq ($(arch),armv7) - CXX=armv7a-linux-androideabi16-clang++ + CXX=armv7a-linux-androideabi29-clang++ CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon ifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),) STRIP=arm-linux-androideabi-strip @@ -549,7 +547,7 @@ ifeq ($(COMP),ndk) endif endif ifeq ($(arch),armv8) - CXX=aarch64-linux-android21-clang++ + CXX=aarch64-linux-android29-clang++ ifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),) STRIP=aarch64-linux-android-strip else @@ -557,14 +555,14 @@ ifeq ($(COMP),ndk) endif endif ifeq ($(arch),x86_64) - CXX=x86_64-linux-android21-clang++ + CXX=x86_64-linux-android29-clang++ ifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),) STRIP=x86_64-linux-android-strip else STRIP=llvm-strip endif endif - LDFLAGS += -static-libstdc++ -pie -lm -latomic + LDFLAGS += -static-libstdc++ endif ### Allow overwriting CXX from command line From 73c55e894959b2dcd40c179ade6e1eafc9d952ea Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 25 May 2025 18:33:04 +0300 Subject: [PATCH 1046/1309] Simplify Double Margin Formula Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 267296 W: 69214 L: 69248 D: 128834 Ptnml(0-2): 760, 31511, 69141, 31475, 761 https://tests.stockfishchess.org/tests/view/682f5d9a6ec7634154f9b01e Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67872 W: 17460 L: 17289 D: 33123 Ptnml(0-2): 25, 7238, 19243, 7401, 29 https://tests.stockfishchess.org/tests/view/6833074b6ec7634154f9b5ae Passed VLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 118000 W: 30337 L: 30222 D: 57441 Ptnml(0-2): 15, 11783, 35289, 11898, 15 https://tests.stockfishchess.org/tests/view/683336c56ec7634154f9ba46 closes https://github.com/official-stockfish/Stockfish/pull/6097 Bench: 2312696 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 10e2448299a..14f235b5ced 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1147,7 +1147,7 @@ Value Search::Worker::search( int corrValAdj2 = std::abs(correctionValue) / 249757; int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj1 - 997 * ttMoveHistory / 131072 - - (ss->ply * 2 > thisThread->rootDepth * 3) * 47; + - (ss->ply > thisThread->rootDepth) * 47; int tripleMargin = 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - corrValAdj2 - (ss->ply * 2 > thisThread->rootDepth * 3) * 54; From 9b79b75c9b728cf52b00793c4283d630a20e74b8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Apr 2025 19:17:52 +0300 Subject: [PATCH 1047/1309] Enforce minimum compiler versions gcc 9.3 clang 10 using unsupported compiler versions will generate an error, older version might miscompile SF CI: improves output on failed bench output closes https://github.com/official-stockfish/Stockfish/pull/6032 No functional change --- src/types.h | 6 ++++++ tests/signature.sh | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/types.h b/src/types.h index 6c797580718..5d76721533a 100644 --- a/src/types.h +++ b/src/types.h @@ -57,11 +57,17 @@ // _WIN32 Building on Windows (any) // _WIN64 Building on Windows 64 bit +// Enforce minimum GCC version #if defined(__GNUC__) && !defined(__clang__) \ && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ < 3)) #error "Stockfish requires GCC 9.3 or later for correct compilation" #endif +// Enforce minimum Clang version +#if defined(__clang__) && (__clang_major__ < 10) + #error "Stockfish requires Clang 10.0 or later for correct compilation" +#endif + #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used diff --git a/tests/signature.sh b/tests/signature.sh index 06bd1892e6c..0f6dd758561 100755 --- a/tests/signature.sh +++ b/tests/signature.sh @@ -2,16 +2,26 @@ # obtain and optionally verify Bench / signature # if no reference is given, the output is deliberately limited to just the signature +STDOUT_FILE=$(mktemp) +STDERR_FILE=$(mktemp) + error() { echo "running bench for signature failed on line $1" + echo "===== STDOUT =====" + cat "$STDOUT_FILE" + echo "===== STDERR =====" + cat "$STDERR_FILE" + rm -f "$STDOUT_FILE" "$STDERR_FILE" exit 1 } trap 'error ${LINENO}' ERR # obtain +eval "$WINE_PATH ./stockfish bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO} +signature=$(grep "Nodes searched : " "$STDERR_FILE" | awk '{print $4}') -signature=`eval "$WINE_PATH ./stockfish bench 2>&1" | grep "Nodes searched : " | awk '{print $4}'` +rm -f "$STDOUT_FILE" "$STDERR_FILE" if [ $# -gt 0 ]; then # compare to given reference @@ -28,4 +38,4 @@ if [ $# -gt 0 ]; then else # just report signature echo $signature -fi +fi \ No newline at end of file From dfa176fc7ee795b69fa72ea1322486a8d8b0647a Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 25 May 2025 12:58:01 -0700 Subject: [PATCH 1048/1309] Small tt verify simplification Also fix probcut comment Passed non-regression STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 69728 W: 18080 L: 17909 D: 33739 Ptnml(0-2): 161, 7157, 20044, 7354, 148 https://tests.stockfishchess.org/tests/view/68324b116ec7634154f9b478 closes https://github.com/official-stockfish/Stockfish/pull/6094 No functional change --- src/search.cpp | 14 +++----------- src/types.h | 8 ++++---- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 14f235b5ced..c6f61ec9034 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -701,18 +701,12 @@ Value Search::Worker::search( do_move(pos, ttData.move, st); Key nextPosKey = pos.key(); auto [ttHitNext, ttDataNext, ttWriterNext] = tt.probe(nextPosKey); - ttDataNext.value = - ttHitNext ? value_from_tt(ttDataNext.value, ss->ply + 1, pos.rule50_count()) - : VALUE_NONE; undo_move(pos, ttData.move); + // Check that the ttValue after the tt move would also trigger a cutoff if (!is_valid(ttDataNext.value)) return ttData.value; - - if (ttData.value >= beta && -ttDataNext.value >= beta) - return ttData.value; - - if (ttData.value <= alpha && -ttDataNext.value <= alpha) + if ((ttData.value >= beta) == (-ttDataNext.value >= beta)) return ttData.value; } else @@ -916,9 +910,7 @@ Value Search::Worker::search( if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt - // 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. + // probCut there && !(is_valid(ttData.value) && ttData.value < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); diff --git a/src/types.h b/src/types.h index 5d76721533a..d40e1e292c2 100644 --- a/src/types.h +++ b/src/types.h @@ -63,10 +63,10 @@ #error "Stockfish requires GCC 9.3 or later for correct compilation" #endif -// Enforce minimum Clang version -#if defined(__clang__) && (__clang_major__ < 10) - #error "Stockfish requires Clang 10.0 or later for correct compilation" -#endif + // Enforce minimum Clang version + #if defined(__clang__) && (__clang_major__ < 10) + #error "Stockfish requires Clang 10.0 or later for correct compilation" + #endif #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) From 9fd40b9ea887bbec989b0beee248d4ebf70705af Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 25 May 2025 10:34:52 -0700 Subject: [PATCH 1049/1309] Simplify tt depth in stat eval history adjustment Passed simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 102208 W: 26498 L: 26349 D: 49361 Ptnml(0-2): 284, 12095, 26166, 12306, 253 https://tests.stockfishchess.org/tests/view/683354c76ec7634154f9be88 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 133422 W: 34050 L: 33945 D: 65427 Ptnml(0-2): 56, 14473, 37559, 14556, 67 https://tests.stockfishchess.org/tests/view/683363626ec7634154f9c298 closes https://github.com/official-stockfish/Stockfish/pull/6099 Bench: 2652411 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c6f61ec9034..670d343e8bc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -807,8 +807,7 @@ Value Search::Worker::search( } // Use static evaluation difference to improve quiet move ordering - if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture - && (ttData.depth - 2) <= depth) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && ttData.depth <= 2) { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1858, 1492) + 661; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1057 / 1024; From dc85c5a4c983d3a957ca7c6affb709d966360412 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 25 May 2025 19:12:43 -0700 Subject: [PATCH 1050/1309] Remove nnz lookup table load optimization Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 63296 W: 16491 L: 16311 D: 30494 Ptnml(0-2): 129, 6624, 17972, 6784, 139 https://tests.stockfishchess.org/tests/view/6833ce486ec7634154f9cb22 Passed 2nd Non-regression STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 369568 W: 95314 L: 95451 D: 178803 Ptnml(0-2): 897, 40231, 102601, 40222, 833 https://tests.stockfishchess.org/tests/view/68355c956ec7634154f9ce07 closes https://github.com/official-stockfish/Stockfish/pull/6100 no functional change --- .../layers/affine_transform_sparse_input.h | 4 ---- src/nnue/simd.h | 18 +++--------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 7c74d5e6402..51f86fd6e4e 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -49,11 +49,7 @@ constexpr int constexpr_lsb(uint64_t bb) { alignas(CacheLineSize) static constexpr struct OffsetIndices { - #if (USE_SSE41) - std::uint8_t offset_indices[256][8]; - #else std::uint16_t offset_indices[256][8]; - #endif constexpr OffsetIndices() : offset_indices() { diff --git a/src/nnue/simd.h b/src/nnue/simd.h index 56022300002..f37eeb93059 100644 --- a/src/nnue/simd.h +++ b/src/nnue/simd.h @@ -74,11 +74,7 @@ using vec_uint_t = __m512i; #define vec128_zero _mm_setzero_si128() #define vec128_set_16(a) _mm_set1_epi16(a) - #if (USE_SSE41) - #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) - #else - #define vec128_load(a) _mm_load_si128(a) - #endif + #define vec128_load(a) _mm_load_si128(a) #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) #define NumRegistersSIMD 16 @@ -119,11 +115,7 @@ using vec_uint_t = __m256i; #define vec128_zero _mm_setzero_si128() #define vec128_set_16(a) _mm_set1_epi16(a) - #if (USE_SSE41) - #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) - #else - #define vec128_load(a) _mm_load_si128(a) - #endif + #define vec128_load(a) _mm_load_si128(a) #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) @@ -159,11 +151,7 @@ using vec_uint_t = __m128i; #define vec128_zero _mm_setzero_si128() #define vec128_set_16(a) _mm_set1_epi16(a) - #if (USE_SSE41) - #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) - #else - #define vec128_load(a) _mm_load_si128(a) - #endif + #define vec128_load(a) _mm_load_si128(a) #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) From d27298d7dc0a6222ce21b5ff3b9f16fe72c108af Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 26 May 2025 18:26:31 -0700 Subject: [PATCH 1051/1309] Remove unused threatenedPieces threatenedPieces is no longer used since #6023 Also can_move_king_or_pawn() can be const. Also remove a couple of redundant declarations. closes https://github.com/official-stockfish/Stockfish/pull/6101 No functional change --- src/movepick.cpp | 11 +++-------- src/movepick.h | 2 +- src/nnue/nnue_common.h | 1 - src/timeman.cpp | 2 -- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index faef753be2b..d2764fa8487 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -128,18 +128,13 @@ void MovePicker::score() { Color us = pos.side_to_move(); - [[maybe_unused]] Bitboard threatenedPieces, threatByLesser[QUEEN + 1]; + [[maybe_unused]] Bitboard threatByLesser[QUEEN + 1]; if constexpr (Type == QUIETS) { threatByLesser[KNIGHT] = threatByLesser[BISHOP] = pos.attacks_by(~us); threatByLesser[ROOK] = pos.attacks_by(~us) | pos.attacks_by(~us) | threatByLesser[KNIGHT]; threatByLesser[QUEEN] = pos.attacks_by(~us) | threatByLesser[ROOK]; - - // Pieces threatened by pieces of lesser material value - threatenedPieces = (pos.pieces(us, QUEEN) & threatByLesser[QUEEN]) - | (pos.pieces(us, ROOK) & threatByLesser[ROOK]) - | (pos.pieces(us, KNIGHT, BISHOP) & threatByLesser[KNIGHT]); } for (auto& m : *this) @@ -316,11 +311,11 @@ Move MovePicker::next_move() { void MovePicker::skip_quiet_moves() { skipQuiets = true; } // this function must be called after all quiet moves and captures have been generated -bool MovePicker::can_move_king_or_pawn() { +bool MovePicker::can_move_king_or_pawn() const { // SEE negative captures shouldn't be returned in GOOD_CAPTURE stage assert(stage > GOOD_CAPTURE && stage != EVASION_INIT); - for (ExtMove* m = moves; m < endCur; ++m) + for (const ExtMove* m = moves; m < endCur; ++m) { PieceType movedPieceType = type_of(pos.moved_piece(*m)); if ((movedPieceType == PAWN || movedPieceType == KING) && pos.legal(*m)) diff --git a/src/movepick.h b/src/movepick.h index 2634fdd780f..b6784fb7828 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -50,7 +50,7 @@ class MovePicker { MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(); void skip_quiet_moves(); - bool can_move_king_or_pawn(); + bool can_move_king_or_pawn() const; private: template diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 35cc8a5fead..550bcaad41b 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -81,7 +81,6 @@ constexpr std::size_t MaxSimdWidth = 32; // Type of input feature after conversion using TransformedFeatureType = std::uint8_t; -using IndexType = std::uint32_t; // Round n up to be a multiple of base template diff --git a/src/timeman.cpp b/src/timeman.cpp index f0894a262ce..29ebffcaa29 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -28,8 +28,6 @@ namespace Stockfish { -enum Color : int8_t; - TimePoint TimeManagement::optimum() const { return optimumTime; } TimePoint TimeManagement::maximum() const { return maximumTime; } From 29b0c07ac88d32cb879248eccd31cb6a2cac93c5 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 27 May 2025 18:27:48 -0700 Subject: [PATCH 1052/1309] Simplify Position::pieces() closes https://github.com/official-stockfish/Stockfish/pull/6104 No functional change --- src/position.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/position.h b/src/position.h index 724165b003a..cf6b1c472cb 100644 --- a/src/position.h +++ b/src/position.h @@ -86,9 +86,9 @@ class Position { std::string fen() const; // Position representation - Bitboard pieces(PieceType pt = ALL_PIECES) const; + Bitboard pieces() const; // All pieces template - Bitboard pieces(PieceType pt, PieceTypes... pts) const; + Bitboard pieces(PieceTypes... pts) const; Bitboard pieces(Color c) const; template Bitboard pieces(Color c, PieceTypes... pts) const; @@ -214,11 +214,11 @@ inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } -inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; } +inline Bitboard Position::pieces() const { return byTypeBB[ALL_PIECES]; } template -inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const { - return pieces(pt) | pieces(pts...); +inline Bitboard Position::pieces(PieceTypes... pts) const { + return (byTypeBB[pts] | ...); } inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; } From d0212906bde4bea7f39357c1729f2492c6531ed3 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 26 May 2025 00:28:18 -0700 Subject: [PATCH 1053/1309] Simplify stat eval history adjustment further closes https://github.com/official-stockfish/Stockfish/pull/6106 bench 2074807 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 670d343e8bc..a58ac3e873e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -807,7 +807,7 @@ Value Search::Worker::search( } // Use static evaluation difference to improve quiet move ordering - if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && ttData.depth <= 2) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && !ttHit) { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1858, 1492) + 661; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1057 / 1024; From 9debc540e5455e25ca5091045d81a033429bbaaa Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Fri, 30 May 2025 08:48:03 +0200 Subject: [PATCH 1054/1309] Fix clang-format version in CONTRIBUTING.md closes https://github.com/official-stockfish/Stockfish/pull/6107 No functional change --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0b6fbce0d07..07fe9f542fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ discussion._ Changes to Stockfish C++ code should respect our coding style defined by [.clang-format](.clang-format). You can format your changes by running -`make format`. This requires clang-format version 18 to be installed on your system. +`make format`. This requires clang-format version 20 to be installed on your system. ## Navigate From 5695486db99de3883188331eb1c7565e68d092cf Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Fri, 30 May 2025 13:36:38 -0700 Subject: [PATCH 1055/1309] Fix outdated comment closes https://github.com/official-stockfish/Stockfish/pull/6108 No functional change --- src/search.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a58ac3e873e..93fe7e58873 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -778,10 +778,7 @@ Value Search::Worker::search( goto moves_loop; } else if (excludedMove) - { - // Providing the hint that this node's accumulator will be used often unadjustedStaticEval = eval = ss->staticEval; - } else if (ss->ttHit) { // Never assume anything about values stored in TT From 8da3c2155a86a78d24aaf454264cf1bc93cb2d3a Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sat, 31 May 2025 15:43:19 +0200 Subject: [PATCH 1056/1309] Simplify NMP eval in qsearch Passed non-regression STC: https://tests.stockfishchess.org/tests/view/6834e9436ec7634154f9cd6e LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 24864 W: 6626 L: 6394 D: 11844 Ptnml(0-2): 62, 2806, 6477, 3012, 75 Passed non-regression LTC: https://tests.stockfishchess.org/tests/view/683598fd6ec7634154f9ce82 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 200148 W: 51461 L: 51424 D: 97263 Ptnml(0-2): 92, 21672, 56503, 21721, 86 closes https://github.com/official-stockfish/Stockfish/pull/6109 Bench: 2316591 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 93fe7e58873..197da7a4fe2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1589,9 +1589,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } else { - // In case of null move search, use previous static eval with opposite sign - unadjustedStaticEval = - (ss - 1)->currentMove != Move::null() ? evaluate(pos) : -(ss - 1)->staticEval; + unadjustedStaticEval = evaluate(pos); + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); } From ddefd6eb6b6213eabbfbbcf4d1535cb30f620826 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 26 May 2025 21:29:43 -0700 Subject: [PATCH 1057/1309] Simplify away check term in statscore Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 61696 W: 16031 L: 15841 D: 29824 Ptnml(0-2): 151, 7160, 16046, 7330, 161 https://tests.stockfishchess.org/tests/view/68353fcc6ec7634154f9cdd5 Passed simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 237990 W: 60994 L: 60995 D: 116001 Ptnml(0-2): 95, 25964, 66903, 25913, 120 https://tests.stockfishchess.org/tests/view/683642256ec7634154f9cf5e closes https://github.com/official-stockfish/Stockfish/pull/6110 Bench: 2521003 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 197da7a4fe2..8b8c3d6d0a1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1223,7 +1223,7 @@ Value Search::Worker::search( else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] + 1000 * ss->inCheck - 3206; + + (*contHist[1])[movedPiece][move.to_sq()] - 3206; // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 826 / 8192; From 70ff5e31635384504f6f5cce9c0f089d60103922 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 26 May 2025 23:47:35 -0700 Subject: [PATCH 1058/1309] Simplify away cutoff term in prior countermove bonus Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 61120 W: 16010 L: 15819 D: 29291 Ptnml(0-2): 150, 7105, 15869, 7276, 160 https://tests.stockfishchess.org/tests/view/683560226ec7634154f9ce0f Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 225090 W: 57555 L: 57543 D: 109992 Ptnml(0-2): 104, 24367, 63603, 24355, 116 https://tests.stockfishchess.org/tests/view/6836420c6ec7634154f9cf5c closes https://github.com/official-stockfish/Stockfish/pull/6111 Bench: 2472910 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8b8c3d6d0a1..f7390bc2cfd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1431,11 +1431,10 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -302; + int bonusScale = -220; bonusScale += std::min(-(ss - 1)->statScore / 103, 323); bonusScale += std::min(73 * depth, 531); bonusScale += 174 * ((ss - 1)->moveCount > 8); - bonusScale += 90 * (ss->cutoffCnt <= 3); bonusScale += 144 * (!ss->inCheck && bestValue <= ss->staticEval - 104); bonusScale += 128 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82); From 3747a1993722bfb9d5e7c784b2225e5aa742276f Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 25 May 2025 12:32:32 -0700 Subject: [PATCH 1059/1309] Simplify away depth condition in IIR Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 359520 W: 92714 L: 92849 D: 173957 Ptnml(0-2): 977, 42640, 92614, 42599, 930 https://tests.stockfishchess.org/tests/view/6833705d6ec7634154f9c302 Passed simplification LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 201756 W: 51544 L: 51507 D: 98705 Ptnml(0-2): 89, 21965, 56728, 22012, 84 https://tests.stockfishchess.org/tests/view/68338e386ec7634154f9c790 Passed simplification VLTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 48558 W: 12675 L: 12492 D: 23391 Ptnml(0-2): 9, 4779, 14516, 4970, 5 https://tests.stockfishchess.org/tests/view/6838e0b26ec7634154f9d25b closes https://github.com/official-stockfish/Stockfish/pull/6112 Bench: 2302583 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f7390bc2cfd..b686808f3f5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -896,7 +896,7 @@ 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 less aggressive. - if (!allNode && depth >= (PvNode ? 5 : 7) && !ttData.move) + if (!allNode && depth >= 6 && !ttData.move) depth--; // Step 11. ProbCut From c9af7674bc120b4f75153e00c40315ff0f54b86c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 30 May 2025 00:03:23 -0700 Subject: [PATCH 1060/1309] Introduce Secondary TT Aging When a high-depth TT entry fail to produce a cutoff, decrease the stored depth by 1. This is intended to help cases such as #5023 (https://github.com/official-stockfish/Stockfish/issues/5023#issuecomment-2814209391), where entries with extremely high depths prevent TT cutoffs, contributing to search explosions. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 128800 W: 33502 L: 33053 D: 62245 Ptnml(0-2): 331, 15084, 33149, 15477, 359 https://tests.stockfishchess.org/tests/view/683958e56ec7634154f9d2a9 Passed LTC: LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 63288 W: 16376 L: 16005 D: 30907 Ptnml(0-2): 26, 6712, 17798, 7081, 27 https://tests.stockfishchess.org/tests/view/683aa4026ec7634154f9d469 closes https://github.com/official-stockfish/Stockfish/pull/6113 Bench: 2144705 --- src/tt.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tt.cpp b/src/tt.cpp index d7f7dbdef6b..953348987e0 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -110,6 +110,8 @@ void TTEntry::save( value16 = int16_t(v); eval16 = int16_t(ev); } + else if (depth8 + DEPTH_ENTRY_OFFSET >= 5 && Bound(genBound8 & 0x3) != BOUND_EXACT) + depth8--; } From 259bdaaa9faf20cdc1c56faf97504f7dd2f62032 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 31 May 2025 15:15:29 -0700 Subject: [PATCH 1061/1309] Remove an unnecessary bound check When failing high, it is always true that `alpha < beta` and `beta <= bestValue`. Therefore if alpha and bestValue is not in decisive range, it is guaranteed that beta is not. closes https://github.com/official-stockfish/Stockfish/pull/6115 no functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b686808f3f5..e3cb1cdb87f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1412,7 +1412,7 @@ Value Search::Worker::search( assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); // Adjust best value for fail high cases - if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) && !is_decisive(alpha)) + if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(alpha)) bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) From 254b6d5e85d50b057f47a99b2515606a2626813c Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 31 May 2025 11:04:42 -0700 Subject: [PATCH 1062/1309] Simplify corrections in extension margins Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 96192 W: 25002 L: 24852 D: 46338 Ptnml(0-2): 242, 10868, 25716, 11038, 232 https://tests.stockfishchess.org/tests/view/683b44cb6ec7634154f9d6ac Passed simplification LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 83334 W: 21473 L: 21317 D: 40544 Ptnml(0-2): 37, 8877, 23674, 9051, 28 https://tests.stockfishchess.org/tests/view/683b79786ec7634154f9d75a closes https://github.com/official-stockfish/Stockfish/pull/6117 Bench: 2294814 --- src/search.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e3cb1cdb87f..25c173f2319 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1131,13 +1131,12 @@ Value Search::Worker::search( if (value < singularBeta) { - int corrValAdj1 = std::abs(correctionValue) / 248400; - int corrValAdj2 = std::abs(correctionValue) / 249757; - int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj1 + int corrValAdj = std::abs(correctionValue) / 248400; + int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj - 997 * ttMoveHistory / 131072 - (ss->ply > thisThread->rootDepth) * 47; - int tripleMargin = 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - - corrValAdj2 - (ss->ply * 2 > thisThread->rootDepth * 3) * 54; + int tripleMargin = 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - corrValAdj + - (ss->ply * 2 > thisThread->rootDepth * 3) * 54; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); From 9ac756695e215e6c343d294fb8ade3eec2a41127 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 1 Jun 2025 13:27:52 -0700 Subject: [PATCH 1063/1309] reduce depth by 5 in probcut Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 63328 W: 16402 L: 16213 D: 30713 Ptnml(0-2): 174, 7378, 16340, 7629, 143 https://tests.stockfishchess.org/tests/view/6833530e6ec7634154f9be7f Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 69936 W: 17795 L: 17625 D: 34516 Ptnml(0-2): 29, 7631, 19474, 7809, 25 https://tests.stockfishchess.org/tests/view/68335e386ec7634154f9c266 closes https://github.com/official-stockfish/Stockfish/pull/6120 Bench: 2307268 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 25c173f2319..ddc27156e44 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -912,7 +912,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); - Depth probCutDepth = std::max(depth - (4 + cutNode), 0); + Depth probCutDepth = std::max(depth - 5, 0); while ((move = mp.next_move()) != Move::none()) { From 5337edfdb6c9593e224be58225907682903db1a9 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 31 May 2025 18:24:19 -0700 Subject: [PATCH 1064/1309] remove non-functional else since we break out of the loop in the other branch closes https://github.com/official-stockfish/Stockfish/pull/6116 no functional change --- src/search.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ddc27156e44..9f44defbe8d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1380,15 +1380,13 @@ Value Search::Worker::search( assert(value >= beta); // Fail high break; } - else - { - // Reduce other moves if we have found at least one score improvement - if (depth > 2 && depth < 16 && !is_decisive(value)) - depth -= 2; - assert(depth > 0); - alpha = value; // Update alpha! Always alpha < beta - } + // Reduce other moves if we have found at least one score improvement + if (depth > 2 && depth < 16 && !is_decisive(value)) + depth -= 2; + + assert(depth > 0); + alpha = value; // Update alpha! Always alpha < beta } } From 15555e8f4ab0ee0a020ab8bd9e9ae56214ca00c1 Mon Sep 17 00:00:00 2001 From: disservin Date: Sun, 29 Jun 2025 12:33:20 +0200 Subject: [PATCH 1065/1309] Disable linux gcc riscv64 (#6145) Temporarily disable it, until we figure out the toolchain issues which are causing the crashes. closes https://github.com/official-stockfish/Stockfish/pull/6145 No functional change --- .github/workflows/tests.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b269bd74243..95ca1209257 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,14 +39,15 @@ jobs: comp: ndk run_armv7_tests: true shell: bash - - name: Linux GCC riscv64 - os: ubuntu-22.04 - compiler: g++ - comp: gcc - run_riscv64_tests: true - base_image: "riscv64/alpine:edge" - platform: linux/riscv64 - shell: bash + # Currently segfaults in the CI unrelated to a Stockfish change. + # - name: Linux GCC riscv64 + # os: ubuntu-22.04 + # compiler: g++ + # comp: gcc + # run_riscv64_tests: true + # base_image: "riscv64/alpine:edge" + # platform: linux/riscv64 + # shell: bash - name: Linux GCC ppc64 os: ubuntu-22.04 compiler: g++ From 34b75f1575698759ab180da369be5e65364a9d1e Mon Sep 17 00:00:00 2001 From: pb00067 Date: Tue, 3 Jun 2025 13:52:17 +0200 Subject: [PATCH 1066/1309] Restore integrity of MovePicker::can_move_king_or_pawn PR6005 broken by PR6071 passed STC non regression https://tests.stockfishchess.org/tests/view/6839791f6ec7634154f9d312 LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 31776 W: 8353 L: 8130 D: 15293 Ptnml(0-2): 74, 3566, 8382, 3795, 71 passed LTC non-regression https://tests.stockfishchess.org/tests/view/6839c87a6ec7634154f9d367 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 120756 W: 31015 L: 30899 D: 58842 Ptnml(0-2): 50, 12732, 34703, 12838, 55 closes https://github.com/official-stockfish/Stockfish/pull/6119 Bench: 1945300 --- src/movepick.cpp | 26 +++++++++++--------------- src/movepick.h | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index d2764fa8487..3e7c10160cb 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -56,6 +56,7 @@ enum Stages { QCAPTURE }; + // Sort moves in descending order up to and including a given limit. // The order of moves smaller than the limit is left unspecified. void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { @@ -207,6 +208,7 @@ Move MovePicker::select(Pred filter) { // picking the move with the highest score from a list of generated moves. Move MovePicker::next_move() { + constexpr int goodQuietThreshold = -14000; top: switch (stage) { @@ -222,7 +224,7 @@ Move MovePicker::next_move() { case PROBCUT_INIT : case QCAPTURE_INIT : cur = endBadCaptures = moves; - endCur = generate(pos, cur); + endCur = endCaptures = generate(pos, cur); score(); partial_insertion_sort(cur, endCur, std::numeric_limits::min()); @@ -244,8 +246,7 @@ Move MovePicker::next_move() { case QUIET_INIT : if (!skipQuiets) { - cur = endBadQuiets = endBadCaptures; - endCur = generate(pos, cur); + endCur = endGenerated = generate(pos, cur); score(); partial_insertion_sort(cur, endCur, -3560 * depth); @@ -255,12 +256,7 @@ Move MovePicker::next_move() { [[fallthrough]]; case GOOD_QUIET : - if (!skipQuiets && select([&]() { - if (cur->value > -14000) - return true; - *endBadQuiets++ = *cur; - return false; - })) + if (!skipQuiets && select([&]() { return cur->value > goodQuietThreshold; })) return *(cur - 1); // Prepare the pointers to loop over the bad captures @@ -274,22 +270,22 @@ Move MovePicker::next_move() { if (select([]() { return true; })) return *(cur - 1); - // Prepare the pointers to loop over the bad quiets - cur = endBadCaptures; - endCur = endBadQuiets; + // Prepare the pointers to loop over quiets again + cur = endCaptures; + endCur = endGenerated; ++stage; [[fallthrough]]; case BAD_QUIET : if (!skipQuiets) - return select([]() { return true; }); + return select([&]() { return cur->value <= goodQuietThreshold; }); return Move::none(); case EVASION_INIT : cur = moves; - endCur = generate(pos, cur); + endCur = endGenerated = generate(pos, cur); score(); partial_insertion_sort(cur, endCur, std::numeric_limits::min()); @@ -315,7 +311,7 @@ bool MovePicker::can_move_king_or_pawn() const { // SEE negative captures shouldn't be returned in GOOD_CAPTURE stage assert(stage > GOOD_CAPTURE && stage != EVASION_INIT); - for (const ExtMove* m = moves; m < endCur; ++m) + for (const ExtMove* m = moves; m < endGenerated; ++m) { PieceType movedPieceType = type_of(pos.moved_piece(*m)); if ((movedPieceType == PAWN || movedPieceType == KING) && pos.legal(*m)) diff --git a/src/movepick.h b/src/movepick.h index b6784fb7828..bf0c96c7c2b 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -67,7 +67,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove * cur, *endCur, *endBadCaptures, *endBadQuiets; + ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated; int stage; int threshold; Depth depth; From a7a56c41f6429d5ab2b210f2ffca85ed76c4ead2 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 3 Jun 2025 11:50:26 -0700 Subject: [PATCH 1067/1309] Simplify history term in futility pruning Passed simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 298816 W: 76814 L: 76881 D: 145121 Ptnml(0-2): 726, 35477, 77057, 35434, 714 https://tests.stockfishchess.org/tests/view/683f440f6ec7634154f9dc7f Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 237774 W: 60801 L: 60802 D: 116171 Ptnml(0-2): 91, 26088, 66532, 26083, 93 https://tests.stockfishchess.org/tests/view/68441189ffbc71bd236778de closes https://github.com/official-stockfish/Stockfish/pull/6130 Bench: 2411502 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9f44defbe8d..820bc8cddd1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1083,7 +1083,7 @@ Value Search::Worker::search( lmrDepth += history / 3388; - Value baseFutility = (bestMove ? 46 : 138 + std::abs(history / 300)); + Value baseFutility = (bestMove ? 46 : 230); Value futilityValue = ss->staticEval + baseFutility + 117 * lmrDepth + 102 * (ss->staticEval > alpha); From 318c948c4d3dfdd0ff9537ea328ac2e1de72c870 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 27 Jun 2025 21:30:47 -0700 Subject: [PATCH 1068/1309] Remove non-functional low-ply history fill lowPlyHistory is always cleared at the start of `iterative_deepening`, so clearing it here is non-functional. closes https://github.com/official-stockfish/Stockfish/pull/6144 No functional change --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 820bc8cddd1..a82b4edf0b3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -536,7 +536,6 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } // Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(67); - lowPlyHistory.fill(107); captureHistory.fill(-688); pawnHistory.fill(-1287); pawnCorrectionHistory.fill(5); From 3a0fff96cf4d7ba8f50f0f86f4d00e254147bfd6 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 13 Jun 2025 09:52:26 -0700 Subject: [PATCH 1069/1309] Simplify quiet move streak logic Passed non-regression STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 148960 W: 38409 L: 38312 D: 72239 Ptnml(0-2): 372, 17664, 38318, 17747, 379 https://tests.stockfishchess.org/tests/view/684c5773703522d4f129c5f7 Passed non-regression LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 180720 W: 46188 L: 46130 D: 88402 Ptnml(0-2): 84, 19608, 50929, 19644, 95 https://tests.stockfishchess.org/tests/view/68505fa5703522d4f129cbab closes https://github.com/official-stockfish/Stockfish/pull/6143 Bench: 2055894 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a82b4edf0b3..cdbc9531877 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1206,8 +1206,7 @@ Value Search::Worker::search( if ((ss + 1)->cutoffCnt > 2) r += 1036 + allNode * 848; - if (!capture && !givesCheck && ss->quietMoveStreak >= 2) - r += (ss->quietMoveStreak - 1) * 50; + r += (ss + 1)->quietMoveStreak * 50; // For first picked move (ttMove) reduce reduction if (move == ttData.move) From 84e2f3851d5465a5dfd08a65bde72d555832a2a1 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Thu, 26 Jun 2025 17:19:09 +0200 Subject: [PATCH 1070/1309] Introduce a constant for ValueList size in search() Having the size of these lists in two separate places likely contributed to the crashes seen during the recent tuning attempt https://tests.stockfishchess.org/tests/view/685c413343ce022d15794536. Thanks to @MinetaS for spotting this. closes https://github.com/official-stockfish/Stockfish/pull/6142 No functional change --- src/search.cpp | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cdbc9531877..4d0e64b320e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -63,6 +63,9 @@ using namespace Search; namespace { +constexpr int SEARCHEDLIST_CAPACITY = 32; +using SearchedList = ValueList; + // (*Scalers): // The values with Scaler asterisks have proven non-linear scaling. // They are optimized to time controls of 180 + 1.8 and longer, @@ -119,16 +122,16 @@ void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); -void update_all_stats(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move bestMove, - Square prevSq, - ValueList& quietsSearched, - ValueList& capturesSearched, - Depth depth, - Move TTMove, - int moveCount); +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + SearchedList& quietsSearched, + SearchedList& capturesSearched, + Depth depth, + Move TTMove, + int moveCount); } // namespace @@ -605,8 +608,8 @@ Value Search::Worker::search( int priorReduction; Piece movedPiece; - ValueList capturesSearched; - ValueList quietsSearched; + SearchedList capturesSearched; + SearchedList quietsSearched; // Step 1. Initialize node Worker* thisThread = this; @@ -1390,7 +1393,7 @@ Value Search::Worker::search( // If the move is worse than some previously searched move, // remember it, to update its stats later. - if (move != bestMove && moveCount <= 32) + if (move != bestMove && moveCount <= SEARCHEDLIST_CAPACITY) { if (capture) capturesSearched.push_back(move); @@ -1833,16 +1836,16 @@ void update_pv(Move* pv, Move move, const Move* childPv) { // Updates stats at the end of search() when a bestMove is found -void update_all_stats(const Position& pos, - Stack* ss, - Search::Worker& workerThread, - Move bestMove, - Square prevSq, - ValueList& quietsSearched, - ValueList& capturesSearched, - Depth depth, - Move ttMove, - int moveCount) { +void update_all_stats(const Position& pos, + Stack* ss, + Search::Worker& workerThread, + Move bestMove, + Square prevSq, + SearchedList& quietsSearched, + SearchedList& capturesSearched, + Depth depth, + Move ttMove, + int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece movedPiece = pos.moved_piece(bestMove); From ea85a54fef82aaa760460c1dea8826bba1ed9597 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 25 Jun 2025 21:33:47 +0900 Subject: [PATCH 1071/1309] Fix trivial errors in Makefile 1. Remove "default" rule as "default" has no special meaning as a rule name. Make runs the very first rule whose name doesn't begin with a dot (which is "help" currently). 2. Make "format" rule not update dependencies. closes https://github.com/official-stockfish/Stockfish/pull/6140 No functional change --- src/Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Makefile b/src/Makefile index 87bf4d69787..50bb2082ac7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -996,10 +996,6 @@ net: format: $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file -# default target -default: - help - ### ========================================================================== ### Section 5. Private Targets ### ========================================================================== @@ -1125,6 +1121,6 @@ icx-profile-use: .depend: $(SRCS) -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null -ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity)) +ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean format config-sanity)) -include .depend endif From ce7254b5ea3b9b7accb37c8e07fb64eeb5fcfcfa Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 24 Jun 2025 15:09:51 -0700 Subject: [PATCH 1072/1309] Optimize find_nnz() using AVX512 About a 1% speedup for ARCH x86-64-avx512 and x86-64-vnni512. Note: This could be optimized further if we wanted to add an ARCH supporting VBMI2 which is even more modern than VNNI. https://en.wikichip.org/wiki/x86/avx512_vbmi2 closes https://github.com/official-stockfish/Stockfish/pull/6139 No functional change --- src/Makefile | 2 +- .../layers/affine_transform_sparse_input.h | 36 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 50bb2082ac7..14c3c50c91e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -701,7 +701,7 @@ endif ifeq ($(avx512),yes) CXXFLAGS += -DUSE_AVX512 ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) - CXXFLAGS += -mavx512f -mavx512bw + CXXFLAGS += -mavx512f -mavx512bw -mavx512dq -mavx512vl endif endif diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 51f86fd6e4e..e77c98f8c66 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -68,9 +68,42 @@ alignas(CacheLineSize) static constexpr struct OffsetIndices { } Lookup; + #if defined(__GNUC__) || defined(__clang__) + #define RESTRICT __restrict__ + #elif defined(_MSC_VER) + #define RESTRICT __restrict + #else + #define RESTRICT + #endif + // Find indices of nonzero numbers in an int32_t array template -void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { +void find_nnz(const std::int32_t* RESTRICT input, + std::uint16_t* RESTRICT out, + IndexType& count_out) { + + #ifdef USE_AVX512 + constexpr IndexType SimdWidth = 16; // 512 bits / 32 bits + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m512i increment = _mm512_set1_epi32(SimdWidth); + __m512i base = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + IndexType count = 0; + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m512i inputV = _mm512_load_si512(input + i * SimdWidth); + + // Get a bitmask and gather non zero indices + const __mmask16 nnzMask = _mm512_test_epi32_mask(inputV, inputV); + const __m512i nnzV = _mm512_maskz_compress_epi32(nnzMask, base); + _mm512_mask_cvtepi32_storeu_epi16(out + count, 0xFFFF, nnzV); + count += popcount(nnzMask); + base = _mm512_add_epi32(base, increment); + } + count_out = count; + + #else + using namespace SIMD; constexpr IndexType InputSimdWidth = sizeof(vec_uint_t) / sizeof(std::int32_t); @@ -104,6 +137,7 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou } } count_out = count; + #endif } #endif From e695b9537ec1d7b19306deefe24d889db563dbb0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 14 Jun 2025 16:15:15 +0300 Subject: [PATCH 1073/1309] Remove eval & beta diff from NM reduction Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 43456 W: 11178 L: 10966 D: 21312 Ptnml(0-2): 114, 5078, 11114, 5326, 96 https://tests.stockfishchess.org/tests/view/6849ae13e84567164b5c9de9 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 63090 W: 16302 L: 16125 D: 30663 Ptnml(0-2): 37, 6837, 17603, 7048, 20 https://tests.stockfishchess.org/tests/view/684ab516e84567164b5ca02f closes https://github.com/official-stockfish/Stockfish/pull/6134 Bench: 2249459 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4d0e64b320e..39407045881 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -859,8 +859,8 @@ Value Search::Worker::search( { assert(eval - beta >= 0); - // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 213, 6) + depth / 3 + 5; + // Null move dynamic reduction based on depth + Depth R = 7 + depth / 3; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; From ce73441f2013e0b8fd3eb7a0c9fd391d52adde70 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 14 Jun 2025 16:10:59 +0300 Subject: [PATCH 1074/1309] Simplify sudden death time optimization Passed Sudden Death STC: https://tests.stockfishchess.org/tests/view/68455fe5375c2b77d9855351 LLR: 2.91 (-2.94,2.94) <-1.75,0.25> Total: 49248 W: 13008 L: 12798 D: 23442 Ptnml(0-2): 309, 5491, 12821, 5687, 316 Passed Sudden Death LTC: https://tests.stockfishchess.org/tests/view/6845a392375c2b77d98553cf LLR: 3.01 (-2.94,2.94) <-1.75,0.25> Total: 551070 W: 141699 L: 142031 D: 267340 Ptnml(0-2): 1923, 60608, 150916, 60054, 2034 Passed Standard STC: https://tests.stockfishchess.org/tests/view/683c5ebb6ec7634154f9d989 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 142624 W: 36808 L: 36709 D: 69107 Ptnml(0-2): 302, 15448, 39745, 15483, 334 Passed Standard LTC: https://tests.stockfishchess.org/tests/view/683f1a4f6ec7634154f9dc5a LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 146922 W: 37381 L: 37296 D: 72245 Ptnml(0-2): 69, 13552, 46117, 13671, 52 closes https://github.com/official-stockfish/Stockfish/pull/6132 Bench: 2249459 --- src/timeman.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/timeman.cpp b/src/timeman.cpp index 29ebffcaa29..5840e255600 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -85,16 +85,13 @@ void TimeManagement::init(Search::LimitsType& limits, // with constants are involved. const int64_t scaleFactor = useNodesTime ? npmsec : 1; const TimePoint scaledTime = limits.time[us] / scaleFactor; - const TimePoint scaledInc = limits.inc[us] / scaleFactor; // Maximum move horizon int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; // If less than one second, gradually reduce mtg - if (scaledTime < 1000 && double(centiMTG) / scaledInc > 5.051) - { + if (scaledTime < 1000) centiMTG = scaledTime * 5.051; - } // Make sure timeLeft is > 0 since we may use it as a divisor TimePoint timeLeft = From 62f08568cd724f10f86781bc58dabce616546478 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 15 Jun 2025 12:49:44 -0700 Subject: [PATCH 1075/1309] Simplify PV term in lmr Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 204000 W: 52541 L: 52506 D: 98953 Ptnml(0-2): 561, 24133, 52589, 24144, 573 https://tests.stockfishchess.org/tests/view/684f24ce703522d4f129cab5 Passed simplification LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 156150 W: 39890 L: 39807 D: 76453 Ptnml(0-2): 82, 16882, 44043, 17007, 61 https://tests.stockfishchess.org/tests/view/6855d8bf1d0d9fc6587538f8 closes https://github.com/official-stockfish/Stockfish/pull/6148 bench 2766493 --- src/search.cpp | 3 +-- src/search.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 39407045881..6430c3e328f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -655,7 +655,6 @@ Value Search::Worker::search( priorReduction = (ss - 1)->reduction; (ss - 1)->reduction = 0; ss->statScore = 0; - ss->isPvNode = PvNode; (ss + 2)->cutoffCnt = 0; // Step 4. Transposition table lookup @@ -1238,7 +1237,7 @@ Value Search::Worker::search( // std::clamp has been replaced by a more robust implementation. Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))) - + (ss - 1)->isPvNode; + + PvNode; ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); diff --git a/src/search.h b/src/search.h index 5caff10e80f..e0b57e30be3 100644 --- a/src/search.h +++ b/src/search.h @@ -75,7 +75,6 @@ struct Stack { bool ttHit; int cutoffCnt; int reduction; - bool isPvNode; int quietMoveStreak; }; From 6a09a24cd89e81662d55131b2d91396d4d1fe72a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 2 Jul 2025 22:50:51 +0300 Subject: [PATCH 1076/1309] Remove depth condition from ttCapture reduction Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 25920 W: 6822 L: 6593 D: 12505 Ptnml(0-2): 54, 2932, 6783, 3113, 78 https://tests.stockfishchess.org/tests/view/6853f9c2038630d25f468672 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 177318 W: 45339 L: 45278 D: 86701 Ptnml(0-2): 85, 19333, 49765, 19388, 88 https://tests.stockfishchess.org/tests/view/6854468a038630d25f4686c0 closes https://github.com/official-stockfish/Stockfish/pull/6149 bench: 2437275 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 6430c3e328f..8decb5d1eef 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1202,7 +1202,7 @@ Value Search::Worker::search( // Increase reduction if ttMove is a capture if (ttCapture) - r += 1210 + (depth < 8) * 963; + r += 1350; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) From a43f12ef08a14c5ab5303d223e7ae4e77754e982 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 14 Jun 2025 17:52:59 -0700 Subject: [PATCH 1077/1309] Simplify away constants in statscore Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 317280 W: 81589 L: 81678 D: 154013 Ptnml(0-2): 799, 37651, 81847, 37526, 817 https://tests.stockfishchess.org/tests/view/684e197a703522d4f129c9f0 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 174816 W: 44656 L: 44593 D: 85567 Ptnml(0-2): 83, 19064, 49058, 19113, 90 https://tests.stockfishchess.org/tests/view/6858aa54a596a06817bb924f closes https://github.com/official-stockfish/Stockfish/pull/6150 bench: 2508140 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8decb5d1eef..cfd3583bc9c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1192,7 +1192,7 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling - r += 316; // Base reduction offset to compensate for other tweaks + r += 650; // Base reduction offset to compensate for other tweaks r -= moveCount * 66; r -= std::abs(correctionValue) / 28047; @@ -1217,12 +1217,11 @@ Value Search::Worker::search( if (capture) ss->statScore = 826 * int(PieceValue[pos.captured_piece()]) / 128 - + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 5030; + + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3206; + + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 826 / 8192; From c13f883dc43b35ccc39738b1611815fe6ae12948 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 14 Jun 2025 17:42:07 -0700 Subject: [PATCH 1078/1309] simplify away TT history term Passed simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 163168 W: 42136 L: 42055 D: 78977 Ptnml(0-2): 410, 19224, 42212, 19351, 387 https://tests.stockfishchess.org/tests/view/684e16ee703522d4f129c9e9 Passed simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 303144 W: 77580 L: 77647 D: 147917 Ptnml(0-2): 150, 33110, 85134, 33013, 165 https://tests.stockfishchess.org/tests/view/6852ff97703522d4f129d5f7 closes https://github.com/official-stockfish/Stockfish/pull/6152 bench: 2294774 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cfd3583bc9c..1b5a2bbbc13 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1271,11 +1271,9 @@ Value Search::Worker::search( if (!ttData.move) r += 1128; - r -= ttMoveHistory / 8; - // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3564) - (r > 4969 && newDepth > 2), !cutNode); + newDepth - (r > 3200) - (r > 4600 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, From 8c2d21f91a5840a67c36267e5043070ffad06860 Mon Sep 17 00:00:00 2001 From: 87 Date: Sun, 6 Jul 2025 18:18:35 +0100 Subject: [PATCH 1079/1309] Speedup movegen with VBMI2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passed STC LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 166720 W: 43191 L: 42701 D: 80828 Ptnml(0-2): 348, 18567, 45069, 18999, 377 https://tests.stockfishchess.org/tests/view/686ae98dfe0f2fe354c0c867 Refactor movegen to emit to a vector with 16-bit elements, which enables a speedup with AVX512-VBMI2 when writing moves to the move list. Very crude timing measurements of perft via timing ./stockfish "go perft 7" demonstrates approximately 17% perft speedup: Summary ./Stockfish-dev/src/stockfish 'go perft 7' ran 1.17 ± 0.04 times faster than ./Stockfish-base/src/stockfish 'go perft 7' Estimated overall nps increase of 0.4% via speedtest: 33605229 -> 33749825 (many thanks JonathanHallstrom). The corresponding arch is avx512icl as it is a good baseline for consumer avx-512 feature set support; Intel Ice Lake was the first consumer AVX-512 CPU and it is a decent subset of what AMD Zen 4 supports. closes https://github.com/official-stockfish/Stockfish/pull/6153 No functional change --- AUTHORS | 1 + scripts/get_native_properties.sh | 4 +- src/Makefile | 31 +++++++- src/misc.cpp | 3 + src/movegen.cpp | 131 ++++++++++++++++++++++--------- src/movegen.h | 12 +-- src/movepick.cpp | 30 ++++--- src/movepick.h | 4 +- 8 files changed, 156 insertions(+), 60 deletions(-) diff --git a/AUTHORS b/AUTHORS index be88a8e967d..b43a9e5b63e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,7 @@ Motohiro Isozaki (yaneurao) Hisayori Noda (nodchip) # All other authors of Stockfish code (in alphabetical order) +87flowers Aditya (absimaldata) Adrian Petrescu (apetresc) Ahmed Kerimov (wcdbmv) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index 773d6c2717d..e8c8f23f289 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -39,7 +39,9 @@ set_arch_loongarch64() { # Set the file CPU x86_64 architecture set_arch_x86_64() { - if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then + if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then + true_arch='x86-64-avx512icl' + elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then true_arch='x86-64-vnni256' elif check_flags 'avx512f' 'avx512bw'; then true_arch='x86-64-avx512' diff --git a/src/Makefile b/src/Makefile index 14c3c50c91e..40dceae0beb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -99,6 +99,7 @@ VPATH = syzygy:nnue:nnue/features # avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 # vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands # vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 +# avx512icl = yes/no --- ... multiple ... --- Use All AVX-512 features available on both Intel Ice Lake and AMD Zen 4 # altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension # vsx = yes/no --- -mvsx --- Use POWER VSX SIMD extension # neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture @@ -125,10 +126,10 @@ ifeq ($(ARCH), native) endif # explicitly check for the list of supported architectures (as listed with make help), -# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true` +# the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true` ifeq ($(ARCH), $(filter $(ARCH), \ - x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ - x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ + x86-64-avx512icl x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni \ + x86-64-bmi2 x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \ armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \ loongarch64 loongarch64-lsx loongarch64-lasx)) @@ -154,6 +155,7 @@ avxvnni = no avx512 = no vnni256 = no vnni512 = no +avx512icl = no altivec = no vsx = no neon = no @@ -290,6 +292,19 @@ ifeq ($(findstring -vnni512,$(ARCH)),-vnni512) vnni512 = yes endif +ifeq ($(findstring -avx512icl,$(ARCH)),-avx512icl) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes + vnni512 = yes + avx512icl = yes +endif + ifeq ($(sse),yes) prefetch = yes endif @@ -719,6 +734,13 @@ ifeq ($(vnni512),yes) endif endif +ifeq ($(avx512icl),yes) + CXXFLAGS += -DUSE_AVX512 -DUSE_VNNI -DUSE_AVX512ICL + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512cd -mavx512vl -mavx512dq -mavx512bw -mavx512ifma -mavx512vbmi -mavx512vbmi2 -mavx512vpopcntdq -mavx512bitalg -mavx512vnni -mvpclmulqdq -mgfni -mvaes + endif +endif + ifeq ($(sse41),yes) CXXFLAGS += -DUSE_SSE41 ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) @@ -877,6 +899,7 @@ help: echo "Supported archs:" && \ echo "" && \ echo "native > select the best architecture for the host processor (default)" && \ + echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lane or AMD Zen 4" && \ echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \ echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \ echo "x86-64-avx512 > x86 64-bit with avx512 support" && \ @@ -1025,6 +1048,7 @@ config-sanity: net echo "avx512: '$(avx512)'" && \ echo "vnni256: '$(vnni256)'" && \ echo "vnni512: '$(vnni512)'" && \ + echo "avx512icl: '$(avx512icl)'" && \ echo "altivec: '$(altivec)'" && \ echo "vsx: '$(vsx)'" && \ echo "neon: '$(neon)'" && \ @@ -1061,6 +1085,7 @@ config-sanity: net (test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \ (test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \ (test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \ + (test "$(avx512icl)" = "yes" || test "$(avx512icl)" = "no") && \ (test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \ (test "$(vsx)" = "yes" || test "$(vsx)" = "no") && \ (test "$(neon)" = "yes" || test "$(neon)" = "no") && \ diff --git a/src/misc.cpp b/src/misc.cpp index f85356c59f2..3bdde000f70 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -237,6 +237,9 @@ std::string compiler_info() { compiler += "\nCompilation settings : "; compiler += (Is64Bit ? "64bit" : "32bit"); +#if defined(USE_AVX512ICL) + compiler += " AVX512ICL"; +#endif #if defined(USE_VNNI) compiler += " VNNI"; #endif diff --git a/src/movegen.cpp b/src/movegen.cpp index a73bd8501de..10adc70be60 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -24,12 +24,88 @@ #include "bitboard.h" #include "position.h" +#if defined(USE_AVX512ICL) + #include + #include + #include +#endif + namespace Stockfish { namespace { +#if defined(USE_AVX512ICL) + +inline Move* write_moves(Move* moveList, uint32_t mask, __m512i vector) { + _mm512_storeu_si512(reinterpret_cast<__m512i*>(moveList), + _mm512_maskz_compress_epi16(mask, vector)); + return moveList + popcount(mask); +} + +template +inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) { + alignas(64) static constexpr auto SPLAT_TABLE = [] { + std::array table{}; + for (int8_t i = 0; i < 64; i++) + { + Square from{std::clamp(i - offset, 0, 63)}; + table[i] = {Move(from, Square{i})}; + } + return table; + }(); + + auto table = reinterpret_cast(SPLAT_TABLE.data()); + + moveList = + write_moves(moveList, static_cast(to_bb >> 0), _mm512_load_si512(table + 0)); + moveList = + write_moves(moveList, static_cast(to_bb >> 32), _mm512_load_si512(table + 1)); + + return moveList; +} + +inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) { + alignas(64) static constexpr auto SPLAT_TABLE = [] { + std::array table{}; + for (int8_t i = 0; i < 64; i++) + table[i] = {Move(SQUARE_ZERO, Square{i})}; + return table; + }(); + + __m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw()); + + auto table = reinterpret_cast(SPLAT_TABLE.data()); + + moveList = write_moves(moveList, static_cast(to_bb >> 0), + _mm512_or_si512(_mm512_load_si512(table + 0), fromVec)); + moveList = write_moves(moveList, static_cast(to_bb >> 32), + _mm512_or_si512(_mm512_load_si512(table + 1), fromVec)); + + return moveList; +} + +#else + +template +inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) { + while (to_bb) + { + Square to = pop_lsb(to_bb); + *moveList++ = Move(to - offset, to); + } + return moveList; +} + +inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) { + while (to_bb) + *moveList++ = Move(from, pop_lsb(to_bb)); + return moveList; +} + +#endif + template -ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { +Move* make_promotions(Move* moveList, [[maybe_unused]] Square to) { constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS; @@ -48,7 +124,7 @@ ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) { template -ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +Move* generate_pawn_moves(const Position& pos, Move* moveList, Bitboard target) { constexpr Color Them = ~Us; constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); @@ -75,17 +151,8 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta b2 &= target; } - while (b1) - { - Square to = pop_lsb(b1); - *moveList++ = Move(to - Up, to); - } - - while (b2) - { - Square to = pop_lsb(b2); - *moveList++ = Move(to - Up - Up, to); - } + moveList = splat_pawn_moves(moveList, b1); + moveList = splat_pawn_moves(moveList, b2); } // Promotions and underpromotions @@ -114,17 +181,8 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta Bitboard b1 = shift(pawnsNotOn7) & enemies; Bitboard b2 = shift(pawnsNotOn7) & enemies; - while (b1) - { - Square to = pop_lsb(b1); - *moveList++ = Move(to - UpRight, to); - } - - while (b2) - { - Square to = pop_lsb(b2); - *moveList++ = Move(to - UpLeft, to); - } + moveList = splat_pawn_moves(moveList, b1); + moveList = splat_pawn_moves(moveList, b2); if (pos.ep_square() != SQ_NONE) { @@ -148,7 +206,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta template -ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) { +Move* generate_moves(const Position& pos, Move* moveList, Bitboard target) { static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()"); @@ -159,8 +217,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) Square from = pop_lsb(bb); Bitboard b = attacks_bb(from, pos.pieces()) & target; - while (b) - *moveList++ = Move(from, pop_lsb(b)); + moveList = splat_moves(moveList, from, b); } return moveList; @@ -168,7 +225,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) template -ExtMove* generate_all(const Position& pos, ExtMove* moveList) { +Move* generate_all(const Position& pos, Move* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate_all()"); @@ -192,8 +249,7 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { Bitboard b = attacks_bb(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target); - while (b) - *moveList++ = Move(ksq, pop_lsb(b)); + moveList = splat_moves(moveList, ksq, b); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) @@ -213,7 +269,7 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) { // // Returns a pointer to the end of the move list. template -ExtMove* generate(const Position& pos, ExtMove* moveList) { +Move* generate(const Position& pos, Move* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate()"); assert((Type == EVASIONS) == bool(pos.checkers())); @@ -225,21 +281,20 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { } // Explicit template instantiations -template ExtMove* generate(const Position&, ExtMove*); -template ExtMove* generate(const Position&, ExtMove*); -template ExtMove* generate(const Position&, ExtMove*); -template ExtMove* generate(const Position&, ExtMove*); - +template Move* generate(const Position&, Move*); +template Move* generate(const Position&, Move*); +template Move* generate(const Position&, Move*); +template Move* generate(const Position&, Move*); // generate generates all the legal moves in the given position template<> -ExtMove* generate(const Position& pos, ExtMove* moveList) { +Move* generate(const Position& pos, Move* moveList) { Color us = pos.side_to_move(); Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us); Square ksq = pos.square(us); - ExtMove* cur = moveList; + Move* cur = moveList; moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); diff --git a/src/movegen.h b/src/movegen.h index 7c6cceb7c3d..287fd8927b5 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -49,7 +49,7 @@ struct ExtMove: public Move { inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; } template -ExtMove* generate(const Position& pos, ExtMove* moveList); +Move* generate(const Position& pos, Move* moveList); // The MoveList struct wraps the generate() function and returns a convenient // list of moves. Using MoveList is sometimes preferable to directly calling @@ -59,13 +59,13 @@ struct MoveList { explicit MoveList(const Position& pos) : last(generate(pos, moveList)) {} - const ExtMove* begin() const { return moveList; } - const ExtMove* end() const { return last; } - size_t size() const { return last - moveList; } - bool contains(Move move) const { return std::find(begin(), end(), move) != end(); } + const Move* begin() const { return moveList; } + const Move* end() const { return last; } + size_t size() const { return last - moveList; } + bool contains(Move move) const { return std::find(begin(), end(), move) != end(); } private: - ExtMove moveList[MAX_MOVES], *last; + Move moveList[MAX_MOVES], *last; }; } // namespace Stockfish diff --git a/src/movepick.cpp b/src/movepick.cpp index 3e7c10160cb..79b6f55a2bf 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -123,7 +123,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo // Captures are ordered by Most Valuable Victim (MVV), preferring captures // with a good history. Quiets moves are ordered using the history tables. template -void MovePicker::score() { +ExtMove* MovePicker::score(MoveList& ml) { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); @@ -138,8 +138,12 @@ void MovePicker::score() { threatByLesser[QUEEN] = pos.attacks_by(~us) | threatByLesser[ROOK]; } - for (auto& m : *this) + ExtMove* it = cur; + for (auto move : ml) { + ExtMove& m = *it++; + m = move; + const Square from = m.from_sq(); const Square to = m.to_sq(); const Piece pc = pos.moved_piece(m); @@ -189,6 +193,7 @@ void MovePicker::score() { } } } + return it; } // Returns the next move satisfying a predicate function. @@ -222,14 +227,16 @@ Move MovePicker::next_move() { case CAPTURE_INIT : case PROBCUT_INIT : - case QCAPTURE_INIT : + case QCAPTURE_INIT : { + MoveList ml(pos); + cur = endBadCaptures = moves; - endCur = endCaptures = generate(pos, cur); + endCur = endCaptures = score(ml); - score(); partial_insertion_sort(cur, endCur, std::numeric_limits::min()); ++stage; goto top; + } case GOOD_CAPTURE : if (select([&]() { @@ -246,9 +253,10 @@ Move MovePicker::next_move() { case QUIET_INIT : if (!skipQuiets) { - endCur = endGenerated = generate(pos, cur); + MoveList ml(pos); + + endCur = endGenerated = score(ml); - score(); partial_insertion_sort(cur, endCur, -3560 * depth); } @@ -283,14 +291,16 @@ Move MovePicker::next_move() { return Move::none(); - case EVASION_INIT : + case EVASION_INIT : { + MoveList ml(pos); + cur = moves; - endCur = endGenerated = generate(pos, cur); + endCur = endGenerated = score(ml); - score(); partial_insertion_sort(cur, endCur, std::numeric_limits::min()); ++stage; [[fallthrough]]; + } case EVASION : case QCAPTURE : diff --git a/src/movepick.h b/src/movepick.h index bf0c96c7c2b..9d6c02b0ef3 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -55,8 +55,8 @@ class MovePicker { private: template Move select(Pred); - template - void score(); + template + ExtMove* score(MoveList&); ExtMove* begin() { return cur; } ExtMove* end() { return endCur; } From e2aa1255707628aa1d70f13c88211724e34fa38e Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 8 Jul 2025 18:34:14 -0700 Subject: [PATCH 1080/1309] Consistent syntax for class members Currently, search.cpp uses three different syntax when accessing class member variables * thisThread object references. This was a remnant from when pos.this_thread() was necessary for thread-local variable access * this object references * implicit member variable access This PR aims to deprecate thisThread and standardize this syntax to implicit variable access, which is consistent with the rest of SF's codebase. closes https://github.com/official-stockfish/Stockfish/pull/6154 no functional change --- src/search.cpp | 152 ++++++++++++++++++++++--------------------------- 1 file changed, 69 insertions(+), 83 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1b5a2bbbc13..fae3fc39fad 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -258,8 +258,8 @@ void Search::Worker::iterative_deepening() { for (int i = 7; i > 0; --i) { (ss - i)->continuationHistory = - &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel - (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; + &continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + (ss - i)->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; (ss - i)->staticEval = VALUE_NONE; } @@ -586,7 +586,7 @@ Value Search::Worker::search( // Check if we have an upcoming move that draws by repetition if (!rootNode && alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) { - alpha = value_draw(this->nodes); + alpha = value_draw(nodes); if (alpha >= beta) return alpha; } @@ -612,29 +612,27 @@ Value Search::Worker::search( SearchedList quietsSearched; // Step 1. Initialize node - Worker* thisThread = this; - ss->inCheck = pos.checkers(); - priorCapture = pos.captured_piece(); - Color us = pos.side_to_move(); - ss->moveCount = 0; - bestValue = -VALUE_INFINITE; - maxValue = VALUE_INFINITE; + ss->inCheck = pos.checkers(); + priorCapture = pos.captured_piece(); + Color us = pos.side_to_move(); + ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; // Check for the available remaining time if (is_mainthread()) - main_manager()->check_time(*thisThread); + main_manager()->check_time(*this); // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) - if (PvNode && thisThread->selDepth < ss->ply + 1) - thisThread->selDepth = ss->ply + 1; + if (PvNode && selDepth < ss->ply + 1) + selDepth = ss->ply + 1; if (!rootNode) { // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : value_draw(nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -663,9 +661,7 @@ Value Search::Worker::search( auto [ttHit, ttData, ttWriter] = tt.probe(posKey); // Need further processing of the saved data ss->ttHit = ttHit; - ttData.move = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] - : ttHit ? ttData.move - : Move::none(); + ttData.move = rootNode ? rootMoves[pvIdx].pv[0] : ttHit ? ttData.move : Move::none(); ttData.value = ttHit ? value_from_tt(ttData.value, ss->ply, pos.rule50_count()) : VALUE_NONE; ss->ttPv = excludedMove ? ss->ttPv : PvNode || (ttHit && ttData.is_pv); ttCapture = ttData.move && pos.capture_stage(ttData.move); @@ -733,7 +729,7 @@ Value Search::Worker::search( if (err != TB::ProbeState::FAIL) { - thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); + tbHits.fetch_add(1, std::memory_order_relaxed); int drawScore = tbConfig.useRule50 ? 1 : 0; @@ -770,7 +766,7 @@ Value Search::Worker::search( // Step 6. Static evaluation of the position Value unadjustedStaticEval = VALUE_NONE; - const auto correctionValue = correction_value(*thisThread, pos, ss); + const auto correctionValue = correction_value(*this, pos, ss); if (ss->inCheck) { // Skip early pruning when in check @@ -808,9 +804,9 @@ Value Search::Worker::search( if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && !ttHit) { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1858, 1492) + 661; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1057 / 1024; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1057 / 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] + pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] << bonus * 1266 / 1024; } @@ -854,7 +850,7 @@ Value Search::Worker::search( // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta && ss->staticEval >= beta - 19 * depth + 389 && !excludedMove && pos.non_pawn_material(us) - && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) + && ss->ply >= nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); @@ -862,8 +858,8 @@ Value Search::Worker::search( Depth R = 7 + depth / 3; ss->currentMove = Move::null(); - ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; - ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0]; + ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; + ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; do_null_move(pos, st); @@ -874,18 +870,18 @@ Value Search::Worker::search( // Do not return unproven mate or TB scores if (nullValue >= beta && !is_win(nullValue)) { - if (thisThread->nmpMinPly || depth < 16) + if (nmpMinPly || depth < 16) return nullValue; - assert(!thisThread->nmpMinPly); // Recursive verification is not allowed + assert(!nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled // until ply exceeds nmpMinPly. - thisThread->nmpMinPly = ss->ply + 3 * (depth - R) / 4; + nmpMinPly = ss->ply + 3 * (depth - R) / 4; Value v = search(pos, ss, beta - 1, beta, depth - R, false); - thisThread->nmpMinPly = 0; + nmpMinPly = 0; if (v >= beta) return nullValue; @@ -912,7 +908,7 @@ Value Search::Worker::search( { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); - MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &thisThread->captureHistory); + MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); Depth probCutDepth = std::max(depth - 5, 0); while ((move = mp.next_move()) != Move::none()) @@ -930,9 +926,9 @@ Value Search::Worker::search( ss->currentMove = move; ss->continuationHistory = - &this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; + &continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = - &this->continuationCorrectionHistory[movedPiece][move.to_sq()]; + &continuationCorrectionHistory[movedPiece][move.to_sq()]; // Perform a preliminary qsearch to verify that the move holds value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); @@ -969,8 +965,8 @@ Value Search::Worker::search( (ss - 4)->continuationHistory, (ss - 5)->continuationHistory, (ss - 6)->continuationHistory}; - MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory, - &thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply); + MovePicker mp(pos, ttData.move, depth, &mainHistory, &lowPlyHistory, &captureHistory, contHist, + &pawnHistory, ss->ply); value = bestValue; @@ -992,9 +988,7 @@ Value Search::Worker::search( // At root obey the "searchmoves" option and skip moves not listed in Root // Move List. In MultiPV mode we also skip PV moves that have been already // searched and those of lower "TB rank" if we are in a TB root position. - if (rootNode - && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, - thisThread->rootMoves.begin() + thisThread->pvLast, move)) + if (rootNode && !std::count(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast, move)) continue; ss->moveCount = ++moveCount; @@ -1002,7 +996,7 @@ Value Search::Worker::search( if (rootNode && is_mainthread() && nodes > 10000000) { main_manager()->updates.onIter( - {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + thisThread->pvIdx}); + {depth, UCIEngine::move(move, pos.is_chess960()), moveCount + pvIdx}); } if (PvNode) (ss + 1)->pv = nullptr; @@ -1041,8 +1035,7 @@ Value Search::Worker::search( if (capture || givesCheck) { Piece capturedPiece = pos.piece_on(move.to_sq()); - int captHist = - thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; + int captHist = captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) @@ -1071,16 +1064,15 @@ Value Search::Worker::search( } else { - int history = - (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; + int history = (*contHist[0])[movedPiece][move.to_sq()] + + (*contHist[1])[movedPiece][move.to_sq()] + + pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning if (history < -4229 * depth) continue; - history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; + history += 68 * mainHistory[us][move.from_to()] / 32; lmrDepth += history / 3388; @@ -1119,7 +1111,7 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 6 - (thisThread->completedDepth > 27) + ss->ttPv && is_valid(ttData.value) + && depth >= 6 - (completedDepth > 27) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { @@ -1134,10 +1126,9 @@ Value Search::Worker::search( { int corrValAdj = std::abs(correctionValue) / 248400; int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj - - 997 * ttMoveHistory / 131072 - - (ss->ply > thisThread->rootDepth) * 47; + - 997 * ttMoveHistory / 131072 - (ss->ply > rootDepth) * 47; int tripleMargin = 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - corrValAdj - - (ss->ply * 2 > thisThread->rootDepth * 3) * 54; + - (ss->ply * 2 > rootDepth * 3) * 54; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1180,9 +1171,9 @@ Value Search::Worker::search( // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = - &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; + &continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = - &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; + &continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; // Decrease reduction for PvNodes (*Scaler) @@ -1215,11 +1206,10 @@ Value Search::Worker::search( r -= 2006; if (capture) - ss->statScore = - 826 * int(PieceValue[pos.captured_piece()]) / 128 - + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; + ss->statScore = 826 * int(PieceValue[pos.captured_piece()]) / 128 + + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else - ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + ss->statScore = 2 * mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()]; @@ -1284,7 +1274,7 @@ Value Search::Worker::search( (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && thisThread->rootDepth > 8) + if (move == ttData.move && rootDepth > 8) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); @@ -1304,8 +1294,7 @@ Value Search::Worker::search( if (rootNode) { - RootMove& rm = - *std::find(thisThread->rootMoves.begin(), thisThread->rootMoves.end(), move); + RootMove& rm = *std::find(rootMoves.begin(), rootMoves.end(), move); rm.effort += nodes - nodeCount; @@ -1320,7 +1309,7 @@ Value Search::Worker::search( if (moveCount == 1 || value > alpha) { rm.score = rm.uciScore = value; - rm.selDepth = thisThread->selDepth; + rm.selDepth = selDepth; rm.scoreLowerbound = rm.scoreUpperbound = false; if (value >= beta) @@ -1344,8 +1333,8 @@ Value Search::Worker::search( // We record how often the best move has been changed in each iteration. // This information is used for time management. In MultiPV mode, // we must take care to only do this for the first PV line. - if (moveCount > 1 && !thisThread->pvIdx) - ++thisThread->bestMoveChanges; + if (moveCount > 1 && !pvIdx) + ++bestMoveChanges; } else // All other moves but the PV, are set to the lowest value: this @@ -1356,8 +1345,8 @@ Value Search::Worker::search( // In case we have an alternative move equal in eval to the current bestmove, // promote it to bestmove by pretending it just exceeds alpha (but not beta). - int inc = (value == bestValue && ss->ply + 2 >= thisThread->rootDepth - && (int(nodes) & 15) == 0 && !is_win(std::abs(value) + 1)); + int inc = (value == bestValue && ss->ply + 2 >= rootDepth && (int(nodes) & 15) == 0 + && !is_win(std::abs(value) + 1)); if (value + inc > bestValue) { @@ -1439,11 +1428,10 @@ Value Search::Worker::search( update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, scaledBonus * 412 / 32768); - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << scaledBonus * 203 / 32768; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 203 / 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] + pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] << scaledBonus * 1040 / 32768; } @@ -1452,7 +1440,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1080; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1080; } if (PvNode) @@ -1465,7 +1453,7 @@ Value Search::Worker::search( // Write gathered information in transposition table. Note that the // static evaluation is saved as it was before correction history. - if (!excludedMove && !(rootNode && thisThread->pvIdx)) + if (!excludedMove && !(rootNode && pvIdx)) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT @@ -1480,7 +1468,7 @@ Value Search::Worker::search( { auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - update_correction_history(pos, ss, *thisThread, bonus); + update_correction_history(pos, ss, *this, bonus); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1507,7 +1495,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // 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); + alpha = value_draw(nodes); if (alpha >= beta) return alpha; } @@ -1528,14 +1516,13 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) ss->pv[0] = Move::none(); } - Worker* thisThread = this; - bestMove = Move::none(); - ss->inCheck = pos.checkers(); - moveCount = 0; + bestMove = Move::none(); + ss->inCheck = pos.checkers(); + moveCount = 0; // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) - if (PvNode && thisThread->selDepth < ss->ply + 1) - thisThread->selDepth = ss->ply + 1; + if (PvNode && selDepth < ss->ply + 1) + selDepth = ss->ply + 1; // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) @@ -1564,7 +1551,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) bestValue = futilityBase = -VALUE_INFINITE; else { - const auto correctionValue = correction_value(*thisThread, pos, ss); + const auto correctionValue = correction_value(*this, pos, ss); if (ss->ttHit) { @@ -1614,8 +1601,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Initialize a MovePicker object for the current position, and prepare to search // the moves. We presently use two stages of move generator in quiescence search: // captures, or evasions only when in check. - MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->lowPlyHistory, - &thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply); + MovePicker mp(pos, ttData.move, DEPTH_QS, &mainHistory, &lowPlyHistory, &captureHistory, + contHist, &pawnHistory, ss->ply); // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta // cutoff occurs. @@ -1663,8 +1650,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Continuation history based pruning if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] - [move.to_sq()] + + pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)][move.to_sq()] <= 6218) continue; @@ -1681,9 +1667,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Update the current move ss->currentMove = move; ss->continuationHistory = - &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; + &continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = - &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; + &continuationCorrectionHistory[movedPiece][move.to_sq()]; value = -qsearch(pos, ss + 1, -beta, -alpha); undo_move(pos, move); From 793110ca194fd6e8c5933d175e9ad7b523a67d67 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 11 Jul 2025 13:36:15 +0300 Subject: [PATCH 1081/1309] Remove !ttData.move condition from cutNode reduction Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 118080 W: 30427 L: 30298 D: 57355 Ptnml(0-2): 290, 13880, 30561, 14029, 280 https://tests.stockfishchess.org/tests/view/6853f9bc038630d25f468670 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 232272 W: 59187 L: 59182 D: 113903 Ptnml(0-2): 111, 25430, 65014, 25505, 76 https://tests.stockfishchess.org/tests/view/68585c40a596a06817bb922e closes https://github.com/official-stockfish/Stockfish/pull/6157 bench: 2227361 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fae3fc39fad..6c4d125b2d0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1189,7 +1189,7 @@ Value Search::Worker::search( // Increase reduction for cut nodes if (cutNode) - r += 2864 + 966 * !ttData.move; + r += 3000; // Increase reduction if ttMove is a capture if (ttCapture) From 2c9a1871aeab32e882223d98001dc84a7cd481c2 Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Sun, 13 Jul 2025 11:01:34 +0200 Subject: [PATCH 1082/1309] Add offsets to history updates. All parameters were tuned on 60k STC games (https://tests.stockfishchess.org/tests/view/6867ef49fe0f2fe354c0c735). Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 60576 W: 15794 L: 15444 D: 29338 Ptnml(0-2): 141, 7003, 15642, 7369, 133 https://tests.stockfishchess.org/tests/view/686d5af11451bd6fb830772a Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 58722 W: 15159 L: 14799 D: 28764 Ptnml(0-2): 19, 6259, 16455, 6599, 29 https://tests.stockfishchess.org/tests/view/686f9fd51451bd6fb83081c9 closes https://github.com/official-stockfish/Stockfish/pull/6158 bench: 2706299 --- src/search.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6c4d125b2d0..60ee42abaa2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1872,13 +1872,16 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { static constexpr std::array conthist_bonuses = { {{1, 1092}, {2, 631}, {3, 294}, {4, 517}, {5, 126}, {6, 445}}}; + static constexpr int conthist_offsets[6]{71, 106, -22, -20, 29, -74}; + for (const auto [i, weight] : conthist_bonuses) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (((ss - i)->currentMove).is_ok()) - (*(ss - i)->continuationHistory)[pc][to] << bonus * weight / 1024; + (*(ss - i)->continuationHistory)[pc][to] + << (bonus * weight / 1024) + conthist_offsets[i - 1]; } } @@ -1891,14 +1894,14 @@ 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 * 792 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << (bonus * 792 / 1024) + 40; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * (bonus > 0 ? 1082 : 784) / 1024); int pIndex = pawn_structure_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << bonus * (bonus > 0 ? 705 : 450) / 1024; + << (bonus * (bonus > 0 ? 705 : 450) / 1024) + 70; } } From bdc393d3a2e95ba2c7a2c40f13ba21170d8befdc Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 13 Jul 2025 21:40:16 +0300 Subject: [PATCH 1083/1309] Correct comment closes https://github.com/official-stockfish/Stockfish/pull/6159 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 60ee42abaa2..65f1a79a5c7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -891,7 +891,7 @@ Value Search::Worker::search( improving |= ss->staticEval >= beta + 94; // Step 10. Internal iterative reductions - // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. + // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. // (*Scaler) Especially if they make IIR less aggressive. if (!allNode && depth >= 6 && !ttData.move) depth--; From e88ccfd835544ffa4ae44ebb1231d50b29143c19 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 15 Jul 2025 22:10:26 +0200 Subject: [PATCH 1084/1309] Prevent accidential misuse of TUNE() Writing a TUNE expression like int smallNetThreshold = 962; TUNE(smallNetThreshold, 850, 1050); is wrong and can lead to a weird binary. It should thus fail to even compile, with the proper intellisense you'll also see the error directly in your editor. A cheap way to prevent this is to disallow any fundamental type in the parameter list. closes https://github.com/official-stockfish/Stockfish/pull/6164 No functional change --- src/tune.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tune.h b/src/tune.h index 4dab6bf457e..d3c9ebaa3a2 100644 --- a/src/tune.h +++ b/src/tune.h @@ -170,11 +170,20 @@ class Tune { static OptionsMap* options; }; +template +constexpr void tune_check_args(Args&&...) { + static_assert((!std::is_fundamental_v && ...), "TUNE macro arguments wrong"); +} + // Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() #define STRINGIFY(x) #x #define UNIQUE2(x, y) x##y #define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ -#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = []() -> int { \ + tune_check_args(__VA_ARGS__); \ + return Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ + }(); #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true From 92514fd3a2bfe66e6d6fa8b89ec69b826bbc31c8 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 10 Jul 2025 23:06:36 -0400 Subject: [PATCH 1085/1309] Simplify away a term in see pruning This change only sets the margin to 0 when depth <= 2 and margin <= 42 which never has a difference, thus non-functional Passed non-regression STC LLR: 3.51 (-2.94,2.94) <-1.75,0.25> Total: 306976 W: 78935 L: 78961 D: 149080 Ptnml(0-2): 630, 34097, 84067, 34057, 637 https://tests.stockfishchess.org/tests/view/68708015fa93cf16d3bb2782 closes https://github.com/official-stockfish/Stockfish/pull/6170 No functional change --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 65f1a79a5c7..e698e2c7fe5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1047,8 +1047,8 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 31, -137 * depth, 125 * depth); - if (!pos.see_ge(move, -158 * depth - seeHist)) + int margin = std::clamp(158 * depth + captHist / 31, 0, 283 * depth); + if (!pos.see_ge(move, -margin)) { bool mayStalemateTrap = depth > 2 && alpha < 0 && pos.non_pawn_material(us) == PieceValue[movedPiece] From a516b517d374bb9b4086dd2cc56be8f55d6aeba8 Mon Sep 17 00:00:00 2001 From: aronpetkovski Date: Tue, 15 Jul 2025 14:23:18 +0300 Subject: [PATCH 1086/1309] Simplify eval >= beta condition from NMP It seems that now only checking if static eval is above beta by some margin is all that's necessary. Passed Non-Regression STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 227264 W: 58430 L: 58421 D: 110413 Ptnml(0-2): 532, 26559, 59454, 26542, 545 https://tests.stockfishchess.org/tests/view/68763a77432ca24f6388c766 Passed Non-Regression LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 72048 W: 18622 L: 18455 D: 34971 Ptnml(0-2): 26, 7775, 20272, 7908, 43 https://tests.stockfishchess.org/tests/view/687c9f2b6e17e7fa3939b0c5 closes https://github.com/official-stockfish/Stockfish/pull/6175 Bench: 2759840 --- AUTHORS | 1 + src/search.cpp | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index b43a9e5b63e..76b4f71ca1d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -31,6 +31,7 @@ Andy Duplain Antoine Champion (antoinechampion) Aram Tumanian (atumanian) Arjun Temurnikar +Aron Petkovski (fury) Artem Solopiy (EntityFX) Auguste Pop Balazs Szilagyi diff --git a/src/search.cpp b/src/search.cpp index e698e2c7fe5..c5534924b4a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -848,12 +848,10 @@ Value Search::Worker::search( } // Step 9. Null move search with verification search - if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta + if (cutNode && (ss - 1)->currentMove != Move::null() && ss->staticEval >= beta - 19 * depth + 389 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { - assert(eval - beta >= 0); - // Null move dynamic reduction based on depth Depth R = 7 + depth / 3; From 90c83c381ff3858b688eebb548985d1d9dbe0875 Mon Sep 17 00:00:00 2001 From: Muzhen Gaming <61100393+XInTheDark@users.noreply.github.com> Date: Mon, 28 Jul 2025 00:04:02 +0800 Subject: [PATCH 1087/1309] VVLTC Search Tune alues were tuned with 125k VVLTC games. Passed VVLTC 1st sprt: https://tests.stockfishchess.org/tests/view/68865feb7b562f5f7b731341 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 39640 W: 10380 L: 10109 D: 19151 Ptnml(0-2): 5, 3556, 12424, 3833, 2 Passed VVLTC 2nd sprt: https://tests.stockfishchess.org/tests/view/68864dfa7b562f5f7b7312ee LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 25972 W: 6807 L: 6537 D: 12628 Ptnml(0-2): 0, 2294, 8132, 2556, 4 closes https://github.com/official-stockfish/Stockfish/pull/6188 Bench: 3143696 --- src/search.cpp | 162 ++++++++++++++++++++++++------------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c5534924b4a..41177b81cdf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -83,7 +83,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 7696 * pcv + 7689 * micv + 9708 * (wnpcv + bnpcv) + 6978 * cntcv; + return 8867 * pcv + 8136 * micv + 10757 * (wnpcv + bnpcv) + 7232 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -99,11 +99,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 = 172; + static constexpr int nonPawnWeight = 165; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 111 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 151 / 128; + << bonus * 128 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 153 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] @@ -111,7 +111,7 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 141 / 128; + << bonus * 153 / 128; } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -288,7 +288,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(86); + lowPlyHistory.fill(89); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -324,13 +324,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11134; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11131; 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] = 137 * avg / (std::abs(avg) + 91); + optimism[us] = 136 * avg / (std::abs(avg) + 93); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -538,9 +538,9 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(67); - captureHistory.fill(-688); - pawnHistory.fill(-1287); + mainHistory.fill(64); + captureHistory.fill(-753); + pawnHistory.fill(-1275); pawnCorrectionHistory.fill(5); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory.fill(0); @@ -555,10 +555,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-473); + h.fill(-494); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2796 / 128.0 * std::log(i)); + reductions[i] = int(2782 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -681,16 +681,16 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(125 * depth - 77, 1157)); + std::min(127 * depth - 74, 1063)); // 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, -2301); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2128); } // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. - if (pos.rule50_count() < 90) + if (pos.rule50_count() < 91) { if (depth >= 8 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move) && !is_decisive(ttData.value)) @@ -803,11 +803,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && !ttHit) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1858, 1492) + 661; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1057 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1979, 1561) + 630; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 935 / 1024; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1266 / 1024; + << bonus * 1428 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -820,26 +820,26 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 175) + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 177) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 486 - 325 * depth * depth) + if (!PvNode && eval < alpha - 495 - 290 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 93 - 20 * (cutNode && !ss->ttHit); + Value futilityMult = 90 - 20 * (cutNode && !ss->ttHit); return futilityMult * d // - improving * futilityMult * 2 // - opponentWorsening * futilityMult / 3 // - + (ss - 1)->statScore / 376 // - + std::abs(correctionValue) / 168639; + + (ss - 1)->statScore / 356 // + + std::abs(correctionValue) / 171290; }; if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta @@ -849,7 +849,7 @@ Value Search::Worker::search( // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() - && ss->staticEval >= beta - 19 * depth + 389 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 19 * depth + 403 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { // Null move dynamic reduction based on depth @@ -886,7 +886,7 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta + 94; + improving |= ss->staticEval >= beta + 87; // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. @@ -897,7 +897,7 @@ Value Search::Worker::search( // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 201 - 58 * improving; + probCutBeta = beta + 215 - 60 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -953,7 +953,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 400; + probCutBeta = beta + 417; 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; @@ -1017,7 +1017,7 @@ Value Search::Worker::search( // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 968; + r += 931; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1038,7 +1038,7 @@ Value Search::Worker::search( // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 232 + 224 * lmrDepth + Value futilityValue = ss->staticEval + 222 + 216 * lmrDepth + PieceValue[capturedPiece] + 131 * captHist / 1024; if (futilityValue <= alpha) continue; @@ -1067,21 +1067,21 @@ Value Search::Worker::search( + pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4229 * depth) + if (history < -4361 * depth) continue; - history += 68 * mainHistory[us][move.from_to()] / 32; + history += 71 * mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3388; + lmrDepth += history / 3233; Value baseFutility = (bestMove ? 46 : 230); Value futilityValue = - ss->staticEval + baseFutility + 117 * lmrDepth + 102 * (ss->staticEval > alpha); + ss->staticEval + baseFutility + 131 * lmrDepth + 91 * (ss->staticEval > alpha); // 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 (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) @@ -1092,7 +1092,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1109,11 +1109,11 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 6 - (completedDepth > 27) + ss->ttPv && is_valid(ttData.value) + && depth >= 6 - (completedDepth > 26) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (58 + 76 * (ss->ttPv && !PvNode)) * depth / 57; + Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 58; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1122,11 +1122,11 @@ Value Search::Worker::search( if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 248400; - int doubleMargin = -4 + 244 * PvNode - 206 * !ttCapture - corrValAdj - - 997 * ttMoveHistory / 131072 - (ss->ply > rootDepth) * 47; - int tripleMargin = 84 + 269 * PvNode - 253 * !ttCapture + 91 * ss->ttPv - corrValAdj - - (ss->ply * 2 > rootDepth * 3) * 54; + int corrValAdj = std::abs(correctionValue) / 249096; + int doubleMargin = 4 + 205 * PvNode - 223 * !ttCapture - corrValAdj + - 959 * ttMoveHistory / 131072 - (ss->ply > rootDepth) * 45; + int tripleMargin = 80 + 276 * PvNode - 249 * !ttCapture + 86 * ss->ttPv - corrValAdj + - (ss->ply * 2 > rootDepth * 3) * 53; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1176,14 +1176,14 @@ Value Search::Worker::search( // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2437 + PvNode * 926 + (ttData.value > alpha) * 901 + r -= 2510 + PvNode * 963 + (ttData.value > alpha) * 916 + (ttData.depth >= depth) * (943 + cutNode * 1180); // These reduction adjustments have no proven non-linear scaling r += 650; // Base reduction offset to compensate for other tweaks - r -= moveCount * 66; - r -= std::abs(correctionValue) / 28047; + r -= moveCount * 69; + r -= std::abs(correctionValue) / 27160; // Increase reduction for cut nodes if (cutNode) @@ -1195,16 +1195,16 @@ Value Search::Worker::search( // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1036 + allNode * 848; + r += 935 + allNode * 763; - r += (ss + 1)->quietMoveStreak * 50; + r += (ss + 1)->quietMoveStreak * 51; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2006; + r -= 2043; if (capture) - ss->statScore = 826 * int(PieceValue[pos.captured_piece()]) / 128 + ss->statScore = 782 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else ss->statScore = 2 * mainHistory[us][move.from_to()] @@ -1212,7 +1212,7 @@ Value Search::Worker::search( + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 826 / 8192; + r -= ss->statScore * 789 / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1237,7 +1237,7 @@ Value Search::Worker::search( { // 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 + 42 + 2 * newDepth); + const bool doDeeperSearch = value > (bestValue + 43 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1246,7 +1246,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - update_continuation_histories(ss, movedPiece, move.to_sq(), 1508); + update_continuation_histories(ss, movedPiece, move.to_sq(), 1412); } else if (value > alpha && value < bestValue + 9) newDepth--; @@ -1257,7 +1257,7 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1128; + r += 1139; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, @@ -1343,7 +1343,7 @@ Value Search::Worker::search( // In case we have an alternative move equal in eval to the current bestmove, // promote it to bestmove by pretending it just exceeds alpha (but not beta). - int inc = (value == bestValue && ss->ply + 2 >= rootDepth && (int(nodes) & 15) == 0 + int inc = (value == bestValue && ss->ply + 2 >= rootDepth && (int(nodes) & 14) == 0 && !is_win(std::abs(value) + 1)); if (value + inc > bestValue) @@ -1406,31 +1406,31 @@ Value Search::Worker::search( update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, ttData.move, moveCount); if (!PvNode) - ttMoveHistory << (bestMove == ttData.move ? 800 : -879); + ttMoveHistory << (bestMove == ttData.move ? 811 : -848); } // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -220; - bonusScale += std::min(-(ss - 1)->statScore / 103, 323); - bonusScale += std::min(73 * depth, 531); - bonusScale += 174 * ((ss - 1)->moveCount > 8); - bonusScale += 144 * (!ss->inCheck && bestValue <= ss->staticEval - 104); - bonusScale += 128 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82); + int bonusScale = -215; + bonusScale += std::min(-(ss - 1)->statScore / 103, 337); + bonusScale += std::min(64 * depth, 552); + bonusScale += 177 * ((ss - 1)->moveCount > 8); + bonusScale += 141 * (!ss->inCheck && bestValue <= ss->staticEval - 94); + bonusScale += 141 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(159 * depth - 94, 1501) * bonusScale; + const int scaledBonus = std::min(155 * depth - 88, 1416) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 412 / 32768); + scaledBonus * 397 / 32768); - mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 203 / 32768; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 224 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1040 / 32768; + << scaledBonus * 1127 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1438,7 +1438,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1080; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1042; } if (PvNode) @@ -1588,7 +1588,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 376; + futilityBase = ss->staticEval + 352; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1649,7 +1649,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)][move.to_sq()] - <= 6218) + <= 5868) continue; // Do not search moves with bad enough SEE values @@ -1735,7 +1735,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 * 794 / rootDelta + !i * reductionScale * 205 / 512 + 1086; + return reductionScale - delta * 731 / rootDelta + !i * reductionScale * 216 / 512 + 1089; } // elapsed() returns the time elapsed since the search started. If the @@ -1831,35 +1831,35 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(143 * depth - 89, 1496) + 302 * (bestMove == ttMove); - int malus = std::min(737 * depth - 179, 3141) - 30 * moveCount; + int bonus = std::min(142 * depth - 88, 1501) + 318 * (bestMove == ttMove); + int malus = std::min(757 * depth - 172, 2848) - 30 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1059 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1054 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1310 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1388 / 1024); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1213 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1235 / 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 * 580 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 595 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1388 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1354 / 1024; } } @@ -1868,7 +1868,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, 1092}, {2, 631}, {3, 294}, {4, 517}, {5, 126}, {6, 445}}}; + {{1, 1108}, {2, 652}, {3, 273}, {4, 572}, {5, 126}, {6, 449}}}; static constexpr int conthist_offsets[6]{71, 106, -22, -20, 29, -74}; @@ -1892,14 +1892,14 @@ 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 * 792 / 1024) + 40; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << (bonus * 771 / 1024) + 40; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), - bonus * (bonus > 0 ? 1082 : 784) / 1024); + bonus * (bonus > 0 ? 979 : 842) / 1024); int pIndex = pawn_structure_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << (bonus * (bonus > 0 ? 705 : 450) / 1024) + 70; + << (bonus * (bonus > 0 ? 704 : 439) / 1024) + 70; } } From b6082ba750b923401709bc7e32b21f807f4f541f Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Fri, 25 Jul 2025 09:24:45 +0200 Subject: [PATCH 1088/1309] Simplify search functions according DRY principle Passed STC non-regression bounds https://tests.stockfishchess.org/tests/view/6870db4bfa93cf16d3bb286a LLR: 3.00 (-2.94,2.94) <-1.75,0.25> Total: 182016 W: 46735 L: 46673 D: 88608 Ptnml(0-2): 368, 19895, 50465, 19867, 413 closes https://github.com/official-stockfish/Stockfish/pull/6161 no functional change --- src/search.cpp | 43 ++++++++++++++----------------------------- src/search.h | 4 ++-- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 41177b81cdf..cd84eb2405b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -516,14 +516,21 @@ void Search::Worker::iterative_deepening() { } -void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st) { - do_move(pos, move, st, pos.gives_check(move)); +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss) { + do_move(pos, move, st, pos.gives_check(move), ss); } -void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck) { +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { + bool capture = pos.capture_stage(move); DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); nodes.fetch_add(1, std::memory_order_relaxed); accumulatorStack.push(dp); + if (ss != nullptr) + { + ss->currentMove = move; + ss->continuationHistory = &continuationHistory[ss->inCheck][capture][dp.pc][move.to_sq()]; + ss->continuationCorrectionHistory = &continuationCorrectionHistory[dp.pc][move.to_sq()]; + } } void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } @@ -695,7 +702,7 @@ Value Search::Worker::search( if (depth >= 8 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move) && !is_decisive(ttData.value)) { - do_move(pos, ttData.move, st); + do_move(pos, ttData.move, st, nullptr); Key nextPosKey = pos.key(); auto [ttHitNext, ttDataNext, ttWriterNext] = tt.probe(nextPosKey); undo_move(pos, ttData.move); @@ -920,13 +927,7 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); - do_move(pos, move, st); - - ss->currentMove = move; - ss->continuationHistory = - &continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; - ss->continuationCorrectionHistory = - &continuationCorrectionHistory[movedPiece][move.to_sq()]; + do_move(pos, move, st, ss); // Perform a preliminary qsearch to verify that the move holds value = -qsearch(pos, ss + 1, -probCutBeta, -probCutBeta + 1); @@ -1161,17 +1162,10 @@ Value Search::Worker::search( } // Step 16. Make the move - do_move(pos, move, st, givesCheck); + do_move(pos, move, st, givesCheck, ss); // Add extension to new depth newDepth += extension; - - // Update the current move (this must be done after singular extension search) - ss->currentMove = move; - ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; - ss->continuationCorrectionHistory = - &continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; // Decrease reduction for PvNodes (*Scaler) @@ -1658,16 +1652,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } // Step 7. Make and search the move - Piece movedPiece = pos.moved_piece(move); - - do_move(pos, move, st, givesCheck); - - // Update the current move - ss->currentMove = move; - ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; - ss->continuationCorrectionHistory = - &continuationCorrectionHistory[movedPiece][move.to_sq()]; + do_move(pos, move, st, givesCheck, ss); value = -qsearch(pos, ss + 1, -beta, -alpha); undo_move(pos, move); diff --git a/src/search.h b/src/search.h index e0b57e30be3..a43649e94eb 100644 --- a/src/search.h +++ b/src/search.h @@ -297,8 +297,8 @@ class Worker { private: void iterative_deepening(); - void do_move(Position& pos, const Move move, StateInfo& st); - void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck); + void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss); + void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); void do_null_move(Position& pos, StateInfo& st); void undo_move(Position& pos, const Move move); void undo_null_move(Position& pos); From 402602a70d29433cdc2eda1116d4e6cc68499b02 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Sat, 26 Jul 2025 17:51:20 +0200 Subject: [PATCH 1089/1309] Simplify static evaluation difference to move ordering logic Passed STC simplification bounds https://tests.stockfishchess.org/tests/view/686fcb091451bd6fb83082bc LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 762816 W: 196717 L: 197295 D: 368804 Ptnml(0-2): 2052, 90457, 196853, 90109, 1937 Passed LTC simplification bounds https://tests.stockfishchess.org/tests/view/68808d316e17e7fa3939b35d LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 44736 W: 11499 L: 11303 D: 21934 Ptnml(0-2): 24, 4766, 12586, 4974, 18 closes https://github.com/official-stockfish/Stockfish/pull/6176 bench: 2684407 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index cd84eb2405b..27a4bea670b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -808,7 +808,7 @@ Value Search::Worker::search( } // Use static evaluation difference to improve quiet move ordering - if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture && !ttHit) + if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1979, 1561) + 630; mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 935 / 1024; From 4fcfb0b4b7406004cd8adcf7de0b4acabe63c6c1 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Jul 2025 19:08:07 +0300 Subject: [PATCH 1090/1309] Tweak the logic for setting the improving flag Tweak the logic for setting the improving flag when the static evaluation is significantly higher than alpha. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 92320 W: 24057 L: 23664 D: 44599 Ptnml(0-2): 247, 10689, 23893, 11086, 245 https://tests.stockfishchess.org/tests/view/68860aba966ed85face248a1 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 281292 W: 72496 L: 71683 D: 137113 Ptnml(0-2): 135, 30289, 78995, 31082, 145 https://tests.stockfishchess.org/tests/view/6886168c966ed85face24962 closes https://github.com/official-stockfish/Stockfish/pull/6180 Bench: 3200439 --- src/search.cpp | 11 ++++++----- src/search.h | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 27a4bea670b..9855d12e178 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -520,14 +520,15 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac do_move(pos, move, st, pos.gives_check(move), ss); } -void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { - bool capture = pos.capture_stage(move); - DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); +void Search::Worker::do_move( + Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { + bool capture = pos.capture_stage(move); + DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); nodes.fetch_add(1, std::memory_order_relaxed); accumulatorStack.push(dp); if (ss != nullptr) { - ss->currentMove = move; + ss->currentMove = move; ss->continuationHistory = &continuationHistory[ss->inCheck][capture][dp.pc][move.to_sq()]; ss->continuationCorrectionHistory = &continuationCorrectionHistory[dp.pc][move.to_sq()]; } @@ -893,7 +894,7 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta + 87; + improving |= ss->staticEval >= beta + 94 * !PvNode; // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. diff --git a/src/search.h b/src/search.h index a43649e94eb..07fc743173f 100644 --- a/src/search.h +++ b/src/search.h @@ -298,7 +298,8 @@ class Worker { void iterative_deepening(); void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss); - void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); + void + do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); void do_null_move(Position& pos, StateInfo& st); void undo_move(Position& pos, const Move move); void undo_null_move(Position& pos); From 9045fdb227fe5303822a2af290448f490401668b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Jul 2025 23:14:28 +0300 Subject: [PATCH 1091/1309] Add depth condition Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 103360 W: 26941 L: 26530 D: 49889 Ptnml(0-2): 291, 11964, 26770, 12353, 302 https://tests.stockfishchess.org/tests/view/68863547966ed85face24a6f Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 115128 W: 29734 L: 29258 D: 56136 Ptnml(0-2): 48, 12406, 32182, 12878, 50 https://tests.stockfishchess.org/tests/view/68864f867b562f5f7b7312f2 closes https://github.com/official-stockfish/Stockfish/pull/6184 Bench: 2972498 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9855d12e178..24ba1c01845 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -826,7 +826,7 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; - if (priorReduction >= 3 && !opponentWorsening) + if (priorReduction >= (depth < 10 ? 1 : 3) && !opponentWorsening) depth++; if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 177) depth--; From 2e2d2778fcae019f8cf53acedd5cb7b5a44696fb Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:07:29 +0200 Subject: [PATCH 1092/1309] increase futility value when capturing last moved piece Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 204320 W: 53059 L: 52500 D: 98761 Ptnml(0-2): 551, 23874, 52778, 24379, 578 https://tests.stockfishchess.org/tests/view/688618ae966ed85face24976 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 138582 W: 35748 L: 35225 D: 67609 Ptnml(0-2): 62, 14874, 38900, 15389, 66 https://tests.stockfishchess.org/tests/view/68862717966ed85face249f2 closes https://github.com/official-stockfish/Stockfish/pull/6190 Bench: 2894413 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 24ba1c01845..c517fd496a3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1040,8 +1040,9 @@ Value Search::Worker::search( // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 222 + 216 * lmrDepth - + PieceValue[capturedPiece] + 131 * captHist / 1024; + Value futilityValue = ss->staticEval + 225 + 220 * lmrDepth + + 275 * (move.to_sq() == prevSq) + PieceValue[capturedPiece] + + 131 * captHist / 1024; if (futilityValue <= alpha) continue; } From a9b7638e966bf59b6216d319d57f65bc16395467 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 27 Jul 2025 19:13:54 +0300 Subject: [PATCH 1093/1309] Simplify newDepth modification formulas Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 21856 W: 5709 L: 5471 D: 10676 Ptnml(0-2): 77, 2466, 5589, 2734, 62 https://tests.stockfishchess.org/tests/view/68862ea4966ed85face24a27 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 108828 W: 27893 L: 27763 D: 53172 Ptnml(0-2): 59, 11860, 30435, 12012, 48 https://tests.stockfishchess.org/tests/view/68863046966ed85face24a3b closes https://github.com/official-stockfish/Stockfish/pull/6181 Bench: 3069051 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c517fd496a3..b0efea869cf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1229,7 +1229,7 @@ Value Search::Worker::search( // Do a full-depth search when reduced LMR search fails high // (*Scaler) Usually doing more shallower searches // doesn't scale well to longer TCs - if (value > alpha && d < newDepth) + if (value > alpha) { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. @@ -1244,8 +1244,6 @@ Value Search::Worker::search( // Post LMR continuation history updates update_continuation_histories(ss, movedPiece, move.to_sq(), 1412); } - else if (value > alpha && value < bestValue + 9) - newDepth--; } // Step 18. Full-depth search when LMR is skipped From 1d8f118c3e8931676f5dbe967e227c7eedcc4449 Mon Sep 17 00:00:00 2001 From: 87 Date: Sun, 27 Jul 2025 20:15:24 +0100 Subject: [PATCH 1094/1309] Remove unnecessary deque allocation in perft closes https://github.com/official-stockfish/Stockfish/pull/6182 No functional change. --- src/perft.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/perft.h b/src/perft.h index 229debd4078..e249a8e49d6 100644 --- a/src/perft.h +++ b/src/perft.h @@ -56,9 +56,9 @@ uint64_t perft(Position& pos, Depth depth) { } inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) { - StateListPtr states(new std::deque(1)); - Position p; - p.set(fen, isChess960, &states->back()); + StateInfo st; + Position p; + p.set(fen, isChess960, &st); return perft(p, depth); } From ab83d320b87f75de6ff01999193ec3f223b10fb2 Mon Sep 17 00:00:00 2001 From: Styx <164851643+styxdoto@users.noreply.github.com> Date: Sun, 27 Jul 2025 17:45:54 -0700 Subject: [PATCH 1095/1309] Simplify NMP condition As this is tried only for cutNode, and all children of cutNodes will be nonCutNodes, the guard condition is redundant. Simplification just removes the unnecessary condition (always true). closes https://github.com/official-stockfish/Stockfish/pull/6187 No functional change --- AUTHORS | 1 + src/search.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 76b4f71ca1d..273cab33b07 100644 --- a/AUTHORS +++ b/AUTHORS @@ -231,6 +231,7 @@ Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Stephen Touset (stouset) +Styx (styxdoto) Syine Mineta (MinetaS) Taras Vuk (TarasVuk) Thanar2 diff --git a/src/search.cpp b/src/search.cpp index b0efea869cf..71a46ea7bd5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -856,10 +856,11 @@ Value Search::Worker::search( } // Step 9. Null move search with verification search - if (cutNode && (ss - 1)->currentMove != Move::null() - && ss->staticEval >= beta - 19 * depth + 403 && !excludedMove && pos.non_pawn_material(us) - && ss->ply >= nmpMinPly && !is_loss(beta)) + if (cutNode && ss->staticEval >= beta - 19 * depth + 403 && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { + assert((ss - 1)->currentMove != Move::null()); + // Null move dynamic reduction based on depth Depth R = 7 + depth / 3; From 525a5749bc873a55893d8b4d4041b49ce074f407 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 27 Jul 2025 10:06:40 -0400 Subject: [PATCH 1096/1309] Simplify extra continuation history updates Passed simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 254464 W: 65774 L: 65793 D: 122897 Ptnml(0-2): 699, 30087, 65638, 30150, 658 https://tests.stockfishchess.org/tests/view/68863283966ed85face24a59 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 144750 W: 37111 L: 37018 D: 70621 Ptnml(0-2): 79, 15676, 40764, 15785, 71 https://tests.stockfishchess.org/tests/view/6886655f7b562f5f7b731359 closes https://github.com/official-stockfish/Stockfish/pull/6189 Bench: 3071078 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 71a46ea7bd5..743f4bce749 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1856,16 +1856,13 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { static constexpr std::array conthist_bonuses = { {{1, 1108}, {2, 652}, {3, 273}, {4, 572}, {5, 126}, {6, 449}}}; - static constexpr int conthist_offsets[6]{71, 106, -22, -20, 29, -74}; - for (const auto [i, weight] : conthist_bonuses) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (((ss - i)->currentMove).is_ok()) - (*(ss - i)->continuationHistory)[pc][to] - << (bonus * weight / 1024) + conthist_offsets[i - 1]; + (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 80 * (i < 2); } } From 64e8e1b247367018ebbce15f0d7cf5a56c7eaf56 Mon Sep 17 00:00:00 2001 From: aronpetkovski Date: Sun, 27 Jul 2025 13:07:47 +0200 Subject: [PATCH 1097/1309] Simplify LMR extension limit formula This patch simplifies the maximum depth formula that reductions in LMR can allow. Passed STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 35616 W: 9358 L: 9139 D: 17119 Ptnml(0-2): 101, 4051, 9278, 4284, 94 https://tests.stockfishchess.org/tests/view/688608cf966ed85face24882 Passed LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 22284 W: 5833 L: 5613 D: 10838 Ptnml(0-2): 8, 2367, 6173, 2585, 9 https://tests.stockfishchess.org/tests/view/68860d7f966ed85face248d5 closes https://github.com/official-stockfish/Stockfish/pull/6191 Bench: 2902492 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 743f4bce749..6503cc14d80 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1219,9 +1219,7 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r / 1024, - newDepth + !allNode + (PvNode && !bestMove))) - + PvNode; + Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + 1 + PvNode)) + PvNode; ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From 83071917e903581542670a87a036f5d02e13fb82 Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 27 Jul 2025 16:09:01 -0700 Subject: [PATCH 1098/1309] Optimize find_nnz() using VBMI2 about a 0.7% speedup for the x86-64-avx512icl ARCH closes https://github.com/official-stockfish/Stockfish/pull/6186 No functional change --- .../layers/affine_transform_sparse_input.h | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index e77c98f8c66..069210304b1 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -82,7 +82,36 @@ void find_nnz(const std::int32_t* RESTRICT input, std::uint16_t* RESTRICT out, IndexType& count_out) { - #ifdef USE_AVX512 + #if defined(USE_AVX512ICL) + + constexpr IndexType SimdWidthIn = 16; // 512 bits / 32 bits + constexpr IndexType SimdWidthOut = 32; // 512 bits / 16 bits + constexpr IndexType NumChunks = InputDimensions / SimdWidthOut; + const __m512i increment = _mm512_set1_epi16(SimdWidthOut); + __m512i base = _mm512_set_epi16(31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + IndexType count = 0; + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m512i inputV0 = _mm512_load_si512(input + i * 2 * SimdWidthIn); + const __m512i inputV1 = _mm512_load_si512(input + i * 2 * SimdWidthIn + SimdWidthIn); + + // Get a bitmask and gather non zero indices + const __mmask32 nnzMask = _mm512_kunpackw(_mm512_test_epi32_mask(inputV1, inputV1), + _mm512_test_epi32_mask(inputV0, inputV0)); + + // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 + __m512i nnz = _mm512_maskz_compress_epi16(nnzMask, base); + _mm512_storeu_epi16(out + count, nnz); + + count += popcount(nnzMask); + base = _mm512_add_epi16(base, increment); + } + count_out = count; + + #elif defined(USE_AVX512) + constexpr IndexType SimdWidth = 16; // 512 bits / 32 bits constexpr IndexType NumChunks = InputDimensions / SimdWidth; const __m512i increment = _mm512_set1_epi32(SimdWidth); From fdd9c34b75767aaebe8d348f3393735af07671a8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 29 Jul 2025 13:51:13 +0300 Subject: [PATCH 1099/1309] Re-adding ttHit condition, but this time in the inner block Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 150240 W: 39305 L: 38819 D: 72116 Ptnml(0-2): 536, 17541, 38494, 17999, 550 https://tests.stockfishchess.org/tests/view/6887ce577b562f5f7b731a85 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 159120 W: 41025 L: 40461 D: 77634 Ptnml(0-2): 96, 16974, 44866, 17518, 106 https://tests.stockfishchess.org/tests/view/6887f0ba7b562f5f7b731c5d closes https://github.com/official-stockfish/Stockfish/pull/6193 bench: 3498926 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 6503cc14d80..e3044bf6a26 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -813,7 +813,7 @@ Value Search::Worker::search( { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1979, 1561) + 630; mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 935 / 1024; - if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) + if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] << bonus * 1428 / 1024; } From 32cb78d782b5918fb658622a2690ba871dc5bb06 Mon Sep 17 00:00:00 2001 From: MinetaS Date: Wed, 30 Jul 2025 15:55:35 +0900 Subject: [PATCH 1100/1309] Increase CI timeouts for perft might fix CI failures on macOS instances closes https://github.com/official-stockfish/Stockfish/pull/6194 No functional change --- tests/perft.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/perft.sh b/tests/perft.sh index 97c462c5721..bb32bee5679 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -16,7 +16,7 @@ EXPECT_SCRIPT=$(mktemp) cat << 'EOF' > $EXPECT_SCRIPT #!/usr/bin/expect -f -set timeout 30 +set timeout 120 lassign [lrange $argv 0 4] pos depth result chess960 logfile log_file -noappend $logfile spawn ./stockfish From 9034730a5a0f1f311dd21ef5afd5b3b9c86d9f83 Mon Sep 17 00:00:00 2001 From: "J. Oster" Date: Wed, 30 Jul 2025 11:21:06 +0200 Subject: [PATCH 1101/1309] Display upper/lowerbound before wdl Bound info belongs directly to score info closes https://github.com/official-stockfish/Stockfish/pull/6195 No functional change. --- src/uci.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 500e881843c..9b1dd865cab 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -630,12 +630,12 @@ void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) { << " multipv " << info.multiPV // << " score " << format_score(info.score); // - if (showWDL) - ss << " wdl " << info.wdl; - if (!info.bound.empty()) ss << " " << info.bound; + if (showWDL) + ss << " wdl " << info.wdl; + ss << " nodes " << info.nodes // << " nps " << info.nps // << " hashfull " << info.hashfull // From cef551092c7d4ca4d9a2d6402f412332b1f3e88a Mon Sep 17 00:00:00 2001 From: aronpetkovski Date: Sun, 27 Jul 2025 22:39:12 +0200 Subject: [PATCH 1102/1309] Only do IIR in minimally reduced nodes Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 164256 W: 42799 L: 42299 D: 79158 Ptnml(0-2): 451, 19305, 42087, 19863, 422 https://tests.stockfishchess.org/tests/view/68868e9b7b562f5f7b731615 Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 279396 W: 71871 L: 71062 D: 136463 Ptnml(0-2): 126, 30117, 78404, 30924, 127 https://tests.stockfishchess.org/tests/view/6886a4977b562f5f7b731663 closes https://github.com/official-stockfish/Stockfish/pull/6196 Bench: 3490609 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e3044bf6a26..9775e46b188 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -813,7 +813,8 @@ Value Search::Worker::search( { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1979, 1561) + 630; mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 935 / 1024; - if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) + if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN + && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] << bonus * 1428 / 1024; } @@ -900,7 +901,7 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. // (*Scaler) Especially if they make IIR less aggressive. - if (!allNode && depth >= 6 && !ttData.move) + if (!allNode && depth >= 6 && !ttData.move && priorReduction <= 3) depth--; // Step 11. ProbCut From 57b32f3e60e596fe1d2452a9548baa5b292fc724 Mon Sep 17 00:00:00 2001 From: CSTENTOR Date: Wed, 30 Jul 2025 15:35:57 +0200 Subject: [PATCH 1103/1309] Make Resetting the Aspiration Window after FailLow more aggressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sets beta=(3alpha+beta)/4 when failLow. Before it was beta=(alpha+beta)/2, even though in theory we should be getting an upper bound from a failLow, we can’t trust the score that caused the failLow. Therefore beta=alpha doesn’t work. I made the buffer between the new beta to the bestValue that caused the failLow smaller. passed STC: https://tests.stockfishchess.org/tests/view/688674947b562f5f7b7315b5 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 119616 W: 31253 L: 30818 D: 57545 Ptnml(0-2): 313, 14028, 30724, 14397, 346 passed LTC: https://tests.stockfishchess.org/tests/view/6887e0ad7b562f5f7b731afc LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 96978 W: 24905 L: 24466 D: 47607 Ptnml(0-2): 59, 10376, 27183, 10809, 62 closes https://github.com/official-stockfish/Stockfish/pull/6197 bench: 3085519 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 273cab33b07..15614effd3e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -57,6 +57,7 @@ Ciekce clefrks Clemens L. (rn5f107s2) Cody Ho (aesrentai) +CSTENTOR Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) diff --git a/src/search.cpp b/src/search.cpp index 9775e46b188..268f9e2cc8d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -371,7 +371,7 @@ void Search::Worker::iterative_deepening() { // otherwise exit the loop. if (bestValue <= alpha) { - beta = (alpha + beta) / 2; + beta = (3 * alpha + beta) / 4; alpha = std::max(bestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; From cd52859bd5783337ae03ba0e73c472e55b3f1243 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 28 Jul 2025 19:11:04 -0400 Subject: [PATCH 1104/1309] Simplify improving term Set improving whenever ss->staticEval >= beta Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 38016 W: 10122 L: 9900 D: 17994 Ptnml(0-2): 143, 4388, 9747, 4564, 166 https://tests.stockfishchess.org/tests/view/688803917b562f5f7b7322eb Passed simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 21648 W: 5594 L: 5375 D: 10679 Ptnml(0-2): 12, 2241, 6096, 2466, 9 https://tests.stockfishchess.org/tests/view/688820397b562f5f7b73235d closes https://github.com/official-stockfish/Stockfish/pull/6198 Bench: 3576884 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 268f9e2cc8d..e151cc7963c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -896,7 +896,7 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta + 94 * !PvNode; + improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. From 62b958f5170bcf34e0cfa1abfa909b7de382c141 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 1 Aug 2025 21:01:59 +0300 Subject: [PATCH 1105/1309] Remove code that is not used anywhere closes https://github.com/official-stockfish/Stockfish/pull/6201 No functional change --- src/bitboard.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index f959bcb860c..27ef89c0e03 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -179,11 +179,6 @@ inline Bitboard between_bb(Square s1, Square s2) { return BetweenBB[s1][s2]; } -// Returns true if the squares s1, s2 and s3 are aligned either on a -// straight or on a diagonal line. -inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; } - - // distance() functions return the distance between x and y, defined as the // number of steps for a king in x to reach y. From a37b38bdf0ad964363a7fef4b278ccc761a52c52 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 2 Aug 2025 14:27:51 +0300 Subject: [PATCH 1106/1309] Add "d < newDepth" condition for doDeeperSearch Add "d < newDepth" condition for doDeeperSearch Passed STC: LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 66144 W: 17512 L: 17148 D: 31484 Ptnml(0-2): 220, 7726, 16833, 8056, 237 https://tests.stockfishchess.org/tests/view/6887cce67b562f5f7b731a79 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 411240 W: 106103 L: 105023 D: 200114 Ptnml(0-2): 256, 44249, 115566, 45257, 292 https://tests.stockfishchess.org/tests/view/6887ec527b562f5f7b731c37 closes https://github.com/official-stockfish/Stockfish/pull/6204 Bench: 2917036 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e151cc7963c..baf6dfb626a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1233,7 +1233,7 @@ Value Search::Worker::search( { // 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 + 43 + 2 * newDepth); + const bool doDeeperSearch = d < newDepth && value > (bestValue + 43 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; From a6e34f128f185d9772e6b3ecc7fdbff448444718 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 2 Aug 2025 13:59:01 +0200 Subject: [PATCH 1107/1309] Fix icx profile-build fixes the issue pointed out in https://github.com/official-stockfish/Stockfish/pull/6202 closes https://github.com/official-stockfish/Stockfish/pull/6203 No functional change --- src/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 40dceae0beb..047a9e71c9d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -590,6 +590,10 @@ endif # Locate the version in the same directory as the compiler used, # with fallback to a generic one if it can't be located LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))llvm-profdata +# for icx +ifeq ($(wildcard $(LLVM_PROFDATA)),) + LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))/compiler/llvm-profdata +endif ifeq ($(wildcard $(LLVM_PROFDATA)),) LLVM_PROFDATA := llvm-profdata endif @@ -1137,7 +1141,7 @@ icx-profile-make: all icx-profile-use: - $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw + $(XCRUN) $(LLVM_PROFDATA) merge -output=stockfish.profdata *.profraw $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ EXTRALDFLAGS='-fprofile-use ' \ From 8e733d68fd8bcbb042af23d3c3f7375c44416b25 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 2 Aug 2025 19:17:22 +0300 Subject: [PATCH 1108/1309] Refactor: Simplify pawn structure indexing Refactoring the mechanism for calculating pawn structure hash indices and make it consistent with the rest. closes https://github.com/official-stockfish/Stockfish/pull/6205 No functional change --- src/history.h | 12 +++++------- src/movepick.cpp | 2 +- src/search.cpp | 18 ++++++++---------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/history.h b/src/history.h index 46914789e59..b2abd9cc004 100644 --- a/src/history.h +++ b/src/history.h @@ -44,14 +44,12 @@ static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); -enum PawnHistoryType { - Normal, - Correction -}; +inline int pawn_history_index(const Position& pos) { + return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); +} -template -inline int pawn_structure_index(const Position& pos) { - return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); +inline int pawn_correction_history_index(const Position& pos) { + return pos.pawn_key() & (CORRECTION_HISTORY_SIZE - 1); } inline int minor_piece_index(const Position& pos) { diff --git a/src/movepick.cpp b/src/movepick.cpp index 79b6f55a2bf..eb7c45837a6 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -158,7 +158,7 @@ ExtMove* MovePicker::score(MoveList& ml) { { // histories m.value = 2 * (*mainHistory)[us][m.from_to()]; - m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to]; + m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to]; diff --git a/src/search.cpp b/src/search.cpp index baf6dfb626a..3c17042ca7b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -75,7 +75,7 @@ using SearchedList = ValueList; 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[pawn_structure_index(pos)][us]; + const auto pcv = w.pawnCorrectionHistory[pawn_correction_history_index(pos)][us]; const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; const auto wnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us]; const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; @@ -101,8 +101,7 @@ void update_correction_history(const Position& pos, static constexpr int nonPawnWeight = 165; - workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 128 / 128; + workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 153 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; @@ -815,7 +814,7 @@ Value Search::Worker::search( mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 935 / 1024; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << bonus * 1428 / 1024; } @@ -823,8 +822,7 @@ Value Search::Worker::search( // bigger than the previous static evaluation at our turn (if we were in // check at our previous move we go back until we weren't in check) and is // false otherwise. The improving flag is used in various pruning heuristics. - improving = ss->staticEval > (ss - 2)->staticEval; - + improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; if (priorReduction >= (depth < 10 ? 1 : 3) && !opponentWorsening) @@ -1069,7 +1067,7 @@ Value Search::Worker::search( { int history = (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; + + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning if (history < -4361 * depth) @@ -1423,7 +1421,7 @@ Value Search::Worker::search( mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 224 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << scaledBonus * 1127 / 32768; } @@ -1642,7 +1640,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Continuation history based pruning if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)][move.to_sq()] + + pawnHistory[pawn_history_index(pos)][pos.moved_piece(move)][move.to_sq()] <= 5868) continue; @@ -1879,7 +1877,7 @@ void update_quiet_histories( update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * (bonus > 0 ? 979 : 842) / 1024); - int pIndex = pawn_structure_index(pos); + int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << (bonus * (bonus > 0 ? 704 : 439) / 1024) + 70; } From 5d1505d8c7475fed854e51512113aa8238b7dafd Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 3 Aug 2025 00:33:14 +0300 Subject: [PATCH 1109/1309] Reintroduce reduction term in LMR for cutnodes This term increases reduction for cutnodes without tt move, term was simplified away recently. Passed STC: https://tests.stockfishchess.org/tests/view/688882007b562f5f7b73262f LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 187616 W: 49367 L: 48824 D: 89425 Ptnml(0-2): 710, 21898, 48034, 22471, 695 Passed LTC: https://tests.stockfishchess.org/tests/view/688c71f4502b34dd5e71139a LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 140280 W: 36280 L: 35750 D: 68250 Ptnml(0-2): 79, 15175, 39121, 15667, 98 closes https://github.com/official-stockfish/Stockfish/pull/6206 Bench: 2996732 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 3c17042ca7b..bf454bbe839 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1183,7 +1183,7 @@ Value Search::Worker::search( // Increase reduction for cut nodes if (cutNode) - r += 3000; + r += 3000 + 1024 * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) From 14a2e50a3d3cb7eafdc5a33256496040d90cf1cd Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 30 Jul 2025 17:07:08 -0700 Subject: [PATCH 1110/1309] Simplify key after Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 534016 W: 139438 L: 139771 D: 254807 Ptnml(0-2): 2098, 63469, 136136, 63278, 2027 https://tests.stockfishchess.org/tests/view/688ac64d502b34dd5e7110a4 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 43980 W: 11346 L: 11149 D: 21485 Ptnml(0-2): 31, 4689, 12353, 4886, 31 https://tests.stockfishchess.org/tests/view/688ea8a47d68fe4f7f130eb3 closes https://github.com/official-stockfish/Stockfish/pull/6207 Bench: 2884232 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bf454bbe839..32ad708c877 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -702,10 +702,10 @@ Value Search::Worker::search( if (depth >= 8 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move) && !is_decisive(ttData.value)) { - do_move(pos, ttData.move, st, nullptr); + pos.do_move(ttData.move, st); Key nextPosKey = pos.key(); auto [ttHitNext, ttDataNext, ttWriterNext] = tt.probe(nextPosKey); - undo_move(pos, ttData.move); + pos.undo_move(ttData.move); // Check that the ttValue after the tt move would also trigger a cutoff if (!is_valid(ttDataNext.value)) From 377a3a5269acc9a7961135e7f0c2232081047e38 Mon Sep 17 00:00:00 2001 From: Jost Triller Date: Wed, 30 Jul 2025 00:37:52 +0200 Subject: [PATCH 1111/1309] Depth dependent reduction threshold when full-depth re-search STC: https://tests.stockfishchess.org/tests/view/68894d577b562f5f7b73273b LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 155072 W: 40651 L: 40150 D: 74271 Ptnml(0-2): 609, 18271, 39292, 18738, 626 LTC: https://tests.stockfishchess.org/tests/view/688c2705502b34dd5e71127a LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 321012 W: 82891 L: 81995 D: 156126 Ptnml(0-2): 227, 34421, 90285, 35375, 198 This commit was generated using qwen3-235b-a22b-thinking-2507: Prompt: https://rentry.co/iqtaoht7 Reasoning/thinking: https://rentry.co/wm6t9hye closes https://github.com/official-stockfish/Stockfish/pull/6210 Bench: 2983938 --- AUTHORS | 1 + src/search.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 15614effd3e..6bf0745ad98 100644 --- a/AUTHORS +++ b/AUTHORS @@ -125,6 +125,7 @@ Jonathan McDermid (jonathanmcdermid) Joost VandeVondele (vondele) Joseph Ellis (jhellis3) Joseph R. Prostko +Jost Triller (tsoj) Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery diff --git a/src/search.cpp b/src/search.cpp index 32ad708c877..b5e6e1be9ef 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1251,9 +1251,13 @@ Value Search::Worker::search( if (!ttData.move) r += 1139; + const int threshold1 = depth <= 4 ? 2000 : 3200; + const int threshold2 = depth <= 4 ? 3500 : 4600; + // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3200) - (r > 4600 && newDepth > 2), !cutNode); + newDepth - (r > threshold1) - (r > threshold2 && newDepth > 2), + !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, From 94175524b1c06f1a4ce80a5640272a15120dcbbd Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Thu, 31 Jul 2025 21:59:01 +0200 Subject: [PATCH 1112/1309] Only set ep square if ep capture is possible for positions such as: position fen rr6/2q2p1k/2P1b1pp/bB2P1n1/R2B2PN/p4P1P/P1Q4K/1R6 b - - 2 38 moves f7f5 position fen 8/p2r1pK1/6p1/1kp1P1P1/2p5/2P5/8/4R3 b - - 0 43 moves f7f5 position fen 4k3/4p3/2b3b1/3P1P2/4K3/8/8/8 b - - moves e7e5 ep is not possible for various reasons. Do not set the ep square and the do not change the zobrist key, as if ep were possible. This fixes 3-fold detection, which could in rare cases be observed as a warning generated by fastchess for PVs that continued beyond an actual 3-fold. Also fixes wrong evals for certain moves e.g. a2a1 for position fen 4k3/1b2p2b/8/3P1P2/4K3/8/8/r7 b - - 0 1 moves e7e5 e4e3 a1a2 e3e4 a2a1 e4f3 a1a2 f3e4 fixes https://github.com/official-stockfish/Stockfish/issues/6138 originally written and tested by rn5f107s2, with small buglet as https://tests.stockfishchess.org/tests/view/685af90843ce022d15794400 failed STC LLR: -2.93 (-2.94,2.94) <-1.75,0.25> Total: 130688 W: 33986 L: 34362 D: 62340 Ptnml(0-2): 414, 13292, 38302, 12928, 408 https://tests.stockfishchess.org/tests/view/688bcb35502b34dd5e7111c9 passed LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 99018 W: 25402 L: 25273 D: 48343 Ptnml(0-2): 54, 9097, 31089, 9204, 65 https://tests.stockfishchess.org/tests/view/688c613c502b34dd5e7112d3 https://github.com/official-stockfish/Stockfish/pull/6211 Bench: 2946135 Co-authored-by: Joost VandeVondele --- src/Makefile | 2 +- src/position.cpp | 77 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/src/Makefile b/src/Makefile index 047a9e71c9d..cec623f5217 100644 --- a/src/Makefile +++ b/src/Makefile @@ -903,7 +903,7 @@ help: echo "Supported archs:" && \ echo "" && \ echo "native > select the best architecture for the host processor (default)" && \ - echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lane or AMD Zen 4" && \ + echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lake or AMD Zen 4" && \ echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \ echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \ echo "x86-64-avx512 > x86 64-bit with avx512 support" && \ diff --git a/src/position.cpp b/src/position.cpp index 5e2c2782269..4ac1369d4b9 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -717,6 +717,8 @@ DirtyPiece Position::do_move(Move m, Piece pc = piece_on(from); Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); + bool checkEP = false; + DirtyPiece dp; dp.pc = pc; dp.from = from; @@ -804,21 +806,14 @@ DirtyPiece Position::do_move(Move m, // Move the piece. The tricky Chess960 castling is handled earlier if (m.type_of() != CASTLING) - { - move_piece(from, to); - } // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) { - // Set en passant square if the moved pawn can be captured - if ((int(to) ^ int(from)) == 16 - && (attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN))) - { - st->epSquare = to - pawn_push(us); - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - } + // Check later if the en passant square needs to be set + if ((int(to) ^ int(from)) == 16) + checkEP = true; else if (m.type_of() == PROMOTION) { @@ -863,11 +858,6 @@ DirtyPiece Position::do_move(Move m, st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - // Update the key with the final value - st->key = k; - if (tt) - prefetch(tt->first_entry(key())); - // Set capture piece st->capturedPiece = captured; @@ -879,6 +869,63 @@ DirtyPiece Position::do_move(Move m, // Update king attacks used for fast check detection set_check_info(); + // Accurate e.p. info is needed for correct zobrist key generation and 3-fold checking + while (checkEP) + { + auto updateEpSquare = [&] { + st->epSquare = to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + }; + + Bitboard pawns = attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN); + + // If there are no pawns attacking the ep square, ep is not possible + if (!pawns) + break; + + // If there are checkers other than the to be captured pawn, ep is never legal + if (checkers() & ~square_bb(to)) + break; + + if (more_than_one(pawns)) + { + // If there are two pawns potentially being abled to capture and at least one + // is not pinned, ep is legal as there are no horizontal exposed checks + if (!more_than_one(blockers_for_king(them) & pawns)) + { + updateEpSquare(); + break; + } + + // If there is no pawn on our king's file, and thus both pawns are pinned + // by bishops, ep is not legal as the king square must be in front of the to square. + // And because the ep square and the king are not on a common diagonal, either ep capture + // would expose the king to a check from one of the bishops + if (!(file_bb(square(them)) & pawns)) + break; + + // Otherwise remove the pawn on the king file, as an ep capture by it can never be legal and the + // check below relies on there only being one pawn + pawns &= ~file_bb(square(them)); + } + + Square ksq = square(them); + Square capsq = to; + Bitboard occupied = (pieces() ^ lsb(pawns) ^ capsq) | (to - pawn_push(us)); + + // If our king is not attacked after making the move, ep is legal. + if (!(attacks_bb(ksq, occupied) & pieces(us, QUEEN, ROOK)) + && !(attacks_bb(ksq, occupied) & pieces(us, QUEEN, BISHOP))) + updateEpSquare(); + + break; + } + + // Update the key with the final value + st->key = k; + if (tt) + prefetch(tt->first_entry(key())); + // Calculate the repetition info. It is the ply distance from the previous // occurrence of the same position, negative in the 3-fold case, or zero // if the position was not repeated. From b177b7394a5506b5a118eec96d5dd80ff3a10498 Mon Sep 17 00:00:00 2001 From: KazApps Date: Tue, 29 Jul 2025 13:12:44 +0900 Subject: [PATCH 1113/1309] Separate bonus/malus parameters for quiet and capture moves Parameters were tuned on 60k STC games. Passed STC LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 16928 W: 4570 L: 4277 D: 8081 Ptnml(0-2): 57, 1905, 4270, 2152, 80 https://tests.stockfishchess.org/tests/view/688b4486502b34dd5e711122 Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 43602 W: 11373 L: 11041 D: 21188 Ptnml(0-2): 23, 4657, 12116, 4975, 30 https://tests.stockfishchess.org/tests/view/688c0eb5502b34dd5e71124b Passed non-regression VLTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 181016 W: 46335 L: 46281 D: 88400 Ptnml(0-2): 33, 18177, 54034, 18231, 33 https://tests.stockfishchess.org/tests/view/688e0a77f17748b4d23c822b closes https://github.com/official-stockfish/Stockfish/pull/6200 bench: 3435529 --- src/search.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b5e6e1be9ef..39d9862be25 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -129,8 +129,7 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move TTMove, - int moveCount); + Move TTMove); } // namespace @@ -1400,7 +1399,7 @@ Value Search::Worker::search( else if (bestMove) { update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - ttData.move, moveCount); + ttData.move); if (!PvNode) ttMoveHistory << (bestMove == ttData.move ? 811 : -848); } @@ -1811,42 +1810,44 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move ttMove, - int moveCount) { + Move ttMove) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(142 * depth - 88, 1501) + 318 * (bestMove == ttMove); - int malus = std::min(757 * depth - 172, 2848) - 30 * moveCount; + int quietBonus = std::min(170 * depth - 87, 1598) + 332 * (bestMove == ttMove); + int quietMalus = std::min(743 * depth - 180, 2287) - 33 * quietsSearched.size(); + int captureBonus = std::min(124 * depth - 62, 1245) + 336 * (bestMove == ttMove); + int captureMalus = std::min(708 * depth - 148, 2287) - 29 * capturesSearched.size(); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1054 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, quietBonus * 978 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1388 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -quietMalus * 1115 / 1024); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1235 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << captureBonus * 1288 / 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 * 595 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, + -captureMalus * 622 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1354 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -captureMalus * 1431 / 1024; } } From 303fe9a1643b40a667923cba122e36beffde288c Mon Sep 17 00:00:00 2001 From: Stockfisher69 Date: Sat, 2 Aug 2025 23:01:50 +0200 Subject: [PATCH 1114/1309] Simplification: Futility pruning for captures tested in final form as simplication passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 113682 W: 29199 L: 29074 D: 55409 Ptnml(0-2): 68, 12359, 31859, 12490, 65 https://tests.stockfishchess.org/tests/view/688e85a17d68fe4f7f130e24 earlier test, equivalent: passed STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 120224 W: 31621 L: 31176 D: 57427 Ptnml(0-2): 415, 14167, 30567, 14484, 479 https://tests.stockfishchess.org/tests/view/68888bdd7b562f5f7b732643 closes https://github.com/official-stockfish/Stockfish/pull/6209 Bench: 2661621 --- AUTHORS | 1 + src/search.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6bf0745ad98..e606e5a8d41 100644 --- a/AUTHORS +++ b/AUTHORS @@ -233,6 +233,7 @@ Stefano Di Martino (StefanoD) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Stephen Touset (stouset) +Stockfisher69 Styx (styxdoto) Syine Mineta (MinetaS) Taras Vuk (TarasVuk) diff --git a/src/search.cpp b/src/search.cpp index 39d9862be25..b807ae7d123 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1039,9 +1039,10 @@ Value Search::Worker::search( // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 225 + 220 * lmrDepth - + 275 * (move.to_sq() == prevSq) + PieceValue[capturedPiece] - + 131 * captHist / 1024; + + Value futilityValue = ss->staticEval + 232 + 224 * lmrDepth + + PieceValue[capturedPiece] + 131 * captHist / 1024; + if (futilityValue <= alpha) continue; } From 125a0cfe7b37064f6200ed526b96b5361a974317 Mon Sep 17 00:00:00 2001 From: Niklas Fiekas Date: Thu, 7 Aug 2025 22:57:01 +0200 Subject: [PATCH 1115/1309] Build x86-64-avx512icl in CI closes https://github.com/official-stockfish/Stockfish/pull/6217 No functional change --- .github/ci/matrix.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index c414c51fcce..57c97686ccf 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -63,6 +63,7 @@ "x86-64-avx512", "x86-64-vnni256", "x86-64-vnni512", + "x86-64-avx512icl", "apple-silicon", "armv8", "armv8-dotprod" @@ -116,6 +117,12 @@ "os": "macos-14" } }, + { + "binaries": "x86-64-avx512icl", + "config": { + "os": "macos-14" + } + }, { "binaries": "x86-64-avxvnni", "config": { @@ -140,6 +147,12 @@ "os": "macos-13" } }, + { + "binaries": "x86-64-avx512icl", + "config": { + "os": "macos-13" + } + }, { "binaries": "x86-64", "config": { @@ -188,6 +201,12 @@ "os": "windows-11-arm" } }, + { + "binaries": "x86-64-avx512icl", + "config": { + "os": "windows-11-arm" + } + }, { "binaries": "apple-silicon", "config": { From 5464f7b114c77fe5ccd7e3058fe5a92c3e7fc08a Mon Sep 17 00:00:00 2001 From: mstembera Date: Sun, 3 Aug 2025 19:10:31 -0700 Subject: [PATCH 1116/1309] Remove an instruction from find_nnz() closes https://github.com/official-stockfish/Stockfish/pull/6212 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 069210304b1..685cf8ea1cf 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -88,8 +88,9 @@ void find_nnz(const std::int32_t* RESTRICT input, constexpr IndexType SimdWidthOut = 32; // 512 bits / 16 bits constexpr IndexType NumChunks = InputDimensions / SimdWidthOut; const __m512i increment = _mm512_set1_epi16(SimdWidthOut); - __m512i base = _mm512_set_epi16(31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, - 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + __m512i base = _mm512_set_epi16( // Same permute order as _mm512_packus_epi32() + 31, 30, 29, 28, 15, 14, 13, 12, 27, 26, 25, 24, 11, 10, 9, 8, 23, 22, 21, 20, 7, 6, 5, 4, 19, + 18, 17, 16, 3, 2, 1, 0); IndexType count = 0; for (IndexType i = 0; i < NumChunks; ++i) @@ -98,8 +99,8 @@ void find_nnz(const std::int32_t* RESTRICT input, const __m512i inputV1 = _mm512_load_si512(input + i * 2 * SimdWidthIn + SimdWidthIn); // Get a bitmask and gather non zero indices - const __mmask32 nnzMask = _mm512_kunpackw(_mm512_test_epi32_mask(inputV1, inputV1), - _mm512_test_epi32_mask(inputV0, inputV0)); + const __m512i inputV01 = _mm512_packus_epi32(inputV0, inputV1); + const __mmask32 nnzMask = _mm512_test_epi16_mask(inputV01, inputV01); // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 __m512i nnz = _mm512_maskz_compress_epi16(nnzMask, base); From ade891744714c40a339764693258dc82040e39f0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 7 Aug 2025 00:10:36 +0300 Subject: [PATCH 1117/1309] Remove outdated comment Remove the Elo worth, as they are now completely outdated, making them irrelevant and potentially misleading Continuation of #5810 as these comments in "history.h" were missed. closes https://github.com/official-stockfish/Stockfish/pull/6216 No functional change --- src/history.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/history.h b/src/history.h index b2abd9cc004..faf4af3d7fb 100644 --- a/src/history.h +++ b/src/history.h @@ -101,7 +101,7 @@ using Stats = MultiArray, Sizes...>; // ButterflyHistory records how often quiet moves have been successful or unsuccessful // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, -// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo) +// see https://www.chessprogramming.org/Butterfly_Boards using ButterflyHistory = Stats; // LowPlyHistory is adressed by play and move's from and to squares, used @@ -118,7 +118,6 @@ using PieceToHistory = Stats; // ContinuationHistory is the combined history of a given pair of moves, usually // the current one given a previous one. The nested history table is based on // PieceToHistory instead of ButterflyBoards. -// (~63 elo) using ContinuationHistory = MultiArray; // PawnHistory is addressed by the pawn structure and a move's [piece][to] From 7a07ac0e768a2d0c6b3c6b31470a3d822d94c15c Mon Sep 17 00:00:00 2001 From: Jost Triller Date: Tue, 5 Aug 2025 22:38:32 +0200 Subject: [PATCH 1118/1309] Adjust probcut on staticEval Reducing probcut depth more when staticEval is very good and less if staticEval is not so good compared to beta STC: https://tests.stockfishchess.org/tests/view/68926bf6690844f940fa1350 LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 174400 W: 45698 L: 45174 D: 83528 Ptnml(0-2): 622, 20356, 44672, 20976, 574 LTC: https://tests.stockfishchess.org/tests/view/689651b4f8a258623dda6531 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 56010 W: 14547 L: 14191 D: 27272 Ptnml(0-2): 31, 5929, 15738, 6267, 40 This patch was generated using qwen3-235b-a22b-thinking-2507: Prompt: https://rentry.co/bm6vriai Thinking: https://rentry.co/34km4zf8 Final respone: https://rentry.co/rauotrvr closes https://github.com/official-stockfish/Stockfish/pull/6219 Bench: 3629755 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b807ae7d123..3bf460a1af1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -914,7 +914,8 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth probCutDepth = std::max(depth - 5, 0); + Depth dynamicReduction = (ss->staticEval - beta) / 300; + Depth probCutDepth = std::max(depth - 5 - dynamicReduction, 0); while ((move = mp.next_move()) != Move::none()) { From 0383670cd556496ab3b3059f9a1fe10f44eef274 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 15 Aug 2025 14:41:55 -0700 Subject: [PATCH 1119/1309] Avoid using _mm512_storeu_epi16() gcc 9 & 10 do not provide this function, instead use the generic _mm512_storeu_si512 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95483 https://tests.stockfishchess.org/actions?max_actions=1&action=&user=&text=&before=1755266300.455175&run_id= closes https://github.com/official-stockfish/Stockfish/pull/6231 No functional change --- src/nnue/layers/affine_transform_sparse_input.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 685cf8ea1cf..a073c61969e 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -104,7 +104,7 @@ void find_nnz(const std::int32_t* RESTRICT input, // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 __m512i nnz = _mm512_maskz_compress_epi16(nnzMask, base); - _mm512_storeu_epi16(out + count, nnz); + _mm512_storeu_si512(out + count, nnz); count += popcount(nnzMask); base = _mm512_add_epi16(base, increment); From 2e91a8635468e40c89a2303ce50384864d088611 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 9 Aug 2025 05:52:42 +0200 Subject: [PATCH 1120/1309] Add log depth as term to reduction factors. Replace most reduction factors with a linear term of the form X + Y * msb(depth) (using msb(depth) as simple and fast approximation for log(depth)) and tune the weights with following SPSA test: https://tests.stockfishchess.org/tests/view/689903860049e8ccef9d6415. Here the tuned values of X[8] and Y[8] were ignored because a simple test with this parameters alone failed badly (https://tests.stockfishchess.org/tests/view/68992b350049e8ccef9d6482). Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 32448 W: 8651 L: 8342 D: 15455 Ptnml(0-2): 81, 3695, 8408, 3914, 126 https://tests.stockfishchess.org/tests/view/6899489b0049e8ccef9d64ad Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 73854 W: 19042 L: 18649 D: 36163 Ptnml(0-2): 37, 7908, 20659, 8271, 52 https://tests.stockfishchess.org/tests/view/689abbe7fd8719b088c8d514 closes https://github.com/official-stockfish/Stockfish/pull/6229 Bench: 3151102 --- src/search.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3bf460a1af1..b4b85ee7666 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1178,27 +1178,27 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling - r += 650; // Base reduction offset to compensate for other tweaks - r -= moveCount * 69; + r += 679 - 6 * msb(depth); // Base reduction offset to compensate for other tweaks + r -= moveCount * (67 - 2 * msb(depth)); r -= std::abs(correctionValue) / 27160; // Increase reduction for cut nodes if (cutNode) - r += 3000 + 1024 * !ttData.move; + r += 2998 + 2 * msb(depth) + (948 + 14 * msb(depth)) * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1350; + r += 1402 - 39 * msb(depth); // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 935 + allNode * 763; + r += 925 + 33 * msb(depth) + allNode * (701 + 224 * msb(depth)); r += (ss + 1)->quietMoveStreak * 51; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2043; + r -= 2121 + 28 * msb(depth); if (capture) ss->statScore = 782 * int(PieceValue[pos.captured_piece()]) / 128 @@ -1209,7 +1209,7 @@ Value Search::Worker::search( + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 789 / 8192; + r -= ss->statScore * (729 - 12 * msb(depth)) / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1250,7 +1250,7 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1139; + r += 1199 + 35 * msb(depth); const int threshold1 = depth <= 4 ? 2000 : 3200; const int threshold2 = depth <= 4 ? 3500 : 4600; From d86414859519717c237570dbdea65de233a8d4f0 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 9 Aug 2025 12:29:40 +0300 Subject: [PATCH 1121/1309] Simplify beta formula Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 152384 W: 39907 L: 39814 D: 72663 Ptnml(0-2): 557, 17958, 39053, 18083, 541 https://tests.stockfishchess.org/tests/view/6890b66692fcad741b804a10 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 98688 W: 25411 L: 25270 D: 48007 Ptnml(0-2): 45, 10692, 27743, 10805, 59 https://tests.stockfishchess.org/tests/view/6896019c618946ab878347b0 closes: https://github.com/official-stockfish/Stockfish/pull/6220 bench: 3002300 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b4b85ee7666..c8101262565 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -369,7 +369,7 @@ void Search::Worker::iterative_deepening() { // otherwise exit the loop. if (bestValue <= alpha) { - beta = (3 * alpha + beta) / 4; + beta = alpha; alpha = std::max(bestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; From 0db4a1bee9a7e1172dbcd105bc9f68b8f4817f86 Mon Sep 17 00:00:00 2001 From: Kevin Lu Date: Thu, 31 Jul 2025 14:17:07 -0700 Subject: [PATCH 1122/1309] Remove completedDepth condition for SE Just removing the term Passed STC: LLR: 3.27 (-2.94,2.94) <-1.75,0.25> Total: 179840 W: 47211 L: 47126 D: 85503 Ptnml(0-2): 567, 17929, 52879, 17942, 603 https://tests.stockfishchess.org/tests/view/688bdd88502b34dd5e7111ea Passed LTC: LLR: 3.26 (-2.94,2.94) <-1.75,0.25> Total: 150708 W: 38738 L: 38637 D: 73333 Ptnml(0-2): 79, 12922, 49262, 13001, 90 https://tests.stockfishchess.org/tests/view/688ce857f17748b4d23c8026 Passed VLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 288312 W: 74104 L: 74152 D: 140056 Ptnml(0-2): 44, 26732, 90657, 26674, 49 https://tests.stockfishchess.org/tests/view/688ec4f57d68fe4f7f130ee3 closes https://github.com/official-stockfish/Stockfish/pull/6221 functional at higher depths Bench: 3002300 --- AUTHORS | 1 + src/search.cpp | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index e606e5a8d41..804824d8cb9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -133,6 +133,7 @@ Justin Blanchard (UncombedCoconut) Kelly Wilson Ken Takusagawa Kenneth Lee (kennethlee33) +kevlu8 Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) diff --git a/src/search.cpp b/src/search.cpp index c8101262565..b4569d9a593 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1112,9 +1112,8 @@ Value Search::Worker::search( // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. - if (!rootNode && move == ttData.move && !excludedMove - && depth >= 6 - (completedDepth > 26) + ss->ttPv && is_valid(ttData.value) - && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) + if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv + && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 58; From c9c002450a52f0b2315af65ebfb767024b33e136 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 9 Aug 2025 11:21:41 -0400 Subject: [PATCH 1123/1309] Simplify depth term in reductions Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 317344 W: 82352 L: 82442 D: 152550 Ptnml(0-2): 999, 37395, 81907, 37439, 932 https://tests.stockfishchess.org/tests/live_elo/68976798f8a258623dda6c46 Passed simplification LTC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 291708 W: 74733 L: 74787 D: 142188 Ptnml(0-2): 159, 31813, 81956, 31775, 151 https://tests.stockfishchess.org/tests/live_elo/689791a5f8a258623dda6d03 closes https://github.com/official-stockfish/Stockfish/pull/6225 Bench: 2436991 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index b4569d9a593..6687ecb225c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1251,13 +1251,12 @@ Value Search::Worker::search( if (!ttData.move) r += 1199 + 35 * msb(depth); - const int threshold1 = depth <= 4 ? 2000 : 3200; - const int threshold2 = depth <= 4 ? 3500 : 4600; + if (depth <= 4) + r += 1150; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > threshold1) - (r > threshold2 && newDepth > 2), - !cutNode); + newDepth - (r > 3200) - (r > 4600 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, From 169737a9eb3edcb144e9c1db5d4555bd1e5934c0 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 10 Aug 2025 13:10:20 -0400 Subject: [PATCH 1124/1309] Simplify dual bonuses Merge dual capture/quiet bonuses into one Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 223200 W: 57868 L: 57854 D: 107478 Ptnml(0-2): 689, 26441, 57296, 26515, 659 https://tests.stockfishchess.org/tests/view/6898d28e0049e8ccef9d6384 Passed simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 147270 W: 37750 L: 37659 D: 71861 Ptnml(0-2): 84, 15869, 41633, 15970, 79 https://tests.stockfishchess.org/tests/view/68990fe30049e8ccef9d643c closes https://github.com/official-stockfish/Stockfish/pull/6226 Bench: 2996176 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6687ecb225c..aa78a673f51 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1816,14 +1816,13 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int quietBonus = std::min(170 * depth - 87, 1598) + 332 * (bestMove == ttMove); + int bonus = std::min(170 * depth - 87, 1598) + 332 * (bestMove == ttMove); int quietMalus = std::min(743 * depth - 180, 2287) - 33 * quietsSearched.size(); - int captureBonus = std::min(124 * depth - 62, 1245) + 336 * (bestMove == ttMove); int captureMalus = std::min(708 * depth - 148, 2287) - 29 * capturesSearched.size(); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, quietBonus * 978 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 978 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) @@ -1833,7 +1832,7 @@ void update_all_stats(const Position& pos, { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << captureBonus * 1288 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus; } // Extra penalty for a quiet early move that was not a TT move in From 4b6b13ce5a76985ee00ad04d63164bd075b93720 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 17 Aug 2025 22:03:55 +0200 Subject: [PATCH 1125/1309] Update sde action to 2.4, switch to 9.33.0 older version no longer downloadable. closes https://github.com/official-stockfish/Stockfish/pull/6240 No functional change --- .github/ci/matrix.json | 4 ++-- .github/workflows/compilation.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index 57c97686ccf..436cb4b8446 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -8,7 +8,7 @@ "comp": "gcc", "shell": "bash", "archive_ext": "tar", - "sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future --" + "sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-lin/sde -future --" }, { "name": "MacOS 13 Apple Clang", @@ -38,7 +38,7 @@ "msys_env": "x86_64-gcc", "shell": "msys2 {0}", "ext": ".exe", - "sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future --", + "sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-win/sde.exe -future --", "archive_ext": "zip" }, { diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 67b2e1c5528..7805b24d6bf 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -43,10 +43,10 @@ jobs: - name: Download SDE package if: runner.os == 'Linux' || runner.os == 'Windows' - uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3 + uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4 with: environmentVariableName: SDE_DIR - sdeVersion: 9.27.0 + sdeVersion: 9.33.0 - name: Download the used network from the fishtest framework run: make net From c56bd10cb5ba192c76b6c5c6228625d58bd99892 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 16 Aug 2025 13:40:48 +0200 Subject: [PATCH 1126/1309] Fix undefined behavior in stalemate trap detection. An important part of it is a function which detects if we can move a king or pawn. If this function called before quiet move generation phase the end of the checked move list is undefined (Thanks to AliceRoselia pointing out we have some problem because of wrong bench in a test). This problem exists also in master but is really rarely triggered (thanks to vondele which found one) but it seen significant more often if the capture order score is increased. This fix now simply assumes in this case (and other similiar cases) that a king or pawn can move without checking any moves. Passed non-regression STC (with default book): LLR: 3.03 (-2.94,2.94) <-1.75,0.25> Total: 203008 W: 52465 L: 52424 D: 98119 Ptnml(0-2): 498, 20293, 59893, 20310, 510 https://tests.stockfishchess.org/tests/view/68a06ec5fd8719b088c8dc1a Passed non-regression STC (with stalemates book): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 31616 W: 14418 L: 14366 D: 2832 Ptnml(0-2): 1, 189, 15375, 243, 0 https://tests.stockfishchess.org/tests/view/68a07538fd8719b088c8dc1f closes https://github.com/official-stockfish/Stockfish/pull/6235 Bench: 2996176 --- src/movepick.cpp | 9 +++++++-- src/movepick.h | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index eb7c45837a6..dd6d7116754 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -318,8 +318,13 @@ void MovePicker::skip_quiet_moves() { skipQuiets = true; } // this function must be called after all quiet moves and captures have been generated bool MovePicker::can_move_king_or_pawn() const { - // SEE negative captures shouldn't be returned in GOOD_CAPTURE stage - assert(stage > GOOD_CAPTURE && stage != EVASION_INIT); + + assert((GOOD_QUIET <= stage && stage <= BAD_QUIET) || stage == EVASION); + + // Until good capture state no quiet moves are generated for comparison so simply assume king or pawns can move. + // Do the same for other states that don't have a valid available move list. + if ((GOOD_QUIET > stage || stage > BAD_QUIET) && stage != EVASION) + return true; for (const ExtMove* m = moves; m < endGenerated; ++m) { diff --git a/src/movepick.h b/src/movepick.h index 9d6c02b0ef3..6a3305c6c2a 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -67,7 +67,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated; + ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated = moves; int stage; int threshold; Depth depth; From 2176c9387e24cb269e25126ef44dc53faef3cff1 Mon Sep 17 00:00:00 2001 From: 87 Date: Sat, 16 Aug 2025 22:41:55 +0100 Subject: [PATCH 1127/1309] Add performance warning comment for vpcompressw closes https://github.com/official-stockfish/Stockfish/pull/6237 No functional change --- src/movegen.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/movegen.cpp b/src/movegen.cpp index 10adc70be60..697a83cdfc5 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -37,6 +37,7 @@ namespace { #if defined(USE_AVX512ICL) inline Move* write_moves(Move* moveList, uint32_t mask, __m512i vector) { + // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 _mm512_storeu_si512(reinterpret_cast<__m512i*>(moveList), _mm512_maskz_compress_epi16(mask, vector)); return moveList + popcount(mask); From 8ecfc3c89d48418f84864fb235635d69e6ea37b8 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 17 Aug 2025 08:38:16 +0200 Subject: [PATCH 1128/1309] Bigger thread dependent initial window. Increase the initial delta of the aspiration window by "thread id mod 8". Passed SMP STC: LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 106176 W: 27470 L: 27069 D: 51637 Ptnml(0-2): 121, 12032, 28386, 12423, 126 https://tests.stockfishchess.org/tests/view/68a178fab6fb3300203bbaec Passed SMP LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 133076 W: 34272 L: 33772 D: 65032 Ptnml(0-2): 38, 13709, 38537, 14223, 31 https://tests.stockfishchess.org/tests/view/68a1b5d2b6fb3300203bbb71 closes https://github.com/official-stockfish/Stockfish/pull/6239 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index aa78a673f51..446f8137964 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -322,7 +322,7 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11131; + delta = 5 + threadIdx % 8 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11131; Value avg = rootMoves[pvIdx].averageScore; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); From 7fe46b5a70c7422e78bc0b484e772158b7fb4683 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 17 Aug 2025 23:38:06 +0300 Subject: [PATCH 1129/1309] VVLTC tweak Passed VVLTC with STC bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 60796 W: 15908 L: 15609 D: 29279 Ptnml(0-2): 6, 5598, 18889, 5901, 4 https://tests.stockfishchess.org/tests/view/68a1fbfeb6fb3300203bbc5c Passed VVLTC with LTC bounds: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 24914 W: 6528 L: 6256 D: 12130 Ptnml(0-2): 4, 2263, 7648, 2541, 1 https://tests.stockfishchess.org/tests/view/68a211aeb6fb3300203bbca7 closes https://github.com/official-stockfish/Stockfish/pull/6241 Bench: 2130122 --- src/search.cpp | 215 +++++++++++++++++++++++++------------------------ 1 file changed, 108 insertions(+), 107 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 446f8137964..e54f7c439d1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -81,9 +81,9 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - : 0; + : 8; - return 8867 * pcv + 8136 * micv + 10757 * (wnpcv + bnpcv) + 7232 * cntcv; + return 9536 * pcv + 8494 * micv + 10132 * (wnpcv + bnpcv) + 7156 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -99,10 +99,10 @@ 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; + const int nonPawnWeight = 165; workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 153 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] @@ -110,7 +110,7 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 153 / 128; + << bonus * 137 / 128; } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -286,7 +286,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(89); + lowPlyHistory.fill(97); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -322,13 +322,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + threadIdx % 8 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11131; + delta = 5 + threadIdx % 8 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 9000; 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] = 136 * avg / (std::abs(avg) + 93); + optimism[us] = 137 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -457,29 +457,29 @@ void Search::Worker::iterative_deepening() { rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); double fallingEval = - (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) - + 0.968 * (mainThread->iterValue[iterIdx] - bestValue)) + (11.325 + 2.115 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.987 * (mainThread->iterValue[iterIdx] - bestValue)) / 100.0; - fallingEval = std::clamp(fallingEval, 0.5786, 1.6752); + fallingEval = std::clamp(fallingEval, 0.5688, 1.5698); // If the bestMove is stable over several iterations, reduce time accordingly - double k = 0.527; - double center = lastBestMoveDepth + 11; - timeReduction = 0.8 + 0.84 / (1.077 + std::exp(-k * (completedDepth - center))); + double k = 0.5189; + double center = lastBestMoveDepth + 11.57; + timeReduction = 0.723 + 0.79 / (1.104 + std::exp(-k * (completedDepth - center))); double reduction = - (1.4540 + mainThread->previousTimeReduction) / (2.1593 * timeReduction); - double bestMoveInstability = 0.9929 + 1.8519 * totBestMoveChanges / threads.size(); + (1.455 + mainThread->previousTimeReduction) / (2.2375 * timeReduction); + double bestMoveInstability = 1.04 + 1.8956 * totBestMoveChanges / threads.size(); double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) - totalTime = std::min(500.0, totalTime); + totalTime = std::min(502.0, totalTime); auto elapsedTime = elapsed(); - if (completedDepth >= 10 && nodesEffort >= 97056 && elapsedTime > totalTime * 0.6540 + if (completedDepth >= 10 && nodesEffort >= 92425 && elapsedTime > totalTime * 0.666 && !mainThread->ponder) threads.stop = true; @@ -494,7 +494,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.5138; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.503; } mainThread->iterValue[iterIdx] = bestValue; @@ -544,9 +544,9 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(64); - captureHistory.fill(-753); - pawnHistory.fill(-1275); + mainHistory.fill(68); + captureHistory.fill(-689); + pawnHistory.fill(-1238); pawnCorrectionHistory.fill(5); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory.fill(0); @@ -561,10 +561,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-494); + h.fill(-529); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2782 / 128.0 * std::log(i)); + reductions[i] = int(2809 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -687,16 +687,16 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(127 * depth - 74, 1063)); + std::min(130 * depth - 71, 1043)); // 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, -2128); + if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2142); } // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. - if (pos.rule50_count() < 91) + if (pos.rule50_count() < 96) { if (depth >= 8 && ttData.move && pos.pseudo_legal(ttData.move) && pos.legal(ttData.move) && !is_decisive(ttData.value)) @@ -809,12 +809,12 @@ 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), -1979, 1561) + 630; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 935 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -2023, 1563) + 583; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 944 / 1024; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1428 / 1024; + << bonus * 1438 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -824,28 +824,28 @@ Value Search::Worker::search( improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; - if (priorReduction >= (depth < 10 ? 1 : 3) && !opponentWorsening) + if (priorReduction >= (depth < 10 ? 2 : 3) && !opponentWorsening) depth++; - if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 177) + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 495 - 290 * depth * depth) + if (!PvNode && eval < alpha - 514 - 294 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 90 - 20 * (cutNode && !ss->ttHit); + Value futilityMult = 91 - 21 * (!ss->ttHit); - return futilityMult * d // - - improving * futilityMult * 2 // - - opponentWorsening * futilityMult / 3 // - + (ss - 1)->statScore / 356 // - + std::abs(correctionValue) / 171290; + return futilityMult * d // + - 2094 * improving * futilityMult / 1024 // + - 1324 * opponentWorsening * futilityMult / 4096 // + + (ss - 1)->statScore / 331 // + + std::abs(correctionValue) / 158105; }; if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta @@ -854,13 +854,13 @@ Value Search::Worker::search( } // Step 9. Null move search with verification search - if (cutNode && ss->staticEval >= beta - 19 * depth + 403 && !excludedMove + if (cutNode && ss->staticEval >= beta - 18 * depth + 390 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 7 + depth / 3; + Depth R = 6 + depth / 3; ss->currentMove = Move::null(); ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; @@ -904,7 +904,7 @@ Value Search::Worker::search( // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 215 - 60 * improving; + probCutBeta = beta + 224 - 64 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -914,7 +914,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth dynamicReduction = (ss->staticEval - beta) / 300; + Depth dynamicReduction = (ss->staticEval - beta) / 306; Depth probCutDepth = std::max(depth - 5 - dynamicReduction, 0); while ((move = mp.next_move()) != Move::none()) @@ -955,7 +955,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 417; + probCutBeta = beta + 418; 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; @@ -1019,7 +1019,7 @@ Value Search::Worker::search( // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 931; + r += 946; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1041,15 +1041,15 @@ Value Search::Worker::search( if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 232 + 224 * lmrDepth - + PieceValue[capturedPiece] + 131 * captHist / 1024; + Value futilityValue = ss->staticEval + 231 + 211 * lmrDepth + + PieceValue[capturedPiece] + 130 * captHist / 1024; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int margin = std::clamp(158 * depth + captHist / 31, 0, 283 * depth); + int margin = std::clamp(157 * depth + captHist / 29, 0, 279 * depth); if (!pos.see_ge(move, -margin)) { bool mayStalemateTrap = @@ -1071,16 +1071,16 @@ Value Search::Worker::search( + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4361 * depth) + if (history < -4312 * depth) continue; - history += 71 * mainHistory[us][move.from_to()] / 32; + history += 76 * mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3233; + lmrDepth += history / 3220; - Value baseFutility = (bestMove ? 46 : 230); + Value baseFutility = (bestMove ? 47 : 218); Value futilityValue = - ss->staticEval + baseFutility + 131 * lmrDepth + 91 * (ss->staticEval > alpha); + ss->staticEval + baseFutility + 134 * lmrDepth + 90 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning @@ -1096,7 +1096,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1116,7 +1116,7 @@ Value Search::Worker::search( && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (56 + 79 * (ss->ttPv && !PvNode)) * depth / 58; + Value singularBeta = ttData.value - (56 + 81 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1125,11 +1125,11 @@ Value Search::Worker::search( if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 249096; - int doubleMargin = 4 + 205 * PvNode - 223 * !ttCapture - corrValAdj - - 959 * ttMoveHistory / 131072 - (ss->ply > rootDepth) * 45; - int tripleMargin = 80 + 276 * PvNode - 249 * !ttCapture + 86 * ss->ttPv - corrValAdj - - (ss->ply * 2 > rootDepth * 3) * 53; + int corrValAdj = std::abs(correctionValue) / 229958; + int doubleMargin = -4 + 198 * PvNode - 212 * !ttCapture - corrValAdj + - 921 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 45; + int tripleMargin = 76 + 308 * PvNode - 250 * !ttCapture + 92 * ss->ttPv - corrValAdj + - (ss->ply * 2 > rootDepth * 3) * 52; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1172,35 +1172,35 @@ Value Search::Worker::search( // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2510 + PvNode * 963 + (ttData.value > alpha) * 916 - + (ttData.depth >= depth) * (943 + cutNode * 1180); + r -= 2618 + PvNode * 991 + (ttData.value > alpha) * 903 + + (ttData.depth >= depth) * (978 + cutNode * 1051); // These reduction adjustments have no proven non-linear scaling - r += 679 - 6 * msb(depth); // Base reduction offset to compensate for other tweaks - r -= moveCount * (67 - 2 * msb(depth)); - r -= std::abs(correctionValue) / 27160; + r += 700 - 6 * msb(depth); // Base reduction offset to compensate for other tweaks + r -= moveCount * (64 - 2 * msb(depth)); + r -= std::abs(correctionValue) / 30450; // Increase reduction for cut nodes if (cutNode) - r += 2998 + 2 * msb(depth) + (948 + 14 * msb(depth)) * !ttData.move; + r += 3092 + 2 * msb(depth) + (980 + 15 * msb(depth)) * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1402 - 39 * msb(depth); + r += 1467 - 40 * msb(depth); // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 925 + 33 * msb(depth) + allNode * (701 + 224 * msb(depth)); + r += 1041 + 34 * msb(depth) + allNode * (752 + 226 * msb(depth)); - r += (ss + 1)->quietMoveStreak * 51; + r += (ss + 1)->quietMoveStreak * 50; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2121 + 28 * msb(depth); + r -= 2096 + 27 * msb(depth); if (capture) - ss->statScore = 782 * int(PieceValue[pos.captured_piece()]) / 128 + ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else ss->statScore = 2 * mainHistory[us][move.from_to()] @@ -1208,7 +1208,7 @@ Value Search::Worker::search( + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * (729 - 12 * msb(depth)) / 8192; + r -= ss->statScore * (734 - 12 * msb(depth)) / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1218,7 +1218,8 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + 1 + PvNode)) + PvNode; + Depth d = + std::max(1, std::min(newDepth - r / 1024, newDepth + (PvNode ? 2 : 1))) + PvNode; ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); @@ -1240,7 +1241,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - update_continuation_histories(ss, movedPiece, move.to_sq(), 1412); + update_continuation_histories(ss, movedPiece, move.to_sq(), 1365); } } @@ -1249,14 +1250,14 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1199 + 35 * msb(depth); + r += 1178 + 35 * msb(depth); - if (depth <= 4) - r += 1150; + if (depth < 5) + r += 1080; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3200) - (r > 4600 && newDepth > 2), !cutNode); + newDepth - (r > 3212) - (r > 4784 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1361,7 +1362,7 @@ Value Search::Worker::search( } // Reduce other moves if we have found at least one score improvement - if (depth > 2 && depth < 16 && !is_decisive(value)) + if (depth > 2 && depth < 14 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1401,31 +1402,31 @@ Value Search::Worker::search( update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, ttData.move); if (!PvNode) - ttMoveHistory << (bestMove == ttData.move ? 811 : -848); + ttMoveHistory << (bestMove == ttData.move ? 809 : -865); } // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -215; - bonusScale += std::min(-(ss - 1)->statScore / 103, 337); - bonusScale += std::min(64 * depth, 552); - bonusScale += 177 * ((ss - 1)->moveCount > 8); - bonusScale += 141 * (!ss->inCheck && bestValue <= ss->staticEval - 94); - bonusScale += 141 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 76); + int bonusScale = -228; + bonusScale += std::min(-(ss - 1)->statScore / 104, 322); + bonusScale += std::min(63 * depth, 508); + bonusScale += 184 * ((ss - 1)->moveCount > 8); + bonusScale += 143 * (!ss->inCheck && bestValue <= ss->staticEval - 92); + bonusScale += 149 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 70); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(155 * depth - 88, 1416) * bonusScale; + const int scaledBonus = std::min(144 * depth - 92, 1365) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 397 / 32768); + scaledBonus * 400 / 32768); - mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 224 / 32768; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 220 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1127 / 32768; + << scaledBonus * 1164 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1433,7 +1434,7 @@ Value Search::Worker::search( { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1042; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 964; } if (PvNode) @@ -1644,11 +1645,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + pawnHistory[pawn_history_index(pos)][pos.moved_piece(move)][move.to_sq()] - <= 5868) + <= 5475) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -74)) + if (!pos.see_ge(move, -78)) continue; } @@ -1721,7 +1722,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 * 731 / rootDelta + !i * reductionScale * 216 / 512 + 1089; + return reductionScale - delta * 757 / rootDelta + !i * reductionScale * 218 / 512 + 1200; } // elapsed() returns the time elapsed since the search started. If the @@ -1816,17 +1817,17 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(170 * depth - 87, 1598) + 332 * (bestMove == ttMove); - int quietMalus = std::min(743 * depth - 180, 2287) - 33 * quietsSearched.size(); - int captureMalus = std::min(708 * depth - 148, 2287) - 29 * capturesSearched.size(); + int bonus = std::min(151 * depth - 91, 1730) + 302 * (bestMove == ttMove); + int quietMalus = std::min(798 * depth - 175, 2268) - 33 * quietsSearched.size(); + int captureMalus = std::min(757 * depth - 134, 2129) - 28 * capturesSearched.size(); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 978 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 957 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -quietMalus * 1115 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -quietMalus * 1208 / 1024); } else { @@ -1839,14 +1840,14 @@ void update_all_stats(const Position& pos, // 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, - -captureMalus * 622 / 1024); + -captureMalus * 594 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -captureMalus * 1431 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -captureMalus * 1366 / 1024; } } @@ -1854,8 +1855,8 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - static constexpr std::array conthist_bonuses = { - {{1, 1108}, {2, 652}, {3, 273}, {4, 572}, {5, 126}, {6, 449}}}; + const std::array conthist_bonuses = { + {{1, 1157}, {2, 648}, {3, 288}, {4, 576}, {5, 140}, {6, 441}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1863,7 +1864,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { if (ss->inCheck && i > 2) break; if (((ss - i)->currentMove).is_ok()) - (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 80 * (i < 2); + (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 88 * (i < 2); } } @@ -1876,10 +1877,10 @@ 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 * 771 / 1024) + 40; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << (bonus * 741 / 1024) + 38; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), - bonus * (bonus > 0 ? 979 : 842) / 1024); + bonus * (bonus > 0 ? 995 : 915) / 1024); int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] From d11f49b790429c236a1a4169f0d8052635fc03dc Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 21 Aug 2025 18:23:49 +0200 Subject: [PATCH 1130/1309] Remove log depth reduction terms. Motivated by the elo loss of last VVLTC regression test i remove the new log depth reductions (tuned at STC) to regain strength at VVLTC. The constant terms are adjusted based on the old values and the changes from the last added VVLTC tuning. Passed VVLTC with STC bound: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 53802 W: 14030 L: 13740 D: 26032 Ptnml(0-2): 5, 4924, 16754, 5212, 6 https://tests.stockfishchess.org/tests/view/68a9a9f575da51a345a5a675 Passed VVLTC with LTC bound: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 53658 W: 14022 L: 13699 D: 25937 Ptnml(0-2): 3, 4894, 16712, 5217, 3 https://tests.stockfishchess.org/tests/view/68a8d2b2b6fb3300203bca77 closes https://github.com/official-stockfish/Stockfish/pull/6257 Bench: 2566780 --- src/search.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e54f7c439d1..286b339c019 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1177,27 +1177,27 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling - r += 700 - 6 * msb(depth); // Base reduction offset to compensate for other tweaks - r -= moveCount * (64 - 2 * msb(depth)); + r += 671; // Base reduction offset to compensate for other tweaks + r -= moveCount * 66; r -= std::abs(correctionValue) / 30450; // Increase reduction for cut nodes if (cutNode) - r += 3092 + 2 * msb(depth) + (980 + 15 * msb(depth)) * !ttData.move; + r += 3094 + 1056 * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1467 - 40 * msb(depth); + r += 1415; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1041 + 34 * msb(depth) + allNode * (752 + 226 * msb(depth)); + r += 1051 + allNode * 814; r += (ss + 1)->quietMoveStreak * 50; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2096 + 27 * msb(depth); + r -= 2018; if (capture) ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 @@ -1208,7 +1208,7 @@ Value Search::Worker::search( + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * (734 - 12 * msb(depth)) / 8192; + r -= ss->statScore * 794 / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1250,7 +1250,7 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1178 + 35 * msb(depth); + r += 1118; if (depth < 5) r += 1080; From 20bc19558e6ab9b5927d05227f6c29d577fa5960 Mon Sep 17 00:00:00 2001 From: Myself <63040919+AliceRoselia@users.noreply.github.com> Date: Sat, 23 Aug 2025 18:16:57 +0700 Subject: [PATCH 1131/1309] Speedup_threat_by_lesser This patch could have been considered a non-functional speedup, but changing the ranking of illegal moves. Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 131040 W: 34247 L: 33792 D: 63001 Ptnml(0-2): 407, 15281, 33675, 15764, 393 https://tests.stockfishchess.org/tests/live_elo/68a9a75b75da51a345a5a66d Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 280146 W: 72055 L: 71242 D: 136849 Ptnml(0-2): 153, 30252, 78438, 31089, 141 https://tests.stockfishchess.org/tests/view/68a9f58475da51a345a5a6e0 closes https://github.com/official-stockfish/Stockfish/pull/6261 Bench: 2321931 --- src/movepick.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index dd6d7116754..0d2a72306ff 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -129,13 +129,15 @@ ExtMove* MovePicker::score(MoveList& ml) { Color us = pos.side_to_move(); - [[maybe_unused]] Bitboard threatByLesser[QUEEN + 1]; + [[maybe_unused]] Bitboard threatByLesser[KING + 1]; if constexpr (Type == QUIETS) { + threatByLesser[PAWN] = 0; threatByLesser[KNIGHT] = threatByLesser[BISHOP] = pos.attacks_by(~us); threatByLesser[ROOK] = pos.attacks_by(~us) | pos.attacks_by(~us) | threatByLesser[KNIGHT]; threatByLesser[QUEEN] = pos.attacks_by(~us) | threatByLesser[ROOK]; + threatByLesser[KING] = pos.attacks_by(~us) | threatByLesser[QUEEN]; } ExtMove* it = cur; @@ -170,12 +172,10 @@ ExtMove* MovePicker::score(MoveList& ml) { // penalty for moving to a square threatened by a lesser piece // or bonus for escaping an attack by a lesser piece. - if (KNIGHT <= pt && pt <= QUEEN) - { - static constexpr int bonus[QUEEN + 1] = {0, 0, 144, 144, 256, 517}; - int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); - m.value += bonus[pt] * v; - } + static constexpr int bonus[KING + 1] = {0, 0, 144, 144, 256, 517, 10000}; + int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); + m.value += bonus[pt] * v; + if (ply < LOW_PLY_HISTORY_SIZE) m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); From 176ef577e24e2346a6cc817079fe28b755fcdc8d Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 17 Aug 2025 23:54:09 +0300 Subject: [PATCH 1132/1309] Set back static constexpr closes https://github.com/official-stockfish/Stockfish/pull/6243 No functional change --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 286b339c019..7068c332d05 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -99,7 +99,7 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - const int nonPawnWeight = 165; + constexpr int nonPawnWeight = 165; workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; @@ -839,7 +839,7 @@ Value Search::Worker::search( // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 91 - 21 * (!ss->ttHit); + Value futilityMult = 91 - 21 * !ss->ttHit; return futilityMult * d // - 2094 * improving * futilityMult / 1024 // @@ -1855,7 +1855,7 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - const std::array conthist_bonuses = { + static constexpr std::array conthist_bonuses = { {{1, 1157}, {2, 648}, {3, 288}, {4, 576}, {5, 140}, {6, 441}}}; for (const auto [i, weight] : conthist_bonuses) From 47d60a50b6b2704802641763335166c0b8a83453 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 19 Aug 2025 22:05:08 +0200 Subject: [PATCH 1133/1309] CI: fix typo in flag closes https://github.com/official-stockfish/Stockfish/pull/6247 No functional change --- .github/workflows/sanitizers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sanitizers.yml b/.github/workflows/sanitizers.yml index 950435f30a1..d9b783eb617 100644 --- a/.github/workflows/sanitizers.yml +++ b/.github/workflows/sanitizers.yml @@ -42,7 +42,7 @@ jobs: - name: Run with glibcxx assertions make_option: "" cxx_extra_flags: -D_GLIBCXX_ASSERTIONS - instrumented_option: non + instrumented_option: none defaults: run: working-directory: src From 57d76bd459a0c29f0932112d1ee432592054e1ce Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 17 Aug 2025 17:20:08 -0400 Subject: [PATCH 1134/1309] Simplify depth condition in prior reduction Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 172864 W: 45085 L: 45015 D: 82764 Ptnml(0-2): 516, 20456, 44405, 20552, 503 https://tests.stockfishchess.org/tests/view/68a24791b6fb3300203bbdb6 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 84738 W: 21733 L: 21578 D: 41427 Ptnml(0-2): 49, 9169, 23780, 9320, 51 https://tests.stockfishchess.org/tests/view/68a2665ab6fb3300203bc269 closes https://github.com/official-stockfish/Stockfish/pull/6244 Bench: 2131292 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 7068c332d05..f5fad184a5a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -824,7 +824,7 @@ Value Search::Worker::search( improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; - if (priorReduction >= (depth < 10 ? 2 : 3) && !opponentWorsening) + if (priorReduction >= 3 && !opponentWorsening) depth++; if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) depth--; From 2659351ce7c01b02528b6cf6c3553c571ccce962 Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Mon, 18 Aug 2025 06:33:51 +0200 Subject: [PATCH 1135/1309] Remove depth reduction in the full-depth search Passed simplification STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 217184 W: 56218 L: 56195 D: 104771 Ptnml(0-2): 627, 25673, 55985, 25664, 643 https://tests.stockfishchess.org/tests/view/68a21e0eb6fb3300203bbcf3 Passed simplification LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 30474 W: 7923 L: 7713 D: 14838 Ptnml(0-2): 20, 3205, 8569, 3431, 12 https://tests.stockfishchess.org/tests/view/68a2adacb6fb3300203bc3f9 closes https://github.com/official-stockfish/Stockfish/pull/6246 bench 2626263 --- src/search.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f5fad184a5a..47409b3a871 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1252,9 +1252,6 @@ Value Search::Worker::search( if (!ttData.move) r += 1118; - if (depth < 5) - r += 1080; - // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3212) - (r > 4784 && newDepth > 2), !cutNode); From 7fa7a36dc66792749eea2f1a24953ba27be19648 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 17 Aug 2025 17:09:09 -0400 Subject: [PATCH 1136/1309] Remove upper bound in see margin Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 99840 W: 25965 L: 25815 D: 48060 Ptnml(0-2): 326, 11708, 25688, 11886, 312 https://tests.stockfishchess.org/tests/view/68a24504b6fb3300203bbdae Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 248388 W: 63544 L: 63556 D: 121288 Ptnml(0-2): 125, 27132, 69709, 27086, 142 https://tests.stockfishchess.org/tests/view/68a24f3bb6fb3300203bbdee closes https://github.com/official-stockfish/Stockfish/pull/6250 Bench: 2432765 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 47409b3a871..aafc3378906 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1049,7 +1049,7 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks - int margin = std::clamp(157 * depth + captHist / 29, 0, 279 * depth); + int margin = std::max(157 * depth + captHist / 29, 0); if (!pos.see_ge(move, -margin)) { bool mayStalemateTrap = From 85f87649bf70c68234b5f2dba33975690aece9f0 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 20 Aug 2025 16:42:24 -0700 Subject: [PATCH 1137/1309] Simplify stalemate detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passed Non-regression STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 107392 W: 27959 L: 27818 D: 51615 Ptnml(0-2): 317, 12094, 28735, 12231, 319 https://tests.stockfishchess.org/tests/view/68a65d8ab6fb3300203bc825 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94014 W: 24129 L: 23986 D: 45899 Ptnml(0-2): 47, 9917, 26934, 10064, 45 https://tests.stockfishchess.org/tests/view/68a8f45cb6fb3300203bcb5e Passed Stalemate 10k: Elo: 2.22 ± 1.3 (95%) LOS: 100.0% Total: 10000 W: 4626 L: 4562 D: 812 Ptnml(0-2): 1, 137, 4659, 203, 0 nElo: 12.00 ± 6.8 (95%) PairsRatio: 1.47 https://tests.stockfishchess.org/tests/view/68a65d8ab6fb3300203bc825 Supersedes #6114 Closes #6232 closes https://github.com/official-stockfish/Stockfish/pull/6255 Bench: 2473929 --- src/movepick.cpp | 19 ------------------- src/movepick.h | 1 - src/search.cpp | 3 +-- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 0d2a72306ff..fa447e18960 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -316,23 +316,4 @@ Move MovePicker::next_move() { void MovePicker::skip_quiet_moves() { skipQuiets = true; } -// this function must be called after all quiet moves and captures have been generated -bool MovePicker::can_move_king_or_pawn() const { - - assert((GOOD_QUIET <= stage && stage <= BAD_QUIET) || stage == EVASION); - - // Until good capture state no quiet moves are generated for comparison so simply assume king or pawns can move. - // Do the same for other states that don't have a valid available move list. - if ((GOOD_QUIET > stage || stage > BAD_QUIET) && stage != EVASION) - return true; - - for (const ExtMove* m = moves; m < endGenerated; ++m) - { - PieceType movedPieceType = type_of(pos.moved_piece(*m)); - if ((movedPieceType == PAWN || movedPieceType == KING) && pos.legal(*m)) - return true; - } - return false; -} - } // namespace Stockfish diff --git a/src/movepick.h b/src/movepick.h index 6a3305c6c2a..922a9069b04 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -50,7 +50,6 @@ class MovePicker { MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(); void skip_quiet_moves(); - bool can_move_king_or_pawn() const; private: template diff --git a/src/search.cpp b/src/search.cpp index aafc3378906..1396a817dfa 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1056,8 +1056,7 @@ Value Search::Worker::search( depth > 2 && alpha < 0 && pos.non_pawn_material(us) == PieceValue[movedPiece] && PieceValue[movedPiece] >= RookValue // it can't be stalemate if we moved a piece adjacent to the king - && !(attacks_bb(pos.square(us)) & move.from_sq()) - && !mp.can_move_king_or_pawn(); + && !(attacks_bb(pos.square(us)) & move.from_sq()); // avoid pruning sacrifices of our last piece for stalemate if (!mayStalemateTrap) From 901ad7e7eed1d7166cc5a3b30da78001a5e686a7 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 23 Aug 2025 06:29:45 -0700 Subject: [PATCH 1138/1309] Simplify quiet move streak Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 46272 W: 12076 L: 11866 D: 22330 Ptnml(0-2): 140, 5407, 11860, 5561, 168 https://tests.stockfishchess.org/tests/view/68a9c26575da51a345a5a6a2 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 140442 W: 35984 L: 35886 D: 68572 Ptnml(0-2): 72, 15333, 39305, 15447, 64 https://tests.stockfishchess.org/tests/view/68aa245e75da51a345a5a80d closes https://github.com/official-stockfish/Stockfish/pull/6258 Bench: 2931171 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 1396a817dfa..6a17e16240e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1006,7 +1006,7 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); - (ss + 1)->quietMoveStreak = (!capture && !givesCheck) ? (ss->quietMoveStreak + 1) : 0; + (ss + 1)->quietMoveStreak = capture ? 0 : (ss->quietMoveStreak + 1); // Calculate new depth for this move newDepth = depth - 1; From e2fdf6f005bd772b6d376c8ddeb14b12760f73c2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 21 Aug 2025 21:05:42 -0700 Subject: [PATCH 1139/1309] Simplify separate malus formulas Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 16352 W: 4336 L: 4090 D: 7926 Ptnml(0-2): 54, 1832, 4157, 2080, 53 https://tests.stockfishchess.org/tests/view/68a808f0b6fb3300203bca08 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94014 W: 24129 L: 23986 D: 45899 Ptnml(0-2): 47, 9917, 26934, 10064, 45 https://tests.stockfishchess.org/tests/view/68a8f45cb6fb3300203bcb5e closes https://github.com/official-stockfish/Stockfish/pull/6256 Bench: 2483704 --- src/search.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6a17e16240e..40e8ef7cfb6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1813,9 +1813,8 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(151 * depth - 91, 1730) + 302 * (bestMove == ttMove); - int quietMalus = std::min(798 * depth - 175, 2268) - 33 * quietsSearched.size(); - int captureMalus = std::min(757 * depth - 134, 2129) - 28 * capturesSearched.size(); + int bonus = std::min(151 * depth - 91, 1730) + 302 * (bestMove == ttMove); + int malus = std::min(951 * depth - 156, 2468) - 30 * quietsSearched.size(); if (!pos.capture_stage(bestMove)) { @@ -1823,7 +1822,7 @@ void update_all_stats(const Position& pos, // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -quietMalus * 1208 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus); } else { @@ -1835,15 +1834,14 @@ void update_all_stats(const Position& pos, // 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, - -captureMalus * 594 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 503 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -captureMalus * 1366 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1157 / 1024; } } From af181d9fe11216a296113292f772561cfd9823d3 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 24 Aug 2025 12:47:00 +0300 Subject: [PATCH 1140/1309] Small simplification in probCut movepicker Remove no longer required condition for tt moves. The idea is that if tt the value is greater than probCut beta try tt move that is a capture anyway - even if it doesn't pass SEE check. This idea in various implementations passed some STCs and was looking decent at LTCs as a gainer. Passed STC: https://tests.stockfishchess.org/tests/view/68aa1ee075da51a345a5a805 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 136160 W: 35189 L: 35079 D: 65892 Ptnml(0-2): 436, 16083, 34891, 16275, 395 Passed LTC: https://tests.stockfishchess.org/tests/view/68aa91d375da51a345a5a884 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 80022 W: 20652 L: 20492 D: 38878 Ptnml(0-2): 33, 8717, 22357, 8865, 39 closes https://github.com/official-stockfish/Stockfish/pull/6259 Bench: 2307940 --- src/movepick.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index fa447e18960..4b01beb6741 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -115,8 +115,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo threshold(th) { assert(!pos.checkers()); - stage = PROBCUT_TT - + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); + stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm)); } // Assigns a numerical value to each move in a list, used for sorting. From c99eb8ef323dde5eafc2286590231e3d1594c9a3 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 24 Aug 2025 14:02:03 +0300 Subject: [PATCH 1141/1309] Remove cap from a bonusScale formula Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 127264 W: 33159 L: 33042 D: 61063 Ptnml(0-2): 336, 14509, 33866, 14544, 377 https://tests.stockfishchess.org/tests/view/68a63fccb6fb3300203bc818 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 469512 W: 120094 L: 120331 D: 229087 Ptnml(0-2): 238, 51317, 131885, 51076, 240 https://tests.stockfishchess.org/tests/view/68a8d867b6fb3300203bcab5 closes https://github.com/official-stockfish/Stockfish/pull/6260 Bench: 2433974 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 40e8ef7cfb6..7704d530e39 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1405,7 +1405,7 @@ Value Search::Worker::search( else if (!priorCapture && prevSq != SQ_NONE) { int bonusScale = -228; - bonusScale += std::min(-(ss - 1)->statScore / 104, 322); + bonusScale -= (ss - 1)->statScore / 104; bonusScale += std::min(63 * depth, 508); bonusScale += 184 * ((ss - 1)->moveCount > 8); bonusScale += 143 * (!ss->inCheck && bestValue <= ss->staticEval - 92); From 39c077f15a88ff1e563971c396eb9a27b0ac6ac5 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 17 Aug 2025 20:05:43 +0200 Subject: [PATCH 1142/1309] Less reduction for later threads. Give "(thread id mod 8) * 64" less reductions (up to nearly a half ply). Passed SMP STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 413176 W: 106496 L: 105666 D: 201014 Ptnml(0-2): 504, 46732, 111266, 47602, 484 https://tests.stockfishchess.org/tests/view/68a24aeeb6fb3300203bbdc4 Passed SMP LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 60420 W: 15622 L: 15268 D: 29530 Ptnml(0-2): 11, 6106, 17632, 6440, 21 https://tests.stockfishchess.org/tests/view/68a2c2ffb6fb3300203bc516 closes https://github.com/official-stockfish/Stockfish/pull/6249 No functional change --- src/search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/search.cpp b/src/search.cpp index 7704d530e39..d786b572aad 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1177,6 +1177,7 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling r += 671; // Base reduction offset to compensate for other tweaks + r -= (threadIdx % 8) * 64; r -= moveCount * 66; r -= std::abs(correctionValue) / 30450; From 678d503d8f9c36e4087d7506f5d443c08f859c36 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sat, 23 Aug 2025 06:26:40 -0700 Subject: [PATCH 1143/1309] Simplify LMR Extensions Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 236896 W: 61683 L: 61683 D: 113530 Ptnml(0-2): 730, 28057, 60901, 28003, 757 https://tests.stockfishchess.org/tests/view/68a9c20475da51a345a5a6a0 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 73644 W: 18936 L: 18770 D: 35938 Ptnml(0-2): 33, 7951, 20685, 8123, 30 https://tests.stockfishchess.org/tests/view/68aa95c575da51a345a5a88a closes https://github.com/official-stockfish/Stockfish/pull/6265 Bench: 2693376 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d786b572aad..5f2f63c65ac 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1218,8 +1218,7 @@ Value Search::Worker::search( // beyond the first move depth. // To prevent problems when the max value is less than the min value, // std::clamp has been replaced by a more robust implementation. - Depth d = - std::max(1, std::min(newDepth - r / 1024, newDepth + (PvNode ? 2 : 1))) + PvNode; + Depth d = std::max(1, std::min(newDepth - r / 1024, newDepth + 2)) + PvNode; ss->reduction = newDepth - d; value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); From cb7232a818d9994a5d122cf5e1af0d4915e265c0 Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sun, 24 Aug 2025 19:48:24 +0200 Subject: [PATCH 1144/1309] Use integer type for UCI spin values In particular, parse spin values as integers to avoid rounding errors. Fixes https://github.com/official-stockfish/Stockfish/issues/6263 closes https://github.com/official-stockfish/Stockfish/pull/6264 No functional change --- src/ucioption.cpp | 6 +++--- src/ucioption.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ucioption.cpp b/src/ucioption.cpp index a76bd3ace96..ff6235695ea 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -110,7 +110,7 @@ Option::Option(OnChange f) : max(0), on_change(std::move(f)) {} -Option::Option(double v, int minv, int maxv, OnChange f) : +Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), @@ -154,7 +154,7 @@ Option& Option::operator=(const std::string& v) { if ((type != "button" && type != "string" && v.empty()) || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (std::stof(v) < min || std::stof(v) > max))) + || (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) return *this; if (type == "combo") @@ -202,7 +202,7 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { } else if (o.type == "spin") - os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max " + os << " default " << stoi(o.defaultValue) << " min " << o.min << " max " << o.max; break; diff --git a/src/ucioption.h b/src/ucioption.h index 3d7386c30a4..0c957fda171 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -43,7 +43,7 @@ class Option { Option(OnChange = nullptr); Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); - Option(double v, int minv, int maxv, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); Option(const char* v, const char* cur, OnChange = nullptr); Option& operator=(const std::string&); From 69de394439265584e17caa497a31c48c1be0dc7a Mon Sep 17 00:00:00 2001 From: Akshat Sinha Date: Wed, 27 Aug 2025 20:36:01 +0530 Subject: [PATCH 1145/1309] fix ppc altivec check & loongarch64-lsx typo closes https://github.com/official-stockfish/Stockfish/pull/6276 No functional change --- scripts/get_native_properties.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index e8c8f23f289..180c15d15f5 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -31,7 +31,7 @@ set_arch_loongarch64() { if check_flags 'lasx'; then true_arch='loongarch64-lasx' elif check_flags 'lsx'; then - true_arch='lonngarch64-lsx' + true_arch='loongarch64-lsx' else true_arch='loongarch64' fi @@ -57,7 +57,7 @@ set_arch_x86_64() { } set_arch_ppc_64() { - if $(grep -q -w "altivec" /proc/cpuinfo); then + if grep -q -w "altivec" /proc/cpuinfo; then power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo) if [ "0$power" -gt 7 ]; then # VSX started with POWER8 From 75f07da912577adbfab96f57716172cc972fe026 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Mon, 25 Aug 2025 14:09:48 +0200 Subject: [PATCH 1146/1309] Avoid high rule50 count zeroing cutoffs If the depth is low enough, don't TT cut if the rule50 count is high and the TT move is zeroing. Passed STC: https://tests.stockfishchess.org/tests/view/68a8fdd8b6fb3300203bcb92 LLR: 3.05 (-2.94,2.94) <0.00,2.00> Total: 110304 W: 28805 L: 28402 D: 53097 Ptnml(0-2): 275, 11174, 31875, 11529, 299 Passed LTC: https://tests.stockfishchess.org/tests/view/68aa200f75da51a345a5a809 LLR: 3.00 (-2.94,2.94) <0.50,2.50> Total: 187956 W: 48489 L: 47928 D: 91539 Ptnml(0-2): 59, 16118, 61075, 16655, 71 closes https://github.com/official-stockfish/Stockfish/pull/6271 bench: 2641840 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5f2f63c65ac..01155e26044 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -679,7 +679,10 @@ 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 > 5)) + && (cutNode == (ttData.value >= beta) || depth > 5) + // avoid a TT cutoff if the rule50 count is high and the TT move is zeroing + && (depth > 8 || ttData.move == Move::none() || pos.rule50_count() < 80 + || (!ttCapture && type_of(pos.moved_piece(ttData.move)) != PAWN))) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) From 222df615c1bbce15290d33f3f38d401a6df1743a Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Mon, 25 Aug 2025 11:09:39 +0200 Subject: [PATCH 1147/1309] revert #6259 The recent commit af181d9 was merged as a simplification, but unfortunately hurts mate finding efficiency. https://github.com/vondele/matetrack/blob/69f5c5e8627c163a6a8480b869ee09bc44dc44d4/matetrack1000000.csv#L4075-L4076 So this PR proposes to revert it, while adding a comment in the code for future reference. closes https://github.com/official-stockfish/Stockfish/pull/6269 Bench: 2566711 --- src/movepick.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 4b01beb6741..b4523463fae 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -115,7 +115,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo threshold(th) { assert(!pos.checkers()); - stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm)); + // Removing the SEE check passes as simplification, but hurts mate finding + stage = PROBCUT_TT + + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); } // Assigns a numerical value to each move in a list, used for sorting. From a289ee389aef03e4e2028f26aa472d82b909a892 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 24 Aug 2025 02:02:31 -0700 Subject: [PATCH 1148/1309] Simplify Capture Futility Pruning Passed Non-regression STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 187904 W: 48639 L: 48583 D: 90682 Ptnml(0-2): 560, 22150, 48502, 22154, 586 https://tests.stockfishchess.org/tests/view/68aad56075da51a345a5a9e3 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 94302 W: 24246 L: 24101 D: 45955 Ptnml(0-2): 44, 10274, 26371, 10417, 45 https://tests.stockfishchess.org/tests/view/68ab541975da51a345a5aacf closes https://github.com/official-stockfish/Stockfish/pull/6266 Bench: 2376717 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 01155e26044..41f650e79df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1041,9 +1041,8 @@ Value Search::Worker::search( int captHist = captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; // Futility pruning for captures - if (!givesCheck && lmrDepth < 7 && !ss->inCheck) + if (!givesCheck && lmrDepth < 7) { - Value futilityValue = ss->staticEval + 231 + 211 * lmrDepth + PieceValue[capturedPiece] + 130 * captHist / 1024; From 7d213afd37ff90bf1f6ca6deba5533f3f7fc51ff Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 24 Aug 2025 15:03:34 -0700 Subject: [PATCH 1149/1309] Non-functional simplifications closes https://github.com/official-stockfish/Stockfish/pull/6267 No functional change Co-authored-by: Daniel Monroe --- src/search.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 41f650e79df..e7c5e6ac18f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -929,8 +929,6 @@ Value Search::Worker::search( assert(pos.capture_stage(move)); - movedPiece = pos.moved_piece(move); - do_move(pos, move, st, ss); // Perform a preliminary qsearch to verify that the move holds @@ -1079,9 +1077,8 @@ Value Search::Worker::search( lmrDepth += history / 3220; - Value baseFutility = (bestMove ? 47 : 218); - Value futilityValue = - ss->staticEval + baseFutility + 134 * lmrDepth + 90 * (ss->staticEval > alpha); + Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth + + 90 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning @@ -1353,7 +1350,7 @@ Value Search::Worker::search( if (value >= beta) { - // (* Scaler) Especially if they make cutoffCnt increment more often. + // (*Scaler) Especially if they make cutoffCnt increment more often. ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; From 3f183523963e4db27e6ac23394d78c11ef0fea40 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 24 Aug 2025 11:09:31 -0700 Subject: [PATCH 1150/1309] Further Simplify Stalemate Detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passed Non-regression STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 191520 W: 49633 L: 49582 D: 92305 Ptnml(0-2): 557, 20676, 53260, 20693, 574 https://tests.stockfishchess.org/tests/view/68ab570075da51a345a5abe1 Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 212934 W: 54643 L: 54618 D: 103673 Ptnml(0-2): 117, 22417, 61364, 22462, 107 https://tests.stockfishchess.org/tests/view/68ab84e975da51a345a5ac6b 10k Stalemate: Elo: 0.35 ± 1.2 (95%) LOS: 71.6% Total: 10000 W: 4602 L: 4592 D: 806 Ptnml(0-2): 0, 148, 4694, 158, 0 nElo: 1.99 ± 6.8 (95%) PairsRatio: 1.07 https://tests.stockfishchess.org/tests/view/68abeb8175da51a345a5af82 closes https://github.com/official-stockfish/Stockfish/pull/6268 Bench: 2420973 --- src/movepick.h | 2 +- src/search.cpp | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/movepick.h b/src/movepick.h index 922a9069b04..5b3190594e9 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -66,7 +66,7 @@ class MovePicker { const PieceToHistory** continuationHistory; const PawnHistory* pawnHistory; Move ttMove; - ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated = moves; + ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated; int stage; int threshold; Depth depth; diff --git a/src/search.cpp b/src/search.cpp index e7c5e6ac18f..de2ab7a3e16 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,10 +1053,7 @@ Value Search::Worker::search( if (!pos.see_ge(move, -margin)) { bool mayStalemateTrap = - depth > 2 && alpha < 0 && pos.non_pawn_material(us) == PieceValue[movedPiece] - && PieceValue[movedPiece] >= RookValue - // it can't be stalemate if we moved a piece adjacent to the king - && !(attacks_bb(pos.square(us)) & move.from_sq()); + depth > 2 && alpha < 0 && pos.non_pawn_material(us) == PieceValue[movedPiece]; // avoid pruning sacrifices of our last piece for stalemate if (!mayStalemateTrap) From 6a6dadb5a63c1d62735b6ce6d7747ee56dc4680b Mon Sep 17 00:00:00 2001 From: Torsten Hellwig Date: Tue, 26 Aug 2025 10:30:33 +0200 Subject: [PATCH 1151/1309] Remove buggy and unused function The function does not fulfill its purpose and is not used anywhere. See https://discord.com/channels/435943710472011776/1101022188313772083/1409801409855094874 closes https://github.com/official-stockfish/Stockfish/pull/6275 no functional change --- src/position.h | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/position.h b/src/position.h index cf6b1c472cb..56bb7b5a404 100644 --- a/src/position.h +++ b/src/position.h @@ -103,10 +103,9 @@ class Position { Square square(Color c) const; // Castling - CastlingRights castling_rights(Color c) const; - bool can_castle(CastlingRights cr) const; - bool castling_impeded(CastlingRights cr) const; - Square castling_rook_square(CastlingRights cr) const; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; // Checking Bitboard checkers() const; @@ -248,10 +247,6 @@ inline Square Position::ep_square() const { return st->epSquare; } inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; } -inline CastlingRights Position::castling_rights(Color c) const { - return c & CastlingRights(st->castlingRights); -} - inline bool Position::castling_impeded(CastlingRights cr) const { assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); return pieces() & castlingPath[cr]; From 7a3483fa9e618de10718c1888f61f82f83b955ef Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Wed, 27 Aug 2025 22:32:09 +0200 Subject: [PATCH 1152/1309] Remove superfluous cast closes https://github.com/official-stockfish/Stockfish/pull/6277 No functional change --- src/position.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/position.cpp b/src/position.cpp index 4ac1369d4b9..f5963247511 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -79,7 +79,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { for (Bitboard b = pos.checkers(); b;) os << UCIEngine::square(pop_lsb(b)) << " "; - if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + if (Tablebases::MaxCardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { StateInfo st; From 731ad9bbc3332cb7c73ef9aa8797e38c9deee452 Mon Sep 17 00:00:00 2001 From: Arseniy Surkov <93079612+codedeliveryservice@users.noreply.github.com> Date: Fri, 29 Aug 2025 04:12:23 +0300 Subject: [PATCH 1153/1309] Simplify `adjust_key50` template Remove the AfterMove template from the adjust_key50 function, which is only ever called with false. closes https://github.com/official-stockfish/Stockfish/pull/6278 No functional change --- AUTHORS | 1 + src/position.h | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 804824d8cb9..6bd323d2c15 100644 --- a/AUTHORS +++ b/AUTHORS @@ -32,6 +32,7 @@ Antoine Champion (antoinechampion) Aram Tumanian (atumanian) Arjun Temurnikar Aron Petkovski (fury) +Arseniy Surkov (codedeliveryservice) Artem Solopiy (EntityFX) Auguste Pop Balazs Szilagyi diff --git a/src/position.h b/src/position.h index 56bb7b5a404..dde496fe049 100644 --- a/src/position.h +++ b/src/position.h @@ -183,8 +183,7 @@ class Position { Square& rfrom, Square& rto, DirtyPiece* const dp = nullptr); - template - Key adjust_key50(Key k) const; + Key adjust_key50(Key k) const; // Data members Piece board[SQUARE_NB]; @@ -283,11 +282,10 @@ inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; } inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; } -inline Key Position::key() const { return adjust_key50(st->key); } +inline Key Position::key() const { return adjust_key50(st->key); } -template inline Key Position::adjust_key50(Key k) const { - return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8); + return st->rule50 < 14 ? k : k ^ make_key((st->rule50 - 14) / 8); } inline Key Position::pawn_key() const { return st->pawnKey; } From 38335838627107b0a9363b9b011c85b3c0a4bf19 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 31 Aug 2025 11:35:30 +0200 Subject: [PATCH 1154/1309] limit dynamic reduction. fixes https://github.com/official-stockfish/Stockfish/issues/6280 prevents probcut from extending depth as analyzed here: https://github.com/official-stockfish/Stockfish/pull/6254#issuecomment-3239144280 passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 86688 W: 22591 L: 22426 D: 41671 Ptnml(0-2): 305, 10125, 22311, 10306, 297 https://tests.stockfishchess.org/tests/view/68b418ab467ff96994ae4cd5 passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 82914 W: 21287 L: 21130 D: 40497 Ptnml(0-2): 39, 8959, 23305, 9114, 40 https://tests.stockfishchess.org/tests/view/68b47ffa78ed7a752a9e8f36 closes https://github.com/official-stockfish/Stockfish/pull/6286 Bench: 2787731 Co-Authored-By: xu-shawn <50402888+xu-shawn@users.noreply.github.com> --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index de2ab7a3e16..d2afc480bc8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -917,7 +917,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth dynamicReduction = (ss->staticEval - beta) / 306; + Depth dynamicReduction = std::max((ss->staticEval - beta) / 306, -1); Depth probCutDepth = std::max(depth - 5 - dynamicReduction, 0); while ((move = mp.next_move()) != Move::none()) From da63060ea303bf18bd828114901c791d9061a944 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 27 Aug 2025 11:03:10 -0400 Subject: [PATCH 1155/1309] Simplify sign term in quiet histories Simplify sign term in quiet histories Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 497824 W: 129130 L: 129418 D: 239276 Ptnml(0-2): 1546, 59040, 128008, 58792, 1526 https://tests.stockfishchess.org/tests/view/68a2a9c1b6fb3300203bc3ed Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 70830 W: 18185 L: 18016 D: 34629 Ptnml(0-2): 36, 7658, 19861, 7821, 39 https://tests.stockfishchess.org/tests/view/68af36c96217b8721dca98d9 closes https://github.com/official-stockfish/Stockfish/pull/6282 Bench: 2393762 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d2afc480bc8..70124c589b0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1869,8 +1869,7 @@ void update_quiet_histories( if (ss->ply < LOW_PLY_HISTORY_SIZE) workerThread.lowPlyHistory[ss->ply][move.from_to()] << (bonus * 741 / 1024) + 38; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), - bonus * (bonus > 0 ? 995 : 915) / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] From 75ac6c7a061a8f326d043b4454b15e16164885a6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 24 Aug 2025 21:03:20 -0700 Subject: [PATCH 1156/1309] simplify stalemate further MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 155200 W: 40650 L: 40562 D: 73988 Ptnml(0-2): 533, 17588, 41258, 17700, 521 https://tests.stockfishchess.org/tests/view/68abe11375da51a345a5adec Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82824 W: 21442 L: 21287 D: 40095 Ptnml(0-2): 51, 8738, 23675, 8901, 47 https://tests.stockfishchess.org/tests/view/68b205606217b8721dca9c8e 10k Stalemate: Elo: 1.46 ± 1.2 (95%) LOS: 99.0% Total: 10000 W: 4640 L: 4598 D: 762 Ptnml(0-2): 0, 140, 4678, 182, 0 https://tests.stockfishchess.org/tests/view/68b2059b6217b8721dca9c90 closes https://github.com/official-stockfish/Stockfish/pull/6283 Bench: 2431727 --- src/search.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 70124c589b0..63449268265 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1049,16 +1049,11 @@ Value Search::Worker::search( } // SEE based pruning for captures and checks + // Avoid pruning sacrifices of our last piece for stalemate int margin = std::max(157 * depth + captHist / 29, 0); - if (!pos.see_ge(move, -margin)) - { - bool mayStalemateTrap = - depth > 2 && alpha < 0 && pos.non_pawn_material(us) == PieceValue[movedPiece]; - - // avoid pruning sacrifices of our last piece for stalemate - if (!mayStalemateTrap) - continue; - } + if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece]) + && !pos.see_ge(move, -margin)) + continue; } else { From f2da0ccf3f82c663daf958d05243d75247de6eb2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 24 Aug 2025 15:47:51 -0700 Subject: [PATCH 1157/1309] Simplify SMP Reduction Passed Non-regression SMP STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 232784 W: 59845 L: 59841 D: 113098 Ptnml(0-2): 289, 26459, 62934, 26379, 331 https://tests.stockfishchess.org/tests/view/68ab96bf75da51a345a5acd6 Passed Non-regression SMP LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 374270 W: 95978 L: 96113 D: 182179 Ptnml(0-2): 118, 38575, 109888, 38432, 122 https://tests.stockfishchess.org/tests/view/68abcf1c75da51a345a5adb6 closes https://github.com/official-stockfish/Stockfish/pull/6285 Bench: 2667107 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 63449268265..5a0185618c1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1167,8 +1167,7 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling - r += 671; // Base reduction offset to compensate for other tweaks - r -= (threadIdx % 8) * 64; + r += 543; // Base reduction offset to compensate for other tweaks r -= moveCount * 66; r -= std::abs(correctionValue) / 30450; From adfddd2c984fac5f2ac02d87575af821ec118fa8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 31 Aug 2025 18:56:01 -0700 Subject: [PATCH 1158/1309] Add scaling note to STC/LTC tunes It has been repeatedly shown that such tunes are suspectible to become anti-scaling. Below are some recent examples: https://github.com/official-stockfish/Stockfish/commit/2e91a8635468e40c89a2303ce50384864d088611 https://github.com/official-stockfish/Stockfish/commit/d11f49b790429c236a1a4169f0d8052635fc03dc Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 32448 W: 8651 L: 8342 D: 15455 Ptnml(0-2): 81, 3695, 8408, 3914, 126 https://tests.stockfishchess.org/tests/view/6899489b0049e8ccef9d64ad Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 73854 W: 19042 L: 18649 D: 36163 Ptnml(0-2): 37, 7908, 20659, 8271, 52 https://tests.stockfishchess.org/tests/view/689abbe7fd8719b088c8d514 Revert VVLTC with STC bound: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 53802 W: 14030 L: 13740 D: 26032 Ptnml(0-2): 5, 4924, 16754, 5212, 6 https://tests.stockfishchess.org/tests/view/68a9a9f575da51a345a5a675 Revert VVLTC with LTC bound: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 53658 W: 14022 L: 13699 D: 25937 Ptnml(0-2): 3, 4894, 16712, 5217, 3 https://tests.stockfishchess.org/tests/view/68a8d2b2b6fb3300203bca77 https://tests.stockfishchess.org/tests/view/688cf38bf17748b4d23c8057 https://tests.stockfishchess.org/tests/view/6890bc7792fcad741b804a19 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 74928 W: 19466 L: 19071 D: 36391 Ptnml(0-2): 37, 8048, 20901, 8439, 39 Failed Non-regression VLTC: LLR: -2.94 (-2.94,2.94) <-1.75,0.25> Total: 57704 W: 14643 L: 14928 D: 28133 Ptnml(0-2): 5, 5925, 17280, 5634, 8 https://tests.stockfishchess.org/tests/view/6890bc7792fcad741b804a19 (Note that an STC-tuned version passed non-regression, but was shortly simplified) https://github.com/official-stockfish/Stockfish/pull/6040 Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 27776 W: 7352 L: 7054 D: 13370 Ptnml(0-2): 68, 3126, 7221, 3386, 87 https://tests.stockfishchess.org/tests/view/680ec0f83629b02d74b1605b Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 161304 W: 41432 L: 40864 D: 79008 Ptnml(0-2): 61, 17305, 45357, 17863, 66 https://tests.stockfishchess.org/tests/view/680ec7f93629b02d74b16084 Failed Non-regression VVLTC: LLR: -2.94 (-2.94,2.94) <-1.75,0.25> Total: 313466 W: 80573 L: 81089 D: 151804 Ptnml(0-2): 38, 29689, 97782, 29199, 25 https://tests.stockfishchess.org/tests/view/6810d0533629b02d74b16756 https://github.com/official-stockfish/Stockfish/pull/5907 https://github.com/official-stockfish/Stockfish/pull/5887 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 Revert VVLTC w/ STC bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 56342 W: 14536 L: 14246 D: 27560 Ptnml(0-2): 7, 5061, 17741, 5359, 3 https://tests.stockfishchess.org/tests/view/67be4f8ad8d5c2c657c52d10 Revert VVLTC w/ LTC bounds: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66562 W: 17364 L: 17016 D: 32182 Ptnml(0-2): 3, 6145, 20637, 6493, 3 https://tests.stockfishchess.org/tests/view/67bcd25ff6b602bd7222ea40 closes https://github.com/official-stockfish/Stockfish/pull/6284 no functional change --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 5a0185618c1..40676e2e2b2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -72,6 +72,9 @@ using SearchedList = ValueList; // so changing them or adding conditions that are similar requires // tests at these types of time controls. +// (*Scaler) All tuned parameters at time controls shorter than +// optimized for require verifications at longer time controls + 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; From d5f152b5df14bb27bbf2006f877dd9c5c51a8639 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 2 Sep 2025 17:15:22 -0700 Subject: [PATCH 1159/1309] Reintroduce #6259 Reintroduces af181d9, which no longer regresses on matetrack after #6286 Using ./stockfish on matetrack.epd with --nodes 1000000 Engine ID: Stockfish dev-20250902-adfddd2c Total FENs: 6554 Found mates: 3490 Best mates: 2429 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 37608 W: 9726 L: 9523 D: 18359 Ptnml(0-2): 16, 4001, 10578, 4182, 27 https://tests.stockfishchess.org/tests/view/68b886778f94a4e5a7fe77d9 closes https://github.com/official-stockfish/Stockfish/pull/6289 Bench: 2493363 --- src/movepick.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b4523463fae..4b01beb6741 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -115,9 +115,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo threshold(th) { assert(!pos.checkers()); - // Removing the SEE check passes as simplification, but hurts mate finding - stage = PROBCUT_TT - + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold)); + stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm)); } // Assigns a numerical value to each move in a list, used for sorting. From bfc7000597b8b5b0899e99bc911f6120a75c6297 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 24 Aug 2025 20:28:08 +0200 Subject: [PATCH 1160/1309] Adjustment of the aspiration window after fail high/low. For the new bound in the opposite direction of the fail, use a weighted average of alpha, beta and best value +- delta. In the case of a fail high, different average weights are used depending on whether or not there was a best move change during the last search. The weights are determined from the following two consecutive LTC tunings. First tuning: https://tests.stockfishchess.org/tests/view/68ab727975da51a345a5ac2e Second tuning: https://tests.stockfishchess.org/tests/view/68aba3fe75da51a345a5ad52 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 39504 W: 10243 L: 9947 D: 19314 Ptnml(0-2): 25, 4182, 11041, 4480, 24 https://tests.stockfishchess.org/tests/view/68acbb6d6217b8721dca95f8 Passed VLTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 91196 W: 23574 L: 23167 D: 44455 Ptnml(0-2): 13, 8943, 27276, 9356, 10 https://tests.stockfishchess.org/tests/view/68af64786217b8721dca993d closes https://github.com/official-stockfish/Stockfish/pull/6292 Bench: 2785713 --- src/search.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 40676e2e2b2..9c4a94df833 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -344,7 +344,8 @@ void Search::Worker::iterative_deepening() { // effective increment for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - rootDelta = beta - alpha; + rootDelta = beta - alpha; + size_t previousBestMoveChanges = bestMoveChanges; bestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting @@ -372,7 +373,9 @@ void Search::Worker::iterative_deepening() { // otherwise exit the loop. if (bestValue <= alpha) { - beta = alpha; + beta = + (alpha * 123 + beta * 9 + std::min(bestValue + delta, VALUE_INFINITE) * 12) + / 144; alpha = std::max(bestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; @@ -381,6 +384,15 @@ void Search::Worker::iterative_deepening() { } else if (bestValue >= beta) { + if (bestMoveChanges > previousBestMoveChanges) + alpha = + (alpha * 116 + beta + std::max(bestValue - delta, -VALUE_INFINITE) * 7) + / 124; + else + alpha = (alpha * 119 + beta * 6 + + std::max(bestValue - delta, -VALUE_INFINITE) * 16) + / 141; + beta = std::min(bestValue + delta, VALUE_INFINITE); ++failedHighCnt; } From fc54d8730174cdb5cfc4f7074b90128e706e4040 Mon Sep 17 00:00:00 2001 From: Syine Mineta Date: Thu, 11 Sep 2025 13:53:40 +0900 Subject: [PATCH 1161/1309] Revert "Adjustment of the aspiration window after fail high/low." This reverts commit bfc7000597b8b5b0899e99bc911f6120a75c6297. Fixes https://github.com/official-stockfish/Stockfish/issues/6296 After this commit a bug has been reported when the position is close to mate or being mated. Since no workarounds have been suggested yet, it is best to revert this commit until a better solution is found. closes https://github.com/official-stockfish/Stockfish/pull/6309 Bench: 2493363 --- src/search.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9c4a94df833..40676e2e2b2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -344,8 +344,7 @@ void Search::Worker::iterative_deepening() { // effective increment for every four searchAgain steps (see issue #2717). Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4); - rootDelta = beta - alpha; - size_t previousBestMoveChanges = bestMoveChanges; + rootDelta = beta - alpha; bestValue = search(rootPos, ss, alpha, beta, adjustedDepth, false); // Bring the best move to the front. It is critical that sorting @@ -373,9 +372,7 @@ void Search::Worker::iterative_deepening() { // otherwise exit the loop. if (bestValue <= alpha) { - beta = - (alpha * 123 + beta * 9 + std::min(bestValue + delta, VALUE_INFINITE) * 12) - / 144; + beta = alpha; alpha = std::max(bestValue - delta, -VALUE_INFINITE); failedHighCnt = 0; @@ -384,15 +381,6 @@ void Search::Worker::iterative_deepening() { } else if (bestValue >= beta) { - if (bestMoveChanges > previousBestMoveChanges) - alpha = - (alpha * 116 + beta + std::max(bestValue - delta, -VALUE_INFINITE) * 7) - / 124; - else - alpha = (alpha * 119 + beta * 6 - + std::max(bestValue - delta, -VALUE_INFINITE) * 16) - / 141; - beta = std::min(bestValue + delta, VALUE_INFINITE); ++failedHighCnt; } From 1a3a9c90053f87c4b386efbb2e80153fd5b9a773 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 7 Sep 2025 12:41:02 +0200 Subject: [PATCH 1162/1309] simplify upload_binaries ci Previously the upload step used the os of the artifact to create the upload and used an extra msys2 step. This is in fact not needed and we can do all required changes on ubuntu instead. closes https://github.com/official-stockfish/Stockfish/pull/6294 No functional change --- .github/workflows/upload_binaries.yml | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 1067f6e7615..073e40a13a1 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -12,20 +12,17 @@ on: jobs: Artifacts: name: ${{ matrix.config.name }} ${{ matrix.binaries }} - runs-on: ${{ matrix.config.os }} + runs-on: ubuntu-latest env: - COMPCXX: ${{ matrix.config.compiler }} - COMP: ${{ matrix.config.comp }} EXT: ${{ matrix.config.ext }} NAME: ${{ matrix.config.simple_name }} BINARY: ${{ matrix.binaries }} - SDE: ${{ matrix.config.sde }} strategy: fail-fast: false matrix: ${{ fromJson(inputs.matrix) }} defaults: run: - shell: ${{ matrix.config.shell }} + shell: bash steps: - uses: actions/checkout@v4 with: @@ -37,13 +34,6 @@ jobs: name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }} - - name: Setup msys and install required packages - if: runner.os == 'Windows' - uses: msys2/setup-msys2@v2 - with: - msystem: ${{ matrix.config.msys_sys }} - install: mingw-w64-${{ matrix.config.msys_env }} make git zip - - name: Create Package run: | mkdir stockfish @@ -69,13 +59,13 @@ jobs: cp CONTRIBUTING.md ../stockfish/ - name: Create tar - if: runner.os != 'Windows' + if: ${{ !startsWith(matrix.config.os, 'windows') }} run: | chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT tar -cvf stockfish-$NAME-$BINARY.tar stockfish - name: Create zip - if: runner.os == 'Windows' + if: ${{ startsWith(matrix.config.os, 'windows') }} run: | zip -r stockfish-$NAME-$BINARY.zip stockfish From f922fb5d554b9f0d105f2b022a1028d76ad11b7e Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sun, 7 Sep 2025 18:43:22 +0200 Subject: [PATCH 1163/1309] add multithreaded matetrack runs to CI Add runs with --threads 4 to our matetrack CI. These won't be deterministic, of course. But they may catch early some multithreading bugs in our mate reporting. Motivated by #6293 and #6296. https://github.com/official-stockfish/Stockfish/pull/6297 No functional change. --- .github/workflows/matetrack.yml | 39 ++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index 85c2be3e7e8..c4d14fc7bde 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -47,25 +47,44 @@ jobs: wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-wdl/ wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-dtz/ - - name: Run matetrack + - name: Run matetrack th1 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 - ! grep "issues were detected" matecheckout.out > /dev/null + 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 matecheckout1.out + ! grep "issues were detected" matecheckout1.out > /dev/null - - name: Run matetrack with --syzygy50MoveRule false + - name: Run matetrack th4 + 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 --threads 4 | tee matecheckout4.out + ! grep "issues were detected" matecheckout4.out > /dev/null + + - name: Run matetrack th1 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 matecheckcursed1.out + ! grep "issues were detected" matecheckcursed1.out > /dev/null + + - name: Run matetrack th4 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 + 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 --threads 4 --syzygy50MoveRule false | tee matecheckcursed4.out + ! grep "issues were detected" matecheckcursed4.out > /dev/null - - name: Verify mate and TB win count for matecheckcursed.out + - name: Verify mate and TB win count for matecheckcursed[14].out working-directory: matetrack run: | - mates=$(grep "Found mates:" matecheckcursed.out | awk '{print $3}') - tbwins=$(grep "Found TB wins:" matecheckcursed.out | awk '{print $4}') + mates=$(grep "Found mates:" matecheckcursed1.out | awk '{print $3}') + tbwins=$(grep "Found TB wins:" matecheckcursed1.out | awk '{print $4}') + if [ $(($mates + $tbwins)) -ne 32 ]; then + echo "Sum of mates and TB wins is not 32 in matecheckcursed1.out" >&2 + exit 1 + fi + mates=$(grep "Found mates:" matecheckcursed4.out | awk '{print $3}') + tbwins=$(grep "Found TB wins:" matecheckcursed4.out | awk '{print $4}') if [ $(($mates + $tbwins)) -ne 32 ]; then - echo "Sum of mates and TB wins is not 32 in matecheckcursed.out" >&2 + echo "Sum of mates and TB wins is not 32 in matecheckcursed4.out" >&2 exit 1 fi From a82e2a4cb6154a985c1fbf61f74ab56bd097cc1c Mon Sep 17 00:00:00 2001 From: Sebastian Buchwald Date: Sat, 27 Sep 2025 20:35:20 +0200 Subject: [PATCH 1164/1309] Replace deprecated macOS 13 runner image The macOS 13 runner image will be retired by December 4th, 2025. closes https://github.com/official-stockfish/Stockfish/pull/6330 No functional change --- .github/ci/matrix.json | 22 +++++++++++----------- .github/workflows/tests.yml | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index 436cb4b8446..d916fd07117 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -11,8 +11,8 @@ "sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-lin/sde -future --" }, { - "name": "MacOS 13 Apple Clang", - "os": "macos-13", + "name": "macOS 15 Apple Clang", + "os": "macos-15-intel", "simple_name": "macos", "compiler": "clang++", "comp": "clang", @@ -20,7 +20,7 @@ "archive_ext": "tar" }, { - "name": "MacOS 14 Apple Clang M1", + "name": "macOS 14 Apple Clang M1", "os": "macos-14", "simple_name": "macos-m1", "compiler": "clang++", @@ -126,31 +126,31 @@ { "binaries": "x86-64-avxvnni", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { "binaries": "x86-64-avx512", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { "binaries": "x86-64-vnni256", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { "binaries": "x86-64-vnni512", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { "binaries": "x86-64-avx512icl", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { @@ -234,7 +234,7 @@ { "binaries": "apple-silicon", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { @@ -258,7 +258,7 @@ { "binaries": "armv8", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { @@ -288,7 +288,7 @@ { "binaries": "armv8-dotprod", "config": { - "os": "macos-13" + "os": "macos-15-intel" } }, { diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 95ca1209257..c2280f0b427 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,21 +56,21 @@ jobs: base_image: "ppc64le/alpine:latest" platform: linux/ppc64le shell: bash - - name: MacOS 13 Apple Clang - os: macos-13 + - name: macOS 15 Apple Clang + os: macos-15-intel compiler: clang++ comp: clang run_64bit_tests: true shell: bash - - name: MacOS 14 Apple Clang M1 + - name: macOS 14 Apple Clang M1 os: macos-14 compiler: clang++ comp: clang run_64bit_tests: false run_m1_tests: true shell: bash - - name: MacOS 13 GCC 11 - os: macos-13 + - name: macOS 15 GCC 11 + os: macos-15-intel compiler: g++-11 comp: gcc run_64bit_tests: true From a47a1c1804f1382c01b206442cf386435b446e99 Mon Sep 17 00:00:00 2001 From: Syine Mineta Date: Thu, 11 Sep 2025 09:00:01 +0900 Subject: [PATCH 1165/1309] Penalty to TT move history on MultiCut If a reduced search fails high with the TT move excluded, we know that there are multiple moves that can produce cutoffs. Applying a penalty to the TT move history reduces extensions for TT moves so that it may spend a bit more time exploring other moves. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 263680 W: 68965 L: 68313 D: 126402 Ptnml(0-2): 855, 31090, 67336, 31666, 893 https://tests.stockfishchess.org/tests/view/68c1f65a59efc3c96b6110e5 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 61008 W: 15713 L: 15350 D: 29945 Ptnml(0-2): 27, 6428, 17235, 6783, 31 https://tests.stockfishchess.org/tests/view/68c1fc5359efc3c96b611141 Passed non-regression VLTC (60+0.6, Threads=8): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 50340 W: 13003 L: 12830 D: 24507 Ptnml(0-2): 3, 4570, 15849, 4747, 1 https://tests.stockfishchess.org/tests/view/68c2056559efc3c96b6111c3 closes https://github.com/official-stockfish/Stockfish/pull/6305 Bench: 2342975 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 40676e2e2b2..0a4a390bb92 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1137,7 +1137,10 @@ Value Search::Worker::search( // singular (multiple moves fail high), and we can prune the whole // subtree by returning a softbound. else if (value >= beta && !is_decisive(value)) + { + ttMoveHistory << std::max(-400 - 100 * depth, -4000); return value; + } // Negative extensions // If other moves failed high over (ttValue - margin) without the From bbad001a4921e8351b42d6231b7d27c8c3ac9446 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 11 Sep 2025 03:04:53 +0300 Subject: [PATCH 1166/1309] Double PawnHistory size and update formula Doubling PAWN_HISTORY_SIZE to 1024. So with that, we can apply a stronger learning signal. The bonus/malus multipliers in the update_quiet_histories function have been increased accordingly. Passed STC: LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 111008 W: 29136 L: 28708 D: 53164 Ptnml(0-2): 367, 12870, 28609, 13284, 374 https://tests.stockfishchess.org/tests/view/68c201d659efc3c96b61117e Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 117210 W: 30142 L: 29664 D: 57404 Ptnml(0-2): 49, 12532, 32970, 13000, 54 https://tests.stockfishchess.org/tests/view/68c20a6259efc3c96b6111ef closes https://github.com/official-stockfish/Stockfish/pull/6306 Bench: 2788334 --- src/history.h | 2 +- src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/history.h b/src/history.h index faf4af3d7fb..1f7abc542ba 100644 --- a/src/history.h +++ b/src/history.h @@ -33,7 +33,7 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 1024; // has to be a power of 2 constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; diff --git a/src/search.cpp b/src/search.cpp index 0a4a390bb92..25102b131d7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1873,7 +1873,7 @@ void update_quiet_histories( int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << (bonus * (bonus > 0 ? 704 : 439) / 1024) + 70; + << (bonus * (bonus > 0 ? 800 : 500) / 1024) + 70; } } From 6fa42d9724b5bd21c5e20f578bb362be01afa0f5 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Fri, 12 Sep 2025 21:03:50 +0200 Subject: [PATCH 1167/1309] Simplify RFP return value Passed non-regression STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 220800 W: 57351 L: 57332 D: 106117 Ptnml(0-2): 726, 26200, 56548, 26181, 745 https://tests.stockfishchess.org/tests/view/68b1db6e6217b8721dca9c67 Passed gainer LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 257820 W: 66286 L: 65523 D: 126011 Ptnml(0-2): 118, 27586, 72779, 28269, 158 https://tests.stockfishchess.org/tests/view/68c1d40859efc3c96b610e3d closes https://github.com/official-stockfish/Stockfish/pull/6310 bench: 2364596 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 25102b131d7..fbf4934869f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -856,7 +856,7 @@ Value Search::Worker::search( if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) - return beta + (eval - beta) / 3; + return (2 * beta + eval) / 3; } // Step 9. Null move search with verification search From 4f4f78f86e8d44df2c13cfd6671f777dad516067 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sat, 20 Sep 2025 09:29:19 +0300 Subject: [PATCH 1168/1309] Extend nodes pre qsearch only with deep enough tt entries Modification of current pre qsearch extensions - allowing it only for deep enough tt entries. Passed STC: https://tests.stockfishchess.org/tests/view/68c954d302c43c969fe7eea5 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 35872 W: 9548 L: 9236 D: 17088 Ptnml(0-2): 101, 4075, 9295, 4341, 124 Passed LTC: https://tests.stockfishchess.org/tests/view/68ca4e4f02c43c969fe7ef5f LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 107754 W: 27784 L: 27324 D: 52646 Ptnml(0-2): 47, 11528, 30300, 11922, 80 closes https://github.com/official-stockfish/Stockfish/pull/6324 bench: 2462792 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fbf4934869f..1ef01e5a626 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1260,7 +1260,7 @@ Value Search::Worker::search( (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && rootDepth > 8) + if (move == ttData.move && ttData.depth > 1 && rootDepth > 8) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); From 9b164d952061213da4fc0f7ac8646e44e8a77cd5 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sat, 27 Sep 2025 08:40:42 -0700 Subject: [PATCH 1169/1309] Shave some instructions off a hot loop in affine transform On x86, GCC generates highly suboptimal code for this loop in its old form, about 2x as many instructions as necessary. This decreases throughput especially in an SMT setting. Clang does a better job but this change still has some improvement. Note that the std::ptrdiff_t type is not optional; using an unsigned type brings back the bad assembly. (Not sure why, but it seems reliable on all the GCC versions I tested.) passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 44672 W: 11841 L: 11527 D: 21304 Ptnml(0-2): 165, 4625, 12415, 4993, 138 https://tests.stockfishchess.org/tests/view/68d8111efa806e2e8393b10e closes https://github.com/official-stockfish/Stockfish/pull/6331 No functional change --- AUTHORS | 1 + src/nnue/layers/affine_transform_sparse_input.h | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6bd323d2c15..1fb91adaf02 100644 --- a/AUTHORS +++ b/AUTHORS @@ -243,6 +243,7 @@ Thanar2 thaspel theo77186 TierynnB +Timothy Herchen (anematode) Ting-Hsuan Huang (fffelix-huang) Tobias Steinmann Tomasz Sobczyk (Sopel97) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index a073c61969e..11e46066634 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -22,6 +22,7 @@ #define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED #include +#include #include #include @@ -287,12 +288,18 @@ class AffineTransformSparseInput { for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasvec[k]; - for (IndexType j = 0; j < count; ++j) + auto* start = nnz; + auto* end = nnz + count; + + // convince GCC to not do weird pointer arithmetic in the following loop + const std::int8_t* weights_cp = weights; + + while (start < end) { - const auto i = nnz[j]; - const invec_t in = vec_set_32(input32[i]); - const auto col = - reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + const std::ptrdiff_t i = *start; + start++; + const invec_t in = vec_set_32(input32[i]); + const auto col = (const invec_t*) (&weights_cp[i * OutputDimensions * ChunkSize]); for (IndexType k = 0; k < NumRegs; ++k) vec_add_dpbusd_32(acc[k], in, col[k]); } From 7a36c0e95fba5c544014b440fb2f35ef73e50393 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 2 Sep 2025 14:10:11 -0700 Subject: [PATCH 1170/1309] Remove quiet move streak Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67712 W: 17744 L: 17555 D: 32413 Ptnml(0-2): 204, 8030, 17274, 8069, 279 https://tests.stockfishchess.org/tests/view/68b784628f94a4e5a7fe7706 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 196050 W: 50270 L: 50228 D: 95552 Ptnml(0-2): 122, 21465, 54813, 21499, 126 https://tests.stockfishchess.org/tests/view/68ba119d8f94a4e5a7fe7941 closes https://github.com/official-stockfish/Stockfish/pull/6299 Bench: 2238789 Co-authored-by: Daniel Monroe --- src/search.cpp | 6 +----- src/search.h | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1ef01e5a626..9b6bbe188c6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1010,8 +1010,6 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); givesCheck = pos.gives_check(move); - (ss + 1)->quietMoveStreak = capture ? 0 : (ss->quietMoveStreak + 1); - // Calculate new depth for this move newDepth = depth - 1; @@ -1173,7 +1171,7 @@ Value Search::Worker::search( // These reduction adjustments have no proven non-linear scaling - r += 543; // Base reduction offset to compensate for other tweaks + r += 843; // Base reduction offset to compensate for other tweaks r -= moveCount * 66; r -= std::abs(correctionValue) / 30450; @@ -1189,8 +1187,6 @@ Value Search::Worker::search( if ((ss + 1)->cutoffCnt > 2) r += 1051 + allNode * 814; - r += (ss + 1)->quietMoveStreak * 50; - // For first picked move (ttMove) reduce reduction if (move == ttData.move) r -= 2018; diff --git a/src/search.h b/src/search.h index 07fc743173f..d4bbca5c614 100644 --- a/src/search.h +++ b/src/search.h @@ -75,7 +75,6 @@ struct Stack { bool ttHit; int cutoffCnt; int reduction; - int quietMoveStreak; }; From 415a1ad42658dfc58ad1f6913f9dde6d069b37f3 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 2 Sep 2025 13:10:00 -0700 Subject: [PATCH 1171/1309] Simplify Probcut Clamp Further Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 157984 W: 41116 L: 41030 D: 75838 Ptnml(0-2): 568, 18570, 40601, 18714, 539 https://tests.stockfishchess.org/tests/view/68b750518f94a4e5a7fe76cd Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 335232 W: 85443 L: 85543 D: 164246 Ptnml(0-2): 177, 36616, 94137, 36502, 184 https://tests.stockfishchess.org/tests/view/68bc767259efc3c96b61076b closes https://github.com/official-stockfish/Stockfish/pull/6303 Bench: 2213844 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9b6bbe188c6..888a8d87028 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -920,8 +920,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth dynamicReduction = std::max((ss->staticEval - beta) / 306, -1); - Depth probCutDepth = std::max(depth - 5 - dynamicReduction, 0); + Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 306, 0, depth); while ((move = mp.next_move()) != Move::none()) { From 40aeb5a4118abc100987fab13a3329863b8d45c5 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 11 Sep 2025 00:06:22 +0300 Subject: [PATCH 1172/1309] Simplify away conthist 0 While at it, I also added the scaler note to the Lmrdepth/history formula. Passed STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 25376 W: 6660 L: 6423 D: 12293 Ptnml(0-2): 77, 2947, 6403, 3184, 77 https://tests.stockfishchess.org/tests/view/68c1ccf759efc3c96b610deb Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 208464 W: 53371 L: 53342 D: 101751 Ptnml(0-2): 110, 22776, 58426, 22815, 105 https://tests.stockfishchess.org/tests/view/68c1d04b59efc3c96b610e13 closes https://github.com/official-stockfish/Stockfish/pull/6304 Bench: 2029296 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 888a8d87028..13d86125a29 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,6 +1067,7 @@ Value Search::Worker::search( history += 76 * mainHistory[us][move.from_to()] / 32; + // (*Scaler): Generally, a lower divisor scales well lmrDepth += history / 3220; Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth @@ -1630,9 +1631,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Continuation history based pruning if (!capture - && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + pawnHistory[pawn_history_index(pos)][pos.moved_piece(move)][move.to_sq()] - <= 5475) + && pawnHistory[pawn_history_index(pos)][pos.moved_piece(move)][move.to_sq()] < 7300) continue; // Do not search moves with bad enough SEE values From c62e71e78f605bd66667289f2223429b3817eeab Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 10 Sep 2025 17:57:03 -0700 Subject: [PATCH 1173/1309] Simplify a separate term in low ply history bonus formula Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 211200 W: 54887 L: 54860 D: 101453 Ptnml(0-2): 719, 24894, 54296, 25023, 668 https://tests.stockfishchess.org/tests/view/68c21e7f59efc3c96b6112c8 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 217842 W: 55587 L: 55568 D: 106687 Ptnml(0-2): 130, 23651, 61313, 23724, 103 https://tests.stockfishchess.org/tests/view/68c230ec59efc3c96b61135a closes https://github.com/official-stockfish/Stockfish/pull/6307 Bench: 2070860 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 13d86125a29..0684ea5cb4a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1861,7 +1861,7 @@ 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 * 741 / 1024) + 38; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 761 / 1024; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); From 3073d82ccf25a8ab31e0e0215a2a661d0dcdadd7 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 10 Sep 2025 20:17:08 -0700 Subject: [PATCH 1174/1309] Simplify use of low-ply history in evasions Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 65024 W: 16991 L: 16804 D: 31229 Ptnml(0-2): 182, 7423, 17119, 7602, 186 https://tests.stockfishchess.org/tests/view/68c23f5459efc3c96b6113df Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 207312 W: 53126 L: 53095 D: 101091 Ptnml(0-2): 126, 21986, 59389, 22041, 114 https://tests.stockfishchess.org/tests/view/68c241e359efc3c96b6113ef closes https://github.com/official-stockfish/Stockfish/pull/6308 Bench: 2515619 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 4b01beb6741..e7ac44c15a4 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -188,7 +188,7 @@ ExtMove* MovePicker::score(MoveList& ml) { { m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 2 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); + m.value += 2 * (*lowPlyHistory)[ply][m.from_to()]; } } } From 5895f47dab6aee100bc274f870b275e20982a086 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 10 Sep 2025 21:10:07 -0700 Subject: [PATCH 1175/1309] Further simplify low ply history in evasions Passed Non-regression STC (vs #6308): LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 174208 W: 45414 L: 45343 D: 83451 Ptnml(0-2): 633, 20324, 45095, 20443, 609 https://tests.stockfishchess.org/tests/view/68c24be359efc3c96b611487 Passed Non-regression LTC (vs #6308): LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 110070 W: 28099 L: 27969 D: 54002 Ptnml(0-2): 56, 11919, 30962, 12035, 63 https://tests.stockfishchess.org/tests/view/68c4efa559efc3c96b611dfc closes https://github.com/official-stockfish/Stockfish/pull/6321 Bench: 2151873 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index e7ac44c15a4..2eec3556b76 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -188,7 +188,7 @@ ExtMove* MovePicker::score(MoveList& ml) { { m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 2 * (*lowPlyHistory)[ply][m.from_to()]; + m.value += (*lowPlyHistory)[ply][m.from_to()]; } } } From 5c93616a3f7517c69eef55cdea67d6f04da63ce9 Mon Sep 17 00:00:00 2001 From: nicolasduhamel Date: Thu, 2 Oct 2025 20:17:32 +0200 Subject: [PATCH 1176/1309] Adjust aspiration window Narrow the aspiration window after fail high. Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 51296 W: 13550 L: 13207 D: 24539 Ptnml(0-2): 165, 5971, 13052, 6276, 184 https://tests.stockfishchess.org/tests/view/68d99afffa806e2e8393b7ae Passed LTC; LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 87780 W: 22795 L: 22375 D: 42610 Ptnml(0-2): 52, 9340, 24694, 9744, 60 https://tests.stockfishchess.org/tests/view/68dae0a6fa806e2e8393baad See the comments in #6293 discussing the mechanisms leading to issue #6296 closes https://github.com/official-stockfish/Stockfish/pull/6337 Bench: 2336606 --- AUTHORS | 1 + src/search.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1fb91adaf02..0429f9f0a00 100644 --- a/AUTHORS +++ b/AUTHORS @@ -183,6 +183,7 @@ Nathan Rugg (nmrugg) Nguyen Pham (nguyenpham) Nicklas Persson (NicklasPersson) Nick Pelling (nickpelling) +Nicolas Duhamel (nikloskoda) Niklas Fiekas (niklasf) Nikolay Kostov (NikolayIT) Norman Schmidt (FireFather) diff --git a/src/search.cpp b/src/search.cpp index 0684ea5cb4a..04c04b5be58 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -381,7 +381,8 @@ void Search::Worker::iterative_deepening() { } else if (bestValue >= beta) { - beta = std::min(bestValue + delta, VALUE_INFINITE); + alpha = std::max(beta - delta, alpha); + beta = std::min(bestValue + delta, VALUE_INFINITE); ++failedHighCnt; } else From 7a7c033a86be1c14817cfa1de2c85937585526b6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 3 Oct 2025 01:39:39 +0300 Subject: [PATCH 1177/1309] Tweak Correction History Bonus Asymmetrically Refine the correction history update by applying an asymmetric bonus based on the type of evaluation error. It differentiates between negative corrections and positive corrections. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 165184 W: 43314 L: 42807 D: 79063 Ptnml(0-2): 551, 19391, 42261, 19778, 611 https://tests.stockfishchess.org/tests/view/68cae49902c43c969fe7f008 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 243234 W: 62765 L: 62029 D: 118440 Ptnml(0-2): 163, 25996, 68551, 26756, 151 https://tests.stockfishchess.org/tests/view/68d1c50dfa806e2e8393aa1f closes https://github.com/official-stockfish/Stockfish/pull/6338 Bench: 2746404 --- src/search.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 04c04b5be58..64f8ea1897d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1449,9 +1449,11 @@ Value Search::Worker::search( && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { - auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, - -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - update_correction_history(pos, ss, *this, bonus); + auto bonus = + std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + update_correction_history(pos, ss, *this, + (1088 - 180 * (bestValue > ss->staticEval)) * bonus / 1024); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); From b09339a4205c3066fcddbae1490fb5f7a524d839 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Fri, 3 Oct 2025 12:41:21 -0700 Subject: [PATCH 1178/1309] Split accumulator 3-Way Squeeze a tiny bit more juice from the original idea in #6336 which this is on top of. https://tests.stockfishchess.org/tests/view/68dddd85fa806e2e8393c0b9 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 156320 W: 40925 L: 40447 D: 74948 Ptnml(0-2): 427, 17330, 42172, 17800, 431 4-way doesn't look to be better than this. https://tests.stockfishchess.org/tests/view/68dde19efa806e2e8393c0c1 closes https://github.com/official-stockfish/Stockfish/pull/6339 No functional change Co-authored-by: M Stembera --- .../layers/affine_transform_sparse_input.h | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 11e46066634..effda826b84 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -248,6 +248,7 @@ class AffineTransformSparseInput { #if defined(USE_AVX512) using invec_t = __m512i; using outvec_t = __m512i; + #define vec_add_32 _mm512_add_epi32 #define vec_set_32 _mm512_set1_epi32 #define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32 #elif defined(USE_AVX2) @@ -274,7 +275,16 @@ class AffineTransformSparseInput { static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; - constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth; + // If there's only one accumulator and we're using high-latency dot product instructions, + // split it to create three separate dependency chains and merge at the end + constexpr bool SplitAccums = + #if defined(USE_VNNI) + NumAccums == 1; + #else + false; + #endif + constexpr IndexType NumRegs = SplitAccums ? 3 * NumAccums : NumAccums; std::uint16_t nnz[NumChunks]; IndexType count; @@ -285,27 +295,50 @@ class AffineTransformSparseInput { const outvec_t* biasvec = reinterpret_cast(biases); outvec_t acc[NumRegs]; - for (IndexType k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < NumAccums; ++k) acc[k] = biasvec[k]; - auto* start = nnz; - auto* end = nnz + count; + const auto* start = nnz; + const auto* end = nnz + count; // convince GCC to not do weird pointer arithmetic in the following loop const std::int8_t* weights_cp = weights; + if constexpr (SplitAccums) + { + acc[1] = acc[2] = vec_set_32(0); + while (start < end - 2) + { + const std::ptrdiff_t i0 = *start++; + const std::ptrdiff_t i1 = *start++; + const std::ptrdiff_t i2 = *start++; + const invec_t in0 = vec_set_32(input32[i0]); + const invec_t in1 = vec_set_32(input32[i1]); + const invec_t in2 = vec_set_32(input32[i2]); + const auto col0 = + reinterpret_cast(&weights_cp[i0 * OutputDimensions * ChunkSize]); + const auto col1 = + reinterpret_cast(&weights_cp[i1 * OutputDimensions * ChunkSize]); + const auto col2 = + reinterpret_cast(&weights_cp[i2 * OutputDimensions * ChunkSize]); + vec_add_dpbusd_32(acc[0], in0, *col0); + vec_add_dpbusd_32(acc[1], in1, *col1); + vec_add_dpbusd_32(acc[2], in2, *col2); + } + acc[0] = vec_add_32(vec_add_32(acc[0], acc[1]), acc[2]); + } while (start < end) { - const std::ptrdiff_t i = *start; - start++; - const invec_t in = vec_set_32(input32[i]); - const auto col = (const invec_t*) (&weights_cp[i * OutputDimensions * ChunkSize]); - for (IndexType k = 0; k < NumRegs; ++k) + const std::ptrdiff_t i = *start++; + const invec_t in = vec_set_32(input32[i]); + const auto col = + reinterpret_cast(&weights_cp[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumAccums; ++k) vec_add_dpbusd_32(acc[k], in, col[k]); } outvec_t* outptr = reinterpret_cast(output); - for (IndexType k = 0; k < NumRegs; ++k) + for (IndexType k = 0; k < NumAccums; ++k) outptr[k] = acc[k]; #undef vec_set_32 #undef vec_add_dpbusd_32 From e5c2dc5edd088fc614f0014219c8271840565c0c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 29 Sep 2025 19:59:17 -0700 Subject: [PATCH 1179/1309] remove clang-format workaround closes https://github.com/official-stockfish/Stockfish/pull/6332 No functional change --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 64f8ea1897d..6b2c28da2ba 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -585,10 +585,7 @@ Value Search::Worker::search( // Dive into quiescence search when the depth reaches zero if (depth <= 0) - { - constexpr auto nt = PvNode ? PV : NonPV; - return qsearch(pos, ss, alpha, beta); - } + return qsearch(pos, ss, alpha, beta); // Limit the depth if extensions made it too large depth = std::min(depth, MAX_PLY - 1); From ee243f0fdccd943fbba5a0eb5afa3531c23feffa Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 4 Oct 2025 15:12:25 -0700 Subject: [PATCH 1180/1309] Remove x86-64-vnni256 target When vnni256 was first introduced #3038 it was very slightly faster than vnni512 on some machines. We have since sped up vnni512 significantly (#4796 over 10% alone, #6139, and probably others I'm forgetting). Since any machine that can run vnni256 can run vnni512 this arch is no longer useful. Note, x86-64-avxvnni still covers targets that don't have AVX512 but do have VNNI on 128- and 256-bit vectors. closes https://github.com/official-stockfish/Stockfish/pull/6340 No functional change --- .github/ci/matrix.json | 19 ------------------- scripts/get_native_properties.sh | 4 ++-- src/Makefile | 27 ++------------------------- 3 files changed, 4 insertions(+), 46 deletions(-) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index d916fd07117..f72451f5bb7 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -61,7 +61,6 @@ "x86-64-bmi2", "x86-64-avxvnni", "x86-64-avx512", - "x86-64-vnni256", "x86-64-vnni512", "x86-64-avx512icl", "apple-silicon", @@ -105,12 +104,6 @@ "os": "macos-14" } }, - { - "binaries": "x86-64-vnni256", - "config": { - "os": "macos-14" - } - }, { "binaries": "x86-64-vnni512", "config": { @@ -135,12 +128,6 @@ "os": "macos-15-intel" } }, - { - "binaries": "x86-64-vnni256", - "config": { - "os": "macos-15-intel" - } - }, { "binaries": "x86-64-vnni512", "config": { @@ -189,12 +176,6 @@ "os": "windows-11-arm" } }, - { - "binaries": "x86-64-vnni256", - "config": { - "os": "windows-11-arm" - } - }, { "binaries": "x86-64-vnni512", "config": { diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index 180c15d15f5..dec5998da00 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -42,7 +42,7 @@ set_arch_x86_64() { if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then true_arch='x86-64-avx512icl' elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then - true_arch='x86-64-vnni256' + true_arch='x86-64-vnni512' elif check_flags 'avx512f' 'avx512bw'; then true_arch='x86-64-avx512' elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then @@ -83,7 +83,7 @@ case $uname_s in 'x86_64') flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') set_arch_x86_64 - if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then + if [ "$true_arch" = 'x86-64-avx512' ]; then file_arch='x86-64-bmi2' fi ;; diff --git a/src/Makefile b/src/Makefile index cec623f5217..7244f70405c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -97,7 +97,6 @@ VPATH = syzygy:nnue:nnue/features # avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 # avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX # avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 -# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands # vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 # avx512icl = yes/no --- ... multiple ... --- Use All AVX-512 features available on both Intel Ice Lake and AMD Zen 4 # altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension @@ -128,7 +127,7 @@ endif # explicitly check for the list of supported architectures (as listed with make help), # the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true` ifeq ($(ARCH), $(filter $(ARCH), \ - x86-64-avx512icl x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni \ + x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \ x86-64-bmi2 x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \ armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \ @@ -153,7 +152,6 @@ sse41 = no avx2 = no avxvnni = no avx512 = no -vnni256 = no vnni512 = no avx512icl = no altivec = no @@ -269,17 +267,6 @@ ifeq ($(findstring -avx512,$(ARCH)),-avx512) avx512 = yes endif -ifeq ($(findstring -vnni256,$(ARCH)),-vnni256) - popcnt = yes - sse = yes - sse2 = yes - ssse3 = yes - sse41 = yes - avx2 = yes - pext = yes - vnni256 = yes -endif - ifeq ($(findstring -vnni512,$(ARCH)),-vnni512) popcnt = yes sse = yes @@ -724,17 +711,10 @@ ifeq ($(avx512),yes) endif endif -ifeq ($(vnni256),yes) - CXXFLAGS += -DUSE_VNNI - ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) - CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 - endif -endif - ifeq ($(vnni512),yes) CXXFLAGS += -DUSE_VNNI ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) - CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512 + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl endif endif @@ -905,7 +885,6 @@ help: echo "native > select the best architecture for the host processor (default)" && \ echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lake or AMD Zen 4" && \ echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \ - echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \ echo "x86-64-avx512 > x86 64-bit with avx512 support" && \ echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" && \ echo "x86-64-bmi2 > x86 64-bit with bmi2 support" && \ @@ -1050,7 +1029,6 @@ config-sanity: net echo "avx2: '$(avx2)'" && \ echo "avxvnni: '$(avxvnni)'" && \ echo "avx512: '$(avx512)'" && \ - echo "vnni256: '$(vnni256)'" && \ echo "vnni512: '$(vnni512)'" && \ echo "avx512icl: '$(avx512icl)'" && \ echo "altivec: '$(altivec)'" && \ @@ -1087,7 +1065,6 @@ config-sanity: net (test "$(sse41)" = "yes" || test "$(sse41)" = "no") && \ (test "$(avx2)" = "yes" || test "$(avx2)" = "no") && \ (test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \ - (test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \ (test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \ (test "$(avx512icl)" = "yes" || test "$(avx512icl)" = "no") && \ (test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \ From feb17e5acfc6eec264628db7b1531db6fc94a3e4 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 6 Oct 2025 04:47:37 -0400 Subject: [PATCH 1181/1309] Make sure we don't move a nonexistent piece in SEE added assert. closes https://github.com/official-stockfish/Stockfish/pull/6342 No functional change --- src/position.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/position.cpp b/src/position.cpp index f5963247511..d0cad3e7f65 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1105,6 +1105,8 @@ bool Position::see_ge(Move m, int threshold) const { Square from = m.from_sq(), to = m.to_sq(); + assert(piece_on(from) != NO_PIECE); + int swap = PieceValue[piece_on(to)] - threshold; if (swap < 0) return false; From e18ed795f2603d6482ac18bc0a6546e2a18406ae Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Sun, 5 Oct 2025 10:38:21 +0200 Subject: [PATCH 1182/1309] Introduce 4-ply continuation correction history Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 113984 W: 29752 L: 29323 D: 54909 Ptnml(0-2): 376, 13191, 29435, 13608, 382 https://tests.stockfishchess.org/tests/view/68dc3576fa806e2e8393bd93 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 80154 W: 20823 L: 20417 D: 38914 Ptnml(0-2): 47, 8600, 22383, 8994, 53 https://tests.stockfishchess.org/tests/view/68df83e0fa806e2e8393cbe8 Passed non-regression VLTC (rebased): LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 38158 W: 9992 L: 9805 D: 18361 Ptnml(0-2): 3, 3406, 12075, 3591, 4 https://tests.stockfishchess.org/tests/view/68e22f2afa806e2e8393d0ed closes https://github.com/official-stockfish/Stockfish/pull/6345 bench 2169281 --- src/search.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6b2c28da2ba..8dcdd5ed7c5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -84,6 +84,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 8; return 9536 * pcv + 8494 * micv + 10132 * (wnpcv + bnpcv) + 7156 * cntcv; @@ -112,8 +113,12 @@ void update_correction_history(const Position& pos, << bonus * nonPawnWeight / 128; if (m.is_ok()) - (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 137 / 128; + { + const Square to = m.to_sq(); + const Piece pc = pos.piece_on(m.to_sq()); + (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 137 / 128; + (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 64 / 128; + } } // Add a small random component to draw evaluations to avoid 3-fold blindness From fc5d296f9dfa62444dde910985c98e9055c1d9a3 Mon Sep 17 00:00:00 2001 From: dav1312 <63931154+dav1312@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:29:52 +0200 Subject: [PATCH 1183/1309] Update get_native_properties.sh for AVXVNNI Update get_native_properties.sh to detect and report 'x86-64-avxvnni' when the CPU supports it. closes https://github.com/official-stockfish/Stockfish/pull/6346 No functional change --- scripts/get_native_properties.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index dec5998da00..67dd60ca5d1 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -45,6 +45,8 @@ set_arch_x86_64() { true_arch='x86-64-vnni512' elif check_flags 'avx512f' 'avx512bw'; then true_arch='x86-64-avx512' + elif check_flags 'avxvnni'; then + true_arch='x86-64-avxvnni' elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then true_arch='x86-64-bmi2' elif check_flags 'avx2'; then From c956df4cbb4ffded736a95baa1bde7df6d48e319 Mon Sep 17 00:00:00 2001 From: mstembera Date: Tue, 7 Oct 2025 12:38:39 -0700 Subject: [PATCH 1184/1309] Split accumulator 3-way for avxvnni This does the same thing for x86-64-avxvnni as #6336, #6339. closes https://github.com/official-stockfish/Stockfish/pull/6347 No functional change --- .../layers/affine_transform_sparse_input.h | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index effda826b84..472da834f7d 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -254,6 +254,7 @@ class AffineTransformSparseInput { #elif defined(USE_AVX2) using invec_t = __m256i; using outvec_t = __m256i; + #define vec_add_32 _mm256_add_epi32 #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 #elif defined(USE_SSSE3) @@ -272,21 +273,19 @@ class AffineTransformSparseInput { #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) #define vec_add_dpbusd_32 SIMD::neon_m128_add_dpbusd_epi32 #endif - static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); - + constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth; - // If there's only one accumulator and we're using high-latency dot product instructions, - // split it to create three separate dependency chains and merge at the end - constexpr bool SplitAccums = + // If we're using high-latency dot product instructions, split the accumulators + // to create 3 separate dependency chains and merge at the end + constexpr IndexType NumRegs = #if defined(USE_VNNI) - NumAccums == 1; + 3 * NumAccums; #else - false; + NumAccums; #endif - constexpr IndexType NumRegs = SplitAccums ? 3 * NumAccums : NumAccums; - std::uint16_t nnz[NumChunks]; - IndexType count; + std::uint16_t nnz[NumChunks]; + IndexType count; const auto input32 = reinterpret_cast(input); @@ -303,30 +302,34 @@ class AffineTransformSparseInput { // convince GCC to not do weird pointer arithmetic in the following loop const std::int8_t* weights_cp = weights; + #if defined(USE_VNNI) + for (IndexType k = NumAccums; k < NumRegs; ++k) + acc[k] = vec_zero(); - if constexpr (SplitAccums) + while (start < end - 2) { - acc[1] = acc[2] = vec_set_32(0); - while (start < end - 2) + const std::ptrdiff_t i0 = *start++; + const std::ptrdiff_t i1 = *start++; + const std::ptrdiff_t i2 = *start++; + const invec_t in0 = vec_set_32(input32[i0]); + const invec_t in1 = vec_set_32(input32[i1]); + const invec_t in2 = vec_set_32(input32[i2]); + const auto col0 = + reinterpret_cast(&weights_cp[i0 * OutputDimensions * ChunkSize]); + const auto col1 = + reinterpret_cast(&weights_cp[i1 * OutputDimensions * ChunkSize]); + const auto col2 = + reinterpret_cast(&weights_cp[i2 * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumAccums; ++k) { - const std::ptrdiff_t i0 = *start++; - const std::ptrdiff_t i1 = *start++; - const std::ptrdiff_t i2 = *start++; - const invec_t in0 = vec_set_32(input32[i0]); - const invec_t in1 = vec_set_32(input32[i1]); - const invec_t in2 = vec_set_32(input32[i2]); - const auto col0 = - reinterpret_cast(&weights_cp[i0 * OutputDimensions * ChunkSize]); - const auto col1 = - reinterpret_cast(&weights_cp[i1 * OutputDimensions * ChunkSize]); - const auto col2 = - reinterpret_cast(&weights_cp[i2 * OutputDimensions * ChunkSize]); - vec_add_dpbusd_32(acc[0], in0, *col0); - vec_add_dpbusd_32(acc[1], in1, *col1); - vec_add_dpbusd_32(acc[2], in2, *col2); + vec_add_dpbusd_32(acc[k], in0, col0[k]); + vec_add_dpbusd_32(acc[k + NumAccums], in1, col1[k]); + vec_add_dpbusd_32(acc[k + 2 * NumAccums], in2, col2[k]); } - acc[0] = vec_add_32(vec_add_32(acc[0], acc[1]), acc[2]); } + for (IndexType k = 0; k < NumAccums; ++k) + acc[k] = vec_add_32(vec_add_32(acc[k], acc[k + NumAccums]), acc[k + 2 * NumAccums]); + #endif while (start < end) { const std::ptrdiff_t i = *start++; @@ -340,8 +343,12 @@ class AffineTransformSparseInput { outvec_t* outptr = reinterpret_cast(output); for (IndexType k = 0; k < NumAccums; ++k) outptr[k] = acc[k]; + #undef vec_set_32 #undef vec_add_dpbusd_32 + #ifdef vec_add_32 + #undef vec_add_32 + #endif #else // Use dense implementation for the other architectures. affine_transform_non_ssse3( From 63d449d1d9b8fe5c01e35c7cf874371a1c82ccb2 Mon Sep 17 00:00:00 2001 From: rustam-cpp Date: Wed, 8 Oct 2025 22:50:28 +0300 Subject: [PATCH 1185/1309] bigger PAWN_HISTORY_SIZE STC (10+0.1 th1) was accepted: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 75712 W: 19701 L: 19326 D: 36685 Ptnml(0-2): 254, 8738, 19513, 9081, 270 https://tests.stockfishchess.org/tests/view/68e286d5fa806e2e8393d160 LTC (60+0.6 th1) was accepted: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 108492 W: 28068 L: 27604 D: 52820 Ptnml(0-2): 60, 11639, 30390, 12091, 66 https://tests.stockfishchess.org/tests/view/68e3e564a017f472e763dac0 closes https://github.com/official-stockfish/Stockfish/pull/6350 bench 2128316 --- AUTHORS | 1 + src/history.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 0429f9f0a00..8d57062cc7e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -217,6 +217,7 @@ Ronald de Man (syzygy1, syzygy) Ron Britvich (Britvich) rqs Rui Coelho (ruicoelhopedro) +rustam-cpp Ryan Schmitt Ryan Takker Sami Kiminki (skiminki) diff --git a/src/history.h b/src/history.h index 1f7abc542ba..940e9899160 100644 --- a/src/history.h +++ b/src/history.h @@ -33,7 +33,7 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 1024; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; From e7a4708ad558da49f88c8c72aed8c9290fef8d05 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 7 Oct 2025 18:25:08 -0400 Subject: [PATCH 1186/1309] Remove condition in qsearch Instead of skipping non-captures when pawn history is exceptionally high, skip all non-captures Passed non-regression STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 38016 W: 10018 L: 9795 D: 18203 Ptnml(0-2): 155, 4346, 9755, 4625, 127 https://tests.stockfishchess.org/tests/view/68e43d4aa017f472e763db2e Passed rebased non-regression LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 96048 W: 24854 L: 24710 D: 46484 Ptnml(0-2): 47, 10504, 26780, 10644, 49 https://tests.stockfishchess.org/tests/view/68e59352a017f472e763dcf9 closes https://github.com/official-stockfish/Stockfish/pull/6355 bench 2343840 --- AUTHORS | 2 +- src/search.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8d57062cc7e..4729afeab5e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -62,7 +62,7 @@ CSTENTOR Dale Weiler (graphitemaster) Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) -Daniel Monroe (Ergodice) +Daniel Monroe (daniel-monroe) Daniel Samek (DanSamek) Dan Schmidt (dfannius) Dariusz Orzechowski (dorzechowski) diff --git a/src/search.cpp b/src/search.cpp index 8dcdd5ed7c5..fa2355130df 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1634,9 +1634,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } } - // Continuation history based pruning - if (!capture - && pawnHistory[pawn_history_index(pos)][pos.moved_piece(move)][move.to_sq()] < 7300) + // Skip non-captures + if (!capture) continue; // Do not search moves with bad enough SEE values From 315f8ba4bf7d846b35f984d5e6040c14a512d9b9 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Tue, 14 Oct 2025 08:40:27 +0200 Subject: [PATCH 1187/1309] let CI check for mate scores outside the valid range closes https://github.com/official-stockfish/Stockfish/pull/6358 No functional change --- .github/workflows/matetrack.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index c4d14fc7bde..43d35ca1835 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -24,7 +24,7 @@ jobs: with: repository: vondele/matetrack path: matetrack - ref: 4f8a80860ed8f3607f05a9195df8b40203bdc360 + ref: 2d96fa3373f90edb032b7ea7468473fb9e6f0343 persist-credentials: false - name: matetrack install deps From 75edbee01e6f8cb53a2555499192ccaddb883577 Mon Sep 17 00:00:00 2001 From: Kieren Pearson Date: Sat, 11 Oct 2025 17:04:06 +1100 Subject: [PATCH 1188/1309] Use huge pages for worker data As the worker data is quite large (28MB after #6350) we can make use of huge pages as a speedup. prior to #6350 STC passed elo gaining bounds: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 166272 W: 43479 L: 42993 D: 79800 Ptnml(0-2): 540, 17598, 46365, 18102, 531 https://tests.stockfishchess.org/tests/view/68e9f3c0d323fd15c04e3ba4 Tested the speedup on a large machine with speedtest: ==== master ==== Average (over 20): 288644510 ==== largePageWorker ==== Average (over 20): 292082422 Test after #6350: ==== rustam-cpp-testPR ==== Average (over 20): 291035351 ==== rustam-cpp-testPR-pages ==== Average (over 20): 291937367 https://github.com/official-stockfish/Stockfish/pull/6359 No functional change --- AUTHORS | 1 + src/thread.cpp | 5 +++-- src/thread.h | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4729afeab5e..31ff51def8a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -136,6 +136,7 @@ Ken Takusagawa Kenneth Lee (kennethlee33) kevlu8 Kian E (KJE-98) +Kieren Pearson (KierenP) kinderchocolate Kiran Panditrao (Krgp) Kirill Zaripov (kokodio) diff --git a/src/thread.cpp b/src/thread.cpp index 43ba7d9c08d..f87d7a94d23 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -26,6 +26,7 @@ #include #include +#include "memory.h" #include "movegen.h" #include "search.h" #include "syzygy/tbprobe.h" @@ -53,8 +54,8 @@ Thread::Thread(Search::SharedState& sharedState, // the Worker allocation. Ideally we would also allocate the SearchManager // here, but that's minor. this->numaAccessToken = binder(); - this->worker = - std::make_unique(sharedState, std::move(sm), n, this->numaAccessToken); + this->worker = make_unique_large_page(sharedState, std::move(sm), n, + this->numaAccessToken); }); wait_for_search_finished(); diff --git a/src/thread.h b/src/thread.h index 00616097a7b..79376b10a8e 100644 --- a/src/thread.h +++ b/src/thread.h @@ -28,6 +28,7 @@ #include #include +#include "memory.h" #include "numa.h" #include "position.h" #include "search.h" @@ -93,8 +94,8 @@ class Thread { void wait_for_search_finished(); size_t id() const { return idx; } - std::unique_ptr worker; - std::function jobFunc; + LargePagePtr worker; + std::function jobFunc; private: std::mutex mutex; From b9e3e7921b638109bd19395fd201f87c9110614a Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Tue, 14 Oct 2025 21:16:34 +0200 Subject: [PATCH 1189/1309] Increase NMP reduction when improving Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 52896 W: 13904 L: 13565 D: 25427 Ptnml(0-2): 186, 6022, 13706, 6335, 199 https://tests.stockfishchess.org/tests/view/68e67d02a017f472e763dfaf Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 168354 W: 43750 L: 43163 D: 81441 Ptnml(0-2): 81, 18284, 46882, 18827, 103 https://tests.stockfishchess.org/tests/view/68e79d7ba017f472e763e352 closes https://github.com/official-stockfish/Stockfish/pull/6361 bench: 2537382 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index fa2355130df..e6ee4274ecf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -869,7 +869,7 @@ Value Search::Worker::search( assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 6 + depth / 3; + Depth R = 6 + depth / 3 + improving; ss->currentMove = Move::null(); ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; From f434cc291892e826c675056bc5e770add7e004b3 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 17 Oct 2025 15:53:49 -0700 Subject: [PATCH 1190/1309] Allow AccumulatorStack::size to point to one past the end this is guaranteed to be correct since we access the last element with `size - 1` closes https://github.com/official-stockfish/Stockfish/pull/6368 fixes https://github.com/official-stockfish/Stockfish/issues/6367 no functional change --- src/nnue/nnue_accumulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index d13105aa47e..3096758b91e 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -75,7 +75,7 @@ void AccumulatorStack::reset() noexcept { } void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { - assert(size + 1 < accumulators.size()); + assert(size < accumulators.size()); accumulators[size].reset(dirtyPiece); size++; } From 3bb01ce7a9f5bf60b4ce4e608b39f4a64a57e001 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Fri, 17 Oct 2025 23:39:45 -0700 Subject: [PATCH 1191/1309] prefetch earlier if checKEP is false Only a modest amount of work happens between the transposition table prefetch and the probe, so the probe still often stalls waiting for DRAM. The vast majority of the time (in particular, if !checkEP), the key is known much earlier in the do_move function and the latency can be better hidden. passed STC SMP https://tests.stockfishchess.org/tests/view/68f337c528e6d77fcffa066a LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 65256 W: 16806 L: 16462 D: 31988 Ptnml(0-2): 76, 7386, 17362, 7726, 78 but failed to gain STC https://tests.stockfishchess.org/tests/view/68f3378328e6d77fcffa0665 LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 109824 W: 28523 L: 28618 D: 52683 Ptnml(0-2): 311, 11799, 30788, 11702, 312 In local tests, the speedup grows with thread count closes https://github.com/official-stockfish/Stockfish/pull/6372 No functional change --- src/position.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/position.cpp b/src/position.cpp index d0cad3e7f65..1551eb9dfde 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -858,6 +858,10 @@ DirtyPiece Position::do_move(Move m, st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } + // If en passant is impossible, then k will not change and we can prefetch earlier + if (tt && !checkEP) + prefetch(tt->first_entry(adjust_key50(k))); + // Set capture piece st->capturedPiece = captured; From 676456191607e2ea6ca84d83e130bab8afdd2ea6 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Tue, 14 Oct 2025 05:38:44 -0700 Subject: [PATCH 1192/1309] Improve index generation The speedup seems to vary by machine. The indexing function can be changed w/o needing to understand intrinsics. Result of 100 runs ================== base (...ish_baseline) = 1719637 +/- 3233 test (./stockfish ) = 1734245 +/- 3534 diff = +14608 +/- 4868 speedup = +0.0085 P(speedup > 0) = 1.0000 closes https://github.com/official-stockfish/Stockfish/pull/6366 No functional change --- src/nnue/nnue_accumulator.cpp | 66 +++++++++++++++++++++-------------- src/nnue/nnue_accumulator.h | 4 +-- src/position.h | 11 +++--- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 3096758b91e..b1743329fb8 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -18,9 +18,9 @@ #include "nnue_accumulator.h" +#include #include #include -#include #include #include "../bitboard.h" @@ -362,6 +362,29 @@ void update_accumulator_incremental( (target_state.acc()).computed[Perspective] = true; } +Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) { +#if defined(USE_AVX512) || defined(USE_AVX2) + static_assert(sizeof(Piece) == 1); + Bitboard same_bb = 0; + for (int i = 0; i < 64; i += 32) + { + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(old + i)); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(new_ + i)); + const __m256i cmp_equal = _mm256_cmpeq_epi8(old_v, new_v); + const std::uint32_t equal_mask = _mm256_movemask_epi8(cmp_equal); + same_bb |= static_cast(equal_mask) << i; + } + return ~same_bb; +#else + Bitboard changed = 0; + for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq) + { + changed |= static_cast(old[sq] != new_[sq]) << sq; + } + return changed; +#endif +} + template void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, const Position& pos, @@ -374,28 +397,23 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat auto& entry = cache[ksq][Perspective]; FeatureSet::IndexList removed, added; - for (Color c : {WHITE, BLACK}) - { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; + const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array()); + Bitboard removed_bb = changed_bb & entry.pieceBB; + Bitboard added_bb = changed_bb & pos.pieces(); - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - } + while (removed_bb) + { + Square sq = pop_lsb(removed_bb); + removed.push_back(FeatureSet::make_index(sq, entry.pieces[sq], ksq)); } + while (added_bb) + { + Square sq = pop_lsb(added_bb); + added.push_back(FeatureSet::make_index(sq, pos.piece_on(sq), ksq)); + } + + entry.pieceBB = pos.pieces(); + std::copy_n(pos.piece_array(), SQUARE_NB, entry.pieces); auto& accumulator = accumulatorState.acc(); accumulator.computed[Perspective] = true; @@ -518,12 +536,6 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, sizeof(int32_t) * PSQTBuckets); #endif - - for (Color c : {WHITE, BLACK}) - entry.byColorBB[c] = pos.pieces(c); - - for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[pt] = pos.pieces(pt); } } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 10aadc91776..56de0b7d044 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -71,8 +71,8 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Entry { BiasType accumulation[Size]; PSQTWeightType psqtAccumulation[PSQTBuckets]; - Bitboard byColorBB[COLOR_NB]; - Bitboard byTypeBB[PIECE_TYPE_NB]; + Piece pieces[SQUARE_NB]; + Bitboard pieceBB; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top diff --git a/src/position.h b/src/position.h index dde496fe049..579121bf3cd 100644 --- a/src/position.h +++ b/src/position.h @@ -91,10 +91,11 @@ class Position { Bitboard pieces(PieceTypes... pts) const; Bitboard pieces(Color c) const; template - Bitboard pieces(Color c, PieceTypes... pts) const; - Piece piece_on(Square s) const; - Square ep_square() const; - bool empty(Square s) const; + Bitboard pieces(Color c, PieceTypes... pts) const; + Piece piece_on(Square s) const; + const Piece* piece_array() const; + Square ep_square() const; + bool empty(Square s) const; template int count(Color c) const; template @@ -208,6 +209,8 @@ inline Piece Position::piece_on(Square s) const { return board[s]; } +inline const Piece* Position::piece_array() const { return board; } + inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } From a49b52cf693aee554aa137244ac1f10a22590f87 Mon Sep 17 00:00:00 2001 From: shaowyx Date: Sat, 25 Oct 2025 17:23:04 +0900 Subject: [PATCH 1193/1309] Revert malus and associated coefficient parameters resulting from using only quiet moves Following #6226 and #6256, this patch ultimately corresponds to the revert of #6200. Parameters were tuned on 60k LTC games. STC (10+0.1 th1) was accepted: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 167488 W: 43573 L: 43063 D: 80852 Ptnml(0-2): 506, 19644, 43004, 20014, 576 https://tests.stockfishchess.org/tests/view/68f526a4637acd2a11e721c2 LTC (60+0.6 th1) was accepted: LLR: 2.99 (-2.94,2.94) <0.50,2.50> Total: 61068 W: 15882 L: 15510 D: 29676 Ptnml(0-2): 31, 6578, 16949, 6940, 36 https://tests.stockfishchess.org/tests/view/68fa1968637acd2a11e72a0a Non-regression VLTC (180+1.8 th1) was accepted: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 50380 W: 13087 L: 12905 D: 24388 Ptnml(0-2): 5, 5018, 14962, 5200, 5 https://tests.stockfishchess.org/tests/view/68fdc6e5637acd2a11e72f33 closes https://github.com/official-stockfish/Stockfish/pull/6378 Bench: 2530552 --- src/search.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e6ee4274ecf..ef87c828ebc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -137,7 +137,8 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move TTMove); + Move TTMove, + int moveCount); } // namespace @@ -1391,7 +1392,7 @@ Value Search::Worker::search( else if (bestMove) { update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - ttData.move); + ttData.move, moveCount); if (!PvNode) ttMoveHistory << (bestMove == ttData.move ? 809 : -865); } @@ -1801,41 +1802,42 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move ttMove) { + Move ttMove, + int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(151 * depth - 91, 1730) + 302 * (bestMove == ttMove); - int malus = std::min(951 * depth - 156, 2468) - 30 * quietsSearched.size(); + int bonus = std::min(121 * depth - 77, 1633) + 375 * (bestMove == ttMove); + int malus = std::min(825 * depth - 196, 2159) - 16 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 957 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 881 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1083 / 1024); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1482 / 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 * 503 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 614 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1157 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1397 / 1024; } } From fa3b4ef5af7535b286e97d7296c788926e7c5203 Mon Sep 17 00:00:00 2001 From: pb00067 Date: Thu, 30 Oct 2025 14:31:23 +0100 Subject: [PATCH 1194/1309] Improve retrograde analisys and matefinding capability fixes https://github.com/official-stockfish/Stockfish/issues/6328 Before calling search(depth), the patch ensures that depth is at least 1 whenever we encounter a decisive score in the transposition table (TT). This prevents search(depth) from being executed by qsearch, which would otherwise ignore that information. Typically, decisive TT hits occur when analyzing a mating sequence backward. Due to the nature of Iterative Deepening (IID), such scores are usually first found at depth 0. Without this patch, valuable information can be lost because qsearch may overwrite the TT entry by replacing the value with a static evaluation, even though the node was already processed at a higher depth. This is also why the engine sometimes loses track of an already discovered mate. Using ..\sf\patch.exe on matetrack.epd with --nodes 1000000 Engine ID: Stockfish dev-20251015-nogit Total FENs: 6554 Found mates: 3437 Best mates: 2438 Using ..\sf\master.exe on matetrack.epd with --nodes 1000000 Engine ID: Stockfish dev-20251015-nogit Total FENs: 6554 Found mates: 3337 Best mates: 2407 Passed STC https://tests.stockfishchess.org/tests/view/68fa3fa7637acd2a11e72a79 LLR: 3.55 (-2.94,2.94) <0.00,2.00> Total: 134144 W: 34960 L: 34471 D: 64713 Ptnml(0-2): 376, 14199, 37459, 14636, 402 Failed LTC https://tests.stockfishchess.org/tests/view/68ffc1b5637acd2a11e73377 LLR: -3.10 (-2.94,2.94) <0.50,2.50> Total: 75360 W: 19423 L: 19519 D: 36418 Ptnml(0-2): 38, 7553, 22605, 7435, 49 closes https://github.com/official-stockfish/Stockfish/pull/6386 bench: 2530552 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ef87c828ebc..151b4c6ee25 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1260,7 +1260,10 @@ Value Search::Worker::search( (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && ttData.depth > 1 && rootDepth > 8) + // decisive score handling improves mate finding and retrograde analysis. + if (move == ttData.move + && ((is_valid(ttData.value) && is_decisive(ttData.value) && ttData.depth > 0) + || (ttData.depth > 1 && rootDepth > 8))) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); From 11ab4cde071be92d4fbd188c4741010833ab8340 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Thu, 9 Oct 2025 12:10:46 -0700 Subject: [PATCH 1195/1309] Avoid unnecessary allocations in AccumulatorStack replacing the vector with an array resulted in a .45+-.10 % speedup. closes https://github.com/official-stockfish/Stockfish/pull/6352 no functional change --- src/nnue/network.h | 2 -- src/nnue/nnue_accumulator.h | 11 +++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/nnue/network.h b/src/nnue/network.h index c9358823bdb..6855831d5e4 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -106,8 +106,6 @@ class Network { template friend struct AccumulatorCaches::Cache; - - friend class AccumulatorStack; }; // Definitions of the network types diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 56de0b7d044..7ff95b6eff7 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -25,7 +25,6 @@ #include #include #include -#include #include "../types.h" #include "nnue_architecture.h" @@ -48,7 +47,7 @@ template struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[COLOR_NB][Size]; std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - std::array computed; + std::array computed = {}; }; @@ -142,10 +141,6 @@ struct AccumulatorState { class AccumulatorStack { public: - AccumulatorStack() : - accumulators(MAX_PLY + 1), - size{1} {} - [[nodiscard]] const AccumulatorState& latest() const noexcept; void reset() noexcept; @@ -178,8 +173,8 @@ class AccumulatorStack { const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; - std::vector accumulators; - std::size_t size; + std::array accumulators; + std::size_t size = 1; }; } // namespace Stockfish::Eval::NNUE From 9e071f3561a624ac7f48356f5580e2c2dc755bc0 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 10 Oct 2025 13:23:08 -0400 Subject: [PATCH 1196/1309] Simplify best move effort Passed non-regression STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 44224 W: 11614 L: 11403 D: 21207 Ptnml(0-2): 147, 4936, 11726, 5165, 138 https://tests.stockfishchess.org/tests/view/68e9410ed323fd15c04e3a87 Passed non-regression LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 109788 W: 28223 L: 28096 D: 53469 Ptnml(0-2): 60, 11493, 31657, 11628, 56 https://tests.stockfishchess.org/tests/view/68eb5ac0a23744016c14af1d closes https://github.com/official-stockfish/Stockfish/pull/6371 No functional change --- src/search.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 151b4c6ee25..3cd10ad9bb3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -479,9 +479,10 @@ void Search::Worker::iterative_deepening() { double reduction = (1.455 + mainThread->previousTimeReduction) / (2.2375 * timeReduction); double bestMoveInstability = 1.04 + 1.8956 * totBestMoveChanges / threads.size(); + double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 92425 ? 0.666 : 1.0; - double totalTime = - mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; + double totalTime = mainThread->tm.optimum() * fallingEval * reduction + * bestMoveInstability * highBestMoveEffort; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) @@ -489,10 +490,6 @@ void Search::Worker::iterative_deepening() { auto elapsedTime = elapsed(); - if (completedDepth >= 10 && nodesEffort >= 92425 && elapsedTime > totalTime * 0.666 - && !mainThread->ponder) - threads.stop = true; - // Stop the search if we have exceeded the totalTime or maximum if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum()))) { From fd3c563f575c5ae1ce286749303eab2932609124 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 6 Oct 2025 05:01:17 -0400 Subject: [PATCH 1197/1309] Simplify r50 condition in cutoff Passed non-regression STC LLR: 3.11 (-2.94,2.94) <-1.75,0.25> Total: 114560 W: 29832 L: 29689 D: 55039 Ptnml(0-2): 332, 12302, 31869, 12445, 332 https://tests.stockfishchess.org/tests/view/68e38587fa806e2e8393d4a9 Passed non-regression LTC LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 256272 W: 65817 L: 65832 D: 124623 Ptnml(0-2): 137, 25528, 76817, 25521, 133 https://tests.stockfishchess.org/tests/view/68e69b47a017f472e763e065 closes https://github.com/official-stockfish/Stockfish/pull/6373 Bench: 2438327 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3cd10ad9bb3..7343720045a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -683,10 +683,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 > 5) - // avoid a TT cutoff if the rule50 count is high and the TT move is zeroing - && (depth > 8 || ttData.move == Move::none() || pos.rule50_count() < 80 - || (!ttCapture && type_of(pos.moved_piece(ttData.move)) != PAWN))) + && (cutNode == (ttData.value >= beta) || depth > 5)) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) From 013d42914fa564c9aa69cc93f16aed6a6e3869b9 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 8 Oct 2025 00:11:20 -0400 Subject: [PATCH 1198/1309] Simplify static eval bonus Passed non-regression STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 176896 W: 45998 L: 45933 D: 84965 Ptnml(0-2): 609, 20845, 45451, 20958, 585 https://tests.stockfishchess.org/tests/view/68e5e48ba017f472e763dd21 Passed non-regression LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 77682 W: 20046 L: 19884 D: 37752 Ptnml(0-2): 41, 8404, 21786, 8572, 38 https://tests.stockfishchess.org/tests/view/68ee71e328e6d77fcff9fd68 closes https://github.com/official-stockfish/Stockfish/pull/6374 bench 2101654 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7343720045a..50585eb1365 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -813,12 +813,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), -2023, 1563) + 583; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 944 / 1024; + int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1438 / 1024; + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; } // Set up the improving flag, which is true if current static evaluation is From c9a2aff48529d5b624cb60c9e70354dacad39a57 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 7 Oct 2025 08:10:45 -0400 Subject: [PATCH 1199/1309] Simplify correction update condition Passed non-regression STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 95136 W: 24722 L: 24564 D: 45850 Ptnml(0-2): 307, 11139, 24522, 11289, 311 https://tests.stockfishchess.org/tests/view/68e5034ea017f472e763dc5a Passed non-regression LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 256440 W: 65854 L: 65873 D: 124713 Ptnml(0-2): 144, 28287, 71375, 28272, 142 https://tests.stockfishchess.org/tests/view/68e71ae4a017f472e763e291 closes https://github.com/official-stockfish/Stockfish/pull/6375 bench 2216361 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 50585eb1365..b9321de75f0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1443,10 +1443,10 @@ Value Search::Worker::search( moveCount != 0 ? depth : std::min(MAX_PLY - 1, depth + 6), bestMove, unadjustedStaticEval, tt.generation()); - // Adjust correction history + // Adjust correction history if the best move is not a capture + // and the error direction matches whether we are above/below bounds. if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) - && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high - || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low + && (bestValue < ss->staticEval) == !bestMove) { auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), From 035165299526cfd6d97cbe71c1220a4e9a7a8cce Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 19 Oct 2025 17:11:36 -0700 Subject: [PATCH 1200/1309] Simplify Pawn History Bonus Passed Non-regression STC: LLR: 2.98 (-2.94,2.94) <-1.75,0.25> Total: 122016 W: 31655 L: 31525 D: 58836 Ptnml(0-2): 388, 14317, 31474, 14435, 394 https://tests.stockfishchess.org/tests/view/68f57e66637acd2a11e7221d Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 81348 W: 21016 L: 20858 D: 39474 Ptnml(0-2): 45, 8793, 22841, 8949, 46 https://tests.stockfishchess.org/tests/view/68f9e2d9637acd2a11e72997 closes https://github.com/official-stockfish/Stockfish/pull/6382 Bench: 2370893 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b9321de75f0..4a55da30156 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1869,7 +1869,7 @@ void update_quiet_histories( int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << (bonus * (bonus > 0 ? 800 : 500) / 1024) + 70; + << bonus * (bonus > 0 ? 850 : 550) / 1024; } } From e9674a788843ea886d382e541b354f41a585df25 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Thu, 23 Oct 2025 15:54:38 -0700 Subject: [PATCH 1201/1309] simplify make_index() passed non-regression STC: https://tests.stockfishchess.org/tests/view/68fdb409637acd2a11e72f11 LLR: 3.08 (-2.94,2.94) <-1.75,0.25> Total: 95008 W: 24837 L: 24677 D: 45494 Ptnml(0-2): 268, 10024, 26775, 10154, 283 closes https://github.com/official-stockfish/Stockfish/pull/6384 no functional change --- src/nnue/features/half_ka_v2_hm.cpp | 7 ++++--- src/nnue/features/half_ka_v2_hm.h | 28 ++++++---------------------- 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 70e9beeb15e..5bcfb0849d0 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -29,9 +29,10 @@ namespace Stockfish::Eval::NNUE::Features { // Index of a feature for a given king position and another piece on some square template -inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] - + KingBuckets[Perspective][ksq]); +IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { + const IndexType flip = 56 * Perspective; + return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[Perspective][pc] + + KingBuckets[int(ksq) ^ flip]; } // Get a list of indices for active features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index ba122adc8da..b72bbbce4e3 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -75,45 +75,29 @@ class HalfKAv2_hm { #define B(v) (v * PS_NB) // clang-format off - static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { - { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), + static constexpr IndexType KingBuckets[SQUARE_NB] = { + B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), - B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) }, - { B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0), - B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), - B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), - B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), - B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), - B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), - B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), - B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } + B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0), }; // clang-format on #undef B // clang-format off // Orient a square according to perspective (rotates by 180 for black) - static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { - { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + static constexpr IndexType OrientTBL[SQUARE_NB] = { + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, - SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 }, - { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, - SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, - SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, - SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, - SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, - SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, - SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, - SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 , }; // clang-format on From cd7880c030bc941a3014b4b9ea6a455b3a328c59 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 19 Oct 2025 17:18:42 -0700 Subject: [PATCH 1202/1309] Simplify Corrhist Bonus Passed Non-regression STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 187776 W: 48687 L: 48631 D: 90458 Ptnml(0-2): 570, 22327, 48090, 22279, 622 https://tests.stockfishchess.org/tests/view/68f58019637acd2a11e72233 Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 159378 W: 41027 L: 40946 D: 77405 Ptnml(0-2): 122, 17645, 44078, 17718, 126 https://tests.stockfishchess.org/tests/view/68fe7492637acd2a11e73090 closes https://github.com/official-stockfish/Stockfish/pull/6385 Bench: 2458738 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4a55da30156..212e14fcf3c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1451,8 +1451,7 @@ Value Search::Worker::search( auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - update_correction_history(pos, ss, *this, - (1088 - 180 * (bestValue > ss->staticEval)) * bonus / 1024); + update_correction_history(pos, ss, *this, bonus); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); From bc9f08731f10896a306bcc34e30e5606087af79a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 1 Nov 2025 03:36:04 +0300 Subject: [PATCH 1203/1309] Simplify Futility pruning formula Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 170112 W: 44266 L: 44193 D: 81653 Ptnml(0-2): 599, 20062, 43611, 20235, 549 https://tests.stockfishchess.org/tests/view/68f50b94637acd2a11e721ad Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 164658 W: 42393 L: 42318 D: 79947 Ptnml(0-2): 120, 18127, 45747, 18228, 107 https://tests.stockfishchess.org/tests/view/68fa6683637acd2a11e72ac3 closes https://github.com/official-stockfish/Stockfish/pull/6389 bench: 2351426 --- src/search.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 212e14fcf3c..a0c6eafe32d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -847,7 +847,6 @@ Value Search::Worker::search( return futilityMult * d // - 2094 * improving * futilityMult / 1024 // - 1324 * opponentWorsening * futilityMult / 4096 // - + (ss - 1)->statScore / 331 // + std::abs(correctionValue) / 158105; }; From 69a01b88f35db2a5003d42116f573207ca5c275b Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Tue, 22 Jul 2025 13:42:01 +0200 Subject: [PATCH 1204/1309] Use shared memory for network weights MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This enables different Stockfish processes that use the same weights to use the same memory. The approach establishes equivalence by memory content, and is compatible with NUMA replication. The benefit of sharing is reduced memory usage and a speedup thanks to improved (inter-process) caching of the network in the CPUs cache, and thus reduced bandwidth usage to main memory. Even though this change doesn't benefit a user running a single process, this helps on fishtest or e.g. for Lichess, when multiple games run concurrently, or multiple positions are analyzed in parallel. This concept was probably first introduced in the Monty engine (https://github.com/official-monty/Monty/pull/62), after a discussion in https://github.com/official-stockfish/fishtest/issues/2077 on the issue of memory pressure. Measurements based on Torch (https://github.com/user-attachments/files/21386224/verbatim.pdf) further suggested that large gains were possible. Multiple other engines have adopted this 'verbatim' format as well. The implementation here adds the flexibility needed for SF, for example, retains the ability to bundle compressed networks with the binary, to load nets by uci option, and to distribute the shared nets to the proper NUMA region. This flexibility comes with a fair amount of complexity in the implementation, such as OS specific code, and fallback code. For most users this should be transparent. However, for example, those running docker containers should ensure the `--ipc` flag is set correctly, and `--shm-size` is sufficiently large. The benefits of this patch significantly depend on hardware, with systems with many cores and a large (O(150MB), the net size) L3 cache benefitting typically most. On such systems SF speedups (as measured via nps playing games with large concurrency but just 1 thread) can be 38%, which results in master vs. patch Elo which gains about 25 Elo. ``` # PLAYER : RATING ERROR POINTS PLAYED (%) 1 shared_memoryPR : 24.8 1.9 39432.0 73728 53 2 master : 0.0 ---- 34296.0 73728 47 ``` In a multithreaded setup, where weights are already shared, that benefit is smaller, for example on the same HW as above, but with 8t for each side. ``` # PLAYER : RATING ERROR POINTS PLAYED (%) 1 shared_memoryPR : 5.2 3.5 9351.0 18432 51 2 master : 0.0 ---- 9081.0 18432 49 ``` On fishtest with a typical hardware mix of our contributors, the following was measured: STC, 60k games https://tests.stockfishchess.org/tests/view/69074a49ea4b268f1fac236c Elo: 4.69 ± 1.4 (95%) LOS: 100.0% Total: 60000 W: 16085 L: 15275 D: 28640 Ptnml(0-2): 154, 6440, 16053, 7148, 205 nElo: 9.38 ± 2.8 (95%) PairsRatio: 1.12 To verify correctness with a single process on a NUMA architecture, speedtest was used, confirming near equivalence: ``` master: Average (over 10): 296236186 shared_memory: Average (over 10): 295769332 ``` Currently, using large pages for the shared network weights is not always possible, which can lead to a small slowdown (1-2%), in case a single process is run. closes https://github.com/official-stockfish/Stockfish/pull/6173 No functional change Co-authored-by: disservin Co-authored-by: Joost VandeVondele --- src/Makefile | 16 +- src/engine.cpp | 43 +- src/engine.h | 8 +- src/evaluate.cpp | 8 +- src/main.cpp | 10 +- src/memory.cpp | 85 +-- src/memory.h | 98 +++ src/misc.h | 101 ++- src/nnue/layers/affine_transform.h | 9 + .../layers/affine_transform_sparse_input.h | 9 + src/nnue/layers/clipped_relu.h | 6 + src/nnue/layers/sqr_clipped_relu.h | 6 + src/nnue/network.cpp | 82 +-- src/nnue/network.h | 49 +- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_architecture.h | 22 +- src/nnue/nnue_common.h | 4 +- src/nnue/nnue_feature_transformer.h | 34 +- src/nnue/nnue_misc.cpp | 12 +- src/nnue/nnue_misc.h | 21 +- src/numa.h | 142 +++- src/search.h | 24 +- src/shm.h | 634 +++++++++++++++++ src/shm_linux.h | 658 ++++++++++++++++++ 24 files changed, 1885 insertions(+), 198 deletions(-) create mode 100644 src/shm.h create mode 100644 src/shm_linux.h diff --git a/src/Makefile b/src/Makefile index 7244f70405c..b30e16c074c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -435,7 +435,7 @@ endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations -Wstack-usage=128000 ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) ifeq ($(OS),Android) @@ -618,6 +618,19 @@ ifneq ($(comp),mingw) ifneq ($(KERNEL),Haiku) ifneq ($(COMP),ndk) LDFLAGS += -lpthread + + add_lrt = yes + ifeq ($(target_windows),yes) + add_lrt = no + endif + + ifeq ($(KERNEL),Darwin) + add_lrt = no + endif + + ifeq ($(add_lrt),yes) + LDFLAGS += -lrt + endif endif endif endif @@ -628,6 +641,7 @@ ifeq ($(debug),no) CXXFLAGS += -DNDEBUG else CXXFLAGS += -g + CXXFLAGS += -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG endif ### 3.2.2 Debugging with undefined behavior sanitizers diff --git a/src/engine.cpp b/src/engine.cpp index a4c0bb1ebeb..9edff486476 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -33,10 +33,12 @@ #include "misc.h" #include "nnue/network.h" #include "nnue/nnue_common.h" +#include "nnue/nnue_misc.h" #include "numa.h" #include "perft.h" #include "position.h" #include "search.h" +#include "shm.h" #include "syzygy/tbprobe.h" #include "types.h" #include "uci.h" @@ -57,11 +59,14 @@ Engine::Engine(std::optional path) : threads(), networks( numaContext, - NN::Networks( - NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG), - NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { - pos.set(StartFEN, false, &states->back()); + // Heap-allocate because sizeof(NN::Networks) is large + std::make_unique( + std::make_unique(NN::EvalFile{EvalFileDefaultNameBig, "None", ""}, + NN::EmbeddedNNUEType::BIG), + std::make_unique(NN::EvalFile{EvalFileDefaultNameSmall, "None", ""}, + NN::EmbeddedNNUEType::SMALL))) { + pos.set(StartFEN, false, &states->back()); options.add( // "Debug Log File", Option("", [](const Option& o) { @@ -254,6 +259,36 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } void Engine::verify_networks() const { networks->big.verify(options["EvalFile"], onVerifyNetworks); networks->small.verify(options["EvalFileSmall"], onVerifyNetworks); + + auto statuses = networks.get_status_and_errors(); + for (size_t i = 0; i < statuses.size(); ++i) + { + const auto [status, error] = statuses[i]; + std::string message = "Network replica " + std::to_string(i + 1) + ": "; + if (status == SystemWideSharedConstantAllocationStatus::NoAllocation) + { + message += "No allocation."; + } + else if (status == SystemWideSharedConstantAllocationStatus::LocalMemory) + { + message += "Local memory."; + } + else if (status == SystemWideSharedConstantAllocationStatus::SharedMemory) + { + message += "Shared memory."; + } + else + { + message += "Unknown status."; + } + + if (error.has_value()) + { + message += " " + *error; + } + + onVerifyNetworks(message); + } } void Engine::load_networks() { diff --git a/src/engine.h b/src/engine.h index d26844f4ce4..7315b88815f 100644 --- a/src/engine.h +++ b/src/engine.h @@ -115,10 +115,10 @@ class Engine { Position pos; StateListPtr states; - OptionsMap options; - ThreadPool threads; - TranspositionTable tt; - LazyNumaReplicated networks; + OptionsMap options; + ThreadPool threads; + TranspositionTable tt; + LazyNumaReplicatedSystemWide networks; Search::SearchManager::UpdateContext updateContext; std::function onVerifyNetworks; diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 23f4b8c2b2f..23bc70d0ed4 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -98,8 +98,8 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pos.checkers()) return "Final evaluation: none (in check)"; - Eval::NNUE::AccumulatorStack accumulators; - auto caches = std::make_unique(networks); + auto accumulators = std::make_unique(); + auto caches = std::make_unique(networks); std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); @@ -107,12 +107,12 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches->big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches->big); Value v = psqt + positional; v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; - v = evaluate(networks, pos, accumulators, *caches, VALUE_ZERO); + v = evaluate(networks, pos, *accumulators, *caches, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/main.cpp b/src/main.cpp index e262f387576..9988680aa61 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,28 +17,28 @@ */ #include +#include #include "bitboard.h" #include "misc.h" #include "position.h" +#include "tune.h" #include "types.h" #include "uci.h" -#include "tune.h" using namespace Stockfish; int main(int argc, char* argv[]) { - std::cout << engine_info() << std::endl; Bitboards::init(); Position::init(); - UCIEngine uci(argc, argv); + auto uci = std::make_unique(argc, argv); - Tune::init(uci.engine_options()); + Tune::init(uci->engine_options()); - uci.loop(); + uci->loop(); return 0; } diff --git a/src/memory.cpp b/src/memory.cpp index 92cb650f26f..f4aa8fc2656 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -55,12 +55,6 @@ // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. -extern "C" { -using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); -using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); -using AdjustTokenPrivileges_t = - bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); -} #endif @@ -106,77 +100,14 @@ void std_aligned_free(void* ptr) { static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { - #if !defined(_WIN64) - return nullptr; - #else - - HANDLE hProcessToken{}; - LUID luid{}; - void* mem = nullptr; - - const size_t largePageSize = GetLargePageMinimum(); - if (!largePageSize) - return nullptr; - - // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges - - HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); - - if (!hAdvapi32) - hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); - - auto OpenProcessToken_f = - OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); - if (!OpenProcessToken_f) - return nullptr; - auto LookupPrivilegeValueA_f = - LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); - if (!LookupPrivilegeValueA_f) - return nullptr; - auto AdjustTokenPrivileges_f = - AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); - if (!AdjustTokenPrivileges_f) - return nullptr; - - // We need SeLockMemoryPrivilege, so try to enable it for the process - - if (!OpenProcessToken_f( // OpenProcessToken() - GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) - return nullptr; - - if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) - { - TOKEN_PRIVILEGES tp{}; - TOKEN_PRIVILEGES prevTp{}; - DWORD prevTpLen = 0; - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() - // succeeds, we still need to query GetLastError() to ensure that the privileges - // were actually obtained. - - if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, - &prevTpLen) - && GetLastError() == ERROR_SUCCESS) - { - // Round up size to full pages and allocate - allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); - mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, - PAGE_READWRITE); - - // Privilege no longer needed, restore previous state - AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); - } - } - - CloseHandle(hProcessToken); - - return mem; - - #endif + return windows_try_with_large_page_priviliges( + [&](size_t largePageSize) { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + return VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); + }, + []() { return (void*) nullptr; }); } void* aligned_large_pages_alloc(size_t allocSize) { diff --git a/src/memory.h b/src/memory.h index e4a59fec5e0..b9be6f170dc 100644 --- a/src/memory.h +++ b/src/memory.h @@ -29,6 +29,29 @@ #include "types.h" +#if defined(_WIN64) + + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + + #if !defined(NOMINMAX) + #define NOMINMAX + #endif + #include + + #include + +extern "C" { +using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); +using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using AdjustTokenPrivileges_t = + bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +} +#endif + + namespace Stockfish { void* std_aligned_alloc(size_t alignment, size_t size); @@ -211,6 +234,81 @@ T* align_ptr_up(T* ptr) { reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); } +#if defined(_WIN32) + +template +auto windows_try_with_large_page_priviliges([[maybe_unused]] FuncYesT&& fyes, FuncNoT&& fno) { + + #if !defined(_WIN64) + return fno(); + #else + + HANDLE hProcessToken{}; + LUID luid{}; + + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return fno(); + + // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + auto OpenProcessToken_f = + OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!OpenProcessToken_f) + return fno(); + auto LookupPrivilegeValueA_f = + LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!LookupPrivilegeValueA_f) + return fno(); + auto AdjustTokenPrivileges_f = + AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!AdjustTokenPrivileges_f) + return fno(); + + // We need SeLockMemoryPrivilege, so try to enable it for the process + + if (!OpenProcessToken_f( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return fno(); + + if (!LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) + return fno(); + + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() + // succeeds, we still need to query GetLastError() to ensure that the privileges + // were actually obtained. + + if (!AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, + &prevTpLen) + || GetLastError() != ERROR_SUCCESS) + return fno(); + + auto&& ret = fyes(largePageSize); + + // Privilege no longer needed, restore previous state + AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + + CloseHandle(hProcessToken); + + return std::forward(ret); + + #endif +} + +#endif } // namespace Stockfish diff --git a/src/misc.h b/src/misc.h index 756433290ee..b1707e74217 100644 --- a/src/misc.h +++ b/src/misc.h @@ -23,11 +23,15 @@ #include #include #include -#include #include #include +#include // IWYU pragma: keep +// IWYU pragma: no_include <__exception/terminate.h> +#include #include #include +#include +#include #include #include #include @@ -291,6 +295,94 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { } +template +inline void hash_combine(std::size_t& seed, const T& v) { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +template<> +inline void hash_combine(std::size_t& seed, const std::size_t& v) { + seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +template +inline std::size_t get_raw_data_hash(const T& value) { + return std::hash{}( + std::string_view(reinterpret_cast(&value), sizeof(value))); +} + +template +class FixedString { + public: + FixedString() : + length_(0) { + data_[0] = '\0'; + } + + FixedString(const char* str) { + size_t len = std::strlen(str); + if (len > Capacity) + std::terminate(); + std::memcpy(data_, str, len); + length_ = len; + data_[length_] = '\0'; + } + + FixedString(const std::string& str) { + if (str.size() > Capacity) + std::terminate(); + std::memcpy(data_, str.data(), str.size()); + length_ = str.size(); + data_[length_] = '\0'; + } + + std::size_t size() const { return length_; } + std::size_t capacity() const { return Capacity; } + + const char* c_str() const { return data_; } + const char* data() const { return data_; } + + char& operator[](std::size_t i) { return data_[i]; } + + const char& operator[](std::size_t i) const { return data_[i]; } + + FixedString& operator+=(const char* str) { + size_t len = std::strlen(str); + if (length_ + len > Capacity) + std::terminate(); + std::memcpy(data_ + length_, str, len); + length_ += len; + data_[length_] = '\0'; + return *this; + } + + FixedString& operator+=(const FixedString& other) { return (*this += other.c_str()); } + + operator std::string() const { return std::string(data_, length_); } + + operator std::string_view() const { return std::string_view(data_, length_); } + + template + bool operator==(const T& other) const noexcept { + return (std::string_view) (*this) == other; + } + + template + bool operator!=(const T& other) const noexcept { + return (std::string_view) (*this) != other; + } + + void clear() { + length_ = 0; + data_[0] = '\0'; + } + + private: + char data_[Capacity + 1]; // +1 for null terminator + std::size_t length_; +}; + struct CommandLine { public: CommandLine(int _argc, char** _argv) : @@ -335,4 +427,11 @@ void move_to_front(std::vector& vec, Predicate pred) { } // namespace Stockfish +template +struct std::hash> { + std::size_t operator()(const Stockfish::FixedString& fstr) const noexcept { + return std::hash{}((std::string_view) fstr); + } +}; + #endif // #ifndef MISC_H_INCLUDED diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index b4440c1e3fb..7ead09327e5 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -181,6 +181,15 @@ class AffineTransform { return !stream.fail(); } + + std::size_t get_content_hash() const { + std::size_t h = 0; + hash_combine(h, get_raw_data_hash(biases)); + hash_combine(h, get_raw_data_hash(weights)); + hash_combine(h, get_hash_value(0)); + return h; + } + // Forward propagation void propagate(const InputType* input, OutputType* output) const { diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 472da834f7d..85c0dbfecab 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -241,6 +241,15 @@ class AffineTransformSparseInput { return !stream.fail(); } + + std::size_t get_content_hash() const { + std::size_t h = 0; + hash_combine(h, get_raw_data_hash(biases)); + hash_combine(h, get_raw_data_hash(weights)); + hash_combine(h, get_hash_value(0)); + return h; + } + // Forward propagation void propagate(const InputType* input, OutputType* output) const { diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index 2ad5a86a12c..a8679d14d72 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -58,6 +58,12 @@ class ClippedReLU { // Write network parameters bool write_parameters(std::ostream&) const { return true; } + std::size_t get_content_hash() const { + std::size_t h = 0; + hash_combine(h, get_hash_value(0)); + return h; + } + // Forward propagation void propagate(const InputType* input, OutputType* output) const { diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index d14f1e0aba3..4218c0fe259 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -58,6 +58,12 @@ class SqrClippedReLU { // Write network parameters bool write_parameters(std::ostream&) const { return true; } + std::size_t get_content_hash() const { + std::size_t h = 0; + hash_combine(h, get_hash_value(0)); + return h; + } + // Forward propagation void propagate(const InputType* input, OutputType* output) const { diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 957dc7bffcc..48aeafdf6cd 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -30,7 +29,6 @@ #include "../incbin/incbin.h" #include "../evaluate.h" -#include "../memory.h" #include "../misc.h" #include "../position.h" #include "../types.h" @@ -101,7 +99,7 @@ bool read_parameters(std::istream& stream, T& reference) { // Write evaluation function parameters template -bool write_parameters(std::ostream& stream, T& reference) { +bool write_parameters(std::ostream& stream, const T& reference) { write_little_endian(stream, T::get_hash_value()); return reference.write_parameters(stream); @@ -109,43 +107,6 @@ bool write_parameters(std::ostream& stream, T& reference) { } // namespace Detail -template -Network::Network(const Network& other) : - evalFile(other.evalFile), - embeddedType(other.embeddedType) { - - if (other.featureTransformer) - featureTransformer = make_unique_large_page(*other.featureTransformer); - - network = make_unique_aligned(LayerStacks); - - if (!other.network) - return; - - for (std::size_t i = 0; i < LayerStacks; ++i) - network[i] = other.network[i]; -} - -template -Network& -Network::operator=(const Network& other) { - evalFile = other.evalFile; - embeddedType = other.embeddedType; - - if (other.featureTransformer) - featureTransformer = make_unique_large_page(*other.featureTransformer); - - network = make_unique_aligned(LayerStacks); - - if (!other.network) - return *this; - - for (std::size_t i = 0; i < LayerStacks; ++i) - network[i] = other.network[i]; - - return *this; -} - template void Network::load(const std::string& rootDirectory, std::string evalfilePath) { #if defined(DEFAULT_NNUE_DIRECTORY) @@ -160,14 +121,14 @@ void Network::load(const std::string& rootDirectory, std::str for (const auto& directory : dirs) { - if (evalFile.current != evalfilePath) + if (std::string(evalFile.current) != evalfilePath) { if (directory != "") { load_user_net(directory, evalfilePath); } - if (directory == "" && evalfilePath == evalFile.defaultName) + if (directory == "" && evalfilePath == std::string(evalFile.defaultName)) { load_internal(); } @@ -185,7 +146,7 @@ bool Network::save(const std::optional& filename actualFilename = filename.value(); else { - if (evalFile.current != evalFile.defaultName) + if (std::string(evalFile.current) != std::string(evalFile.defaultName)) { msg = "Failed to export a net. " "A non-embedded net can only be saved if the filename is specified"; @@ -222,7 +183,7 @@ Network::evaluate(const Position& pos const int bucket = (pos.count() - 1) / 4; const auto psqt = - featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); + featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); return {static_cast(psqt / OutputScale), static_cast(positional / OutputScale)}; } @@ -234,7 +195,7 @@ void Network::verify(std::string if (evalfilePath.empty()) evalfilePath = evalFile.defaultName; - if (evalFile.current != evalfilePath) + if (std::string(evalFile.current) != evalfilePath) { if (f) { @@ -245,7 +206,7 @@ void Network::verify(std::string "including the directory name, to the network file."; std::string msg4 = "The default net can be downloaded from: " "https://tests.stockfishchess.org/api/nn/" - + evalFile.defaultName; + + std::string(evalFile.defaultName); std::string msg5 = "The engine will be terminated now."; std::string msg = "ERROR: " + msg1 + '\n' + "ERROR: " + msg2 + '\n' + "ERROR: " + msg3 @@ -259,9 +220,9 @@ void Network::verify(std::string if (f) { - size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks; + size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks; f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024)) - + "MiB, (" + std::to_string(featureTransformer->InputDimensions) + ", " + + "MiB, (" + std::to_string(featureTransformer.InputDimensions) + ", " + std::to_string(network[0].TransformedFeatureDimensions) + ", " + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + ", 1))"); @@ -287,7 +248,7 @@ Network::trace_evaluate(const Position& for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); + featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); @@ -341,8 +302,7 @@ void Network::load_internal() { template void Network::initialize() { - featureTransformer = make_unique_large_page(); - network = make_unique_aligned(LayerStacks); + initialized = true; } @@ -366,6 +326,20 @@ std::optional Network::load(std::istream& stream } +template +std::size_t Network::get_content_hash() const { + if (!initialized) + return 0; + + std::size_t h = 0; + hash_combine(h, featureTransformer); + for (auto&& layerstack : network) + hash_combine(h, layerstack); + hash_combine(h, evalFile); + hash_combine(h, static_cast(embeddedType)); + return h; +} + // Read network header template bool Network::read_header(std::istream& stream, @@ -399,13 +373,13 @@ bool Network::write_header(std::ostream& stream, template bool Network::read_parameters(std::istream& stream, - std::string& netDescription) const { + std::string& netDescription) { std::uint32_t hashValue; if (!read_header(stream, &hashValue, &netDescription)) return false; if (hashValue != Network::hash) return false; - if (!Detail::read_parameters(stream, *featureTransformer)) + if (!Detail::read_parameters(stream, featureTransformer)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) { @@ -421,7 +395,7 @@ bool Network::write_parameters(std::ostream& stream, const std::string& netDescription) const { if (!write_header(stream, Network::hash, netDescription)) return false; - if (!Detail::write_parameters(stream, *featureTransformer)) + if (!Detail::write_parameters(stream, featureTransformer)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) { diff --git a/src/nnue/network.h b/src/nnue/network.h index 6855831d5e4..c701ac6f5a8 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -19,16 +19,18 @@ #ifndef NETWORK_H_INCLUDED #define NETWORK_H_INCLUDED +#include #include #include #include +#include #include #include #include #include #include -#include "../memory.h" +#include "../misc.h" #include "../types.h" #include "nnue_accumulator.h" #include "nnue_architecture.h" @@ -49,6 +51,9 @@ enum class EmbeddedNNUEType { using NetworkOutput = std::tuple; +// The network must be a trivial type, i.e. the memory must be in-line. +// This is required to allow sharing the network via shared memory, as +// there is no way to run destructors. template class Network { static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions; @@ -58,15 +63,17 @@ class Network { evalFile(file), embeddedType(type) {} - Network(const Network& other); - Network(Network&& other) = default; + Network(const Network& other) = default; + Network(Network&& other) = default; - Network& operator=(const Network& other); - Network& operator=(Network&& other) = default; + Network& operator=(const Network& other) = default; + Network& operator=(Network&& other) = default; void load(const std::string& rootDirectory, std::string evalfilePath); bool save(const std::optional& filename) const; + std::size_t get_content_hash() const; + NetworkOutput evaluate(const Position& pos, AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const; @@ -89,18 +96,20 @@ class Network { bool read_header(std::istream&, std::uint32_t*, std::string*) const; bool write_header(std::ostream&, std::uint32_t, const std::string&) const; - bool read_parameters(std::istream&, std::string&) const; + bool read_parameters(std::istream&, std::string&); bool write_parameters(std::ostream&, const std::string&) const; // Input feature converter - LargePagePtr featureTransformer; + Transformer featureTransformer; // Evaluation function - AlignedPtr network; + Arch network[LayerStacks]; EvalFile evalFile; EmbeddedNNUEType embeddedType; + bool initialized = false; + // Hash value of evaluation function structure static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); @@ -121,9 +130,9 @@ using NetworkSmall = Network; struct Networks { - Networks(NetworkBig&& nB, NetworkSmall&& nS) : - big(std::move(nB)), - small(std::move(nS)) {} + Networks(std::unique_ptr&& nB, std::unique_ptr&& nS) : + big(std::move(*nB)), + small(std::move(*nS)) {} NetworkBig big; NetworkSmall small; @@ -132,4 +141,22 @@ struct Networks { } // namespace Stockfish +template +struct std::hash> { + std::size_t operator()( + const Stockfish::Eval::NNUE::Network& network) const noexcept { + return network.get_content_hash(); + } +}; + +template<> +struct std::hash { + std::size_t operator()(const Stockfish::Eval::NNUE::Networks& networks) const noexcept { + std::size_t h = 0; + Stockfish::hash_combine(h, networks.big); + Stockfish::hash_combine(h, networks.small); + return h; + } +}; + #endif diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 7ff95b6eff7..95cf74fa3d0 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -87,7 +87,7 @@ struct AccumulatorCaches { void clear(const Network& network) { for (auto& entries1D : entries) for (auto& entry : entries1D) - entry.clear(network.featureTransformer->biases); + entry.clear(network.featureTransformer.biases); } std::array& operator[](Square sq) { return entries[sq]; } diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index c020ce05b38..5965f24aa38 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -97,7 +97,7 @@ struct NetworkArchitecture { && fc_2.write_parameters(stream); } - std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { + std::int32_t propagate(const TransformedFeatureType* transformedFeatures) const { struct alignas(CacheLineSize) Buffer { alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out; alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType @@ -136,8 +136,28 @@ struct NetworkArchitecture { return outputValue; } + + std::size_t get_content_hash() const { + std::size_t h = 0; + hash_combine(h, fc_0.get_content_hash()); + hash_combine(h, ac_sqr_0.get_content_hash()); + hash_combine(h, ac_0.get_content_hash()); + hash_combine(h, fc_1.get_content_hash()); + hash_combine(h, ac_1.get_content_hash()); + hash_combine(h, fc_2.get_content_hash()); + hash_combine(h, get_hash_value()); + return h; + } }; } // namespace Stockfish::Eval::NNUE +template +struct std::hash> { + std::size_t + operator()(const Stockfish::Eval::NNUE::NetworkArchitecture& arch) const noexcept { + return arch.get_content_hash(); + } +}; + #endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 550bcaad41b..3617a85eb28 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -258,8 +258,8 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size } }; - auto write = [&](std::uint8_t byte) { - buf[buf_pos++] = byte; + auto write = [&](std::uint8_t b) { + buf[buf_pos++] = b; if (buf_pos == BUF_SIZE) flush(); }; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 37634cbc6b7..3439bc43c66 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -157,20 +157,28 @@ class FeatureTransformer { } // Write network parameters - bool write_parameters(std::ostream& stream) { + bool write_parameters(std::ostream& stream) const { + std::unique_ptr copy = std::make_unique(*this); - unpermute_weights(); - scale_weights(false); + copy->unpermute_weights(); + copy->scale_weights(false); - write_leb_128(stream, biases, HalfDimensions); - write_leb_128(stream, weights, HalfDimensions * InputDimensions); - write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + write_leb_128(stream, copy->biases, HalfDimensions); + write_leb_128(stream, copy->weights, HalfDimensions * InputDimensions); + write_leb_128(stream, copy->psqtWeights, PSQTBuckets * InputDimensions); - permute_weights(); - scale_weights(true); return !stream.fail(); } + std::size_t get_content_hash() const { + std::size_t h = 0; + hash_combine(h, get_raw_data_hash(biases)); + hash_combine(h, get_raw_data_hash(weights)); + hash_combine(h, get_raw_data_hash(psqtWeights)); + hash_combine(h, get_hash_value()); + return h; + } + // Convert input features std::int32_t transform(const Position& pos, AccumulatorStack& accumulatorStack, @@ -309,4 +317,14 @@ class FeatureTransformer { } // namespace Stockfish::Eval::NNUE + +template +struct std::hash> { + std::size_t + operator()(const Stockfish::Eval::NNUE::FeatureTransformer& ft) + const noexcept { + return ft.get_content_hash(); + } +}; + #endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index c99874076db..957e3453f19 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -120,11 +120,11 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat format_cp_compact(value, &board[y + 2][x + 2], pos); }; - AccumulatorStack accumulators; + auto accumulators = std::make_unique(); // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -139,8 +139,8 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat { pos.remove_piece(sq); - accumulators.reset(); - std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + accumulators->reset(); + std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, &caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; @@ -156,8 +156,8 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << board[row] << '\n'; ss << '\n'; - accumulators.reset(); - auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big); + accumulators->reset(); + auto t = networks.big.trace_evaluate(pos, *accumulators, &caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index 02212160a12..7ecfd58a2f6 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -20,8 +20,10 @@ #define NNUE_MISC_H_INCLUDED #include +#include #include +#include "../misc.h" #include "../types.h" #include "nnue_architecture.h" @@ -31,17 +33,17 @@ class Position; namespace Eval::NNUE { +// EvalFile uses fixed string types because it's part of the network structure which must be trivial. struct EvalFile { // Default net name, will use one of the EvalFileDefaultName* macros defined // in evaluate.h - std::string defaultName; + FixedString<256> defaultName; // Selected net name, either via uci option or default - std::string current; + FixedString<256> current; // Net description extracted from the net file - std::string netDescription; + FixedString<256> netDescription; }; - struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); @@ -58,4 +60,15 @@ std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& ca } // namespace Stockfish::Eval::NNUE } // namespace Stockfish +template<> +struct std::hash { + std::size_t operator()(const Stockfish::Eval::NNUE::EvalFile& evalFile) const noexcept { + std::size_t h = 0; + Stockfish::hash_combine(h, evalFile.defaultName); + Stockfish::hash_combine(h, evalFile.current); + Stockfish::hash_combine(h, evalFile.netDescription); + return h; + } +}; + #endif // #ifndef NNUE_MISC_H_INCLUDED diff --git a/src/numa.h b/src/numa.h index 5cadbc726fc..261b6005d37 100644 --- a/src/numa.h +++ b/src/numa.h @@ -37,7 +37,7 @@ #include #include -#include "memory.h" +#include "shm.h" // We support linux very well, but we explicitly do NOT support Android, // because there is no affected systems, not worth maintaining. @@ -945,10 +945,11 @@ class NumaConfig { th.join(); } - private: std::vector> nodes; std::map nodeByCpu; - CpuIndex highestCpuIndex; + + private: + CpuIndex highestCpuIndex; bool customAffinity; @@ -1263,6 +1264,141 @@ class LazyNumaReplicated: public NumaReplicatedBase { } }; +// Utilizes shared memory. +template +class LazyNumaReplicatedSystemWide: public NumaReplicatedBase { + public: + using ReplicatorFuncType = std::function; + + LazyNumaReplicatedSystemWide(NumaReplicationContext& ctx) : + NumaReplicatedBase(ctx) { + prepare_replicate_from(std::make_unique()); + } + + LazyNumaReplicatedSystemWide(NumaReplicationContext& ctx, std::unique_ptr&& source) : + NumaReplicatedBase(ctx) { + prepare_replicate_from(std::move(source)); + } + + LazyNumaReplicatedSystemWide(const LazyNumaReplicatedSystemWide&) = delete; + LazyNumaReplicatedSystemWide(LazyNumaReplicatedSystemWide&& other) noexcept : + NumaReplicatedBase(std::move(other)), + instances(std::exchange(other.instances, {})) {} + + LazyNumaReplicatedSystemWide& operator=(const LazyNumaReplicatedSystemWide&) = delete; + LazyNumaReplicatedSystemWide& operator=(LazyNumaReplicatedSystemWide&& other) noexcept { + NumaReplicatedBase::operator=(*this, std::move(other)); + instances = std::exchange(other.instances, {}); + + return *this; + } + + LazyNumaReplicatedSystemWide& operator=(std::unique_ptr&& source) { + prepare_replicate_from(std::move(source)); + + return *this; + } + + ~LazyNumaReplicatedSystemWide() override = default; + + const T& operator[](NumaReplicatedAccessToken token) const { + assert(token.get_numa_index() < instances.size()); + ensure_present(token.get_numa_index()); + return *(instances[token.get_numa_index()]); + } + + const T& operator*() const { return *(instances[0]); } + + const T* operator->() const { return &*instances[0]; } + + std::vector>> + get_status_and_errors() const { + std::vector>> + status; + status.reserve(instances.size()); + + for (const auto& instance : instances) + { + status.emplace_back(instance.get_status(), instance.get_error_message()); + } + + return status; + } + + template + void modify_and_replicate(FuncT&& f) { + auto source = std::make_unique(*instances[0]); + std::forward(f)(*source); + prepare_replicate_from(std::move(source)); + } + + void on_numa_config_changed() override { + // Use the first one as the source. It doesn't matter which one we use, + // because they all must be identical, but the first one is guaranteed to exist. + auto source = std::make_unique(*instances[0]); + prepare_replicate_from(std::move(source)); + } + + private: + mutable std::vector> instances; + mutable std::mutex mutex; + + std::size_t get_discriminator(NumaIndex idx) const { + const NumaConfig& cfg = get_numa_config(); + const NumaConfig& cfg_sys = NumaConfig::from_system(false); + // as a descriminator, locate the hardware/system numadomain this cpuindex belongs to + CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex + NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; + std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx); + return std::hash{}(s); + } + + void ensure_present(NumaIndex idx) const { + assert(idx < instances.size()); + + if (instances[idx] != nullptr) + return; + + assert(idx != 0); + + std::unique_lock lock(mutex); + // Check again for races. + if (instances[idx] != nullptr) + return; + + const NumaConfig& cfg = get_numa_config(); + cfg.execute_on_numa_node(idx, [this, idx]() { + instances[idx] = SystemWideSharedConstant(*instances[0], get_discriminator(idx)); + }); + } + + void prepare_replicate_from(std::unique_ptr&& source) { + instances.clear(); + + const NumaConfig& cfg = get_numa_config(); + // We just need to make sure the first instance is there. + // Note that we cannot move here as we need to reallocate the data + // on the correct NUMA node. + // Even in the case of a single NUMA node we have to copy since it's shared memory. + if (cfg.requires_memory_replication()) + { + assert(cfg.num_numa_nodes() > 0); + + cfg.execute_on_numa_node(0, [this, &source]() { + instances.emplace_back(SystemWideSharedConstant(*source, get_discriminator(0))); + }); + + // Prepare others for lazy init. + instances.resize(cfg.num_numa_nodes()); + } + else + { + assert(cfg.num_numa_nodes() == 1); + instances.emplace_back(SystemWideSharedConstant(*source, get_discriminator(0))); + } + } +}; + class NumaReplicationContext { public: NumaReplicationContext(NumaConfig&& cfg) : diff --git a/src/search.h b/src/search.h index d4bbca5c614..4c4357d3e2d 100644 --- a/src/search.h +++ b/src/search.h @@ -133,19 +133,19 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& optionsMap, - ThreadPool& threadPool, - TranspositionTable& transpositionTable, - const LazyNumaReplicated& nets) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const LazyNumaReplicatedSystemWide& nets) : options(optionsMap), threads(threadPool), tt(transpositionTable), networks(nets) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - const LazyNumaReplicated& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const LazyNumaReplicatedSystemWide& networks; }; class Worker; @@ -349,10 +349,10 @@ class Worker { Tablebases::Config tbConfig; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - const LazyNumaReplicated& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const LazyNumaReplicatedSystemWide& networks; // Used by NNUE Eval::NNUE::AccumulatorStack accumulatorStack; diff --git a/src/shm.h b/src/shm.h new file mode 100644 index 00000000000..ae542967653 --- /dev/null +++ b/src/shm.h @@ -0,0 +1,634 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SHM_H_INCLUDED +#define SHM_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) && !defined(__ANDROID__) + #include "shm_linux.h" +#endif + +#if defined(__ANDROID__) + #include + #define SF_MAX_SEM_NAME_LEN NAME_MAX +#endif + +#include "types.h" + +#include "memory.h" + +#if defined(_WIN32) + + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + + #if !defined(NOMINMAX) + #define NOMINMAX + #endif + #include +#else + #include + #include + #include + #include + #include + #include + #include +#endif + + +#if defined(__APPLE__) + #include + #include + +#elif defined(__sun) + #include + +#elif defined(__FreeBSD__) + #include + #include + #include + +#elif defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__) + #include + #include +#endif + + +namespace Stockfish { + +// argv[0] CANNOT be used because we need to identify the executable. +// argv[0] contains the command used to invoke it, which does not involve the full path. +// Just using a path is not fully resilient either, as the executable could +// have changed if it wasn't locked by the OS. Ideally we would hash the executable +// but it's not really that important at this point. +// If the path is longer than 4095 bytes the hash will be computed from an unspecified +// amount of bytes of the path; in particular it can a hash of an empty string. + +inline std::string getExecutablePathHash() { + char executable_path[4096] = {0}; + std::size_t path_length = 0; + +#if defined(_WIN32) + path_length = GetModuleFileNameA(NULL, executable_path, sizeof(executable_path)); + +#elif defined(__APPLE__) + uint32_t size = sizeof(executable_path); + if (_NSGetExecutablePath(executable_path, &size) == 0) + { + path_length = std::strlen(executable_path); + } + +#elif defined(__sun) // Solaris + const char* path = getexecname(); + if (path) + { + std::strncpy(executable_path, path, sizeof(executable_path) - 1); + path_length = std::strlen(executable_path); + } + +#elif defined(__FreeBSD__) + size_t size = sizeof(executable_path); + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + if (sysctl(mib, 4, executable_path, &size, NULL, 0) == 0) + { + path_length = std::strlen(executable_path); + } + +#elif defined(__NetBSD__) || defined(__DragonFly__) + ssize_t len = readlink("/proc/curproc/exe", executable_path, sizeof(executable_path) - 1); + if (len >= 0) + { + executable_path[len] = '\0'; + path_length = len; + } + +#elif defined(__linux__) + ssize_t len = readlink("/proc/self/exe", executable_path, sizeof(executable_path) - 1); + if (len >= 0) + { + executable_path[len] = '\0'; + path_length = len; + } + +#endif + + // In case of any error the path will be empty. + return std::string(executable_path, path_length); +} + +enum class SystemWideSharedConstantAllocationStatus { + NoAllocation, + LocalMemory, + SharedMemory +}; + +#if defined(_WIN32) + +inline std::string GetLastErrorAsString(DWORD error) { + //Get the error message ID, if any. + DWORD errorMessageID = error; + if (errorMessageID == 0) + { + return std::string(); //No error message has been recorded + } + + LPSTR messageBuffer = nullptr; + + //Ask Win32 to give us the string version of that message ID. + //The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be). + size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR) &messageBuffer, 0, NULL); + + //Copy the error message into a std::string. + std::string message(messageBuffer, size); + + //Free the Win32's string's buffer. + LocalFree(messageBuffer); + + return message; +} + +// Utilizes shared memory to store the value. It is deduplicated system-wide (for the single user). +template +class SharedMemoryBackend { + public: + enum class Status { + Success, + LargePageAllocationError, + FileMappingError, + MapViewError, + MutexCreateError, + MutexWaitError, + MutexReleaseError, + NotInitialized + }; + + static constexpr DWORD IS_INITIALIZED_VALUE = 1; + + SharedMemoryBackend() : + status(Status::NotInitialized) {}; + + SharedMemoryBackend(const std::string& shm_name, const T& value) : + status(Status::NotInitialized) { + + initialize(shm_name, value); + } + + bool is_valid() const { return status == Status::Success; } + + std::optional get_error_message() const { + switch (status) + { + case Status::Success : + return std::nullopt; + case Status::LargePageAllocationError : + return "Failed to allocate large page memory"; + case Status::FileMappingError : + return "Failed to create file mapping: " + last_error_message; + case Status::MapViewError : + return "Failed to map view: " + last_error_message; + case Status::MutexCreateError : + return "Failed to create mutex: " + last_error_message; + case Status::MutexWaitError : + return "Failed to wait on mutex: " + last_error_message; + case Status::MutexReleaseError : + return "Failed to release mutex: " + last_error_message; + case Status::NotInitialized : + return "Not initialized"; + default : + return "Unknown error"; + } + } + + void* get() const { return is_valid() ? pMap : nullptr; } + + ~SharedMemoryBackend() { cleanup(); } + + SharedMemoryBackend(const SharedMemoryBackend&) = delete; + SharedMemoryBackend& operator=(const SharedMemoryBackend&) = delete; + + SharedMemoryBackend(SharedMemoryBackend&& other) noexcept : + pMap(other.pMap), + hMapFile(other.hMapFile), + status(other.status), + last_error_message(std::move(other.last_error_message)) { + + other.pMap = nullptr; + other.hMapFile = 0; + other.status = Status::NotInitialized; + } + + SharedMemoryBackend& operator=(SharedMemoryBackend&& other) noexcept { + if (this != &other) + { + cleanup(); + pMap = other.pMap; + hMapFile = other.hMapFile; + status = other.status; + last_error_message = std::move(other.last_error_message); + + other.pMap = nullptr; + other.hMapFile = 0; + other.status = Status::NotInitialized; + } + return *this; + } + + SystemWideSharedConstantAllocationStatus get_status() const { + return status == Status::Success ? SystemWideSharedConstantAllocationStatus::SharedMemory + : SystemWideSharedConstantAllocationStatus::NoAllocation; + } + + private: + void initialize(const std::string& shm_name, const T& value) { + const size_t total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE); + + // Try allocating with large pages first. + hMapFile = windows_try_with_large_page_priviliges( + [&](size_t largePageSize) { + const size_t total_size_aligned = + (total_size + largePageSize - 1) / largePageSize * largePageSize; + + #if defined(_WIN64) + DWORD total_size_low = total_size_aligned & 0xFFFFFFFFu; + DWORD total_size_high = total_size_aligned >> 32u; + #else + DWORD total_size_low = total_size_aligned; + DWORD total_size_high = 0; + #endif + + return CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES, + total_size_high, total_size_low, shm_name.c_str()); + }, + []() { return (void*) nullptr; }); + + // Fallback to normal allocation if no large pages available. + if (!hMapFile) + { + hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, + static_cast(total_size), shm_name.c_str()); + } + + if (!hMapFile) + { + const DWORD err = GetLastError(); + last_error_message = GetLastErrorAsString(err); + status = Status::FileMappingError; + return; + } + + pMap = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, total_size); + if (!pMap) + { + const DWORD err = GetLastError(); + last_error_message = GetLastErrorAsString(err); + status = Status::MapViewError; + cleanup_partial(); + return; + } + + // Use named mutex to ensure only one initializer + std::string mutex_name = shm_name + "$mutex"; + HANDLE hMutex = CreateMutexA(NULL, FALSE, mutex_name.c_str()); + if (!hMutex) + { + const DWORD err = GetLastError(); + last_error_message = GetLastErrorAsString(err); + status = Status::MutexCreateError; + cleanup_partial(); + return; + } + + DWORD wait_result = WaitForSingleObject(hMutex, INFINITE); + if (wait_result != WAIT_OBJECT_0) + { + const DWORD err = GetLastError(); + last_error_message = GetLastErrorAsString(err); + status = Status::MutexWaitError; + CloseHandle(hMutex); + cleanup_partial(); + return; + } + + // Crucially, we place the object first to ensure alignment. + volatile DWORD* is_initialized = + std::launder(reinterpret_cast(reinterpret_cast(pMap) + sizeof(T))); + T* object = std::launder(reinterpret_cast(pMap)); + + if (*is_initialized != IS_INITIALIZED_VALUE) + { + // First time initialization, message for debug purposes + new (object) T{value}; + *is_initialized = IS_INITIALIZED_VALUE; + } + + BOOL release_result = ReleaseMutex(hMutex); + CloseHandle(hMutex); + + if (!release_result) + { + const DWORD err = GetLastError(); + last_error_message = GetLastErrorAsString(err); + status = Status::MutexReleaseError; + cleanup_partial(); + return; + } + + status = Status::Success; + } + + void cleanup_partial() { + if (pMap != nullptr) + { + UnmapViewOfFile(pMap); + pMap = nullptr; + } + if (hMapFile) + { + CloseHandle(hMapFile); + hMapFile = 0; + } + } + + void cleanup() { + if (pMap != nullptr) + { + UnmapViewOfFile(pMap); + pMap = nullptr; + } + if (hMapFile) + { + CloseHandle(hMapFile); + hMapFile = 0; + } + } + + void* pMap = nullptr; + HANDLE hMapFile = 0; + Status status = Status::NotInitialized; + std::string last_error_message; +}; + +#elif !defined(__ANDROID__) + +template +class SharedMemoryBackend { + public: + SharedMemoryBackend() = default; + + SharedMemoryBackend(const std::string& shm_name, const T& value) : + shm1(shm::create_shared(shm_name, value)) {} + + void* get() const { + const T* ptr = &shm1->get(); + return reinterpret_cast(const_cast(ptr)); + } + + bool is_valid() const { return shm1 && shm1->is_open() && shm1->is_initialized(); } + + SystemWideSharedConstantAllocationStatus get_status() const { + return is_valid() ? SystemWideSharedConstantAllocationStatus::SharedMemory + : SystemWideSharedConstantAllocationStatus::NoAllocation; + } + + std::optional get_error_message() const { + if (!shm1) + return "Shared memory not initialized"; + + if (!shm1->is_open()) + return "Shared memory is not open"; + + if (!shm1->is_initialized()) + return "Not initialized"; + + return std::nullopt; + } + + private: + std::optional> shm1; +}; + +#else + +// For systems that don't have shared memory, or support is troublesome. +// The way fallback is done is that we need a dummy backend. + +template +class SharedMemoryBackend { + public: + SharedMemoryBackend() = default; + + SharedMemoryBackend(const std::string& shm_name, const T& value) {} + + void* get() const { return nullptr; } + + bool is_valid() const { return false; } + + SystemWideSharedConstantAllocationStatus get_status() const { + return SystemWideSharedConstantAllocationStatus::NoAllocation; + } + + std::optional get_error_message() const { return "Dummy SharedMemoryBackend"; } +}; + +#endif + +template +struct SharedMemoryBackendFallback { + SharedMemoryBackendFallback() = default; + + SharedMemoryBackendFallback(const std::string&, const T& value) : + fallback_object(make_unique_large_page(value)) {} + + void* get() const { return fallback_object.get(); } + + SharedMemoryBackendFallback(const SharedMemoryBackendFallback&) = delete; + SharedMemoryBackendFallback& operator=(const SharedMemoryBackendFallback&) = delete; + + SharedMemoryBackendFallback(SharedMemoryBackendFallback&& other) noexcept : + fallback_object(std::move(other.fallback_object)) {} + + SharedMemoryBackendFallback& operator=(SharedMemoryBackendFallback&& other) noexcept { + fallback_object = std::move(other.fallback_object); + return *this; + } + + SystemWideSharedConstantAllocationStatus get_status() const { + return fallback_object == nullptr ? SystemWideSharedConstantAllocationStatus::NoAllocation + : SystemWideSharedConstantAllocationStatus::LocalMemory; + } + + std::optional get_error_message() const { + if (fallback_object == nullptr) + return "Not initialized"; + + return "Shared memory not supported by the OS. Local allocation fallback."; + } + + private: + LargePagePtr fallback_object; +}; + +// Platform-independent wrapper +template +struct SystemWideSharedConstant { + private: + static std::string createHashString(const std::string& input) { + size_t hash = std::hash{}(input); + + std::stringstream ss; + ss << std::hex << std::setfill('0') << hash; + + return ss.str(); + } + + public: + // We can't run the destructor because it may be in a completely different process. + // The object stored must also be obviously in-line but we can't check for that, other than some basic checks that cover most cases. + static_assert(std::is_trivially_destructible_v); + static_assert(std::is_trivially_move_constructible_v); + static_assert(std::is_trivially_copy_constructible_v); + + SystemWideSharedConstant() = default; + + + // Content is addressed by its hash. An additional discriminator can be added to account for differences + // that are not present in the content, for example NUMA node allocation. + SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) { + std::size_t content_hash = std::hash{}(value); + std::size_t executable_hash = std::hash{}(getExecutablePathHash()); + + std::string shm_name = std::string("Local\\sf_") + std::to_string(content_hash) + "$" + + std::to_string(executable_hash) + "$" + + std::to_string(discriminator); + +#if !defined(_WIN32) + // POSIX shared memory names must start with a slash + shm_name = "/sf_" + createHashString(shm_name); + + // hash name and make sure it is not longer than SF_MAX_SEM_NAME_LEN + if (shm_name.size() > SF_MAX_SEM_NAME_LEN) + { + shm_name = shm_name.substr(0, SF_MAX_SEM_NAME_LEN - 1); + } +#endif + + SharedMemoryBackend shm_backend(shm_name, value); + + if (shm_backend.is_valid()) + { + backend = std::move(shm_backend); + } + else + { + backend = SharedMemoryBackendFallback(shm_name, value); + } + } + + SystemWideSharedConstant(const SystemWideSharedConstant&) = delete; + SystemWideSharedConstant& operator=(const SystemWideSharedConstant&) = delete; + + SystemWideSharedConstant(SystemWideSharedConstant&& other) noexcept : + backend(std::move(other.backend)) {} + + SystemWideSharedConstant& operator=(SystemWideSharedConstant&& other) noexcept { + backend = std::move(other.backend); + return *this; + } + + const T& operator*() const { return *std::launder(reinterpret_cast(get_ptr())); } + + bool operator==(std::nullptr_t) const noexcept { return get_ptr() == nullptr; } + + bool operator!=(std::nullptr_t) const noexcept { return get_ptr() != nullptr; } + + SystemWideSharedConstantAllocationStatus get_status() const { + return std::visit( + [](const auto& end) -> SystemWideSharedConstantAllocationStatus { + if constexpr (std::is_same_v, std::monostate>) + { + return SystemWideSharedConstantAllocationStatus::NoAllocation; + } + else + { + return end.get_status(); + } + }, + backend); + } + + std::optional get_error_message() const { + return std::visit( + [](const auto& end) -> std::optional { + if constexpr (std::is_same_v, std::monostate>) + { + return std::nullopt; + } + else + { + return end.get_error_message(); + } + }, + backend); + } + + private: + auto get_ptr() const { + return std::visit( + [](const auto& end) -> void* { + if constexpr (std::is_same_v, std::monostate>) + { + return nullptr; + } + else + { + return end.get(); + } + }, + backend); + } + + std::variant, SharedMemoryBackendFallback> backend; +}; + + +} // namespace Stockfish + +#endif // #ifndef SHM_H_INCLUDED diff --git a/src/shm_linux.h b/src/shm_linux.h new file mode 100644 index 00000000000..a8b5404b2e4 --- /dev/null +++ b/src/shm_linux.h @@ -0,0 +1,658 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SHM_LINUX_H_INCLUDED +#define SHM_LINUX_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__) + #include + #define SF_MAX_SEM_NAME_LEN NAME_MAX +#elif defined(__APPLE__) + #define SF_MAX_SEM_NAME_LEN 31 +#else + #define SF_MAX_SEM_NAME_LEN 255 +#endif + + +namespace Stockfish::shm { + +namespace detail { + +struct ShmHeader { + static constexpr uint32_t SHM_MAGIC = 0xAD5F1A12; + pthread_mutex_t mutex; + std::atomic ref_count{0}; + std::atomic initialized{false}; + uint32_t magic = SHM_MAGIC; +}; + +class SharedMemoryBase { + public: + virtual ~SharedMemoryBase() = default; + virtual void close() noexcept = 0; + virtual const std::string& name() const noexcept = 0; +}; + +class SharedMemoryRegistry { + private: + static std::mutex registry_mutex_; + static std::unordered_set active_instances_; + + public: + static void register_instance(SharedMemoryBase* instance) { + std::scoped_lock lock(registry_mutex_); + active_instances_.insert(instance); + } + + static void unregister_instance(SharedMemoryBase* instance) { + std::scoped_lock lock(registry_mutex_); + active_instances_.erase(instance); + } + + static void cleanup_all() noexcept { + std::scoped_lock lock(registry_mutex_); + for (auto* instance : active_instances_) + instance->close(); + active_instances_.clear(); + } +}; + +inline std::mutex SharedMemoryRegistry::registry_mutex_; +inline std::unordered_set SharedMemoryRegistry::active_instances_; + +class CleanupHooks { + private: + static std::once_flag register_once_; + + static void handle_signal(int sig) noexcept { + SharedMemoryRegistry::cleanup_all(); + _Exit(128 + sig); + } + + static void register_signal_handlers() noexcept { + std::atexit([]() { SharedMemoryRegistry::cleanup_all(); }); + + constexpr int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, + SIGSEGV, SIGTERM, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ}; + + struct sigaction sa; + sa.sa_handler = handle_signal; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + for (int sig : signals) + sigaction(sig, &sa, nullptr); + } + + public: + static void ensure_registered() noexcept { + std::call_once(register_once_, register_signal_handlers); + } +}; + +inline std::once_flag CleanupHooks::register_once_; + + +inline int portable_fallocate(int fd, off_t offset, off_t length) { +#ifdef __APPLE__ + fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, offset, length, 0}; + int ret = fcntl(fd, F_PREALLOCATE, &store); + if (ret == -1) + { + store.fst_flags = F_ALLOCATEALL; + ret = fcntl(fd, F_PREALLOCATE, &store); + } + if (ret != -1) + ret = ftruncate(fd, offset + length); + return ret; +#else + return posix_fallocate(fd, offset, length); +#endif +} + +} // namespace detail + +template +class SharedMemory: public detail::SharedMemoryBase { + static_assert(std::is_trivially_copyable_v, "T must be trivially copyable"); + static_assert(!std::is_pointer_v, "T cannot be a pointer type"); + + private: + std::string name_; + int fd_ = -1; + void* mapped_ptr_ = nullptr; + T* data_ptr_ = nullptr; + detail::ShmHeader* header_ptr_ = nullptr; + size_t total_size_ = 0; + std::string sentinel_base_; + std::string sentinel_path_; + + static constexpr size_t calculate_total_size() noexcept { + return sizeof(T) + sizeof(detail::ShmHeader); + } + + static std::string make_sentinel_base(const std::string& name) { + uint64_t hash = std::hash{}(name); + char buf[32]; + std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIx64, static_cast(hash)); + return buf; + } + + public: + explicit SharedMemory(const std::string& name) noexcept : + name_(name), + total_size_(calculate_total_size()), + sentinel_base_(make_sentinel_base(name)) {} + + ~SharedMemory() noexcept override { + detail::SharedMemoryRegistry::unregister_instance(this); + close(); + } + + SharedMemory(const SharedMemory&) = delete; + SharedMemory& operator=(const SharedMemory&) = delete; + + SharedMemory(SharedMemory&& other) noexcept : + name_(std::move(other.name_)), + fd_(other.fd_), + mapped_ptr_(other.mapped_ptr_), + data_ptr_(other.data_ptr_), + header_ptr_(other.header_ptr_), + total_size_(other.total_size_), + sentinel_base_(std::move(other.sentinel_base_)), + sentinel_path_(std::move(other.sentinel_path_)) { + + detail::SharedMemoryRegistry::unregister_instance(&other); + detail::SharedMemoryRegistry::register_instance(this); + other.reset(); + } + + SharedMemory& operator=(SharedMemory&& other) noexcept { + if (this != &other) + { + detail::SharedMemoryRegistry::unregister_instance(this); + close(); + + name_ = std::move(other.name_); + fd_ = other.fd_; + mapped_ptr_ = other.mapped_ptr_; + data_ptr_ = other.data_ptr_; + header_ptr_ = other.header_ptr_; + total_size_ = other.total_size_; + sentinel_base_ = std::move(other.sentinel_base_); + sentinel_path_ = std::move(other.sentinel_path_); + + detail::SharedMemoryRegistry::unregister_instance(&other); + detail::SharedMemoryRegistry::register_instance(this); + + other.reset(); + } + return *this; + } + + [[nodiscard]] bool open(const T& initial_value) noexcept { + detail::CleanupHooks::ensure_registered(); + + bool retried_stale = false; + + while (true) + { + if (is_open()) + return false; + + bool created_new = false; + fd_ = shm_open(name_.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666); + + if (fd_ == -1) + { + fd_ = shm_open(name_.c_str(), O_RDWR, 0666); + if (fd_ == -1) + return false; + } + else + created_new = true; + + if (!lock_file(LOCK_EX)) + { + ::close(fd_); + reset(); + return false; + } + + bool invalid_header = false; + bool success = + created_new ? setup_new_region(initial_value) : setup_existing_region(invalid_header); + + if (!success) + { + if (created_new || invalid_header) + shm_unlink(name_.c_str()); + if (mapped_ptr_) + unmap_region(); + unlock_file(); + ::close(fd_); + reset(); + + if (!created_new && invalid_header && !retried_stale) + { + retried_stale = true; + continue; + } + return false; + } + + if (!lock_shared_mutex()) + { + if (created_new) + shm_unlink(name_.c_str()); + if (mapped_ptr_) + unmap_region(); + unlock_file(); + ::close(fd_); + reset(); + + if (!created_new && !retried_stale) + { + retried_stale = true; + continue; + } + return false; + } + + if (!create_sentinel_file_locked()) + { + unlock_shared_mutex(); + unmap_region(); + if (created_new) + shm_unlink(name_.c_str()); + unlock_file(); + ::close(fd_); + reset(); + return false; + } + + header_ptr_->ref_count.fetch_add(1, std::memory_order_acq_rel); + + unlock_shared_mutex(); + unlock_file(); + detail::SharedMemoryRegistry::register_instance(this); + return true; + } + } + + void close() noexcept override { + if (fd_ == -1 && mapped_ptr_ == nullptr) + return; + + bool remove_region = false; + bool file_locked = lock_file(LOCK_EX); + bool mutex_locked = false; + + if (file_locked && header_ptr_ != nullptr) + mutex_locked = lock_shared_mutex(); + + if (mutex_locked) + { + if (header_ptr_) + { + header_ptr_->ref_count.fetch_sub(1, std::memory_order_acq_rel); + } + remove_sentinel_file(); + remove_region = !has_other_live_sentinels_locked(); + unlock_shared_mutex(); + } + else + { + remove_sentinel_file(); + decrement_refcount_relaxed(); + } + + unmap_region(); + + if (remove_region) + shm_unlink(name_.c_str()); + + if (file_locked) + unlock_file(); + + if (fd_ != -1) + { + ::close(fd_); + fd_ = -1; + } + + reset(); + } + + const std::string& name() const noexcept override { return name_; } + + [[nodiscard]] bool is_open() const noexcept { return fd_ != -1 && mapped_ptr_ && data_ptr_; } + + [[nodiscard]] const T& get() const noexcept { return *data_ptr_; } + + [[nodiscard]] const T* operator->() const noexcept { return data_ptr_; } + + [[nodiscard]] const T& operator*() const noexcept { return *data_ptr_; } + + [[nodiscard]] uint32_t ref_count() const noexcept { + return header_ptr_ ? header_ptr_->ref_count.load(std::memory_order_acquire) : 0; + } + + [[nodiscard]] bool is_initialized() const noexcept { + return header_ptr_ ? header_ptr_->initialized.load(std::memory_order_acquire) : false; + } + + static void cleanup_all_instances() noexcept { detail::SharedMemoryRegistry::cleanup_all(); } + + private: + void reset() noexcept { + fd_ = -1; + mapped_ptr_ = nullptr; + data_ptr_ = nullptr; + header_ptr_ = nullptr; + sentinel_path_.clear(); + } + + void unmap_region() noexcept { + if (mapped_ptr_) + { + munmap(mapped_ptr_, total_size_); + mapped_ptr_ = nullptr; + data_ptr_ = nullptr; + header_ptr_ = nullptr; + } + } + + [[nodiscard]] bool lock_file(int operation) noexcept { + if (fd_ == -1) + return false; + + while (flock(fd_, operation) == -1) + { + if (errno == EINTR) + continue; + return false; + } + return true; + } + + void unlock_file() noexcept { + if (fd_ == -1) + return; + + while (flock(fd_, LOCK_UN) == -1) + { + if (errno == EINTR) + continue; + break; + } + } + + std::string sentinel_full_path(pid_t pid) const { + std::string path = "/dev/shm/"; + path += sentinel_base_; + path.push_back('.'); + path += std::to_string(pid); + return path; + } + + void decrement_refcount_relaxed() noexcept { + if (!header_ptr_) + return; + + uint32_t expected = header_ptr_->ref_count.load(std::memory_order_relaxed); + while (expected != 0 + && !header_ptr_->ref_count.compare_exchange_weak( + expected, expected - 1, std::memory_order_acq_rel, std::memory_order_relaxed)) + {} + } + + bool create_sentinel_file_locked() noexcept { + if (!header_ptr_) + return false; + + const pid_t self_pid = getpid(); + sentinel_path_ = sentinel_full_path(self_pid); + + for (int attempt = 0; attempt < 2; ++attempt) + { + int fd = ::open(sentinel_path_.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600); + if (fd != -1) + { + ::close(fd); + return true; + } + + if (errno == EEXIST) + { + ::unlink(sentinel_path_.c_str()); + decrement_refcount_relaxed(); + continue; + } + + break; + } + + sentinel_path_.clear(); + return false; + } + + void remove_sentinel_file() noexcept { + if (!sentinel_path_.empty()) + { + ::unlink(sentinel_path_.c_str()); + sentinel_path_.clear(); + } + } + + static bool pid_is_alive(pid_t pid) noexcept { + if (pid <= 0) + return false; + + if (kill(pid, 0) == 0) + return true; + + return errno == EPERM; + } + + [[nodiscard]] bool initialize_shared_mutex() noexcept { + if (!header_ptr_) + return false; + + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr) != 0) + return false; + + bool success = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) == 0; +#ifdef PTHREAD_MUTEX_ROBUST + if (success) + success = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST) == 0; +#endif + + if (success) + success = pthread_mutex_init(&header_ptr_->mutex, &attr) == 0; + + pthread_mutexattr_destroy(&attr); + return success; + } + + [[nodiscard]] bool lock_shared_mutex() noexcept { + if (!header_ptr_) + return false; + + while (true) + { + int rc = pthread_mutex_lock(&header_ptr_->mutex); + if (rc == 0) + return true; + +#ifdef PTHREAD_MUTEX_ROBUST + if (rc == EOWNERDEAD) + { + if (pthread_mutex_consistent(&header_ptr_->mutex) == 0) + return true; + return false; + } +#endif + + if (rc == EINTR) + continue; + + return false; + } + } + + void unlock_shared_mutex() noexcept { + if (header_ptr_) + pthread_mutex_unlock(&header_ptr_->mutex); + } + + bool has_other_live_sentinels_locked() const noexcept { + DIR* dir = opendir("/dev/shm"); + if (!dir) + return false; + + std::string prefix = sentinel_base_ + "."; + bool found = false; + + while (dirent* entry = readdir(dir)) + { + std::string name = entry->d_name; + if (name.rfind(prefix, 0) != 0) + continue; + + auto pid_str = name.substr(prefix.size()); + char* end = nullptr; + long value = std::strtol(pid_str.c_str(), &end, 10); + if (!end || *end != '\0') + continue; + + pid_t pid = static_cast(value); + if (pid_is_alive(pid)) + { + found = true; + break; + } + + std::string stale_path = std::string("/dev/shm/") + name; + ::unlink(stale_path.c_str()); + const_cast(this)->decrement_refcount_relaxed(); + } + + closedir(dir); + return found; + } + + [[nodiscard]] bool setup_new_region(const T& initial_value) noexcept { + if (ftruncate(fd_, static_cast(total_size_)) == -1) + return false; + + if (detail::portable_fallocate(fd_, 0, static_cast(total_size_)) != 0) + return false; + + mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0); + if (mapped_ptr_ == MAP_FAILED) + { + mapped_ptr_ = nullptr; + return false; + } + + data_ptr_ = static_cast(mapped_ptr_); + header_ptr_ = + reinterpret_cast(static_cast(mapped_ptr_) + sizeof(T)); + + new (header_ptr_) detail::ShmHeader{}; + new (data_ptr_) T{initial_value}; + + if (!initialize_shared_mutex()) + return false; + + header_ptr_->ref_count.store(0, std::memory_order_release); + header_ptr_->initialized.store(true, std::memory_order_release); + return true; + } + + [[nodiscard]] bool setup_existing_region(bool& invalid_header) noexcept { + invalid_header = false; + + struct stat st; + fstat(fd_, &st); + if (static_cast(st.st_size) < total_size_) + { + invalid_header = true; + return false; + } + + mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0); + if (mapped_ptr_ == MAP_FAILED) + { + mapped_ptr_ = nullptr; + return false; + } + + data_ptr_ = static_cast(mapped_ptr_); + header_ptr_ = std::launder( + reinterpret_cast(static_cast(mapped_ptr_) + sizeof(T))); + + if (!header_ptr_->initialized.load(std::memory_order_acquire) + || header_ptr_->magic != detail::ShmHeader::SHM_MAGIC) + { + invalid_header = true; + unmap_region(); + return false; + } + + return true; + } +}; + +template +[[nodiscard]] std::optional> create_shared(const std::string& name, + const T& initial_value) noexcept { + SharedMemory shm(name); + if (shm.open(initial_value)) + return shm; + return std::nullopt; +} + +} // namespace Stockfish::shm + +#endif // #ifndef SHM_LINUX_H_INCLUDED From 8e5392d79a36aba5b997cf6fb590937e3e624e80 Mon Sep 17 00:00:00 2001 From: sscg13 Date: Tue, 11 Nov 2025 23:56:07 -0800 Subject: [PATCH 1205/1309] Update NNUE architecture to SFNNv10 with Threat Inputs and net nn-49c1193b131c.nnue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces Full Threat Input features, which are a subset of Piece(Square)-Piece(Square) pairs. In any given position, the active features consist of pairs where the second piece’s square lies in the attack set of the first piece. This is an extremely simplified explanation that leaves out many details. The already-used HalfKAv2_hm feature set completes the input features. Minor quantization changes have also been made. The net nn-49c1193b131c.nnue was trained by vondele using the following setup: https://github.com/vondele/nettest/blob/7de71238e9b295e3f88ed7c9c5936af632c9b981/threats.yaml A graphical version of an earlier scheme (with less refinement) that illustrates the core concepts can be found attached. [NewInputs.pdf](https://github.com/user-attachments/files/23478441/NewInputs.pdf) Further information, as well as a brief description of the history of development, can be found attached. [Stockfish threat inputs PR summary.pdf](https://github.com/user-attachments/files/23478634/Stockfish.threat.inputs.PR.summary.pdf) This has been a huge effort spanning over half a year, with the original [discussion thread](https://discord.com/channels/435943710472011776/1336647760388034610) reaching over 11k messages. Thanks to everyone who has contributed. Monty PRs: https://github.com/official-monty/Monty/pull/87 (Initial threat input PR) https://github.com/official-monty/Monty/pull/114 (Fixed threat indexing to take into account colour correctly) https://github.com/official-monty/Monty/pull/116 (i8 quantisation of weights whilst keeping calculations in i16) Yukari commit: https://github.com/yukarichess/yukari/commit/2d482c64a79cec03cf4987d5289334b9cdc737bc (Threat inputs merged) Plentychess PRs: https://github.com/Yoshie2000/PlentyChess/pull/400 (Threat inputs merged) https://github.com/Yoshie2000/PlentyChess/pull/411 (Threat input weights quantised to i8) Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 63424 W: 16956 L: 16591 D: 29877 Ptnml(0-2): 276, 7522, 15797, 7795, 322 https://tests.stockfishchess.org/tests/view/69105b3dec1d00d2c195c569 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 27876 W: 7417 L: 7110 D: 13349 Ptnml(0-2): 23, 3033, 7530, 3318, 34 https://tests.stockfishchess.org/tests/view/6910d817ec1d00d2c195c66e Passed VVLTC (Hash accidentally set to 1/2 normal value for both sides): LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 12458 W: 3353 L: 3102 D: 6003 Ptnml(0-2): 0, 1106, 3767, 1355, 1 https://tests.stockfishchess.org/tests/view/69115a26ec1d00d2c195c7cd This version has also passed non-regression LTC against the originally passed version: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 51144 W: 13086 L: 12903 D: 25155 Ptnml(0-2): 22, 5167, 15018, 5336, 29 https://tests.stockfishchess.org/tests/view/69138a317ca87818523314bf LTC elo estimate on ARM: 1 patch : 13.9 1.9 38296.5 73728 52 2 master : 0.0 ---- 35431.5 73728 48 closes https://github.com/official-stockfish/Stockfish/pull/6411 bench: 2626086 Co-authored-by: Shawn Xu Co-authored-by: Timothy Herchen Co-authored-by: Viren6 <94880762+Viren6@users.noreply.github.com> Co-authored-by: Yoshie2000 Co-authored-by: Joost Vandevondele Co-authored-by: rn5f107s2 Co-authored-by: cj5716 <125858804+cj5716@users.noreply.github.com> Co-authored-by: AliceRoselia <63040919+AliceRoselia@users.noreply.github.com> Co-authored-by: Linmiao Xu Co-authored-by: Disservin --- AUTHORS | 1 + src/Makefile | 13 +- src/benchmark.cpp | 6 +- src/bitboard.cpp | 3 + src/bitboard.h | 15 + src/evaluate.h | 2 +- src/main.cpp | 3 +- src/misc.h | 11 +- src/nnue/features/full_threats.cpp | 311 +++++++++++++++ src/nnue/features/full_threats.h | 115 ++++++ src/nnue/features/half_ka_v2_hm.cpp | 48 +-- src/nnue/features/half_ka_v2_hm.h | 5 +- src/nnue/network.cpp | 2 +- src/nnue/nnue_accumulator.cpp | 591 ++++++++++++++++++++++------ src/nnue/nnue_accumulator.h | 51 ++- src/nnue/nnue_architecture.h | 6 +- src/nnue/nnue_common.h | 23 +- src/nnue/nnue_feature_transformer.h | 215 ++++++++-- src/nnue/simd.h | 32 +- src/position.cpp | 157 +++++++- src/position.h | 89 +++-- src/search.cpp | 17 +- src/types.h | 43 ++ 23 files changed, 1474 insertions(+), 285 deletions(-) create mode 100644 src/nnue/features/full_threats.cpp create mode 100644 src/nnue/features/full_threats.h diff --git a/AUTHORS b/AUTHORS index 31ff51def8a..db552a3a576 100644 --- a/AUTHORS +++ b/AUTHORS @@ -200,6 +200,7 @@ Panthee Pascal Romaret Pasquale Pigazzini (ppigazzini) Patrick Jansen (mibere) +Patrick Leonhardt (Yoshie2000) Peter Schneider (pschneider1968) Peter Zsifkovits (CoffeeOne) PikaCat diff --git a/src/Makefile b/src/Makefile index b30e16c074c..cc85ac78ecd 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,15 +55,16 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \ + nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/network.cpp \ + nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \ engine.cpp score.cpp memory.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ - nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ - nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h \ - nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ - nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h position.h \ - search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ + nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \ + nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \ + nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h \ + position.h search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index a9f70f10d1a..38cafaec705 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -65,6 +65,10 @@ const std::vector Defaults = { "3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40", "4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1", + // Positions with high numbers of changed threats + "k7/2n1n3/1nbNbn2/2NbRBn1/1nbRQR2/2NBRBN1/3N1N2/7K w - - 0 1", + "K7/8/8/BNQNQNB1/N5N1/R1Q1q2r/n5n1/bnqnqnbk w - - 0 1", + // 5-man positions "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate "8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate @@ -509,4 +513,4 @@ BenchmarkSetup setup_benchmark(std::istream& is) { return setup; } -} // namespace Stockfish \ No newline at end of file +} // namespace Stockfish diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 9b1d674c097..6c97d786333 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -31,6 +31,7 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; alignas(64) Magic Magics[SQUARE_NB][2]; @@ -105,6 +106,8 @@ void Bitboards::init() { LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + RayPassBB[s1][s2] = + attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2); } BetweenBB[s1][s2] |= s2; } diff --git a/src/bitboard.h b/src/bitboard.h index 27ef89c0e03..1cd717b8f17 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -61,6 +61,7 @@ extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; @@ -252,6 +253,20 @@ inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { } } +inline Bitboard attacks_bb(Piece pc, Square s) { + if (type_of(pc) == PAWN) + return PseudoAttacks[color_of(pc)][s]; + + return PseudoAttacks[type_of(pc)][s]; +} + + +inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { + if (type_of(pc) == PAWN) + return PseudoAttacks[color_of(pc)][s]; + + return attacks_bb(type_of(pc), s, occupied); +} // Counts the number of non-zero bits in a bitboard. inline int popcount(Bitboard b) { diff --git a/src/evaluate.h b/src/evaluate.h index 2a6c9afa90e..c8dc64ace9c 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-1c0000000000.nnue" +#define EvalFileDefaultNameBig "nn-49c1193b131c.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { diff --git a/src/main.cpp b/src/main.cpp index 9988680aa61..6ab3507f3be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,9 @@ #include "bitboard.h" #include "misc.h" +#include "nnue/features/full_threats.h" #include "position.h" #include "tune.h" -#include "types.h" #include "uci.h" using namespace Stockfish; @@ -33,6 +33,7 @@ int main(int argc, char* argv[]) { Bitboards::init(); Position::init(); + Eval::NNUE::Features::init_threat_offsets(); auto uci = std::make_unique(argc, argv); diff --git a/src/misc.h b/src/misc.h index b1707e74217..fce6f17dfac 100644 --- a/src/misc.h +++ b/src/misc.h @@ -134,10 +134,13 @@ class ValueList { public: std::size_t size() const { return size_; } - void push_back(const T& value) { values_[size_++] = value; } - const T* begin() const { return values_; } - const T* end() const { return values_ + size_; } - const T& operator[](int index) const { return values_[index]; } + void push_back(const T& value) { + assert(size_ < MaxSize); + values_[size_++] = value; + } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } private: T values_[MaxSize]; diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp new file mode 100644 index 00000000000..6fc6b00ec1e --- /dev/null +++ b/src/nnue/features/full_threats.cpp @@ -0,0 +1,311 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features FullThreats of NNUE evaluation function + +#include "full_threats.h" + +#include +#include + +#include "../../bitboard.h" +#include "../../misc.h" +#include "../../position.h" +#include "../../types.h" +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Features { + +// Lookup array for indexing threats +IndexType offsets[PIECE_NB][SQUARE_NB + 2]; + +// Information on a particular pair of pieces and whether they should be excluded +struct PiecePairData { + // Layout: bits 8..31 are the index contribution of this piece pair, bits 0 and 1 are exclusion info + uint32_t data; + PiecePairData() {} + PiecePairData(bool excluded_pair, bool semi_excluded_pair, IndexType feature_index_base) { + data = + excluded_pair << 1 | (semi_excluded_pair && !excluded_pair) | feature_index_base << 8; + } + // lsb: excluded if from < to; 2nd lsb: always excluded + uint8_t excluded_pair_info() const { return (uint8_t) data; } + IndexType feature_index_base() const { return data >> 8; } +}; + +constexpr std::array AllPieces = { + W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, +}; + +// The final index is calculated from summing data found in these two LUTs, as well +// as offsets[attacker][from] +PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] +uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] + +static void init_index_luts() { + for (Piece attacker : AllPieces) + { + for (Piece attacked : AllPieces) + { + bool enemy = (attacker ^ attacked) == 8; + PieceType attackerType = type_of(attacker); + PieceType attackedType = type_of(attacked); + + int map = FullThreats::map[attackerType - 1][attackedType - 1]; + bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN); + IndexType feature = offsets[attacker][65] + + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) + * offsets[attacker][64]; + + bool excluded = map < 0; + index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); + } + } + + for (Piece attacker : AllPieces) + { + for (int from = 0; from < SQUARE_NB; ++from) + { + for (int to = 0; to < SQUARE_NB; ++to) + { + Bitboard attacks = attacks_bb(attacker, Square(from)); + index_lut2[attacker][from][to] = popcount((square_bb(Square(to)) - 1) & attacks); + } + } + } +} + +void init_threat_offsets() { + int cumulativeOffset = 0; + for (Piece piece : AllPieces) + { + int pieceIdx = piece; + int cumulativePieceOffset = 0; + + for (Square from = SQ_A1; from <= SQ_H8; ++from) + { + offsets[pieceIdx][from] = cumulativePieceOffset; + + if (type_of(piece) != PAWN) + { + Bitboard attacks = attacks_bb(piece, from, 0ULL); + cumulativePieceOffset += popcount(attacks); + } + + else if (from >= SQ_A2 && from <= SQ_H7) + { + Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb(square_bb(from)) + : pawn_attacks_bb(square_bb(from)); + cumulativePieceOffset += popcount(attacks); + } + } + + offsets[pieceIdx][64] = cumulativePieceOffset; + offsets[pieceIdx][65] = cumulativeOffset; + + cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; + } + + init_index_luts(); +} + +// Index of a feature for a given king position and another piece on some square +template +IndexType +FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { + from = (Square) (int(from) ^ OrientTBL[Perspective][ksq]); + to = (Square) (int(to) ^ OrientTBL[Perspective][ksq]); + + if (Perspective == BLACK) + { + attacker = ~attacker; + attacked = ~attacked; + } + + auto piecePairData = index_lut1[attacker][attacked]; + + // Some threats imply the existence of the corresponding ones in the opposite + // direction. We filter them here to ensure only one such threat is active. + + // In the below addition, the 2nd lsb gets set iff either the pair is always excluded, + // or the pair is semi-excluded and from < to. By using an unsigned compare, the following + // sequence can use an add-with-carry instruction. + bool less_than = static_cast(from) < static_cast(to); + if ((piecePairData.excluded_pair_info() + less_than) & 2) + return Dimensions; + + IndexType index = + piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; + + sf_assume(index != Dimensions); + return index; +} + +// Get a list of indices for active features in ascending order +template +void FullThreats::append_active_indices(const Position& pos, IndexList& active) { + static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; + + Square ksq = pos.square(Perspective); + Bitboard occupied = pos.pieces(); + + for (Color color : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + Color c = order[Perspective][color]; + Piece attacker = make_piece(c, pt); + Bitboard bb = pos.pieces(c, pt); + + if (pt == PAWN) + { + auto right = (c == WHITE) ? NORTH_EAST : SOUTH_WEST; + auto left = (c == WHITE) ? NORTH_WEST : SOUTH_EAST; + auto attacks_left = + ((c == WHITE) ? shift(bb) : shift(bb)) & occupied; + auto attacks_right = + ((c == WHITE) ? shift(bb) : shift(bb)) & occupied; + + while (attacks_left) + { + Square to = pop_lsb(attacks_left); + Square from = to - right; + Piece attacked = pos.piece_on(to); + IndexType index = make_index(attacker, from, to, attacked, ksq); + + if (index < Dimensions) + active.push_back(index); + } + + while (attacks_right) + { + Square to = pop_lsb(attacks_right); + Square from = to - left; + Piece attacked = pos.piece_on(to); + IndexType index = make_index(attacker, from, to, attacked, ksq); + + if (index < Dimensions) + active.push_back(index); + } + } + else + { + while (bb) + { + Square from = pop_lsb(bb); + Bitboard attacks = (attacks_bb(pt, from, occupied)) & occupied; + + while (attacks) + { + Square to = pop_lsb(attacks); + Piece attacked = pos.piece_on(to); + IndexType index = + make_index(attacker, from, to, attacked, ksq); + + if (index < Dimensions) + active.push_back(index); + } + } + } + } + } +} + +// Explicit template instantiations +template void FullThreats::append_active_indices(const Position& pos, IndexList& active); +template void FullThreats::append_active_indices(const Position& pos, IndexList& active); +template IndexType +FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); +template IndexType +FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); + +// Get a list of indices for recently changed features +template +void FullThreats::append_changed_indices(Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added, + FusedUpdateData* fusedData, + bool first) { + for (const auto dirty : diff.list) + { + auto attacker = dirty.pc(); + auto attacked = dirty.threatened_pc(); + auto from = dirty.pc_sq(); + auto to = dirty.threatened_sq(); + auto add = dirty.add(); + + if (fusedData) + { + if (from == fusedData->dp2removed) + { + if (add) + { + if (first) + { + fusedData->dp2removedOriginBoard |= square_bb(to); + continue; + } + } + else if (fusedData->dp2removedOriginBoard & square_bb(to)) + continue; + } + + if (to != SQ_NONE && to == fusedData->dp2removed) + { + if (add) + { + if (first) + { + fusedData->dp2removedTargetBoard |= square_bb(from); + continue; + } + } + else if (fusedData->dp2removedTargetBoard & square_bb(from)) + continue; + } + } + + IndexType index = make_index(attacker, from, to, attacked, ksq); + + if (index != Dimensions) + (add ? added : removed).push_back(index); + } +} + +// Explicit template instantiations +template void FullThreats::append_changed_indices(Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added, + FusedUpdateData* fd, + bool first); +template void FullThreats::append_changed_indices(Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added, + FusedUpdateData* fd, + bool first); + +bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { + return perspective == diff.us + && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; +} + +} // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h new file mode 100644 index 00000000000..458b04dd189 --- /dev/null +++ b/src/nnue/features/full_threats.h @@ -0,0 +1,115 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features Simplified_Threats of NNUE evaluation function + +#ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED +#define NNUE_FEATURES_FULL_THREATS_INCLUDED + +#include + +#include "../../misc.h" +#include "../../types.h" +#include "../nnue_common.h" + +namespace Stockfish { +class Position; +} + +namespace Stockfish::Eval::NNUE::Features { + +static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0, + 0, 6, 12, 10, 10, 12, 8, 0}; +extern IndexType offsets[PIECE_NB][SQUARE_NB + 2]; +void init_threat_offsets(); + +class FullThreats { + public: + // Feature name + static constexpr const char* Name = "Full_Threats(Friend)"; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t HashValue = 0x8f234cb8u; + + // Number of feature dimensions + static constexpr IndexType Dimensions = 79856; + + // clang-format off + // Orient a square according to perspective (rotates by 180 for black) + static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { + { SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1 }, + { SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, + SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, + SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, + SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, + SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, + SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, + SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, + SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8 } + }; + + static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = { + {0, 1, -1, 2, -1, -1}, + {0, 1, 2, 3, 4, 5}, + {0, 1, 2, 3, -1, 4}, + {0, 1, 2, 3, -1, 4}, + {0, 1, 2, 3, 4, 5}, + {0, 1, 2, 3, -1, -1} + }; + // clang-format on + + struct FusedUpdateData { + Bitboard dp2removedOriginBoard = 0; + Bitboard dp2removedTargetBoard = 0; + + Square dp2removed; + }; + + // Maximum number of simultaneously active features. + static constexpr IndexType MaxActiveDimensions = 128; + using IndexList = ValueList; + using DiffType = DirtyThreats; + + template + static IndexType make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); + + // Get a list of indices for active features + template + static void append_active_indices(const Position& pos, IndexList& active); + + // Get a list of indices for recently changed features + template + static void append_changed_indices(Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added, + FusedUpdateData* fd = nullptr, + bool first = false); + + // Returns whether the change stored in this DirtyPiece means + // that a full accumulator refresh is required. + static bool requires_refresh(const DiffType& diff, Color perspective); +}; + +} // namespace Stockfish::Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5bcfb0849d0..f652ba0b8bb 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -55,33 +55,33 @@ template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq // Get a list of indices for recently changed features template -void HalfKAv2_hm::append_changed_indices(Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added) { - removed.push_back(make_index(dp.from, dp.pc, ksq)); - if (dp.to != SQ_NONE) - added.push_back(make_index(dp.to, dp.pc, ksq)); - - if (dp.remove_sq != SQ_NONE) - removed.push_back(make_index(dp.remove_sq, dp.remove_pc, ksq)); - - if (dp.add_sq != SQ_NONE) - added.push_back(make_index(dp.add_sq, dp.add_pc, ksq)); +void HalfKAv2_hm::append_changed_indices(Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added) { + removed.push_back(make_index(diff.from, diff.pc, ksq)); + if (diff.to != SQ_NONE) + added.push_back(make_index(diff.to, diff.pc, ksq)); + + if (diff.remove_sq != SQ_NONE) + removed.push_back(make_index(diff.remove_sq, diff.remove_pc, ksq)); + + if (diff.add_sq != SQ_NONE) + added.push_back(make_index(diff.add_sq, diff.add_pc, ksq)); } // Explicit template instantiations -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added); -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DirtyPiece& dp, - IndexList& removed, - IndexList& added); - -bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) { - return dirtyPiece.pc == make_piece(perspective, KING); +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DiffType& dp, + IndexList& removed, + IndexList& added); +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DiffType& dp, + IndexList& removed, + IndexList& added); + +bool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) { + return diff.pc == make_piece(perspective, KING); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index b72bbbce4e3..e695b273a19 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -104,6 +104,7 @@ class HalfKAv2_hm { // Maximum number of simultaneously active features. static constexpr IndexType MaxActiveDimensions = 32; using IndexList = ValueList; + using DiffType = DirtyPiece; // Index of a feature for a given king position and another piece on some square template @@ -116,11 +117,11 @@ class HalfKAv2_hm { // Get a list of indices for recently changed features template static void - append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); + append_changed_indices(Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. - static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective); + static bool requires_refresh(const DiffType& diff, Color perspective); }; } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 48aeafdf6cd..b91763f061c 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -222,7 +222,7 @@ void Network::verify(std::string { size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks; f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024)) - + "MiB, (" + std::to_string(featureTransformer.InputDimensions) + ", " + + "MiB, (" + std::to_string(featureTransformer.TotalInputDimensions) + ", " + std::to_string(network[0].TransformedFeatureDimensions) + ", " + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + ", 1))"); diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index b1743329fb8..0444b6e4054 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -27,7 +27,9 @@ #include "../misc.h" #include "../position.h" #include "../types.h" +#include "features/half_ka_v2_hm.h" #include "nnue_architecture.h" +#include "nnue_common.h" #include "nnue_feature_transformer.h" // IWYU pragma: keep #include "simd.h" @@ -40,43 +42,90 @@ namespace { template void double_inc_update(const FeatureTransformer& featureTransformer, const Square ksq, - AccumulatorState& middle_state, - AccumulatorState& target_state, - const AccumulatorState& computed); + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed); -template +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed, + const DirtyPiece& dp2); + +template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, const Square ksq, - AccumulatorState& target_state, - const AccumulatorState& computed); + AccumulatorState& target_state, + const AccumulatorState& computed); template void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, const Position& pos, - AccumulatorState& accumulatorState, + AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); +template +void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState); +} + +template +const AccumulatorState& AccumulatorStack::latest() const noexcept { + return accumulators()[size - 1]; } -void AccumulatorState::reset(const DirtyPiece& dp) noexcept { - dirtyPiece = dp; - accumulatorBig.computed.fill(false); - accumulatorSmall.computed.fill(false); +// Explicit template instantiations +template const AccumulatorState& AccumulatorStack::latest() const noexcept; +template const AccumulatorState& AccumulatorStack::latest() const noexcept; + +template +AccumulatorState& AccumulatorStack::mut_latest() noexcept { + return mut_accumulators()[size - 1]; +} + +template +const std::array, AccumulatorStack::MaxSize>& +AccumulatorStack::accumulators() const noexcept { + static_assert(std::is_same_v || std::is_same_v, + "Invalid Feature Set Type"); + + if constexpr (std::is_same_v) + return psq_accumulators; + + if constexpr (std::is_same_v) + return threat_accumulators; } -const AccumulatorState& AccumulatorStack::latest() const noexcept { return accumulators[size - 1]; } +template +std::array, AccumulatorStack::MaxSize>& +AccumulatorStack::mut_accumulators() noexcept { + static_assert(std::is_same_v || std::is_same_v, + "Invalid Feature Set Type"); -AccumulatorState& AccumulatorStack::mut_latest() noexcept { return accumulators[size - 1]; } + if constexpr (std::is_same_v) + return psq_accumulators; + + if constexpr (std::is_same_v) + return threat_accumulators; +} void AccumulatorStack::reset() noexcept { - accumulators[0].reset({}); + psq_accumulators[0].reset({}); + threat_accumulators[0].reset({}); size = 1; } -void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { - assert(size < accumulators.size()); - accumulators[size].reset(dirtyPiece); +void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept { + assert(size < MaxSize); + psq_accumulators[size].reset(dirtyBoardData.dp); + threat_accumulators[size].reset(dirtyBoardData.dts); size++; } @@ -89,53 +138,71 @@ template void AccumulatorStack::evaluate(const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept { + constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig); + + evaluate_side(pos, featureTransformer, cache); - evaluate_side(pos, featureTransformer, cache); - evaluate_side(pos, featureTransformer, cache); + if (UseThreats) + evaluate_side(pos, featureTransformer, cache); + + evaluate_side(pos, featureTransformer, cache); + + if (UseThreats) + evaluate_side(pos, featureTransformer, cache); } -template +template void AccumulatorStack::evaluate_side(const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept { - const auto last_usable_accum = find_last_usable_accumulator(); + const auto last_usable_accum = + find_last_usable_accumulator(); - if ((accumulators[last_usable_accum].template acc()).computed[Perspective]) - forward_update_incremental(pos, featureTransformer, last_usable_accum); + if ((accumulators()[last_usable_accum].template acc()) + .computed[Perspective]) + forward_update_incremental(pos, featureTransformer, + last_usable_accum); else { - update_accumulator_refresh_cache(featureTransformer, pos, mut_latest(), cache); - backward_update_incremental(pos, featureTransformer, last_usable_accum); + if constexpr (std::is_same_v) + update_accumulator_refresh_cache(featureTransformer, pos, + mut_latest(), cache); + else + update_threats_accumulator_full(featureTransformer, pos, + mut_latest()); + + backward_update_incremental(pos, featureTransformer, + last_usable_accum); } } // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator // state just before a change that requires full refresh. -template +template std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) { - if ((accumulators[curr_idx].template acc()).computed[Perspective]) + if ((accumulators()[curr_idx].template acc()).computed[Perspective]) return curr_idx; - if (FeatureSet::requires_refresh(accumulators[curr_idx].dirtyPiece, Perspective)) + if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, Perspective)) return curr_idx; } return 0; } -template +template void AccumulatorStack::forward_update_incremental( const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { - assert(begin < accumulators.size()); - assert((accumulators[begin].acc()).computed[Perspective]); + assert(begin < accumulators().size()); + assert((accumulators()[begin].template acc()).computed[Perspective]); const Square ksq = pos.square(Perspective); @@ -143,45 +210,65 @@ void AccumulatorStack::forward_update_incremental( { if (next + 1 < size) { - DirtyPiece& dp1 = accumulators[next].dirtyPiece; - DirtyPiece& dp2 = accumulators[next + 1].dirtyPiece; + DirtyPiece& dp1 = mut_accumulators()[next].diff; + DirtyPiece& dp2 = mut_accumulators()[next + 1].diff; + + auto& accumulators = mut_accumulators(); - if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq) + if constexpr (std::is_same_v) { - const Square captureSq = dp1.to; - dp1.to = dp2.remove_sq = SQ_NONE; - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1]); - dp1.to = dp2.remove_sq = captureSq; - - next++; - continue; + if (dp2.remove_sq != SQ_NONE + && (accumulators[next].diff.threateningSqs & square_bb(dp2.remove_sq))) + { + double_inc_update(featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1], + dp2); + next++; + continue; + } + } + + if constexpr (std::is_same_v) + { + if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq) + { + const Square captureSq = dp1.to; + dp1.to = dp2.remove_sq = SQ_NONE; + double_inc_update(featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); + dp1.to = dp2.remove_sq = captureSq; + next++; + continue; + } } } - update_accumulator_incremental( - featureTransformer, ksq, accumulators[next], accumulators[next - 1]); + + update_accumulator_incremental(featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next - 1]); } - assert((latest().acc()).computed[Perspective]); + assert((latest().acc()).computed[Perspective]); } -template +template void AccumulatorStack::backward_update_incremental( const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept { - assert(end < accumulators.size()); + assert(end < accumulators().size()); assert(end < size); - assert((latest().acc()).computed[Perspective]); + assert((latest().template acc()).computed[Perspective]); const Square ksq = pos.square(Perspective); for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--) - update_accumulator_incremental( - featureTransformer, ksq, accumulators[next], accumulators[next + 1]); + update_accumulator_incremental(featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next + 1]); - assert((accumulators[end].acc()).computed[Perspective]); + assert((accumulators()[end].template acc()).computed[Perspective]); } // Explicit template instantiations @@ -214,15 +301,15 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const.. vecIn[i], reinterpret_cast(rows)[i]...); } -template +template struct AccumulatorUpdateContext { const FeatureTransformer& featureTransformer; - const AccumulatorState& from; - AccumulatorState& to; + const AccumulatorState& from; + AccumulatorState& to; AccumulatorUpdateContext(const FeatureTransformer& ft, - const AccumulatorState& accF, - AccumulatorState& accT) noexcept : + const AccumulatorState& accF, + AccumulatorState& accT) noexcept : featureTransformer{ft}, from{accF}, to{accT} {} @@ -240,40 +327,169 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.acc()).accumulation[Perspective], - (to.acc()).accumulation[Perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[Perspective], + (to.template acc()).accumulation[Perspective], to_weight_vector(indices)...); fused_row_reduce( - (from.acc()).psqtAccumulation[Perspective], - (to.acc()).psqtAccumulation[Perspective], to_psqt_weight_vector(indices)...); + (from.template acc()).psqtAccumulation[Perspective], + (to.template acc()).psqtAccumulation[Perspective], + to_psqt_weight_vector(indices)...); + } + + void apply(typename FeatureSet::IndexList added, typename FeatureSet::IndexList removed) { + const auto fromAcc = from.template acc().accumulation[Perspective]; + const auto toAcc = to.template acc().accumulation[Perspective]; + + const auto fromPsqtAcc = from.template acc().psqtAccumulation[Perspective]; + const auto toPsqtAcc = to.template acc().psqtAccumulation[Perspective]; + +#ifdef VECTOR + using Tiling = SIMDTiling; + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; + + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) + { + auto* fromTile = reinterpret_cast(&fromAcc[j * Tiling::TileHeight]); + auto* toTile = reinterpret_cast(&toAcc[j * Tiling::TileHeight]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = fromTile[k]; + + for (IndexType i = 0; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = + reinterpret_cast(&featureTransformer.threatWeights[offset]); + + #ifdef USE_NEON + for (IndexType k = 0; k < Tiling::NumRegs; k += 2) + { + acc[k] = vec_sub_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2]))); + acc[k + 1] = vec_sub_16(acc[k + 1], vmovl_high_s8(column[k / 2])); + } + #else + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], vec_convert_8_16(column[k])); + #endif + } + + for (IndexType i = 0; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = + reinterpret_cast(&featureTransformer.threatWeights[offset]); + + #ifdef USE_NEON + for (IndexType k = 0; k < Tiling::NumRegs; k += 2) + { + acc[k] = vec_add_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2]))); + acc[k + 1] = vec_add_16(acc[k + 1], vmovl_high_s8(column[k / 2])); + } + #else + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_convert_8_16(column[k])); + #endif + } + + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&toTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) + { + auto* fromTilePsqt = + reinterpret_cast(&fromPsqtAcc[j * Tiling::PsqtTileHeight]); + auto* toTilePsqt = + reinterpret_cast(&toPsqtAcc[j * Tiling::PsqtTileHeight]); + + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = fromTilePsqt[k]; + + for (IndexType i = 0; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast( + &featureTransformer.threatPsqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + for (IndexType i = 0; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast( + &featureTransformer.threatPsqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&toTilePsqt[k], psqt[k]); + } + +#else + + std::copy_n(fromAcc, Dimensions, toAcc); + std::copy_n(fromPsqtAcc, PSQTBuckets, toPsqtAcc); + + for (const auto index : removed) + { + const IndexType offset = Dimensions * index; + + for (IndexType j = 0; j < Dimensions; ++j) + toAcc[j] -= featureTransformer.threatWeights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + toPsqtAcc[k] -= featureTransformer.threatPsqtWeights[index * PSQTBuckets + k]; + } + + for (const auto index : added) + { + const IndexType offset = Dimensions * index; + + for (IndexType j = 0; j < Dimensions; ++j) + toAcc[j] += featureTransformer.threatWeights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + toPsqtAcc[k] += featureTransformer.threatPsqtWeights[index * PSQTBuckets + k]; + } + +#endif } }; -template +template auto make_accumulator_update_context(const FeatureTransformer& featureTransformer, - const AccumulatorState& accumulatorFrom, - AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{featureTransformer, accumulatorFrom, - accumulatorTo}; + const AccumulatorState& accumulatorFrom, + AccumulatorState& accumulatorTo) noexcept { + return AccumulatorUpdateContext{ + featureTransformer, accumulatorFrom, accumulatorTo}; } template void double_inc_update(const FeatureTransformer& featureTransformer, const Square ksq, - AccumulatorState& middle_state, - AccumulatorState& target_state, - const AccumulatorState& computed) { + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed) { assert(computed.acc().computed[Perspective]); assert(!middle_state.acc().computed[Perspective]); assert(!target_state.acc().computed[Perspective]); - FeatureSet::IndexList removed, added; - FeatureSet::append_changed_indices(ksq, middle_state.dirtyPiece, removed, added); + PSQFeatureSet::IndexList removed, added; + PSQFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added); // you can't capture a piece that was just involved in castling since the rook ends up // in a square that the king passed assert(added.size() < 2); - FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, added); + PSQFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added); assert(added.size() == 1); assert(removed.size() == 2 || removed.size() == 3); @@ -300,15 +516,48 @@ void double_inc_update(const FeatureTransformer& f target_state.acc().computed[Perspective] = true; } -template +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed, + const DirtyPiece& dp2) { + + assert(computed.acc().computed[Perspective]); + assert(!middle_state.acc().computed[Perspective]); + assert(!target_state.acc().computed[Perspective]); + + ThreatFeatureSet::FusedUpdateData fusedData; + + fusedData.dp2removed = dp2.remove_sq; + + ThreatFeatureSet::IndexList removed, added; + ThreatFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added, + &fusedData, true); + ThreatFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added, + &fusedData, false); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + updateContext.apply(added, removed); + + target_state.acc().computed[Perspective] = true; +} + +template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, const Square ksq, - AccumulatorState& target_state, - const AccumulatorState& computed) { + AccumulatorState& target_state, + const AccumulatorState& computed) { - assert((computed.acc()).computed[Perspective]); - assert(!(target_state.acc()).computed[Perspective]); + assert((computed.template acc()).computed[Perspective]); + assert(!(target_state.template acc()).computed[Perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -316,50 +565,56 @@ void update_accumulator_incremental( // 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; + typename FeatureSet::IndexList removed, added; if constexpr (Forward) - FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, - added); + FeatureSet::template append_changed_indices(ksq, target_state.diff, removed, + added); else - FeatureSet::append_changed_indices(ksq, computed.dirtyPiece, added, removed); - - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); - assert((Forward && added.size() <= removed.size()) - || (!Forward && added.size() >= removed.size())); - - // Workaround compiler warning for uninitialized variables, replicated on - // profile builds on windows with gcc 14.2.0. - // TODO remove once unneeded - sf_assume(added.size() == 1 || added.size() == 2); - sf_assume(removed.size() == 1 || removed.size() == 2); + FeatureSet::template append_changed_indices(ksq, computed.diff, added, + removed); auto updateContext = make_accumulator_update_context(featureTransformer, computed, target_state); - if ((Forward && removed.size() == 1) || (!Forward && added.size() == 1)) - { - assert(added.size() == 1 && removed.size() == 1); - updateContext.template apply(added[0], removed[0]); - } - else if (Forward && added.size() == 1) - { - assert(removed.size() == 2); - updateContext.template apply(added[0], removed[0], removed[1]); - } - else if (!Forward && removed.size() == 1) - { - assert(added.size() == 2); - updateContext.template apply(added[0], added[1], removed[0]); - } + if constexpr (std::is_same_v) + updateContext.apply(added, removed); else { - assert(added.size() == 2 && removed.size() == 2); - updateContext.template apply(added[0], added[1], removed[0], - removed[1]); + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + assert((Forward && added.size() <= removed.size()) + || (!Forward && added.size() >= removed.size())); + + // Workaround compiler warning for uninitialized variables, replicated + // on profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1 || added.size() == 2); + sf_assume(removed.size() == 1 || removed.size() == 2); + + if ((Forward && removed.size() == 1) || (!Forward && added.size() == 1)) + { + assert(added.size() == 1 && removed.size() == 1); + updateContext.template apply(added[0], removed[0]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + updateContext.template apply(added[0], removed[0], removed[1]); + } + else if (!Forward && removed.size() == 1) + { + assert(added.size() == 2); + updateContext.template apply(added[0], added[1], removed[0]); + } + else + { + assert(added.size() == 2 && removed.size() == 2); + updateContext.template apply(added[0], added[1], removed[0], + removed[1]); + } } - (target_state.acc()).computed[Perspective] = true; + (target_state.template acc()).computed[Perspective] = true; } Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) { @@ -388,32 +643,32 @@ Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_ template void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, const Position& pos, - AccumulatorState& accumulatorState, + AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache) { using Tiling [[maybe_unused]] = SIMDTiling; - const Square ksq = pos.square(Perspective); - auto& entry = cache[ksq][Perspective]; - FeatureSet::IndexList removed, added; + const Square ksq = pos.square(Perspective); + auto& entry = cache[ksq][Perspective]; + PSQFeatureSet::IndexList removed, added; - const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array()); + const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array().data()); Bitboard removed_bb = changed_bb & entry.pieceBB; Bitboard added_bb = changed_bb & pos.pieces(); while (removed_bb) { Square sq = pop_lsb(removed_bb); - removed.push_back(FeatureSet::make_index(sq, entry.pieces[sq], ksq)); + removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); } while (added_bb) { Square sq = pop_lsb(added_bb); - added.push_back(FeatureSet::make_index(sq, pos.piece_on(sq), ksq)); + added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); } entry.pieceBB = pos.pieces(); - std::copy_n(pos.piece_array(), SQUARE_NB, entry.pieces); + std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); auto& accumulator = accumulatorState.acc(); accumulator.computed[Perspective] = true; @@ -530,14 +785,110 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation.data(), sizeof(BiasType) * Dimensions); - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation.data(), sizeof(int32_t) * PSQTBuckets); #endif } +template +void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState) { + using Tiling [[maybe_unused]] = SIMDTiling; + + ThreatFeatureSet::IndexList active; + ThreatFeatureSet::append_active_indices(pos, active); + + auto& accumulator = accumulatorState.acc(); + accumulator.computed[Perspective] = true; + +#ifdef VECTOR + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; + + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) + { + auto* accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_zero(); + + IndexType i = 0; + + for (; i < active.size(); ++i) + { + IndexType index = active[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = + reinterpret_cast(&featureTransformer.threatWeights[offset]); + + #ifdef USE_NEON + for (IndexType k = 0; k < Tiling::NumRegs; k += 2) + { + acc[k] = vec_add_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2]))); + acc[k + 1] = vec_add_16(acc[k + 1], vmovl_high_s8(column[k / 2])); + } + #else + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_convert_8_16(column[k])); + #endif + } + + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) + { + auto* accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); + + for (IndexType i = 0; i < active.size(); ++i) + { + IndexType index = active[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.threatPsqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + + for (IndexType j = 0; j < Dimensions; ++j) + accumulator.accumulation[Perspective][j] = 0; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] = 0; + + for (const auto index : active) + { + const IndexType offset = Dimensions * index; + + for (IndexType j = 0; j < Dimensions; ++j) + accumulator.accumulation[Perspective][j] += + featureTransformer.threatWeights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[Perspective][k] += + featureTransformer.threatPsqtWeights[index * PSQTBuckets + k]; + } + +#endif +} + } } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 95cf74fa3d0..4c907c7e3ae 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -68,16 +68,16 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Cache { struct alignas(CacheLineSize) Entry { - BiasType accumulation[Size]; - PSQTWeightType psqtAccumulation[PSQTBuckets]; - Piece pieces[SQUARE_NB]; - Bitboard pieceBB; + std::array accumulation; + std::array psqtAccumulation; + Piece pieces[SQUARE_NB]; + Bitboard pieceBB; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top - void clear(const BiasType* biases) { + void clear(const std::array& biases) { - std::memcpy(accumulation, biases, sizeof(accumulation)); + accumulation = biases; std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } @@ -106,10 +106,11 @@ struct AccumulatorCaches { }; +template struct AccumulatorState { Accumulator accumulatorBig; Accumulator accumulatorSmall; - DirtyPiece dirtyPiece; + typename FeatureSet::DiffType diff; template auto& acc() noexcept { @@ -135,16 +136,22 @@ struct AccumulatorState { return accumulatorSmall; } - void reset(const DirtyPiece& dp) noexcept; + void reset(const typename FeatureSet::DiffType& dp) noexcept { + diff = dp; + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); + } }; - class AccumulatorStack { public: - [[nodiscard]] const AccumulatorState& latest() const noexcept; + static constexpr std::size_t MaxSize = MAX_PLY + 1; + + template + [[nodiscard]] const AccumulatorState& latest() const noexcept; void reset() noexcept; - void push(const DirtyPiece& dirtyPiece) noexcept; + void push(const DirtyBoardData& dirtyBoardData) noexcept; void pop() noexcept; template @@ -153,28 +160,36 @@ class AccumulatorStack { AccumulatorCaches::Cache& cache) noexcept; private: - [[nodiscard]] AccumulatorState& mut_latest() noexcept; + template + [[nodiscard]] AccumulatorState& mut_latest() noexcept; + + template + [[nodiscard]] const std::array, MaxSize>& accumulators() const noexcept; + + template + [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; - template + template void evaluate_side(const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; - template + template [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; - template + template void forward_update_incremental(const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept; - template + template void backward_update_incremental(const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; - std::array accumulators; - std::size_t size = 1; + std::array, MaxSize> psq_accumulators; + std::array, MaxSize> threat_accumulators; + std::size_t size = 1; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 5965f24aa38..5093abdd7a9 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -26,6 +26,7 @@ #include #include "features/half_ka_v2_hm.h" +#include "features/full_threats.h" #include "layers/affine_transform.h" #include "layers/affine_transform_sparse_input.h" #include "layers/clipped_relu.h" @@ -35,10 +36,11 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function -using FeatureSet = Features::HalfKAv2_hm; +using ThreatFeatureSet = Features::FullThreats; +using PSQFeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensionsBig = 3072; +constexpr IndexType TransformedFeatureDimensionsBig = 1024; constexpr int L2Big = 15; constexpr int L3Big = 32; diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 3617a85eb28..8a877ae2b4b 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -48,10 +48,11 @@ namespace Stockfish::Eval::NNUE { -using BiasType = std::int16_t; -using WeightType = std::int16_t; -using PSQTWeightType = std::int32_t; -using IndexType = std::uint32_t; +using BiasType = std::int16_t; +using ThreatWeightType = std::int8_t; +using WeightType = std::int16_t; +using PSQTWeightType = std::int32_t; +using IndexType = std::uint32_t; // Version of the evaluation file constexpr std::uint32_t Version = 0x7AF32F20u; @@ -172,8 +173,8 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std // Read N signed integers from the stream s, putting them in the array out. // The stream is assumed to be compressed using the signed LEB128 format. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. -template -inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { +template +inline void read_leb_128(std::istream& stream, std::array& out) { // Check the presence of our LEB128 magic string char leb128MagicString[Leb128MagicStringSize]; @@ -188,7 +189,7 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) auto bytes_left = read_little_endian(stream); std::uint32_t buf_pos = BUF_SIZE; - for (std::size_t i = 0; i < count; ++i) + for (std::size_t i = 0; i < Count; ++i) { IntType result = 0; size_t shift = 0; @@ -223,8 +224,8 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) // This takes N integers from array values, compresses them with // the LEB128 algorithm and writes the result on the stream s. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. -template -inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { +template +inline void write_leb_128(std::ostream& stream, const std::array& values) { // Write our LEB128 magic string stream.write(Leb128MagicString, Leb128MagicStringSize); @@ -232,7 +233,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size static_assert(std::is_signed_v, "Not implemented for unsigned types"); std::uint32_t byte_count = 0; - for (std::size_t i = 0; i < count; ++i) + for (std::size_t i = 0; i < Count; ++i) { IntType value = values[i]; std::uint8_t byte; @@ -264,7 +265,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size flush(); }; - for (std::size_t i = 0; i < count; ++i) + for (std::size_t i = 0; i < Count; ++i) { IntType value = values[i]; while (true) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 3439bc43c66..5ad2d337175 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "../position.h" #include "../types.h" @@ -48,7 +49,7 @@ invert_permutation(const std::array& order) { // 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) { +void permute(std::array& data, const std::array& order) { constexpr std::size_t TotalSize = N * sizeof(T); static_assert(TotalSize % (BlockSize * OrderSize) == 0, @@ -58,7 +59,7 @@ void permute(T (&data)[N], const std::array& order) { std::array buffer{}; - std::byte* const bytes = reinterpret_cast(data); + std::byte* const bytes = reinterpret_cast(data.data()); for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize) { @@ -79,7 +80,8 @@ void permute(T (&data)[N], const std::array& order) { // Input feature converter template class FeatureTransformer { - + static constexpr bool UseThreats = + (TransformedFeatureDimensions == TransformedFeatureDimensionsBig); // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; @@ -88,7 +90,10 @@ class FeatureTransformer { using OutputType = TransformedFeatureType; // Number of input/output dimensions - static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType InputDimensions = PSQFeatureSet::Dimensions; + static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions; + static constexpr IndexType TotalInputDimensions = + InputDimensions + (UseThreats ? ThreatInputDimensions : 0); static constexpr IndexType OutputDimensions = HalfDimensions; // Size of forward propagation buffer @@ -119,17 +124,24 @@ class FeatureTransformer { // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { - return FeatureSet::HashValue ^ (OutputDimensions * 2); + return (UseThreats ? ThreatFeatureSet::HashValue : PSQFeatureSet::HashValue) + ^ (OutputDimensions * 2); } void permute_weights() { permute<16>(biases, PackusEpi16Order); permute<16>(weights, PackusEpi16Order); + + if (UseThreats) + permute<8>(threatWeights, PackusEpi16Order); } void unpermute_weights() { permute<16>(biases, InversePackusEpi16Order); permute<16>(weights, InversePackusEpi16Order); + + if (UseThreats) + permute<8>(threatWeights, InversePackusEpi16Order); } inline void scale_weights(bool read) { @@ -145,14 +157,51 @@ class FeatureTransformer { } // Read network parameters + // TODO: This is ugly. Currently LEB128 on the entire L1 necessitates + // reading the weights into a combined array, and then splitting. bool read_parameters(std::istream& stream) { + read_leb_128(stream, biases); + + if (UseThreats) + { + auto combinedWeights = + std::make_unique>(); + auto combinedPsqtWeights = + std::make_unique>(); - read_leb_128(stream, biases, HalfDimensions); - read_leb_128(stream, weights, HalfDimensions * InputDimensions); - read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + read_leb_128(stream, *combinedWeights); + + std::copy(combinedWeights->begin(), + combinedWeights->begin() + ThreatInputDimensions * HalfDimensions, + std::begin(threatWeights)); + + std::copy(combinedWeights->begin() + ThreatInputDimensions * HalfDimensions, + combinedWeights->begin() + + (ThreatInputDimensions + InputDimensions) * HalfDimensions, + std::begin(weights)); + + read_leb_128(stream, *combinedPsqtWeights); + + std::copy(combinedPsqtWeights->begin(), + combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets, + std::begin(threatPsqtWeights)); + + std::copy(combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets, + combinedPsqtWeights->begin() + + (ThreatInputDimensions + InputDimensions) * PSQTBuckets, + std::begin(psqtWeights)); + } + else + { + read_leb_128(stream, weights); + read_leb_128(stream, psqtWeights); + } permute_weights(); - scale_weights(true); + + if (!UseThreats) + scale_weights(true); + return !stream.fail(); } @@ -161,11 +210,44 @@ class FeatureTransformer { std::unique_ptr copy = std::make_unique(*this); copy->unpermute_weights(); - copy->scale_weights(false); - write_leb_128(stream, copy->biases, HalfDimensions); - write_leb_128(stream, copy->weights, HalfDimensions * InputDimensions); - write_leb_128(stream, copy->psqtWeights, PSQTBuckets * InputDimensions); + if (!UseThreats) + copy->scale_weights(false); + + write_leb_128(stream, copy->biases); + + if (UseThreats) + { + auto combinedWeights = + std::make_unique>(); + auto combinedPsqtWeights = + std::make_unique>(); + + std::copy(std::begin(copy->threatWeights), + std::begin(copy->threatWeights) + ThreatInputDimensions * HalfDimensions, + combinedWeights->begin()); + + std::copy(std::begin(copy->weights), + std::begin(copy->weights) + InputDimensions * HalfDimensions, + combinedWeights->begin() + ThreatInputDimensions * HalfDimensions); + + write_leb_128(stream, *combinedWeights); + + std::copy(std::begin(copy->threatPsqtWeights), + std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets, + combinedPsqtWeights->begin()); + + std::copy(std::begin(copy->psqtWeights), + std::begin(copy->psqtWeights) + InputDimensions * PSQTBuckets, + combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets); + + write_leb_128(stream, *combinedPsqtWeights); + } + else + { + write_leb_128(stream, copy->weights); + write_leb_128(stream, copy->psqtWeights); + } return !stream.fail(); } @@ -187,17 +269,29 @@ class FeatureTransformer { int bucket) const { using namespace SIMD; - accumulatorStack.evaluate(pos, *this, *cache); - const auto& accumulatorState = accumulatorStack.latest(); + const auto& accumulatorState = accumulatorStack.latest(); + const auto& threatAccumulatorState = accumulatorStack.latest(); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& psqtAccumulation = (accumulatorState.acc()).psqtAccumulation; - const auto psqt = - (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) - / 2; + auto psqt = + (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]); + + if (UseThreats) + { + const auto& threatPsqtAccumulation = + (threatAccumulatorState.acc()).psqtAccumulation; + psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket] + - threatPsqtAccumulation[perspectives[1]][bucket]) + / 2; + } + else + psqt /= 2; const auto& accumulation = (accumulatorState.acc()).accumulation; + const auto& threatAccumulation = + (threatAccumulatorState.acc()).accumulation; for (IndexType p = 0; p < 2; ++p) { @@ -210,7 +304,7 @@ class FeatureTransformer { constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; const vec_t Zero = vec_zero(); - const vec_t One = vec_set_16(127 * 2); + const vec_t One = vec_set_16(UseThreats ? 255 : 127 * 2); const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); const vec_t* in1 = @@ -276,20 +370,48 @@ class FeatureTransformer { #else 6; #endif - - for (IndexType j = 0; j < NumOutputChunks; ++j) + if (UseThreats) { - const vec_t sum0a = - vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift); - const vec_t sum0b = - vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift); - const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One); - const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One); - - const vec_t pa = vec_mulhi_16(sum0a, sum1a); - const vec_t pb = vec_mulhi_16(sum0b, sum1b); - - out[j] = vec_packus_16(pa, pb); + const vec_t* tin0 = + reinterpret_cast(&(threatAccumulation[perspectives[p]][0])); + const vec_t* tin1 = reinterpret_cast( + &(threatAccumulation[perspectives[p]][HalfDimensions / 2])); + for (IndexType j = 0; j < NumOutputChunks; ++j) + { + const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]); + const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]); + const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]); + const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]); + + const vec_t sum0a = + vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift); + const vec_t sum0b = + vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift); + const vec_t sum1a = vec_min_16(acc1a, One); + const vec_t sum1b = vec_min_16(acc1b, One); + + const vec_t pa = vec_mulhi_16(sum0a, sum1a); + const vec_t pb = vec_mulhi_16(sum0b, sum1b); + + out[j] = vec_packus_16(pa, pb); + } + } + else + { + for (IndexType j = 0; j < NumOutputChunks; ++j) + { + const vec_t sum0a = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift); + const vec_t sum0b = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift); + const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One); + const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One); + + const vec_t pa = vec_mulhi_16(sum0a, sum1a); + const vec_t pb = vec_mulhi_16(sum0b, sum1b); + + out[j] = vec_packus_16(pa, pb); + } } #else @@ -299,8 +421,21 @@ class FeatureTransformer { BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - sum0 = std::clamp(sum0, 0, 127 * 2); - sum1 = std::clamp(sum1, 0, 127 * 2); + + if (UseThreats) + { + BiasType sum0t = threatAccumulation[static_cast(perspectives[p])][j + 0]; + BiasType sum1t = + threatAccumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; + sum0 = std::clamp(sum0 + sum0t, 0, 255); + sum1 = std::clamp(sum1 + sum1t, 0, 255); + } + else + { + sum0 = std::clamp(sum0, 0, 127 * 2); + sum1 = std::clamp(sum1, 0, 127 * 2); + } + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 512); } @@ -310,9 +445,15 @@ class FeatureTransformer { return psqt; } // end of function transform() - alignas(CacheLineSize) BiasType biases[HalfDimensions]; - alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; - alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; + alignas(CacheLineSize) std::array biases; + alignas(CacheLineSize) std::array weights; + alignas(CacheLineSize) + std::array threatWeights; + alignas(CacheLineSize) std::array psqtWeights; + alignas(CacheLineSize) + std::array threatPsqtWeights; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/simd.h b/src/nnue/simd.h index f37eeb93059..6160221b99e 100644 --- a/src/nnue/simd.h +++ b/src/nnue/simd.h @@ -47,11 +47,13 @@ namespace Stockfish::Eval::NNUE::SIMD { #ifdef USE_AVX512 using vec_t = __m512i; +using vec_i8_t = __m256i; using vec128_t = __m128i; using psqt_vec_t = __m256i; using vec_uint_t = __m512i; #define vec_load(a) _mm512_load_si512(a) #define vec_store(a, b) _mm512_store_si512(a, b) + #define vec_convert_8_16(a) _mm512_cvtepi8_epi16(a) #define vec_add_16(a, b) _mm512_add_epi16(a, b) #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) #define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b) @@ -82,11 +84,13 @@ using vec_uint_t = __m512i; #elif USE_AVX2 using vec_t = __m256i; +using vec_i8_t = __m128i; using vec128_t = __m128i; using psqt_vec_t = __m256i; using vec_uint_t = __m256i; #define vec_load(a) _mm256_load_si256(a) #define vec_store(a, b) _mm256_store_si256(a, b) + #define vec_convert_8_16(a) _mm256_cvtepi8_epi16(a) #define vec_add_16(a, b) _mm256_add_epi16(a, b) #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) #define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b) @@ -119,11 +123,12 @@ using vec_uint_t = __m256i; #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) - #define NumRegistersSIMD 16 + #define NumRegistersSIMD 12 #define MaxChunkSize 32 #elif USE_SSE2 using vec_t = __m128i; +using vec_i8_t = std::uint64_t; // for the correct size -- will be loaded into an xmm reg using vec128_t = __m128i; using psqt_vec_t = __m128i; using vec_uint_t = __m128i; @@ -149,17 +154,35 @@ using vec_uint_t = __m128i; _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) #endif + #ifdef __i386__ +inline __m128i _mm_cvtsi64_si128(int64_t val) { + return _mm_loadl_epi64(reinterpret_cast(&val)); +} + #endif + + #ifdef USE_SSE41 + #define vec_convert_8_16(a) _mm_cvtepi8_epi16(_mm_cvtsi64_si128(static_cast(a))) + #else +// Credit: Yoshie2000 +inline __m128i vec_convert_8_16(uint64_t x) { + __m128i v8 = _mm_cvtsi64_si128(static_cast(x)); + __m128i sign = _mm_cmpgt_epi8(_mm_setzero_si128(), v8); + return _mm_unpacklo_epi8(v8, sign); +} + #endif + #define vec128_zero _mm_setzero_si128() #define vec128_set_16(a) _mm_set1_epi16(a) #define vec128_load(a) _mm_load_si128(a) #define vec128_storeu(a, b) _mm_storeu_si128(a, b) #define vec128_add(a, b) _mm_add_epi16(a, b) - #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define NumRegistersSIMD (Is64Bit ? 12 : 6) #define MaxChunkSize 16 #elif USE_NEON using vec_t = int16x8_t; +using vec_i8_t = int8x16_t; using psqt_vec_t = int32x4_t; using vec128_t = uint16x8_t; using vec_uint_t = uint32x4_t; @@ -191,6 +214,11 @@ static constexpr std::uint32_t Mask[4] = {1, 2, 4, 8}; #define NumRegistersSIMD 16 #define MaxChunkSize 16 + #ifndef __aarch64__ +// Single instruction doesn't exist on 32-bit ARM +inline int8x16_t vmovl_high_s8(int8x16_t val) { return vmovl_s8(vget_high_s8(val)); } + #endif + #else #undef VECTOR diff --git a/src/position.cpp b/src/position.cpp index 1551eb9dfde..a515876b6df 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -48,6 +48,7 @@ Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; Key side, noPawns; + } namespace { @@ -687,10 +688,10 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position // will be prefetched -DirtyPiece Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +DirtyBoardData Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -724,6 +725,10 @@ DirtyPiece Position::do_move(Move m, dp.from = from; dp.to = to; dp.add_sq = SQ_NONE; + DirtyThreats dts; + dts.us = us; + dts.prevKsq = square(us); + dts.threatenedSqs = dts.threateningSqs = 0; assert(color_of(pc) == us); assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us)); @@ -735,7 +740,7 @@ DirtyPiece Position::do_move(Move m, assert(captured == make_piece(us, ROOK)); Square rfrom, rto; - do_castling(us, from, to, rfrom, rto, &dp); + do_castling(us, from, to, rfrom, rto, &dts, &dp); k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; @@ -758,6 +763,9 @@ DirtyPiece Position::do_move(Move m, assert(relative_rank(us, to) == RANK_6); assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); + + // Update board and piece lists in ep case, normal captures are updated later + remove_piece(capsq, &dts); } st->pawnKey ^= Zobrist::psq[captured][capsq]; @@ -774,11 +782,9 @@ DirtyPiece Position::do_move(Move m, dp.remove_pc = captured; dp.remove_sq = capsq; - // Update board and piece lists - remove_piece(capsq); - k ^= Zobrist::psq[captured][capsq]; - st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]]; + st->materialKey ^= + Zobrist::psq[captured][8 + pieceCount[captured] - (m.type_of() != EN_PASSANT)]; // Reset rule 50 counter st->rule50 = 0; @@ -806,7 +812,15 @@ DirtyPiece Position::do_move(Move m, // Move the piece. The tricky Chess960 castling is handled earlier if (m.type_of() != CASTLING) - move_piece(from, to); + { + if (captured && m.type_of() != EN_PASSANT) + { + remove_piece(from, &dts); + swap_piece(to, pc, &dts); + } + else + move_piece(from, to, &dts); + } // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) @@ -823,8 +837,7 @@ DirtyPiece Position::do_move(Move m, assert(relative_rank(us, to) == RANK_8); assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); - remove_piece(to); - put_piece(promotion, to); + swap_piece(to, promotion, &dts); dp.add_pc = promotion; dp.add_sq = to; @@ -949,13 +962,16 @@ DirtyPiece Position::do_move(Move m, } } + dts.ksq = square(us); + assert(pos_is_ok()); assert(dp.pc != NO_PIECE); assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE)); assert(dp.from != SQ_NONE); assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING)); - return dp; + + return {dp, dts}; } @@ -1021,12 +1037,113 @@ void Position::undo_move(Move m) { assert(pos_is_ok()); } +template +inline void add_dirty_threat( + DirtyThreats* const dts, Piece pc, Piece threatened, Square s, Square threatenedSq) { + if (PutPiece) + { + dts->threatenedSqs |= square_bb(threatenedSq); + dts->threateningSqs |= square_bb(s); + } + + dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); +} + +template +void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { + // Add newly threatened pieces + Bitboard occupied = pieces(); + + Bitboard rAttacks = attacks_bb(s, occupied); + Bitboard bAttacks = attacks_bb(s, occupied); + Bitboard qAttacks = rAttacks | bAttacks; + + Bitboard threatened; + + switch (type_of(pc)) + { + case PAWN : + threatened = PseudoAttacks[color_of(pc)][s]; + break; + case BISHOP : + threatened = bAttacks; + break; + case ROOK : + threatened = rAttacks; + break; + case QUEEN : + threatened = qAttacks; + break; + + default : + threatened = PseudoAttacks[type_of(pc)][s]; + } + + threatened &= occupied; + + while (threatened) + { + Square threatened_sq = pop_lsb(threatened); + Piece threatened_pc = piece_on(threatened_sq); + + assert(threatened_sq != s); + assert(threatened_pc); + + add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); + } + + Bitboard sliders = (pieces(ROOK, QUEEN) & rAttacks) | (pieces(BISHOP, QUEEN) & bAttacks); + + Bitboard incoming_threats = (attacks_bb(s, occupied) & pieces(KNIGHT)) + | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) + | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) + | (attacks_bb(s, occupied) & pieces(KING)); + + while (sliders) + { + Square slider_sq = pop_lsb(sliders); + Piece slider = piece_on(slider_sq); + + Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; + threatened = ray & qAttacks & occupied; + + assert(!more_than_one(threatened)); + if (ComputeRay && threatened) + { + Square threatened_sq = lsb(threatened); + + Piece threatened_pc = piece_on(threatened_sq); + add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + } + + add_dirty_threat(dts, slider, pc, slider_sq, s); + } + + // Add threats of sliders that were already threatening s, + // sliders are already handled in the loop above + + while (incoming_threats) + { + Square src_sq = pop_lsb(incoming_threats); + Piece src_pc = piece_on(src_sq); + + assert(src_sq != s); + assert(src_pc != NO_PIECE); + + add_dirty_threat(dts, src_pc, pc, src_sq, s); + } +} // Helper used to do/undo a castling move. This is a bit // tricky in Chess960 where from/to squares can overlap. template -void Position::do_castling( - Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) { +void Position::do_castling(Color us, + Square from, + Square& to, + Square& rfrom, + Square& rto, + DirtyThreats* const dts, + DirtyPiece* const dp) { bool kingSide = to > from; rfrom = to; // Castling is encoded as "king captures friendly rook" @@ -1044,12 +1161,12 @@ void Position::do_castling( } // Remove both pieces first since squares could overlap in Chess960 - remove_piece(Do ? from : to); - remove_piece(Do ? rfrom : rto); + remove_piece(Do ? from : to, dts); + remove_piece(Do ? rfrom : rto, dts); board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // remove_piece does not do this for us - put_piece(make_piece(us, KING), Do ? to : from); - put_piece(make_piece(us, ROOK), Do ? rto : rfrom); + put_piece(make_piece(us, KING), Do ? to : from, dts); + put_piece(make_piece(us, ROOK), Do ? rto : rfrom, dts); } @@ -1349,7 +1466,7 @@ bool Position::pos_is_ok() const { for (Piece pc : Pieces) if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) - || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + || pieceCount[pc] != std::count(board.begin(), board.end(), pc)) assert(0 && "pos_is_ok: Pieces"); for (Color c : {WHITE, BLACK}) diff --git a/src/position.h b/src/position.h index 579121bf3cd..9afdb17f90d 100644 --- a/src/position.h +++ b/src/position.h @@ -19,6 +19,7 @@ #ifndef POSITION_H_INCLUDED #define POSITION_H_INCLUDED +#include #include #include #include @@ -91,11 +92,11 @@ class Position { Bitboard pieces(PieceTypes... pts) const; Bitboard pieces(Color c) const; template - Bitboard pieces(Color c, PieceTypes... pts) const; - Piece piece_on(Square s) const; - const Piece* piece_array() const; - Square ep_square() const; - bool empty(Square s) const; + Bitboard pieces(Color c, PieceTypes... pts) const; + Piece piece_on(Square s) const; + const std::array& piece_array() const; + Square ep_square() const; + bool empty(Square s) const; template int count(Color c) const; template @@ -132,11 +133,11 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - DirtyPiece do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); - void undo_move(Move m); - void do_null_move(StateInfo& newSt, const TranspositionTable& tt); - void undo_null_move(); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + DirtyBoardData do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -166,8 +167,9 @@ class Position { StateInfo* state() const; - void put_piece(Piece pc, Square s); - void remove_piece(Square s); + void put_piece(Piece pc, Square s, DirtyThreats* const dts = nullptr); + void remove_piece(Square s, DirtyThreats* const dts = nullptr); + void swap_piece(Square s, Piece pc, DirtyThreats* const dts = nullptr); private: // Initialization helpers (used while setting up a position) @@ -176,20 +178,24 @@ class Position { void set_check_info() const; // Other helpers - void move_piece(Square from, Square to); + template + void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts); + void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); template - void do_castling(Color us, - Square from, - Square& to, - Square& rfrom, - Square& rto, - DirtyPiece* const dp = nullptr); + void do_castling(Color us, + Square from, + Square& to, + Square& rfrom, + Square& rto, + DirtyThreats* const dts = nullptr, + DirtyPiece* const dp = nullptr); Key adjust_key50(Key k) const; // Data members - Piece board[SQUARE_NB]; - Bitboard byTypeBB[PIECE_TYPE_NB]; - Bitboard byColorBB[COLOR_NB]; + std::array board; + std::array byTypeBB; + std::array byColorBB; + int pieceCount[PIECE_NB]; int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; @@ -209,7 +215,7 @@ inline Piece Position::piece_on(Square s) const { return board[s]; } -inline const Piece* Position::piece_array() const { return board; } +inline const std::array& Position::piece_array() const { return board; } inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } @@ -326,18 +332,23 @@ inline bool Position::capture_stage(Move m) const { inline Piece Position::captured_piece() const { return st->capturedPiece; } -inline void Position::put_piece(Piece pc, Square s) { - +inline void Position::put_piece(Piece pc, Square s, DirtyThreats* const dts) { board[s] = pc; byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; byColorBB[color_of(pc)] |= s; pieceCount[pc]++; pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; -} -inline void Position::remove_piece(Square s) { + if (dts) + update_piece_threats(pc, s, dts); +} +inline void Position::remove_piece(Square s, DirtyThreats* const dts) { Piece pc = board[s]; + + if (dts) + update_piece_threats(pc, s, dts); + byTypeBB[ALL_PIECES] ^= s; byTypeBB[type_of(pc)] ^= s; byColorBB[color_of(pc)] ^= s; @@ -346,15 +357,35 @@ inline void Position::remove_piece(Square s) { pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; } -inline void Position::move_piece(Square from, Square to) { - +inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts) { Piece pc = board[from]; Bitboard fromTo = from | to; + + if (dts) + update_piece_threats(pc, from, dts); + byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; byColorBB[color_of(pc)] ^= fromTo; board[from] = NO_PIECE; board[to] = pc; + + if (dts) + update_piece_threats(pc, to, dts); +} + +inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { + Piece old = board[s]; + + remove_piece(s); + + if (dts) + update_piece_threats(old, s, dts); + + put_piece(pc, s); + + if (dts) + update_piece_threats(pc, s, dts); } inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { diff --git a/src/search.cpp b/src/search.cpp index a0c6eafe32d..ce7d4100840 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -46,6 +46,7 @@ #include "thread.h" #include "timeman.h" #include "tt.h" +#include "types.h" #include "uci.h" #include "ucioption.h" @@ -527,15 +528,19 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac void Search::Worker::do_move( Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { - bool capture = pos.capture_stage(move); - DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); + bool capture = pos.capture_stage(move); nodes.fetch_add(1, std::memory_order_relaxed); - accumulatorStack.push(dp); + + DirtyBoardData dirtyBoardData = pos.do_move(move, st, givesCheck, &tt); + accumulatorStack.push(dirtyBoardData); + if (ss != nullptr) { - ss->currentMove = move; - ss->continuationHistory = &continuationHistory[ss->inCheck][capture][dp.pc][move.to_sq()]; - ss->continuationCorrectionHistory = &continuationCorrectionHistory[dp.pc][move.to_sq()]; + ss->currentMove = move; + ss->continuationHistory = + &continuationHistory[ss->inCheck][capture][dirtyBoardData.dp.pc][move.to_sq()]; + ss->continuationCorrectionHistory = + &continuationCorrectionHistory[dirtyBoardData.dp.pc][move.to_sq()]; } } diff --git a/src/types.h b/src/types.h index d40e1e292c2..46aa16a030c 100644 --- a/src/types.h +++ b/src/types.h @@ -40,6 +40,7 @@ #include #include #include + #include "misc.h" #if defined(_MSC_VER) // Disable some silly and noisy warnings from MSVC compiler @@ -290,6 +291,48 @@ struct DirtyPiece { Piece remove_pc, add_pc; }; +// Keep track of what threats change on the board (used by NNUE) +struct DirtyThreat { + DirtyThreat() { /* don't initialize data */ } + DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { + data = (add << 28) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + } + + Piece pc() const { return static_cast(data >> 20 & 0xf); } + Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } + Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } + Square pc_sq() const { return static_cast(data & 0xff); } + bool add() const { + uint32_t b = data >> 28; + sf_assume(b == 0 || b == 1); + return b; + } + + private: + uint32_t data; +}; + +using DirtyThreatList = ValueList; + +// A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. +// Moving a piece also can reveal at most 8 discovered attacks. +// This implies that a non-castling move can change at most (8 + 16) * 3 + 8 = 80 features. +// By similar logic, a castling move can change at most (5 + 1 + 3 + 9) * 2 = 36 features. +// Thus, 80 should work as an upper bound. + +struct DirtyThreats { + DirtyThreatList list; + Color us; + Square prevKsq, ksq; + + Bitboard threatenedSqs, threateningSqs; +}; + +struct DirtyBoardData { + DirtyPiece dp; + DirtyThreats dts; +}; + #define ENABLE_INCR_OPERATORS_ON(T) \ constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \ constexpr T& operator--(T& d) { return d = T(int(d) - 1); } From 7ac8e6221979f14395bb63566e6ca43d803e2acb Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 3 Nov 2025 21:59:50 -0500 Subject: [PATCH 1206/1309] Refine constant in correction history update Passed STC LLR: 3.04 (-2.94,2.94) <0.00,2.00> Total: 250112 W: 65277 L: 64635 D: 120200 Ptnml(0-2): 841, 29326, 64134, 29860, 895 https://tests.stockfishchess.org/tests/view/69096c46ea4b268f1fac2a39 Passed LTC LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 142908 W: 37141 L: 36604 D: 69163 Ptnml(0-2): 100, 15478, 39742, 16053, 81 https://tests.stockfishchess.org/tests/view/690e7dfbec1d00d2c195c351 closes https://github.com/official-stockfish/Stockfish/pull/6405 bench 2613869 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ce7d4100840..afd862273f4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1452,9 +1452,9 @@ Value Search::Worker::search( if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) && (bestValue < ss->staticEval) == !bestMove) { - auto bonus = - std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), - -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth + / (8 + 2 * (bestValue > ss->staticEval)), + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); update_correction_history(pos, ss, *this, bonus); } From 3ae76847145b31553a958ff87c52c280e4f784be Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:08:23 +0000 Subject: [PATCH 1207/1309] Improve Threats Speed Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 23168 W: 6132 L: 5845 D: 11191 Ptnml(0-2): 77, 2405, 6325, 2708, 69 https://tests.stockfishchess.org/tests/view/69148c3c7ca8781852331831 ``` Result of 50 runs ================== base (./stockfish.master ) = 985641 +/- 4249 test (./stockfish.patch ) = 1038567 +/- 5679 diff = +52926 +/- 4473 speedup = +0.0537 P(speedup > 0) = 1.0000 CPU: 16 x AMD Ryzen 9 3950X 16-Core Processor Hyperthreading: on ``` closes https://github.com/official-stockfish/Stockfish/pull/6415 No functional change --- src/nnue/features/full_threats.cpp | 81 +++++++++++++++++------------- src/nnue/nnue_accumulator.cpp | 18 ++++++- src/position.cpp | 71 ++++++++++++++++---------- 3 files changed, 107 insertions(+), 63 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 6fc6b00ec1e..994d2160c05 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -58,6 +58,35 @@ constexpr std::array AllPieces = { PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] +namespace { + +template +IndexType make_index_with_orientation( + Piece attacker, Square from, Square to, Piece attacked, int orientation) { + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); + + if constexpr (Perspective == BLACK) + { + attacker = ~attacker; + attacked = ~attacked; + } + + const auto piecePairData = index_lut1[attacker][attacked]; + + const bool less_than = static_cast(from) < static_cast(to); + if ((piecePairData.excluded_pair_info() + less_than) & 2) + return FullThreats::Dimensions; + + const IndexType index = + piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; + + sf_assume(index != FullThreats::Dimensions); + return index; +} + +} // namespace + static void init_index_luts() { for (Piece attacker : AllPieces) { @@ -129,32 +158,8 @@ void init_threat_offsets() { template IndexType FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - from = (Square) (int(from) ^ OrientTBL[Perspective][ksq]); - to = (Square) (int(to) ^ OrientTBL[Perspective][ksq]); - - if (Perspective == BLACK) - { - attacker = ~attacker; - attacked = ~attacked; - } - - auto piecePairData = index_lut1[attacker][attacked]; - - // Some threats imply the existence of the corresponding ones in the opposite - // direction. We filter them here to ensure only one such threat is active. - - // In the below addition, the 2nd lsb gets set iff either the pair is always excluded, - // or the pair is semi-excluded and from < to. By using an unsigned compare, the following - // sequence can use an add-with-carry instruction. - bool less_than = static_cast(from) < static_cast(to); - if ((piecePairData.excluded_pair_info() + less_than) & 2) - return Dimensions; - - IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != Dimensions); - return index; + return make_index_with_orientation(attacker, from, to, attacked, + OrientTBL[Perspective][ksq]); } // Get a list of indices for active features in ascending order @@ -162,8 +167,9 @@ template void FullThreats::append_active_indices(const Position& pos, IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(Perspective); - Bitboard occupied = pos.pieces(); + Square ksq = pos.square(Perspective); + const int orientation = OrientTBL[Perspective][ksq]; + Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { @@ -187,7 +193,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -198,7 +205,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -215,8 +223,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = - make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -243,7 +251,9 @@ void FullThreats::append_changed_indices(Square ksq, IndexList& added, FusedUpdateData* fusedData, bool first) { - for (const auto dirty : diff.list) + const int orientation = OrientTBL[Perspective][ksq]; + + for (const auto& dirty : diff.list) { auto attacker = dirty.pc(); auto attacked = dirty.threatened_pc(); @@ -282,9 +292,10 @@ void FullThreats::append_changed_indices(Square ksq, } } - IndexType index = make_index(attacker, from, to, attacked, ksq); + const IndexType index = + make_index_with_orientation(attacker, from, to, attacked, orientation); - if (index != Dimensions) + if (index < Dimensions) (add ? added : removed).push_back(index); } } diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 0444b6e4054..7f723c61fa2 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -336,7 +336,8 @@ struct AccumulatorUpdateContext { to_psqt_weight_vector(indices)...); } - void apply(typename FeatureSet::IndexList added, typename FeatureSet::IndexList removed) { + void apply(const typename FeatureSet::IndexList& added, + const typename FeatureSet::IndexList& removed) { const auto fromAcc = from.template acc().accumulation[Perspective]; const auto toAcc = to.template acc().accumulation[Perspective]; @@ -573,6 +574,21 @@ void update_accumulator_incremental( FeatureSet::template append_changed_indices(ksq, computed.diff, added, removed); + if (!added.size() && !removed.size()) + { + auto& targetAcc = target_state.template acc(); + const auto& sourceAcc = computed.template acc(); + + std::memcpy(targetAcc.accumulation[Perspective], sourceAcc.accumulation[Perspective], + sizeof(targetAcc.accumulation[Perspective])); + std::memcpy(targetAcc.psqtAccumulation[Perspective], + sourceAcc.psqtAccumulation[Perspective], + sizeof(targetAcc.psqtAccumulation[Perspective])); + + targetAcc.computed[Perspective] = true; + return; + } + auto updateContext = make_accumulator_update_context(featureTransformer, computed, target_state); diff --git a/src/position.cpp b/src/position.cpp index a515876b6df..58edbcc3328 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1051,12 +1051,22 @@ inline void add_dirty_threat( template void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { - // Add newly threatened pieces - Bitboard occupied = pieces(); - - Bitboard rAttacks = attacks_bb(s, occupied); - Bitboard bAttacks = attacks_bb(s, occupied); - Bitboard qAttacks = rAttacks | bAttacks; + const Bitboard occupied = pieces(); + const Bitboard rookQueens = pieces(ROOK, QUEEN); + const Bitboard bishopQueens = pieces(BISHOP, QUEEN); + const Bitboard knights = pieces(KNIGHT); + const Bitboard kings = pieces(KING); + const Bitboard whitePawns = pieces(WHITE, PAWN); + const Bitboard blackPawns = pieces(BLACK, PAWN); + + const Bitboard rAttacks = attacks_bb(s, occupied); + const Bitboard bAttacks = attacks_bb(s, occupied); + + Bitboard qAttacks = Bitboard(0); + if constexpr (ComputeRay) + qAttacks = rAttacks | bAttacks; + else if (type_of(pc) == QUEEN) + qAttacks = rAttacks | bAttacks; Bitboard threatened; @@ -1092,35 +1102,42 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); } - Bitboard sliders = (pieces(ROOK, QUEEN) & rAttacks) | (pieces(BISHOP, QUEEN) & bAttacks); - - Bitboard incoming_threats = (attacks_bb(s, occupied) & pieces(KNIGHT)) - | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) - | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) - | (attacks_bb(s, occupied) & pieces(KING)); + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); - while (sliders) + if constexpr (ComputeRay) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); + while (sliders) + { + Square slider_sq = pop_lsb(sliders); + Piece slider = piece_on(slider_sq); - Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; - threatened = ray & qAttacks & occupied; + const Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; + const Bitboard discovered = ray & qAttacks & occupied; - assert(!more_than_one(threatened)); - if (ComputeRay && threatened) - { - Square threatened_sq = lsb(threatened); + assert(!more_than_one(discovered)); + if (discovered) + { + const Square threatened_sq = lsb(discovered); + const Piece threatened_pc = piece_on(threatened_sq); + add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + } - Piece threatened_pc = piece_on(threatened_sq); - add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + add_dirty_threat(dts, slider, pc, slider_sq, s); + } + } + else + { + while (sliders) + { + Square slider_sq = pop_lsb(sliders); + Piece slider = piece_on(slider_sq); + add_dirty_threat(dts, slider, pc, slider_sq, s); } - - add_dirty_threat(dts, slider, pc, slider_sq, s); } - // Add threats of sliders that were already threatening s, - // sliders are already handled in the loop above + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); while (incoming_threats) { From fa4f05d3ef2dc393da73b0709e6c2c59a09652c5 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 9 Nov 2025 03:50:15 -0800 Subject: [PATCH 1208/1309] Don't copy around DirtyThreats The contents of DirtyThreats gets memcpyed twice for each call to do_move. (Return value optimization doesn't apply to the do_move function itself because it constructs a std::pair, so it gets copied; and the calls to reset also require a copy.) This patch inserts the dirty info in place. Sometimes the caller of do_move ignores the DirtyThreats info, so we pass in scratch objects. I found that stack-allocating these scratch objects was bad on Fishtest, so I begrudgingly put them in the Position struct. Both templating the do_move function on whether dirty threats are needed and putting a null-check branch for each use of dirty threats were slowdowns locally. Of course, nothing prevents a future attempt at cleaning this up. passed STC LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 68448 W: 17770 L: 17418 D: 33260 Ptnml(0-2): 198, 7425, 18630, 7769, 202 https://tests.stockfishchess.org/tests/view/6914c01a7ca87818523318ba closes https://github.com/official-stockfish/Stockfish/pull/6414 No functional change --- src/nnue/nnue_accumulator.cpp | 9 ++++++--- src/nnue/nnue_accumulator.h | 14 ++++++++++--- src/position.cpp | 24 +++++++++++------------ src/position.h | 37 ++++++++++++++++++++++------------- src/search.cpp | 8 ++++---- 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 7f723c61fa2..47f09afce57 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "../bitboard.h" @@ -122,11 +123,13 @@ void AccumulatorStack::reset() noexcept { size = 1; } -void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept { +std::pair AccumulatorStack::push() noexcept { assert(size < MaxSize); - psq_accumulators[size].reset(dirtyBoardData.dp); - threat_accumulators[size].reset(dirtyBoardData.dts); + auto& dp = psq_accumulators[size].reset(); + auto& dts = threat_accumulators[size].reset(); + new (&dts) DirtyThreats; size++; + return {dp, dts}; } void AccumulatorStack::pop() noexcept { diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 4c907c7e3ae..341960b3020 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "../types.h" #include "nnue_architecture.h" @@ -141,6 +142,12 @@ struct AccumulatorState { accumulatorBig.computed.fill(false); accumulatorSmall.computed.fill(false); } + + typename FeatureSet::DiffType& reset() noexcept { + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); + return diff; + } }; class AccumulatorStack { @@ -150,9 +157,10 @@ class AccumulatorStack { template [[nodiscard]] const AccumulatorState& latest() const noexcept; - void reset() noexcept; - void push(const DirtyBoardData& dirtyBoardData) noexcept; - void pop() noexcept; + void reset() noexcept; + void push(const DirtyBoardData& dirtyBoardData) noexcept; + std::pair push() noexcept; + void pop() noexcept; template void evaluate(const Position& pos, diff --git a/src/position.cpp b/src/position.cpp index 58edbcc3328..347bbcfc89a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -201,7 +201,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { Square sq = SQ_A8; std::istringstream ss(fenStr); - std::memset(this, 0, sizeof(Position)); + std::memset(reinterpret_cast(this), 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); st = si; @@ -688,10 +688,12 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position // will be prefetched -DirtyBoardData Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +void Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -720,12 +722,10 @@ DirtyBoardData Position::do_move(Move m, bool checkEP = false; - DirtyPiece dp; - dp.pc = pc; - dp.from = from; - dp.to = to; - dp.add_sq = SQ_NONE; - DirtyThreats dts; + dp.pc = pc; + dp.from = from; + dp.to = to; + dp.add_sq = SQ_NONE; dts.us = us; dts.prevKsq = square(us); dts.threatenedSqs = dts.threateningSqs = 0; @@ -970,8 +970,6 @@ DirtyBoardData Position::do_move(Move m, assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE)); assert(dp.from != SQ_NONE); assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING)); - - return {dp, dts}; } diff --git a/src/position.h b/src/position.h index 9afdb17f90d..c95697d19f4 100644 --- a/src/position.h +++ b/src/position.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "bitboard.h" @@ -133,11 +134,16 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - DirtyBoardData do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); - void undo_move(Move m); - void do_null_move(StateInfo& newSt, const TranspositionTable& tt); - void undo_null_move(); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + void do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -196,14 +202,16 @@ class Position { std::array byTypeBB; std::array byColorBB; - int pieceCount[PIECE_NB]; - int castlingRightsMask[SQUARE_NB]; - Square castlingRookSquare[CASTLING_RIGHT_NB]; - Bitboard castlingPath[CASTLING_RIGHT_NB]; - StateInfo* st; - int gamePly; - Color sideToMove; - bool chess960; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; + DirtyPiece scratch_dp; + DirtyThreats scratch_dts; }; std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -389,7 +397,8 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { } inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { - do_move(m, newSt, gives_check(m), tt); + new (&scratch_dts) DirtyThreats; + do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt); } inline StateInfo* Position::state() const { return st; } diff --git a/src/search.cpp b/src/search.cpp index afd862273f4..43d96bf24af 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -531,16 +531,16 @@ void Search::Worker::do_move( bool capture = pos.capture_stage(move); nodes.fetch_add(1, std::memory_order_relaxed); - DirtyBoardData dirtyBoardData = pos.do_move(move, st, givesCheck, &tt); - accumulatorStack.push(dirtyBoardData); + auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); + pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); if (ss != nullptr) { ss->currentMove = move; ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][dirtyBoardData.dp.pc][move.to_sq()]; + &continuationHistory[ss->inCheck][capture][dirtyPiece.pc][move.to_sq()]; ss->continuationCorrectionHistory = - &continuationCorrectionHistory[dirtyBoardData.dp.pc][move.to_sq()]; + &continuationCorrectionHistory[dirtyPiece.pc][move.to_sq()]; } } From 84148586e53f4bd4f3177a98c7f201f63245fd99 Mon Sep 17 00:00:00 2001 From: mstembera <5421953+mstembera@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:27:33 -0800 Subject: [PATCH 1209/1309] Fix MSVC compile broken after Shared Memory patch. closes https://github.com/official-stockfish/Stockfish/pull/6397 No functional change --- src/memory.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/memory.h b/src/memory.h index b9be6f170dc..dad07df1ddb 100644 --- a/src/memory.h +++ b/src/memory.h @@ -41,6 +41,13 @@ #endif #include + // Some Windows headers (RPC/old headers) define short macros such + // as 'small' expanding to 'char', which breaks identifiers in the code. + // Undefine those macros immediately after including . + #ifdef small + #undef small + #endif + #include extern "C" { From 4784ff2b3be97a0364b77a677e21a644403f7b3a Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Mon, 3 Nov 2025 08:55:26 +0100 Subject: [PATCH 1210/1309] Unify do_move & do_null_move workload While being there also remove and unused assignment. Tested for non-regression at STC: https://tests.stockfishchess.org/tests/view/69060d81ea4b268f1fac1f36 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 94496 W: 24570 L: 24421 D: 45505 Ptnml(0-2): 264, 10145, 26275, 10306, 258 closes https://github.com/official-stockfish/Stockfish/pull/6383 no functional change --- src/search.cpp | 17 ++++++++--------- src/search.h | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 43d96bf24af..d28ac5f271b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -544,7 +544,12 @@ void Search::Worker::do_move( } } -void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } +void Search::Worker::do_null_move(Position& pos, StateInfo& st, Stack* const ss) { + pos.do_null_move(st, tt); + ss->currentMove = Move::null(); + ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; + ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; +} void Search::Worker::undo_move(Position& pos, const Move move) { pos.undo_move(move); @@ -868,12 +873,7 @@ Value Search::Worker::search( // Null move dynamic reduction based on depth Depth R = 6 + depth / 3 + improving; - - ss->currentMove = Move::null(); - ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; - ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; - - do_null_move(pos, st); + do_null_move(pos, st, ss); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); @@ -1580,8 +1580,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) futilityBase = ss->staticEval + 352; } - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, - (ss - 2)->continuationHistory}; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; diff --git a/src/search.h b/src/search.h index 4c4357d3e2d..f3d99d41529 100644 --- a/src/search.h +++ b/src/search.h @@ -299,7 +299,7 @@ class Worker { void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss); void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); - void do_null_move(Position& pos, StateInfo& st); + void do_null_move(Position& pos, StateInfo& st, Stack* const ss); void undo_move(Position& pos, const Move move); void undo_null_move(Position& pos); From 8551f86efce1d55c3cf5bb639247212a3c290bdf Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 25 Oct 2025 03:23:06 -0400 Subject: [PATCH 1211/1309] Remove check term in capture movepick Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 248448 W: 64697 L: 64708 D: 119043 Ptnml(0-2): 784, 29393, 63971, 29202, 874 https://tests.stockfishchess.org/tests/view/68fc7afb637acd2a11e72d86 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 193626 W: 49808 L: 49764 D: 94054 Ptnml(0-2): 162, 21415, 53621, 21447, 168 https://tests.stockfishchess.org/tests/view/6901ad09637acd2a11e73828 closes https://github.com/official-stockfish/Stockfish/pull/6395 bench 2920273 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 2eec3556b76..b5b02609ea9 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -153,7 +153,7 @@ ExtMove* MovePicker::score(MoveList& ml) { if constexpr (Type == CAPTURES) m.value = (*captureHistory)[pc][to][type_of(capturedPiece)] - + 7 * int(PieceValue[capturedPiece]) + 1024 * bool(pos.check_squares(pt) & to); + + 7 * int(PieceValue[capturedPiece]); else if constexpr (Type == QUIETS) { From 55643baa3f6be3265598fba7c7e8b308fe6ccbcf Mon Sep 17 00:00:00 2001 From: Didier Durand Date: Fri, 7 Nov 2025 17:23:26 +0100 Subject: [PATCH 1212/1309] Fix some doc typos closes https://github.com/official-stockfish/Stockfish/pull/6400 No functional change --- src/history.h | 2 +- src/numa.h | 2 +- tests/instrumented.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/history.h b/src/history.h index 940e9899160..445d541818e 100644 --- a/src/history.h +++ b/src/history.h @@ -104,7 +104,7 @@ using Stats = MultiArray, Sizes...>; // see https://www.chessprogramming.org/Butterfly_Boards using ButterflyHistory = Stats; -// LowPlyHistory is adressed by play and move's from and to squares, used +// LowPlyHistory is addressed by play and move's from and to squares, used // to improve move ordering near the root using LowPlyHistory = Stats; diff --git a/src/numa.h b/src/numa.h index 261b6005d37..76d265af2fd 100644 --- a/src/numa.h +++ b/src/numa.h @@ -1346,7 +1346,7 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase { std::size_t get_discriminator(NumaIndex idx) const { const NumaConfig& cfg = get_numa_config(); const NumaConfig& cfg_sys = NumaConfig::from_system(false); - // as a descriminator, locate the hardware/system numadomain this cpuindex belongs to + // as a discriminator, locate the hardware/system numadomain this cpuindex belongs to CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx); diff --git a/tests/instrumented.py b/tests/instrumented.py index db5ec8e0802..23de446ef74 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -508,7 +508,7 @@ def parse_args(): framework = MiniTestFramework() - # Each test suite will be ran inside a temporary directory + # Each test suite will be run inside a temporary directory framework.run([TestCLI, TestInteractive, TestSyzygy]) EPD.delete_bench_epd() From 9e38023a8c2c67204f9a5d03eac5d5f00ea93d3f Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 31 Oct 2025 02:18:22 -0400 Subject: [PATCH 1213/1309] Simplify threat term in movepick Passed simplification STC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 71296 W: 18605 L: 18419 D: 34272 Ptnml(0-2): 250, 8374, 18183, 8622, 219 https://tests.stockfishchess.org/tests/view/690454c7ea4b268f1fac1bbe Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 240552 W: 61870 L: 61874 D: 116808 Ptnml(0-2): 113, 26300, 67460, 26284, 119 https://tests.stockfishchess.org/tests/view/69063956ea4b268f1fac1f66 closes https://github.com/official-stockfish/Stockfish/pull/6401 bench 2697501 --- src/movepick.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b5b02609ea9..0b18cf56554 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -171,9 +171,8 @@ ExtMove* MovePicker::score(MoveList& ml) { // penalty for moving to a square threatened by a lesser piece // or bonus for escaping an attack by a lesser piece. - static constexpr int bonus[KING + 1] = {0, 0, 144, 144, 256, 517, 10000}; - int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); - m.value += bonus[pt] * v; + int v = threatByLesser[pt] & to ? -19 : 20 * bool(threatByLesser[pt] & from); + m.value += PieceValue[pt] * v; if (ply < LOW_PLY_HISTORY_SIZE) From 3f2405bf4e4c87d67e3939f2469419bcccb8a707 Mon Sep 17 00:00:00 2001 From: Torsten Hellwig Date: Sun, 9 Nov 2025 15:54:20 +0100 Subject: [PATCH 1214/1309] Print NEON before POPCNT in compilation settings Normally, Stockfish outputs the compilation settings from advanced to less advanced, e.g.: `AVX512ICL VNNI AVX512 BMI2 AVX2 SSE41 SSSE3 SSE2 POPCNT` With NEON, however, POPCNT is printed first, followed by the more advanced NEON options: `POPCNT NEON_DOTPROD` This PR places POPCNT behind the NEON options as well. closes https://github.com/official-stockfish/Stockfish/pull/6402 no functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 3bdde000f70..d2149728036 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -259,12 +259,12 @@ std::string compiler_info() { #if defined(USE_SSE2) compiler += " SSE2"; #endif - compiler += (HasPopCnt ? " POPCNT" : ""); #if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; #elif defined(USE_NEON) compiler += " NEON"; #endif + compiler += (HasPopCnt ? " POPCNT" : ""); #if !defined(NDEBUG) compiler += " DEBUG"; From 229bd1e2e350b4b67ed5a962ae99d939c0a27d29 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Tue, 11 Nov 2025 06:09:26 -0800 Subject: [PATCH 1215/1309] check for material key validity in tbprobe During recent work on threat inputs, there was a hard-to-debug crash in tbprobe caused by incorrectly computing st->materialKey. This PR adds correctness checking to pos_is_ok and a faster check in tbprobe that will actually run in CI (because pos_is_ok checking is disabled even in debug mode, for performance purposes). closes https://github.com/official-stockfish/Stockfish/pull/6410 No functional change --- src/position.cpp | 16 +++++++++++++--- src/position.h | 2 ++ src/syzygy/tbprobe.cpp | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 347bbcfc89a..c34ceb400bf 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -336,8 +336,8 @@ void Position::set_check_info() const { // The function is only used when a new position is set up void Position::set_state() const { - st->key = st->materialKey = 0; - st->minorPieceKey = 0; + st->key = 0; + st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -375,10 +375,15 @@ void Position::set_state() const { st->key ^= Zobrist::side; st->key ^= Zobrist::castling[st->castlingRights]; + st->materialKey = compute_material_key(); +} +Key Position::compute_material_key() const { + Key k = 0; for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][8 + cnt]; + k ^= Zobrist::psq[pc][8 + cnt]; + return k; } @@ -1447,6 +1452,9 @@ void Position::flip() { } +bool Position::material_key_is_ok() const { return compute_material_key() == st->materialKey; } + + // Performs some consistency checks for the position object // and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. @@ -1496,6 +1504,8 @@ bool Position::pos_is_ok() const { assert(0 && "pos_is_ok: Castling"); } + assert(material_key_is_ok() && "pos_is_ok: materialKey"); + return true; } diff --git a/src/position.h b/src/position.h index c95697d19f4..711c4e44484 100644 --- a/src/position.h +++ b/src/position.h @@ -169,6 +169,7 @@ class Position { // Position consistency check, for debugging bool pos_is_ok() const; + bool material_key_is_ok() const; void flip(); StateInfo* state() const; @@ -180,6 +181,7 @@ class Position { private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); + Key compute_material_key() const; void set_state() const; void set_check_info() const; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index cbf8dce5ea2..657866ba4d1 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1214,6 +1214,8 @@ template void* mapped(TBTable& e, const Position& pos) { static std::mutex mutex; + // Because TB is the only usage of materialKey, check it here in debug mode + assert(pos.material_key_is_ok()); // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). From df88db16c611516c494facadae2ec9ff0d0b6c06 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 14 Nov 2025 01:02:06 +0300 Subject: [PATCH 1216/1309] Simplify NMP reduction formula Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 178912 W: 46625 L: 46559 D: 85728 Ptnml(0-2): 540, 21167, 45975, 21235, 539 https://tests.stockfishchess.org/tests/view/69060677ea4b268f1fac1f25 Passed LTC: LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 140988 W: 36278 L: 36176 D: 68534 Ptnml(0-2): 82, 15520, 39215, 15568, 109 https://tests.stockfishchess.org/tests/view/6908bc32ea4b268f1fac288f closes https://github.com/official-stockfish/Stockfish/pull/6416 bench: 2959834 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d28ac5f271b..b88c428e2e4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -872,7 +872,7 @@ Value Search::Worker::search( assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 6 + depth / 3 + improving; + Depth R = 7 + depth / 3; do_null_move(pos, st, ss); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); From 7b7a9485d6d36b246b8eb3f278570e52d929aa51 Mon Sep 17 00:00:00 2001 From: AliceRoselia <63040919+AliceRoselia@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:18:29 +0100 Subject: [PATCH 1217/1309] Simplify indexing Removes 1 function (from_to) from the Move type, change them to simpler raw. Remove the "and" use in the correction history structure calculation. Adjust the indexing. Passed simplification STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 42880 W: 11327 L: 11113 D: 20440 Ptnml(0-2): 161, 4852, 11212, 5042, 173 https://tests.stockfishchess.org/tests/view/690593b8ea4b268f1fac1e8a Passed simplification LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 43560 W: 11276 L: 11079 D: 21205 Ptnml(0-2): 17, 4679, 12197, 4864, 23 https://tests.stockfishchess.org/tests/view/6906f819ea4b268f1fac216b closes https://github.com/official-stockfish/Stockfish/pull/6391 Bench: 2523092 --- src/history.h | 27 +++++++++++---------------- src/movepick.cpp | 8 ++++---- src/search.cpp | 12 ++++++------ src/types.h | 2 -- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/history.h b/src/history.h index 445d541818e..a605ae4175b 100644 --- a/src/history.h +++ b/src/history.h @@ -33,32 +33,28 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 +constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits::max() + 1; constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, +static_assert((UINT_16_HISTORY_SIZE & (UINT_16_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); inline int pawn_history_index(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } -inline int pawn_correction_history_index(const Position& pos) { - return pos.pawn_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t pawn_correction_history_index(const Position& pos) { return pos.pawn_key(); } -inline int minor_piece_index(const Position& pos) { - return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t minor_piece_index(const Position& pos) { return pos.minor_piece_key(); } template -inline int non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); +inline uint16_t non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c); } // StatsEntry is the container of various numerical statistics. We use a class @@ -102,12 +98,11 @@ using Stats = MultiArray, Sizes...>; // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, // see https://www.chessprogramming.org/Butterfly_Boards -using ButterflyHistory = Stats; +using ButterflyHistory = Stats; // LowPlyHistory is addressed by play and move's from and to squares, used // to improve move ordering near the root -using LowPlyHistory = - Stats; +using LowPlyHistory = Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; @@ -139,7 +134,7 @@ namespace Detail { template struct CorrHistTypedef { - using type = Stats; + using type = Stats; }; template<> @@ -155,7 +150,7 @@ struct CorrHistTypedef { template<> struct CorrHistTypedef { using type = - Stats; + Stats; }; } diff --git a/src/movepick.cpp b/src/movepick.cpp index 0b18cf56554..7de11fa1f11 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -158,7 +158,7 @@ ExtMove* MovePicker::score(MoveList& ml) { else if constexpr (Type == QUIETS) { // histories - m.value = 2 * (*mainHistory)[us][m.from_to()]; + m.value = 2 * (*mainHistory)[us][m.raw()]; m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -176,7 +176,7 @@ ExtMove* MovePicker::score(MoveList& ml) { if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); + m.value += 8 * (*lowPlyHistory)[ply][m.raw()] / (1 + ply); } else // Type == EVASIONS @@ -185,9 +185,9 @@ ExtMove* MovePicker::score(MoveList& ml) { m.value = PieceValue[capturedPiece] + (1 << 28); else { - m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; + m.value = (*mainHistory)[us][m.raw()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += (*lowPlyHistory)[ply][m.from_to()]; + m.value += (*lowPlyHistory)[ply][m.raw()]; } } } diff --git a/src/search.cpp b/src/search.cpp index b88c428e2e4..7404b939cf1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -824,7 +824,7 @@ Value Search::Worker::search( if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << evalDiff * 9; + mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; @@ -1066,7 +1066,7 @@ Value Search::Worker::search( if (history < -4312 * depth) continue; - history += 76 * mainHistory[us][move.from_to()] / 32; + history += 76 * mainHistory[us][move.raw()] / 32; // (*Scaler): Generally, a lower divisor scales well lmrDepth += history / 3220; @@ -1196,7 +1196,7 @@ Value Search::Worker::search( ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else - ss->statScore = 2 * mainHistory[us][move.from_to()] + ss->statScore = 2 * mainHistory[us][move.raw()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()]; @@ -1414,7 +1414,7 @@ Value Search::Worker::search( update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, scaledBonus * 400 / 32768); - mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 220 / 32768; + mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 220 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] @@ -1862,10 +1862,10 @@ void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { Color us = pos.side_to_move(); - workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort + workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 761 / 1024; + workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 761 / 1024; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); diff --git a/src/types.h b/src/types.h index 46aa16a030c..a2171b638bd 100644 --- a/src/types.h +++ b/src/types.h @@ -448,8 +448,6 @@ class Move { return Square(data & 0x3F); } - constexpr int from_to() const { return data & 0xFFF; } - constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } From bd82b9e01f9e5280b5e3771891e4c471d16cfc08 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:19:15 -0800 Subject: [PATCH 1218/1309] Cleanup, fix style issues use naming convention for variables, functions, constants closes https://github.com/official-stockfish/Stockfish/pull/6416 no functional change --- src/nnue/nnue_accumulator.cpp | 37 ++++++++++++++++---------------- src/position.cpp | 40 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 47f09afce57..fa059463e57 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -636,25 +636,26 @@ void update_accumulator_incremental( (target_state.template acc()).computed[Perspective] = true; } -Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) { +Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { #if defined(USE_AVX512) || defined(USE_AVX2) static_assert(sizeof(Piece) == 1); - Bitboard same_bb = 0; + Bitboard sameBB = 0; + for (int i = 0; i < 64; i += 32) { - const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(old + i)); - const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(new_ + i)); - const __m256i cmp_equal = _mm256_cmpeq_epi8(old_v, new_v); - const std::uint32_t equal_mask = _mm256_movemask_epi8(cmp_equal); - same_bb |= static_cast(equal_mask) << i; + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(oldPieces + i)); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(newPieces + i)); + const __m256i cmpEqual = _mm256_cmpeq_epi8(old_v, new_v); + const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual); + sameBB |= static_cast(equalMask) << i; } - return ~same_bb; + return ~sameBB; #else Bitboard changed = 0; + for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq) - { - changed |= static_cast(old[sq] != new_[sq]) << sq; - } + changed |= static_cast(oldPieces[sq] != newPieces[sq]) << sq; + return changed; #endif } @@ -671,18 +672,18 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat auto& entry = cache[ksq][Perspective]; PSQFeatureSet::IndexList removed, added; - const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array().data()); - Bitboard removed_bb = changed_bb & entry.pieceBB; - Bitboard added_bb = changed_bb & pos.pieces(); + const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); + Bitboard removedBB = changedBB & entry.pieceBB; + Bitboard addedBB = changedBB & pos.pieces(); - while (removed_bb) + while (removedBB) { - Square sq = pop_lsb(removed_bb); + Square sq = pop_lsb(removedBB); removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); } - while (added_bb) + while (addedBB) { - Square sq = pop_lsb(added_bb); + Square sq = pop_lsb(addedBB); added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); } diff --git a/src/position.cpp b/src/position.cpp index c34ceb400bf..8993c240644 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1096,13 +1096,13 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) while (threatened) { - Square threatened_sq = pop_lsb(threatened); - Piece threatened_pc = piece_on(threatened_sq); + Square threatenedSq = pop_lsb(threatened); + Piece threatenedPc = piece_on(threatenedSq); - assert(threatened_sq != s); - assert(threatened_pc); + assert(threatenedSq != s); + assert(threatenedPc); - add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); + add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); } Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); @@ -1111,30 +1111,30 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { while (sliders) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); + Square sliderSq = pop_lsb(sliders); + Piece slider = piece_on(sliderSq); - const Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; + const Bitboard ray = RayPassBB[sliderSq][s] & ~BetweenBB[sliderSq][s]; const Bitboard discovered = ray & qAttacks & occupied; assert(!more_than_one(discovered)); if (discovered) { - const Square threatened_sq = lsb(discovered); - const Piece threatened_pc = piece_on(threatened_sq); - add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + const Square threatenedSq = lsb(discovered); + const Piece threatenedPc = piece_on(threatenedSq); + add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); } - add_dirty_threat(dts, slider, pc, slider_sq, s); + add_dirty_threat(dts, slider, pc, sliderSq, s); } } else { while (sliders) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); - add_dirty_threat(dts, slider, pc, slider_sq, s); + Square sliderSq = pop_lsb(sliders); + Piece slider = piece_on(sliderSq); + add_dirty_threat(dts, slider, pc, sliderSq, s); } } @@ -1144,13 +1144,13 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) while (incoming_threats) { - Square src_sq = pop_lsb(incoming_threats); - Piece src_pc = piece_on(src_sq); + Square srcSq = pop_lsb(incoming_threats); + Piece srcPc = piece_on(srcSq); - assert(src_sq != s); - assert(src_pc != NO_PIECE); + assert(srcSq != s); + assert(srcPc != NO_PIECE); - add_dirty_threat(dts, src_pc, pc, src_sq, s); + add_dirty_threat(dts, srcPc, pc, srcSq, s); } } From a191791f46735817e76d1ba29ec81a5304e16430 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 13 Nov 2025 22:53:48 -0500 Subject: [PATCH 1219/1309] Clean up code and comments closes https://github.com/official-stockfish/Stockfish/pull/6416 No functional change --- src/search.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7404b939cf1..e4ef2d42e77 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -834,9 +834,12 @@ Value Search::Worker::search( // bigger than the previous static evaluation at our turn (if we were in // check at our previous move we go back until we weren't in check) and is // false otherwise. The improving flag is used in various pruning heuristics. + // Similarly, opponentWorsening is true if our static evaluation is better + // for us than at the last ply. improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; + // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) @@ -856,7 +859,7 @@ Value Search::Worker::search( return futilityMult * d // - 2094 * improving * futilityMult / 1024 // - - 1324 * opponentWorsening * futilityMult / 4096 // + - 331 * opponentWorsening * futilityMult / 1024 // + std::abs(correctionValue) / 158105; }; @@ -904,7 +907,7 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. - // (*Scaler) Especially if they make IIR less aggressive. + // (*Scaler) Making IIR more aggressive scales poorly. if (!allNode && depth >= 6 && !ttData.move && priorReduction <= 3) depth--; @@ -1018,12 +1021,11 @@ Value Search::Worker::search( Depth r = reduction(improving, depth, moveCount, delta); // Increase reduction for ttPv nodes (*Scaler) - // Smaller or even negative value is better for short time controls - // Bigger value is better for long time controls + // Larger values scale well if (ss->ttPv) r += 946; - // Step 14. Pruning at shallow depth. + // Step 14. Pruning at shallow depths. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { @@ -1068,7 +1070,7 @@ Value Search::Worker::search( history += 76 * mainHistory[us][move.raw()] / 32; - // (*Scaler): Generally, a lower divisor scales well + // (*Scaler): Generally, lower divisors scales well lmrDepth += history / 3220; Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth @@ -1076,7 +1078,7 @@ Value Search::Worker::search( // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning - // scales well with respect to time and threads + // scales well if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) @@ -1218,8 +1220,7 @@ Value Search::Worker::search( ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high - // (*Scaler) Usually doing more shallower searches - // doesn't scale well to longer TCs + // (*Scaler) Shallower searches here don't scale well if (value > alpha) { // Adjust full-depth search based on LMR results - if the result was @@ -1347,7 +1348,7 @@ Value Search::Worker::search( if (value >= beta) { - // (*Scaler) Especially if they make cutoffCnt increment more often. + // (*Scaler) Infrequent and small updates scale well ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; @@ -1450,10 +1451,9 @@ Value Search::Worker::search( // Adjust correction history if the best move is not a capture // and the error direction matches whether we are above/below bounds. if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) - && (bestValue < ss->staticEval) == !bestMove) + && (bestValue > ss->staticEval) == bool(bestMove)) { - auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth - / (8 + 2 * (bestValue > ss->staticEval)), + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (bestMove ? 10 : 8), -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); update_correction_history(pos, ss, *this, bonus); } From db824e26bebd08c70d1ccfb03f2c4ef06af82fef Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:46:47 -0800 Subject: [PATCH 1220/1309] Pass accumulator caches by reference closes https://github.com/official-stockfish/Stockfish/pull/6416 No functional change --- src/evaluate.cpp | 8 ++++---- src/nnue/network.cpp | 4 ++-- src/nnue/network.h | 4 ++-- src/nnue/nnue_feature_transformer.h | 4 ++-- src/nnue/nnue_misc.cpp | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 23bc70d0ed4..4bbbcaac600 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,15 +59,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) - : networks.big.evaluate(pos, accumulators, &caches.big); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small) + : networks.big.evaluate(pos, accumulators, caches.big); Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (std::abs(nnue) < 236)) { - std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); nnue = (125 * psqt + 131 * positional) / 128; smallNet = false; } @@ -107,7 +107,7 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches->big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches->big); Value v = psqt + positional; v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index b91763f061c..a4d464df0fc 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -172,7 +172,7 @@ template NetworkOutput Network::evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; @@ -234,7 +234,7 @@ template NnueEvalTrace Network::trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; diff --git a/src/nnue/network.h b/src/nnue/network.h index c701ac6f5a8..ba8f469f7cd 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -76,13 +76,13 @@ class Network { NetworkOutput evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; private: void load_user_net(const std::string&, const std::string&); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 5ad2d337175..1f328fff485 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -264,12 +264,12 @@ class FeatureTransformer { // Convert input features std::int32_t transform(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache, + AccumulatorCaches::Cache& cache, OutputType* output, int bucket) const { using namespace SIMD; - accumulatorStack.evaluate(pos, *this, *cache); + accumulatorStack.evaluate(pos, *this, cache); const auto& accumulatorState = accumulatorStack.latest(); const auto& threatAccumulatorState = accumulatorStack.latest(); diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 957e3453f19..220140e5e08 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -124,7 +124,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -140,7 +140,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat pos.remove_piece(sq); accumulators->reset(); - std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; @@ -157,7 +157,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << '\n'; accumulators->reset(); - auto t = networks.big.trace_evaluate(pos, *accumulators, &caches.big); + auto t = networks.big.trace_evaluate(pos, *accumulators, caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl From 4b71d8e20293bdc5db5dbb07ae75f3ef65497642 Mon Sep 17 00:00:00 2001 From: Robert Nurnberg Date: Fri, 14 Nov 2025 10:07:14 +0100 Subject: [PATCH 1221/1309] Allow time checking after each DTZ probe This PR allows for time checking within Tablebases::rank_root_moves(). The principal application for now is syzygy_extend_pv(). In the past, Stockfish suffered some time losses at e.g. TCEC when the HDD with the DTZ tables was too slow for the chosen move overhead setting, see #5894. ``` > ./fastchess -engine name="patch" cmd=./stockfish.patch -engine name="master" cmd=./stockfish.master -each tc=10+0.1 option.SyzygyPath=/disk1/syzygy/3-4-5-6/WDL:/disk2/syzygy/3-4-5-6/DTZ -draw movenumber=34 movecount=8 score=20 -openings file=UHO_Lichess_4852_v1.epd format=epd -rounds 50 -repeat -concurrency 16 ... Results of patch vs master (10+0.1, 1t, 16MB, UHO_Lichess_4852_v1.epd): Elo: 59.64 +/- 35.35, nElo: 117.61 +/- 68.10 LOS: 99.96 %, DrawRatio: 56.00 %, PairsRatio: 4.50 Games: 100, Wins: 34, Losses: 17, Draws: 49, Points: 58.5 (58.50 %) Ptnml(0-2): [0, 4, 28, 15, 3], WL/DD Ratio: 0.87 -------------------------------------------------- Player: master Timeouts: 19 Crashed: 0 Finished match ``` closes https://github.com/official-stockfish/Stockfish/pull/6422 No functional change --- src/search.cpp | 12 +++++++----- src/syzygy/tbprobe.cpp | 27 +++++++++++++++------------ src/syzygy/tbprobe.h | 17 ++++++++++++----- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e4ef2d42e77..9fb7b702f35 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -857,8 +857,8 @@ Value Search::Worker::search( auto futility_margin = [&](Depth d) { Value futilityMult = 91 - 21 * !ss->ttHit; - return futilityMult * d // - - 2094 * improving * futilityMult / 1024 // + return futilityMult * d // + - 2094 * improving * futilityMult / 1024 // - 331 * opponentWorsening * futilityMult / 1024 // + std::abs(correctionValue) / 158105; }; @@ -1980,8 +1980,9 @@ void syzygy_extend_pv(const OptionsMap& options, for (const auto& m : MoveList(pos)) legalMoves.emplace_back(m); - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves); - RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, false, time_abort); + RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); if (legalMoves[0].tbRank != rm.tbRank) break; @@ -2040,7 +2041,8 @@ void syzygy_extend_pv(const OptionsMap& options, [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); // The winning side tries to minimize DTZ, the losing side maximizes it - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, true, time_abort); // If DTZ is not available we might not find a mate, so we bail out if (!config.rootInTB || config.cardinality > 0) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 657866ba4d1..c8ff60739a3 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1594,10 +1594,11 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, - Search::RootMoves& rootMoves, - bool rule50, - bool rankDTZ) { +bool Tablebases::root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort) { ProbeState result = OK; StateInfo st; @@ -1642,7 +1643,7 @@ bool Tablebases::root_probe(Position& pos, pos.undo_move(m.pv[0]); - if (result == FAIL) + if (time_abort() || result == FAIL) return false; // Better moves are ranked higher. Certain wins are ranked equally. @@ -1707,10 +1708,11 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo return true; } -Config Tablebases::rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ) { +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ, + const std::function& time_abort) { Config config; if (rootMoves.empty()) @@ -1733,10 +1735,11 @@ Config Tablebases::rank_root_moves(const OptionsMap& options, if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { - // Rank moves using DTZ tables - config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ); + // Rank moves using DTZ tables, bail out if time_abort flags zeitnot + config.rootInTB = + root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort); - if (!config.rootInTB) + if (!config.rootInTB && !time_abort()) { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index c34338fe3c9..4a6c3b763a4 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,6 +19,7 @@ #ifndef TBPROBE_H #define TBPROBE_H +#include #include #include @@ -66,12 +67,18 @@ extern int MaxCardinality; void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ); +bool root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); -Config rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ = false); +Config rank_root_moves( + const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ = false, + const std::function& time_abort = []() { return false; }); } // namespace Stockfish::Tablebases From 1d504b927fde7c3227a0c32f26fd49810c0fcd55 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:57:58 -0800 Subject: [PATCH 1222/1309] Fix undefined behavior C++ standard does not define `uint8_t` as an allowed pointer alias type. Despite `uint8_t` being `unsigned char` on most platforms, this is not enforced by the standard. https://eel.is/c++draft/basic.lval#11 https://stackoverflow.com/a/16138470 closes https://github.com/official-stockfish/Stockfish/pull/6420 No functional change --- src/nnue/nnue_accumulator.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 341960b3020..c0a912f58c3 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -77,10 +77,9 @@ struct AccumulatorCaches { // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top void clear(const std::array& biases) { - accumulation = biases; - std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, - sizeof(Entry) - offsetof(Entry, psqtAccumulation)); + std::memset(reinterpret_cast(this) + offsetof(Entry, psqtAccumulation), + 0, sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; From b083049fe0c3671d20d195aa8dea72687cd6e44c Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Mon, 17 Nov 2025 13:24:05 +0100 Subject: [PATCH 1223/1309] Use non-locking operations for nodes count and tbHits fetch_add and friends must produce a locking instruction because they guarantee consistency in the presence of multiple writers. Because only one thread ever writes to nodes and tbHits, we may use relaxed load and store to emit regular loads and stores (on both x86-64 and arm64), while avoiding undefined behavior and miscounting nodes. Credit must go to Arseniy Surkov of Reckless (codedeliveryservice) for uncovering this performance gotcha. failed yellow as a gainer on fishtest: LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 219616 W: 57165 L: 57105 D: 105346 Ptnml(0-2): 732, 24277, 59720, 24357, 722 https://tests.stockfishchess.org/tests/view/69086abfea4b268f1fac2809 potential small speedup on large core system: ``` ==== master ==== 1 Nodes/second : 294269736 2 Nodes/second : 295217629 Average (over 2): 294743682 ==== patch ==== 1 Nodes/second : 299003603 2 Nodes/second : 298111320 Average (over 2): 298557461 ``` closes https://github.com/official-stockfish/Stockfish/pull/6392 No functional change --- src/search.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9fb7b702f35..53ff9f5d9cc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -529,7 +529,8 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac void Search::Worker::do_move( Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { bool capture = pos.capture_stage(move); - nodes.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); @@ -749,7 +750,8 @@ Value Search::Worker::search( if (err != TB::ProbeState::FAIL) { - tbHits.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); int drawScore = tbConfig.useRule50 ? 1 : 0; From 2084d94266f76f1ab5631f32708916a4d5cca246 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Thu, 13 Nov 2025 11:45:45 -0800 Subject: [PATCH 1224/1309] inline make_index() and avoid templating perspective combination of two patches. pass perspective as argument passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 52832 W: 13725 L: 13528 D: 25579 Ptnml(0-2): 154, 5756, 14412, 5927, 167 https://tests.stockfishchess.org/tests/view/69162e307ca8781852331c6a inline make_index passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 68768 W: 17786 L: 17607 D: 33375 Ptnml(0-2): 187, 7591, 18694, 7680, 232 https://tests.stockfishchess.org/tests/view/6916859e7ca8781852331d36 closes https://github.com/official-stockfish/Stockfish/pull/6429 No functional change --- src/misc.h | 9 + src/nnue/features/full_threats.cpp | 113 +++++-------- src/nnue/features/full_threats.h | 15 +- src/nnue/features/half_ka_v2_hm.cpp | 47 ++--- src/nnue/features/half_ka_v2_hm.h | 18 +- src/nnue/nnue_accumulator.cpp | 254 +++++++++++++++------------- src/nnue/nnue_accumulator.h | 19 ++- 7 files changed, 231 insertions(+), 244 deletions(-) diff --git a/src/misc.h b/src/misc.h index fce6f17dfac..66c03b806b1 100644 --- a/src/misc.h +++ b/src/misc.h @@ -412,6 +412,15 @@ void move_to_front(std::vector& vec, Predicate pred) { } } +#if defined(__GNUC__) + #define sf_always_inline __attribute__((always_inline)) +#elif defined(__MSVC) + #define sf_always_inline __forceinline +#else + // do nothign for other compilers + #define sf_always_inline +#endif + #if defined(__GNUC__) && !defined(__clang__) #if __GNUC__ >= 13 #define sf_assume(cond) __attribute__((assume(cond))) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 994d2160c05..122478dc694 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -58,35 +58,6 @@ constexpr std::array AllPieces = { PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] -namespace { - -template -IndexType make_index_with_orientation( - Piece attacker, Square from, Square to, Piece attacked, int orientation) { - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); - - if constexpr (Perspective == BLACK) - { - attacker = ~attacker; - attacked = ~attacked; - } - - const auto piecePairData = index_lut1[attacker][attacked]; - - const bool less_than = static_cast(from) < static_cast(to); - if ((piecePairData.excluded_pair_info() + less_than) & 2) - return FullThreats::Dimensions; - - const IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != FullThreats::Dimensions); - return index; -} - -} // namespace - static void init_index_luts() { for (Piece attacker : AllPieces) { @@ -155,27 +126,49 @@ void init_threat_offsets() { } // Index of a feature for a given king position and another piece on some square -template -IndexType -FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - return make_index_with_orientation(attacker, from, to, attacked, - OrientTBL[Perspective][ksq]); +inline sf_always_inline +IndexType FullThreats::make_index(Color perspective, + Piece attacker, + Square from, + Square to, + Piece attacked, + Square ksq) { + const int orientation = OrientTBL[perspective][ksq]; + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); + + std::int8_t swap = 8 * perspective; + attacker = Piece(attacker ^ swap); + attacked = Piece(attacked ^ swap); + + const auto piecePairData = index_lut1[attacker][attacked]; + + const bool less_than = static_cast(from) < static_cast(to); + if ((piecePairData.excluded_pair_info() + less_than) & 2) + return FullThreats::Dimensions; + + const IndexType index = piecePairData.feature_index_base() + offsets[attacker][from] + + index_lut2[attacker][from][to]; + + sf_assume(index != FullThreats::Dimensions); + return index; } // Get a list of indices for active features in ascending order -template -void FullThreats::append_active_indices(const Position& pos, IndexList& active) { + +void FullThreats::append_active_indices(Color perspective, + const Position& pos, + IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(Perspective); - const int orientation = OrientTBL[Perspective][ksq]; + Square ksq = pos.square(perspective); Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { for (PieceType pt = PAWN; pt <= KING; ++pt) { - Color c = order[Perspective][color]; + Color c = order[perspective][color]; Piece attacker = make_piece(c, pt); Bitboard bb = pos.pieces(c, pt); @@ -193,8 +186,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -205,8 +197,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -223,8 +214,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index( + perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -235,23 +226,15 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) } } -// Explicit template instantiations -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); - // Get a list of indices for recently changed features -template -void FullThreats::append_changed_indices(Square ksq, + +void FullThreats::append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, FusedUpdateData* fusedData, bool first) { - const int orientation = OrientTBL[Perspective][ksq]; for (const auto& dirty : diff.list) { @@ -292,28 +275,14 @@ void FullThreats::append_changed_indices(Square ksq, } } - const IndexType index = - make_index_with_orientation(attacker, from, to, attacked, orientation); + const IndexType index = make_index(perspective, + attacker, from, to, attacked, ksq); if (index < Dimensions) (add ? added : removed).push_back(index); } } -// Explicit template instantiations -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); - bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { return perspective == diff.us && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index 458b04dd189..d5c91d8c226 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -89,16 +89,19 @@ class FullThreats { using IndexList = ValueList; using DiffType = DirtyThreats; - template - static IndexType make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); + static IndexType make_index(Color perspective, + Piece attkr, + Square from, + Square to, + Piece attkd, + Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - template - static void append_changed_indices(Square ksq, + static void append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index f652ba0b8bb..5a6f610cf2d 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -28,58 +28,45 @@ namespace Stockfish::Eval::NNUE::Features { // Index of a feature for a given king position and another piece on some square -template -IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - const IndexType flip = 56 * Perspective; - return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[Perspective][pc] + +IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) { + const IndexType flip = 56 * perspective; + return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[perspective][pc] + KingBuckets[int(ksq) ^ flip]; } // Get a list of indices for active features -template -void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { - Square ksq = pos.square(Perspective); + +void HalfKAv2_hm::append_active_indices(Color perspective, + const Position& pos, + IndexList& active) { + Square ksq = pos.square(perspective); Bitboard bb = pos.pieces(); while (bb) { Square s = pop_lsb(bb); - active.push_back(make_index(s, pos.piece_on(s), ksq)); + active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); } } -// Explicit template instantiations -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); - // Get a list of indices for recently changed features -template -void HalfKAv2_hm::append_changed_indices(Square ksq, + +void HalfKAv2_hm::append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) { - removed.push_back(make_index(diff.from, diff.pc, ksq)); + removed.push_back(make_index(perspective, diff.from, diff.pc, ksq)); if (diff.to != SQ_NONE) - added.push_back(make_index(diff.to, diff.pc, ksq)); + added.push_back(make_index(perspective, diff.to, diff.pc, ksq)); if (diff.remove_sq != SQ_NONE) - removed.push_back(make_index(diff.remove_sq, diff.remove_pc, ksq)); + removed.push_back(make_index(perspective, diff.remove_sq, diff.remove_pc, ksq)); if (diff.add_sq != SQ_NONE) - added.push_back(make_index(diff.add_sq, diff.add_pc, ksq)); + added.push_back(make_index(perspective, diff.add_sq, diff.add_pc, ksq)); } -// Explicit template instantiations -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); - bool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) { return diff.pc == make_piece(perspective, KING); } diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index e695b273a19..e9448f838c2 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -107,17 +107,21 @@ class HalfKAv2_hm { using DiffType = DirtyPiece; // Index of a feature for a given king position and another piece on some square - template - static IndexType make_index(Square s, Piece pc, Square ksq); + + static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + + static void append_active_indices(Color perspective, + const Position& pos, + IndexList& active); // Get a list of indices for recently changed features - template - static void - append_changed_indices(Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); + static void append_changed_indices(Color perspective, + Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index fa059463e57..3d99b8063fd 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -40,39 +40,43 @@ using namespace SIMD; namespace { -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed); -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2); -template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed); -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState); } @@ -143,71 +147,73 @@ void AccumulatorStack::evaluate(const Position& pos, AccumulatorCaches::Cache& cache) noexcept { constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); } -template -void AccumulatorStack::evaluate_side(const Position& pos, +template +void AccumulatorStack::evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept { const auto last_usable_accum = - find_last_usable_accumulator(); + find_last_usable_accumulator(perspective); if ((accumulators()[last_usable_accum].template acc()) - .computed[Perspective]) - forward_update_incremental(pos, featureTransformer, - last_usable_accum); + .computed[perspective]) + forward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); else { if constexpr (std::is_same_v) - update_accumulator_refresh_cache(featureTransformer, pos, - mut_latest(), cache); + update_accumulator_refresh_cache(perspective, featureTransformer, pos, + mut_latest(), cache); else - update_threats_accumulator_full(featureTransformer, pos, - mut_latest()); + update_threats_accumulator_full(perspective, featureTransformer, pos, + mut_latest()); - backward_update_incremental(pos, featureTransformer, - last_usable_accum); + backward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); } } // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator // state just before a change that requires full refresh. -template -std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { +template +std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) const noexcept { for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) { - if ((accumulators()[curr_idx].template acc()).computed[Perspective]) + if ((accumulators()[curr_idx].template acc()).computed[perspective]) return curr_idx; - if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, Perspective)) + if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, perspective)) return curr_idx; } return 0; } -template +template void AccumulatorStack::forward_update_incremental( + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { assert(begin < accumulators().size()); - assert((accumulators()[begin].template acc()).computed[Perspective]); + assert((accumulators()[begin].template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::size_t next = begin + 1; next < size; next++) { @@ -223,9 +229,8 @@ void AccumulatorStack::forward_update_incremental( if (dp2.remove_sq != SQ_NONE && (accumulators[next].diff.threateningSqs & square_bb(dp2.remove_sq))) { - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1], - dp2); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1], dp2); next++; continue; } @@ -237,8 +242,8 @@ void AccumulatorStack::forward_update_incremental( { const Square captureSq = dp1.to; dp1.to = dp2.remove_sq = SQ_NONE; - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1]); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); dp1.to = dp2.remove_sq = captureSq; next++; continue; @@ -246,32 +251,33 @@ void AccumulatorStack::forward_update_incremental( } } - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next - 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next - 1]); } - assert((latest().acc()).computed[Perspective]); + assert((latest().acc()).computed[perspective]); } -template -void AccumulatorStack::backward_update_incremental( +template +void AccumulatorStack::backward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept { assert(end < accumulators().size()); assert(end < size); - assert((latest().template acc()).computed[Perspective]); + assert((latest().template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--) - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next + 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next + 1]); - assert((accumulators()[end].template acc()).computed[Perspective]); + assert((accumulators()[end].template acc()).computed[perspective]); } // Explicit template instantiations @@ -304,15 +310,18 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const.. vecIn[i], reinterpret_cast(rows)[i]...); } -template +template struct AccumulatorUpdateContext { + Color perspective; const FeatureTransformer& featureTransformer; const AccumulatorState& from; AccumulatorState& to; - AccumulatorUpdateContext(const FeatureTransformer& ft, + AccumulatorUpdateContext(Color persp, + const FeatureTransformer& ft, const AccumulatorState& accF, AccumulatorState& accT) noexcept : + perspective{persp}, featureTransformer{ft}, from{accF}, to{accT} {} @@ -330,22 +339,22 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.template acc()).accumulation[Perspective], - (to.template acc()).accumulation[Perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[perspective], + (to.template acc()).accumulation[perspective], to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[Perspective], - (to.template acc()).psqtAccumulation[Perspective], + (from.template acc()).psqtAccumulation[perspective], + (to.template acc()).psqtAccumulation[perspective], to_psqt_weight_vector(indices)...); } void apply(const typename FeatureSet::IndexList& added, const typename FeatureSet::IndexList& removed) { - const auto fromAcc = from.template acc().accumulation[Perspective]; - const auto toAcc = to.template acc().accumulation[Perspective]; + const auto fromAcc = from.template acc().accumulation[perspective]; + const auto toAcc = to.template acc().accumulation[perspective]; - const auto fromPsqtAcc = from.template acc().psqtAccumulation[Perspective]; - const auto toPsqtAcc = to.template acc().psqtAccumulation[Perspective]; + const auto fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; + const auto toPsqtAcc = to.template acc().psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; @@ -469,31 +478,33 @@ struct AccumulatorUpdateContext { } }; -template -auto make_accumulator_update_context(const FeatureTransformer& featureTransformer, +template +auto make_accumulator_update_context(Color perspective, + const FeatureTransformer& featureTransformer, const AccumulatorState& accumulatorFrom, AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - featureTransformer, accumulatorFrom, accumulatorTo}; + return AccumulatorUpdateContext{ + perspective, featureTransformer, accumulatorFrom, accumulatorTo}; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed) { - assert(computed.acc().computed[Perspective]); - assert(!middle_state.acc().computed[Perspective]); - assert(!target_state.acc().computed[Perspective]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); PSQFeatureSet::IndexList removed, added; - PSQFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added); // you can't capture a piece that was just involved in castling since the rook ends up // in a square that the king passed assert(added.size() < 2); - PSQFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); assert(added.size() == 1); assert(removed.size() == 2 || removed.size() == 3); @@ -505,7 +516,7 @@ void double_inc_update(const FeatureTransformer& f sf_assume(removed.size() == 2 || removed.size() == 3); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if (removed.size() == 2) { @@ -517,51 +528,52 @@ void double_inc_update(const FeatureTransformer& f removed[2]); } - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, +const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2) { - assert(computed.acc().computed[Perspective]); - assert(!middle_state.acc().computed[Perspective]); - assert(!target_state.acc().computed[Perspective]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); ThreatFeatureSet::FusedUpdateData fusedData; fusedData.dp2removed = dp2.remove_sq; ThreatFeatureSet::IndexList removed, added; - ThreatFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added, - &fusedData, true); - ThreatFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added, - &fusedData, false); + ThreatFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added, + &fusedData, true); + ThreatFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added, + &fusedData, false); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); updateContext.apply(added, removed); - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed) { - assert((computed.template acc()).computed[Perspective]); - assert(!(target_state.template acc()).computed[Perspective]); + assert((computed.template acc()).computed[perspective]); + assert(!(target_state.template acc()).computed[perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -571,29 +583,27 @@ void update_accumulator_incremental( // is 2, since we are incrementally updating one move at a time. typename FeatureSet::IndexList removed, added; if constexpr (Forward) - FeatureSet::template append_changed_indices(ksq, target_state.diff, removed, - added); + FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); else - FeatureSet::template append_changed_indices(ksq, computed.diff, added, - removed); + FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed); if (!added.size() && !removed.size()) { auto& targetAcc = target_state.template acc(); const auto& sourceAcc = computed.template acc(); - std::memcpy(targetAcc.accumulation[Perspective], sourceAcc.accumulation[Perspective], - sizeof(targetAcc.accumulation[Perspective])); - std::memcpy(targetAcc.psqtAccumulation[Perspective], - sourceAcc.psqtAccumulation[Perspective], - sizeof(targetAcc.psqtAccumulation[Perspective])); + std::memcpy(targetAcc.accumulation[perspective], sourceAcc.accumulation[perspective], + sizeof(targetAcc.accumulation[perspective])); + std::memcpy(targetAcc.psqtAccumulation[perspective], + sourceAcc.psqtAccumulation[perspective], + sizeof(targetAcc.psqtAccumulation[perspective])); - targetAcc.computed[Perspective] = true; + targetAcc.computed[perspective] = true; return; } auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if constexpr (std::is_same_v) updateContext.apply(added, removed); @@ -633,7 +643,7 @@ void update_accumulator_incremental( } } - (target_state.template acc()).computed[Perspective] = true; + (target_state.template acc()).computed[perspective] = true; } Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { @@ -660,16 +670,17 @@ Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPie #endif } -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache) { using Tiling [[maybe_unused]] = SIMDTiling; - const Square ksq = pos.square(Perspective); - auto& entry = cache[ksq][Perspective]; + const Square ksq = pos.square(perspective); + auto& entry = cache[ksq][perspective]; PSQFeatureSet::IndexList removed, added; const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); @@ -679,19 +690,19 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat while (removedBB) { Square sq = pop_lsb(removedBB); - removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); + removed.push_back(PSQFeatureSet::make_index(perspective, sq, entry.pieces[sq], ksq)); } while (addedBB) { Square sq = pop_lsb(addedBB); - added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); + added.push_back(PSQFeatureSet::make_index(perspective, sq, pos.piece_on(sq), ksq)); } entry.pieceBB = pos.pieces(); std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -700,7 +711,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) @@ -747,7 +758,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); auto* entryTilePsqt = reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); @@ -805,25 +816,26 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation.data(), + std::memcpy(accumulator.accumulation[perspective], entry.accumulation.data(), sizeof(BiasType) * Dimensions); - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation.data(), + std::memcpy(accumulator.psqtAccumulation[perspective], entry.psqtAccumulation.data(), sizeof(int32_t) * PSQTBuckets); #endif } -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState) { using Tiling [[maybe_unused]] = SIMDTiling; ThreatFeatureSet::IndexList active; - ThreatFeatureSet::append_active_indices(pos, active); + ThreatFeatureSet::append_active_indices(perspective, pos, active); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -832,7 +844,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_zero(); @@ -865,7 +877,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); @@ -888,21 +900,21 @@ void update_threats_accumulator_full(const FeatureTransformer& featu #else for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] = 0; + accumulator.accumulation[perspective][j] = 0; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; + accumulator.psqtAccumulation[perspective][k] = 0; for (const auto index : active) { const IndexType offset = Dimensions * index; for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] += + accumulator.accumulation[perspective][j] += featureTransformer.threatWeights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += + accumulator.psqtAccumulation[perspective][k] += featureTransformer.threatPsqtWeights[index * PSQTBuckets + k]; } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index c0a912f58c3..181412b4311 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -176,21 +176,24 @@ class AccumulatorStack { template [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; - template - void evaluate_side(const Position& pos, + template + void evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; - template - [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + template + [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept; - template - void forward_update_incremental(const Position& pos, + template + void forward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept; - template - void backward_update_incremental(const Position& pos, + template + void backward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; From 4b8fffe3b3cde2f351aff0afb0a0a5c33f376b40 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 15 Nov 2025 09:44:08 +0100 Subject: [PATCH 1225/1309] Silence warning for systems without shared memory support Fixes https://github.com/official-stockfish/Stockfish/issues/6425 closes https://github.com/official-stockfish/Stockfish/pull/6426 No functional change --- src/shm.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shm.h b/src/shm.h index ae542967653..b870afc2434 100644 --- a/src/shm.h +++ b/src/shm.h @@ -455,7 +455,8 @@ class SharedMemoryBackend { public: SharedMemoryBackend() = default; - SharedMemoryBackend(const std::string& shm_name, const T& value) {} + SharedMemoryBackend([[maybe_unused]] const std::string& shm_name, + [[maybe_unused]] const T& value) {} void* get() const { return nullptr; } From 563b4a9c4d649a78c3c4ee45cc779fb5caaed385 Mon Sep 17 00:00:00 2001 From: Andreas Matthies Date: Sat, 15 Nov 2025 16:46:55 +0100 Subject: [PATCH 1226/1309] Cleanup benchmark Avoid unnecessary consecutive ucinewgame commands in benchmark setup. Remove measuring time and nodes in speedtest warmup. closes https://github.com/official-stockfish/Stockfish/pull/6427 No functional change --- src/benchmark.cpp | 1 - src/uci.cpp | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 38cafaec705..136b4031e85 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -483,7 +483,6 @@ BenchmarkSetup setup_benchmark(std::istream& is) { float totalTime = 0; for (const auto& game : BenchmarkPositions) { - setup.commands.emplace_back("ucinewgame"); int ply = 1; for (int i = 0; i < static_cast(game.size()); ++i) { diff --git a/src/uci.cpp b/src/uci.cpp index 9b1dd865cab..5bd23582387 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -339,16 +339,9 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); - TimePoint elapsed = now(); - // Run with silenced network verification engine.go(limits); engine.wait_for_search_finished(); - - totalTime += now() - elapsed; - - nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); @@ -396,6 +389,7 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); + nodesSearched = 0; TimePoint elapsed = now(); // Run with silenced network verification @@ -407,7 +401,6 @@ void UCIEngine::benchmark(std::istream& args) { updateHashfullReadings(); nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); From a27fcd6274a8538bf6d07844c7f799d2a0401db6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 17 Nov 2025 13:57:53 +0100 Subject: [PATCH 1227/1309] Fix format No functional change --- src/nnue/features/full_threats.cpp | 40 ++++++++++++----------------- src/nnue/features/full_threats.h | 8 ++---- src/nnue/features/half_ka_v2_hm.cpp | 11 +++----- src/nnue/features/half_ka_v2_hm.h | 11 +++----- src/nnue/nnue_accumulator.cpp | 35 ++++++++++++------------- 5 files changed, 40 insertions(+), 65 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 122478dc694..9c818a8e003 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -126,20 +126,15 @@ void init_threat_offsets() { } // Index of a feature for a given king position and another piece on some square -inline sf_always_inline -IndexType FullThreats::make_index(Color perspective, - Piece attacker, - Square from, - Square to, - Piece attacked, - Square ksq) { +inline sf_always_inline IndexType FullThreats::make_index( + Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { const int orientation = OrientTBL[perspective][ksq]; - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); std::int8_t swap = 8 * perspective; - attacker = Piece(attacker ^ swap); - attacked = Piece(attacked ^ swap); + attacker = Piece(attacker ^ swap); + attacked = Piece(attacked ^ swap); const auto piecePairData = index_lut1[attacker][attacked]; @@ -147,8 +142,8 @@ IndexType FullThreats::make_index(Color perspective, if ((piecePairData.excluded_pair_info() + less_than) & 2) return FullThreats::Dimensions; - const IndexType index = piecePairData.feature_index_base() + offsets[attacker][from] - + index_lut2[attacker][from][to]; + const IndexType index = + piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; sf_assume(index != FullThreats::Dimensions); return index; @@ -156,13 +151,11 @@ IndexType FullThreats::make_index(Color perspective, // Get a list of indices for active features in ascending order -void FullThreats::append_active_indices(Color perspective, - const Position& pos, - IndexList& active) { +void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(perspective); - Bitboard occupied = pos.pieces(); + Square ksq = pos.square(perspective); + Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { @@ -186,7 +179,7 @@ void FullThreats::append_active_indices(Color perspective, Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -197,7 +190,7 @@ void FullThreats::append_active_indices(Color perspective, Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -214,8 +207,8 @@ void FullThreats::append_active_indices(Color perspective, { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = make_index( - perspective, attacker, from, to, attacked, ksq); + IndexType index = + make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -275,8 +268,7 @@ void FullThreats::append_changed_indices(Color perspective, } } - const IndexType index = make_index(perspective, - attacker, from, to, attacked, ksq); + const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) (add ? added : removed).push_back(index); diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index d5c91d8c226..bc8a1ce329c 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -89,12 +89,8 @@ class FullThreats { using IndexList = ValueList; using DiffType = DirtyThreats; - static IndexType make_index(Color perspective, - Piece attkr, - Square from, - Square to, - Piece attkd, - Square ksq); + static IndexType + make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq); // Get a list of indices for active features static void append_active_indices(Color perspective, const Position& pos, IndexList& active); diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5a6f610cf2d..56779ddcea7 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -37,9 +37,7 @@ IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square // Get a list of indices for active features -void HalfKAv2_hm::append_active_indices(Color perspective, - const Position& pos, - IndexList& active) { +void HalfKAv2_hm::append_active_indices(Color perspective, const Position& pos, IndexList& active) { Square ksq = pos.square(perspective); Bitboard bb = pos.pieces(); while (bb) @@ -51,11 +49,8 @@ void HalfKAv2_hm::append_active_indices(Color perspective, // Get a list of indices for recently changed features -void HalfKAv2_hm::append_changed_indices(Color perspective, - Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added) { +void HalfKAv2_hm::append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) { removed.push_back(make_index(perspective, diff.from, diff.pc, ksq)); if (diff.to != SQ_NONE) added.push_back(make_index(perspective, diff.to, diff.pc, ksq)); diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index e9448f838c2..c58a3246be8 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -112,16 +112,11 @@ class HalfKAv2_hm { // Get a list of indices for active features - static void append_active_indices(Color perspective, - const Position& pos, - IndexList& active); + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - static void append_changed_indices(Color perspective, - Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added); + static void append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 3d99b8063fd..3ed729ffbf7 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -41,7 +41,7 @@ using namespace SIMD; namespace { template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -49,7 +49,7 @@ void double_inc_update(Color p const AccumulatorState& computed); template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -57,9 +57,7 @@ void double_inc_update(Color p const AccumulatorState& computed, const DirtyPiece& dp2); -template +template void update_accumulator_incremental( Color perspective, const FeatureTransformer& featureTransformer, @@ -68,14 +66,14 @@ void update_accumulator_incremental( const AccumulatorState& computed); template -void update_accumulator_refresh_cache(Color perspective, +void update_accumulator_refresh_cache(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); template -void update_threats_accumulator_full(Color perspective, +void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState); @@ -205,7 +203,7 @@ std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) co template void AccumulatorStack::forward_update_incremental( - Color perspective, + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { @@ -260,7 +258,8 @@ void AccumulatorStack::forward_update_incremental( } template -void AccumulatorStack::backward_update_incremental(Color perspective, +void AccumulatorStack::backward_update_incremental( + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, @@ -479,16 +478,16 @@ struct AccumulatorUpdateContext { }; template -auto make_accumulator_update_context(Color perspective, +auto make_accumulator_update_context(Color perspective, const FeatureTransformer& featureTransformer, const AccumulatorState& accumulatorFrom, AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - perspective, featureTransformer, accumulatorFrom, accumulatorTo}; + return AccumulatorUpdateContext{perspective, featureTransformer, + accumulatorFrom, accumulatorTo}; } template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -532,8 +531,8 @@ void double_inc_update(Color p } template -void double_inc_update(Color perspective, -const FeatureTransformer& featureTransformer, +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, @@ -562,9 +561,7 @@ const FeatureTransformer& featureTransformer, target_state.acc().computed[perspective] = true; } -template +template void update_accumulator_incremental( Color perspective, const FeatureTransformer& featureTransformer, @@ -825,7 +822,7 @@ void update_accumulator_refresh_cache(Color pers } template -void update_threats_accumulator_full(Color perspective, +void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState) { From 61149ac9ee59b1d2ea96f9a5a36fa5e74bcf5359 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 12 Nov 2025 17:38:27 +0100 Subject: [PATCH 1228/1309] Update main net to nn-c0ae49f08b40.nnue Trained using the recipe https://github.com/vondele/nettest/blob/d39f72420504e474a716b9977c1380541a7b482e/threats.yaml fix CI check for negative evals. fix format passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 84032 W: 21876 L: 21490 D: 40666 Ptnml(0-2): 239, 9821, 21553, 10121, 282 https://tests.stockfishchess.org/tests/view/6914b81e7ca87818523318aa passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 126420 W: 32429 L: 31927 D: 62064 Ptnml(0-2): 56, 13748, 35118, 14214, 74 https://tests.stockfishchess.org/tests/view/6916c1277ca8781852331dd8 closes https://github.com/official-stockfish/Stockfish/pull/6431 Bench: 2513286 --- src/evaluate.h | 2 +- tests/instrumented.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index c8dc64ace9c..fc77843644c 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-49c1193b131c.nnue" +#define EvalFileDefaultNameBig "nn-c0ae49f08b40.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { diff --git a/tests/instrumented.py b/tests/instrumented.py index 23de446ef74..80831ce3ff8 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -67,7 +67,6 @@ def Stockfish(*args, **kwargs): class TestCLI(metaclass=OrderedClassMembers): - def beforeAll(self): pass @@ -141,7 +140,7 @@ def test_bench_128_threads_8_default_depth(self): def test_bench_128_threads_3_bench_tmp_epd_depth(self): self.stockfish = Stockfish( - f"bench 128 {get_threads()} 3 {os.path.join(PATH,'bench_tmp.epd')} depth".split( + f"bench 128 {get_threads()} 3 {os.path.join(PATH, 'bench_tmp.epd')} depth".split( " " ), True, @@ -167,7 +166,7 @@ def test_uci(self): def test_export_net_verify_nnue(self): current_path = os.path.abspath(os.getcwd()) self.stockfish = Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) assert self.stockfish.process.returncode == 0 @@ -254,7 +253,7 @@ def test_depth_5_with_callback(self): self.stockfish.send_command("go depth 5") def callback(output): - regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp -?\d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth") and not re.match(regex, output): assert False if output.startswith("bestmove"): @@ -274,7 +273,7 @@ def test_ucinewgame_and_go_depth_9(self): def callback(output): nonlocal depth - regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp -?\d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth"): if not re.match(regex, output): @@ -390,7 +389,7 @@ def test_fen_position_with_moves_with_mate_go_depth_and_searchmoves(self): def test_verify_nnue_network(self): current_path = os.path.abspath(os.getcwd()) Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) self.stockfish.send_command("setoption name EvalFile value verify.nnue") @@ -469,7 +468,7 @@ def test_syzygy_position_3(self): self.stockfish.send_command("go depth 5") def check_output(output): - if "score cp -20000" in output or "score mate" in output: + if "score cp -20000" in output or "score mate -" in output: return True self.stockfish.check_output(check_output) From 035cb146d430b601e94c13e3f2fefcc7688ec1d6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:30:59 -0800 Subject: [PATCH 1229/1309] Do more futility pruning closes https://github.com/official-stockfish/Stockfish/pull/6433 Bench: 2469519 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 53ff9f5d9cc..1c3875cdcb6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -857,7 +857,7 @@ Value Search::Worker::search( // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 91 - 21 * !ss->ttHit; + Value futilityMult = 81 - 21 * !ss->ttHit; return futilityMult * d // - 2094 * improving * futilityMult / 1024 // From d9fd516547849bd5ca2a05c491aadc66fc750a39 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 22 Nov 2025 00:17:02 -0500 Subject: [PATCH 1230/1309] Post-NNUEv10 tune Tune search parameters after the switch to NNUEv10. The change is neutral at STC but increases with the TC. The main changes are more aggressive corrections, futility pruning, and extensions. Failed STC LLR: -2.96 (-2.94,2.94) <0.00,2.00> Total: 108320 W: 27616 L: 27719 D: 52985 Ptnml(0-2): 332, 12833, 27884, 12828, 283 https://tests.stockfishchess.org/tests/view/69212e623b03dd3a060e6114 Passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 43272 W: 11117 L: 10788 D: 21367 Ptnml(0-2): 20, 4543, 12180, 4874, 19 https://tests.stockfishchess.org/tests/view/692130813b03dd3a060e6123 Passed VLTC LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 22714 W: 5840 L: 5581 D: 11293 Ptnml(0-2): 2, 2152, 6795, 2401, 7 https://tests.stockfishchess.org/tests/view/6921387b3b03dd3a060e6191 Passed VLTC SMP LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 11868 W: 3142 L: 2896 D: 5830 Ptnml(0-2): 0, 1007, 3676, 1249, 2 https://tests.stockfishchess.org/tests/view/69212e953b03dd3a060e611b closes https://github.com/official-stockfish/Stockfish/pull/6444 bench 2907929 --- src/evaluate.cpp | 12 ++-- src/search.cpp | 184 ++++++++++++++++++++++++----------------------- 2 files changed, 101 insertions(+), 95 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4bbbcaac600..d20843e854b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -65,7 +65,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (std::abs(nnue) < 236)) + if (smallNet && (std::abs(nnue) < 277)) { std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); nnue = (125 * psqt + 131 * positional) / 128; @@ -74,14 +74,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); - optimism += optimism * nnueComplexity / 468; - nnue -= nnue * nnueComplexity / 18000; + optimism += optimism * nnueComplexity / 476; + nnue -= nnue * nnueComplexity / 18236; - int material = 535 * pos.count() + pos.non_pawn_material(); - int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; + int material = 534 * pos.count() + pos.non_pawn_material(); + int v = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871; // Damp down the evaluation linearly when shuffling - v -= v * pos.rule50_count() / 212; + v -= v * pos.rule50_count() / 199; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/search.cpp b/src/search.cpp index 1c3875cdcb6..d2a90283cb6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -88,7 +88,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 8; - return 9536 * pcv + 8494 * micv + 10132 * (wnpcv + bnpcv) + 7156 * cntcv; + return 10347 * pcv + 8821 * micv + 11168 * (wnpcv + bnpcv) + 7841 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -104,10 +104,10 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - constexpr int nonPawnWeight = 165; + constexpr int nonPawnWeight = 178; workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 156 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] @@ -117,8 +117,8 @@ void update_correction_history(const Position& pos, { const Square to = m.to_sq(); const Piece pc = pos.piece_on(m.to_sq()); - (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 137 / 128; - (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 64 / 128; + (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 127 / 128; + (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 59 / 128; } } @@ -338,7 +338,7 @@ void Search::Worker::iterative_deepening() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 137 * avg / (std::abs(avg) + 91); + optimism[us] = 142 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -467,20 +467,23 @@ void Search::Worker::iterative_deepening() { uint64_t nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); - double fallingEval = - (11.325 + 2.115 * (mainThread->bestPreviousAverageScore - bestValue) - + 0.987 * (mainThread->iterValue[iterIdx] - bestValue)) - / 100.0; - fallingEval = std::clamp(fallingEval, 0.5688, 1.5698); + double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.93 * (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; + + fallingEval = std::clamp(fallingEval, 0.57, 1.70); // If the bestMove is stable over several iterations, reduce time accordingly - double k = 0.5189; - double center = lastBestMoveDepth + 11.57; - timeReduction = 0.723 + 0.79 / (1.104 + std::exp(-k * (completedDepth - center))); - double reduction = - (1.455 + mainThread->previousTimeReduction) / (2.2375 * timeReduction); - double bestMoveInstability = 1.04 + 1.8956 * totBestMoveChanges / threads.size(); - double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 92425 ? 0.666 : 1.0; + double k = 0.51; + double center = lastBestMoveDepth + 12.15; + + timeReduction = 0.66 + 0.85 / (0.98 + std::exp(-k * (completedDepth - center))); + + double reduction = (1.43 + mainThread->previousTimeReduction) / (2.28 * timeReduction); + + double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size(); + + double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 93337 ? 0.75 : 1.0; double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * highBestMoveEffort; @@ -502,7 +505,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.503; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.50; } mainThread->iterValue[iterIdx] = bestValue; @@ -582,7 +585,7 @@ void Search::Worker::clear() { h.fill(-529); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2809 / 128.0 * std::log(i)); + reductions[i] = int(2747 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -702,11 +705,12 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(130 * depth - 71, 1043)); + std::min(132 * depth - 72, 985)); + // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2142); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060); } // Partial workaround for the graph history interaction problem @@ -825,11 +829,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; + int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -209, 167) + 59; mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13; } // Set up the improving flag, which is true if current static evaluation is @@ -844,25 +848,25 @@ Value Search::Worker::search( // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 514 - 294 * depth * depth) + if (!PvNode && eval < alpha - 485 - 281 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 81 - 21 * !ss->ttHit; + Value futilityMult = 76 - 23 * !ss->ttHit; return futilityMult * d // - - 2094 * improving * futilityMult / 1024 // + - 2474 * improving * futilityMult / 1024 // - 331 * opponentWorsening * futilityMult / 1024 // - + std::abs(correctionValue) / 158105; + + std::abs(correctionValue) / 174665; }; if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta @@ -871,7 +875,7 @@ Value Search::Worker::search( } // Step 9. Null move search with verification search - if (cutNode && ss->staticEval >= beta - 18 * depth + 390 && !excludedMove + if (cutNode && ss->staticEval >= beta - 18 * depth + 350 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { assert((ss - 1)->currentMove != Move::null()); @@ -905,6 +909,7 @@ Value Search::Worker::search( } } + improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions @@ -916,7 +921,7 @@ Value Search::Worker::search( // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 224 - 64 * improving; + probCutBeta = beta + 235 - 63 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -926,7 +931,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 306, 0, depth); + Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 315, 0, depth); while ((move = mp.next_move()) != Move::none()) { @@ -1046,8 +1051,8 @@ Value Search::Worker::search( // Futility pruning for captures if (!givesCheck && lmrDepth < 7) { - Value futilityValue = ss->staticEval + 231 + 211 * lmrDepth - + PieceValue[capturedPiece] + 130 * captHist / 1024; + Value futilityValue = ss->staticEval + 232 + 217 * lmrDepth + + PieceValue[capturedPiece] + 131 * captHist / 1024; if (futilityValue <= alpha) continue; @@ -1055,7 +1060,7 @@ Value Search::Worker::search( // SEE based pruning for captures and checks // Avoid pruning sacrifices of our last piece for stalemate - int margin = std::max(157 * depth + captHist / 29, 0); + int margin = std::max(166 * depth + captHist / 29, 0); if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece]) && !pos.see_ge(move, -margin)) continue; @@ -1067,21 +1072,21 @@ Value Search::Worker::search( + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4312 * depth) + if (history < -4083 * depth) continue; - history += 76 * mainHistory[us][move.raw()] / 32; + history += 69 * mainHistory[us][move.raw()] / 32; // (*Scaler): Generally, lower divisors scales well - lmrDepth += history / 3220; + lmrDepth += history / 3208; - Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth - + 90 * (ss->staticEval > alpha); + Value futilityValue = ss->staticEval + 42 + 161 * !bestMove + 127 * lmrDepth + + 85 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning // scales well - if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) @@ -1092,7 +1097,7 @@ Value Search::Worker::search( lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) continue; } } @@ -1107,12 +1112,11 @@ Value Search::Worker::search( // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. - if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (56 + 81 * (ss->ttPv && !PvNode)) * depth / 60; + Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1121,11 +1125,11 @@ Value Search::Worker::search( if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 229958; - int doubleMargin = -4 + 198 * PvNode - 212 * !ttCapture - corrValAdj - - 921 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 45; - int tripleMargin = 76 + 308 * PvNode - 250 * !ttCapture + 92 * ss->ttPv - corrValAdj - - (ss->ply * 2 > rootDepth * 3) * 52; + int corrValAdj = std::abs(correctionValue) / 230673; + int doubleMargin = -4 + 199 * PvNode - 201 * !ttCapture - corrValAdj + - 897 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 42; + int tripleMargin = 73 + 302 * PvNode - 248 * !ttCapture + 90 * ss->ttPv - corrValAdj + - (ss->ply * 2 > rootDepth * 3) * 50; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1171,33 +1175,32 @@ Value Search::Worker::search( // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2618 + PvNode * 991 + (ttData.value > alpha) * 903 - + (ttData.depth >= depth) * (978 + cutNode * 1051); - + r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922 + + (ttData.depth >= depth) * (934 + cutNode * 1011); // These reduction adjustments have no proven non-linear scaling - r += 843; // Base reduction offset to compensate for other tweaks - r -= moveCount * 66; - r -= std::abs(correctionValue) / 30450; + r += 714; // Base reduction offset to compensate for other tweaks + r -= moveCount * 73; + r -= std::abs(correctionValue) / 30370; // Increase reduction for cut nodes if (cutNode) - r += 3094 + 1056 * !ttData.move; + r += 3372 + 997 * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1415; + r += 1119; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1051 + allNode * 814; + r += 991 + allNode * 923; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2018; + r -= 2151; if (capture) - ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + ss->statScore = 868 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else ss->statScore = 2 * mainHistory[us][move.raw()] @@ -1205,7 +1208,8 @@ Value Search::Worker::search( + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 794 / 8192; + r -= ss->statScore * 850 / 8192; + // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1245,13 +1249,12 @@ Value Search::Worker::search( { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1118; + r += 1140; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3212) - (r > 4784 && newDepth > 2), !cutNode); + newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode); } - // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. if (PvNode && (moveCount == 1 || value > alpha)) @@ -1403,25 +1406,25 @@ Value Search::Worker::search( // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -228; - bonusScale -= (ss - 1)->statScore / 104; - bonusScale += std::min(63 * depth, 508); + int bonusScale = -215; + bonusScale -= (ss - 1)->statScore / 100; + bonusScale += std::min(56 * depth, 489); bonusScale += 184 * ((ss - 1)->moveCount > 8); - bonusScale += 143 * (!ss->inCheck && bestValue <= ss->staticEval - 92); - bonusScale += 149 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 70); + bonusScale += 147 * (!ss->inCheck && bestValue <= ss->staticEval - 107); + bonusScale += 156 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 65); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(144 * depth - 92, 1365) * bonusScale; + const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 400 / 32768); + scaledBonus * 406 / 32768); - mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 220 / 32768; + mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1164 / 32768; + << scaledBonus * 1160 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1429,9 +1432,10 @@ Value Search::Worker::search( { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 964; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012; } + if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1579,9 +1583,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 352; + futilityBase = ss->staticEval + 351; } + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; @@ -1640,7 +1645,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -78)) + if (!pos.see_ge(move, -80)) continue; } @@ -1713,9 +1718,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 757 / rootDelta + !i * reductionScale * 218 / 512 + 1200; + return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182; } + // elapsed() returns the time elapsed since the search started. If the // 'nodestime' option is enabled, it will return the count of nodes searched // instead. This function is called to check whether the search should be @@ -1809,35 +1815,35 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(121 * depth - 77, 1633) + 375 * (bestMove == ttMove); - int malus = std::min(825 * depth - 196, 2159) - 16 * moveCount; + int bonus = std::min(116 * depth - 81, 1515) + 347 * (bestMove == ttMove); + int malus = std::min(848 * depth - 207, 2446) - 17 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 881 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 910 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1083 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1085 / 1024); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1482 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1395 / 1024; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 614 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 602 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1397 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1448 / 1024; } } @@ -1845,8 +1851,8 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - static constexpr std::array conthist_bonuses = { - {{1, 1157}, {2, 648}, {3, 288}, {4, 576}, {5, 140}, {6, 441}}}; + static std::array conthist_bonuses = { + {{1, 1133}, {2, 683}, {3, 312}, {4, 582}, {5, 149}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1867,13 +1873,13 @@ void update_quiet_histories( workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 761 / 1024; + workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 805 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 896 / 1024); int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << bonus * (bonus > 0 ? 850 : 550) / 1024; + << bonus * (bonus > 0 ? 905 : 505) / 1024; } } From 1132d893e09d6dae78a452b841375545aca9e03e Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:57:08 -0800 Subject: [PATCH 1231/1309] Simplify Accumulator Updates Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 146848 W: 37915 L: 37820 D: 71113 Ptnml(0-2): 415, 16159, 40186, 16244, 420 https://tests.stockfishchess.org/tests/view/691772217ca878185233202b closes https://github.com/official-stockfish/Stockfish/pull/6421 No functional change --- src/nnue/nnue_accumulator.cpp | 50 +++++++++++++++-------------------- src/nnue/nnue_accumulator.h | 8 +++--- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 3ed729ffbf7..e63a68cb756 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -18,7 +18,6 @@ #include "nnue_accumulator.h" -#include #include #include #include @@ -338,22 +337,23 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.template acc()).accumulation[perspective], - (to.template acc()).accumulation[perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[perspective].data(), + (to.template acc()).accumulation[perspective].data(), + to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[perspective], - (to.template acc()).psqtAccumulation[perspective], + (from.template acc()).psqtAccumulation[perspective].data(), + (to.template acc()).psqtAccumulation[perspective].data(), to_psqt_weight_vector(indices)...); } void apply(const typename FeatureSet::IndexList& added, const typename FeatureSet::IndexList& removed) { - const auto fromAcc = from.template acc().accumulation[perspective]; - const auto toAcc = to.template acc().accumulation[perspective]; + const auto& fromAcc = from.template acc().accumulation[perspective]; + auto& toAcc = to.template acc().accumulation[perspective]; - const auto fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; - const auto toPsqtAcc = to.template acc().psqtAccumulation[perspective]; + const auto& fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; + auto& toPsqtAcc = to.template acc().psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; @@ -448,8 +448,8 @@ struct AccumulatorUpdateContext { #else - std::copy_n(fromAcc, Dimensions, toAcc); - std::copy_n(fromPsqtAcc, PSQTBuckets, toPsqtAcc); + toAcc = fromAcc; + toPsqtAcc = fromPsqtAcc; for (const auto index : removed) { @@ -589,13 +589,10 @@ void update_accumulator_incremental( auto& targetAcc = target_state.template acc(); const auto& sourceAcc = computed.template acc(); - std::memcpy(targetAcc.accumulation[perspective], sourceAcc.accumulation[perspective], - sizeof(targetAcc.accumulation[perspective])); - std::memcpy(targetAcc.psqtAccumulation[perspective], - sourceAcc.psqtAccumulation[perspective], - sizeof(targetAcc.psqtAccumulation[perspective])); + targetAcc.accumulation[perspective] = sourceAcc.accumulation[perspective]; + targetAcc.psqtAccumulation[perspective] = sourceAcc.psqtAccumulation[perspective]; + targetAcc.computed[perspective] = true; - targetAcc.computed[perspective] = true; return; } @@ -643,15 +640,16 @@ void update_accumulator_incremental( (target_state.template acc()).computed[perspective] = true; } -Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { +Bitboard get_changed_pieces(const std::array& oldPieces, + const std::array& newPieces) { #if defined(USE_AVX512) || defined(USE_AVX2) static_assert(sizeof(Piece) == 1); Bitboard sameBB = 0; for (int i = 0; i < 64; i += 32) { - const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(oldPieces + i)); - const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(newPieces + i)); + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(&oldPieces[i])); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(&newPieces[i])); const __m256i cmpEqual = _mm256_cmpeq_epi8(old_v, new_v); const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual); sameBB |= static_cast(equalMask) << i; @@ -680,7 +678,7 @@ void update_accumulator_refresh_cache(Color pers auto& entry = cache[ksq][perspective]; PSQFeatureSet::IndexList removed, added; - const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); + const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array()); Bitboard removedBB = changedBB & entry.pieceBB; Bitboard addedBB = changedBB & pos.pieces(); @@ -696,7 +694,7 @@ void update_accumulator_refresh_cache(Color pers } entry.pieceBB = pos.pieces(); - std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); + entry.pieces = pos.piece_array(); auto& accumulator = accumulatorState.acc(); accumulator.computed[perspective] = true; @@ -812,12 +810,8 @@ void update_accumulator_refresh_cache(Color pers // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - - std::memcpy(accumulator.accumulation[perspective], entry.accumulation.data(), - sizeof(BiasType) * Dimensions); - - std::memcpy(accumulator.psqtAccumulation[perspective], entry.psqtAccumulation.data(), - sizeof(int32_t) * PSQTBuckets); + accumulator.accumulation[perspective] = entry.accumulation; + accumulator.psqtAccumulation[perspective] = entry.psqtAccumulation; #endif } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 181412b4311..69ab4963269 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -46,9 +46,9 @@ class FeatureTransformer; // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[COLOR_NB][Size]; - std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - std::array computed = {}; + std::array, COLOR_NB> accumulation; + std::array, COLOR_NB> psqtAccumulation; + std::array computed = {}; }; @@ -71,7 +71,7 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Entry { std::array accumulation; std::array psqtAccumulation; - Piece pieces[SQUARE_NB]; + std::array pieces; Bitboard pieceBB; // To initialize a refresh entry, we set all its bitboards empty, From 93f2d14d95ca72ec2ec96c6e3de0b9a6121fe118 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:09:06 -0800 Subject: [PATCH 1232/1309] Simplify Incremental Updates Passed Non-regression STC: LLR: 3.03 (-2.94,2.94) <-1.75,0.25> Total: 277856 W: 71575 L: 71611 D: 134670 Ptnml(0-2): 842, 30719, 75836, 30695, 836 https://tests.stockfishchess.org/tests/view/69169dc17ca8781852331d76 Supersedes #6430 closes https://github.com/official-stockfish/Stockfish/pull/6434 No functional change --- src/nnue/nnue_accumulator.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index e63a68cb756..358a4b27880 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -584,18 +584,6 @@ void update_accumulator_incremental( else FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed); - if (!added.size() && !removed.size()) - { - auto& targetAcc = target_state.template acc(); - const auto& sourceAcc = computed.template acc(); - - targetAcc.accumulation[perspective] = sourceAcc.accumulation[perspective]; - targetAcc.psqtAccumulation[perspective] = sourceAcc.psqtAccumulation[perspective]; - targetAcc.computed[perspective] = true; - - return; - } - auto updateContext = make_accumulator_update_context(perspective, featureTransformer, computed, target_state); From f4244e13e41b936f73076c51eac38511f406d8e4 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Sat, 15 Nov 2025 20:46:14 -0800 Subject: [PATCH 1233/1309] Some more work on FullThreats::make_index [Passed STC](https://tests.stockfishchess.org/tests/live_elo/691fc321acb6dbdf23d07e4b): ``` LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 215360 W: 55651 L: 55108 D: 104601 Ptnml(0-2): 520, 22399, 61290, 22960, 511 ``` This PR is on top of ces42's work so I'll rebase if that PR changes. Essentially, it comprises my adjustments to `make_index` that remained unmerged before threat inputs was finalized. The unsigned intermediate variables let us skip a bunch of useless sign extensions (because Square and Piece inherit from `int8_t`, and C/C++ semantics require narrow integers to be promoted to `int` before doing arithmetic on them). Additionally, by removing the special usage of `offsets[][64]` and `[65]` the indexing becomes more efficient. (This usage was a temporary hack from sscg anyway, so I think he'll like that it's gone.) Finally, the `sf_assume` condition was fixed so that it actually makes a difference to the compiler. The speedup is fairly nice locally (combining both ces42's and these changes): ``` Result of 100 runs ================== base (...kfish.master) = 1691982 +/- 1907 test (./stockfish ) = 1714349 +/- 1789 diff = +22367 +/- 2465 speedup = +0.0132 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/6445 No functional change --- src/nnue/features/full_threats.cpp | 50 ++++++++++++++++-------------- src/nnue/features/full_threats.h | 15 ++------- src/types.h | 8 ++--- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 9c818a8e003..645bb7090bb 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -32,7 +32,12 @@ namespace Stockfish::Eval::NNUE::Features { // Lookup array for indexing threats -IndexType offsets[PIECE_NB][SQUARE_NB + 2]; +IndexType offsets[PIECE_NB][SQUARE_NB]; + +struct HelperOffsets { + int cumulativePieceOffset, cumulativeOffset; +}; +std::array helper_offsets; // Information on a particular pair of pieces and whether they should be excluded struct PiecePairData { @@ -69,9 +74,9 @@ static void init_index_luts() { int map = FullThreats::map[attackerType - 1][attackedType - 1]; bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN); - IndexType feature = offsets[attacker][65] + IndexType feature = helper_offsets[attacker].cumulativeOffset + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) - * offsets[attacker][64]; + * helper_offsets[attacker].cumulativePieceOffset; bool excluded = map < 0; index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); @@ -116,8 +121,7 @@ void init_threat_offsets() { } } - offsets[pieceIdx][64] = cumulativePieceOffset; - offsets[pieceIdx][65] = cumulativeOffset; + helper_offsets[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; } @@ -128,32 +132,30 @@ void init_threat_offsets() { // Index of a feature for a given king position and another piece on some square inline sf_always_inline IndexType FullThreats::make_index( Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - const int orientation = OrientTBL[perspective][ksq]; - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); + const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective); + unsigned from_oriented = uint8_t(from) ^ orientation; + unsigned to_oriented = uint8_t(to) ^ orientation; - std::int8_t swap = 8 * perspective; - attacker = Piece(attacker ^ swap); - attacked = Piece(attacked ^ swap); + std::int8_t swap = 8 * perspective; + unsigned attacker_oriented = attacker ^ swap; + unsigned attacked_oriented = attacked ^ swap; - const auto piecePairData = index_lut1[attacker][attacked]; + const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented]; - const bool less_than = static_cast(from) < static_cast(to); + const bool less_than = from_oriented < to_oriented; if ((piecePairData.excluded_pair_info() + less_than) & 2) return FullThreats::Dimensions; - const IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != FullThreats::Dimensions); + const IndexType index = piecePairData.feature_index_base() + + offsets[attacker_oriented][from_oriented] + + index_lut2[attacker_oriented][from_oriented][to_oriented]; + sf_assume(index < Dimensions); return index; } // Get a list of indices for active features in ascending order void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) { - static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(perspective); Bitboard occupied = pos.pieces(); @@ -161,7 +163,7 @@ void FullThreats::append_active_indices(Color perspective, const Position& pos, { for (PieceType pt = PAWN; pt <= KING; ++pt) { - Color c = order[perspective][color]; + Color c = Color(perspective ^ color); Piece attacker = make_piece(c, pt); Bitboard bb = pos.pieces(c, pt); @@ -268,16 +270,16 @@ void FullThreats::append_changed_indices(Color perspective, } } - const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + auto& insert = add ? added : removed; + const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) - (add ? added : removed).push_back(index); + insert.push_back(index); } } bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { - return perspective == diff.us - && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; + return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index bc8a1ce329c..177e9fabe7f 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -32,7 +32,6 @@ namespace Stockfish::Eval::NNUE::Features { static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0, 0, 6, 12, 10, 10, 12, 8, 0}; -extern IndexType offsets[PIECE_NB][SQUARE_NB + 2]; void init_threat_offsets(); class FullThreats { @@ -48,23 +47,15 @@ class FullThreats { // clang-format off // Orient a square according to perspective (rotates by 180 for black) - static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { - { SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + static constexpr std::int8_t OrientTBL[SQUARE_NB] = { + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, - SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1 }, - { SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8 } }; static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = { diff --git a/src/types.h b/src/types.h index a2171b638bd..3aeb2bcc4bf 100644 --- a/src/types.h +++ b/src/types.h @@ -295,18 +295,14 @@ struct DirtyPiece { struct DirtyThreat { DirtyThreat() { /* don't initialize data */ } DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 28) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); } Piece pc() const { return static_cast(data >> 20 & 0xf); } Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { - uint32_t b = data >> 28; - sf_assume(b == 0 || b == 1); - return b; - } + bool add() const { return data >> 31; } private: uint32_t data; From c73f21df97e98c960773d0eb0fcf765582c06fed Mon Sep 17 00:00:00 2001 From: pb00067 Date: Sat, 22 Nov 2025 13:24:03 +0100 Subject: [PATCH 1234/1309] Fix for SF getting stuck during search (issue #5023) Among several passed similar versions this is the simpliest one. Passed STC non-regression https://tests.stockfishchess.org/tests/view/691f3b4aacb6dbdf23d07d11 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 126368 W: 32607 L: 32489 D: 61272 Ptnml(0-2): 408, 13974, 34298, 14100, 404 Passed LTC non-regression https://tests.stockfishchess.org/tests/view/69209712acb6dbdf23d0836a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 114786 W: 29097 L: 28976 D: 56713 Ptnml(0-2): 53, 11841, 33488, 11954, 57 closes https://github.com/official-stockfish/Stockfish/pull/6447 Bench: 2941767 --- src/search.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d2a90283cb6..9924b0cec55 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -141,6 +141,16 @@ void update_all_stats(const Position& pos, Move TTMove, int moveCount); +bool isShuffling(Move move, Stack* const ss, const Position& pos) { + if (type_of(pos.moved_piece(move)) == PAWN || pos.capture_stage(move) + || pos.rule50_count() < 10) + return false; + if (pos.state()->pliesFromNull <= 6 || ss->ply < 20) + return false; + return move.from_sq() == (ss - 2)->currentMove.to_sq() + && (ss - 2)->currentMove.from_sq() == (ss - 4)->currentMove.to_sq(); +} + } // namespace Search::Worker::Worker(SharedState& sharedState, @@ -1114,7 +1124,7 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3) + && ttData.depth >= depth - 3 && !isShuffling(move, ss, pos)) { Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; From 45d034fa504c0262c9be958e6139cabcc65aeaf6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 21 Nov 2025 23:11:16 -0800 Subject: [PATCH 1235/1309] Formatting fixups closes https://github.com/official-stockfish/Stockfish/pull/6446 No functional change --- src/search.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9924b0cec55..f5133905e43 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -480,7 +480,6 @@ void Search::Worker::iterative_deepening() { double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue) + 0.93 * (mainThread->iterValue[iterIdx] - bestValue)) / 100.0; - fallingEval = std::clamp(fallingEval, 0.57, 1.70); // If the bestMove is stable over several iterations, reduce time accordingly @@ -717,7 +716,6 @@ Value Search::Worker::search( update_quiet_histories(pos, ss, *this, ttData.move, std::min(132 * depth - 72, 985)); - // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060); @@ -738,6 +736,7 @@ Value Search::Worker::search( // Check that the ttValue after the tt move would also trigger a cutoff if (!is_valid(ttDataNext.value)) return ttData.value; + if ((ttData.value >= beta) == (-ttDataNext.value >= beta)) return ttData.value; } @@ -858,6 +857,7 @@ Value Search::Worker::search( // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) depth--; @@ -919,7 +919,6 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions @@ -1187,7 +1186,6 @@ Value Search::Worker::search( if (ss->ttPv) r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922 + (ttData.depth >= depth) * (934 + cutNode * 1011); - // These reduction adjustments have no proven non-linear scaling r += 714; // Base reduction offset to compensate for other tweaks r -= moveCount * 73; @@ -1220,7 +1218,6 @@ Value Search::Worker::search( // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 850 / 8192; - // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) { @@ -1265,6 +1262,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode); } + // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. if (PvNode && (moveCount == 1 || value > alpha)) @@ -1445,7 +1443,6 @@ Value Search::Worker::search( captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012; } - if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1560,8 +1557,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(pos); + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); @@ -1573,8 +1572,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) else { unadjustedStaticEval = evaluate(pos); - - ss->staticEval = bestValue = + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); } @@ -1583,6 +1581,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { if (!is_decisive(bestValue)) bestValue = (bestValue + beta) / 2; + if (!ss->ttHit) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, @@ -1596,7 +1595,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) futilityBase = ss->staticEval + 351; } - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; @@ -1699,7 +1697,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!is_decisive(bestValue) && bestValue > beta) bestValue = (bestValue + beta) / 2; - Color us = pos.side_to_move(); if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us) && type_of(pos.captured_piece()) >= ROOK) @@ -1731,7 +1728,6 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182; } - // elapsed() returns the time elapsed since the search started. If the // 'nodestime' option is enabled, it will return the count of nodes searched // instead. This function is called to check whether the search should be @@ -1869,6 +1865,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; + if (((ss - i)->currentMove).is_ok()) (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 88 * (i < 2); } @@ -1925,7 +1922,6 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { return best; } - // Used to print debug info and, more importantly, to detect // when we are out of available time and thus stop the search. void SearchManager::check_time(Search::Worker& worker) { From 7c7c574e6b572ff7e23388d9f767471e597cb872 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sat, 22 Nov 2025 14:15:50 -0800 Subject: [PATCH 1236/1309] Fix msvc macro closes https://github.com/official-stockfish/Stockfish/pull/6449 No functional change --- src/misc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.h b/src/misc.h index 66c03b806b1..54598cd0e03 100644 --- a/src/misc.h +++ b/src/misc.h @@ -414,7 +414,7 @@ void move_to_front(std::vector& vec, Predicate pred) { #if defined(__GNUC__) #define sf_always_inline __attribute__((always_inline)) -#elif defined(__MSVC) +#elif defined(_MSC_VER) #define sf_always_inline __forceinline #else // do nothign for other compilers From c95386256ef79c2a415eeb4ab83ae5ec9e5b1532 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 26 Nov 2025 17:43:38 +0300 Subject: [PATCH 1237/1309] Simplify the highBestMoveEffort formula in Time management Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 22272 W: 5885 L: 5655 D: 10732 Ptnml(0-2): 76, 2329, 6108, 2535, 88 https://tests.stockfishchess.org/tests/view/69244d07ba083df4ca63e02b Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 48690 W: 12405 L: 12221 D: 24064 Ptnml(0-2): 29, 4755, 14597, 4931, 33 https://tests.stockfishchess.org/tests/view/6924de9aba083df4ca63e327 closes https://github.com/official-stockfish/Stockfish/pull/6451 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f5133905e43..8e30f3dc3ae 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -492,7 +492,7 @@ void Search::Worker::iterative_deepening() { double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size(); - double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 93337 ? 0.75 : 1.0; + double highBestMoveEffort = nodesEffort >= 93340 ? 0.76 : 1.0; double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * highBestMoveEffort; From 4fe04a2cc6efdd3ce1a859cd4d0bd61ca875b4e2 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 23 Nov 2025 17:57:33 +0100 Subject: [PATCH 1238/1309] Update main network to nn-87a9d7857d88.nnue trained with https://github.com/vondele/nettest/blob/842d95177f882c0e9b5262247c38d8fcb3e0f3db/threats.yaml passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 114880 W: 30291 L: 29851 D: 54738 Ptnml(0-2): 499, 13547, 28947, 13909, 538 https://tests.stockfishchess.org/tests/view/6923ec4bba083df4ca63dd39 passed LTC: LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 113268 W: 29115 L: 28634 D: 55519 Ptnml(0-2): 114, 12295, 31340, 12766, 119 https://tests.stockfishchess.org/tests/view/6925cd01b23dfeae38cfe134 closes https://github.com/official-stockfish/Stockfish/pull/6452 Bench: 3028457 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index fc77843644c..9eff4670983 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-c0ae49f08b40.nnue" +#define EvalFileDefaultNameBig "nn-87a9d7857d88.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 74303ca7f9ccba952c1937e2b9e09a341c6c0c34 Mon Sep 17 00:00:00 2001 From: KazApps Date: Sat, 29 Nov 2025 18:11:46 +0900 Subject: [PATCH 1239/1309] Update AUTHORS (KazApps) closes https://github.com/official-stockfish/Stockfish/pull/6454 No functional change --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index db552a3a576..fac1e7d8d90 100644 --- a/AUTHORS +++ b/AUTHORS @@ -131,6 +131,7 @@ Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery Justin Blanchard (UncombedCoconut) +Kazuki Yamashita (KazApps) Kelly Wilson Ken Takusagawa Kenneth Lee (kennethlee33) From 9e2ee13e775274ff5faab3fa12a7c0fd0370df3a Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 28 Nov 2025 07:42:26 +0100 Subject: [PATCH 1240/1309] Update main network to nn-2962dca31855.nnue trained with https://github.com/vondele/nettest/blob/dd921672bd4ad8eb09fa45248078450857e81884/threats.yaml on top off and tested against https://github.com/official-stockfish/Stockfish/pull/6452 passed STC LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 130720 W: 34028 L: 33570 D: 63122 Ptnml(0-2): 435, 15348, 33384, 15710, 483 https://tests.stockfishchess.org/tests/view/69294539b23dfeae38cff3ad passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 51144 W: 13140 L: 12794 D: 25210 Ptnml(0-2): 38, 5370, 14408, 5720, 36 https://tests.stockfishchess.org/tests/view/692a9c37b23dfeae38cffa4f closes https://github.com/official-stockfish/Stockfish/pull/6457 Bench: 2823033 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 9eff4670983..853aeb5b2c6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-87a9d7857d88.nnue" +#define EvalFileDefaultNameBig "nn-2962dca31855.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From abd835dcbc3a28481224f6253b00b7420d062513 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 30 Nov 2025 13:17:39 -0800 Subject: [PATCH 1241/1309] Improve update_piece_threats passed on avx512ICL: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 30240 W: 8026 L: 7726 D: 14488 Ptnml(0-2): 95, 3235, 8171, 3513, 106 https://tests.stockfishchess.org/tests/view/69281d9ab23dfeae38cfeeb8 passed on generic architectures: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 73184 W: 19183 L: 18821 D: 35180 Ptnml(0-2): 258, 7988, 19744, 8338, 264 https://tests.stockfishchess.org/tests/view/6928ba11b23dfeae38cff276 subsequent cleanups tested as: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 72480 W: 18678 L: 18502 D: 35300 Ptnml(0-2): 242, 7925, 19718, 8125, 230 https://tests.stockfishchess.org/tests/view/692a26adb23dfeae38cff566 We add an argument noRaysContaining, which skips all discoveries which contain all bits in the argument; if the argument is from | to, then this will eliminate the discovery. Separately, on AVX512ICL we can speed up the computation of DirtyThreats by moving from a pop_lsb loop to a vector extraction with vpcompressb. See PR for details. closes https://github.com/official-stockfish/Stockfish/pull/6453 No functional change --- src/misc.h | 7 ++++ src/position.cpp | 94 ++++++++++++++++++++++++++++++++++++++++-------- src/position.h | 9 +++-- src/types.h | 23 ++++++++---- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/misc.h b/src/misc.h index 54598cd0e03..db5f701e9f1 100644 --- a/src/misc.h +++ b/src/misc.h @@ -142,6 +142,13 @@ class ValueList { const T* end() const { return values_ + size_; } const T& operator[](int index) const { return values_[index]; } + T* make_space(size_t count) { + T* result = &values_[size_]; + size_ += count; + assert(size_ <= MaxSize); + return result; + } + private: T values_[MaxSize]; std::size_t size_ = 0; diff --git a/src/position.cpp b/src/position.cpp index 8993c240644..049c7268229 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1052,8 +1052,51 @@ inline void add_dirty_threat( dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); } +#ifdef USE_AVX512ICL +// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats +// present at the given bitboard. +template +void write_multiple_dirties(const Position& p, + Bitboard mask, + DirtyThreat dt_template, + DirtyThreats* dts) { + static_assert(sizeof(DirtyThreat) == 4); + + const __m512i board = _mm512_loadu_si512(p.piece_array().data()); + const __m512i AllSquares = _mm512_set_epi8( + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, + 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + const int dt_count = popcount(mask); + assert(dt_count <= 16); + + const __m512i template_v = _mm512_set1_epi32(dt_template.raw()); + auto* write = dts->list.make_space(dt_count); + + // Extract the list of squares and upconvert to 32 bits. There are never more than 16 + // incoming threats so this is sufficient. + __m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares); + threat_squares = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares)); + + __m512i threat_pieces = + _mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board); + + // Shift the piece and square into place + threat_squares = _mm512_slli_epi32(threat_squares, SqShift); + threat_pieces = _mm512_slli_epi32(threat_pieces, PcShift); + + const __m512i dirties = + _mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */); + _mm512_storeu_si512(reinterpret_cast<__m512i*>(write), dirties); +} +#endif + template -void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { +void Position::update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining) { const Bitboard occupied = pieces(); const Bitboard rookQueens = pieces(ROOK, QUEEN); const Bitboard bishopQueens = pieces(BISHOP, QUEEN); @@ -1093,7 +1136,36 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) } threatened &= occupied; + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); + +#ifdef USE_AVX512ICL + if (threatened) + { + if constexpr (PutPiece) + { + dts->threatenedSqs |= threatened; + dts->threateningSqs |= square_bb(s); + } + + DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece}; + write_multiple_dirties( + *this, threatened, dt_template, dts); + } + + Bitboard all_attackers = sliders | incoming_threats; + if (!all_attackers) + return; // Square s is threatened iff there's at least one attacker + dts->threatenedSqs |= square_bb(s); + dts->threateningSqs |= all_attackers; + + DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece}; + write_multiple_dirties(*this, all_attackers, + dt_template, dts); +#else while (threatened) { Square threatenedSq = pop_lsb(threatened); @@ -1104,8 +1176,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); } - - Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); +#endif if constexpr (ComputeRay) { @@ -1118,30 +1189,24 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) const Bitboard discovered = ray & qAttacks & occupied; assert(!more_than_one(discovered)); - if (discovered) + if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining) { const Square threatenedSq = lsb(discovered); const Piece threatenedPc = piece_on(threatenedSq); add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); } +#ifndef USE_AVX512ICL // for ICL, direct threats were processed earlier (all_attackers) add_dirty_threat(dts, slider, pc, sliderSq, s); +#endif } } else { - while (sliders) - { - Square sliderSq = pop_lsb(sliders); - Piece slider = piece_on(sliderSq); - add_dirty_threat(dts, slider, pc, sliderSq, s); - } + incoming_threats |= sliders; } - Bitboard incoming_threats = - (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) - | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); - +#ifndef USE_AVX512ICL while (incoming_threats) { Square srcSq = pop_lsb(incoming_threats); @@ -1152,6 +1217,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, srcPc, pc, srcSq, s); } +#endif } // Helper used to do/undo a castling move. This is a bit diff --git a/src/position.h b/src/position.h index 711c4e44484..7a029ce184f 100644 --- a/src/position.h +++ b/src/position.h @@ -187,7 +187,10 @@ class Position { // Other helpers template - void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts); + void update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining = -1ULL); void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); template void do_castling(Color us, @@ -372,7 +375,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts Bitboard fromTo = from | to; if (dts) - update_piece_threats(pc, from, dts); + update_piece_threats(pc, from, dts, fromTo); byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; @@ -381,7 +384,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts board[to] = pc; if (dts) - update_piece_threats(pc, to, dts); + update_piece_threats(pc, to, dts, fromTo); } inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { diff --git a/src/types.h b/src/types.h index 3aeb2bcc4bf..c6613f2f295 100644 --- a/src/types.h +++ b/src/types.h @@ -293,22 +293,31 @@ struct DirtyPiece { // Keep track of what threats change on the board (used by NNUE) struct DirtyThreat { + static constexpr int PcSqOffset = 0; + static constexpr int ThreatenedSqOffset = 8; + static constexpr int ThreatenedPcOffset = 16; + static constexpr int PcOffset = 20; + DirtyThreat() { /* don't initialize data */ } + DirtyThreat(uint32_t raw) : + data(raw) {} DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset) + | (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset); } - Piece pc() const { return static_cast(data >> 20 & 0xf); } - Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } - Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } - Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { return data >> 31; } + Piece pc() const { return static_cast(data >> 20 & 0xf); } + Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } + Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } + Square pc_sq() const { return static_cast(data & 0xff); } + bool add() const { return data >> 31; } + uint32_t raw() const { return data; } private: uint32_t data; }; -using DirtyThreatList = ValueList; +using DirtyThreatList = ValueList; // A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. // Moving a piece also can reveal at most 8 discovered attacks. From 5297ba0a1a1aa0a15332e0d64ce6b32952342cac Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 22 Nov 2025 12:43:34 -0500 Subject: [PATCH 1242/1309] Move hindsight reductions above cutoffs Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 52640 W: 13701 L: 13361 D: 25578 Ptnml(0-2): 168, 6143, 13356, 6487, 166 https://tests.stockfishchess.org/tests/view/6918dc407ca8781852332339 Passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 207690 W: 53187 L: 52520 D: 101983 Ptnml(0-2): 93, 22515, 57994, 23118, 125 https://tests.stockfishchess.org/tests/view/6919dd307ca87818523324c7 closes https://github.com/official-stockfish/Stockfish/pull/6448 Bench: 2912398 --- src/search.cpp | 98 +++++++++++++++++++++++++------------------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8e30f3dc3ae..c31a08c4937 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -702,6 +702,56 @@ Value Search::Worker::search( // At this point, if excluded, skip straight to step 6, static eval. However, // to save indentation, we list the condition in all code between here and there. + // Step 6. Static evaluation of the position + Value unadjustedStaticEval = VALUE_NONE; + const auto correctionValue = correction_value(*this, pos, ss); + if (ss->inCheck) + { + // Skip early pruning when in check + ss->staticEval = eval = (ss - 2)->staticEval; + improving = false; + } + else if (excludedMove) + unadjustedStaticEval = eval = ss->staticEval; + else if (ss->ttHit) + { + // Never assume anything about values stored in TT + unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) + unadjustedStaticEval = evaluate(pos); + + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); + + // 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; + } + else + { + unadjustedStaticEval = evaluate(pos); + ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); + + // Static evaluation is saved as it was before adjustment by correction history + ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), + unadjustedStaticEval, tt.generation()); + } + + // Set up the improving flag, which is true if current static evaluation is + // bigger than the previous static evaluation at our turn (if we were in + // check at our previous move we go back until we weren't in check) and is + // false otherwise. The improving flag is used in various pruning heuristics. + // Similarly, opponentWorsening is true if our static evaluation is better + // for us than at the last ply. + improving = ss->staticEval > (ss - 2)->staticEval; + opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; + + // Hindsight adjustment of reductions based on static evaluation difference. + if (priorReduction >= 3 && !opponentWorsening) + depth++; + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) + depth--; + // At non-PV nodes we check for an early TT cutoff if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() @@ -799,41 +849,8 @@ Value Search::Worker::search( } } - // Step 6. Static evaluation of the position - Value unadjustedStaticEval = VALUE_NONE; - const auto correctionValue = correction_value(*this, pos, ss); if (ss->inCheck) - { - // Skip early pruning when in check - ss->staticEval = eval = (ss - 2)->staticEval; - improving = false; goto moves_loop; - } - else if (excludedMove) - unadjustedStaticEval = eval = ss->staticEval; - else if (ss->ttHit) - { - // Never assume anything about values stored in TT - unadjustedStaticEval = ttData.eval; - if (!is_valid(unadjustedStaticEval)) - unadjustedStaticEval = evaluate(pos); - - ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - - // 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; - } - else - { - unadjustedStaticEval = evaluate(pos); - ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - - // Static evaluation is saved as it was before adjustment by correction history - ttWriter.write(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_UNSEARCHED, Move::none(), - unadjustedStaticEval, tt.generation()); - } // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) @@ -845,21 +862,6 @@ Value Search::Worker::search( pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13; } - // Set up the improving flag, which is true if current static evaluation is - // bigger than the previous static evaluation at our turn (if we were in - // check at our previous move we go back until we weren't in check) and is - // false otherwise. The improving flag is used in various pruning heuristics. - // Similarly, opponentWorsening is true if our static evaluation is better - // for us than at the last ply. - improving = ss->staticEval > (ss - 2)->staticEval; - opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; - - // Hindsight adjustment of reductions based on static evaluation difference. - if (priorReduction >= 3 && !opponentWorsening) - depth++; - - if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) - depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. From c109a88ebe93ab7652c7cb4694cfc405568e5e50 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Tue, 2 Dec 2025 15:50:24 -0800 Subject: [PATCH 1243/1309] fix missing condition Fixes a bug in https://github.com/official-stockfish/Stockfish/pull/6453 https://github.com/official-stockfish/Stockfish/commit/abd835dcbc3a28481224f6253b00b7420d062513 The modifications to the DirtyThreats bitboards should only happen if PutPiece is true. This somehow didn't affect bench at the default parameters. Reference AVX2: ./stockfish.killdeer-fix.avx2 bench 64 1 23 Nodes searched : 178140156 Nodes/second : 1503152 before patch: ./stockfish.master.avx512icl bench 64 1 23 Nodes searched : 218349728 Nodes/second : 1743450 after patch: ./stockfish.killdeer-fix.avx512icl bench 64 1 23 Nodes searched : 178140156 Nodes/second : 1727520 passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 39328 W: 10293 L: 10080 D: 18955 Ptnml(0-2): 113, 4306, 10629, 4487, 129 https://tests.stockfishchess.org/tests/view/692fb2d8b23dfeae38d01c81 closes https://github.com/official-stockfish/Stockfish/pull/6463 Bench: 2912398 --- src/position.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 049c7268229..b08cabf3f26 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1159,8 +1159,11 @@ void Position::update_piece_threats(Piece pc, if (!all_attackers) return; // Square s is threatened iff there's at least one attacker - dts->threatenedSqs |= square_bb(s); - dts->threateningSqs |= all_attackers; + if constexpr (PutPiece) + { + dts->threatenedSqs |= square_bb(s); + dts->threateningSqs |= all_attackers; + } DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece}; write_multiple_dirties(*this, all_attackers, From 5edfabd07029dc26228f4b22931b67be5402860a Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 17 Nov 2025 18:54:24 +0300 Subject: [PATCH 1244/1309] Fix Typo closes https://github.com/official-stockfish/Stockfish/pull/6436 No functional change --- src/history.h | 2 +- src/misc.cpp | 2 +- src/misc.h | 2 +- src/position.cpp | 2 +- src/search.cpp | 8 +------- 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/history.h b/src/history.h index a605ae4175b..f0161b74ebe 100644 --- a/src/history.h +++ b/src/history.h @@ -100,7 +100,7 @@ using Stats = MultiArray, Sizes...>; // see https://www.chessprogramming.org/Butterfly_Boards using ButterflyHistory = Stats; -// LowPlyHistory is addressed by play and move's from and to squares, used +// LowPlyHistory is addressed by ply and move's from and to squares, used // to improve move ordering near the root using LowPlyHistory = Stats; diff --git a/src/misc.cpp b/src/misc.cpp index d2149728036..886544b6c42 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -44,7 +44,7 @@ constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We -// can toggle the logging of std::cout and std:cin at runtime whilst preserving +// can toggle the logging of std::cout and std::cin at runtime whilst preserving // usual I/O functionality, all without changing a single line of code! // Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 diff --git a/src/misc.h b/src/misc.h index db5f701e9f1..32f5b047e7d 100644 --- a/src/misc.h +++ b/src/misc.h @@ -424,7 +424,7 @@ void move_to_front(std::vector& vec, Predicate pred) { #elif defined(_MSC_VER) #define sf_always_inline __forceinline #else - // do nothign for other compilers + // do nothing for other compilers #define sf_always_inline #endif diff --git a/src/position.cpp b/src/position.cpp index b08cabf3f26..092e40fd478 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -911,7 +911,7 @@ void Position::do_move(Move m, if (more_than_one(pawns)) { - // If there are two pawns potentially being abled to capture and at least one + // If there are two pawns potentially being able to capture and at least one // is not pinned, ep is legal as there are no horizontal exposed checks if (!more_than_one(blockers_for_king(them) & pawns)) { diff --git a/src/search.cpp b/src/search.cpp index c31a08c4937..5f60bafdcb6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -699,18 +699,12 @@ Value Search::Worker::search( ss->ttPv = excludedMove ? ss->ttPv : PvNode || (ttHit && ttData.is_pv); ttCapture = ttData.move && pos.capture_stage(ttData.move); - // At this point, if excluded, skip straight to step 6, static eval. However, - // to save indentation, we list the condition in all code between here and there. - // Step 6. Static evaluation of the position Value unadjustedStaticEval = VALUE_NONE; const auto correctionValue = correction_value(*this, pos, ss); + // Skip early pruning when in check if (ss->inCheck) - { - // Skip early pruning when in check ss->staticEval = eval = (ss - 2)->staticEval; - improving = false; - } else if (excludedMove) unadjustedStaticEval = eval = ss->staticEval; else if (ss->ttHit) From 863c0ec6d00e7bb1f453ccebcf16e0883ca8f4ff Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 1 Dec 2025 23:14:00 +0300 Subject: [PATCH 1245/1309] Simplify piece threat calculation Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 105984 W: 27206 L: 27067 D: 51711 Ptnml(0-2): 260, 11556, 29245, 11647, 284 https://tests.stockfishchess.org/tests/view/6914798e7ca87818523317cf Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 53028 W: 13635 L: 13456 D: 25937 Ptnml(0-2): 28, 5184, 15908, 5369, 25 https://tests.stockfishchess.org/tests/view/6918c5fc7ca8781852332312 closes https://github.com/official-stockfish/Stockfish/pull/6459 No functional change --- src/position.cpp | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 092e40fd478..3125d1f7a66 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1108,35 +1108,8 @@ void Position::update_piece_threats(Piece pc, const Bitboard rAttacks = attacks_bb(s, occupied); const Bitboard bAttacks = attacks_bb(s, occupied); - Bitboard qAttacks = Bitboard(0); - if constexpr (ComputeRay) - qAttacks = rAttacks | bAttacks; - else if (type_of(pc) == QUEEN) - qAttacks = rAttacks | bAttacks; - - Bitboard threatened; - - switch (type_of(pc)) - { - case PAWN : - threatened = PseudoAttacks[color_of(pc)][s]; - break; - case BISHOP : - threatened = bAttacks; - break; - case ROOK : - threatened = rAttacks; - break; - case QUEEN : - threatened = qAttacks; - break; - - default : - threatened = PseudoAttacks[type_of(pc)][s]; - } - - threatened &= occupied; - Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); + Bitboard threatened = attacks_bb(pc, s, occupied) & occupied; + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); Bitboard incoming_threats = (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); @@ -1189,7 +1162,7 @@ void Position::update_piece_threats(Piece pc, Piece slider = piece_on(sliderSq); const Bitboard ray = RayPassBB[sliderSq][s] & ~BetweenBB[sliderSq][s]; - const Bitboard discovered = ray & qAttacks & occupied; + const Bitboard discovered = ray & (rAttacks | bAttacks) & occupied; assert(!more_than_one(discovered)); if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining) From a98c3f687816348dd8d047dd9229171d23c1bfe8 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Mon, 1 Dec 2025 16:23:18 -0800 Subject: [PATCH 1246/1309] Small threat-related cleanups Remove a couple unused things, mark `noRaysContaining` as `[[maybe_unused]]` (suggested by Viz) to silence a warning on GCC 10, update a comment, and replace inline constants closes https://github.com/official-stockfish/Stockfish/pull/6460 No functional change --- src/nnue/nnue_accumulator.h | 1 - src/position.cpp | 8 ++++---- src/position.h | 2 +- src/types.h | 22 +++++++++------------- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 69ab4963269..1ccab5f2f3a 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -157,7 +157,6 @@ class AccumulatorStack { [[nodiscard]] const AccumulatorState& latest() const noexcept; void reset() noexcept; - void push(const DirtyBoardData& dirtyBoardData) noexcept; std::pair push() noexcept; void pop() noexcept; diff --git a/src/position.cpp b/src/position.cpp index 3125d1f7a66..cd778011f4b 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1093,10 +1093,10 @@ void write_multiple_dirties(const Position& p, #endif template -void Position::update_piece_threats(Piece pc, - Square s, - DirtyThreats* const dts, - Bitboard noRaysContaining) { +void Position::update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + [[maybe_unused]] Bitboard noRaysContaining) const { const Bitboard occupied = pieces(); const Bitboard rookQueens = pieces(ROOK, QUEEN); const Bitboard bishopQueens = pieces(BISHOP, QUEEN); diff --git a/src/position.h b/src/position.h index 7a029ce184f..e5d3f6d28b5 100644 --- a/src/position.h +++ b/src/position.h @@ -190,7 +190,7 @@ class Position { void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts, - Bitboard noRaysContaining = -1ULL); + Bitboard noRaysContaining = -1ULL) const; void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); template void do_castling(Color us, diff --git a/src/types.h b/src/types.h index c6613f2f295..1bb8bd3cc9a 100644 --- a/src/types.h +++ b/src/types.h @@ -306,24 +306,25 @@ struct DirtyThreat { | (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset); } - Piece pc() const { return static_cast(data >> 20 & 0xf); } - Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } - Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } - Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { return data >> 31; } + Piece pc() const { return static_cast(data >> PcOffset & 0xf); } + Piece threatened_pc() const { return static_cast(data >> ThreatenedPcOffset & 0xf); } + Square threatened_sq() const { return static_cast(data >> ThreatenedSqOffset & 0xff); } + Square pc_sq() const { return static_cast(data >> PcSqOffset & 0xff); } + bool add() const { return data >> 31; } uint32_t raw() const { return data; } private: uint32_t data; }; -using DirtyThreatList = ValueList; - // A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. // Moving a piece also can reveal at most 8 discovered attacks. // This implies that a non-castling move can change at most (8 + 16) * 3 + 8 = 80 features. // By similar logic, a castling move can change at most (5 + 1 + 3 + 9) * 2 = 36 features. -// Thus, 80 should work as an upper bound. +// Thus, 80 should work as an upper bound. Finally, 16 entries are added to accommodate +// unmasked vector stores near the end of the list. + +using DirtyThreatList = ValueList; struct DirtyThreats { DirtyThreatList list; @@ -333,11 +334,6 @@ struct DirtyThreats { Bitboard threatenedSqs, threateningSqs; }; -struct DirtyBoardData { - DirtyPiece dp; - DirtyThreats dts; -}; - #define ENABLE_INCR_OPERATORS_ON(T) \ constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \ constexpr T& operator--(T& d) { return d = T(int(d) - 1); } From 8449e5eb9d082d6561e5a491a97e40e1f7532893 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 2 Dec 2025 12:56:35 -0500 Subject: [PATCH 1247/1309] Remove non-functional term in isShuffling closes https://github.com/official-stockfish/Stockfish/pull/6462 No functional change --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5f60bafdcb6..c9d30ce30c7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -141,9 +141,8 @@ void update_all_stats(const Position& pos, Move TTMove, int moveCount); -bool isShuffling(Move move, Stack* const ss, const Position& pos) { - if (type_of(pos.moved_piece(move)) == PAWN || pos.capture_stage(move) - || pos.rule50_count() < 10) +bool is_shuffling(Move move, Stack* const ss, const Position& pos) { + if (pos.capture_stage(move) || pos.rule50_count() < 10) return false; if (pos.state()->pliesFromNull <= 6 || ss->ply < 20) return false; @@ -1119,7 +1118,7 @@ Value Search::Worker::search( // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3 && !isShuffling(move, ss, pos)) + && ttData.depth >= depth - 3 && !is_shuffling(move, ss, pos)) { Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; From e1c919fd7eca39c00c23274f58e54606e8aa19c6 Mon Sep 17 00:00:00 2001 From: mstembera <5421953+mstembera@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:36:01 -0800 Subject: [PATCH 1248/1309] Fix one error and all warnings on MSVC 2026 The error is "C1001: Internal compiler error." in uci.cpp on line 370 of master. For some reason the compiler can't handle the std::size(hashfullAges) inside the lambda. Older MSVC versions had no problem. Most of the warnings are due to implicit type conversions "conversion from type A to type B, possible loss of data" many of which have been present for a while. closes https://github.com/official-stockfish/Stockfish/pull/6464 No functional change --- src/benchmark.cpp | 4 ++-- src/history.h | 8 +++++--- src/nnue/nnue_feature_transformer.h | 7 ++++--- src/syzygy/tbprobe.cpp | 4 ++-- src/thread.cpp | 4 ++-- src/timeman.cpp | 2 +- src/uci.cpp | 13 +++++++------ 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 136b4031e85..039e384c9ac 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -454,7 +454,7 @@ BenchmarkSetup setup_benchmark(std::istream& is) { int desiredTimeS; if (!(is >> setup.threads)) - setup.threads = get_hardware_concurrency(); + setup.threads = int(get_hardware_concurrency()); else setup.originalInvocation += std::to_string(setup.threads); @@ -486,7 +486,7 @@ BenchmarkSetup setup_benchmark(std::istream& is) { int ply = 1; for (int i = 0; i < static_cast(game.size()); ++i) { - const float correctedTime = getCorrectedTime(ply); + const float correctedTime = float(getCorrectedTime(ply)); totalTime += correctedTime; ply += 1; } diff --git a/src/history.h b/src/history.h index f0161b74ebe..87004ead9cf 100644 --- a/src/history.h +++ b/src/history.h @@ -48,13 +48,15 @@ inline int pawn_history_index(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } -inline uint16_t pawn_correction_history_index(const Position& pos) { return pos.pawn_key(); } +inline uint16_t pawn_correction_history_index(const Position& pos) { + return uint16_t(pos.pawn_key()); +} -inline uint16_t minor_piece_index(const Position& pos) { return pos.minor_piece_key(); } +inline uint16_t minor_piece_index(const Position& pos) { return uint16_t(pos.minor_piece_key()); } template inline uint16_t non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c); + return uint16_t(pos.non_pawn_key(c)); } // StatsEntry is the container of various numerical statistics. We use a class diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 1f328fff485..091ae074da1 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -171,9 +171,10 @@ class FeatureTransformer { read_leb_128(stream, *combinedWeights); - std::copy(combinedWeights->begin(), - combinedWeights->begin() + ThreatInputDimensions * HalfDimensions, - std::begin(threatWeights)); + std::transform(combinedWeights->begin(), + combinedWeights->begin() + ThreatInputDimensions * HalfDimensions, + std::begin(threatWeights), + [](WeightType w) { return static_cast(w); }); std::copy(combinedWeights->begin() + ThreatInputDimensions * HalfDimensions, combinedWeights->begin() diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index c8ff60739a3..e3f7c0a1882 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -584,7 +584,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) { // idx = k * d->span + idx % d->span (2) // // So from (1) and (2) we can compute idx - I(K): - int diff = idx % d->span - d->span / 2; + int diff = int(idx % d->span - d->span / 2); // Sum the above to offset to find the offset corresponding to our idx offset += diff; @@ -1092,7 +1092,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) { // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf std::vector visited(d->symlen.size()); - for (std::size_t sym = 0; sym < d->symlen.size(); ++sym) + for (Sym sym = 0; sym < d->symlen.size(); ++sym) if (!visited[sym]) d->symlen[sym] = set_symlen(d, sym, visited); diff --git a/src/thread.cpp b/src/thread.cpp index f87d7a94d23..58840a87448 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -283,8 +283,8 @@ void ThreadPool::start_thinking(const OptionsMap& options, { th->run_custom_job([&]() { th->worker->limits = limits; - th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly = - th->worker->bestMoveChanges = 0; + th->worker->nodes = th->worker->tbHits = th->worker->bestMoveChanges = 0; + th->worker->nmpMinPly = 0; th->worker->rootDepth = th->worker->completedDepth = 0; th->worker->rootMoves = rootMoves; th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState); diff --git a/src/timeman.cpp b/src/timeman.cpp index 5840e255600..e82a1f6bf18 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -91,7 +91,7 @@ void TimeManagement::init(Search::LimitsType& limits, // If less than one second, gradually reduce mtg if (scaledTime < 1000) - centiMTG = scaledTime * 5.051; + centiMTG = int(scaledTime * 5.051); // Make sure timeLeft is > 0 since we may use it as a divisor TimePoint timeLeft = diff --git a/src/uci.cpp b/src/uci.cpp index 5bd23582387..be7de97d715 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -313,8 +313,8 @@ void UCIEngine::benchmark(std::istream& args) { Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args); - const int numGoCommands = count_if(setup.commands.begin(), setup.commands.end(), - [](const std::string& s) { return s.find("go ") == 0; }); + const auto numGoCommands = count_if(setup.commands.begin(), setup.commands.end(), + [](const std::string& s) { return s.find("go ") == 0; }); TimePoint totalTime = 0; @@ -361,13 +361,14 @@ void UCIEngine::benchmark(std::istream& args) { int numHashfullReadings = 0; constexpr int hashfullAges[] = {0, 999}; // Only normal hashfull and touched hash. - int totalHashfull[std::size(hashfullAges)] = {0}; - int maxHashfull[std::size(hashfullAges)] = {0}; + constexpr int hashfullAgeCount = std::size(hashfullAges); + int totalHashfull[hashfullAgeCount] = {0}; + int maxHashfull[hashfullAgeCount] = {0}; auto updateHashfullReadings = [&]() { numHashfullReadings += 1; - for (int i = 0; i < static_cast(std::size(hashfullAges)); ++i) + for (int i = 0; i < hashfullAgeCount; ++i) { const int hashfull = engine.get_hashfull(hashfullAges[i]); maxHashfull[i] = std::max(maxHashfull[i], hashfull); @@ -554,7 +555,7 @@ int UCIEngine::to_cp(Value v, const Position& pos) { auto [a, b] = win_rate_params(pos); - return std::round(100 * int(v) / a); + return int(std::round(100 * int(v) / a)); } std::string UCIEngine::wdl(Value v, const Position& pos) { From e0e6fdf094d7b34750f99d3603943349ab7a57e9 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Tue, 2 Dec 2025 21:48:50 -0800 Subject: [PATCH 1249/1309] Tweak nnue_accumulator indexing ``` LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 92736 W: 24149 L: 23764 D: 44823 Ptnml(0-2): 280, 10056, 25334, 10395, 303 ``` The use of `IndexType` in the loops is suboptimal because it requires truncation to 32 bits, and thereby defeats some optimizations. Using `size_t` for the loop body works nicely, and a signed index into the index lists allows the compiler to assume the case `added.size() == UINT_MAX`, which would be an infinite loop, to not happen. Passed STC https://tests.stockfishchess.org/tests/live_elo/692fd98ab23dfeae38d01d98 closes https://github.com/official-stockfish/Stockfish/pull/6466 No functional change --- src/misc.h | 1 + src/nnue/nnue_accumulator.cpp | 113 ++++++++++++++++++---------------- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/src/misc.h b/src/misc.h index 32f5b047e7d..c9951e5557e 100644 --- a/src/misc.h +++ b/src/misc.h @@ -134,6 +134,7 @@ class ValueList { public: std::size_t size() const { return size_; } + int ssize() const { return int(size_); } void push_back(const T& value) { assert(size_ < MaxSize); values_[size_++] = value; diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 358a4b27880..763fb7a5e46 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -360,6 +360,8 @@ struct AccumulatorUpdateContext { vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; + const auto* threatWeights = &featureTransformer.threatWeights[0]; + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* fromTile = reinterpret_cast(&fromAcc[j * Tiling::TileHeight]); @@ -368,12 +370,11 @@ struct AccumulatorUpdateContext { for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = fromTile[k]; - for (IndexType i = 0; i < removed.size(); ++i) + for (int i = 0; i < removed.ssize(); ++i) { - IndexType index = removed[i]; - const IndexType offset = Dimensions * index + j * Tiling::TileHeight; - auto* column = - reinterpret_cast(&featureTransformer.threatWeights[offset]); + size_t index = removed[i]; + const size_t offset = Dimensions * index; + auto* column = reinterpret_cast(&threatWeights[offset]); #ifdef USE_NEON for (IndexType k = 0; k < Tiling::NumRegs; k += 2) @@ -387,12 +388,11 @@ struct AccumulatorUpdateContext { #endif } - for (IndexType i = 0; i < added.size(); ++i) + for (int i = 0; i < added.ssize(); ++i) { - IndexType index = added[i]; - const IndexType offset = Dimensions * index + j * Tiling::TileHeight; - auto* column = - reinterpret_cast(&featureTransformer.threatWeights[offset]); + size_t index = added[i]; + const size_t offset = Dimensions * index; + auto* column = reinterpret_cast(&threatWeights[offset]); #ifdef USE_NEON for (IndexType k = 0; k < Tiling::NumRegs; k += 2) @@ -408,6 +408,8 @@ struct AccumulatorUpdateContext { for (IndexType k = 0; k < Tiling::NumRegs; k++) vec_store(&toTile[k], acc[k]); + + threatWeights += Tiling::TileHeight; } for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) @@ -420,22 +422,22 @@ struct AccumulatorUpdateContext { for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = fromTilePsqt[k]; - for (IndexType i = 0; i < removed.size(); ++i) + for (int i = 0; i < removed.ssize(); ++i) { - IndexType index = removed[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast( + size_t index = removed[i]; + const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast( &featureTransformer.threatPsqtWeights[offset]); for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } - for (IndexType i = 0; i < added.size(); ++i) + for (int i = 0; i < added.ssize(); ++i) { - IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast( + size_t index = added[i]; + const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast( &featureTransformer.threatPsqtWeights[offset]); for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) @@ -691,6 +693,8 @@ void update_accumulator_refresh_cache(Color pers vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; + const auto* weights = &featureTransformer.weights[0]; + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = @@ -700,33 +704,33 @@ void update_accumulator_refresh_cache(Color pers for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = entryTile[k]; - IndexType i = 0; - for (; i < std::min(removed.size(), added.size()); ++i) + int i = 0; + for (; i < std::min(removed.ssize(), added.ssize()); ++i) { - IndexType indexR = removed[i]; - const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + size_t indexR = removed[i]; + const size_t offsetR = Dimensions * indexR; + auto* columnR = reinterpret_cast(&weights[offsetR]); + size_t indexA = added[i]; + const size_t offsetA = Dimensions * indexA; + auto* columnA = reinterpret_cast(&weights[offsetA]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = fused(acc[k], columnA[k], columnR[k]); } - for (; i < removed.size(); ++i) + for (; i < removed.ssize(); ++i) { - IndexType index = removed[i]; - const IndexType offset = Dimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + size_t index = removed[i]; + const size_t offset = Dimensions * index; + auto* column = reinterpret_cast(&weights[offset]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } - for (; i < added.size(); ++i) + for (; i < added.ssize(); ++i) { - IndexType index = added[i]; - const IndexType offset = Dimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + size_t index = added[i]; + const size_t offset = Dimensions * index; + auto* column = reinterpret_cast(&weights[offset]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_add_16(acc[k], column[k]); @@ -736,6 +740,8 @@ void update_accumulator_refresh_cache(Color pers vec_store(&entryTile[k], acc[k]); for (IndexType k = 0; k < Tiling::NumRegs; k++) vec_store(&accTile[k], acc[k]); + + weights += Tiling::TileHeight; } for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) @@ -748,21 +754,21 @@ void update_accumulator_refresh_cache(Color pers for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; - for (IndexType i = 0; i < removed.size(); ++i) + for (int i = 0; i < removed.ssize(); ++i) { - IndexType index = removed[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = + size_t index = removed[i]; + const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast(&featureTransformer.psqtWeights[offset]); for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } - for (IndexType i = 0; i < added.size(); ++i) + for (int i = 0; i < added.ssize(); ++i) { - IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = + size_t index = added[i]; + const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast(&featureTransformer.psqtWeights[offset]); for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) @@ -820,6 +826,8 @@ void update_threats_accumulator_full(Color persp vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; + const auto* threatWeights = &featureTransformer.threatWeights[0]; + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = @@ -828,14 +836,13 @@ void update_threats_accumulator_full(Color persp for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_zero(); - IndexType i = 0; + int i = 0; - for (; i < active.size(); ++i) + for (; i < active.ssize(); ++i) { - IndexType index = active[i]; - const IndexType offset = Dimensions * index + j * Tiling::TileHeight; - auto* column = - reinterpret_cast(&featureTransformer.threatWeights[offset]); + size_t index = active[i]; + const size_t offset = Dimensions * index; + auto* column = reinterpret_cast(&threatWeights[offset]); #ifdef USE_NEON for (IndexType k = 0; k < Tiling::NumRegs; k += 2) @@ -851,6 +858,8 @@ void update_threats_accumulator_full(Color persp for (IndexType k = 0; k < Tiling::NumRegs; k++) vec_store(&accTile[k], acc[k]); + + threatWeights += Tiling::TileHeight; } for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) @@ -861,11 +870,11 @@ void update_threats_accumulator_full(Color persp for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); - for (IndexType i = 0; i < active.size(); ++i) + for (int i = 0; i < active.ssize(); ++i) { - IndexType index = active[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = + size_t index = active[i]; + const size_t offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = reinterpret_cast(&featureTransformer.threatPsqtWeights[offset]); for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) From 955c927265c36396d6519a57b3d1e81a5c700420 Mon Sep 17 00:00:00 2001 From: Pieter te Brake Date: Mon, 8 Dec 2025 22:46:07 +0100 Subject: [PATCH 1250/1309] Removed redundant board updates Contrary to what the comment says, `remove_piece` does in fact set the relevant `board` elements to `NO_PIECE`. closes https://github.com/official-stockfish/Stockfish/pull/6471 No functional change --- src/position.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index cd778011f4b..a4286b86a86 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1225,8 +1225,6 @@ void Position::do_castling(Color us, // Remove both pieces first since squares could overlap in Chess960 remove_piece(Do ? from : to, dts); remove_piece(Do ? rfrom : rto, dts); - board[Do ? from : to] = board[Do ? rfrom : rto] = - NO_PIECE; // remove_piece does not do this for us put_piece(make_piece(us, KING), Do ? to : from, dts); put_piece(make_piece(us, ROOK), Do ? rto : rfrom, dts); } From d92e6b458aa92f884862f28e89d0792203385dfd Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Wed, 10 Dec 2025 15:15:28 +0100 Subject: [PATCH 1251/1309] chore(ci): bump runner to macos-15 closes https://github.com/official-stockfish/Stockfish/pull/6475 No functional change --- .github/ci/matrix.json | 24 ++++++++++++------------ .github/workflows/tests.yml | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/ci/matrix.json b/.github/ci/matrix.json index f72451f5bb7..e9215269b21 100644 --- a/.github/ci/matrix.json +++ b/.github/ci/matrix.json @@ -20,8 +20,8 @@ "archive_ext": "tar" }, { - "name": "macOS 14 Apple Clang M1", - "os": "macos-14", + "name": "macOS 15 Apple Clang M1", + "os": "macos-15", "simple_name": "macos-m1", "compiler": "clang++", "comp": "clang", @@ -71,49 +71,49 @@ { "binaries": "x86-64", "config": { - "os": "macos-14" + "os": "macos-15" } }, { "binaries": "x86-64-sse41-popcnt", "config": { - "os": "macos-14" + "os": "macos-15" } }, { "binaries": "x86-64-avx2", "config": { - "os": "macos-14" + "os": "macos-15" } }, { "binaries": "x86-64-bmi2", "config": { - "os": "macos-14" + "os": "macos-15" } }, { "binaries": "x86-64-avxvnni", "config": { - "os": "macos-14" + "os": "macos-15" } }, { "binaries": "x86-64-avx512", "config": { - "os": "macos-14" + "os": "macos-15" } }, { "binaries": "x86-64-vnni512", "config": { - "os": "macos-14" + "os": "macos-15" } }, { "binaries": "x86-64-avx512icl", "config": { - "os": "macos-14" + "os": "macos-15" } }, { @@ -245,7 +245,7 @@ { "binaries": "armv8", "config": { - "os": "macos-14" + "os": "macos-15" } }, { @@ -275,7 +275,7 @@ { "binaries": "armv8-dotprod", "config": { - "os": "macos-14" + "os": "macos-15" } } ] diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c2280f0b427..f0da51e7e59 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,8 +62,8 @@ jobs: comp: clang run_64bit_tests: true shell: bash - - name: macOS 14 Apple Clang M1 - os: macos-14 + - name: macOS 15 Apple Clang M1 + os: macos-15 compiler: clang++ comp: clang run_64bit_tests: false From b4b01d0ca27808a5219b069520b89274416bca31 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 4 Dec 2025 23:30:13 -0500 Subject: [PATCH 1252/1309] Remove rootDepth condition in newDepth clamping Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 132256 W: 34462 L: 34347 D: 63447 Ptnml(0-2): 470, 15625, 33833, 15720, 480 https://tests.stockfishchess.org/tests/view/69325fe0a24a6df719fcca16 Passed simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 128220 W: 32503 L: 32392 D: 63325 Ptnml(0-2): 94, 13900, 35982, 14069, 65 https://tests.stockfishchess.org/tests/view/6934c2b3a24a6df719fcdf1e closes https://github.com/official-stockfish/Stockfish/pull/6472 Bench: 2926903 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c9d30ce30c7..64f85bcbe00 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1269,7 +1269,7 @@ Value Search::Worker::search( // decisive score handling improves mate finding and retrograde analysis. if (move == ttData.move && ((is_valid(ttData.value) && is_decisive(ttData.value) && ttData.depth > 0) - || (ttData.depth > 1 && rootDepth > 8))) + || ttData.depth > 1)) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); From 32292d1e622d964ea011ecb87b2b758d35965823 Mon Sep 17 00:00:00 2001 From: sscg13 Date: Tue, 16 Dec 2025 17:30:15 -0800 Subject: [PATCH 1253/1309] Represent threat weights directly as i8 LEB128 adds no additional compression and adds extra complexity to the code currently. closes https://github.com/official-stockfish/Stockfish/pull/6479 No functional change --- src/evaluate.h | 2 +- src/nnue/nnue_feature_transformer.h | 34 +++++++---------------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index 853aeb5b2c6..8ed2eb99454 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-2962dca31855.nnue" +#define EvalFileDefaultNameBig "nn-c288c895ea92.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 091ae074da1..ce23bdf0ee6 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -164,23 +164,13 @@ class FeatureTransformer { if (UseThreats) { - auto combinedWeights = - std::make_unique>(); + read_little_endian(stream, threatWeights.data(), + ThreatInputDimensions * HalfDimensions); + read_leb_128(stream, weights); + auto combinedPsqtWeights = std::make_unique>(); - read_leb_128(stream, *combinedWeights); - - std::transform(combinedWeights->begin(), - combinedWeights->begin() + ThreatInputDimensions * HalfDimensions, - std::begin(threatWeights), - [](WeightType w) { return static_cast(w); }); - - std::copy(combinedWeights->begin() + ThreatInputDimensions * HalfDimensions, - combinedWeights->begin() - + (ThreatInputDimensions + InputDimensions) * HalfDimensions, - std::begin(weights)); - read_leb_128(stream, *combinedPsqtWeights); std::copy(combinedPsqtWeights->begin(), @@ -219,21 +209,13 @@ class FeatureTransformer { if (UseThreats) { - auto combinedWeights = - std::make_unique>(); + write_little_endian(stream, copy->threatWeights.data(), + ThreatInputDimensions * HalfDimensions); + write_leb_128(stream, copy->weights); + auto combinedPsqtWeights = std::make_unique>(); - std::copy(std::begin(copy->threatWeights), - std::begin(copy->threatWeights) + ThreatInputDimensions * HalfDimensions, - combinedWeights->begin()); - - std::copy(std::begin(copy->weights), - std::begin(copy->weights) + InputDimensions * HalfDimensions, - combinedWeights->begin() + ThreatInputDimensions * HalfDimensions); - - write_leb_128(stream, *combinedWeights); - std::copy(std::begin(copy->threatPsqtWeights), std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets, combinedPsqtWeights->begin()); From 495296fc76cc380b216d577710c15cd1efef1a41 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 21 Nov 2025 23:25:03 -0800 Subject: [PATCH 1254/1309] Remove Secondary TT Aging Passed VVLTC w/ STC Bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 107006 W: 27676 L: 27326 D: 52004 Ptnml(0-2): 21, 9505, 34097, 9863, 17 https://tests.stockfishchess.org/tests/view/6939ac5b75b70713ef796f11 Passed VVLTC w/ LTC Bounds: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 263190 W: 67547 L: 66837 D: 128806 Ptnml(0-2): 32, 23939, 82942, 24651, 31 https://tests.stockfishchess.org/tests/view/692165823b03dd3a060e666d closes https://github.com/official-stockfish/Stockfish/pull/6480 Bench: 2800411 --- src/tt.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tt.cpp b/src/tt.cpp index 953348987e0..d7f7dbdef6b 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -110,8 +110,6 @@ void TTEntry::save( value16 = int16_t(v); eval16 = int16_t(ev); } - else if (depth8 + DEPTH_ENTRY_OFFSET >= 5 && Bound(genBound8 & 0x3) != BOUND_EXACT) - depth8--; } From c467fe5ba42545827d769c360aa404767308ac18 Mon Sep 17 00:00:00 2001 From: AliceRoselia <63040919+AliceRoselia@users.noreply.github.com> Date: Sun, 14 Dec 2025 14:14:27 +0700 Subject: [PATCH 1255/1309] Simplify futility pruning Passed non regression STC: https://tests.stockfishchess.org/tests/view/693e642c46f342e1ec20f68d LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 107968 W: 28080 L: 27937 D: 51951 Ptnml(0-2): 381, 12708, 27626, 12925, 344 Passed non regression LTC: https://tests.stockfishchess.org/tests/view/693ff10c46f342e1ec20fa6a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 334266 W: 85271 L: 85370 D: 163625 Ptnml(0-2): 179, 36395, 94086, 36292, 181 closes https://github.com/official-stockfish/Stockfish/pull/6484 Bench: 2987379 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 64f85bcbe00..adb7985d2a7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -868,9 +868,8 @@ Value Search::Worker::search( auto futility_margin = [&](Depth d) { Value futilityMult = 76 - 23 * !ss->ttHit; - return futilityMult * d // - - 2474 * improving * futilityMult / 1024 // - - 331 * opponentWorsening * futilityMult / 1024 // + return futilityMult * d + - (2474 * improving + 331 * opponentWorsening) * futilityMult / 1024 // + std::abs(correctionValue) / 174665; }; From fb41f2953f453c4bbe03459f8a93c505ff76a2e8 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 23 Dec 2025 21:27:54 +0100 Subject: [PATCH 1256/1309] Remove low ply history for check evasions scoring Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 81888 W: 21336 L: 21166 D: 39386 Ptnml(0-2): 284, 9438, 21342, 9584, 296 https://tests.stockfishchess.org/tests/view/692ada47b23dfeae38cffce5 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 107328 W: 27534 L: 27404 D: 52390 Ptnml(0-2): 55, 11390, 30659, 11490, 70 https://tests.stockfishchess.org/tests/view/692d7a01b23dfeae38d011ab closes https://github.com/official-stockfish/Stockfish/pull/6467 Bench: 3006182 --- src/movepick.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 7de11fa1f11..d20ab151e8c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -184,11 +184,7 @@ ExtMove* MovePicker::score(MoveList& ml) { if (pos.capture_stage(m)) m.value = PieceValue[capturedPiece] + (1 << 28); else - { m.value = (*mainHistory)[us][m.raw()] + (*continuationHistory[0])[pc][to]; - if (ply < LOW_PLY_HISTORY_SIZE) - m.value += (*lowPlyHistory)[ply][m.raw()]; - } } } return it; From 1a67ccc72ef2e3c06e9c905a793a14416d53643f Mon Sep 17 00:00:00 2001 From: anematode Date: Tue, 23 Dec 2025 21:28:15 +0100 Subject: [PATCH 1257/1309] Share correction history between threads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We did quite a few tests because this is a pretty involved change with unknown scaling behavior, but results are decent. [STC 10+0.1 1th, non-regression](https://tests.stockfishchess.org/tests/live_elo/6941ce3b46f342e1ec210180) ``` LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 83200 W: 21615 L: 21452 D: 40133 Ptnml(0-2): 247, 9064, 22844, 9169, 276 ``` [STC 5+0.05 8th](https://tests.stockfishchess.org/tests/live_elo/693dc38346f342e1ec20f555) ``` LLR: 3.48 (-2.94,2.94) <0.00,2.00> Total: 58536 W: 15067 L: 14688 D: 28781 Ptnml(0-2): 87, 6474, 15781, 6825, 101 ``` [LTC 20+0.2 8th](https://tests.stockfishchess.org/tests/live_elo/693f2afb46f342e1ec20f847) ``` LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 27716 W: 7211 L: 6925 D: 13580 Ptnml(0-2): 8, 2674, 8207, 2962, 7 ``` [LTC 10+0.1 64th](https://tests.stockfishchess.org/tests/live_elo/694003aa46f342e1ec20fac4): ``` LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 16918 W: 4439 L: 4182 D: 8297 Ptnml(0-2): 3, 1493, 5213, 1744, 6 ``` [NUMA test, 5+0.05 256th](https://tests.stockfishchess.org/tests/view/6941ee4e46f342e1ec210203) ``` LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 7124 W: 1910 L: 1678 D: 3536 Ptnml(0-2): 0, 560, 2211, 790, 1 ``` [LTC 60+0.6 64th](https://tests.stockfishchess.org/tests/live_elo/6940a85346f342e1ec20fcde): ``` LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 15504 W: 4045 L: 3826 D: 7633 Ptnml(0-2): 0, 1002, 5530, 1219, 1 ``` Bonus (courtesy of Viz): The 1 double kill in this last test was master blundering a cool mate in 3: https://lichess.org/jyNZuRl4 Basically the idea here is to share correction history between threads. That way, T1 can use the correction values produced by T2, which already searched positions with that pawn structure etc., so that T1 can search more efficiently. The table size per thread is about the same, so we shouldn't get a large increase in hash collisions; in fact, I'd expect a lower collision rate overall. Although I came up with and implemented the idea independently, [Caissa](https://github.com/Witek902/Caissa) was the first engine to implement corrhist sharing (and corrhist in the first place) – this idea is not completely novel. The table size is rounded to a power of two. In particular, it's `65536 * nextPowerOfTwo(threadCount)`. That way, the indexing operation becomes an AND of the key bits with a mask, rather than something more expensive (e.g., a `mul_hi64`-style approach or a modulo). The updates are racy, like the TT, but because `entry` is hoisted into a register, there's no risk of writing back a value that's out of the designated range `[-D, D]`. Various attempts at rewriting using atomics led to substantial slowdowns, so we begrudgingly ignored the functions in thread sanitizer, but at some point we'd like to make this better. We allocate one shared correction history per NUMA node, because the penalty associated with crossing nodes is substantial – I get a 40% hit with NPS=4 and 256 threads, which is intolerable. With separate tables per NUMA node I get a 6% penalty for nodes per second, which isn't ideal but apparently compensated for. closes https://github.com/official-stockfish/Stockfish/pull/6478 Bench: 2690604 Co-authored-by: Disservin --- src/engine.cpp | 3 +- src/engine.h | 3 + src/history.h | 140 ++++++++++++++++++++++++++++++++++++++--------- src/position.cpp | 14 ++++- src/position.h | 7 ++- src/search.cpp | 45 +++++++++------ src/search.h | 27 +++++---- src/thread.cpp | 44 +++++++++++++-- src/thread.h | 4 +- 9 files changed, 220 insertions(+), 67 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 9edff486476..40466c8f874 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -240,7 +240,8 @@ void Engine::set_numa_config_from_option(const std::string& o) { void Engine::resize_threads() { threads.wait_for_search_finished(); - threads.set(numaContext.get_numa_config(), {options, threads, tt, networks}, updateContext); + threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, networks}, + updateContext); // Reallocate the hash with the new threadpool size set_tt_size(options["Hash"]); diff --git a/src/engine.h b/src/engine.h index 7315b88815f..6fd1ce04002 100644 --- a/src/engine.h +++ b/src/engine.h @@ -22,12 +22,14 @@ #include #include #include +#include #include #include #include #include #include +#include "history.h" #include "nnue/network.h" #include "numa.h" #include "position.h" @@ -122,6 +124,7 @@ class Engine { Search::SearchManager::UpdateContext updateContext; std::function onVerifyNetworks; + std::map sharedHists; }; } // namespace Stockfish diff --git a/src/history.h b/src/history.h index 87004ead9cf..127fb9ef3ba 100644 --- a/src/history.h +++ b/src/history.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include // IWYU pragma: keep +#include "memory.h" #include "misc.h" #include "position.h" @@ -35,56 +37,54 @@ namespace Stockfish { constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits::max() + 1; +constexpr int CORRHIST_BASE_SIZE = UINT_16_HISTORY_SIZE; constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -static_assert((UINT_16_HISTORY_SIZE & (UINT_16_HISTORY_SIZE - 1)) == 0, - "CORRECTION_HISTORY_SIZE has to be a power of 2"); +static_assert((CORRHIST_BASE_SIZE & (CORRHIST_BASE_SIZE - 1)) == 0, + "CORRHIST_BASE_SIZE has to be a power of 2"); inline int pawn_history_index(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } -inline uint16_t pawn_correction_history_index(const Position& pos) { - return uint16_t(pos.pawn_key()); -} - -inline uint16_t minor_piece_index(const Position& pos) { return uint16_t(pos.minor_piece_key()); } - -template -inline uint16_t non_pawn_index(const Position& pos) { - return uint16_t(pos.non_pawn_key(c)); -} - // StatsEntry is the container of various numerical statistics. We use a class // instead of a naked value to directly call history update operator<<() on // the entry. The first template parameter T is the base type of the array, // and the second template parameter D limits the range of updates in [-D, D] // when we update values with the << operator -template -class StatsEntry { - +template +struct StatsEntry { static_assert(std::is_arithmetic_v, "Not an arithmetic type"); - static_assert(D <= std::numeric_limits::max(), "D overflows T"); - T entry; + private: + std::conditional_t, T> entry; public: - StatsEntry& operator=(const T& v) { - entry = v; - return *this; + void operator=(const T& v) { + if constexpr (Atomic) + entry.store(v, std::memory_order_relaxed); + else + entry = v; + } + + operator T() const { + if constexpr (Atomic) + return entry.load(std::memory_order_relaxed); + else + return entry; } - operator const T&() const { return entry; } void operator<<(int bonus) { // Make sure that bonus is in range [-D, D] int clampedBonus = std::clamp(bonus, -D, D); - entry += clampedBonus - entry * std::abs(clampedBonus) / D; + T val = *this; + *this = val + clampedBonus - val * std::abs(clampedBonus) / D; - assert(std::abs(entry) <= D); + assert(std::abs(T(*this)) <= D); } }; @@ -96,6 +96,37 @@ enum StatsType { template using Stats = MultiArray, Sizes...>; +// DynStats is a dynamically sized array of Stats, used for thread-shared histories +// which should scale with the total number of threads. The SizeMultiplier gives +// the per-thread allocation count of T. +template +struct DynStats { + explicit DynStats(size_t s) { + size = s * SizeMultiplier; + data = make_unique_large_page(size); + } + // Sets all values in the range to 0 + void clear_range(size_t start, size_t end) { + assert(start < size); + assert(end <= size); + T* fill_start = &(*this)[start]; + memset(reinterpret_cast(fill_start), 0, sizeof(T) * (end - start)); + } + size_t get_size() const { return size; } + T& operator[](size_t index) { + assert(index < size); + return data.get()[index]; + } + const T& operator[](size_t index) const { + assert(index < size); + return data.get()[index]; + } + + private: + size_t size; + LargePagePtr data; +}; + // ButterflyHistory records how often quiet moves have been successful or unsuccessful // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, @@ -132,11 +163,20 @@ enum CorrHistType { Continuation, // Combined history of move pairs }; +template +struct CorrectionBundle { + StatsEntry pawn; + StatsEntry minor; + StatsEntry nonPawnWhite; + StatsEntry nonPawnBlack; +}; + namespace Detail { template struct CorrHistTypedef { - using type = Stats; + using type = + DynStats, CORRHIST_BASE_SIZE>; }; template<> @@ -151,17 +191,63 @@ struct CorrHistTypedef { template<> struct CorrHistTypedef { - using type = - Stats; + using type = DynStats, + CORRHIST_BASE_SIZE>; }; } +using UnifiedCorrectionHistory = + DynStats, COLOR_NB>, + CORRHIST_BASE_SIZE>; + template using CorrectionHistory = typename Detail::CorrHistTypedef::type; using TTMoveHistory = StatsEntry; +// Set of histories shared between groups of threads. To avoid excessive +// cross-node data transfer, histories are shared only between threads +// on a given NUMA node. The passed size must be a power of two to make +// the indexing more efficient. +struct SharedHistories { + SharedHistories(size_t threadCount) : + correctionHistory(threadCount) { + assert((threadCount & (threadCount - 1)) == 0 && threadCount != 0); + sizeMinus1 = correctionHistory.get_size() - 1; + } + + size_t get_size() const { return sizeMinus1 + 1; } + + auto& pawn_correction_entry(const Position& pos) { + return correctionHistory[pos.pawn_key() & sizeMinus1]; + } + const auto& pawn_correction_entry(const Position& pos) const { + return correctionHistory[pos.pawn_key() & sizeMinus1]; + } + + auto& minor_piece_correction_entry(const Position& pos) { + return correctionHistory[pos.minor_piece_key() & sizeMinus1]; + } + const auto& minor_piece_correction_entry(const Position& pos) const { + return correctionHistory[pos.minor_piece_key() & sizeMinus1]; + } + + template + auto& nonpawn_correction_entry(const Position& pos) { + return correctionHistory[pos.non_pawn_key(c) & sizeMinus1]; + } + template + const auto& nonpawn_correction_entry(const Position& pos) const { + return correctionHistory[pos.non_pawn_key(c) & sizeMinus1]; + } + + UnifiedCorrectionHistory correctionHistory; + + private: + size_t sizeMinus1; +}; + } // namespace Stockfish #endif // #ifndef HISTORY_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp index a4286b86a86..7377b20290f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -32,6 +32,7 @@ #include #include "bitboard.h" +#include "history.h" #include "misc.h" #include "movegen.h" #include "syzygy/tbprobe.h" @@ -692,13 +693,14 @@ bool Position::gives_check(Move m) const { // to a StateInfo object. The move is assumed to be legal. Pseudo-legal // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position -// will be prefetched +// will be prefetched, and likewise for shared history. void Position::do_move(Move m, StateInfo& newSt, bool givesCheck, DirtyPiece& dp, DirtyThreats& dts, - const TranspositionTable* tt = nullptr) { + const TranspositionTable* tt = nullptr, + const SharedHistories* history = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -880,6 +882,14 @@ void Position::do_move(Move m, if (tt && !checkEP) prefetch(tt->first_entry(adjust_key50(k))); + if (history) + { + prefetch(&history->pawn_correction_entry(*this)); + prefetch(&history->minor_piece_correction_entry(*this)); + prefetch(&history->nonpawn_correction_entry(*this)); + prefetch(&history->nonpawn_correction_entry(*this)); + } + // Set capture piece st->capturedPiece = captured; diff --git a/src/position.h b/src/position.h index e5d3f6d28b5..e49e10f963c 100644 --- a/src/position.h +++ b/src/position.h @@ -33,6 +33,7 @@ namespace Stockfish { class TranspositionTable; +struct SharedHistories; // StateInfo struct stores information needed to restore a Position object to // its previous state when we retract a move. Whenever a move is made on the @@ -69,7 +70,6 @@ struct StateInfo { // elements are not invalidated upon list resizing. using StateListPtr = std::unique_ptr>; - // Position class stores information regarding the board representation as // pieces, side to move, hash keys, castling info, etc. Important methods are // do_move() and undo_move(), used by the search to update node info when @@ -140,7 +140,8 @@ class Position { bool givesCheck, DirtyPiece& dp, DirtyThreats& dts, - const TranspositionTable* tt); + const TranspositionTable* tt, + const SharedHistories* worker); void undo_move(Move m); void do_null_move(StateInfo& newSt, const TranspositionTable& tt); void undo_null_move(); @@ -403,7 +404,7 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { new (&scratch_dts) DirtyThreats; - do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt); + do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt, nullptr); } inline StateInfo* Position::state() const { return st; } diff --git a/src/search.cpp b/src/search.cpp index adb7985d2a7..7c08bb0fa44 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -77,16 +77,17 @@ using SearchedList = ValueList; // optimized for require verifications at longer time controls 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[pawn_correction_history_index(pos)][us]; - const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; - const auto wnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us]; - const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; - const auto cntcv = + const Color us = pos.side_to_move(); + const auto m = (ss - 1)->currentMove; + const auto& shared = w.sharedHistory; + const int pcv = shared.pawn_correction_entry(pos).at(us).pawn; + const int micv = shared.minor_piece_correction_entry(pos).at(us).minor; + const int wnpcv = shared.nonpawn_correction_entry(pos).at(us).nonPawnWhite; + const int bnpcv = shared.nonpawn_correction_entry(pos).at(us).nonPawnBlack; + const int cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - : 8; + : 8; return 10347 * pcv + 8821 * micv + 11168 * (wnpcv + bnpcv) + 7841 * cntcv; } @@ -105,13 +106,12 @@ void update_correction_history(const Position& pos, const Color us = pos.side_to_move(); constexpr int nonPawnWeight = 178; + auto& shared = workerThread.sharedHistory; - workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 156 / 128; - workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] - << bonus * nonPawnWeight / 128; - workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] - << bonus * nonPawnWeight / 128; + shared.pawn_correction_entry(pos).at(us).pawn << bonus; + shared.minor_piece_correction_entry(pos).at(us).minor << bonus * 156 / 128; + shared.nonpawn_correction_entry(pos).at(us).nonPawnWhite << bonus * nonPawnWeight / 128; + shared.nonpawn_correction_entry(pos).at(us).nonPawnBlack << bonus * nonPawnWeight / 128; if (m.is_ok()) { @@ -155,9 +155,14 @@ bool is_shuffling(Move move, Stack* const ss, const Position& pos) { Search::Worker::Worker(SharedState& sharedState, std::unique_ptr sm, size_t threadId, + size_t numaThreadId, + size_t numaTotalThreads, NumaReplicatedAccessToken token) : // Unpack the SharedState struct into member variables + sharedHistory(sharedState.sharedHistories.at(token.get_numa_index())), threadIdx(threadId), + numaThreadIdx(numaThreadId), + numaTotal(numaTotalThreads), numaAccessToken(token), manager(std::move(sm)), options(sharedState.options), @@ -544,7 +549,7 @@ void Search::Worker::do_move( nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); - pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); + pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt, &sharedHistory); if (ss != nullptr) { @@ -576,9 +581,13 @@ void Search::Worker::clear() { mainHistory.fill(68); captureHistory.fill(-689); pawnHistory.fill(-1238); - pawnCorrectionHistory.fill(5); - minorPieceCorrectionHistory.fill(0); - nonPawnCorrectionHistory.fill(0); + + // Each thread is responsible for clearing their part of shared history + size_t len = sharedHistory.get_size() / numaTotal; + size_t start = numaThreadIdx * len; + size_t end = std::min(start + len, sharedHistory.get_size()); + + sharedHistory.correctionHistory.clear_range(start, end); ttMoveHistory = 0; diff --git a/src/search.h b/src/search.h index f3d99d41529..9644f6aa52e 100644 --- a/src/search.h +++ b/src/search.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -136,15 +137,18 @@ struct SharedState { SharedState(const OptionsMap& optionsMap, ThreadPool& threadPool, TranspositionTable& transpositionTable, + std::map& sharedHists, const LazyNumaReplicatedSystemWide& nets) : options(optionsMap), threads(threadPool), tt(transpositionTable), + sharedHistories(sharedHists), networks(nets) {} const OptionsMap& options; ThreadPool& threads; TranspositionTable& tt; + std::map& sharedHistories; const LazyNumaReplicatedSystemWide& networks; }; @@ -258,13 +262,17 @@ class NullSearchManager: public ISearchManager { void check_time(Search::Worker&) override {} }; - // Search::Worker is the class that does the actual search. // It is instantiated once per thread, and it is responsible for keeping track // of the search history, and storing data required for the search. class Worker { public: - Worker(SharedState&, std::unique_ptr, size_t, NumaReplicatedAccessToken); + Worker(SharedState&, + std::unique_ptr, + size_t, + size_t, + size_t, + NumaReplicatedAccessToken); // Called at instantiation to initialize reductions tables. // Reset histories, usually before a new game. @@ -282,16 +290,13 @@ class Worker { ButterflyHistory mainHistory; LowPlyHistory lowPlyHistory; - CapturePieceToHistory captureHistory; - ContinuationHistory continuationHistory[2][2]; - PawnHistory pawnHistory; - - CorrectionHistory pawnCorrectionHistory; - CorrectionHistory minorPieceCorrectionHistory; - CorrectionHistory nonPawnCorrectionHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + PawnHistory pawnHistory; CorrectionHistory continuationCorrectionHistory; - TTMoveHistory ttMoveHistory; + TTMoveHistory ttMoveHistory; + SharedHistories& sharedHistory; private: void iterative_deepening(); @@ -338,7 +343,7 @@ class Worker { Depth rootDepth, completedDepth; Value rootDelta; - size_t threadIdx; + size_t threadIdx, numaThreadIdx, numaTotal; NumaReplicatedAccessToken numaAccessToken; // Reductions lookup table initialized at startup diff --git a/src/thread.cpp b/src/thread.cpp index 58840a87448..eaf1d09bdee 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -21,11 +21,14 @@ #include #include #include +#include #include #include #include #include +#include "bitboard.h" +#include "history.h" #include "memory.h" #include "movegen.h" #include "search.h" @@ -42,8 +45,12 @@ namespace Stockfish { Thread::Thread(Search::SharedState& sharedState, std::unique_ptr sm, size_t n, + size_t numaN, + size_t totalNumaCount, OptionalThreadToNumaNodeBinder binder) : idx(n), + idxInNuma(numaN), + totalNuma(totalNumaCount), nthreads(sharedState.options["Threads"]), stdThread(&Thread::idle_loop, this) { @@ -54,8 +61,8 @@ Thread::Thread(Search::SharedState& sharedState, // the Worker allocation. Ideally we would also allocate the SearchManager // here, but that's minor. this->numaAccessToken = binder(); - this->worker = make_unique_large_page(sharedState, std::move(sm), n, - this->numaAccessToken); + this->worker = make_unique_large_page( + sharedState, std::move(sm), n, idxInNuma, totalNuma, this->numaAccessToken); }); wait_for_search_finished(); @@ -134,6 +141,8 @@ Search::SearchManager* ThreadPool::main_manager() { return main_thread()->worker uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); } uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); } +static size_t next_power_of_two(uint64_t count) { return count > 1 ? (2ULL << msb(count - 1)) : 1; } + // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. @@ -172,10 +181,36 @@ void ThreadPool::set(const NumaConfig& numaConfig, return true; }(); + std::map counts; boundThreadToNumaNode = doBindThreads ? numaConfig.distribute_threads_among_numa_nodes(requested) : std::vector{}; + if (boundThreadToNumaNode.empty()) + counts[0] = requested; // Pretend all threads are part of numa node 0 + else + { + for (size_t i = 0; i < boundThreadToNumaNode.size(); ++i) + counts[boundThreadToNumaNode[i]]++; + } + + sharedState.sharedHistories.clear(); + for (auto pair : counts) + { + NumaIndex numaIndex = pair.first; + uint64_t count = pair.second; + auto f = [&]() { + sharedState.sharedHistories.try_emplace(numaIndex, next_power_of_two(count)); + }; + if (doBindThreads) + numaConfig.execute_on_numa_node(numaIndex, f); + else + f(); + } + + auto threadsPerNode = counts; + counts.clear(); + while (threads.size() < requested) { const size_t threadId = threads.size(); @@ -191,8 +226,9 @@ void ThreadPool::set(const NumaConfig& numaConfig, auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId) : OptionalThreadToNumaNodeBinder(numaId); - threads.emplace_back( - std::make_unique(sharedState, std::move(manager), threadId, binder)); + threads.emplace_back(std::make_unique(sharedState, std::move(manager), threadId, + counts[numaId]++, threadsPerNode[numaId], + binder)); } clear(); diff --git a/src/thread.h b/src/thread.h index 79376b10a8e..2368c3069c1 100644 --- a/src/thread.h +++ b/src/thread.h @@ -76,6 +76,8 @@ class Thread { Thread(Search::SharedState&, std::unique_ptr, size_t, + size_t, + size_t, OptionalThreadToNumaNodeBinder); virtual ~Thread(); @@ -100,7 +102,7 @@ class Thread { private: std::mutex mutex; std::condition_variable cv; - size_t idx, nthreads; + size_t idx, idxInNuma, totalNuma, nthreads; bool exit = false, searching = true; // Set before starting std::thread NativeThread stdThread; NumaReplicatedAccessToken numaAccessToken; From 447f66acac30f5f56c4cfbea3dcc2f90504f77f7 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 23 Dec 2025 21:28:51 +0100 Subject: [PATCH 1258/1309] Less penalty for quiet late moves that didn't beat the best move. This moves since they are late in move ordering probably already have pretty bad stats anyway. Passed STC: https://tests.stockfishchess.org/tests/view/6943bcd546f342e1ec210e25 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 96704 W: 25206 L: 24798 D: 46700 Ptnml(0-2): 357, 11244, 24767, 11602, 382 Passed LTC: https://tests.stockfishchess.org/tests/view/6946a8723c8768ca450722f0 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 89814 W: 23193 L: 22770 D: 43851 Ptnml(0-2): 53, 9532, 25321, 9941, 60 bench 2717363 closes https://github.com/official-stockfish/Stockfish/pull/6485 Bench: 2791988 --- src/search.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 7c08bb0fa44..377b9634310 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1831,9 +1831,16 @@ void update_all_stats(const Position& pos, { update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 910 / 1024); + int i = 0; // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1085 / 1024); + { + i++; + int actualMalus = malus * 1085 / 1024; + if (i > 5) + actualMalus -= actualMalus * (i - 5) / i; + update_quiet_histories(pos, ss, workerThread, move, -actualMalus); + } } else { From 4d4c6ebd0255f29a45fb5e071fc7471ab0adf316 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 23 Dec 2025 21:29:16 +0100 Subject: [PATCH 1259/1309] Simplify doDeeperSearch formula Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 211776 W: 54939 L: 54911 D: 101926 Ptnml(0-2): 714, 24971, 54484, 25011, 708 https://tests.stockfishchess.org/tests/view/6938971875b70713ef796b70 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 216774 W: 55346 L: 55326 D: 106102 Ptnml(0-2): 105, 23599, 60980, 23577, 126 https://tests.stockfishchess.org/tests/view/693fc91f46f342e1ec20f9f6 closes https://github.com/official-stockfish/Stockfish/pull/6486 Bench: 3267755 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 377b9634310..9c52592e0d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1241,7 +1241,7 @@ Value Search::Worker::search( { // 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 = d < newDepth && value > (bestValue + 43 + 2 * newDepth); + const bool doDeeperSearch = d < newDepth && value > (bestValue + newDepth + 44); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; From 73b3b1859518e88c9c292d2efbd7debe57d7351d Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 23 Dec 2025 21:30:16 +0100 Subject: [PATCH 1260/1309] Init threat offsets at compile time Init threat offsets at compile time. Avoid another global init function call. Passed STC Non-Regression: https://tests.stockfishchess.org/tests/view/694971a83c8768ca4507275c LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 43296 W: 11284 L: 11077 D: 20935 Ptnml(0-2): 152, 4611, 11924, 4800, 161 closes https://github.com/official-stockfish/Stockfish/pull/6487 No functional change --- src/bitboard.cpp | 48 +------ src/bitboard.h | 218 ++++++++++++++++++++--------- src/main.cpp | 2 - src/nnue/features/full_threats.cpp | 162 +++++++++++++++------ src/nnue/features/full_threats.h | 1 - src/syzygy/tbprobe.cpp | 1 + 6 files changed, 276 insertions(+), 156 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 6c97d786333..350e56c927f 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -32,7 +32,6 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; -Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; alignas(64) Magic Magics[SQUARE_NB][2]; @@ -42,13 +41,6 @@ Bitboard RookTable[0x19000]; // To store rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]); - -// Returns the bitboard of target square for the given step -// from the given square. If the step is off the board, returns empty bitboard. -Bitboard safe_destination(Square s, int step) { - Square to = Square(s + step); - return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); -} } // Returns an ASCII representation of a bitboard suitable @@ -86,18 +78,6 @@ void Bitboards::init() { for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { - PseudoAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - PseudoAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); - - for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) - PseudoAttacks[KING][s1] |= safe_destination(s1, step); - - for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) - PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step); - - PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); - PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb(s1, 0); - for (PieceType pt : {BISHOP, ROOK}) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) { @@ -115,30 +95,6 @@ void Bitboards::init() { } namespace { - -Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { - - Bitboard attacks = 0; - Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; - Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; - - for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) - { - Square s = sq; - while (safe_destination(s, d)) - { - attacks |= (s += d); - if (occupied & s) - { - break; - } - } - } - - return attacks; -} - - // Computes all rook and bishop attacks at startup. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see // https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use @@ -167,7 +123,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. Magic& m = magics[s][pt - BISHOP]; - m.mask = sliding_attack(pt, s, 0) & ~edges; + m.mask = Bitboards::sliding_attack(pt, s, 0) & ~edges; #ifndef USE_PEXT m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); #endif @@ -184,7 +140,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { #ifndef USE_PEXT occupancy[size] = b; #endif - reference[size] = sliding_attack(pt, s, b); + reference[size] = Bitboards::sliding_attack(pt, s, b); if (HasPext) m.attacks[pext(b, m.mask)] = reference[size]; diff --git a/src/bitboard.h b/src/bitboard.h index 1cd717b8f17..1da11d45c31 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include "types.h" @@ -62,8 +64,6 @@ extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; -extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; - // Magic holds all magic bitboards relevant data for a single square struct Magic { @@ -203,69 +203,12 @@ inline int distance(Square x, Square y) { inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -// Returns the pseudo attacks of the given piece type -// assuming an empty board. -template -inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) { - - assert((Pt != PAWN || c < COLOR_NB) && (is_ok(s))); - return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s]; -} - - -// Returns the attacks by the given piece -// assuming the board is occupied according to the passed Bitboard. -// Sliding piece attacks do not continue passed an occupied square. -template -inline Bitboard attacks_bb(Square s, Bitboard occupied) { - - assert((Pt != PAWN) && (is_ok(s))); - - switch (Pt) - { - case BISHOP : - case ROOK : - return Magics[s][Pt - BISHOP].attacks_bb(occupied); - case QUEEN : - return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : - return PseudoAttacks[Pt][s]; - } -} - -// Returns the attacks by the given piece -// assuming the board is occupied according to the passed Bitboard. -// Sliding piece attacks do not continue passed an occupied square. -inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { - - assert((pt != PAWN) && (is_ok(s))); - - switch (pt) - { - case BISHOP : - return attacks_bb(s, occupied); - case ROOK : - return attacks_bb(s, occupied); - case QUEEN : - return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : - return PseudoAttacks[pt][s]; - } -} - -inline Bitboard attacks_bb(Piece pc, Square s) { - if (type_of(pc) == PAWN) - return PseudoAttacks[color_of(pc)][s]; - return PseudoAttacks[type_of(pc)][s]; -} - - -inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { - if (type_of(pc) == PAWN) - return PseudoAttacks[color_of(pc)][s]; - - return attacks_bb(type_of(pc), s, occupied); +constexpr int constexpr_popcount(Bitboard b) { + b = b - ((b >> 1) & 0x5555555555555555ULL); + b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL); + b = (b + (b >> 4)) & 0x0F0F0F0F0F0F0F0FULL; + return static_cast((b * 0x0101010101010101ULL) >> 56); } // Counts the number of non-zero bits in a bitboard. @@ -373,6 +316,153 @@ inline Square pop_lsb(Bitboard& b) { return s; } +namespace Bitboards { +// Returns the bitboard of target square for the given step +// from the given square. If the step is off the board, returns empty bitboard. +constexpr Bitboard safe_destination(Square s, int step) { + constexpr auto abs = [](int v) { return v < 0 ? -v : v; }; + Square to = Square(s + step); + return is_ok(to) && abs(file_of(s) - file_of(to)) <= 2 ? square_bb(to) : Bitboard(0); +} + +constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { + Bitboard attacks = 0; + Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + + for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) + { + Square s = sq; + while (safe_destination(s, d)) + { + attacks |= (s += d); + if (occupied & s) + { + break; + } + } + } + + return attacks; +} + +constexpr Bitboard knight_attack(Square sq) { + Bitboard b = {}; + for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) + b |= safe_destination(sq, step); + return b; +} + +constexpr Bitboard king_attack(Square sq) { + Bitboard b = {}; + for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) + b |= safe_destination(sq, step); + return b; +} + +constexpr Bitboard pseudo_attacks(PieceType pt, Square sq) { + switch (pt) + { + case PieceType::ROOK : + case PieceType::BISHOP : + return sliding_attack(pt, sq, 0); + case PieceType::QUEEN : + return sliding_attack(PieceType::ROOK, sq, 0) | sliding_attack(PieceType::BISHOP, sq, 0); + case PieceType::KNIGHT : + return knight_attack(sq); + case PieceType::KING : + return king_attack(sq); + default : + assert(false); + return 0; + } +} + +} + +inline constexpr auto PseudoAttacks = []() constexpr { + std::array, PIECE_TYPE_NB> attacks{}; + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + attacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + attacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + + attacks[KING][s1] = Bitboards::pseudo_attacks(KING, s1); + attacks[KNIGHT][s1] = Bitboards::pseudo_attacks(KNIGHT, s1); + attacks[QUEEN][s1] = attacks[BISHOP][s1] = Bitboards::pseudo_attacks(BISHOP, s1); + attacks[QUEEN][s1] |= attacks[ROOK][s1] = Bitboards::pseudo_attacks(ROOK, s1); + } + + return attacks; +}(); + + +// Returns the pseudo attacks of the given piece type +// assuming an empty board. +template +inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) { + + assert((Pt != PAWN || c < COLOR_NB) && (is_ok(s))); + return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s]; +} + + +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. +template +inline Bitboard attacks_bb(Square s, Bitboard occupied) { + + assert((Pt != PAWN) && (is_ok(s))); + + switch (Pt) + { + case BISHOP : + case ROOK : + return Magics[s][Pt - BISHOP].attacks_bb(occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[Pt][s]; + } +} + +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. +inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { + + assert((pt != PAWN) && (is_ok(s))); + + switch (pt) + { + case BISHOP : + return attacks_bb(s, occupied); + case ROOK : + return attacks_bb(s, occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[pt][s]; + } +} + +inline Bitboard attacks_bb(Piece pc, Square s) { + if (type_of(pc) == PAWN) + return PseudoAttacks[color_of(pc)][s]; + + return PseudoAttacks[type_of(pc)][s]; +} + + +inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { + if (type_of(pc) == PAWN) + return PseudoAttacks[color_of(pc)][s]; + + return attacks_bb(type_of(pc), s, occupied); +} + } // namespace Stockfish #endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index 6ab3507f3be..107b5e43d5f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,7 +21,6 @@ #include "bitboard.h" #include "misc.h" -#include "nnue/features/full_threats.h" #include "position.h" #include "tune.h" #include "uci.h" @@ -33,7 +32,6 @@ int main(int argc, char* argv[]) { Bitboards::init(); Position::init(); - Eval::NNUE::Features::init_threat_offsets(); auto uci = std::make_unique(argc, argv); diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 645bb7090bb..63b7f8e1394 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -21,7 +21,9 @@ #include "full_threats.h" #include +#include #include +#include #include "../../bitboard.h" #include "../../misc.h" @@ -31,26 +33,28 @@ namespace Stockfish::Eval::NNUE::Features { -// Lookup array for indexing threats -IndexType offsets[PIECE_NB][SQUARE_NB]; - struct HelperOffsets { int cumulativePieceOffset, cumulativeOffset; }; -std::array helper_offsets; // Information on a particular pair of pieces and whether they should be excluded struct PiecePairData { // Layout: bits 8..31 are the index contribution of this piece pair, bits 0 and 1 are exclusion info uint32_t data; - PiecePairData() {} - PiecePairData(bool excluded_pair, bool semi_excluded_pair, IndexType feature_index_base) { - data = - excluded_pair << 1 | (semi_excluded_pair && !excluded_pair) | feature_index_base << 8; - } + + constexpr PiecePairData() : + data(0) {} + + constexpr PiecePairData(bool excluded_pair, + bool semi_excluded_pair, + IndexType feature_index_base) : + data((uint32_t(excluded_pair) << 1) | (uint32_t(semi_excluded_pair && !excluded_pair)) + | (uint32_t(feature_index_base) << 8)) {} + // lsb: excluded if from < to; 2nd lsb: always excluded - uint8_t excluded_pair_info() const { return (uint8_t) data; } - IndexType feature_index_base() const { return data >> 8; } + constexpr uint8_t excluded_pair_info() const { return static_cast(data); } + + constexpr IndexType feature_index_base() const { return static_cast(data >> 8); } }; constexpr std::array AllPieces = { @@ -58,45 +62,80 @@ constexpr std::array AllPieces = { B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, }; -// The final index is calculated from summing data found in these two LUTs, as well -// as offsets[attacker][from] -PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] -uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] +template +constexpr auto make_piece_indices_type() { + static_assert(PT != PieceType::PAWN); -static void init_index_luts() { - for (Piece attacker : AllPieces) - { - for (Piece attacked : AllPieces) - { - bool enemy = (attacker ^ attacked) == 8; - PieceType attackerType = type_of(attacker); - PieceType attackedType = type_of(attacked); + std::array, SQUARE_NB> out{}; - int map = FullThreats::map[attackerType - 1][attackedType - 1]; - bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN); - IndexType feature = helper_offsets[attacker].cumulativeOffset - + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) - * helper_offsets[attacker].cumulativePieceOffset; + for (int from = 0; from < SQUARE_NB; ++from) + { + Bitboard attacks = PseudoAttacks[PT][Square(from)]; - bool excluded = map < 0; - index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); + for (int to = 0; to < SQUARE_NB; ++to) + { + out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks); } } - for (Piece attacker : AllPieces) + return out; +} + +template +constexpr auto make_piece_indices_piece() { + static_assert(type_of(P) == PieceType::PAWN); + + std::array, SQUARE_NB> out{}; + + constexpr Color C = color_of(P); + + for (int from = 0; from < SQUARE_NB; ++from) { - for (int from = 0; from < SQUARE_NB; ++from) + Bitboard attacks = PseudoAttacks[C][from]; + + for (int to = 0; to < SQUARE_NB; ++to) { - for (int to = 0; to < SQUARE_NB; ++to) - { - Bitboard attacks = attacks_bb(attacker, Square(from)); - index_lut2[attacker][from][to] = popcount((square_bb(Square(to)) - 1) & attacks); - } + out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks); } } + + return out; +} + +constexpr auto index_lut2_array() { + constexpr auto KNIGHT_ATTACKS = make_piece_indices_type(); + constexpr auto BISHOP_ATTACKS = make_piece_indices_type(); + constexpr auto ROOK_ATTACKS = make_piece_indices_type(); + constexpr auto QUEEN_ATTACKS = make_piece_indices_type(); + constexpr auto KING_ATTACKS = make_piece_indices_type(); + + std::array, SQUARE_NB>, PIECE_NB> indices{}; + + indices[W_PAWN] = make_piece_indices_piece(); + indices[B_PAWN] = make_piece_indices_piece(); + + indices[W_KNIGHT] = KNIGHT_ATTACKS; + indices[B_KNIGHT] = KNIGHT_ATTACKS; + + indices[W_BISHOP] = BISHOP_ATTACKS; + indices[B_BISHOP] = BISHOP_ATTACKS; + + indices[W_ROOK] = ROOK_ATTACKS; + indices[B_ROOK] = ROOK_ATTACKS; + + indices[W_QUEEN] = QUEEN_ATTACKS; + indices[B_QUEEN] = QUEEN_ATTACKS; + + indices[W_KING] = KING_ATTACKS; + indices[B_KING] = KING_ATTACKS; + + return indices; } -void init_threat_offsets() { +constexpr auto init_threat_offsets() { + std::array indices{}; + std::array, PIECE_NB> offsets{}; + int cumulativeOffset = 0; for (Piece piece : AllPieces) { @@ -109,26 +148,63 @@ void init_threat_offsets() { if (type_of(piece) != PAWN) { - Bitboard attacks = attacks_bb(piece, from, 0ULL); - cumulativePieceOffset += popcount(attacks); + Bitboard attacks = PseudoAttacks[type_of(piece)][from]; + cumulativePieceOffset += constexpr_popcount(attacks); } else if (from >= SQ_A2 && from <= SQ_H7) { Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb(square_bb(from)) : pawn_attacks_bb(square_bb(from)); - cumulativePieceOffset += popcount(attacks); + cumulativePieceOffset += constexpr_popcount(attacks); } } - helper_offsets[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; + indices[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; } - init_index_luts(); + return std::pair{indices, offsets}; } +constexpr auto helper_offsets = init_threat_offsets().first; +// Lookup array for indexing threats +constexpr auto offsets = init_threat_offsets().second; + +constexpr auto init_index_luts() { + std::array, PIECE_NB> indices{}; + + for (Piece attacker : AllPieces) + { + for (Piece attacked : AllPieces) + { + bool enemy = (attacker ^ attacked) == 8; + PieceType attackerType = type_of(attacker); + PieceType attackedType = type_of(attacked); + + int map = FullThreats::map[attackerType - 1][attackedType - 1]; + bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN); + IndexType feature = helper_offsets[attacker].cumulativeOffset + + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) + * helper_offsets[attacker].cumulativePieceOffset; + + bool excluded = map < 0; + indices[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); + } + } + + return indices; +} + +// The final index is calculated from summing data found in these two LUTs, as well +// as offsets[attacker][from] + +// [attacker][attacked] +constexpr auto index_lut1 = init_index_luts(); +// [attacker][from][to] +constexpr auto index_lut2 = index_lut2_array(); + // Index of a feature for a given king position and another piece on some square inline sf_always_inline IndexType FullThreats::make_index( Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index 177e9fabe7f..e4fa3331bac 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -32,7 +32,6 @@ namespace Stockfish::Eval::NNUE::Features { static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0, 0, 6, 12, 10, 10, 12, 8, 0}; -void init_threat_offsets(); class FullThreats { public: diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index e3f7c0a1882..c371259d67d 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include "../bitboard.h" #include "../misc.h" From c475024be75c1d239b6410aa8ec3122fb5b4260c Mon Sep 17 00:00:00 2001 From: Taras Vuk <117687515+TarasVuk@users.noreply.github.com> Date: Tue, 23 Dec 2025 21:31:48 +0100 Subject: [PATCH 1261/1309] Incorporate statscore into history bonus Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 80128 W: 20879 L: 20498 D: 38751 Ptnml(0-2): 274, 9318, 20496, 9705, 271 https://tests.stockfishchess.org/tests/view/6945d11f3c8768ca45072218 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 134298 W: 34497 L: 33983 D: 65818 Ptnml(0-2): 81, 14334, 37812, 14834, 88 https://tests.stockfishchess.org/tests/view/6947bf033c8768ca45072491 closes https://github.com/official-stockfish/Stockfish/pull/6488 Bench: 2325401 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9c52592e0d9..c42fcd4ad66 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1824,7 +1824,8 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(116 * depth - 81, 1515) + 347 * (bestMove == ttMove); + int bonus = + std::min(116 * depth - 81, 1515) + 347 * (bestMove == ttMove) + (ss - 1)->statScore / 32; int malus = std::min(848 * depth - 207, 2446) - 17 * moveCount; if (!pos.capture_stage(bestMove)) From cd3a8373243d145291abe9e546fd0396fc0e73fd Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 28 Dec 2025 14:48:56 +0100 Subject: [PATCH 1262/1309] Refine reduction logic based on next-ply cutoff count Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 38208 W: 10076 L: 9754 D: 18378 Ptnml(0-2): 139, 4390, 9742, 4676, 157 https://tests.stockfishchess.org/tests/view/6945bb6446f342e1ec211d93 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 64086 W: 16529 L: 16157 D: 31400 Ptnml(0-2): 34, 6808, 17990, 7174, 37 https://tests.stockfishchess.org/tests/view/69479d303c8768ca45072446 closes https://github.com/official-stockfish/Stockfish/pull/6489 Bench: 2442415 --- src/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c42fcd4ad66..8ac58fbabed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1203,8 +1203,9 @@ Value Search::Worker::search( r += 1119; // Increase reduction if next ply has a lot of fail high - if ((ss + 1)->cutoffCnt > 2) - r += 991 + allNode * 923; + if ((ss + 1)->cutoffCnt > 1) + r += 120 + 1024 * ((ss + 1)->cutoffCnt > 2) + 100 * ((ss + 1)->cutoffCnt > 3) + + 1024 * allNode; // For first picked move (ttMove) reduce reduction if (move == ttData.move) From 9d69577e1937aa532a832040c1a4616a4971c508 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 28 Dec 2025 14:50:12 +0100 Subject: [PATCH 1263/1309] Removing redundant parentheses closes https://github.com/official-stockfish/Stockfish/pull/6490 No functional change --- src/evaluate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d20843e854b..e7133f2dffc 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -42,8 +42,8 @@ namespace Stockfish { // an approximation of the material advantage on the board in terms of pawns. int Eval::simple_eval(const Position& pos) { Color c = pos.side_to_move(); - return PawnValue * (pos.count(c) - pos.count(~c)) - + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); + return PawnValue * (pos.count(c) - pos.count(~c)) + pos.non_pawn_material(c) + - pos.non_pawn_material(~c); } bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; } From 06819ad54c728aa873098e29094f22236a8bb3a6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 28 Dec 2025 14:51:09 +0100 Subject: [PATCH 1264/1309] Update Top CPU Contributors update to current closes https://github.com/official-stockfish/Stockfish/pull/6491 No functional change --- Top CPU Contributors.txt | 227 +++++++++++++++++++++++---------------- 1 file changed, 132 insertions(+), 95 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 4e598ecfc5b..f8134a19961 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,89 +1,101 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2025-03-22. +Contributors to Fishtest with >10,000 CPU hours, as of 2025-12-24. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 41712226 3294628533 -vdv 28993864 954145232 -technologov 24984442 1115931964 -linrock 11463033 741692823 +noobpwnftw 42692720 3385202467 +vdv 39922218 1277282126 +technologov 26354561 1163905856 +linrock 12002255 785641643 +olafm 3030005 197722318 mlang 3026000 200065824 -okrout 2726068 248285678 -olafm 2420096 161297116 -pemo 1838361 62294199 -TueRens 1804847 80170868 +okrout 3020471 268364402 +pemo 2009761 66178221 +TueRens 1956328 83294326 +sebastronomy 1806628 73868874 dew 1689162 100033738 -sebastronomy 1655637 67294942 -grandphish2 1474752 92156319 -JojoM 1130625 73666098 -rpngn 973590 59996557 -oz 921203 60370346 +grandphish2 1479778 92306101 +JojoM 1130646 73666860 +rpngn 1081976 65292619 +oz 1029329 69522328 +gvreuls 844572 59249068 tvijlbrief 796125 51897690 -gvreuls 792215 55184194 mibere 703840 46867607 -leszek 599745 44681421 +leszek 609538 45301765 cw 519602 34988289 fastgm 503862 30260818 -CSU_Dynasty 474794 31654170 -maximmasiutin 441753 28129452 -robal 437950 28869118 -ctoks 435150 28542141 +robal 503208 32703510 +maximmasiutin 500174 30818270 +CSU_Dynasty 481663 31916842 +ctoks 435431 28551199 crunchy 427414 27371625 bcross 415724 29061187 mgrabiak 380202 27586936 +tolkki963 358623 26373242 velislav 342588 22140902 ncfish1 329039 20624527 Fisherman 327231 21829379 -Sylvain27 317021 11494912 +Fifis 323909 16200123 +Sylvain27 320732 11671388 marrco 310446 19587107 +Calis007 310201 18969692 +Viren6 297938 5847458 Dantist 296386 18031762 -Fifis 289595 14969251 -tolkki963 286043 23596996 -Calis007 272677 17281620 +naclosagc 296040 13865010 +anematode 293146 3918134 +maposora 278093 20454200 +javran 271465 20506096 cody 258835 13301710 nordlandia 249322 16420192 -javran 212141 16507618 +Goatminola 218812 21411814 +Torom 211061 7238522 glinscott 208125 13277240 drabel 204167 13930674 +Wencey 203584 9943614 mhoram 202894 12601997 +sschnee 201756 12874780 bking_US 198894 11876016 -Wencey 198537 9606420 +Mineta 195312 10337614 Thanar 179852 12365359 -sschnee 170521 10891112 -armo9494 168141 11177514 +armo9494 169747 11254404 +amicic 161636 11290899 DesolatedDodo 160605 10392474 +markkulix 158320 13538874 spams 157128 10319326 -maposora 155839 13963260 sqrt2 147963 9724586 -vdbergh 140514 9242985 +vdbergh 141201 9308647 jcAEie 140086 10603658 CoffeeOne 137100 5024116 malala 136182 8002293 -Goatminola 134893 11640524 xoto 133759 9159372 -markkulix 132104 11000548 -naclosagc 131472 4660806 -Dubslow 129685 8527664 +Dubslow 130795 8609646 +zeryl 129154 7911565 davar 129023 8376525 DMBK 122960 8980062 +cuistot 122470 8393996 +megaman7de 122254 8066174 dsmith 122059 7570238 Wolfgang 120919 8619168 CypressChess 120902 8683904 -amicic 119661 7938029 -cuistot 116864 7828864 sterni1971 113754 6054022 +Spprtr 113356 8129809 Data 113305 8220352 BrunoBanani 112960 7436849 -megaman7de 109139 7360928 skiminki 107583 7218170 -zeryl 104523 6618969 +MediumBerry5575 103884 7830022 MaZePallas 102823 6633619 +YvesKn 102213 5098076 sunu 100167 7040199 -thirdlife 99178 2246544 +thirdlife 99182 2246960 ElbertoOne 99028 7023771 +TechiePirate 98957 1249064 +DeepnessFulled 97313 5083358 TataneSan 97257 4239502 romangol 95662 7784954 bigpen0r 94825 6529241 +jojo2357 94358 7635486 +malfoy 92712 3392874 +voidedstarlight 92582 2342038 brabos 92118 6186135 Maxim 90818 3283364 psk 89957 5984901 @@ -92,26 +104,26 @@ jromang 87260 5988073 racerschmacer 85805 6122790 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 -Spprtr 82103 5663635 +MarcusTullius 82359 5335665 BRAVONE 81239 5054681 -MarcusTullius 78930 5189659 -Mineta 78731 4947996 -Torom 77978 2651656 +rn 78566 6000852 nssy 76497 5259388 woutboat 76379 6031688 teddybaer 75125 5407666 Pking_cda 73776 5293873 -Viren6 73664 1356502 yurikvelo 73611 5046822 +Zirie 71260 4602355 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 +0x539 67147 2918044 manap 66273 4121774 tinker 64333 4268790 +CounterFlow 63914 3775062 +mecevdimitar 62493 3508750 +DanielMiao1 62188 1335664 qurashee 61208 3429862 -DanielMiao1 60181 1317252 AGI 58316 4336328 -jojo2357 57435 4944212 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 @@ -120,44 +132,52 @@ rkl 55132 4164467 jmdana 54988 4041917 notchris 53936 4184018 renouve 53811 3501516 -CounterFlow 52536 3203740 +jibarbosa 53504 5110028 +somethingintheshadows 52333 4344808 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 +sylvek 50391 3765170 rap 49985 3219146 pb00067 49733 3298934 GPUex 48686 3684998 OuaisBla 48626 3445134 +lemtea 48563 1672454 ronaldjerum 47654 3240695 +abdicj 46740 2709482 biffhero 46564 3111352 -oryx 46141 3583236 -jibarbosa 45890 4541218 -DeepnessFulled 45734 3944282 -abdicj 45577 2631772 +oryx 46422 3607582 VoyagerOne 45476 3452465 -mecevdimitar 44240 2584396 +rdp65536 43948 2881890 speedycpu 43842 3003273 jbwiebe 43305 2805433 gopeto 43046 2821514 -YvesKn 42628 2177630 Antihistamine 41788 2761312 mhunt 41735 2691355 -somethingintheshadows 41502 3330418 +WoodMan777 40858 3491196 +Epic29 40771 4067404 +drauh 40419 1634770 homyur 39893 2850481 gri 39871 2515779 vidar808 39774 1656372 +Gaster319 38994 3477702 Garf 37741 2999686 SC 37299 2731694 -Gaster319 37229 3289674 +ZacHFX 36533 2553282 csnodgrass 36207 2688994 -ZacHFX 35528 2486328 -icewulf 34782 2415146 +icewulf 34935 2421834 strelock 34716 2074055 +Jopo12321 33921 2531448 +xuhdev 33798 3295210 +csnodgra 33780 1446866 EthanOConnor 33370 2090311 slakovv 32915 2021889 -shawnxu 32144 2814668 +IslandLambda 32667 1659344 +Kataiser 32477 2688862 +shawnxu 32330 2830036 +srowen 32248 1791136 +qgluca 31941 2491622 Gelma 31771 1551204 -srowen 31181 1732120 kdave 31157 2198362 manapbk 30987 1810399 votoanthuan 30691 2460856 @@ -168,15 +188,26 @@ spcc 29925 1901692 hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 -WoodMan777 29300 2579864 +Flopzee 29388 1899905 +hoching 29054 2067144 belzedar94 28846 1811530 +wizardassassin 28007 2318204 +purpletree 27892 2061966 +Kyrega 27674 963872 +joendter 27193 1781570 +Danielv123 27132 1043614 chriswk 26902 1868317 xwziegtm 26897 2124586 -Jopo12321 26818 1816482 +spotscene 26877 2139674 achambord 26582 1767323 +shreven 26448 1703328 Patrick_G 26276 1801617 yorkman 26193 1992080 -Ulysses 25517 1711634 +ols 26173 1443517 +wer 26136 793146 +Skiff84 26083 1135002 +RudyMars 25980 2211364 +Ulysses 25544 1714542 SFTUser 25182 1675689 nabildanial 25068 1531665 Sharaf_DG 24765 1786697 @@ -184,30 +215,28 @@ rodneyc 24376 1416402 jsys14 24297 1721230 AndreasKrug 24235 1934711 agg177 23890 1395014 +Disservin 23768 1934576 Ente 23752 1678188 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 Nullvalue 23155 2022752 fishtester 23115 1581502 -wizardassassin 23073 1789536 -Skiff84 22984 1053680 cisco2015 22920 1763301 -ols 22914 1322047 Hjax 22561 1566151 -Zirie 22542 1472937 +gerbil 22435 1679842 +Serpensin 22396 1861156 team-oh 22272 1636708 mkstockfishtester 22253 2029566 Roady 22220 1465606 +tsim67 22077 1353048 MazeOfGalious 21978 1629593 sg4032 21950 1643373 -tsim67 21939 1343944 +sev 21791 1983016 ianh2105 21725 1632562 -Serpensin 21704 1809188 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 -IslandLambda 21468 1239756 user213718 21454 1404128 sphinx 21211 1384728 qoo_charly_cai 21136 1514927 @@ -215,22 +244,20 @@ jjoshua2 21001 1423089 Zake9298 20938 1565848 horst.prack 20878 1465656 0xB00B1ES 20590 1208666 +t3hf1sht3ster 20544 673134 Dinde 20459 1292774 -t3hf1sht3ster 20456 670646 j3corre 20405 941444 -0x539 20332 1039516 Adrian.Schmidt123 20316 1281436 -malfoy 20313 1350694 -purpletree 20019 1461026 wei 19973 1745989 teenychess 19819 1762006 +RickGroszkiewicz 19749 1913986 rstoesser 19569 1293588 eudhan 19274 1283717 nalanzeyu 19211 396674 vulcan 18871 1729392 Karpovbot 18766 1053178 +Farseer 18536 1078326 jundery 18445 1115855 -Farseer 18281 1074642 sebv15 18267 1262588 whelanh 17887 347974 ville 17883 1384026 @@ -239,84 +266,94 @@ purplefishies 17595 1092533 dju 17414 981289 iisiraider 17275 1049015 Karby 17177 1030688 +fogleman 17134 815562 +zhujianzhao 17111 1666972 DragonLord 17014 1162790 -pirt 16991 1274215 +pirt 16993 1274363 redstone59 16842 1461780 Alb11747 16787 1213990 Naven94 16414 951718 scuzzi 16155 995347 IgorLeMasson 16064 1147232 +micpilar 15866 1399266 ako027ako 15671 1173203 -xuhdev 15516 1528278 infinigon 15285 965966 +fishtrawler 15205 1436165 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 OssumOpossum 14857 1007129 LunaticBFF57 14525 1190310 +YELNAMRON 14480 1141420 enedene 14476 905279 -YELNAMRON 14475 1141330 -RickGroszkiewicz 14272 1385984 -joendter 14269 982014 +MooTheCow 14459 1023868 +BestBoyBerlin 14353 1365584 bpfliegel 14233 882523 mpx86 14019 759568 jpulman 13982 870599 getraideBFF 13871 1172846 crocogoat 13817 1119086 Nesa92 13806 1116101 -joster 13710 946160 +joster 13717 946960 mbeier 13650 1044928 Pablohn26 13552 1088532 wxt9861 13550 1312306 +biniek 13469 930029 Dark_wizzie 13422 1007152 +Jackfish 13422 914984 +Hongildong 13297 699288 Rudolphous 13244 883140 -Jackfish 13177 894206 -MooTheCow 13091 892304 +Phoenix17 13032 1124066 Machariel 13010 863104 mabichito 12903 749391 +FormazChar 12899 980413 thijsk 12886 722107 AdrianSA 12860 804972 -Flopzee 12698 894821 -szczur90 12684 977536 -Kyrega 12661 456438 +szczur90 12720 979324 mschmidt 12644 863193 korposzczur 12606 838168 fatmurphy 12547 853210 -Oakwen 12532 855759 +Oakwen 12537 856257 SapphireBrand 12416 969604 +Snuuka 12392 509082 deflectooor 12386 579392 modolief 12386 896470 ckaz 12273 754644 -Hongildong 12201 648712 pgontarz 12151 848794 dbernier 12103 860824 -FormazChar 12051 913497 -shreven 12044 884734 rensonthemove 11999 971993 stocky 11954 699440 +ali-al-zhrani 11887 836126 3cho 11842 1036786 +Craftyawesome 11736 832254 +dragon123118 11578 1044142 ImperiumAeternum 11482 979142 +lvdv 11475 594400 infinity 11470 727027 +kusihe 11468 468450 +vaskoul 11446 976902 aga 11412 695127 Def9Infinity 11408 700682 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 d64 11263 789184 -ali-al-zhrani 11245 779246 -vaskoul 11144 953906 +Poly 11172 455568 +enizor 11140 630194 snicolet 11106 869170 dapper 11032 771402 Ethnikoi 10993 945906 -Snuuka 10938 435504 Karmatron 10871 678306 -gerbil 10871 1005842 +zarthus 10773 1034536 OliverClarke 10696 942654 +Omed 10680 669816 +cyberthink 10647 936538 basepi 10637 744851 michaelrpg 10624 748179 Cubox 10621 826448 -dragon123118 10421 936506 +GBx3TV 10499 343266 +Styx 10450 867836 OIVAS7572 10420 995586 -GBx3TV 10388 339952 Garruk 10365 706465 dzjp 10343 732529 +Lorenz 10311 886308 borinot 10026 902130 From 1047f844d13ba890463c0eaf337b4fef613c2725 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sun, 28 Dec 2025 14:52:56 +0100 Subject: [PATCH 1265/1309] Simplify doDeeperSearch Passed simplification STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 92096 W: 23888 L: 23728 D: 44480 Ptnml(0-2): 336, 10796, 23608, 10988, 320 https://tests.stockfishchess.org/tests/view/694b6b9d572093c1986d6ae0 Passed simplification LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 50064 W: 12789 L: 12598 D: 24677 Ptnml(0-2): 24, 5350, 14103, 5521, 34 https://tests.stockfishchess.org/tests/view/694d49aa572093c1986d7021 closes https://github.com/official-stockfish/Stockfish/pull/6493 Bench: 2494221 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 8ac58fbabed..41bbcd6d3a2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1242,7 +1242,7 @@ Value Search::Worker::search( { // 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 = d < newDepth && value > (bestValue + newDepth + 44); + const bool doDeeperSearch = d < newDepth && value > bestValue + 50; const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; From b2e60960b39d7191105193d5ff8b7482dbcbf351 Mon Sep 17 00:00:00 2001 From: KazApps Date: Sun, 28 Dec 2025 14:54:37 +0100 Subject: [PATCH 1266/1309] Fix nonPawnKey Fix incorrect nonPawnKey update Passed non-reg SMP STC: ``` LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 139424 W: 35792 L: 35690 D: 67942 Ptnml(0-2): 197, 15783, 37665, 15855, 212 ``` https://tests.stockfishchess.org/tests/view/694b7b7e572093c1986d6b0d Passed non-reg SMP LTC: ``` LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 88880 W: 22863 L: 22718 D: 43299 Ptnml(0-2): 16, 8947, 26401, 9028, 48 ``` https://tests.stockfishchess.org/tests/view/694d2ceb572093c1986d6fc8 fixes https://github.com/official-stockfish/Stockfish/issues/6492 closes https://github.com/official-stockfish/Stockfish/pull/6494 Bench: 2475788 --- src/position.cpp | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/position.cpp b/src/position.cpp index 7377b20290f..cfbb85e841a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -855,6 +855,7 @@ void Position::do_move(Move m, k ^= Zobrist::psq[promotion][to]; st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1] ^ Zobrist::psq[pc][8 + pieceCount[pc]]; + st->nonPawnKey[us] ^= Zobrist::psq[promotion][to]; if (promotionType <= BISHOP) st->minorPieceKey ^= Zobrist::psq[promotion][to]; diff --git a/src/search.cpp b/src/search.cpp index 41bbcd6d3a2..86b69db57cf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -89,7 +89,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 8; - return 10347 * pcv + 8821 * micv + 11168 * (wnpcv + bnpcv) + 7841 * cntcv; + return 10347 * pcv + 8821 * micv + 11665 * (wnpcv + bnpcv) + 7841 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation From 1780c1fd6e1e63a852e5b901656ed6d76188b726 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 28 Dec 2025 14:54:57 +0100 Subject: [PATCH 1267/1309] For expected ALL nodes scale up reduction with depth dependent factor. Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 141120 W: 36860 L: 36390 D: 67870 Ptnml(0-2): 470, 16441, 36314, 16819, 516 https://tests.stockfishchess.org/tests/view/694978e93c8768ca45072763 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 66576 W: 17078 L: 16700 D: 32798 Ptnml(0-2): 45, 7093, 18628, 7483, 39 https://tests.stockfishchess.org/tests/view/694bb608572093c1986d6ba6 closes https://github.com/official-stockfish/Stockfish/pull/6496 Bench: 2503391 --- src/search.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 86b69db57cf..95dd9719638 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1222,6 +1222,10 @@ Value Search::Worker::search( // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 850 / 8192; + // Scale up reductions for expected ALL nodes + if (allNode) + r += r / (depth + 1); + // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) { From 969285fa5dff3c8367784758627cde732a886727 Mon Sep 17 00:00:00 2001 From: anematode Date: Sun, 28 Dec 2025 14:56:46 +0100 Subject: [PATCH 1268/1309] Shared pawn history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Passed STC SMP](https://tests.stockfishchess.org/tests/view/694e506c572093c1986d7276): ``` LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 14992 W: 3924 L: 3653 D: 7415 Ptnml(0-2): 20, 1547, 4090, 1820, 19 ``` [Passed LTC SMP](https://tests.stockfishchess.org/tests/live_elo/694ead61572093c1986d7365): ``` LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 41146 W: 10654 L: 10342 D: 20150 Ptnml(0-2): 17, 3999, 12225, 4319, 13 ``` [Passed a sanity check STC SMP post-refactoring](https://tests.stockfishchess.org/tests/view/69503997572093c1986d763a): ``` LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 46728 W: 12178 L: 11863 D: 22687 Ptnml(0-2): 82, 5093, 12685, 5436, 68 ``` (The large gain of the first STC was probably a fluke, and this result is more reasonable!) After shared correction history, Viz suggested we try sharing other histories, especially `pawnHistory`. As far as we're aware, sharing history besides correction history (like Caissa does) is novel. The implementation follows the same pattern as shared correction history – the size of the history table is scaled with `next_power_of_two(threadsInNumaNode)` and the entry is prefetched in `do_move`. A bit of refactoring was done to accommodate this new history. Note that we prefetch `&history->pawn_entry(*this)[pc][to]` rather than `&history->pawn_entry(*this)` because unlike the other entries, each entry contains multiple cache lines. closes https://github.com/official-stockfish/Stockfish/pull/6498 Bench: 2503391 Co-authored-by: Michael Chaly --- src/history.h | 50 +++++++++++++++++++++++++++++++++--------------- src/movepick.cpp | 6 +++--- src/movepick.h | 4 ++-- src/position.cpp | 1 + src/search.cpp | 21 ++++++++------------ src/search.h | 1 - 6 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/history.h b/src/history.h index 127fb9ef3ba..9811d039761 100644 --- a/src/history.h +++ b/src/history.h @@ -35,22 +35,18 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 +constexpr int PAWN_HISTORY_BASE_SIZE = 8192; // has to be a power of 2 constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits::max() + 1; constexpr int CORRHIST_BASE_SIZE = UINT_16_HISTORY_SIZE; constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; -static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, - "PAWN_HISTORY_SIZE has to be a power of 2"); +static_assert((PAWN_HISTORY_BASE_SIZE & (PAWN_HISTORY_BASE_SIZE - 1)) == 0, + "PAWN_HISTORY_BASE_SIZE has to be a power of 2"); static_assert((CORRHIST_BASE_SIZE & (CORRHIST_BASE_SIZE - 1)) == 0, "CORRHIST_BASE_SIZE has to be a power of 2"); -inline int pawn_history_index(const Position& pos) { - return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); -} - // StatsEntry is the container of various numerical statistics. We use a class // instead of a naked value to directly call history update operator<<() on // the entry. The first template parameter T is the base type of the array, @@ -96,6 +92,9 @@ enum StatsType { template using Stats = MultiArray, Sizes...>; +template +using AtomicStats = MultiArray, Sizes...>; + // DynStats is a dynamically sized array of Stats, used for thread-shared histories // which should scale with the total number of threads. The SizeMultiplier gives // the per-thread allocation count of T. @@ -106,11 +105,13 @@ struct DynStats { data = make_unique_large_page(size); } // Sets all values in the range to 0 - void clear_range(size_t start, size_t end) { + void clear_range(int value, size_t threadIdx) { + size_t start = threadIdx * SizeMultiplier; assert(start < size); - assert(end <= size); - T* fill_start = &(*this)[start]; - memset(reinterpret_cast(fill_start), 0, sizeof(T) * (end - start)); + size_t end = std::min(start + SizeMultiplier, size); + + while (start < end) + data[start++].fill(value); } size_t get_size() const { return size; } T& operator[](size_t index) { @@ -149,7 +150,8 @@ using PieceToHistory = Stats; using ContinuationHistory = MultiArray; // PawnHistory is addressed by the pawn structure and a move's [piece][to] -using PawnHistory = Stats; +using PawnHistory = + DynStats, PAWN_HISTORY_BASE_SIZE>; // Correction histories record differences between the static evaluation of // positions and their search score. It is used to improve the static evaluation @@ -169,6 +171,13 @@ struct CorrectionBundle { StatsEntry minor; StatsEntry nonPawnWhite; StatsEntry nonPawnBlack; + + void operator=(T val) { + pawn = val; + minor = val; + nonPawnWhite = val; + nonPawnBlack = val; + } }; namespace Detail { @@ -212,13 +221,22 @@ using TTMoveHistory = StatsEntry; // the indexing more efficient. struct SharedHistories { SharedHistories(size_t threadCount) : - correctionHistory(threadCount) { + correctionHistory(threadCount), + pawnHistory(threadCount) { assert((threadCount & (threadCount - 1)) == 0 && threadCount != 0); - sizeMinus1 = correctionHistory.get_size() - 1; + sizeMinus1 = correctionHistory.get_size() - 1; + pawnHistSizeMinus1 = pawnHistory.get_size() - 1; } size_t get_size() const { return sizeMinus1 + 1; } + auto& pawn_entry(const Position& pos) { + return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1]; + } + const auto& pawn_entry(const Position& pos) const { + return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1]; + } + auto& pawn_correction_entry(const Position& pos) { return correctionHistory[pos.pawn_key() & sizeMinus1]; } @@ -243,9 +261,11 @@ struct SharedHistories { } UnifiedCorrectionHistory correctionHistory; + PawnHistory pawnHistory; + private: - size_t sizeMinus1; + size_t sizeMinus1, pawnHistSizeMinus1; }; } // namespace Stockfish diff --git a/src/movepick.cpp b/src/movepick.cpp index d20ab151e8c..15f64d1cb36 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -87,14 +87,14 @@ MovePicker::MovePicker(const Position& p, const LowPlyHistory* lph, const CapturePieceToHistory* cph, const PieceToHistory** ch, - const PawnHistory* ph, + const SharedHistories* sh, int pl) : pos(p), mainHistory(mh), lowPlyHistory(lph), captureHistory(cph), continuationHistory(ch), - pawnHistory(ph), + sharedHistory(sh), ttMove(ttm), depth(d), ply(pl) { @@ -159,7 +159,7 @@ ExtMove* MovePicker::score(MoveList& ml) { { // histories m.value = 2 * (*mainHistory)[us][m.raw()]; - m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to]; + m.value += 2 * sharedHistory->pawn_entry(pos)[pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; m.value += (*continuationHistory[2])[pc][to]; diff --git a/src/movepick.h b/src/movepick.h index 5b3190594e9..b1e041c17f0 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -45,7 +45,7 @@ class MovePicker { const LowPlyHistory*, const CapturePieceToHistory*, const PieceToHistory**, - const PawnHistory*, + const SharedHistories*, int); MovePicker(const Position&, Move, int, const CapturePieceToHistory*); Move next_move(); @@ -64,7 +64,7 @@ class MovePicker { const LowPlyHistory* lowPlyHistory; const CapturePieceToHistory* captureHistory; const PieceToHistory** continuationHistory; - const PawnHistory* pawnHistory; + const SharedHistories* sharedHistory; Move ttMove; ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated; int stage; diff --git a/src/position.cpp b/src/position.cpp index cfbb85e841a..f32530fddf3 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -885,6 +885,7 @@ void Position::do_move(Move m, if (history) { + prefetch(&history->pawn_entry(*this)[pc][to]); prefetch(&history->pawn_correction_entry(*this)); prefetch(&history->minor_piece_correction_entry(*this)); prefetch(&history->nonpawn_correction_entry(*this)); diff --git a/src/search.cpp b/src/search.cpp index 95dd9719638..87a96cab812 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -580,14 +580,10 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } void Search::Worker::clear() { mainHistory.fill(68); captureHistory.fill(-689); - pawnHistory.fill(-1238); // Each thread is responsible for clearing their part of shared history - size_t len = sharedHistory.get_size() / numaTotal; - size_t start = numaThreadIdx * len; - size_t end = std::min(start + len, sharedHistory.get_size()); - - sharedHistory.correctionHistory.clear_range(start, end); + sharedHistory.correctionHistory.clear_range(0, numaThreadIdx); + sharedHistory.pawnHistory.clear_range(-1238, numaThreadIdx); ttMoveHistory = 0; @@ -861,7 +857,7 @@ Value Search::Worker::search( mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13; + sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << evalDiff * 13; } @@ -992,7 +988,7 @@ Value Search::Worker::search( MovePicker mp(pos, ttData.move, depth, &mainHistory, &lowPlyHistory, &captureHistory, contHist, - &pawnHistory, ss->ply); + &sharedHistory, ss->ply); value = bestValue; @@ -1081,7 +1077,7 @@ Value Search::Worker::search( { int history = (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; + + sharedHistory.pawn_entry(pos)[movedPiece][move.to_sq()]; // Continuation history based pruning if (history < -4083 * depth) @@ -1439,7 +1435,7 @@ Value Search::Worker::search( mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] + sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << scaledBonus * 1160 / 32768; } @@ -1611,7 +1607,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // the moves. We presently use two stages of move generator in quiescence search: // captures, or evasions only when in check. MovePicker mp(pos, ttData.move, DEPTH_QS, &mainHistory, &lowPlyHistory, &captureHistory, - contHist, &pawnHistory, ss->ply); + contHist, &sharedHistory, ss->ply); // Step 5. Loop through all pseudo-legal moves until no moves remain or a beta // cutoff occurs. @@ -1900,8 +1896,7 @@ void update_quiet_histories( update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 896 / 1024); - int pIndex = pawn_history_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] + workerThread.sharedHistory.pawn_entry(pos)[pos.moved_piece(move)][move.to_sq()] << bonus * (bonus > 0 ? 905 : 505) / 1024; } diff --git a/src/search.h b/src/search.h index 9644f6aa52e..eb4dda77c17 100644 --- a/src/search.h +++ b/src/search.h @@ -292,7 +292,6 @@ class Worker { CapturePieceToHistory captureHistory; ContinuationHistory continuationHistory[2][2]; - PawnHistory pawnHistory; CorrectionHistory continuationCorrectionHistory; TTMoveHistory ttMoveHistory; From 44d5467bbe06789e8a3cbaee87e699e033b3081a Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 28 Dec 2025 14:57:28 +0100 Subject: [PATCH 1269/1309] Remove -Wstack-usage on (apple) clang Clang pretends to be GCC, but is enraged by `-Wstack-usage`: closes https://github.com/official-stockfish/Stockfish/pull/6499 No functional change --- src/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index cc85ac78ecd..bf3fbe80faa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -436,7 +436,7 @@ endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations -Wstack-usage=128000 + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) ifeq ($(OS),Android) @@ -607,6 +607,8 @@ ifeq ($(COMP),gcc) ifneq ($(gccisclang),) profile_make = clang-profile-make profile_use = clang-profile-use + else + CXXFLAGS += -Wstack-usage=128000 endif endif From e0fb783c30f86d9ff01328b14fefded21492677e Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 31 Dec 2025 15:55:00 +0100 Subject: [PATCH 1270/1309] Fix incorrect initialization Fixes https://github.com/official-stockfish/Stockfish/issues/6505 Missing initialization seemingly resulting in side effects, as discussed in the issue. Credit to Sopel for spotting the bug. PR used as a testcase for CoPilot, doing the right thing https://github.com/official-stockfish/Stockfish/pull/6478#discussion_r2655467218 closes https://github.com/official-stockfish/Stockfish/pull/6511 No functional change --- src/history.h | 6 +++--- src/search.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/history.h b/src/history.h index 9811d039761..3aa9ef623ab 100644 --- a/src/history.h +++ b/src/history.h @@ -105,10 +105,10 @@ struct DynStats { data = make_unique_large_page(size); } // Sets all values in the range to 0 - void clear_range(int value, size_t threadIdx) { - size_t start = threadIdx * SizeMultiplier; + void clear_range(int value, size_t threadIdx, size_t numaTotal) { + size_t start = uint64_t(threadIdx) * size / numaTotal; assert(start < size); - size_t end = std::min(start + SizeMultiplier, size); + size_t end = threadIdx + 1 == numaTotal ? size : uint64_t(threadIdx + 1) * size / numaTotal; while (start < end) data[start++].fill(value); diff --git a/src/search.cpp b/src/search.cpp index 87a96cab812..c1a7d588051 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -582,8 +582,8 @@ void Search::Worker::clear() { captureHistory.fill(-689); // Each thread is responsible for clearing their part of shared history - sharedHistory.correctionHistory.clear_range(0, numaThreadIdx); - sharedHistory.pawnHistory.clear_range(-1238, numaThreadIdx); + sharedHistory.correctionHistory.clear_range(0, numaThreadIdx, numaTotal); + sharedHistory.pawnHistory.clear_range(-1238, numaThreadIdx, numaTotal); ttMoveHistory = 0; From 145369149620591f7205faaa7f5ee44bdd5ce15e Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 31 Dec 2025 11:20:25 +0100 Subject: [PATCH 1271/1309] Fix feature check Use _POSIX_C_SOURCE to check for PTHREAD_MUTEX_ROBUST support. The latter is a enum, not a defined variable. closes https://github.com/official-stockfish/Stockfish/pull/6510 No functional change --- src/shm_linux.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shm_linux.h b/src/shm_linux.h index a8b5404b2e4..52abe8ec440 100644 --- a/src/shm_linux.h +++ b/src/shm_linux.h @@ -502,7 +502,7 @@ class SharedMemory: public detail::SharedMemoryBase { return false; bool success = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) == 0; -#ifdef PTHREAD_MUTEX_ROBUST +#if _POSIX_C_SOURCE >= 200809L if (success) success = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST) == 0; #endif @@ -524,7 +524,7 @@ class SharedMemory: public detail::SharedMemoryBase { if (rc == 0) return true; -#ifdef PTHREAD_MUTEX_ROBUST +#if _POSIX_C_SOURCE >= 200809L if (rc == EOWNERDEAD) { if (pthread_mutex_consistent(&header_ptr_->mutex) == 0) From ced9f69834378f88efbf196d05666fb058fc4b00 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 29 Dec 2025 10:47:40 +0300 Subject: [PATCH 1272/1309] Adjust main history with every new root position this patch dampens down main history to 3/4 of it value for all possible moves at the start of ID loop, making it partially refresh with every new root position. Passed STC: https://tests.stockfishchess.org/tests/view/694e33ff572093c1986d7234 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 115520 W: 30164 L: 29735 D: 55621 Ptnml(0-2): 395, 13192, 30192, 13551, 430 Passed LTC: https://tests.stockfishchess.org/tests/view/6950cbe6572093c1986d816c LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 63672 W: 16480 L: 16114 D: 31078 Ptnml(0-2): 46, 6524, 18329, 6892, 45 closes https://github.com/official-stockfish/Stockfish/pull/6504 bench 2710946 --- src/search.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c1a7d588051..05f9b47fa18 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -65,6 +65,7 @@ using namespace Search; namespace { constexpr int SEARCHEDLIST_CAPACITY = 32; +constexpr int mainHistoryDefault = 68; using SearchedList = ValueList; // (*Scalers): @@ -312,6 +313,10 @@ void Search::Worker::iterative_deepening() { lowPlyHistory.fill(97); + for (Color c: {WHITE, BLACK}) + for (int i = 0; i < UINT_16_HISTORY_SIZE; i++) + mainHistory[c][i] = (mainHistory[c][i] - mainHistoryDefault) * 3 / 4 + mainHistoryDefault; + // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop && !(limits.depth && mainThread && rootDepth > limits.depth)) @@ -578,7 +583,7 @@ void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(68); + mainHistory.fill(mainHistoryDefault); captureHistory.fill(-689); // Each thread is responsible for clearing their part of shared history From aeb3bf33a9bbf6dd662e9e570accf56c57eafbd7 Mon Sep 17 00:00:00 2001 From: anematode Date: Wed, 31 Dec 2025 17:44:43 -0800 Subject: [PATCH 1273/1309] port get_changed_pieces to ARM NEON passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 71968 W: 18833 L: 18489 D: 34646 Ptnml(0-2): 192, 7310, 20643, 7640, 199 https://tests.stockfishchess.org/tests/view/69509e5c572093c1986d7a0a closes https://github.com/official-stockfish/Stockfish/pull/6512 No functional change --- src/nnue/nnue_accumulator.cpp | 12 ++++++++++++ src/search.cpp | 7 ++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 763fb7a5e46..338d291e8f5 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -645,6 +645,18 @@ Bitboard get_changed_pieces(const std::array& oldPieces, sameBB |= static_cast(equalMask) << i; } return ~sameBB; +#elif defined(USE_NEON) + uint8x16x4_t old_v = vld4q_u8(reinterpret_cast(oldPieces.data())); + uint8x16x4_t new_v = vld4q_u8(reinterpret_cast(newPieces.data())); + auto cmp = [=](const int i) { return vceqq_u8(old_v.val[i], new_v.val[i]); }; + + uint8x16_t cmp0_1 = vsriq_n_u8(cmp(1), cmp(0), 1); + uint8x16_t cmp2_3 = vsriq_n_u8(cmp(3), cmp(2), 1); + uint8x16_t merged = vsriq_n_u8(cmp2_3, cmp0_1, 2); + merged = vsriq_n_u8(merged, merged, 4); + uint8x8_t sameBB = vshrn_n_u16(vreinterpretq_u16_u8(merged), 4); + + return ~vget_lane_u64(vreinterpret_u64_u8(sameBB), 0); #else Bitboard changed = 0; diff --git a/src/search.cpp b/src/search.cpp index 05f9b47fa18..a880a741f33 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -65,7 +65,7 @@ using namespace Search; namespace { constexpr int SEARCHEDLIST_CAPACITY = 32; -constexpr int mainHistoryDefault = 68; +constexpr int mainHistoryDefault = 68; using SearchedList = ValueList; // (*Scalers): @@ -313,9 +313,10 @@ void Search::Worker::iterative_deepening() { lowPlyHistory.fill(97); - for (Color c: {WHITE, BLACK}) + for (Color c : {WHITE, BLACK}) for (int i = 0; i < UINT_16_HISTORY_SIZE; i++) - mainHistory[c][i] = (mainHistory[c][i] - mainHistoryDefault) * 3 / 4 + mainHistoryDefault; + mainHistory[c][i] = + (mainHistory[c][i] - mainHistoryDefault) * 3 / 4 + mainHistoryDefault; // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop From 0317c6ccec12b15a80b8fbe98637c6f5a747f240 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Sun, 28 Dec 2025 14:10:23 +0100 Subject: [PATCH 1274/1309] build: rename WINE_PATH to RUN_PREFIX for wrapper execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WINE_PATH started as a Wine-specific knob, but it’s now used more generally as a command prefix to run the built engine under wrappers like Intel SDE, qemu-user, etc. - Add RUN_PREFIX as the supported “run wrapper/prefix” variable in Makefile - Set WINE_PATH as a deprecated alias - Update CI and scripts to use RUN_PREFIX closes https://github.com/official-stockfish/Stockfish/pull/6500 No functional change --- .github/workflows/arm_compilation.yml | 4 ++-- .github/workflows/compilation.yml | 4 ++-- src/Makefile | 21 ++++++++++++++++++--- tests/signature.sh | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/arm_compilation.yml b/.github/workflows/arm_compilation.yml index 781bd807024..86d22218272 100644 --- a/.github/workflows/arm_compilation.yml +++ b/.github/workflows/arm_compilation.yml @@ -80,9 +80,9 @@ jobs: export LDFLAGS="-static -Wno-unused-command-line-argument" fi make clean - make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU + make -j4 profile-build ARCH=$BINARY COMP=$COMP RUN_PREFIX=$EMU make strip ARCH=$BINARY COMP=$COMP - WINE_PATH=$EMU ../tests/signature.sh $benchref + RUN_PREFIX=$EMU ../tests/signature.sh $benchref mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT - name: Remove non src files diff --git a/.github/workflows/compilation.yml b/.github/workflows/compilation.yml index 7805b24d6bf..473665ec2d2 100644 --- a/.github/workflows/compilation.yml +++ b/.github/workflows/compilation.yml @@ -76,9 +76,9 @@ jobs: - name: Compile ${{ matrix.config.binaries }} build run: | make clean - make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE" + make -j4 profile-build ARCH=$BINARY COMP=$COMP RUN_PREFIX="$SDE" make strip ARCH=$BINARY COMP=$COMP - WINE_PATH="$SDE" ../tests/signature.sh $benchref + RUN_PREFIX="$SDE" ../tests/signature.sh $benchref mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT - name: Remove non src files diff --git a/src/Makefile b/src/Makefile index bf3fbe80faa..fa629793668 100644 --- a/src/Makefile +++ b/src/Makefile @@ -25,6 +25,21 @@ ifeq ($(KERNEL),Linux) OS := $(shell uname -o) endif +### Command prefix to run the built executable (e.g. wine, sde, qemu) +### Backward compatible alias: WINE_PATH (deprecated) +ifneq ($(strip $(WINE_PATH)),) +ifeq ($(strip $(RUN_PREFIX)),) +RUN_PREFIX := $(WINE_PATH) +endif +ifeq ($(MAKELEVEL),0) +ifneq ($(strip $(RUN_PREFIX)),$(strip $(WINE_PATH))) +$(warning *** Both RUN_PREFIX and WINE_PATH are set; ignoring WINE_PATH. ***) +else +$(warning *** WINE_PATH is deprecated; use RUN_PREFIX instead. ***) +endif +endif +endif + ### Target Windows OS ifeq ($(OS),Windows_NT) ifneq ($(COMP),ndk) @@ -32,8 +47,8 @@ ifeq ($(OS),Windows_NT) endif else ifeq ($(COMP),mingw) target_windows = yes - ifeq ($(WINE_PATH),) - WINE_PATH := $(shell which wine) + ifeq ($(RUN_PREFIX),) + RUN_PREFIX := $(shell which wine) endif endif @@ -49,7 +64,7 @@ PREFIX = /usr/local BINDIR = $(PREFIX)/bin ### Built-in benchmark for pgo-builds -PGOBENCH = $(WINE_PATH) ./$(EXE) bench +PGOBENCH = $(RUN_PREFIX) ./$(EXE) bench ### Source and object files SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ diff --git a/tests/signature.sh b/tests/signature.sh index 0f6dd758561..ef781a0f492 100755 --- a/tests/signature.sh +++ b/tests/signature.sh @@ -18,7 +18,7 @@ error() trap 'error ${LINENO}' ERR # obtain -eval "$WINE_PATH ./stockfish bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO} +eval "$RUN_PREFIX ./stockfish bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO} signature=$(grep "Nodes searched : " "$STDERR_FILE" | awk '{print $4}') rm -f "$STDOUT_FILE" "$STDERR_FILE" From 593eeaf24c062482db095b331f10147e105524be Mon Sep 17 00:00:00 2001 From: anematode Date: Sun, 28 Dec 2025 12:18:33 -0800 Subject: [PATCH 1275/1309] simplify find_nnz a bit This code path is never taken for vector sizes >= 512, so we can simplify it. closes https://github.com/official-stockfish/Stockfish/pull/6501 No functional change --- .../layers/affine_transform_sparse_input.h | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 85c0dbfecab..789ee454e6b 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -138,11 +138,12 @@ void find_nnz(const std::int32_t* RESTRICT input, using namespace SIMD; constexpr IndexType InputSimdWidth = sizeof(vec_uint_t) / sizeof(std::int32_t); - // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) - constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); - constexpr IndexType NumChunks = InputDimensions / ChunkSize; - constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; - constexpr IndexType OutputsPerChunk = ChunkSize / 8; + // Outputs are processed 8 elements at a time, even if the SIMD width is narrower + constexpr IndexType ChunkSize = 8; + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + + static_assert(InputsPerChunk > 0 && "SIMD width too wide"); const auto inputVector = reinterpret_cast(input); IndexType count = 0; @@ -157,15 +158,11 @@ void find_nnz(const std::int32_t* RESTRICT input, const vec_uint_t inputChunk = inputVector[i * InputsPerChunk + j]; nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); } - for (IndexType j = 0; j < OutputsPerChunk; ++j) - { - 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); - } + const vec128_t offsets = + vec128_load(reinterpret_cast(&Lookup.offset_indices[nnz])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); + count += popcount(nnz); + base = vec128_add(base, increment); } count_out = count; #endif From 5b9259e51fbf0231d2d97039f43590ff47a9d481 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 29 Dec 2025 00:51:10 +0300 Subject: [PATCH 1276/1309] Replacing nested loops with a single range-based for loop closes https://github.com/official-stockfish/Stockfish/pull/6503 No functional change --- src/nnue/nnue_feature_transformer.h | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index ce23bdf0ee6..98b031f6b39 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -145,15 +145,10 @@ class FeatureTransformer { } inline void scale_weights(bool read) { - for (IndexType j = 0; j < InputDimensions; ++j) - { - WeightType* w = &weights[j * HalfDimensions]; - for (IndexType i = 0; i < HalfDimensions; ++i) - w[i] = read ? w[i] * 2 : w[i] / 2; - } - - for (IndexType i = 0; i < HalfDimensions; ++i) - biases[i] = read ? biases[i] * 2 : biases[i] / 2; + for (auto& w : weights) + w = read ? w * 2 : w / 2; + for (auto& b : biases) + b = read ? b * 2 : b / 2; } // Read network parameters From 8815d1ef02038e5f60b974b2d24c380bbd6ba4d8 Mon Sep 17 00:00:00 2001 From: mstembera <5421953+mstembera@users.noreply.github.com> Date: Tue, 30 Dec 2025 20:45:32 -0800 Subject: [PATCH 1277/1309] Minor cleanup in full_threats.cpp closes https://github.com/official-stockfish/Stockfish/pull/6509 No functional change --- src/nnue/features/full_threats.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 63b7f8e1394..9006d851be1 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -68,11 +68,11 @@ constexpr auto make_piece_indices_type() { std::array, SQUARE_NB> out{}; - for (int from = 0; from < SQUARE_NB; ++from) + for (Square from = SQ_A1; from <= SQ_H8; ++from) { - Bitboard attacks = PseudoAttacks[PT][Square(from)]; + Bitboard attacks = PseudoAttacks[PT][from]; - for (int to = 0; to < SQUARE_NB; ++to) + for (Square to = SQ_A1; to <= SQ_H8; ++to) { out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks); } @@ -89,11 +89,11 @@ constexpr auto make_piece_indices_piece() { constexpr Color C = color_of(P); - for (int from = 0; from < SQUARE_NB; ++from) + for (Square from = SQ_A1; from <= SQ_H8; ++from) { Bitboard attacks = PseudoAttacks[C][from]; - for (int to = 0; to < SQUARE_NB; ++to) + for (Square to = SQ_A1; to <= SQ_H8; ++to) { out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks); } @@ -323,11 +323,11 @@ void FullThreats::append_changed_indices(Color perspective, { if (first) { - fusedData->dp2removedOriginBoard |= square_bb(to); + fusedData->dp2removedOriginBoard |= to; continue; } } - else if (fusedData->dp2removedOriginBoard & square_bb(to)) + else if (fusedData->dp2removedOriginBoard & to) continue; } @@ -337,11 +337,11 @@ void FullThreats::append_changed_indices(Color perspective, { if (first) { - fusedData->dp2removedTargetBoard |= square_bb(from); + fusedData->dp2removedTargetBoard |= from; continue; } } - else if (fusedData->dp2removedTargetBoard & square_bb(from)) + else if (fusedData->dp2removedTargetBoard & from) continue; } } From 28844fc6975b002150190464914e5b340a2c9209 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Thu, 1 Jan 2026 15:17:27 +0100 Subject: [PATCH 1278/1309] Update of the year Happy New Year! closes https://github.com/official-stockfish/Stockfish/pull/6514 No functional change --- src/Makefile | 2 +- src/benchmark.cpp | 2 +- src/benchmark.h | 2 +- src/bitboard.cpp | 2 +- src/bitboard.h | 2 +- src/engine.cpp | 2 +- src/engine.h | 2 +- src/evaluate.cpp | 2 +- src/evaluate.h | 2 +- src/history.h | 2 +- src/main.cpp | 2 +- src/memory.cpp | 2 +- src/memory.h | 2 +- src/misc.cpp | 2 +- src/misc.h | 2 +- src/movegen.cpp | 2 +- src/movegen.h | 2 +- src/movepick.cpp | 2 +- src/movepick.h | 2 +- src/nnue/features/full_threats.cpp | 2 +- src/nnue/features/full_threats.h | 2 +- src/nnue/features/half_ka_v2_hm.cpp | 2 +- src/nnue/features/half_ka_v2_hm.h | 2 +- src/nnue/layers/affine_transform.h | 2 +- src/nnue/layers/affine_transform_sparse_input.h | 2 +- src/nnue/layers/clipped_relu.h | 2 +- src/nnue/layers/sqr_clipped_relu.h | 2 +- src/nnue/network.cpp | 2 +- src/nnue/network.h | 2 +- src/nnue/nnue_accumulator.cpp | 2 +- src/nnue/nnue_accumulator.h | 2 +- src/nnue/nnue_architecture.h | 2 +- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 2 +- src/nnue/nnue_misc.cpp | 2 +- src/nnue/nnue_misc.h | 2 +- src/nnue/simd.h | 2 +- src/numa.h | 2 +- src/perft.h | 2 +- src/position.cpp | 2 +- src/position.h | 2 +- src/score.cpp | 2 +- src/score.h | 2 +- src/search.cpp | 2 +- src/search.h | 2 +- src/shm.h | 2 +- src/shm_linux.h | 2 +- src/syzygy/tbprobe.cpp | 2 +- src/syzygy/tbprobe.h | 2 +- src/thread.cpp | 2 +- src/thread.h | 2 +- src/thread_win32_osx.h | 2 +- src/timeman.cpp | 2 +- src/timeman.h | 2 +- src/tt.cpp | 2 +- src/tt.h | 2 +- src/tune.cpp | 2 +- src/tune.h | 2 +- src/types.h | 2 +- src/uci.cpp | 2 +- src/uci.h | 2 +- src/ucioption.cpp | 2 +- src/ucioption.h | 2 +- 63 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/Makefile b/src/Makefile index fa629793668..dcd3f1fea09 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) +# Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 039e384c9ac..4e266db89c3 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/benchmark.h b/src/benchmark.h index d6bdc275df9..a6606e78cad 100644 --- a/src/benchmark.h +++ b/src/benchmark.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 350e56c927f..4decb8d6666 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/bitboard.h b/src/bitboard.h index 1da11d45c31..f97ff32199a 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/engine.cpp b/src/engine.cpp index 40466c8f874..355103c7b70 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/engine.h b/src/engine.h index 6fd1ce04002..10c92d7592f 100644 --- a/src/engine.h +++ b/src/engine.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e7133f2dffc..745bd3e4d56 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/evaluate.h b/src/evaluate.h index 8ed2eb99454..b4f54a381aa 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/history.h b/src/history.h index 3aa9ef623ab..c98a7ee223b 100644 --- a/src/history.h +++ b/src/history.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/main.cpp b/src/main.cpp index 107b5e43d5f..9a7376efbaf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/memory.cpp b/src/memory.cpp index f4aa8fc2656..94a5993991b 100644 --- a/src/memory.cpp +++ b/src/memory.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/memory.h b/src/memory.h index dad07df1ddb..c307a131747 100644 --- a/src/memory.h +++ b/src/memory.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.cpp b/src/misc.cpp index 886544b6c42..3ddae503ccb 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/misc.h b/src/misc.h index c9951e5557e..f1016b496de 100644 --- a/src/misc.h +++ b/src/misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.cpp b/src/movegen.cpp index 697a83cdfc5..d22faad2582 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movegen.h b/src/movegen.h index 287fd8927b5..7f209f92a7e 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.cpp b/src/movepick.cpp index 15f64d1cb36..415d252f31d 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/movepick.h b/src/movepick.h index b1e041c17f0..08bd9a53915 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 9006d851be1..4e3ba81cf85 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index e4fa3331bac..5b2582954e9 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 56779ddcea7..a82e89de486 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index c58a3246be8..49b0a87a4d0 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index 7ead09327e5..a3d072f6782 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 789ee454e6b..5e0551f6963 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h index a8679d14d72..7284c1033fa 100644 --- a/src/nnue/layers/clipped_relu.h +++ b/src/nnue/layers/clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h index 4218c0fe259..53412d014a4 100644 --- a/src/nnue/layers/sqr_clipped_relu.h +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index a4d464df0fc..d1f2b14c321 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/network.h b/src/nnue/network.h index ba8f469f7cd..d0e3218ca52 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 338d291e8f5..16af8d5f68e 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 1ccab5f2f3a..438074f430a 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 5093abdd7a9..71fce9bd59a 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index 8a877ae2b4b..febe7ca7050 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 98b031f6b39..99cda2a69c4 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 220140e5e08..66a6764a33d 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index 7ecfd58a2f6..ecece5589c2 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/nnue/simd.h b/src/nnue/simd.h index 6160221b99e..25891163ea1 100644 --- a/src/nnue/simd.h +++ b/src/nnue/simd.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/numa.h b/src/numa.h index 76d265af2fd..99169c21128 100644 --- a/src/numa.h +++ b/src/numa.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/perft.h b/src/perft.h index e249a8e49d6..24d125cbf47 100644 --- a/src/perft.h +++ b/src/perft.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.cpp b/src/position.cpp index f32530fddf3..d8b02e8ab9e 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/position.h b/src/position.h index e49e10f963c..a136c0729b2 100644 --- a/src/position.h +++ b/src/position.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/score.cpp b/src/score.cpp index 561bc23c48e..ea62577b9f4 100644 --- a/src/score.cpp +++ b/src/score.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/score.h b/src/score.h index eda90af35c0..cf89d3cdd54 100644 --- a/src/score.h +++ b/src/score.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.cpp b/src/search.cpp index a880a741f33..afdda262ce6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/search.h b/src/search.h index eb4dda77c17..202f7c8db2e 100644 --- a/src/search.h +++ b/src/search.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/shm.h b/src/shm.h index b870afc2434..9bf13f23497 100644 --- a/src/shm.h +++ b/src/shm.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/shm_linux.h b/src/shm_linux.h index 52abe8ec440..1e344e93fd8 100644 --- a/src/shm_linux.h +++ b/src/shm_linux.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index c371259d67d..8db00719407 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index 4a6c3b763a4..7b60d6e2054 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.cpp b/src/thread.cpp index eaf1d09bdee..c485697da38 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread.h b/src/thread.h index 2368c3069c1..f97e2b3f898 100644 --- a/src/thread.h +++ b/src/thread.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h index fb4b2ec9718..5a8d43a2e77 100644 --- a/src/thread_win32_osx.h +++ b/src/thread_win32_osx.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.cpp b/src/timeman.cpp index e82a1f6bf18..4e98081bc2b 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/timeman.h b/src/timeman.h index a2d1a4364d0..e72cc102aa5 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.cpp b/src/tt.cpp index d7f7dbdef6b..ef602809f79 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tt.h b/src/tt.h index 26b63c1adb4..38f6c8f4f62 100644 --- a/src/tt.h +++ b/src/tt.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.cpp b/src/tune.cpp index f53a0eb52d6..f930c267e22 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/tune.h b/src/tune.h index d3c9ebaa3a2..4ce6e759fde 100644 --- a/src/tune.h +++ b/src/tune.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/types.h b/src/types.h index 1bb8bd3cc9a..b5a0498a80b 100644 --- a/src/types.h +++ b/src/types.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.cpp b/src/uci.cpp index be7de97d715..139d97b609e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/uci.h b/src/uci.h index 1686b3a7208..c9b5943935c 100644 --- a/src/uci.h +++ b/src/uci.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/ucioption.cpp b/src/ucioption.cpp index ff6235695ea..8db7967497e 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/ucioption.h b/src/ucioption.h index 0c957fda171..4f6d7541cff 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by From d678f839d8db235a7b6bc4e759569cb23a6d068b Mon Sep 17 00:00:00 2001 From: Syine Mineta Date: Tue, 6 Jan 2026 06:10:47 +0900 Subject: [PATCH 1279/1309] Fix remote access bug across NUMA nodes Ensure that thread-local data is created within the correct NUMA context, so that thread stacks or thread-local storage are allocated to proper NUMA nodes. refs https://github.com/official-stockfish/Stockfish/issues/6516 closes https://github.com/official-stockfish/Stockfish/pull/6518 No functional change --- src/thread.cpp | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/thread.cpp b/src/thread.cpp index c485697da38..441af563d8d 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -213,22 +213,31 @@ void ThreadPool::set(const NumaConfig& numaConfig, while (threads.size() < requested) { - const size_t threadId = threads.size(); - const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0; - auto manager = threadId == 0 ? std::unique_ptr( - std::make_unique(updateContext)) - : std::make_unique(); - - // When not binding threads we want to force all access to happen - // from the same NUMA node, because in case of NUMA replicated memory - // accesses we don't want to trash cache in case the threads get scheduled - // on the same NUMA node. - auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId) - : OptionalThreadToNumaNodeBinder(numaId); - - threads.emplace_back(std::make_unique(sharedState, std::move(manager), threadId, - counts[numaId]++, threadsPerNode[numaId], - binder)); + const size_t threadId = threads.size(); + const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0; + auto create_thread = [&]() { + auto manager = threadId == 0 + ? std::unique_ptr( + std::make_unique(updateContext)) + : std::make_unique(); + + // When not binding threads we want to force all access to happen + // from the same NUMA node, because in case of NUMA replicated memory + // accesses we don't want to trash cache in case the threads get scheduled + // on the same NUMA node. + auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId) + : OptionalThreadToNumaNodeBinder(numaId); + + threads.emplace_back(std::make_unique(sharedState, std::move(manager), + threadId, counts[numaId]++, + threadsPerNode[numaId], binder)); + }; + + // Ensure the worker thread inherits the intended NUMA affinity at creation. + if (doBindThreads) + numaConfig.execute_on_numa_node(numaId, create_thread); + else + create_thread(); } clear(); From 8be6b142189dfaa4fdee979d863a53522ca46b32 Mon Sep 17 00:00:00 2001 From: anematode Date: Mon, 5 Jan 2026 15:37:49 -0800 Subject: [PATCH 1280/1309] Network loading refactoring closes https://github.com/official-stockfish/Stockfish/pull/6523 No functional change --- src/engine.cpp | 12 ++--- src/nnue/network.h | 7 ++- src/nnue/nnue_common.h | 74 +++++++++++++++-------------- src/nnue/nnue_feature_transformer.h | 24 ++-------- 4 files changed, 51 insertions(+), 66 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 355103c7b70..01927fc8434 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -57,14 +57,10 @@ Engine::Engine(std::optional path) : numaContext(NumaConfig::from_system()), states(new std::deque(1)), threads(), - networks( - numaContext, - // Heap-allocate because sizeof(NN::Networks) is large - std::make_unique( - std::make_unique(NN::EvalFile{EvalFileDefaultNameBig, "None", ""}, - NN::EmbeddedNNUEType::BIG), - std::make_unique(NN::EvalFile{EvalFileDefaultNameSmall, "None", ""}, - NN::EmbeddedNNUEType::SMALL))) { + networks(numaContext, + // Heap-allocate because sizeof(NN::Networks) is large + std::make_unique(NN::EvalFile{EvalFileDefaultNameBig, "None", ""}, + NN::EvalFile{EvalFileDefaultNameSmall, "None", ""})) { pos.set(StartFEN, false, &states->back()); diff --git a/src/nnue/network.h b/src/nnue/network.h index d0e3218ca52..cb433718d43 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -28,7 +28,6 @@ #include #include #include -#include #include "../misc.h" #include "../types.h" @@ -130,9 +129,9 @@ using NetworkSmall = Network; struct Networks { - Networks(std::unique_ptr&& nB, std::unique_ptr&& nS) : - big(std::move(*nB)), - small(std::move(*nS)) {} + Networks(EvalFile bigFile, EvalFile smallFile) : + big(bigFile, EmbeddedNNUEType::BIG), + small(smallFile, EmbeddedNNUEType::SMALL) {} NetworkBig big; NetworkSmall small; diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index febe7ca7050..27852ac7b7b 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -169,52 +169,56 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std write_little_endian(stream, values[i]); } - // Read N signed integers from the stream s, putting them in the array out. // The stream is assumed to be compressed using the signed LEB128 format. // See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. -template -inline void read_leb_128(std::istream& stream, std::array& out) { - - // Check the presence of our LEB128 magic string - char leb128MagicString[Leb128MagicStringSize]; - stream.read(leb128MagicString, Leb128MagicStringSize); - assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); +template +inline void read_leb_128_detail(std::istream& stream, + std::array& out, + std::uint32_t& bytes_left, + BufType& buf, + std::uint32_t& buf_pos) { static_assert(std::is_signed_v, "Not implemented for unsigned types"); + static_assert(sizeof(IntType) <= 4, "Not implemented for types larger than 32 bit"); - const std::uint32_t BUF_SIZE = 4096; - std::uint8_t buf[BUF_SIZE]; - - auto bytes_left = read_little_endian(stream); - - std::uint32_t buf_pos = BUF_SIZE; - for (std::size_t i = 0; i < Count; ++i) + IntType result = 0; + size_t shift = 0, i = 0; + while (i < Count) { - IntType result = 0; - size_t shift = 0; - do + if (buf_pos == buf.size()) { - if (buf_pos == BUF_SIZE) - { - stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); - buf_pos = 0; - } + stream.read(reinterpret_cast(buf.data()), + std::min(std::size_t(bytes_left), buf.size())); + buf_pos = 0; + } - std::uint8_t byte = buf[buf_pos++]; - --bytes_left; - result |= (byte & 0x7f) << shift; - shift += 7; + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << (shift % 32); + shift += 7; - if ((byte & 0x80) == 0) - { - out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) - ? result - : result | ~((1 << shift) - 1); - break; - } - } while (shift < sizeof(IntType) * 8); + if ((byte & 0x80) == 0) + { + out[i++] = (shift >= 32 || (byte & 0x40) == 0) ? result : result | ~((1 << shift) - 1); + result = 0; + shift = 0; + } } +} + +template +inline void read_leb_128(std::istream& stream, Arrays&... outs) { + // Check the presence of our LEB128 magic string + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + + auto bytes_left = read_little_endian(stream); + std::array buf; + std::uint32_t buf_pos = buf.size(); + + (read_leb_128_detail(stream, outs, bytes_left, buf, buf_pos), ...); assert(bytes_left == 0); } diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 99cda2a69c4..c7b784573c2 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -152,35 +152,21 @@ class FeatureTransformer { } // Read network parameters - // TODO: This is ugly. Currently LEB128 on the entire L1 necessitates - // reading the weights into a combined array, and then splitting. bool read_parameters(std::istream& stream) { - read_leb_128(stream, biases); + read_leb_128(stream, biases); if (UseThreats) { read_little_endian(stream, threatWeights.data(), ThreatInputDimensions * HalfDimensions); - read_leb_128(stream, weights); + read_leb_128(stream, weights); - auto combinedPsqtWeights = - std::make_unique>(); - - read_leb_128(stream, *combinedPsqtWeights); - - std::copy(combinedPsqtWeights->begin(), - combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets, - std::begin(threatPsqtWeights)); - - std::copy(combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets, - combinedPsqtWeights->begin() - + (ThreatInputDimensions + InputDimensions) * PSQTBuckets, - std::begin(psqtWeights)); + read_leb_128(stream, threatPsqtWeights, psqtWeights); } else { - read_leb_128(stream, weights); - read_leb_128(stream, psqtWeights); + read_leb_128(stream, weights); + read_leb_128(stream, psqtWeights); } permute_weights(); From c27c1747e3956bc3048ceb1ced00802bda0ec86d Mon Sep 17 00:00:00 2001 From: Jakub Ciolek Date: Sat, 3 Jan 2026 23:46:46 +0100 Subject: [PATCH 1281/1309] qsearch: prevent bestValue from going down The bestValue can sometimes go down. This happens 2% of the time or so. This fix stops it from decreasing. Failed gainer STC: LLR: -2.94 (-2.94,2.94) <0.00,2.00> Total: 146176 W: 37930 L: 37976 D: 70270 Ptnml(0-2): 480, 17422, 37366, 17304, 516 https://tests.stockfishchess.org/tests/view/6953be19572093c1986da66a Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 257796 W: 65662 L: 65683 D: 126451 Ptnml(0-2): 164, 28247, 72087, 28246, 154 https://tests.stockfishchess.org/tests/view/69554ff0d844c1ce7cc7e333 closes https://github.com/official-stockfish/Stockfish/pull/6520 fixes https://github.com/official-stockfish/Stockfish/issues/6519 Bench: 2477446 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index fac1e7d8d90..1c3f4b97fc7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -111,6 +111,7 @@ Hongzhi Cheng Ivan Ivec (IIvec) Jacques B. (Timshel) Jake Senne (w1wwwwww) +Jakub Ciolek (jake-ciolek) Jan Ondruš (hxim) Jared Kish (Kurtbusch, kurt22i) Jarrod Torriero (DU-jdto) diff --git a/src/search.cpp b/src/search.cpp index afdda262ce6..42294ed0120 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1653,7 +1653,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // we can prune this move. if (!pos.see_ge(move, alpha - futilityBase)) { - bestValue = std::min(alpha, futilityBase); + bestValue = std::max(bestValue, std::min(alpha, futilityBase)); continue; } } From d39bfb61a2778a5fab5706309ce5379b7b3fd7b4 Mon Sep 17 00:00:00 2001 From: Disservin Date: Wed, 7 Jan 2026 23:58:55 +0100 Subject: [PATCH 1282/1309] Fix Clang Tbprobe Miscompilation Recent changes to the Square enum (reducing it from int32_t to int8_t) now allow the compiler to vectorize loops that were previously too wide for targets below AVX-512. However, this vectorization which Clang performs is not correct and causes a miscompilation. Disable this vectorization. This particular issue was noticable with Clang 15 and Clang 19, on avx2 as well as applie-silicon. Ref: #6063 Original Clang Issue: llvm/llvm-project#80494 First reported by #6528, though misinterpreted. closes #6529 No functional change --- src/syzygy/tbprobe.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 8db00719407..9fe6df9dca0 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -709,15 +709,11 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { return value + 1; } -// A temporary fix for the compiler bug with AVX-512. (#4450) -#ifdef USE_AVX512 - #if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15 - #define CLANG_AVX512_BUG_FIX __attribute__((optnone)) - #endif -#endif - -#ifndef CLANG_AVX512_BUG_FIX - #define CLANG_AVX512_BUG_FIX +// A temporary fix for the compiler bug with vectorization. (#4450) +#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15 + #define DISABLE_CLANG_LOOP_VEC _Pragma("clang loop vectorize(disable)") +#else + #define DISABLE_CLANG_LOOP_VEC #endif // Compute a unique index out of a position and use it to probe the TB file. To @@ -727,8 +723,7 @@ int map_score(TBTable* entry, File f, int value, WDLScore wdl) { // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] // template -CLANG_AVX512_BUG_FIX Ret -do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { +Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { Square squares[TBPIECES]; Piece pieces[TBPIECES]; @@ -812,8 +807,11 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) // Now we map again the squares so that the square of the lead piece is in // the triangle A1-D1-D4. if (file_of(squares[0]) > FILE_D) + { + DISABLE_CLANG_LOOP_VEC for (int i = 0; i < size; ++i) squares[i] = flip_file(squares[i]); + } // Encode leading pawns starting with the one with minimum MapPawns[] and // proceeding in ascending order. @@ -832,19 +830,26 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) // In positions without pawns, we further flip the squares to ensure leading // piece is below RANK_5. if (rank_of(squares[0]) > RANK_4) + { + DISABLE_CLANG_LOOP_VEC for (int i = 0; i < size; ++i) squares[i] = flip_rank(squares[i]); + } // Look for the first piece of the leading group not on the A1-D4 diagonal // and ensure it is mapped below the diagonal. + DISABLE_CLANG_LOOP_VEC for (int i = 0; i < d->groupLen[0]; ++i) { if (!off_A1H8(squares[i])) continue; if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 + { + DISABLE_CLANG_LOOP_VEC for (int j = i; j < size; ++j) squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); + } break; } From 5d5e795746c4be012c39506edc451bdececd2981 Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 8 Jan 2026 00:13:24 +0100 Subject: [PATCH 1283/1309] Fix Compiler Warning Only the one on line 158 is actually required but doesn't hurt to add constexpr where applicable here. Warning was "comparison of unsigned expression in '< 0' is always false" closes https://github.com/official-stockfish/Stockfish/pull/6530 No functional change --- src/nnue/nnue_feature_transformer.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index c7b784573c2..f4cf348ab73 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -132,7 +132,7 @@ class FeatureTransformer { permute<16>(biases, PackusEpi16Order); permute<16>(weights, PackusEpi16Order); - if (UseThreats) + if constexpr (UseThreats) permute<8>(threatWeights, PackusEpi16Order); } @@ -140,7 +140,7 @@ class FeatureTransformer { permute<16>(biases, InversePackusEpi16Order); permute<16>(weights, InversePackusEpi16Order); - if (UseThreats) + if constexpr (UseThreats) permute<8>(threatWeights, InversePackusEpi16Order); } @@ -155,7 +155,7 @@ class FeatureTransformer { bool read_parameters(std::istream& stream) { read_leb_128(stream, biases); - if (UseThreats) + if constexpr (UseThreats) { read_little_endian(stream, threatWeights.data(), ThreatInputDimensions * HalfDimensions); @@ -171,7 +171,7 @@ class FeatureTransformer { permute_weights(); - if (!UseThreats) + if constexpr (!UseThreats) scale_weights(true); return !stream.fail(); @@ -183,12 +183,12 @@ class FeatureTransformer { copy->unpermute_weights(); - if (!UseThreats) + if constexpr (!UseThreats) copy->scale_weights(false); write_leb_128(stream, copy->biases); - if (UseThreats) + if constexpr (UseThreats) { write_little_endian(stream, copy->threatWeights.data(), ThreatInputDimensions * HalfDimensions); @@ -242,7 +242,7 @@ class FeatureTransformer { auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]); - if (UseThreats) + if constexpr (UseThreats) { const auto& threatPsqtAccumulation = (threatAccumulatorState.acc()).psqtAccumulation; @@ -334,7 +334,7 @@ class FeatureTransformer { #else 6; #endif - if (UseThreats) + if constexpr (UseThreats) { const vec_t* tin0 = reinterpret_cast(&(threatAccumulation[perspectives[p]][0])); @@ -386,7 +386,7 @@ class FeatureTransformer { BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - if (UseThreats) + if constexpr (UseThreats) { BiasType sum0t = threatAccumulation[static_cast(perspectives[p])][j + 0]; BiasType sum1t = From 1928ef9571a5c742c8ebf82a144d3a0401f59d82 Mon Sep 17 00:00:00 2001 From: Disservin Date: Thu, 8 Jan 2026 19:56:12 +0100 Subject: [PATCH 1284/1309] Compiler Check Compiles and Runs Stockfish on all supported gcc & clang compilers. Only linux and avx2 currently. closes https://github.com/official-stockfish/Stockfish/pull/6533 No functional change --- .github/workflows/avx2_compilers.yml | 85 ++++++++++++++++++++++++++++ .github/workflows/stockfish.yml | 2 + 2 files changed, 87 insertions(+) create mode 100644 .github/workflows/avx2_compilers.yml diff --git a/.github/workflows/avx2_compilers.yml b/.github/workflows/avx2_compilers.yml new file mode 100644 index 00000000000..5ceb62bd9ba --- /dev/null +++ b/.github/workflows/avx2_compilers.yml @@ -0,0 +1,85 @@ +name: AVX2 Compiler Matrix + +on: + workflow_call: + +jobs: + avx2-compiler-matrix: + name: avx2 (${{ matrix.name }}) + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + + strategy: + fail-fast: false + matrix: + include: + - { name: gcc-10, comp: gcc, cxx: g++, image: "gcc:10" } + - { name: gcc-11, comp: gcc, cxx: g++, image: "gcc:11" } + - { name: gcc-12, comp: gcc, cxx: g++, image: "gcc:12" } + - { name: gcc-13, comp: gcc, cxx: g++, image: "gcc:13" } + - { name: gcc-14, comp: gcc, cxx: g++, image: "gcc:14" } + - { name: gcc-15, comp: gcc, cxx: g++, image: "gcc:15" } + + # Using silkeh/clang for older versions + - { name: clang-10, comp: clang, cxx: clang++, image: "silkeh/clang:10", is_clang: true, ver: "10" } + - { name: clang-11, comp: clang, cxx: clang++, image: "silkeh/clang:11", is_clang: true, ver: "11" } + - { name: clang-12, comp: clang, cxx: clang++, image: "silkeh/clang:12", is_clang: true, ver: "12" } + - { name: clang-13, comp: clang, cxx: clang++, image: "silkeh/clang:13", is_clang: true, ver: "13" } + - { name: clang-14, comp: clang, cxx: clang++, image: "silkeh/clang:14", is_clang: true, ver: "14" } + - { name: clang-15, comp: clang, cxx: clang++, image: "silkeh/clang:15", is_clang: true, ver: "15" } + - { name: clang-16, comp: clang, cxx: clang++, image: "silkeh/clang:16", is_clang: true, ver: "16" } + - { name: clang-17, comp: clang, cxx: clang++, image: "silkeh/clang:17", is_clang: true, ver: "17" } + + - { name: clang-18, comp: clang, cxx: clang++-18, image: "ubuntu:rolling", is_clang: true, ver: "18" } + - { name: clang-19, comp: clang, cxx: clang++-19, image: "ubuntu:rolling", is_clang: true, ver: "19" } + - { name: clang-20, comp: clang, cxx: clang++-20, image: "ubuntu:rolling", is_clang: true, ver: "20" } + - { name: clang-21, comp: clang, cxx: clang++-21, image: "ubuntu:rolling", is_clang: true, ver: "21" } + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + if grep -q "buster" /etc/os-release; then + echo "Debian Buster detected. Switching to archive repositories..." + echo "deb http://archive.debian.org/debian buster main contrib non-free" > /etc/apt/sources.list + echo "deb http://archive.debian.org/debian-security buster/updates main contrib non-free" >> /etc/apt/sources.list + echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99-ignore-valid-until + fi + + apt-get update + apt-get install -y curl git make + + - name: Set up Clang + if: ${{ matrix.is_clang && matrix.image == 'ubuntu:rolling' }} + run: | + if [ "${{ matrix.ver }}" -le 20 ]; then + apt-get install -y clang-${{ matrix.ver }} + else + apt-get install -y \ + clang-${{ matrix.ver }} \ + llvm-${{ matrix.ver }}-dev \ + llvm-${{ matrix.ver }}-linker-tools \ + lld-${{ matrix.ver }} + fi + + - name: Download network + working-directory: src + run: make net + + - name: Build avx2 binary + working-directory: src + run: | + export CXXFLAGS="-Werror" + if [ "${{ matrix.ver }}" -ge 20 ]; then + export LDFLAGS="-fuse-ld=lld" + apt install -y lld + fi + make clean + make -j build ARCH=x86-64-avx2 COMP=${{ matrix.comp }} COMPCXX=${{ matrix.cxx }} + + - name: Smoke test + working-directory: src + run: ./stockfish bench 16 1 6 \ No newline at end of file diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index 1f87e061be9..f08d3474ab6 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -120,3 +120,5 @@ jobs: contents: write # For deleting/creating a (pre)release secrets: token: ${{ secrets.GITHUB_TOKEN }} + CompilerCheck: + uses: ./.github/workflows/avx2_compilers.yml From b4d4eecfb24633c89d0f7fde3f76a3c7c4e0dac1 Mon Sep 17 00:00:00 2001 From: anematode Date: Wed, 7 Jan 2026 06:36:25 -0800 Subject: [PATCH 1285/1309] Make shared history allocation aware of non-uniform cache access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although shared history has been successful overall, it led to some speed issues with large numbers of threads. Originally we just split by NUMA node, but on systems with non-unified L3 caches (most AMD workstation and server CPUs, and some Intel E-core based server CPUs), this can still lead to a speed penalty at the default config. Thus, we decided to further subdivide the shared history based on the L3 cache structure. Based on this test, the original SPRTs, and speed experiments, we decided that grouping L3 domains to reach 32 threads per SharedHistories was a reasonable balance for affected systems – but we may revisit this in the future. See the PR for full details. In an extreme case, a single-socket EPYC 9755 configured with 1 numa domain per socket, the nps increases from: Nodes/second : 182827480 to Nodes/second : 229118365 In many cases, when L3 caches are shared between many threads, or when several numa nodes are already configured per socket, this patch does not influence the default. This default setting can adjusted with the existing NumaPolicy option. closes https://github.com/official-stockfish/Stockfish/pull/6526 No functional change. --- src/engine.cpp | 12 +- src/numa.h | 396 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 309 insertions(+), 99 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 01927fc8434..dfd2099d1e5 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -52,9 +52,15 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency())); +// The default configuration will attempt to group L3 domains up to 32 threads. +// This size was found to be a good balance between the Elo gain of increased +// history sharing and the speed loss from more cross-cache accesses (see +// PR#6526). The user can always explicitly override this behavior. +constexpr NumaAutoPolicy DefaultNumaPolicy = BundledL3Policy{32}; + Engine::Engine(std::optional path) : binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), - numaContext(NumaConfig::from_system()), + numaContext(NumaConfig::from_system(DefaultNumaPolicy)), states(new std::deque(1)), threads(), networks(numaContext, @@ -213,12 +219,12 @@ void Engine::set_position(const std::string& fen, const std::vector void Engine::set_numa_config_from_option(const std::string& o) { if (o == "auto" || o == "system") { - numaContext.set_numa_config(NumaConfig::from_system()); + numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy)); } else if (o == "hardware") { // Don't respect affinity set in the system. - numaContext.set_numa_config(NumaConfig::from_system(false)); + numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy, false)); } else if (o == "none") { diff --git a/src/numa.h b/src/numa.h index 99169c21128..df11f02864f 100644 --- a/src/numa.h +++ b/src/numa.h @@ -447,14 +447,39 @@ class NumaReplicatedAccessToken { NumaIndex n; }; +struct L3Domain { + NumaIndex systemNumaIndex{}; + std::set cpus{}; +}; + +// Use system NUMA nodes +struct SystemNumaPolicy {}; +// Use system-reported L3 domains +struct L3DomainsPolicy {}; +// Group system-reported L3 domains until they reach bundleSize +struct BundledL3Policy { + size_t bundleSize; +}; + +using NumaAutoPolicy = std::variant; + // Designed as immutable, because there is no good reason to alter an already // existing config in a way that doesn't require recreating it completely, and // it would be complex and expensive to maintain class invariants. // The CPU (processor) numbers always correspond to the actual numbering used // by the system. The NUMA node numbers MAY NOT correspond to the system's -// numbering of the NUMA nodes. In particular, empty nodes may be removed, or -// the user may create custom nodes. It is guaranteed that NUMA nodes are NOT -// empty: every node exposed by NumaConfig has at least one processor assigned. +// numbering of the NUMA nodes. In particular, by default, if the processor has +// non-uniform cache access within a NUMA node (i.e., a non-unified L3 cache structure), +// then L3 domains within a system NUMA node will be used to subdivide it +// into multiple logical NUMA nodes in the config. Additionally, empty nodes may +// be removed, or the user may create custom nodes. +// +// As a special case, when performing system-wide replication of read-only data +// (i.e., LazyNumaReplicatedSystemWide), the system NUMA node is used, rather than +// custom or L3-aware nodes. See that class's get_discriminator() function. +// +// It is guaranteed that NUMA nodes are NOT empty: every node exposed by NumaConfig +// has at least one processor assigned. // // We use startup affinities so as not to modify its own behaviour in time. // @@ -469,78 +494,19 @@ class NumaConfig { add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1); } - // This function queries the system for the mapping of processors to NUMA nodes. - // On Linux we read from standardized kernel sysfs, with a fallback to single NUMA - // node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see - // comment for Windows implementation of get_process_affinity. - static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) { + // This function gets a NumaConfig based on the system's provided information. + // The available policies are documented above. + static NumaConfig from_system([[maybe_unused]] const NumaAutoPolicy& policy, + bool respectProcessAffinity = true) { NumaConfig cfg = empty(); -#if defined(__linux__) && !defined(__ANDROID__) - - std::set allowedCpus; - - if (respectProcessAffinity) - allowedCpus = STARTUP_PROCESSOR_AFFINITY; - - auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) { - return !respectProcessAffinity || allowedCpus.count(c) == 1; - }; - - // On Linux things are straightforward, since there's no processor groups and - // any thread can be scheduled on all processors. - // We try to gather this information from the sysfs first - // https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node - - bool useFallback = false; - auto fallback = [&]() { - useFallback = true; - cfg = empty(); - }; - - // /sys/devices/system/node/online contains information about active NUMA nodes - auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online"); - if (!nodeIdsStr.has_value() || nodeIdsStr->empty()) - { - fallback(); - } - else - { - remove_whitespace(*nodeIdsStr); - for (size_t n : indices_from_shortened_string(*nodeIdsStr)) - { - // /sys/devices/system/node/node.../cpulist - std::string path = - std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist"; - auto cpuIdsStr = read_file_to_string(path); - // Now, we only bail if the file does not exist. Some nodes may be - // empty, that's fine. An empty node still has a file that appears - // to have some whitespace, so we need to handle that. - if (!cpuIdsStr.has_value()) - { - fallback(); - break; - } - else - { - remove_whitespace(*cpuIdsStr); - for (size_t c : indices_from_shortened_string(*cpuIdsStr)) - { - if (is_cpu_allowed(c)) - cfg.add_cpu_to_node(n, c); - } - } - } - } - - if (useFallback) - { - for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) - if (is_cpu_allowed(c)) - cfg.add_cpu_to_node(NumaIndex{0}, c); - } +#if !((defined(__linux__) && !defined(__ANDROID__)) || defined(_WIN64)) + // Fallback for unsupported systems. + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + cfg.add_cpu_to_node(NumaIndex{0}, c); +#else -#elif defined(_WIN64) + #if defined(_WIN64) std::optional> allowedCpus; @@ -555,28 +521,38 @@ class NumaConfig { return !allowedCpus.has_value() || allowedCpus->count(c) == 1; }; - WORD numProcGroups = GetActiveProcessorGroupCount(); - for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup) + #elif defined(__linux__) && !defined(__ANDROID__) + + std::set allowedCpus; + + if (respectProcessAffinity) + allowedCpus = STARTUP_PROCESSOR_AFFINITY; + + auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) { + return !respectProcessAffinity || allowedCpus.count(c) == 1; + }; + + #endif + + bool l3Success = false; + if (!std::holds_alternative(policy)) { - for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) + size_t l3BundleSize = 0; + if (const auto* v = std::get_if(&policy)) { - PROCESSOR_NUMBER procnum; - procnum.Group = procGroup; - procnum.Number = number; - procnum.Reserved = 0; - USHORT nodeNumber; - - const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber); - const CpuIndex c = static_cast(procGroup) * WIN_PROCESSOR_GROUP_SIZE - + static_cast(number); - if (status != 0 && nodeNumber != std::numeric_limits::max() - && is_cpu_allowed(c)) - { - cfg.add_cpu_to_node(nodeNumber, c); - } + l3BundleSize = v->bundleSize; + } + if (auto l3Cfg = + try_get_l3_aware_config(respectProcessAffinity, l3BundleSize, is_cpu_allowed)) + { + cfg = std::move(*l3Cfg); + l3Success = true; } } + if (!l3Success) + cfg = from_system_numa(respectProcessAffinity, is_cpu_allowed); + #if defined(_WIN64) // Split the NUMA nodes to be contained within a group if necessary. // This is needed between Windows 10 Build 20348 and Windows 11, because // the new NUMA allocation behaviour was introduced while there was @@ -623,12 +599,7 @@ class NumaConfig { cfg = std::move(splitCfg); } - -#else - - // Fallback for unsupported systems. - for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) - cfg.add_cpu_to_node(NumaIndex{0}, c); + #endif #endif @@ -1041,6 +1012,239 @@ class NumaConfig { return indices; } + + // This function queries the system for the mapping of processors to NUMA nodes. + // On Linux we read from standardized kernel sysfs, with a fallback to single NUMA + // node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see + // comment for Windows implementation of get_process_affinity. + template + static NumaConfig from_system_numa([[maybe_unused]] bool respectProcessAffinity, + [[maybe_unused]] Pred&& is_cpu_allowed) { + NumaConfig cfg = empty(); + +#if defined(__linux__) && !defined(__ANDROID__) + + // On Linux things are straightforward, since there's no processor groups and + // any thread can be scheduled on all processors. + // We try to gather this information from the sysfs first + // https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node + + bool useFallback = false; + auto fallback = [&]() { + useFallback = true; + cfg = empty(); + }; + + // /sys/devices/system/node/online contains information about active NUMA nodes + auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online"); + if (!nodeIdsStr.has_value() || nodeIdsStr->empty()) + { + fallback(); + } + else + { + remove_whitespace(*nodeIdsStr); + for (size_t n : indices_from_shortened_string(*nodeIdsStr)) + { + // /sys/devices/system/node/node.../cpulist + std::string path = + std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist"; + auto cpuIdsStr = read_file_to_string(path); + // Now, we only bail if the file does not exist. Some nodes may be + // empty, that's fine. An empty node still has a file that appears + // to have some whitespace, so we need to handle that. + if (!cpuIdsStr.has_value()) + { + fallback(); + break; + } + else + { + remove_whitespace(*cpuIdsStr); + for (size_t c : indices_from_shortened_string(*cpuIdsStr)) + { + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(n, c); + } + } + } + } + + if (useFallback) + { + for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c) + if (is_cpu_allowed(c)) + cfg.add_cpu_to_node(NumaIndex{0}, c); + } + +#elif defined(_WIN64) + + WORD numProcGroups = GetActiveProcessorGroupCount(); + for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup) + { + for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) + { + PROCESSOR_NUMBER procnum; + procnum.Group = procGroup; + procnum.Number = number; + procnum.Reserved = 0; + USHORT nodeNumber; + + const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber); + const CpuIndex c = static_cast(procGroup) * WIN_PROCESSOR_GROUP_SIZE + + static_cast(number); + if (status != 0 && nodeNumber != std::numeric_limits::max() + && is_cpu_allowed(c)) + { + cfg.add_cpu_to_node(nodeNumber, c); + } + } + } + +#else + + abort(); // should not reach here + +#endif + + return cfg; + } + + template + static std::optional try_get_l3_aware_config( + bool respectProcessAffinity, size_t bundleSize, [[maybe_unused]] Pred&& is_cpu_allowed) { + // Get the normal system configuration so we know to which NUMA node + // each L3 domain belongs. + NumaConfig systemConfig = + NumaConfig::from_system(SystemNumaPolicy{}, respectProcessAffinity); + std::vector l3Domains; + +#if defined(__linux__) && !defined(__ANDROID__) + + std::set seenCpus; + auto nextUnseenCpu = [&seenCpus]() { + for (CpuIndex i = 0;; ++i) + if (!seenCpus.count(i)) + return i; + }; + + while (true) + { + CpuIndex next = nextUnseenCpu(); + auto siblingsStr = + read_file_to_string("/sys/devices/system/cpu/cpu" + std::to_string(next) + + "/cache/index3/shared_cpu_list"); + + if (!siblingsStr.has_value() || siblingsStr->empty()) + { + break; // we have read all available CPUs + } + + L3Domain domain; + for (size_t c : indices_from_shortened_string(*siblingsStr)) + { + if (is_cpu_allowed(c)) + { + domain.systemNumaIndex = systemConfig.nodeByCpu.at(c); + domain.cpus.insert(c); + } + seenCpus.insert(c); + } + if (!domain.cpus.empty()) + { + l3Domains.emplace_back(std::move(domain)); + } + } + +#elif defined(_WIN64) + + DWORD bufSize = 0; + GetLogicalProcessorInformationEx(RelationCache, nullptr, &bufSize); + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + return std::nullopt; + + std::vector buffer(bufSize); + auto info = reinterpret_cast(buffer.data()); + if (!GetLogicalProcessorInformationEx(RelationCache, info, &bufSize)) + return std::nullopt; + + while (reinterpret_cast(info) < buffer.data() + bufSize) + { + info = std::launder(info); + if (info->Relationship == RelationCache && info->Cache.Level == 3) + { + L3Domain domain{}; + for (WORD procGroup = 0; procGroup < info->Cache.GroupCount; ++procGroup) + { + for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) + { + WORD groupNumber = info->Cache.GroupMasks[procGroup].Group; + const CpuIndex c = + static_cast(groupNumber) * WIN_PROCESSOR_GROUP_SIZE + + static_cast(number); + if (!(info->Cache.GroupMasks[procGroup].Mask & (1ULL << number)) + || !is_cpu_allowed(c)) + continue; + domain.systemNumaIndex = systemConfig.nodeByCpu.at(c); + domain.cpus.insert(c); + } + } + if (!domain.cpus.empty()) + l3Domains.push_back(std::move(domain)); + } + // Variable length data structure, advance to next + info = reinterpret_cast( + reinterpret_cast(info) + info->Size); + } + +#endif + + if (!l3Domains.empty()) + return {NumaConfig::from_l3_info(std::move(l3Domains), bundleSize)}; + + return std::nullopt; + } + + + static NumaConfig from_l3_info(std::vector&& domains, size_t bundleSize) { + assert(!domains.empty()); + + std::map> list; + for (auto& d : domains) + list[d.systemNumaIndex].emplace_back(std::move(d)); + + NumaConfig cfg = empty(); + NumaIndex n = 0; + for (auto& [_, ds] : list) + { + bool changed; + // Scan through pairs and merge them. With roughly equal L3 sizes, should give + // a decent distribution. + do + { + changed = false; + for (size_t j = 0; j + 1 < ds.size(); ++j) + { + if (ds[j].cpus.size() + ds[j + 1].cpus.size() <= bundleSize) + { + changed = true; + ds[j].cpus.merge(ds[j + 1].cpus); + ds.erase(ds.begin() + j + 1); + } + } + // ds.size() has decreased if changed is true, so this loop will terminate + } while (changed); + for (const L3Domain& d : ds) + { + const NumaIndex dn = n++; + for (CpuIndex cpu : d.cpus) + { + cfg.add_cpu_to_node(dn, cpu); + } + } + } + return cfg; + } }; class NumaReplicationContext; @@ -1345,7 +1549,7 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase { std::size_t get_discriminator(NumaIndex idx) const { const NumaConfig& cfg = get_numa_config(); - const NumaConfig& cfg_sys = NumaConfig::from_system(false); + const NumaConfig& cfg_sys = NumaConfig::from_system(SystemNumaPolicy{}, false); // as a discriminator, locate the hardware/system numadomain this cpuindex belongs to CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; From 9b8c5c9f7550f3981d7902028e72e8d63cb47570 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 9 Jan 2026 22:25:30 +0300 Subject: [PATCH 1286/1309] Simplify fail high reduction formula Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 165792 W: 42849 L: 42768 D: 80175 Ptnml(0-2): 512, 19499, 42800, 19566, 519 https://tests.stockfishchess.org/tests/view/695cdd95912b7ff140de60c2 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 80448 W: 20619 L: 20459 D: 39370 Ptnml(0-2): 47, 8693, 22596, 8829, 59 https://tests.stockfishchess.org/tests/view/695f7e84ca95f52e4b852536 closes https://github.com/official-stockfish/Stockfish/pull/6535 bench: 2050811 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 42294ed0120..5385c1499e6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1206,8 +1206,7 @@ Value Search::Worker::search( // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 1) - r += 120 + 1024 * ((ss + 1)->cutoffCnt > 2) + 100 * ((ss + 1)->cutoffCnt > 3) - + 1024 * allNode; + r += 256 + 1024 * ((ss + 1)->cutoffCnt > 2) + 1024 * allNode; // For first picked move (ttMove) reduce reduction if (move == ttData.move) From d852a9195ea92ae7b5699077f358ca102f79fedc Mon Sep 17 00:00:00 2001 From: KazApps Date: Wed, 7 Jan 2026 19:45:50 +0900 Subject: [PATCH 1287/1309] Make enums unsigned Speed up by using unsigned enums. Passed STC: LLR: 2.98 (-2.94,2.94) <0.00,2.00> Total: 49248 W: 12894 L: 12568 D: 23786 Ptnml(0-2): 119, 5353, 13397, 5593, 16 https://tests.stockfishchess.org/tests/view/695e3e5002d0182a589fe965 closes https://github.com/official-stockfish/Stockfish/pull/6532 No functional change --- src/bitboard.cpp | 5 ++++- src/movegen.cpp | 8 ++++---- src/position.cpp | 17 ++++++++++++----- src/timeman.h | 2 +- src/types.h | 18 +++++++++--------- src/uci.h | 2 +- 6 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 4decb8d6666..0861222cf0e 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -49,12 +49,15 @@ std::string Bitboards::pretty(Bitboard b) { std::string s = "+---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) + for (Rank r = RANK_8;; --r) { for (File f = FILE_A; f <= FILE_H; ++f) s += b & make_square(f, r) ? "| X " : "| "; s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n"; + + if (r == RANK_1) + break; } s += " a b c d e f g h\n"; diff --git a/src/movegen.cpp b/src/movegen.cpp index d22faad2582..e63707a1ff4 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -47,10 +47,10 @@ template inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) { alignas(64) static constexpr auto SPLAT_TABLE = [] { std::array table{}; - for (int8_t i = 0; i < 64; i++) + for (int i = 0; i < 64; i++) { - Square from{std::clamp(i - offset, 0, 63)}; - table[i] = {Move(from, Square{i})}; + Square from{uint8_t(std::clamp(i - offset, 0, 63))}; + table[i] = {Move(from, Square{uint8_t(i)})}; } return table; }(); @@ -68,7 +68,7 @@ inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) { inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) { alignas(64) static constexpr auto SPLAT_TABLE = [] { std::array table{}; - for (int8_t i = 0; i < 64; i++) + for (uint8_t i = 0; i < 64; i++) table[i] = {Move(SQUARE_ZERO, Square{i})}; return table; }(); diff --git a/src/position.cpp b/src/position.cpp index d8b02e8ab9e..907dfde4076 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -66,12 +66,15 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { os << "\n +---+---+---+---+---+---+---+---+\n"; - for (Rank r = RANK_8; r >= RANK_1; --r) + for (Rank r = RANK_8;; --r) { for (File f = FILE_A; f <= FILE_H; ++f) os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n"; + + if (r == RANK_1) + break; } os << " a b c d e f g h\n" @@ -416,7 +419,7 @@ string Position::fen() const { int emptyCnt; std::ostringstream ss; - for (Rank r = RANK_8; r >= RANK_1; --r) + for (Rank r = RANK_8;; --r) { for (File f = FILE_A; f <= FILE_H; ++f) { @@ -430,8 +433,9 @@ string Position::fen() const { ss << PieceToChar[piece_on(make_square(f, r))]; } - if (r > RANK_1) - ss << '/'; + if (r == RANK_1) + break; + ss << '/'; } ss << (sideToMove == WHITE ? " w " : " b "); @@ -1477,10 +1481,13 @@ void Position::flip() { string f, token; std::stringstream ss(fen()); - for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement + for (Rank r = RANK_8;; --r) // Piece placement { std::getline(ss, token, r > RANK_1 ? '/' : ' '); f.insert(0, token + (f.empty() ? " " : "/")); + + if (r == RANK_1) + break; } ss >> token; // Active color diff --git a/src/timeman.h b/src/timeman.h index e72cc102aa5..08e8da10dcc 100644 --- a/src/timeman.h +++ b/src/timeman.h @@ -26,7 +26,7 @@ namespace Stockfish { class OptionsMap; -enum Color : int8_t; +enum Color : uint8_t; namespace Search { struct LimitsType; diff --git a/src/types.h b/src/types.h index b5a0498a80b..10a0d1ac519 100644 --- a/src/types.h +++ b/src/types.h @@ -117,13 +117,13 @@ using Bitboard = uint64_t; constexpr int MAX_MOVES = 256; constexpr int MAX_PLY = 246; -enum Color : int8_t { +enum Color : uint8_t { WHITE, BLACK, COLOR_NB = 2 }; -enum CastlingRights : int8_t { +enum CastlingRights : uint8_t { NO_CASTLING, WHITE_OO, WHITE_OOO = WHITE_OO << 1, @@ -139,7 +139,7 @@ enum CastlingRights : int8_t { CASTLING_RIGHT_NB = 16 }; -enum Bound : int8_t { +enum Bound : uint8_t { BOUND_NONE, BOUND_UPPER, BOUND_LOWER, @@ -190,13 +190,13 @@ constexpr Value QueenValue = 2538; // clang-format off -enum PieceType : std::int8_t { +enum PieceType : std::uint8_t { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, ALL_PIECES = 0, PIECE_TYPE_NB = 8 }; -enum Piece : std::int8_t { +enum Piece : std::uint8_t { NO_PIECE, W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, @@ -227,7 +227,7 @@ constexpr Depth DEPTH_UNSEARCHED = -2; constexpr Depth DEPTH_ENTRY_OFFSET = -3; // clang-format off -enum Square : int8_t { +enum Square : uint8_t { SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, @@ -255,7 +255,7 @@ enum Direction : int8_t { NORTH_WEST = NORTH + WEST }; -enum File : int8_t { +enum File : uint8_t { FILE_A, FILE_B, FILE_C, @@ -267,7 +267,7 @@ enum File : int8_t { FILE_NB }; -enum Rank : int8_t { +enum Rank : uint8_t { RANK_1, RANK_2, RANK_3, @@ -406,7 +406,7 @@ constexpr Key make_key(uint64_t seed) { } -enum MoveType { +enum MoveType : uint16_t { NORMAL, PROMOTION = 1 << 14, EN_PASSANT = 2 << 14, diff --git a/src/uci.h b/src/uci.h index c9b5943935c..ebc04fc3c70 100644 --- a/src/uci.h +++ b/src/uci.h @@ -33,7 +33,7 @@ namespace Stockfish { class Position; class Move; class Score; -enum Square : int8_t; +enum Square : uint8_t; using Value = int; class UCIEngine { From e9b2864579362a86e9d71f652127a49a0f1872d0 Mon Sep 17 00:00:00 2001 From: KazApps Date: Fri, 2 Jan 2026 14:02:11 +0900 Subject: [PATCH 1288/1309] Simplify make_index Refactor index LUT construction to simplify make_index. Passed STC Non-Regression: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 62432 W: 16193 L: 16006 D: 30233 Ptnml(0-2): 189, 6950, 16764, 7111, 202 https://tests.stockfishchess.org/tests/view/6959985ad844c1ce7cc7eac8 closes https://github.com/official-stockfish/Stockfish/pull/6522 No functional change. --- src/nnue/features/full_threats.cpp | 44 ++++++------------------------ 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 4e3ba81cf85..47a5f8f0d80 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -37,26 +37,6 @@ struct HelperOffsets { int cumulativePieceOffset, cumulativeOffset; }; -// Information on a particular pair of pieces and whether they should be excluded -struct PiecePairData { - // Layout: bits 8..31 are the index contribution of this piece pair, bits 0 and 1 are exclusion info - uint32_t data; - - constexpr PiecePairData() : - data(0) {} - - constexpr PiecePairData(bool excluded_pair, - bool semi_excluded_pair, - IndexType feature_index_base) : - data((uint32_t(excluded_pair) << 1) | (uint32_t(semi_excluded_pair && !excluded_pair)) - | (uint32_t(feature_index_base) << 8)) {} - - // lsb: excluded if from < to; 2nd lsb: always excluded - constexpr uint8_t excluded_pair_info() const { return static_cast(data); } - - constexpr IndexType feature_index_base() const { return static_cast(data >> 8); } -}; - constexpr std::array AllPieces = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, @@ -173,7 +153,7 @@ constexpr auto helper_offsets = init_threat_offsets().first; constexpr auto offsets = init_threat_offsets().second; constexpr auto init_index_luts() { - std::array, PIECE_NB> indices{}; + std::array, PIECE_NB>, PIECE_NB> indices{}; for (Piece attacker : AllPieces) { @@ -189,8 +169,10 @@ constexpr auto init_index_luts() { + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) * helper_offsets[attacker].cumulativePieceOffset; - bool excluded = map < 0; - indices[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); + bool excluded = map < 0; + indices[attacker][attacked][0] = excluded ? FullThreats::Dimensions : feature; + indices[attacker][attacked][1] = + excluded || semi_excluded ? FullThreats::Dimensions : feature; } } @@ -200,7 +182,7 @@ constexpr auto init_index_luts() { // The final index is calculated from summing data found in these two LUTs, as well // as offsets[attacker][from] -// [attacker][attacked] +// [attacker][attacked][from < to] constexpr auto index_lut1 = init_index_luts(); // [attacker][from][to] constexpr auto index_lut2 = index_lut2_array(); @@ -216,17 +198,9 @@ inline sf_always_inline IndexType FullThreats::make_index( unsigned attacker_oriented = attacker ^ swap; unsigned attacked_oriented = attacked ^ swap; - const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented]; - - const bool less_than = from_oriented < to_oriented; - if ((piecePairData.excluded_pair_info() + less_than) & 2) - return FullThreats::Dimensions; - - const IndexType index = piecePairData.feature_index_base() - + offsets[attacker_oriented][from_oriented] - + index_lut2[attacker_oriented][from_oriented][to_oriented]; - sf_assume(index < Dimensions); - return index; + return index_lut1[attacker_oriented][attacked_oriented][from_oriented < to_oriented] + + offsets[attacker_oriented][from_oriented] + + index_lut2[attacker_oriented][from_oriented][to_oriented]; } // Get a list of indices for active features in ascending order From eb5a65aeb08d787d595044299a41b0226d73b569 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sat, 10 Jan 2026 10:18:48 -0800 Subject: [PATCH 1289/1309] Fix RelationCache on Windows 10 compiles Windows 10 is missing the GroupMasks and GroupCount members, this breaks compiles on Windows 10. Windows 11 builds, including the official ones, run fine on Windows 10/11. To support developers/testers on Windows 10, fallback conditionally to the Windows 10 struct definition. closes https://github.com/official-stockfish/Stockfish/pull/6538 No functional change --- src/numa.h | 64 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/src/numa.h b/src/numa.h index df11f02864f..b3d1c34d92b 100644 --- a/src/numa.h +++ b/src/numa.h @@ -371,6 +371,50 @@ inline WindowsAffinity get_process_affinity() { return affinity; } +// Type machinery used to emulate Cache->GroupCount + +template +struct HasGroupCount: std::false_type {}; + +template +struct HasGroupCount().Cache.GroupCount)>>: std::true_type { +}; + +template::value, bool> = true> +std::set readCacheMembers(const T* info, Pred&& is_cpu_allowed) { + std::set cpus; + // On Windows 10 this will read a 0 because GroupCount doesn't exist + int groupCount = std::max(info->Cache.GroupCount, WORD(1)); + for (WORD procGroup = 0; procGroup < groupCount; ++procGroup) + { + for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) + { + WORD groupNumber = info->Cache.GroupMasks[procGroup].Group; + const CpuIndex c = static_cast(groupNumber) * WIN_PROCESSOR_GROUP_SIZE + + static_cast(number); + if (!(info->Cache.GroupMasks[procGroup].Mask & (1ULL << number)) || !is_cpu_allowed(c)) + continue; + cpus.insert(c); + } + } + return cpus; +} + +template::value, bool> = true> +std::set readCacheMembers(const T* info, Pred&& is_cpu_allowed) { + std::set cpus; + for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) + { + WORD groupNumber = info->Cache.GroupMask.Group; + const CpuIndex c = static_cast(groupNumber) * WIN_PROCESSOR_GROUP_SIZE + + static_cast(number); + if (!(info->Cache.GroupMask.Mask & (1ULL << number)) || !is_cpu_allowed(c)) + continue; + cpus.insert(c); + } + return cpus; +} + #endif #if defined(__linux__) && !defined(__ANDROID__) @@ -1174,29 +1218,17 @@ class NumaConfig { if (info->Relationship == RelationCache && info->Cache.Level == 3) { L3Domain domain{}; - for (WORD procGroup = 0; procGroup < info->Cache.GroupCount; ++procGroup) - { - for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number) - { - WORD groupNumber = info->Cache.GroupMasks[procGroup].Group; - const CpuIndex c = - static_cast(groupNumber) * WIN_PROCESSOR_GROUP_SIZE - + static_cast(number); - if (!(info->Cache.GroupMasks[procGroup].Mask & (1ULL << number)) - || !is_cpu_allowed(c)) - continue; - domain.systemNumaIndex = systemConfig.nodeByCpu.at(c); - domain.cpus.insert(c); - } - } + domain.cpus = readCacheMembers(info, is_cpu_allowed); if (!domain.cpus.empty()) + { + domain.systemNumaIndex = systemConfig.nodeByCpu.at(*domain.cpus.begin()); l3Domains.push_back(std::move(domain)); + } } // Variable length data structure, advance to next info = reinterpret_cast( reinterpret_cast(info) + info->Size); } - #endif if (!l3Domains.empty()) From 0b9068d5105faa5ece9db713d2e50717b5682787 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 11 Jan 2026 21:05:36 +0100 Subject: [PATCH 1290/1309] Fix integer overflow. scaledBonus can reach rather large values, which lead to an int overflow as detected anematode using ubsan. (see https://github.com/official-stockfish/Stockfish/issues/6505#issuecomment-3696988889) It can be fixed by scaling nominator and denominator appropriately, which doesn't change the bench, as long as there is no overflow. First overflow/bench change happens at depth 26 closes https://github.com/official-stockfish/Stockfish/pull/6540 Bench: 2050811 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 5385c1499e6..40ba2effadf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1432,6 +1432,7 @@ Value Search::Worker::search( bonusScale = std::max(bonusScale, 0); + // scaledBonus ranges from 0 to roughly 2.3M, overflows happen for multipliers larger than 900 const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, @@ -1440,8 +1441,7 @@ Value Search::Worker::search( mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1160 / 32768; + sharedHistory.pawn_entry(pos)[pos.piece_on(prevSq)][prevSq] << scaledBonus * 290 / 8192; } // Bonus for prior capture countermove that caused the fail low From 5b8b304ebddf52e0595d140d86008c5b10b86b90 Mon Sep 17 00:00:00 2001 From: anematode Date: Sun, 11 Jan 2026 19:34:10 -0800 Subject: [PATCH 1291/1309] Skip munmap when exiting via a signal avoid munmap of memory when exiting via signal, which avoids side effects such as triggering asserts or (caught) segfaults while the process exists. closes https://github.com/official-stockfish/Stockfish/pull/6542 No functional change --- src/shm_linux.h | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/shm_linux.h b/src/shm_linux.h index 1e344e93fd8..6b3182ae062 100644 --- a/src/shm_linux.h +++ b/src/shm_linux.h @@ -67,7 +67,7 @@ struct ShmHeader { class SharedMemoryBase { public: virtual ~SharedMemoryBase() = default; - virtual void close() noexcept = 0; + virtual void close(bool skip_unmap = false) noexcept = 0; virtual const std::string& name() const noexcept = 0; }; @@ -87,10 +87,10 @@ class SharedMemoryRegistry { active_instances_.erase(instance); } - static void cleanup_all() noexcept { + static void cleanup_all(bool skip_unmap = false) noexcept { std::scoped_lock lock(registry_mutex_); for (auto* instance : active_instances_) - instance->close(); + instance->close(skip_unmap); active_instances_.clear(); } }; @@ -103,12 +103,14 @@ class CleanupHooks { static std::once_flag register_once_; static void handle_signal(int sig) noexcept { - SharedMemoryRegistry::cleanup_all(); + // Search threads may still be running, so skip munmap (but still perform + // other cleanup actions). The memory mappings will be released on exit. + SharedMemoryRegistry::cleanup_all(true); _Exit(128 + sig); } static void register_signal_handlers() noexcept { - std::atexit([]() { SharedMemoryRegistry::cleanup_all(); }); + std::atexit([]() { SharedMemoryRegistry::cleanup_all(true); }); constexpr int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGSEGV, SIGTERM, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ}; @@ -318,7 +320,7 @@ class SharedMemory: public detail::SharedMemoryBase { } } - void close() noexcept override { + void close(bool skip_unmap = false) noexcept override { if (fd_ == -1 && mapped_ptr_ == nullptr) return; @@ -345,7 +347,10 @@ class SharedMemory: public detail::SharedMemoryBase { decrement_refcount_relaxed(); } - unmap_region(); + if (skip_unmap) + mapped_ptr_ = nullptr; + else + unmap_region(); if (remove_region) shm_unlink(name_.c_str()); @@ -359,7 +364,8 @@ class SharedMemory: public detail::SharedMemoryBase { fd_ = -1; } - reset(); + if (!skip_unmap) + reset(); } const std::string& name() const noexcept override { return name_; } From 71f53b92c7095f1f145b85d63b8ad49f9ef553f9 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Thu, 15 Jan 2026 09:01:18 +0100 Subject: [PATCH 1292/1309] update the WDL model This PR updates the internal WDL model, using data from 3.1M games played by the revisions since 44d5467. Note that the normalizing constant increases only moderately from 377 to 385. closes https://github.com/official-stockfish/Stockfish/pull/6549 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 139d97b609e..927007686dc 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -507,8 +507,8 @@ WinRateParams win_rate_params(const Position& pos) { double m = std::clamp(material, 17, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; - constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; + constexpr double as[] = {-72.32565836, 185.93832038, -144.58862193, 416.44950446}; + constexpr double bs[] = {83.86794042, -136.06112997, 69.98820887, 47.62901433}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From f61d4317a325db1e1489bcd257f94ef605db0244 Mon Sep 17 00:00:00 2001 From: anematode Date: Fri, 16 Jan 2026 23:50:40 -0800 Subject: [PATCH 1293/1309] use default signal handler after cleanup With the current setup, on Linux, SIGILL (and SIGSEGV/SIGBUS etc., in the case of a program bug) lead to no feedback if they occur after the signal handlers are installed, they just exit silently. By invoking the default signal handler, we can still get the appropriate feedback. This is particularly important for feedback if someone downloads the wrong SF binary and runs it on Linux. closes https://github.com/official-stockfish/Stockfish/pull/6554 No functional change --- src/shm_linux.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/shm_linux.h b/src/shm_linux.h index 6b3182ae062..1b7ac2c7711 100644 --- a/src/shm_linux.h +++ b/src/shm_linux.h @@ -106,7 +106,16 @@ class CleanupHooks { // Search threads may still be running, so skip munmap (but still perform // other cleanup actions). The memory mappings will be released on exit. SharedMemoryRegistry::cleanup_all(true); - _Exit(128 + sig); + + // Invoke the default handler, which will exit + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(sig, &sa, nullptr) == -1) + _Exit(128 + sig); + + raise(sig); } static void register_signal_handlers() noexcept { From c0f245303b1a666a58e4973ed794bc048aadc55b Mon Sep 17 00:00:00 2001 From: anematode Date: Wed, 21 Jan 2026 21:25:18 -0800 Subject: [PATCH 1294/1309] Make PGO builds deterministic again some environment dependent execution (e.g. pid) were being std::hash'ed, and net filenames put in unordered maps. Also uses sprintf instead of std::to_string. Depending on precise content, this could lead to different PGO'ed binaries. This is mitigated by using a basic hash function. This also fixes a potential issue in net filename generation, in cases where std::hash would use invocation dependent salt, which is not the cases today for typical std libraries. Closes https://github.com/official-stockfish/Stockfish/pull/6562 No functional change --- src/misc.h | 37 ++++++++++++++++++++++++++----------- src/numa.h | 2 +- src/shm.h | 18 ++++++++---------- src/shm_linux.h | 25 +++++++++++++------------ 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/src/misc.h b/src/misc.h index f1016b496de..8adc7240947 100644 --- a/src/misc.h +++ b/src/misc.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #define stringify2(x) #x @@ -305,24 +306,38 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #endif } +inline std::uint64_t hash_bytes(const char* data, std::size_t size) { + // FNV-1a 64-bit + const char* p = data; + std::uint64_t h = 14695981039346656037ull; + for (std::size_t i = 0; i < size; ++i) + h = (h ^ p[i]) * 1099511628211ull; + return h; +} template -inline void hash_combine(std::size_t& seed, const T& v) { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); -} +inline std::size_t get_raw_data_hash(const T& value) { + // We must have no padding bytes because we're reinterpreting as char + static_assert(std::has_unique_object_representations()); -template<> -inline void hash_combine(std::size_t& seed, const std::size_t& v) { - seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2); + return static_cast( + hash_bytes(reinterpret_cast(&value), sizeof(value))); } template -inline std::size_t get_raw_data_hash(const T& value) { - return std::hash{}( - std::string_view(reinterpret_cast(&value), sizeof(value))); +inline void hash_combine(std::size_t& seed, const T& v) { + std::size_t x; + // For primitive types we avoid using the default hasher, which may be + // nondeterministic across program invocations + if constexpr (std::is_integral()) + x = v; + else + x = std::hash{}(v); + seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2); } +inline std::uint64_t hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); } + template class FixedString { public: @@ -450,7 +465,7 @@ void move_to_front(std::vector& vec, Predicate pred) { template struct std::hash> { std::size_t operator()(const Stockfish::FixedString& fstr) const noexcept { - return std::hash{}((std::string_view) fstr); + return Stockfish::hash_bytes(fstr.data(), fstr.size()); } }; diff --git a/src/numa.h b/src/numa.h index b3d1c34d92b..afd868dd085 100644 --- a/src/numa.h +++ b/src/numa.h @@ -1586,7 +1586,7 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase { CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx); - return std::hash{}(s); + return static_cast(hash_string(s)); } void ensure_present(NumaIndex idx) const { diff --git a/src/shm.h b/src/shm.h index 9bf13f23497..42ed5f13c5a 100644 --- a/src/shm.h +++ b/src/shm.h @@ -20,6 +20,7 @@ #define SHM_H_INCLUDED #include +#include #include #include #include @@ -512,12 +513,9 @@ template struct SystemWideSharedConstant { private: static std::string createHashString(const std::string& input) { - size_t hash = std::hash{}(input); - - std::stringstream ss; - ss << std::hex << std::setfill('0') << hash; - - return ss.str(); + char buf[1024]; + std::snprintf(buf, sizeof(buf), "%016" PRIx64, hash_string(input)); + return buf; } public: @@ -534,11 +532,11 @@ struct SystemWideSharedConstant { // that are not present in the content, for example NUMA node allocation. SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) { std::size_t content_hash = std::hash{}(value); - std::size_t executable_hash = std::hash{}(getExecutablePathHash()); + std::size_t executable_hash = hash_string(getExecutablePathHash()); - std::string shm_name = std::string("Local\\sf_") + std::to_string(content_hash) + "$" - + std::to_string(executable_hash) + "$" - + std::to_string(discriminator); + char buf[1024]; + std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator); + std::string shm_name = buf; #if !defined(_WIN32) // POSIX shared memory names must start with a slash diff --git a/src/shm_linux.h b/src/shm_linux.h index 1b7ac2c7711..22a62b823f8 100644 --- a/src/shm_linux.h +++ b/src/shm_linux.h @@ -33,7 +33,6 @@ #include #include #include -#include #include #include @@ -51,6 +50,7 @@ #define SF_MAX_SEM_NAME_LEN 255 #endif +#include "misc.h" namespace Stockfish::shm { @@ -74,17 +74,18 @@ class SharedMemoryBase { class SharedMemoryRegistry { private: static std::mutex registry_mutex_; - static std::unordered_set active_instances_; + static std::vector active_instances_; public: static void register_instance(SharedMemoryBase* instance) { std::scoped_lock lock(registry_mutex_); - active_instances_.insert(instance); + active_instances_.push_back(instance); } static void unregister_instance(SharedMemoryBase* instance) { std::scoped_lock lock(registry_mutex_); - active_instances_.erase(instance); + active_instances_.erase( + std::remove(active_instances_.begin(), active_instances_.end(), instance), active_instances_.end()); } static void cleanup_all(bool skip_unmap = false) noexcept { @@ -96,7 +97,7 @@ class SharedMemoryRegistry { }; inline std::mutex SharedMemoryRegistry::registry_mutex_; -inline std::unordered_set SharedMemoryRegistry::active_instances_; +inline std::vector SharedMemoryRegistry::active_instances_; class CleanupHooks { private: @@ -181,9 +182,10 @@ class SharedMemory: public detail::SharedMemoryBase { } static std::string make_sentinel_base(const std::string& name) { - uint64_t hash = std::hash{}(name); char buf[32]; - std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIx64, static_cast(hash)); + // Using std::to_string here causes non-deterministic PGO builds. + // snprintf, being part of libc, is insensitive to the formatted values. + std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIu64, hash_string(name)); return buf; } @@ -442,11 +444,10 @@ class SharedMemory: public detail::SharedMemoryBase { } std::string sentinel_full_path(pid_t pid) const { - std::string path = "/dev/shm/"; - path += sentinel_base_; - path.push_back('.'); - path += std::to_string(pid); - return path; + char buf[1024]; + // See above snprintf comment + std::snprintf(buf, sizeof(buf), "/dev/shm/%s.%ld", sentinel_base_.c_str(), long(pid)); + return buf; } void decrement_refcount_relaxed() noexcept { From 253aaefbc03fab2e0c2a0ed83c0abf0ee9be4f92 Mon Sep 17 00:00:00 2001 From: anematode Date: Wed, 28 Jan 2026 20:28:10 +0100 Subject: [PATCH 1295/1309] Fix compilation on BSD/macOS fixes github.com/official-stockfish/Stockfish/issues/6571 closes https://github.com/official-stockfish/Stockfish/pull/6572 No functional change --- src/shm.h | 8 ++++---- src/shm_linux.h | 15 ++++++--------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/shm.h b/src/shm.h index 42ed5f13c5a..e1256b26aa8 100644 --- a/src/shm.h +++ b/src/shm.h @@ -36,7 +36,7 @@ #include #include -#if !defined(_WIN32) && !defined(__ANDROID__) +#if defined(__linux__) && !defined(__ANDROID__) #include "shm_linux.h" #endif @@ -60,7 +60,7 @@ #define NOMINMAX #endif #include -#else +#elif defined(__linux__) #include #include #include @@ -407,7 +407,7 @@ class SharedMemoryBackend { std::string last_error_message; }; -#elif !defined(__ANDROID__) +#elif defined(__linux__) && !defined(__ANDROID__) template class SharedMemoryBackend { @@ -538,7 +538,7 @@ struct SystemWideSharedConstant { std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator); std::string shm_name = buf; -#if !defined(_WIN32) +#if defined(__linux__) && !defined(__ANDROID__) // POSIX shared memory names must start with a slash shm_name = "/sf_" + createHashString(shm_name); diff --git a/src/shm_linux.h b/src/shm_linux.h index 22a62b823f8..296ab678f0d 100644 --- a/src/shm_linux.h +++ b/src/shm_linux.h @@ -19,6 +19,10 @@ #ifndef SHM_LINUX_H_INCLUDED #define SHM_LINUX_H_INCLUDED +#if !defined(__linux__) || defined(__ANDROID__) +#error shm_linux.h should not be included on this platform. +#endif + #include #include #include @@ -40,15 +44,8 @@ #include #include #include - -#if defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__) - #include - #define SF_MAX_SEM_NAME_LEN NAME_MAX -#elif defined(__APPLE__) - #define SF_MAX_SEM_NAME_LEN 31 -#else - #define SF_MAX_SEM_NAME_LEN 255 -#endif +#include +#define SF_MAX_SEM_NAME_LEN NAME_MAX #include "misc.h" From cb3d4ee9b47d0c5aae855b12379378ea1439675c Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 16 Jan 2026 21:14:09 +0100 Subject: [PATCH 1296/1309] Stockfish 18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 18 Bench: 2050811 --- Stockfish 18 Today, we have the pleasure of announcing Stockfish 18, a new major release. As always, you can freely download it at [stockfishchess.org/download][1] and use it as a drop-in replacement in the [GUI of your choice][2] to benefit from stronger play and more accurate analysis. Whether you can spare hours or days of CPU time, your help matters for the ongoing development of Stockfish. Find out how you can help at [stockfishchess.org/get-involved][3]. Join our [Discord server][4] to get in touch with the community of developers and users of the project! Quality of chess play In tests against Stockfish 17, this new release brings an Elo gain of up to [46 points][5], and wins [four times as many game pairs][6] as it loses. Quality improved throughout, including in [Fischer Random Chess][7]. Stockfish is stronger than any human, even when running on older or low-end hardware. On the highest-end hardware, where Stockfish can search over [500 million positions per second][8], it continues to [dominate chess engine tournaments][9]. Update highlights Next-Generation Evaluation This release introduces the SFNNv10 network architecture. The network’s input layer has been augmented with 'Threat Inputs' features as part of a massive community effort. These features allow the engine to 'see' which pieces are threatened more naturally, resulting in more accurate evaluations. Hardware and Performance Optimizations A key highlight is the new 'Shared Memory' implementation, which allows different Stockfish processes to share the same memory space for neural network weights. This makes it the most efficient version for cloud analysis and high-concurrency testing. Significant efforts have also been made to utilize hardware more effectively by adapting the code to make use of modern CPU instructions and refining how threads interact during a search. Search Improvements Stockfish 18 features a heavily refined search, utilizing 'Correction History' to dynamically adjust evaluations based on patterns found during the search itself. These and other refinements allow the engine to detect stalemates and evaluate fortresses significantly better than previous versions. A particularly rare issue, involving threefold repetition detection, en passant, and pins, was also fixed. Refactored Training Workflow The training of Stockfish's neural networks has transitioned to an automated and reproducible model. This new framework allows the project to employ standardized recipes to chain complex training stages together. This transition facilitates the training of networks using over 100 billion positions of [Lc0 evaluation data][10]. Thank you In this release in particular, we are deeply grateful to the contributors who shared their research and ideas to help develop the new threat-input network architecture. The Stockfish project builds on a thriving community of enthusiasts (thanks to everybody!) who contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We would like to express our gratitude for the [14.6k stars][11] that light up our GitHub project. Thank you for your support and encouragement – your recognition means a lot to us. Programmers can contribute to the project either directly to [Stockfish][12] (C++), to [Fishtest][13] (HTML, CSS, JavaScript, and Python), to our trainer [nnue-pytorch][14] (C++ and Python), or to our [website][15] (HTML, CSS/SCSS, and JavaScript). The Stockfish team [1]: https://stockfishchess.org/download [2]: https://official-stockfish.github.io/docs/stockfish-wiki/Download-and-usage.html#download-a-chess-gui [3]: https://stockfishchess.org/get-involved/ [4]: https://discord.gg/GWDRS3kU6R [5]: https://tests.stockfishchess.org/tests/view/696a9e15cec152c6220c1d19 [6]: https://tests.stockfishchess.org/tests/view/696a9e4dcec152c6220c1d1b [7]: https://tests.stockfishchess.org/tests/view/696a9e83cec152c6220c1d1d [8]: https://openbenchmarking.org/test/pts/stockfish [9]: https://en.wikipedia.org/wiki/Stockfish_(chess)#Competition_results [10]: https://lczero.org/blog/2021/06/the-importance-of-open-data/ [11]: https://github.com/official-stockfish/Stockfish/stargazers [12]: https://github.com/official-stockfish/Stockfish [13]: https://github.com/official-stockfish/fishtest [14]: https://github.com/official-stockfish/nnue-pytorch [15]: https://github.com/official-stockfish/stockfish-web --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 3ddae503ccb..5f8f31a9fbf 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "dev"; +constexpr std::string_view version = "18"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 823b04e58a8429a576db94f6061aae0b49907401 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 31 Jan 2026 20:02:15 +0100 Subject: [PATCH 1297/1309] Restore development closes https://github.com/official-stockfish/Stockfish/pull/6581 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 5f8f31a9fbf..3ddae503ccb 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "18"; +constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 0d26c61ff2ce27d7af7cdf4c805bde1a032ae435 Mon Sep 17 00:00:00 2001 From: anematode Date: Mon, 2 Feb 2026 01:45:58 -0800 Subject: [PATCH 1298/1309] fix return type of vmovl_high_s8 also add -flax-vector-conversions=none to the build closes https://github.com/official-stockfish/Stockfish/pull/6587 No functional change --- src/Makefile | 2 +- src/nnue/simd.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index dcd3f1fea09..3f4f8be9779 100644 --- a/src/Makefile +++ b/src/Makefile @@ -515,7 +515,7 @@ ifeq ($(COMP),clang) endif CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \ - -Wconditional-uninitialized + -Wconditional-uninitialized -flax-vector-conversions=none ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),) ifeq ($(target_windows),) diff --git a/src/nnue/simd.h b/src/nnue/simd.h index 25891163ea1..9c689b09892 100644 --- a/src/nnue/simd.h +++ b/src/nnue/simd.h @@ -216,7 +216,7 @@ static constexpr std::uint32_t Mask[4] = {1, 2, 4, 8}; #ifndef __aarch64__ // Single instruction doesn't exist on 32-bit ARM -inline int8x16_t vmovl_high_s8(int8x16_t val) { return vmovl_s8(vget_high_s8(val)); } +inline int16x8_t vmovl_high_s8(int8x16_t val) { return vmovl_s8(vget_high_s8(val)); } #endif #else From 1018879fe1b34e44ea31de4eb99aaec2354c7289 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 3 Feb 2026 22:45:47 +0100 Subject: [PATCH 1299/1309] Fix clang-format complaints Somehow we forgot to add shm.h & shm_linux.h to the list of headers to be formatted. closes https://github.com/official-stockfish/Stockfish/pull/6591 No functional change --- src/Makefile | 2 +- src/shm.h | 3 ++- src/shm_linux.h | 17 +++++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Makefile b/src/Makefile index 3f4f8be9779..ff70a73c927 100644 --- a/src/Makefile +++ b/src/Makefile @@ -80,7 +80,7 @@ HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history. nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \ nnue/nnue_architecture.h nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h \ position.h search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h shm.h shm_linux.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/shm.h b/src/shm.h index e1256b26aa8..d581bf08ac7 100644 --- a/src/shm.h +++ b/src/shm.h @@ -535,7 +535,8 @@ struct SystemWideSharedConstant { std::size_t executable_hash = hash_string(getExecutablePathHash()); char buf[1024]; - std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator); + std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, + discriminator); std::string shm_name = buf; #if defined(__linux__) && !defined(__ANDROID__) diff --git a/src/shm_linux.h b/src/shm_linux.h index 296ab678f0d..29c9e90f5aa 100644 --- a/src/shm_linux.h +++ b/src/shm_linux.h @@ -20,7 +20,7 @@ #define SHM_LINUX_H_INCLUDED #if !defined(__linux__) || defined(__ANDROID__) -#error shm_linux.h should not be included on this platform. + #error shm_linux.h should not be included on this platform. #endif #include @@ -63,14 +63,14 @@ struct ShmHeader { class SharedMemoryBase { public: - virtual ~SharedMemoryBase() = default; - virtual void close(bool skip_unmap = false) noexcept = 0; - virtual const std::string& name() const noexcept = 0; + virtual ~SharedMemoryBase() = default; + virtual void close(bool skip_unmap = false) noexcept = 0; + virtual const std::string& name() const noexcept = 0; }; class SharedMemoryRegistry { private: - static std::mutex registry_mutex_; + static std::mutex registry_mutex_; static std::vector active_instances_; public: @@ -82,7 +82,8 @@ class SharedMemoryRegistry { static void unregister_instance(SharedMemoryBase* instance) { std::scoped_lock lock(registry_mutex_); active_instances_.erase( - std::remove(active_instances_.begin(), active_instances_.end(), instance), active_instances_.end()); + std::remove(active_instances_.begin(), active_instances_.end(), instance), + active_instances_.end()); } static void cleanup_all(bool skip_unmap = false) noexcept { @@ -93,7 +94,7 @@ class SharedMemoryRegistry { } }; -inline std::mutex SharedMemoryRegistry::registry_mutex_; +inline std::mutex SharedMemoryRegistry::registry_mutex_; inline std::vector SharedMemoryRegistry::active_instances_; class CleanupHooks { @@ -179,7 +180,7 @@ class SharedMemory: public detail::SharedMemoryBase { } static std::string make_sentinel_base(const std::string& name) { - char buf[32]; + char buf[32]; // Using std::to_string here causes non-deterministic PGO builds. // snprintf, being part of libc, is insensitive to the formatted values. std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIu64, hash_string(name)); From 7f85cfbfa237efc2c08308061c31325783eb7ff5 Mon Sep 17 00:00:00 2001 From: Dieter Dobbelaere Date: Sun, 1 Feb 2026 19:48:25 +0100 Subject: [PATCH 1300/1309] Include threat weights in network hash calculation closes https://github.com/official-stockfish/Stockfish/pull/6583 No functional change --- src/nnue/nnue_feature_transformer.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index f4cf348ab73..5ee5eaac833 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -218,10 +218,19 @@ class FeatureTransformer { std::size_t get_content_hash() const { std::size_t h = 0; + hash_combine(h, get_raw_data_hash(biases)); hash_combine(h, get_raw_data_hash(weights)); hash_combine(h, get_raw_data_hash(psqtWeights)); + + if constexpr (UseThreats) + { + hash_combine(h, get_raw_data_hash(threatWeights)); + hash_combine(h, get_raw_data_hash(threatPsqtWeights)); + } + hash_combine(h, get_hash_value()); + return h; } From 542c30c292d1623b6b4cd710b2ed9ca621bfd589 Mon Sep 17 00:00:00 2001 From: Maxim Masiutin Date: Mon, 26 Jan 2026 17:49:48 +0200 Subject: [PATCH 1301/1309] Branchless correction history with to_sq_unchecked Add Move::to_sq_unchecked() that bypasses the is_ok() assertion, for use in branchless code paths where invalid moves are masked out. passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 92384 W: 24052 L: 23665 D: 44667 Ptnml(0-2): 265, 10229, 24831, 10588, 279 https://tests.stockfishchess.org/tests/view/6974dfc798dc5fff1dba5b74 closes https://github.com/official-stockfish/Stockfish/pull/6569 No functional change --- src/search.cpp | 15 ++++++++------- src/types.h | 4 ++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 40ba2effadf..44cd97c91d8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -114,13 +114,14 @@ void update_correction_history(const Position& pos, shared.nonpawn_correction_entry(pos).at(us).nonPawnWhite << bonus * nonPawnWeight / 128; shared.nonpawn_correction_entry(pos).at(us).nonPawnBlack << bonus * nonPawnWeight / 128; - if (m.is_ok()) - { - const Square to = m.to_sq(); - const Piece pc = pos.piece_on(m.to_sq()); - (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 127 / 128; - (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 59 / 128; - } + // Branchless: use mask to zero bonus when move is not ok + const int mask = int(m.is_ok()); + const Square to = m.to_sq_unchecked(); + const Piece pc = pos.piece_on(to); + const int bonus2 = (bonus * 127 / 128) * mask; + const int bonus4 = (bonus * 59 / 128) * mask; + (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus2; + (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus4; } // Add a small random component to draw evaluations to avoid 3-fold blindness diff --git a/src/types.h b/src/types.h index 10a0d1ac519..bfaa658e9c9 100644 --- a/src/types.h +++ b/src/types.h @@ -449,6 +449,10 @@ class Move { return Square(data & 0x3F); } + // Same as to_sq() but without assertion, for branchless code paths + // where the result is masked/ignored when move is not ok + constexpr Square to_sq_unchecked() const { return Square(data & 0x3F); } + constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } From 9f968446cbbe7630b3044526359baef4f67fa62d Mon Sep 17 00:00:00 2001 From: disservin Date: Wed, 28 Jan 2026 09:41:21 +0100 Subject: [PATCH 1302/1309] Improve usage of sf_assume Make the sf_assume also have an effect with clang and help the compiler proof it's side effect free by comparing against a variable. https://tests.stockfishchess.org/tests/view/6979d14fa2c75f923be1e681 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 63488 W: 16530 L: 16188 D: 30770 Ptnml(0-2): 167, 6904, 17277, 7212, 184 closes https://github.com/official-stockfish/Stockfish/pull/6573 No functional change --- src/misc.h | 10 ++++++- src/nnue/nnue_accumulator.cpp | 50 +++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/misc.h b/src/misc.h index 8adc7240947..1471b1b6a79 100644 --- a/src/misc.h +++ b/src/misc.h @@ -444,7 +444,9 @@ void move_to_front(std::vector& vec, Predicate pred) { #define sf_always_inline #endif -#if defined(__GNUC__) && !defined(__clang__) +#if defined(__clang__) + #define sf_assume(cond) __builtin_assume(cond) +#elif defined(__GNUC__) #if __GNUC__ >= 13 #define sf_assume(cond) __attribute__((assume(cond))) #else @@ -460,6 +462,12 @@ void move_to_front(std::vector& vec, Predicate pred) { #define sf_assume(cond) #endif +#ifdef __GNUC__ + #define sf_unreachable() __builtin_unreachable() +#else + #define sf_unreachable() +#endif + } // namespace Stockfish template diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 16af8d5f68e..926e82d9319 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -507,19 +507,23 @@ void double_inc_update(Color p assert(added.size() < 2); PSQFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); - assert(added.size() == 1); - assert(removed.size() == 2 || removed.size() == 3); + [[maybe_unused]] const int addedSize = added.ssize(); + [[maybe_unused]] const int removedSize = removed.ssize(); + + assert(addedSize == 1); + assert(removedSize == 2 || removedSize == 3); // Workaround compiler warning for uninitialized variables, replicated on // profile builds on windows with gcc 14.2.0. - // TODO remove once unneeded - sf_assume(added.size() == 1); - sf_assume(removed.size() == 2 || removed.size() == 3); + // Also helps with optimizations on some compilers. + + sf_assume(addedSize == 1); + sf_assume(removedSize == 2 || removedSize == 3); auto updateContext = make_accumulator_update_context(perspective, featureTransformer, computed, target_state); - if (removed.size() == 2) + if (removedSize == 2) { updateContext.template apply(added[0], removed[0], removed[1]); } @@ -593,35 +597,41 @@ void update_accumulator_incremental( updateContext.apply(added, removed); else { - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); - assert((Forward && added.size() <= removed.size()) - || (!Forward && added.size() >= removed.size())); + [[maybe_unused]] const int addedSize = added.ssize(); + [[maybe_unused]] const int removedSize = removed.ssize(); + + assert(addedSize == 1 || addedSize == 2); + assert(removedSize == 1 || removedSize == 2); + assert((Forward && addedSize <= removedSize) || (!Forward && addedSize >= removedSize)); // Workaround compiler warning for uninitialized variables, replicated // on profile builds on windows with gcc 14.2.0. - // TODO remove once unneeded - sf_assume(added.size() == 1 || added.size() == 2); - sf_assume(removed.size() == 1 || removed.size() == 2); + // Also helps with optimizations on some compilers. + + sf_assume(addedSize == 1 || addedSize == 2); + sf_assume(removedSize == 1 || removedSize == 2); + + if (!(removedSize == 1 || removedSize == 2) || !(addedSize == 1 || addedSize == 2)) + sf_unreachable(); - if ((Forward && removed.size() == 1) || (!Forward && added.size() == 1)) + if ((Forward && removedSize == 1) || (!Forward && addedSize == 1)) { - assert(added.size() == 1 && removed.size() == 1); + assert(addedSize == 1 && removedSize == 1); updateContext.template apply(added[0], removed[0]); } - else if (Forward && added.size() == 1) + else if (Forward && addedSize == 1) { - assert(removed.size() == 2); + assert(removedSize == 2); updateContext.template apply(added[0], removed[0], removed[1]); } - else if (!Forward && removed.size() == 1) + else if (!Forward && removedSize == 1) { - assert(added.size() == 2); + assert(addedSize == 2); updateContext.template apply(added[0], added[1], removed[0]); } else { - assert(added.size() == 2 && removed.size() == 2); + assert(addedSize == 2 && removedSize == 2); updateContext.template apply(added[0], added[1], removed[0], removed[1]); } From 7f0b5d10ee5428e9895a044f21808e85c598518d Mon Sep 17 00:00:00 2001 From: rn5f107s2 Date: Fri, 23 Jan 2026 15:12:26 +0100 Subject: [PATCH 1303/1309] Only record the ep square from a given FEN if ep is legal When given a FEN with an ep square set, only actually set the ep square if there is a legal ep capture that can be played. Fixes #6563 closes https://github.com/official-stockfish/Stockfish/pull/6564 No functional change --- src/position.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 907dfde4076..3bbe60d6df2 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -264,23 +264,31 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { // 4. En passant square. // Ignore if square is invalid or not on side to move relative rank 6. - bool enpassant = false; + bool enpassant = false, legalEP = false; if (((ss >> col) && (col >= 'a' && col <= 'h')) && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3')))) { st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + Bitboard pawns = attacks_bb(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN); + Bitboard target = (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))); + Bitboard occ = pieces() ^ target ^ st->epSquare; + // En passant square will be considered only if // a) side to move have a pawn threatening epSquare // b) there is an enemy pawn in front of epSquare // c) there is no piece on epSquare or behind epSquare - enpassant = attacks_bb(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN) - && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) + enpassant = pawns + && target && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + + // If no pawn can execute the en passant capture without leaving the king in check, don't record the epSquare + while (pawns) + legalEP |= !(attackers_to(square(sideToMove), occ ^ pop_lsb(pawns)) & pieces(~sideToMove) & ~target); } - if (!enpassant) + if (!enpassant || !legalEP) st->epSquare = SQ_NONE; // 5-6. Halfmove clock and fullmove number From 3ee16a1d8e96a095fcb1c96653031c9758cefc06 Mon Sep 17 00:00:00 2001 From: ppigazzini Date: Fri, 16 Jan 2026 19:37:19 +0100 Subject: [PATCH 1304/1309] refactor(scripts): improve `get_native_properties.sh` Improve `get_native_properties.sh` with a refreshed implementation. The new version covers all Makefile ARCH variants and keeps the script interface stable while improving portability and CPU feature detection across supported platforms. Drop the asset file name to avoid coupling outputs to a specific artifact naming scheme. Refs: https://github.com/ppigazzini/get-native-properties (includes a testing framework) closes https://github.com/official-stockfish/Stockfish/pull/6552 No functional change. --- scripts/get_native_properties.sh | 479 ++++++++++++++++++++++--------- 1 file changed, 339 insertions(+), 140 deletions(-) diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh index 67dd60ca5d1..3733f6df476 100755 --- a/scripts/get_native_properties.sh +++ b/scripts/get_native_properties.sh @@ -1,163 +1,362 @@ #!/bin/sh # -# Returns properties of the native system. -# best architecture as supported by the CPU -# filename of the best binary uploaded as an artifact during CI +# Returns the best architecture supported by the CPU (as expected by src/Makefile ARCH=). # +# Output format: +# "\n" +# + +# --------------------------- +# Helpers (POSIX) +# --------------------------- + +# Test hooks (optional env overrides) +# GP_UNAME_S: override `uname -s` +# GP_UNAME_M: override `uname -m` +# GP_CPUINFO: path to a cpuinfo-like fixture file (defaults to /proc/cpuinfo) +# GP_BITS: override getconf LONG_BIT result (32/64) +# GP_SYSCTL_FEATURES: override sysctl feature strings on Darwin x86_64 + +cpuinfo_path=${GP_CPUINFO:-/proc/cpuinfo} + +# Normalize to a single-line, space-separated string. +normalize_ws() { + printf '%s\n' "$*" | tr '\n\t' ' ' | tr -s ' ' +} -# Check if all the given flags are present in the CPU flags list -check_flags() { - for flag; do - printf '%s\n' "$flags" | grep -q -w "$flag" || return 1 - done +die() { + printf '%s\n' "$*" >&2 + exit 1 } -# Set the CPU flags list -# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 +# Populate $flags from /proc/cpuinfo when available, +# removing underscores and dots to reduce naming variations. get_flags() { - flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo) + if [ -r "$cpuinfo_path" ]; then + flags=$( + awk ' + /^flags[ \t]*:|^Features[ \t]*:/ { + if (!found) { + gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); + line=$0 + found=1 + } + } + END { print line } + ' "$cpuinfo_path" 2>/dev/null + ) + else + flags='' + fi + flags=$(printf '%s\n' "$flags" | tr '[:upper:]' '[:lower:]') + flags=$(normalize_ws "$flags") +} + +# Populate $flags from sysctl on Darwin x86_64. +get_sysctl_flags() { + if [ -n "${GP_SYSCTL_FEATURES:-}" ]; then + flags=$(printf '%s\n' "$GP_SYSCTL_FEATURES") + else + flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features 2>/dev/null) + fi + flags=$(printf '%s\n' "$flags" | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '._') + flags=$(normalize_ws "$flags") +} + +# Best-effort bitness for fallback arch selection. +get_bits() { + if [ -n "${GP_BITS:-}" ]; then + bits=$GP_BITS + else + bits=$(getconf LONG_BIT 2>/dev/null) + fi + case $bits in + 32|64) : ;; + *) bits=64 ;; + esac +} + +# Extract ARM architecture level (5/6/7/8/...) from /proc/cpuinfo when present. +get_arm_arch_level() { + [ -r "$cpuinfo_path" ] || return 1 + awk ' + /^CPU architecture[ \t]*:/{ + s=$0 + sub(/^[^:]*:[ \t]*/, "", s) + if (match(s, /[0-9]+/)) { print substr(s, RSTART, RLENGTH); exit } + } + /^Processor[ \t]*:/{ + s=$0 + sub(/^[^:]*:[ \t]*/, "", s) + if (match(s, /ARMv[0-9]+/)) { print substr(s, RSTART+4, RLENGTH-4); exit } + } + ' "$cpuinfo_path" 2>/dev/null +} + +# Best-effort ARM architecture level (5/6/7/8/...) with a minimal fallback. +# Prefer /proc/cpuinfo when available; fall back to uname -m only when it encodes it. +get_arm_level() { + arm_level=$(get_arm_arch_level || :) + if [ -n "$arm_level" ]; then + printf '%s\n' "$arm_level" + return 0 + fi + case ${1:-} in + armv5*) printf '5\n' ;; + armv6*) printf '6\n' ;; + armv7*) printf '7\n' ;; + armv8l) printf '8\n' ;; + *) return 1 ;; + esac +} + +# Whole-token membership check. +has_flag() { + case " $flags " in + *" $1 "*) return 0 ;; + *) return 1 ;; + esac +} + +match_flags() { + for f; do + has_flag "$f" || return 1 + done + return 0 +} + +match_any_flags() { + for f; do + has_flag "$f" && return 0 + done + return 1 } -# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid -check_znver_1_2() { - vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo) - cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo) - [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true +# SSE3 is often exposed as "pni" in /proc/cpuinfo. +match_sse3() { + match_any_flags sse3 pni } -# Set the file CPU loongarch64 architecture +# AMD Zen1/2 exclusion logic (used for bmi2 tier). +# https://web.archive.org/web/20250821132355/https://en.wikichip.org/wiki/amd/cpuid +is_znver_1_2() ( + [ -r "$cpuinfo_path" ] || exit 1 + vendor_id=$(awk '/^vendor_id/{print $3; exit}' "$cpuinfo_path" 2>/dev/null) + cpu_family=$(awk '/^cpu family/{print $4; exit}' "$cpuinfo_path" 2>/dev/null) + [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] +) + +match_not_znver12_and_flags() { + is_znver_1_2 && return 1 + match_flags "$@" +} + +match_sse3_popcnt() { + has_flag popcnt || return 1 + match_sse3 +} + +match_true() { return 0; } + +# Generic selector: reads lines like "arch|predicate|arg1 arg2 ..." +# First match wins; blank lines and lines starting with '#' are ignored. +select_arch_from_table() { + while IFS='|' read -r arch pred args; do + [ -z "$arch" ] && continue + case $arch in \#*) continue ;; esac + + if [ -n "$args" ]; then + # Intentional splitting of args into words for the predicate. + # shellcheck disable=SC2086 + $pred $args && { printf '%s\n' "$arch"; return 0; } + else + $pred && { printf '%s\n' "$arch"; return 0; } + fi + done + return 1 +} + +# --------------------------- +# Arch selection (table-driven) +# --------------------------- + set_arch_loongarch64() { - if check_flags 'lasx'; then - true_arch='loongarch64-lasx' - elif check_flags 'lsx'; then - true_arch='loongarch64-lsx' - else - true_arch='loongarch64' - fi + true_arch=$( + select_arch_from_table <<'EOF' +loongarch64-lasx|match_flags|lasx +loongarch64-lsx|match_flags|lsx +loongarch64|match_true| +EOF + ) } -# Set the file CPU x86_64 architecture set_arch_x86_64() { - if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then - true_arch='x86-64-avx512icl' - elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then - true_arch='x86-64-vnni512' - elif check_flags 'avx512f' 'avx512bw'; then - true_arch='x86-64-avx512' - elif check_flags 'avxvnni'; then - true_arch='x86-64-avxvnni' - elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then - true_arch='x86-64-bmi2' - elif check_flags 'avx2'; then - true_arch='x86-64-avx2' - elif check_flags 'sse41' && check_flags 'popcnt'; then - true_arch='x86-64-sse41-popcnt' - else - true_arch='x86-64' - fi + true_arch=$( + select_arch_from_table <<'EOF' +# Strongest -> weakest (first match wins) +x86-64-avx512icl|match_flags|avx512f avx512cd avx512vl avx512dq avx512bw avx512ifma avx512vbmi avx512vbmi2 avx512vpopcntdq avx512bitalg avx512vnni vpclmulqdq gfni vaes +x86-64-vnni512|match_flags|avx512vnni avx512dq avx512f avx512bw avx512vl +x86-64-avx512|match_flags|avx512f avx512bw +x86-64-avxvnni|match_flags|avxvnni +x86-64-bmi2|match_not_znver12_and_flags|bmi2 +x86-64-avx2|match_flags|avx2 +x86-64-sse41-popcnt|match_flags|sse41 popcnt +x86-64-ssse3|match_flags|ssse3 +x86-64-sse3-popcnt|match_sse3_popcnt| +x86-64|match_true| +EOF + ) } +set_arch_x86_32() { + true_arch=$( + select_arch_from_table <<'EOF' +x86-32-sse41-popcnt|match_flags|sse41 popcnt +x86-32-sse2|match_flags|sse2 +x86-32|match_true| +EOF + ) +} + +# PPC64 needs a little parsing to distinguish vsx vs altivec. set_arch_ppc_64() { - if grep -q -w "altivec" /proc/cpuinfo; then - power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo) - if [ "0$power" -gt 7 ]; then - # VSX started with POWER8 - true_arch='ppc-64-vsx' - else - true_arch='ppc-64-altivec' - fi - else - true_arch='ppc-64' - fi -} - -# Check the system type -uname_s=$(uname -s) -uname_m=$(uname -m) + if [ -r "$cpuinfo_path" ] && grep -q "altivec" "$cpuinfo_path" 2>/dev/null; then + # Typical: "cpu : POWER8E" (extract the number after POWER) + power=$( + awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \ + | sed -n 's/.*[Pp][Oo][Ww][Ee][Rr][^0-9]*\([0-9][0-9]*\).*/\1/p' + ) + if [ -z "$power" ]; then + power=$( + awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \ + | sed -n 's/.*\([0-9][0-9]*\).*/\1/p' + ) + fi + case $power in + ''|*[!0-9]*) + true_arch='ppc-64-altivec' + ;; + *) + if [ "$power" -gt 7 ] 2>/dev/null; then + true_arch='ppc-64-vsx' + else + true_arch='ppc-64-altivec' + fi + ;; + esac + else + true_arch='ppc-64' + fi +} + +# --------------------------- +# OS / machine dispatch +# --------------------------- + +uname_s=$(uname -s 2>/dev/null) +uname_m=$(uname -m 2>/dev/null) +uname_s=${GP_UNAME_S:-$uname_s} +uname_m=${GP_UNAME_M:-$uname_m} + case $uname_s in - 'Darwin') # Mac OSX system - case $uname_m in - 'arm64') - true_arch='apple-silicon' - file_arch='m1-apple-silicon' - ;; - 'x86_64') - flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') - set_arch_x86_64 - if [ "$true_arch" = 'x86-64-avx512' ]; then - file_arch='x86-64-bmi2' - fi - ;; - esac - file_os='macos' - file_ext='tar' - ;; - 'Linux') # Linux system - get_flags - case $uname_m in - 'x86_64') - file_os='ubuntu' - check_znver_1_2 - set_arch_x86_64 - ;; - 'i686') - file_os='ubuntu' - true_arch='x86-32' - ;; - 'ppc64'*) - file_os='ubuntu' - set_arch_ppc_64 - ;; - 'aarch64') - file_os='android' - true_arch='armv8' - if check_flags 'asimddp'; then - true_arch="$true_arch-dotprod" - fi - ;; - 'armv7'*) - file_os='android' - true_arch='armv7' - if check_flags 'neon'; then - true_arch="$true_arch-neon" - fi - ;; - 'loongarch64'*) - file_os='linux' - set_arch_loongarch64 - ;; - *) # Unsupported machine type, exit with error - printf 'Unsupported machine type: %s\n' "$uname_m" - exit 1 - ;; - esac - file_ext='tar' - ;; - 'MINGW'*'ARM64'*) # Windows ARM64 system with POSIX compatibility layer - # TODO: older chips might be armv8, but we have no good way to detect, /proc/cpuinfo shows x86 info - file_os='windows' - true_arch='armv8-dotprod' - file_ext='zip' - ;; - 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows x86_64system with POSIX compatibility layer - get_flags - check_znver_1_2 - set_arch_x86_64 - file_os='windows' - file_ext='zip' - ;; - *) - # Unknown system type, exit with error - printf 'Unsupported system type: %s\n' "$uname_s" - exit 1 - ;; -esac + Darwin) + case $uname_m in + arm64) + true_arch='apple-silicon' + ;; + x86_64) + get_sysctl_flags + set_arch_x86_64 + ;; + *) + get_bits + if [ "$bits" = "32" ]; then + true_arch='general-32' + else + true_arch='general-64' + fi + ;; + esac + ;; + + Linux) + get_flags + case $uname_m in + x86_64) + set_arch_x86_64 + ;; + i?86) + set_arch_x86_32 + ;; + ppc64*) + set_arch_ppc_64 + ;; + aarch64|arm64) + true_arch='armv8' + if match_flags asimddp; then + true_arch='armv8-dotprod' + fi + ;; + armv5*|armv6*|armv7*|armv8l|arm*) + arm_level=$(get_arm_level "$uname_m" || :) + case $arm_level in + 5|6) + true_arch='general-32' + ;; + 7|8) + true_arch='armv7' + if match_flags neon; then + true_arch='armv7-neon' + fi + ;; + *) + true_arch='general-32' + if match_flags neon; then + true_arch='armv7-neon' + fi + ;; + esac + ;; + loongarch64*) + set_arch_loongarch64 + ;; + riscv64) + true_arch='riscv64' + ;; + e2k*) + true_arch='e2k' + ;; + ppc|ppc32|powerpc) + true_arch='ppc-32' + ;; + *) + # Don't hard-fail: fall back to general-* so ARCH=native still builds + get_bits + if [ "$bits" = "32" ]; then + true_arch='general-32' + else + true_arch='general-64' + fi + ;; + esac + ;; -if [ -z "$file_arch" ]; then - file_arch=$true_arch -fi + MINGW*ARM64*) + # Windows ARM64 (MSYS2/MinGW) + # Can't reliably detect ARM CPU features here + true_arch='armv8-dotprod' + ;; -file_name="stockfish-$file_os-$file_arch.$file_ext" + CYGWIN*|MINGW*|MSYS*) + # Windows x86_64 (MSYS2/Cygwin/MinGW) + get_flags + set_arch_x86_64 + ;; + + *) + die "Unsupported system type: $uname_s" + ;; +esac -printf '%s %s\n' "$true_arch" "$file_name" +printf '%s\n' "$true_arch" From 9cc2985f51cc5e1a09ad9c9000ad986b6d026823 Mon Sep 17 00:00:00 2001 From: disservin Date: Sat, 31 Jan 2026 13:59:10 +0100 Subject: [PATCH 1305/1309] Don't push prerelease for release commits Looks for the text "Official release version of Stockfish" in the commit message to determine if something is a release. closes https://github.com/official-stockfish/Stockfish/pull/6580 No functional change --- .github/workflows/stockfish.yml | 49 ++++++++++++++++++--------- .github/workflows/upload_binaries.yml | 12 ++++++- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/.github/workflows/stockfish.yml b/.github/workflows/stockfish.yml index f08d3474ab6..1b4b0ea06b8 100644 --- a/.github/workflows/stockfish.yml +++ b/.github/workflows/stockfish.yml @@ -15,6 +15,7 @@ jobs: Prerelease: if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')) runs-on: ubuntu-latest + needs: [Matrix] permissions: contents: write # For deleting/creating a prerelease steps: @@ -53,16 +54,25 @@ jobs: id: commit_date run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV + - name: Official Release? + id: official_release + # Check for "Official release version of Stockfish" in the commit message + run: | + if git log -1 --pretty=%B | grep -q "Official release version of Stockfish"; then + echo "OFFICIAL_RELEASE=true" >> $GITHUB_ENV + else + echo "OFFICIAL_RELEASE=false" >> $GITHUB_ENV + fi + # Create a new pre-release, the other upload_binaries.yml will upload the binaries # to this pre-release. - name: Create Prerelease - if: github.ref_name == 'master' && env.CHANGES == '0' + if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false' uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 with: name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }} prerelease: true - Matrix: runs-on: ubuntu-latest outputs: @@ -80,29 +90,38 @@ jobs: run: | TASKS_ARM=$(echo $(cat .github/ci/arm_matrix.json) ) echo "ARM_MATRIX=$TASKS_ARM" >> $GITHUB_OUTPUT - Compilation: - needs: [Matrix] - uses: ./.github/workflows/compilation.yml - with: - matrix: ${{ needs.Matrix.outputs.matrix }} - ARMCompilation: - needs: [Matrix] - uses: ./.github/workflows/arm_compilation.yml - with: - matrix: ${{ needs.Matrix.outputs.arm_matrix }} + # Testing Jobs IWYU: uses: ./.github/workflows/iwyu.yml Sanitizers: + if: ${{ always() }} uses: ./.github/workflows/sanitizers.yml Tests: + if: ${{ always() }} uses: ./.github/workflows/tests.yml Matetrack: + if: ${{ always() }} uses: ./.github/workflows/matetrack.yml Games: + if: ${{ always() }} uses: ./.github/workflows/games.yml + CompilerCheck: + if: ${{ always() }} + uses: ./.github/workflows/avx2_compilers.yml + # Release Jobs + Compilation: + needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck] + uses: ./.github/workflows/compilation.yml + with: + matrix: ${{ needs.Matrix.outputs.matrix }} + ARMCompilation: + needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck] + uses: ./.github/workflows/arm_compilation.yml + with: + matrix: ${{ needs.Matrix.outputs.arm_matrix }} Binaries: if: github.repository == 'official-stockfish/Stockfish' - needs: [Matrix, Prerelease, Compilation] + needs: [Prerelease, Matrix, Compilation] uses: ./.github/workflows/upload_binaries.yml with: matrix: ${{ needs.Matrix.outputs.matrix }} @@ -112,7 +131,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} ARM_Binaries: if: github.repository == 'official-stockfish/Stockfish' - needs: [Matrix, Prerelease, ARMCompilation] + needs: [Prerelease, Matrix, ARMCompilation] uses: ./.github/workflows/upload_binaries.yml with: matrix: ${{ needs.Matrix.outputs.arm_matrix }} @@ -120,5 +139,3 @@ jobs: contents: write # For deleting/creating a (pre)release secrets: token: ${{ secrets.GITHUB_TOKEN }} - CompilerCheck: - uses: ./.github/workflows/avx2_compilers.yml diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml index 073e40a13a1..d4d1eed29da 100644 --- a/.github/workflows/upload_binaries.yml +++ b/.github/workflows/upload_binaries.yml @@ -92,8 +92,18 @@ jobs: CHANGES=$(git rev-list HEAD..origin/master --count) echo "CHANGES=$CHANGES" >> $GITHUB_ENV + - name: Official Release? + id: official_release + # Check for "Official release version of Stockfish" in the commit message + run: | + if git log -1 --pretty=%B | grep -q "Official release version of Stockfish"; then + echo "OFFICIAL_RELEASE=true" >> $GITHUB_ENV + else + echo "OFFICIAL_RELEASE=false" >> $GITHUB_ENV + fi + - name: Prerelease - if: github.ref_name == 'master' && env.CHANGES == '0' + if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false' continue-on-error: true uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981 with: From 9f42980dd2c6109aeb4a56b2f717c223e569e052 Mon Sep 17 00:00:00 2001 From: KazApps Date: Tue, 20 Jan 2026 11:23:49 +0900 Subject: [PATCH 1306/1309] refactor update_piece_threats to reduce branching Passed STC Non-Regression: https://tests.stockfishchess.org/tests/view/696f1a398b64097dacd231c3 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 122272 W: 31587 L: 31466 D: 59219 Ptnml(0-2): 301, 13358, 33750, 13373, 354 slight speedup as well: 1 thread bench: sf_base = 2238429 +/- 1221 (95%) sf_test = 2248298 +/- 1371 (95%) diff = 9869 +/- 1571 (95%) speedup = 0.44090% +/- 0.070% (95%) 32 thread speedtest: sf_base = 41016996 +/- 83654 (95%) sf_test = 41185801 +/- 84269 (95%) diff = 168805 +/- 79986 (95%) speedup = 0.41155% +/- 0.195% (95%) closes https://github.com/official-stockfish/Stockfish/pull/6559 No functional change --- src/position.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 3bbe60d6df2..a8d3cc506a1 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1069,8 +1069,8 @@ inline void add_dirty_threat( DirtyThreats* const dts, Piece pc, Piece threatened, Square s, Square threatenedSq) { if (PutPiece) { - dts->threatenedSqs |= square_bb(threatenedSq); - dts->threateningSqs |= square_bb(s); + dts->threatenedSqs |= threatenedSq; + dts->threateningSqs |= s; } dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); @@ -1139,30 +1139,25 @@ void Position::update_piece_threats(Piece pc, | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); #ifdef USE_AVX512ICL - if (threatened) + if constexpr (PutPiece) { - if constexpr (PutPiece) - { - dts->threatenedSqs |= threatened; - dts->threateningSqs |= square_bb(s); - } - - DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece}; - write_multiple_dirties( - *this, threatened, dt_template, dts); + dts->threatenedSqs |= threatened; + dts->threateningSqs |= s; } + DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece}; + write_multiple_dirties( + *this, threatened, dt_template, dts); + Bitboard all_attackers = sliders | incoming_threats; - if (!all_attackers) - return; // Square s is threatened iff there's at least one attacker if constexpr (PutPiece) { - dts->threatenedSqs |= square_bb(s); + dts->threatenedSqs |= s; dts->threateningSqs |= all_attackers; } - DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece}; + dt_template = {NO_PIECE, pc, Square(0), s, PutPiece}; write_multiple_dirties(*this, all_attackers, dt_template, dts); #else From 2321cf2f77b241d685ee68c9896f6574a6f12d0d Mon Sep 17 00:00:00 2001 From: Pieter te Brake Date: Mon, 26 Jan 2026 01:01:28 +0100 Subject: [PATCH 1307/1309] Simplify en passant square update in Position::do_move(). Passed non-regression STC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 119680 W: 31011 L: 30884 D: 57785 Ptnml(0-2): 354, 13231, 32575, 13294, 386 https://tests.stockfishchess.org/tests/view/6973b06c6cd60a8e97ca62e5 closes https://github.com/official-stockfish/Stockfish/pull/6565 No functional change --- AUTHORS | 1 + src/position.cpp | 101 +++++++++++++++-------------------------------- 2 files changed, 33 insertions(+), 69 deletions(-) diff --git a/AUTHORS b/AUTHORS index 1c3f4b97fc7..62655dfde65 100644 --- a/AUTHORS +++ b/AUTHORS @@ -205,6 +205,7 @@ Patrick Jansen (mibere) Patrick Leonhardt (Yoshie2000) Peter Schneider (pschneider1968) Peter Zsifkovits (CoffeeOne) +Pieter te Brake (pieterteb) PikaCat Praveen Kumar Tummala (praveentml) Prokop Randáček (ProkopRandacek) diff --git a/src/position.cpp b/src/position.cpp index a8d3cc506a1..f1222a66f88 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -273,19 +273,19 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { Bitboard pawns = attacks_bb(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN); Bitboard target = (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))); - Bitboard occ = pieces() ^ target ^ st->epSquare; + Bitboard occ = pieces() ^ target ^ st->epSquare; // En passant square will be considered only if // a) side to move have a pawn threatening epSquare // b) there is an enemy pawn in front of epSquare // c) there is no piece on epSquare or behind epSquare - enpassant = pawns - && target - && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); + enpassant = + pawns && target && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); // If no pawn can execute the en passant capture without leaving the king in check, don't record the epSquare while (pawns) - legalEP |= !(attackers_to(square(sideToMove), occ ^ pop_lsb(pawns)) & pieces(~sideToMove) & ~target); + legalEP |= !(attackers_to(square(sideToMove), occ ^ pop_lsb(pawns)) + & pieces(~sideToMove) & ~target); } if (!enpassant || !legalEP) @@ -739,8 +739,6 @@ void Position::do_move(Move m, Piece pc = piece_on(from); Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); - bool checkEP = false; - dp.pc = pc; dp.from = from; dp.to = to; @@ -844,9 +842,30 @@ void Position::do_move(Move m, // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) { - // Check later if the en passant square needs to be set + // Check if the en passant square needs to be set. Accurate e.p. info is needed + // for correct zobrist key generation and 3-fold checking. if ((int(to) ^ int(from)) == 16) - checkEP = true; + { + Square epSquare = to - pawn_push(us); + Bitboard pawns = attacks_bb(epSquare, us) & pieces(them, PAWN); + + // If there are no pawns attacking the ep square, ep is not possible. + if (pawns) + { + Square ksq = square(them); + Bitboard notBlockers = ~st->previous->blockersForKing[them]; + bool noDiscovery = (from & notBlockers) || file_of(from) == file_of(ksq); + + // If the pawn gives discovered check, ep is never legal. Else, if at least one + // pawn was not a blocker for the enemy king or lies on the same line as the + // enemy king and en passant square, a legal capture exists. + if (noDiscovery && (pawns & (notBlockers | line_bb(epSquare, ksq)))) + { + st->epSquare = epSquare; + k ^= Zobrist::enpassant[file_of(epSquare)]; + } + } + } else if (m.type_of() == PROMOTION) { @@ -891,9 +910,10 @@ void Position::do_move(Move m, st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - // If en passant is impossible, then k will not change and we can prefetch earlier - if (tt && !checkEP) - prefetch(tt->first_entry(adjust_key50(k))); + // Update the key with the final value + st->key = k; + if (tt) + prefetch(tt->first_entry(key())); if (history) { @@ -915,63 +935,6 @@ void Position::do_move(Move m, // Update king attacks used for fast check detection set_check_info(); - // Accurate e.p. info is needed for correct zobrist key generation and 3-fold checking - while (checkEP) - { - auto updateEpSquare = [&] { - st->epSquare = to - pawn_push(us); - k ^= Zobrist::enpassant[file_of(st->epSquare)]; - }; - - Bitboard pawns = attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN); - - // If there are no pawns attacking the ep square, ep is not possible - if (!pawns) - break; - - // If there are checkers other than the to be captured pawn, ep is never legal - if (checkers() & ~square_bb(to)) - break; - - if (more_than_one(pawns)) - { - // If there are two pawns potentially being able to capture and at least one - // is not pinned, ep is legal as there are no horizontal exposed checks - if (!more_than_one(blockers_for_king(them) & pawns)) - { - updateEpSquare(); - break; - } - - // If there is no pawn on our king's file, and thus both pawns are pinned - // by bishops, ep is not legal as the king square must be in front of the to square. - // And because the ep square and the king are not on a common diagonal, either ep capture - // would expose the king to a check from one of the bishops - if (!(file_bb(square(them)) & pawns)) - break; - - // Otherwise remove the pawn on the king file, as an ep capture by it can never be legal and the - // check below relies on there only being one pawn - pawns &= ~file_bb(square(them)); - } - - Square ksq = square(them); - Square capsq = to; - Bitboard occupied = (pieces() ^ lsb(pawns) ^ capsq) | (to - pawn_push(us)); - - // If our king is not attacked after making the move, ep is legal. - if (!(attacks_bb(ksq, occupied) & pieces(us, QUEEN, ROOK)) - && !(attacks_bb(ksq, occupied) & pieces(us, QUEEN, BISHOP))) - updateEpSquare(); - - break; - } - - // Update the key with the final value - st->key = k; - if (tt) - prefetch(tt->first_entry(key())); - // Calculate the repetition info. It is the ply distance from the previous // occurrence of the same position, negative in the 3-fold case, or zero // if the position was not repeated. From 24af6a6bc409541a3d6e5cab7c5923ac397476fd Mon Sep 17 00:00:00 2001 From: Pieter te Brake Date: Mon, 2 Feb 2026 12:26:04 +0100 Subject: [PATCH 1308/1309] Update castling rights unconditionally. passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 163680 W: 42214 L: 42137 D: 79329 Ptnml(0-2): 454, 18054, 44734, 18157, 441 https://tests.stockfishchess.org/tests/view/697e6f4e5f56030af97b5a3c closes https://github.com/official-stockfish/Stockfish/pull/6588 No functional change --- src/position.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index f1222a66f88..218be153dd7 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -819,13 +819,10 @@ void Position::do_move(Move m, st->epSquare = SQ_NONE; } - // Update castling rights if needed - if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) - { - k ^= Zobrist::castling[st->castlingRights]; - st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); - k ^= Zobrist::castling[st->castlingRights]; - } + // Update castling rights. + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + k ^= Zobrist::castling[st->castlingRights]; // Move the piece. The tricky Chess960 castling is handled earlier if (m.type_of() != CASTLING) From fac506bdf3f0ed46fd0823ff1ed592824f91aa5a Mon Sep 17 00:00:00 2001 From: sscg13 Date: Sat, 31 Jan 2026 09:57:10 -0800 Subject: [PATCH 1309/1309] Update NNUE architecture to SFNNv11 and net nn-3dd094f3dfcf.nnue Removes threat features of the form piece -> king, thus saving 13MB of net space and approximately 0.8 threat feature updates per incremental accumulator update. Passed non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 33664 W: 8864 L: 8636 D: 16164 Ptnml(0-2): 136, 3926, 8501, 4112, 157 https://tests.stockfishchess.org/tests/view/6981dcda4776a4e6e7fef2ac Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 46896 W: 12077 L: 11881 D: 22938 Ptnml(0-2): 41, 5118, 12924, 5334, 31 https://tests.stockfishchess.org/tests/view/69827c6beb87369ff4d0c7d5 closes https://github.com/official-stockfish/Stockfish/pull/6593 bench: 2668754 --- src/evaluate.h | 2 +- src/nnue/features/full_threats.h | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index b4f54a381aa..226170e12f5 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-c288c895ea92.nnue" +#define EvalFileDefaultNameBig "nn-3dd094f3dfcf.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index 5b2582954e9..a71368ad45f 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -30,8 +30,8 @@ class Position; namespace Stockfish::Eval::NNUE::Features { -static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0, - 0, 6, 12, 10, 10, 12, 8, 0}; +static constexpr int numValidTargets[PIECE_NB] = {0, 6, 10, 8, 8, 10, 8, 0, + 0, 6, 10, 8, 8, 10, 8, 0}; class FullThreats { public: @@ -42,7 +42,7 @@ class FullThreats { static constexpr std::uint32_t HashValue = 0x8f234cb8u; // Number of feature dimensions - static constexpr IndexType Dimensions = 79856; + static constexpr IndexType Dimensions = 66864; // clang-format off // Orient a square according to perspective (rotates by 180 for black) @@ -59,10 +59,10 @@ class FullThreats { static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = { {0, 1, -1, 2, -1, -1}, - {0, 1, 2, 3, 4, 5}, - {0, 1, 2, 3, -1, 4}, - {0, 1, 2, 3, -1, 4}, - {0, 1, 2, 3, 4, 5}, + {0, 1, 2, 3, 4, -1}, + {0, 1, 2, 3, -1, -1}, + {0, 1, 2, 3, -1, -1}, + {0, 1, 2, 3, 4, -1}, {0, 1, 2, 3, -1, -1} }; // clang-format on