From 299707d2c2cbf1694bb21ed4a375b54ef35d719e Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 17 Mar 2024 12:33:14 +0100 Subject: [PATCH 001/834] 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 | 144 ++++++++++++++------------------------ src/uci.h | 25 +++---- 14 files changed, 341 insertions(+), 149 deletions(-) create mode 100644 src/engine.cpp create mode 100644 src/engine.h diff --git a/src/Makefile b/src/Makefile index 672171bcd..6315bda82 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 000000000..79a2c6047 --- /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 000000000..6afc423de --- /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 bc705b857..dcbfedb49 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 33d5d375f..4e72c0039 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 270d25ad4..1abb81b14 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 de34ee111..d75b236ff 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 725d90d27..3fa6e1b61 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 2edc3ad0a..2dbab828a 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 2263afe76..fd1678959 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 3f882aabd..efc007506 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 @@ moves_loop: // When in check, search starts here 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 b88c085fd..079614db2 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 ee95d5be5..ed23c00a4 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); - }); + 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); }); - 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 + 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 237928d9a..c4e90b48d 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; + Engine engine; + 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 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 002/834] 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 | 110 +++++++++++++++++++++++++++++++++++++++---------- src/uci.h | 17 +++++--- 12 files changed, 372 insertions(+), 104 deletions(-) create mode 100644 src/score.cpp create mode 100644 src/score.h diff --git a/src/Makefile b/src/Makefile index 6315bda82..550f5404d 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 79a2c6047..12fa5c3fd 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 6afc423de..f74209d90 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 4e72c0039..a6a3d1c4e 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 000000000..d1a8a6abe --- /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 000000000..b94d9f7fb --- /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 efc007506..51cd1ae11 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 @@ moves_loop: // When in check, search starts here 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()) + " "; - ss << "info" - << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 - << " score " << UCIEngine::to_score(v, pos); + // remove last whitespace + if (!pv.empty()) + pv.pop_back(); - if (worker.options["UCI_ShowWDL"]) - ss << UCIEngine::wdl(v, pos); + auto wdl = worker.options["UCI_ShowWDL"] ? UCIEngine::wdl(v, pos) : ""; + auto bound = rootMoves[i].scoreLowerbound + ? "lowerbound" + : (rootMoves[i].scoreUpperbound ? "upperbound" : ""); + + 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 22f75ffd4..d14648403 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 90add4ad0..85a2bcbb1 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 81fcc72a7..223652aec 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 ed23c00a4..d6936d38b 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::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); + }}; - 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(); + 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 c4e90b48d..fa8c57fd9 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 003/834] 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 97ca4c4b6..da9c7074e 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 51cd1ae11..a131c958e 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 @@ moves_loop: // When in check, search starts here { 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 @@ moves_loop: // When in check, search starts here } // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 004/834] 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 888edebbd..3101c8d26 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 005/834] 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 ef141971d..3934ac2d6 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 964b5f05e..3524d5e9f 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 7ab1f997a..612f1275c 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 702e86e5f..328c9cf94 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 015b514ce..acf91a8f3 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 550f5404d..45f38b013 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 006/834] 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 a131c958e..3d84eb360 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 007/834] 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 3d84eb360..24805aa70 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 @@ moves_loop: // When in check, search starts here // 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 008/834] 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 d6936d38b..a328ccb08 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 009/834] 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 a328ccb08..a15bc7d4b 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 fa8c57fd9..fa359db45 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 010/834] 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 50f8612d9..267a6b4b2 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 86f8a0ad5..8905fcb18 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 12fa5c3fd..325b971ea 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 f74209d90..7122ee59d 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 d14648403..d30a06fea 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 85a2bcbb1..1438c9f9d 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 a15bc7d4b..8f6978369 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 fa359db45..ee8c2814a 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 011/834] 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 d1a8a6abe..292f53406 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 b94d9f7fb..2eb40f7e0 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 8f6978369..8e20207b7 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 012/834] 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 fd1678959..78e62bda3 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 24805aa70..0eb0f45e1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1083,7 +1083,7 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // 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 9d4d2eca4..41ed4591f 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 013/834] 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 0eb0f45e1..00636865f 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 @@ moves_loop: // When in check, search starts here 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 d30a06fea..3ceaf5ddd 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 229ff3e9d..c651745f0 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 b07712a25..35c3cfc06 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 014/834] 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 00636865f..6813c1a5f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -963,11 +963,11 @@ moves_loop: // When in check, search starts here 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 015/834] 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 3101c8d26..0a0f4217f 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 016/834] 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 8e20207b7..c707f6dc4 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 017/834] 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 612f1275c..78260a182 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 525c7e040..ac534c16a 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 018/834] 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 78260a182..b75c06cfb 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 ac534c16a..4c63fc571 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 019/834] 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 41ed4591f..4885a781a 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 020/834] 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 267a6b4b2..3622ac8af 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 8905fcb18..b1eba40f3 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 325b971ea..4625e00a8 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 7122ee59d..041f56785 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 2dbab828a..e907742da 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 c707f6dc4..cb686a027 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 ee8c2814a..55d580f97 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 021/834] 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 dcbfedb49..ec120a480 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 c1119cf11..4a93662db 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 @@ top: 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 6813c1a5f..183b7bcee 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 @@ moves_loop: // When in check, search starts here { 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 @@ moves_loop: // When in check, search starts here } // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { 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 022/834] 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 ec120a480..f5746ca51 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 da9c7074e..38615ff7d 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 5789db484..71782a7b7 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 8363184f4..963497047 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 bea3e7cb3..656ad97a1 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 21e1c6222..df59732d9 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, - bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false) const; + Value evaluate(const Position& pos, + AccumulatorCaches::Cache* cache, + 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 c0746b4ee..8d73dbef5 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 0a0f4217f..88f0e4031 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 3fa6e1b61..51838fefa 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 5eab02184..27a93f884 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 183b7bcee..893daab20 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 3ceaf5ddd..0fd778b47 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 023/834] 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 4625e00a8..72a37ce9b 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 041f56785..64a814cb4 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 893daab20..396e5aa06 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 0fd778b47..9b3528c87 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 024/834] 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 f5746ca51..6e101e783 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 8d73dbef5..f65385688 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 396e5aa06..11373707b 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 9b3528c87..444e3b8bb 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 025/834] 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 656ad97a1..42320bae1 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 026/834] 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 | 267 ++++++++-------------------- src/nnue/nnue_misc.cpp | 2 +- 6 files changed, 88 insertions(+), 195 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6e101e783..345925f6b 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 42320bae1..2eca18bd1 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 df59732d9..053b7d19c 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 f65385688..dd313958f 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 88f0e4031..60957ebeb 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}) - { - 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) + if (entry.psqtOnly && !psqtOnly) + { + entry.clear(biases); + FeatureSet::append_active_indices(pos, added); + } + else + { + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) { - 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)); + 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(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 (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) { @@ -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 51838fefa..e92dcc710 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 027/834] 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 | 71 +++++++++++++++++------------ src/nnue/nnue_misc.cpp | 3 +- 4 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 345925f6b..fe6b83aa1 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 dd313958f..a2b3b9898 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 60957ebeb..6b3f78a9a 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,15 +755,6 @@ class FeatureTransformer { 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]; @@ -760,6 +764,15 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[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 (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&entryTilePsqt[k], psqt[k]); @@ -767,18 +780,6 @@ class FeatureTransformer { #else - 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]; - } - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } for (const auto index : removed) { if (!psqtOnly) @@ -791,6 +792,18 @@ class FeatureTransformer { for (std::size_t k = 0; k < PSQTBuckets; ++k) entry.psqtAccumulation[Perspective][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[Perspective][j] += weights[offset + j]; + } + + for (std::size_t k = 0; k < 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 e92dcc710..21685d0f2 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 028/834] 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 11373707b..ad59b35a5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,7 +1067,12 @@ moves_loop: // When in check, search starts here // 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 029/834] 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 fe6b83aa1..1d41f3a26 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 030/834] 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 ad59b35a5..3718c3781 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1123,6 +1123,9 @@ moves_loop: // When in check, search starts here 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 031/834] 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 3718c3781..e4f170be6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -967,20 +967,22 @@ moves_loop: // When in check, search starts here 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 032/834] 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 abae401c1..36b2b6f79 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 1d41f3a26..e3aa249ca 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 033/834] 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 6b3f78a9a..402a47a81 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; + (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; + (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; - StateInfo* st2 = states_to_update[i]; + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - for (; i >= 0; --i) - { - (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]; - - 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 034/834] 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 a2b3b9898..179feba55 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 402a47a81..4647ecd06 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 e4f170be6..b8e515f02 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 035/834] 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 72a37ce9b..e8da24aa9 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 1438c9f9d..9052654ba 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 036/834] 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 | 346 ++++++++++++---------------- src/nnue/nnue_misc.cpp | 13 +- src/position.cpp | 18 +- 8 files changed, 172 insertions(+), 244 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e3aa249ca..11999b554 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 38615ff7d..2d244ff67 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 2eca18bd1..de2c7eca6 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 053b7d19c..23f566630 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 179feba55..b8dcf1e48 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 4647ecd06..018b715e6 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]); + + 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) { - auto accIn = - reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + 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]); - 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])); - } + 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]); - - for (IndexType i = 0; i < N; ++i) + // Difference calculation for the deactivated features + for (const auto index : removed[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_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]); } + } 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,95 +625,84 @@ 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) + for (Color c : {WHITE, BLACK}) { - entry.clear(biases); - FeatureSet::append_active_indices(pos, added); - } - else - { - 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) + 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) { - auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = entryTile[k]; + 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]); - 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_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]); + 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_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]); + } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -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 21685d0f2..bf73a58bf 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 78e62bda3..b46ba0299 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 037/834] 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 b8e515f02..cd80e9392 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1335,8 +1335,8 @@ moves_loop: // When in check, search starts here 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 038/834] 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 | 21 +++++++++------------ src/search.h | 1 - 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index cd80e9392..06d31510e 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 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - extension = 1; + 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; - // 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; + extension = 1 + (value < singularBeta - doubleMargin) + + (value < singularBeta - tripleMargin) + + (value < singularBeta - quadMargin); + + depth += ((!PvNode) && (depth < 14)); } // Multi-cut pruning @@ -1104,7 +1102,6 @@ moves_loop: // When in check, search starts here // 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 444e3b8bb..cb73a5afd 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 039/834] 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 11999b554..5be7e7a1b 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 040/834] 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 06d31510e..43f18af21 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 @@ moves_loop: // When in check, search starts here 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 041/834] 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 43f18af21..a60f4d36e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -991,7 +991,7 @@ moves_loop: // When in check, search starts here 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 042/834] 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 018b715e6..2b11adefb 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 043/834] 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 a60f4d36e..6830e4b12 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 044/834] 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 6830e4b12..2c3fc56e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1154,7 +1154,7 @@ moves_loop: // When in check, search starts here + (*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 045/834] 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 2c3fc56e3..3eec00b08 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1154,7 +1154,7 @@ moves_loop: // When in check, search starts here + (*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 046/834] 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 3eec00b08..633f9b515 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 047/834] 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 633f9b515..767ea2380 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 048/834] 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 767ea2380..684b760ec 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 @@ moves_loop: // When in check, search starts here 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 cb73a5afd..c824daf93 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 c651745f0..4feb329b3 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 35c3cfc06..1b6bd849a 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 049/834] 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 684b760ec..1d0cb4ab6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -960,8 +960,7 @@ moves_loop: // When in check, search starts here 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 050/834] 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 1d0cb4ab6..d9f997e8e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1149,8 +1149,7 @@ moves_loop: // When in check, search starts here 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 051/834] 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 d9f997e8e..448da7e25 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -990,7 +990,6 @@ moves_loop: // When in check, search starts here 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 052/834] 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 448da7e25..fdf9871cd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1288,7 +1288,7 @@ moves_loop: // When in check, search starts here 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 053/834] 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 fdf9871cd..4572ffc90 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1087,10 +1087,6 @@ moves_loop: // When in check, search starts here // 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 054/834] 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 4572ffc90..6c30c3e91 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1284,7 +1284,7 @@ moves_loop: // When in check, search starts here 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 055/834] 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 6c30c3e91..3867a3975 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 056/834] 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 3867a3975..1d9e0d81a 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1327,9 +1327,9 @@ moves_loop: // When in check, search starts here // 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 057/834] 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 1d9e0d81a..ae2b1de23 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 058/834] 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 5be7e7a1b..cfe20601e 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 059/834] 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 cfe20601e..b5f28d5ae 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 2d244ff67..afaf35ebe 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 060/834] 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 2b11adefb..bcd14e6fb 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 061/834] 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 bcd14e6fb..7b7aada31 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 ae2b1de23..edbb58c62 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 c824daf93..6e5b22bda 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 9052654ba..8724cb49c 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 4feb329b3..f389e082d 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 1b6bd849a..8f1bb5639 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 062/834] 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 edbb58c62..30f718bd7 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 @@ moves_loop: // When in check, search starts here // 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 063/834] 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 30f718bd7..09a9cc920 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 @@ moves_loop: // When in check, search starts here 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 064/834] 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 b5f28d5ae..de1adc989 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 065/834] 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 09a9cc920..2dadd0dca 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1134,6 +1134,9 @@ moves_loop: // When in check, search starts here 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 066/834] 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 de1adc989..76d630dd9 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 067/834] 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 2dadd0dca..d9041c66a 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 068/834] 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 76d630dd9..3ce148627 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 069/834] 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 d9041c66a..bdcecd1c2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1134,9 +1134,6 @@ moves_loop: // When in check, search starts here 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 070/834] 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 3ce148627..498ec161b 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 afaf35ebe..6612ec9da 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 bf73a58bf..8a7779120 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 071/834] 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 4885a781a..cb46fc8a9 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 072/834] 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 6612ec9da..c87be53c1 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 073/834] 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 498ec161b..09402b8b1 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 c87be53c1..4b3e91acf 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 8a7779120..a13c717c3 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 074/834] 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 32c626d47..c842ca127 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 075/834] 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 bdcecd1c2..06c6e1987 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 076/834] 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 09402b8b1..abb04fcc2 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 077/834] 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 06c6e1987..2b95043fd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,11 +1053,9 @@ moves_loop: // When in check, search starts here 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 078/834] 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 | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index abb04fcc2..e5ebd45ae 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; + // 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; + 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); + // 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 079/834] 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 2b95043fd..54990ce6f 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); // (~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 @@ moves_loop: // When in check, search starts here // 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 080/834] 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 54990ce6f..cbd454efb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,9 +1053,11 @@ moves_loop: // When in check, search starts here 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 081/834] 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 813234c59..2ee378ad8 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 a13c717c3..b54bbaba3 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 3e5ebe5e6..84f59524f 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 cb686a027..cb9d7b085 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 082/834] 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 cbd454efb..5b9c9bb00 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 083/834] 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 5b9c9bb00..2618b9847 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 @@ moves_loop: // When in check, search starts here 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 084/834] 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 2618b9847..7e95dd878 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 085/834] 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 e5ebd45ae..2cf82eaf5 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 086/834] 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 b54bbaba3..a13c717c3 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 7e95dd878..2ed5d97bb 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 087/834] 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 2cf82eaf5..3a24657f9 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 84f59524f..3e5ebe5e6 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 cb9d7b085..cb686a027 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 088/834] 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 2ed5d97bb..4c5e521e7 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 089/834] 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 4c5e521e7..2817247d2 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 090/834] 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 3a24657f9..44e69b3fd 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 091/834] 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 2817247d2..a152b931e 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 092/834] 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 44e69b3fd..ca09aaf9e 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 093/834] 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 4a93662db..7def0ce84 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -361,8 +361,8 @@ top: 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 a152b931e..87cfdbc2b 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 @@ moves_loop: // When in check, search starts here } -// 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 cb46fc8a9..3f5b9d4d9 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 554a81a57..7cc876fb9 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 8b0ffb0ca..aa4af012b 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 094/834] 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 87cfdbc2b..081418186 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 095/834] 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 ca09aaf9e..4c4497748 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 096/834] 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 081418186..a98468ec6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1083,12 +1083,7 @@ moves_loop: // When in check, search starts here // 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 097/834] 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 7b7aada31..483b84a87 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. - const vec_t pa = vec_mul_16(sum0a, sum1a); - const vec_t pb = vec_mul_16(sum0b, sum1b); + #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); - out[j] = vec_msb_pack_16(pa, pb); + 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 @@ -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 098/834] 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 a98468ec6..477667306 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 099/834] 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 4c4497748..7ca470af5 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 100/834] 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 477667306..563a5710f 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 36 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1340,9 +1340,9 @@ moves_loop: // When in check, search starts here // 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 101/834] 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 7ca470af5..75fe0f924 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 102/834] 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 75fe0f924..13a3f2117 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 103/834] 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 563a5710f..ed264f55c 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 104/834] 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 ed264f55c..d253601dd 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 105/834] 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 d253601dd..0dbc6a3a5 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 106/834] 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 1abb81b14..58f804204 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 d75b236ff..3a905dfab 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 107/834] 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 7bdcf5bc2..d3a262b54 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 45f38b013..5119b615f 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 e8da24aa9..3fc27223a 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 64a814cb4..91a8a96b0 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 58f804204..d48b75e1c 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 3a905dfab..99cbecfdd 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 de2c7eca6..db864fcd3 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 23f566630..f0ccfafcb 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 000000000..c04292daf --- /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 0dbc6a3a5..c074e3421 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 6e5b22bda..a61f253c0 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 8724cb49c..5893f4b6d 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 223652aec..102b22990 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 3f5b9d4d9..79274f525 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 7cc876fb9..3b09ec4e1 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 cb686a027..ab0dae394 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 55d580f97..bac62bb90 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 e1ffe5465..4819a68db 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 b575d1646..16d466961 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 108/834] 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 36b2b6f79..a232e115f 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 7def0ce84..55f9ca0e8 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 109/834] 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 13a3f2117..849b7bb68 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 110/834] 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 c074e3421..425782ebf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1341,18 +1341,19 @@ moves_loop: // When in check, search starts here // 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 111/834] 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 99cbecfdd..ec7f7b76c 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 c04292daf..03ee1fdf1 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 112/834] 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 425782ebf..5e9f64763 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1341,7 +1341,7 @@ moves_loop: // When in check, search starts here // 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 113/834] 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 5e9f64763..ec4ae79d5 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 a61f253c0..7cff10d55 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 5893f4b6d..71134ead6 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 f389e082d..f6ca298a8 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 8f1bb5639..8b763089a 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 114/834] 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 ec4ae79d5..22e82be8f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1066,11 +1066,9 @@ moves_loop: // When in check, search starts here { 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 115/834] 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 849b7bb68..666697ddd 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 116/834] 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 55f9ca0e8..6c41916cd 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 117/834] 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 4b3e91acf..4fab1a001 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 118/834] 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 ab0dae394..4b683116a 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 119/834] 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 22e82be8f..d72dbfa15 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1289,7 +1289,7 @@ moves_loop: // When in check, search starts here 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 120/834] 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 79274f525..f95170e94 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 121/834] 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 d72dbfa15..638af546d 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 122/834] 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 6c41916cd..b6828a30b 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 123/834] 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 d48b75e1c..a45becf5d 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 03ee1fdf1..644f212ee 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 124/834] 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 644f212ee..3c9c823aa 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 125/834] 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 a45becf5d..7a4473294 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 126/834] 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 | 154 ++++++++++++++++++++++++++++----------------------- 3 files changed, 107 insertions(+), 84 deletions(-) diff --git a/src/misc.cpp b/src/misc.cpp index 7a4473294..aa22e61f2 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 ec7f7b76c..5c0bde44e 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 3c9c823aa..0553309af 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()) + + // 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()) { - - 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); - } + 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)) @@ -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 127/834] 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 0553309af..ee84e1cf3 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 128/834] 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 638af546d..4086d50f1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1149,10 +1149,10 @@ moves_loop: // When in check, search starts here 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 129/834] 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 666697ddd..35bc9301a 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 130/834] 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 4086d50f1..f738530ad 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 131/834] 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 4fab1a001..bdef9ceb6 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 132/834] 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 f738530ad..514b7b7d9 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 133/834] 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 514b7b7d9..4dc7d3300 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 134/834] 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 b6828a30b..d33359075 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 135/834] 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 35bc9301a..eaf7ab5f9 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 136/834] 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 4dc7d3300..35de756ff 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 7cff10d55..01f7b8bdb 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 71134ead6..1b0fffc35 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 f6ca298a8..9de70fdc6 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 8b763089a..10207a8a7 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 137/834] 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 ee84e1cf3..967e24a66 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 35de756ff..84ca93f8e 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 @@ moves_loop: // When in check, search starts here + (*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 1b0fffc35..a36c2efb7 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 138/834] 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 84ca93f8e..a2a75af0c 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 139/834] 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 967e24a66..5934a0cd8 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 140/834] 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 5119b615f..29c4f879d 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 000000000..565b39b20 --- /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 000000000..ad7ca6025 --- /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 aa22e61f2..a8bb46ec3 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 5c0bde44e..557a4d8c5 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 db864fcd3..71c384ffc 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 f0ccfafcb..6ba3cfbab 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 5934a0cd8..a56d7142d 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 102b22990..7416271b4 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 f95170e94..f808106a6 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 3b09ec4e1..2dcfdd44b 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 141/834] 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 d33359075..52e8c526a 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 a2a75af0c..44da86836 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 142/834] 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 44da86836..4defbadb4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1103,7 +1103,7 @@ moves_loop: // When in check, search starts here } // 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 143/834] 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 eaf7ab5f9..064ea027b 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 144/834] 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 aa4af012b..10ad1fac9 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 145/834] 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 064ea027b..248b25933 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 71c384ffc..f7d2cc6ad 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 6ba3cfbab..152082552 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 a13c717c3..7585cce5a 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 146/834] 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 4defbadb4..c03fe7811 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 01f7b8bdb..a22d32004 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 a36c2efb7..0a33422ac 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 147/834] 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 f808106a6..56779b861 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 2dcfdd44b..974c7eb0c 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 148/834] 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 974c7eb0c..b2e8f582b 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 4b683116a..43b0e005a 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 149/834] 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 248b25933..afba6363b 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 150/834] 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 3fc27223a..6980dd834 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 a56d7142d..c170c178e 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 151/834] 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 cec41474b..55cb7df14 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 c03fe7811..6e03b62a9 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 a22d32004..d5210c2e0 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 152/834] 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 6e03b62a9..1c0bbc4d9 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 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 @@ moves_loop: // When in check, search starts here // 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 153/834] 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 1c0bbc4d9..15cc2d8fe 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 154/834] 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 e20e0d5d6..630edbf93 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 cf9cecda2..caffc916e 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 29c4f879d..742fd1958 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 15cc2d8fe..2cbc76774 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 0a33422ac..4acb9854b 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 3e5ebe5e6..f377e59ec 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 155/834] 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 afba6363b..fdf35eb17 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 156/834] 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 742fd1958..7142b9727 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 157/834] 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 7585cce5a..122610a74 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 f377e59ec..94c9b53ec 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 43b0e005a..42c69cdef 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 158/834] 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 2cbc76774..e0a49dba8 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 159/834] 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 e0a49dba8..7417a4b6f 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 160/834] 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 | 284 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 203 insertions(+), 81 deletions(-) diff --git a/src/numa.h b/src/numa.h index c170c178e..fd9abd4db 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; + + // 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 { - // 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()) @@ -115,47 +117,53 @@ 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)); + // 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)); - USHORT GroupCount = static_cast(numProcGroups); - const BOOL status = GetProcessGroupAffinity(GetCurrentProcess(), &GroupCount, GroupArray.get()); + USHORT* GroupArrayAligned = align_ptr_up(GroupArray.get()); - return std::make_pair(status, std::vector(GroupArray.get(), GroupArray.get() + GroupCount)); + const BOOL status = + GetProcessGroupAffinity(GetCurrentProcess(), &GroupCount, GroupArrayAligned); + + if (status == 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + break; + } + + if (status != 0) + { + return std::make_pair(status, + std::vector(GroupArrayAligned, GroupArrayAligned + GroupCount)); + } + } + + return std::make_pair(0, std::vector()); } -// 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. @@ -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) { - 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.isNewDeterminate = false; } + else + { + std::set cpus; - affinity.newApi = std::move(cpus); + 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); + } } } + // 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; - - const size_t procGroupIndex = groupAffinity[0]; - - uint64_t mask = static_cast(proc); - for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j) + // We detect the case when affinity is set to all processors and correctly + // leave affinity.oldApi as nullopt. + if (GetActiveProcessorGroupCount() != 1 || proc != sys) { - if (mask & (KAFFINITY(1) << j)) - cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j); - } + std::set cpus; - affinity.oldApi = std::move(cpus); + const size_t procGroupIndex = groupAffinity[0]; + + 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; + + 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 161/834] 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 fdf35eb17..1317a01ec 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 162/834] 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 7417a4b6f..06adc92a3 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 163/834] 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 a232e115f..6eefb56dd 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 06adc92a3..8ae12e68b 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 164/834] 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 56779b861..5a44759e4 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 165/834] 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 8ae12e68b..3dbdfd47c 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 7013a22b741b9fa937e0e027c4992c52b999283c Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Jun 2024 22:29:27 +0200 Subject: [PATCH 166/834] 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 6980dd834..233f62701 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 91a8a96b0..0d6f0f2b8 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 94c9b53ec..dfcd34689 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 079614db2..ed4738cdc 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 42c69cdef..75b7dfc77 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(); + engine.get_options().add_info_listener([](const std::optional& str) { + if (!str || (*str).empty()) + return; - options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); + // split all lines + auto ss = std::istringstream{*str}; - options["NumaPolicy"] << Option("auto", [this](const Option& o) { - engine.set_numa_config_from_option(o); - print_numa_config_information(); - print_thread_binding_information(); + for (std::string line; std::getline(ss, line, '\n');) + sync_cout << "info string " << line << sync_endl; }); - 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); }); - - 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 bac62bb90..122bcc40c 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 4819a68db..1cd028c99 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 16d466961..a47cc98de 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 167/834] 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 1317a01ec..4e895fd36 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 c8213ba0d047569141ed58f5eb86579d976b5614 Mon Sep 17 00:00:00 2001 From: Dubslow Date: Mon, 10 Jun 2024 18:03:36 -0500 Subject: [PATCH 168/834] 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 | 148 +++++++++++++++++++++++++------ src/tt.h | 119 ++++++++++--------------- tests/instrumented.sh | 7 +- 4 files changed, 265 insertions(+), 208 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3dbdfd47c..9c3f915db 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 5a44759e4..763e2c9b3 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 (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8) - & TranspositionTable::GENERATION_MASK; + return (GENERATION_CYCLE + generation8 - genBound8) & 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,32 +188,6 @@ void TranspositionTable::clear(ThreadPool& threads) { } -// 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); - 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]; - - // 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) - replace = &tte[i]; - - return found = false, 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. @@ -154,4 +202,46 @@ int TranspositionTable::hashfull() const { 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 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. +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) + // 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; + for (int i = 1; i < ClusterSize; ++i) + if (replace->depth8 - replace->relative_age(generation8) * 2 + > tte[i].depth8 - tte[i].relative_age(generation8) * 2) + replace = &tte[i]; + + return {false, replace->read(), TTWriter(replace)}; +} + + +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 b2e8f582b..1bece002c 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 -// -// 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 { +class ThreadPool; +struct TTEntry; +struct Cluster; - 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; +// 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. +// +// `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 4c63fc571..e77ee0dd2 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 3d92950859e1d45dad60d276dd7a78fbeb097bcb Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Tue, 11 Jun 2024 21:28:11 +0200 Subject: [PATCH 169/834] 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 9c3f915db..91b3c7894 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 e77ee0dd2..cb5a3a9f2 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 170/834] 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 a8bb46ec3..e97d58b93 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 557a4d8c5..bdc7c864d 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 75b7dfc77..4bc358d8a 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 122bcc40c..23745f96a 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 171/834] 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 000000000..dd81f334d --- /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 13d57f9ed..fcaa3f6b8 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 172/834] 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 91b3c7894..af0ab400f 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 173/834] 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 763e2c9b3..30104ab7d 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 174/834] 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 af0ab400f..8fb65fe76 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1134,7 +1134,8 @@ moves_loop: // When in check, search starts here // 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 175/834] 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 8fb65fe76..75eea2fd9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1283,11 +1283,17 @@ moves_loop: // When in check, search starts here 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 176/834] 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 000000000..088695e57 --- /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 fcaa3f6b8..8a1094fbd 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 177/834] 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 75eea2fd9..9b296e7fe 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 178/834] 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 9b296e7fe..562bdbf9c 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 179/834] 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 562bdbf9c..e63595c1b 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 180/834] 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 4bc358d8a..3c9177ee3 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 181/834] 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 e97d58b93..26dd3a289 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 182/834] 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 7416271b4..81ca39bbc 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 183/834] 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 e63595c1b..d04ba194d 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 184/834] 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 30104ab7d..4b55e53fd 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 185/834] 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 d04ba194d..81bb9a06c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1355,10 +1355,16 @@ moves_loop: // When in check, search starts here // 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 186/834] 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 81bb9a06c..da01f82f5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1087,8 +1087,8 @@ moves_loop: // When in check, search starts here // 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 187/834] 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 da01f82f5..b68b30268 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1157,7 +1157,7 @@ moves_loop: // When in check, search starts here // 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 188/834] 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 b68b30268..f561b1832 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1246,7 +1246,7 @@ moves_loop: // When in check, search starts here 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 189/834] 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 f561b1832..52eefdc94 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 190/834] 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 52eefdc94..2e8d47cf9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -994,7 +994,7 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 191/834] 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 2e8d47cf9..31278241c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -906,9 +906,8 @@ moves_loop: // When in check, search starts here // 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 192/834] 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 31278241c..188e81f4f 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 193/834] 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 b46ba0299..d374b1c07 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 154ed6529..3cfb87d06 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 188e81f4f..6368acc6b 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 194/834] 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 | 16 ++++++++++------ src/uci.cpp | 9 +++++---- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 233f62701..2bc0db6af 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(); - if (boundThreadsByNode.empty()) - return ""; - + auto boundThreadsByNode = get_bound_thread_count_by_numa_node(); std::stringstream ss; - ss << "NUMA Node Thread Binding: "; + + 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; diff --git a/src/uci.cpp b/src/uci.cpp index 3c9177ee3..9b60680d8 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 195/834] 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 26dd3a289..b68c12b97 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 bdc7c864d..0184ab88c 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 196/834] 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 3934ac2d6..5bf2a93e5 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 630edbf93..637cfc0d8 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 d949a5a76..d01ed41fe 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 3524d5e9f..5878adecb 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 088695e57..f0bca442f 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 0552a598c..f8898b1c9 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 dd81f334d..de65209fb 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 b75c06cfb..554592921 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 8a1094fbd..5589c7624 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 328c9cf94..836555e61 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 acf91a8f3..c91824a25 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 197/834] 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 5589c7624..1f87e061b 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 c91824a25..c5a2cd105 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 198/834] 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 6368acc6b..5eda1217b 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 @@ moves_loop: // When in check, search starts here 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 199/834] 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 5eda1217b..f74d4f87c 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 @@ moves_loop: // When in check, search starts here + 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 200/834] 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 f74d4f87c..023e51134 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 d5210c2e0..e8e33b1a8 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 722dc9d3e..fc2a092aa 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 e10950f4e..75a185857 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 201/834] 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 637cfc0d8..630edbf93 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 202/834] 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 4e895fd36..44890a361 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 203/834] 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 023e51134..0863013e8 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 204/834] 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 52e8c526a..05f57ae79 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 @@ top: 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 b81f76e18..8a2e0145d 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 0863013e8..cd29176eb 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 @@ moves_loop: // When in check, search starts here 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 e8e33b1a8..122cd549e 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 205/834] 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 cd29176eb..2fd38e50a 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 206/834] 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 0184ab88c..ce49a1f65 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 2fd38e50a..2bdcd25a3 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 @@ moves_loop: // When in check, search starts here // 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 207/834] 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 2bdcd25a3..576e1f904 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 208/834] 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 576e1f904..cb0340ece 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1158,8 +1158,7 @@ moves_loop: // When in check, search starts here // 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 209/834] 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 cb0340ece..ffe6e04b1 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 210/834] 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 ffe6e04b1..1bce9daad 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 211/834] 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 1bce9daad..9d0b5627c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1185,7 +1185,7 @@ moves_loop: // When in check, search starts here 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 212/834] 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 9d0b5627c..153eba188 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 213/834] 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 153eba188..9b0ea9df8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1373,8 +1373,7 @@ moves_loop: // When in check, search starts here + 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 214/834] 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 9b0ea9df8..3f1600474 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 @@ moves_loop: // When in check, search starts here // 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 215/834] 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 05f57ae79..f6f9f0dc8 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -297,9 +297,8 @@ top: [[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 @@ top: 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 8a2e0145d..2564f7301 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 216/834] 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 bdef9ceb6..047c4a56b 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 217/834] 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 3f1600474..d22761e3e 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 @@ moves_loop: // When in check, search starts here 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 218/834] 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 d22761e3e..ac0e59b5e 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 219/834] 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 565b39b20..1769a6617 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 220/834] 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 ac0e59b5e..3e6c56a69 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 221/834] 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 047c4a56b..4b5f447ee 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 222/834] 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 | 83 +++++++++++++++++++++++++++----------------------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/AUTHORS b/AUTHORS index 6eefb56dd..6957682f4 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 3e6c56a69..47c5dc882 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(move.is_ok()); + + if (move == excludedMove) + continue; + + // Check for legality + if (!pos.legal(move)) + continue; + + 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))); + + ss->currentMove = move; + ss->continuationHistory = + &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 + 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) { - assert(pos.capture_stage(move)); + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(captured)] + << stat_bonus(depth - 2); - 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))); - - ss->currentMove = move; - ss->continuationHistory = - &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 - 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; - } + // 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 223/834] 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 630edbf93..452c2f2a3 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 224/834] 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 | 27 +-- 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 | 268 ++++++++++++++-------------- 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 0d6f0f2b8..127f7d7c8 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 1769a6617..ae303c537 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 ad7ca6025..3155a5aab 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 -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 +// Memory aligned by page size, min alignment: 4096 bytes +void* aligned_large_pages_alloc(size_t size); +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) @@ -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 b68c12b97..664ab4b89 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 f6f9f0dc8..c21b14a90 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 483b84a87..ad0fb1b4d 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 fd9abd4db..3de8281d1 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 3cfb87d06..064dd5fa9 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 47c5dc882..d3d95eda8 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 @@ moves_loop: // When in check, search starts here // 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. + // 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. + // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here } -// 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 122cd549e..575967540 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 4acb9854b..f17fc4a53 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 10ad1fac9..8a9400bb8 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 225/834] 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 fc2a092aa..e2344fdab 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(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + TBFile file_dtz(code + ".rtbz"); // KRK -> KRvK + if (file_dtz.is_open()) + { + file_dtz.close(); + foundDTZFiles++; + } + + 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 226/834] 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 c21b14a90..d54bcbc74 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 @@ top: })) 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 @@ top: [[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 @@ top: 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 2564f7301..86a2a5834 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 d3d95eda8..a4da8cb00 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 575967540..65394bc07 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 227/834] 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 6957682f4..1ac40d879 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 2bc0db6af..41b19ac68 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 e2344fdab..9b24e700b 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 1cd028c99..455803cfe 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 228/834] 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 d54bcbc74..7619471f1 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 @@ top: ++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 @@ top: [[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 @@ top: 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 86a2a5834..c6a5d25ae 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 229/834] 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 7619471f1..55bacf6e7 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)); -} + if (pos.checkers()) + stage = EVASION_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); - - 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 c6a5d25ae..92e11de2b 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 230/834] 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 a4da8cb00..26bee2c13 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 231/834] 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 26bee2c13..d1e0b3210 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 232/834] 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 d1e0b3210..ebae94ef1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1035,7 +1035,7 @@ moves_loop: // When in check, search starts here { 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 233/834] 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 ebae94ef1..87310301f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1364,12 +1364,13 @@ moves_loop: // When in check, search starts here // 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 234/834] 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 87310301f..1d709749d 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 235/834] 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 1d709749d..3c6617ebd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1022,7 +1022,7 @@ moves_loop: // When in check, search starts here + 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 236/834] 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 44890a361..1cff6478f 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 237/834] 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 3c6617ebd..09918bfdc 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 238/834] 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 09918bfdc..e34aabba1 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 239/834] 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 e34aabba1..218d1ce4b 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 240/834] 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 218d1ce4b..c03a30f56 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 241/834] 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 1cff6478f..d0c553ffc 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 242/834] 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 c03a30f56..945f8b408 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1368,7 +1368,9 @@ moves_loop: // When in check, search starts here + 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 243/834] 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 de65209fb..dc8dff8d5 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 244/834] 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 945f8b408..435af4b24 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 245/834] 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 e6923067f..69b8fe6ae 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 5f650d2e3..f067f8808 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 55bacf6e7..813843287 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 @@ top: 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 435af4b24..fd9fa6da0 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 8a9400bb8..b12491d6c 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 246/834] 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 fd9fa6da0..f51a74999 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 @@ moves_loop: // When in check, search starts here // 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 247/834] 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 41b19ac68..498b7c3e7 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 f51a74999..0d9824b77 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 65394bc07..d42b5fba9 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 248/834] 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 0d9824b77..ca3465029 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -894,7 +894,8 @@ moves_loop: // When in check, search starts here // 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 249/834] 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 d0c553ffc..221ccde8b 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 250/834] 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 813843287..7bd0252c8 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 92e11de2b..671cbb9ce 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 ca3465029..233dc4f71 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -907,7 +907,7 @@ moves_loop: // When in check, search starts here 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 251/834] 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 233dc4f71..5e260247c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1206,6 +1206,10 @@ moves_loop: // When in check, search starts here (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 252/834] 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 4b5f447ee..558382433 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 253/834] 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 5e260247c..e2df475e9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1131,8 +1131,7 @@ moves_loop: // When in check, search starts here // 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 254/834] 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 e2df475e9..09004ba6e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1401,14 +1401,13 @@ moves_loop: // When in check, search starts here // 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 d42b5fba9..4872a58af 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 255/834] 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 7bd0252c8..bdc0e4aff 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 671cbb9ce..61f6368e6 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 09004ba6e..e83034561 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 256/834] 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 e83034561..f20bd4c93 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 4872a58af..bdb63ffd0 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 257/834] 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 498b7c3e7..81bb260bd 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 127f7d7c8..f3c783986 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 3de8281d1..20d352c91 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 f20bd4c93..beafd87dd 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 bdb63ffd0..0f635186a 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 f17fc4a53..b5d51594c 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 81ca39bbc..43e2e1423 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 258/834] 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 ad9167c05..59a6149f0 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 259/834] 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 beafd87dd..5f87f28fd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1133,7 +1133,7 @@ moves_loop: // When in check, search starts here // 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 260/834] 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 836555e61..8d209a4f9 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 261/834] Improve Comments for Pairwise Multiplication Optimization closes https://github.com/official-stockfish/Stockfish/pull/5524 no functional change --- src/nnue/nnue_feature_transformer.h | 78 ++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index ad0fb1b4d..2f74dcae2 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); + // 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) + 7; + #else + 6; + #endif + 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. - - #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 = From a75717ede14df4526a0990466e7b10d00e89c9ff Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 2 Aug 2024 13:23:44 -0700 Subject: [PATCH 262/834] 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 5f87f28fd..3c7fc2532 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1181,9 +1181,7 @@ moves_loop: // When in check, search starts here 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 263/834] 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 3c7fc2532..35f203b93 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1133,7 +1133,7 @@ moves_loop: // When in check, search starts here // 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 264/834] 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 558382433..c9041efbf 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 265/834] 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 35f203b93..ec8a9dd2d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1274,9 +1274,9 @@ moves_loop: // When in check, search starts here // 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 266/834] 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 ec8a9dd2d..34190596b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1173,7 +1173,7 @@ moves_loop: // When in check, search starts here // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. const bool doDeeperSearch = value > (bestValue + 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 267/834] 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 1ac40d879..3201e7a8a 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 34190596b..b062aa46d 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 268/834] 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 b062aa46d..c94d3c422 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1361,6 +1361,10 @@ moves_loop: // When in check, search starts here << 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 269/834] 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 c94d3c422..531fc42fe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1163,7 +1163,7 @@ moves_loop: // When in check, search starts here // 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 270/834] 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 20d352c91..db8359222 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 271/834] 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 531fc42fe..0b6756a7e 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here } // 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 272/834] 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 0b6756a7e..ad2c35e77 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1292,7 +1292,7 @@ moves_loop: // When in check, search starts here 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 273/834] 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 cb5a3a9f2..5fc6ca9a9 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 274/834] 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 9b60680d8..c94f8b914 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 275/834] 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 11636e840..3d8c52361 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 276/834] 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 664ab4b89..91cdbc4dc 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 277/834] 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 91cdbc4dc..664ab4b89 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 278/834] 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 ad2c35e77..9d950c0e7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1347,7 +1347,7 @@ moves_loop: // When in check, search starts here + 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 279/834] 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 61f6368e6..651091b08 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 9d950c0e7..bc85a5c3e 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 @@ moves_loop: // When in check, search starts here { 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 0f635186a..c9fe9e184 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 280/834] 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 bc85a5c3e..aab5c743c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1140,8 +1140,8 @@ moves_loop: // When in check, search starts here 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 281/834] 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 c5a2cd105..1067f6e76 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 282/834] 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 8d209a4f9..a826e6f06 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 000000000..168fbad66 --- /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 7142b9727..042d9479c 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 283/834] 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 52b123cbd..25da319d5 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 284/834] 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 aab5c743c..1ed849f2a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1148,10 +1148,9 @@ moves_loop: // When in check, search starts here 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 285/834] 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 81bb260bd..b5cc3f832 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 f3c783986..efab1c6af 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 286/834] 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 2f74dcae2..fa180678d 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]; + FeatureSet::IndexList removed, added; - for (int i = N - 1; i >= 0; --i) - { - (states_to_update[i]->*accPtr).computed[Perspective] = true; + 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); - const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; + StateInfo* next = CurrentOnly ? pos.state() : computed->next; + assert(!(next->*accPtr).computed[Perspective]); - 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; - - // 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]); - for (IndexType i = 0; i < N; ++i) + // Difference calculation for the deactivated features + for (const auto index : removed) { - // 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_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) + 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]); - for (IndexType i = 0; i < N; ++i) + // Difference calculation for the deactivated features + for (const auto index : removed) { - // 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_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) + 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)); + 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) - (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = - (st->*accPtr).psqtAccumulation[Perspective][k]; + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (next->*accPtr).psqtAccumulation[Perspective][i] -= + psqtWeights[index * PSQTBuckets + i]; + } - st = states_to_update[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 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]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - (st->*accPtr).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->*accPtr).accumulation[Perspective][j] += weights[offset + j]; - - 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); + StateInfo* oldest = 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()}; - - 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 d374b1c07..df95ffef3 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 064dd5fa9..6cac17319 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 287/834] 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 1ed849f2a..d26f43dbc 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 288/834] 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 d26f43dbc..ac0b9c6d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1159,7 +1159,7 @@ moves_loop: // When in check, search starts here 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 289/834] 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 168fbad66..0bc57a19e 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 290/834] 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 554592921..946a81cec 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 8981efcaf..2fc80d487 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 000000000..a3747d4e9 --- /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 5fc6ca9a9..000000000 --- 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 000000000..d51ca89ac --- /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 291/834] 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 ac0b9c6d9..4f6e75111 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here && 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 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 @@ moves_loop: // When in check, search starts here // 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 292/834] 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 4f6e75111..135db0cee 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1365,7 +1365,7 @@ moves_loop: // When in check, search starts here } // 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 293/834] 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 bdc0e4aff..63d9e8b1a 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 651091b08..f66cdadf5 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 135db0cee..3c6da163b 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 @@ moves_loop: // When in check, search starts here (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 @@ moves_loop: // When in check, search starts here // 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 c9fe9e184..b06c7c948 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 294/834] 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 c842ca127..a8b4e5f44 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 f66cdadf5..13b9635bb 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 df95ffef3..f596b0153 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 6cac17319..888612da7 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 3c6da163b..199b93554 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 @@ moves_loop: // When in check, search starts here -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 b06c7c948..d7a909a82 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 545e750fe..c1532c20c 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 295/834] 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 3201e7a8a..c0a8beebc 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 fb124021a..dfbfac0ea 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 042d9479c..6cb778a68 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 296/834] 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 63d9e8b1a..f4ef0e549 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 297/834] 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 199b93554..229aef9b2 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 @@ moves_loop: // When in check, search starts here { 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 298/834] 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 229aef9b2..d87a6b9a0 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 299/834] 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 | 49 +++--- 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, 663 insertions(+), 52 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 3622ac8af..35ad3c180 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 b1eba40f3..eb3a52d89 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 b5cc3f832..85c840993 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 efab1c6af..257826935 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 ae303c537..47c901b4e 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 3155a5aab..eaf0261aa 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 664ab4b89..10c86b7a6 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 ce49a1f65..21093769b 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 f7d2cc6ad..a8e901a0d 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."; + 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); + } - 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); } - 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 152082552..95253595a 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 db8359222..1063721e3 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 4b55e53fd..507507539 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 1bece002c..e7bb5c452 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 c94f8b914..cfb34db79 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 23745f96a..6adf74cb8 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 300/834] 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 d87a6b9a0..7d84bd388 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1201,8 +1201,8 @@ moves_loop: // When in check, search starts here 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 301/834] 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 13b9635bb..c5e565fe4 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 7d84bd388..4d581a850 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 302/834] 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 4d581a850..a206cddab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1157,7 +1157,7 @@ moves_loop: // When in check, search starts here // 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 303/834] 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 221ccde8b..087765e36 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 304/834] 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 087765e36..b1c7283e3 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 305/834] 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 f4ef0e549..1d1aef0f3 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 c5e565fe4..8deefd140 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 a206cddab..0ed7b6a76 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 @@ moves_loop: // When in check, search starts here (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 @@ moves_loop: // When in check, search starts here // 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 d7a909a82..0761328a7 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 306/834] 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 0ed7b6a76..d79b452d9 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 307/834] 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 c0a8beebc..725b35690 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 8deefd140..9a68aaa1d 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 d79b452d9..c55118ece 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here && !(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 @@ moves_loop: // When in check, search starts here << 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 0761328a7..a407e1058 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 308/834] 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 b1c7283e3..802913a04 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 309/834] 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 cfb34db79..8388cad8c 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 310/834] 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 c55118ece..34fb5a805 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1213,8 +1213,7 @@ moves_loop: // When in check, search starts here 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 311/834] 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 34fb5a805..5598b5ffb 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 312/834] 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 a8b4e5f44..deda6da2a 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 cdff4c759..c4bf18b53 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 313/834] 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 725b35690..31a64c17e 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 dfbfac0ea..ed5fc9af0 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 6cb778a68..15066781b 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 314/834] 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 5598b5ffb..647bae764 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 @@ moves_loop: // When in check, search starts here // (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 @@ moves_loop: // When in check, search starts here 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 a407e1058..2342d9e93 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 315/834] 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 f596b0153..bab7a1fca 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 316/834] 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 1d1aef0f3..064951893 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 9a68aaa1d..5c312531e 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 647bae764..6e513b458 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 317/834] 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 c9041efbf..9bd436b58 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 318/834] 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 802913a04..7c7b54a4f 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 319/834] 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 6e513b458..75a8c9633 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 320/834] 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 5c312531e..6ad13397a 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 75a8c9633..568e147c8 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 @@ moves_loop: // When in check, search starts here -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 2342d9e93..b599da110 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 321/834] 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 568e147c8..c398b7d25 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 322/834] 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 c398b7d25..c78acb6c4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1210,7 +1210,7 @@ moves_loop: // When in check, search starts here 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 323/834] 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 15066781b..4307b7c74 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 324/834] 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 c78acb6c4..8a7bd8109 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 325/834] 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 6ad13397a..dff09f79c 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; +template +struct CorrHistTypedef { + using type = 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::type, NOT_USED, PIECE_NB, SQUARE_NB>; +}; -// NonPawnCorrectionHistory is addressed by color and non-pawn material positions -using NonPawnCorrectionHistory = - Stats; - -// 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 b599da110..751a39848 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 326/834] 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 8a7bd8109..c7a8c28b4 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 327/834] 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 064951893..2a1fb8373 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 @@ top: 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 dff09f79c..f8f84d024 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 c7a8c28b4..4864057c1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -942,12 +942,11 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 328/834] 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 507507539..75689562d 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 329/834] 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 4307b7c74..e7f8ce556 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 000000000..8d14a7a7c --- /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 2a1fb8373..720f2e031 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 f8f84d024..0278b70ec 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 4864057c1..d6914da0b 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 751a39848..b618855b9 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 330/834] 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 d6914da0b..1b9b745ca 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 331/834] 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 1b9b745ca..5c6a62c8b 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 332/834] 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 b8dcf1e48..b92901e4a 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 333/834] 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 5c6a62c8b..c807f1bdf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,7 +999,7 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here { // 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 334/834] 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 c807f1bdf..2a2331cb4 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 335/834] 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 2a2331cb4..5fdfdeb2d 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 336/834] 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 9bd436b58..4604321d3 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 337/834] 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 7c7b54a4f..bc86a7420 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 338/834] 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 a826e6f06..b97aaa29c 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 339/834] 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 5fdfdeb2d..94b20c85b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1181,9 +1181,14 @@ moves_loop: // When in check, search starts here 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 340/834] 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 94b20c85b..50b31d2ad 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1184,7 +1184,7 @@ moves_loop: // When in check, search starts here 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 341/834] 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 50b31d2ad..f1942a4f3 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 342/834] 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 f1942a4f3..93036398d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1425,9 +1425,9 @@ moves_loop: // When in check, search starts here 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 343/834] 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 93036398d..213bbdab7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1184,7 +1184,7 @@ moves_loop: // When in check, search starts here 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 344/834] 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 213bbdab7..ace6385d7 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 345/834] 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 ace6385d7..560b031ba 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 @@ moves_loop: // When in check, search starts here 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 346/834] 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 a3747d4e9..db5ec8e08 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -352,10 +352,10 @@ class TestInteractive(metaclass=OrderedClassMembers): 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 347/834] 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 720f2e031..df722eceb 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 348/834] 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 560b031ba..45f0f10fc 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here << 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 b618855b9..7868f6078 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 349/834] 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 122610a74..a2bece21d 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 292f53406..179796d20 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 45f0f10fc..2e904f40d 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && 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 b5d51594c..5f73771ef 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 b12491d6c..564446011 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 350/834] 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 2e904f40d..ea43017e5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1042,7 +1042,9 @@ moves_loop: // When in check, search starts here { 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 351/834] 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 ea43017e5..5209bd073 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 352/834] 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 5209bd073..8ebbef5b1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1434,7 +1434,8 @@ moves_loop: // When in check, search starts here && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { - const auto m = (ss - 1)->currentMove; + 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 @@ moves_loop: // When in check, search starts here 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 353/834] 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 31a64c17e..ddc53ec02 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 df722eceb..96f031717 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 @@ top: 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 @@ top: [[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 @@ top: [[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 @@ top: case BAD_QUIET : if (!skipQuiets) - return select([]() { return true; }); + return select([]() { return true; }); return Move::none(); @@ -307,17 +299,16 @@ top: 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 0278b70ec..ab4e832fd 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 354/834] 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 8ebbef5b1..149222fc7 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 7868f6078..b618855b9 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 355/834] 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 25da319d5..621f1d130 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 356/834] 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 149222fc7..3ce30b813 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1180,7 +1180,10 @@ moves_loop: // When in check, search starts here 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 357/834] 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 bab7a1fca..1b1c0269f 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 358/834] 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 3ce30b813..e352c96e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1041,9 +1041,7 @@ moves_loop: // When in check, search starts here { 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 359/834] 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 | 78 ++++++++++++++++++++++++++------------------------ src/search.h | 5 ++++ 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/history.h b/src/history.h index 8d14a7a7c..17ab3481c 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 e352c96e3..9a0a9d046 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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; - if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus; + workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); + if (ss->ply < LOW_PLY_HISTORY_SIZE) + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 874 / 1024; + + 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 b618855b9..e9a7943d1 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 360/834] 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 18718b95f..3f662e15d 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"); + * @note By default this is "g". * - * // Now you have the following symbols: - * // const unsigned char gFooData[]; - * // const unsigned char *const gFooEnd; - * // const unsigned int gFooSize; - * @endcode - * - * 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"); + * @note By default this is INCBIN_STYLE_CAMEL * - * // Now you have the following symbols: - * // const unsigned char FooData[]; - * // const unsigned char *const FooEnd; - * // const unsigned int FooSize; - * @endcode - * - * 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 + +#endif \ No newline at end of file diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index a8e901a0d..0a4452f66 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 361/834] 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 0a4452f66..01cf2516d 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 362/834] 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 9a0a9d046..36c0b8c03 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 363/834] 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 85c840993..30ad6ba0f 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 257826935..2d17fb31d 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 36c0b8c03..f42ecf711 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 e9a7943d1..c65267a1d 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 364/834] 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 10c86b7a6..7db8bd59e 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 365/834] 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 f42ecf711..4bebd9850 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 c65267a1d..945c66c1c 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 366/834] 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 946a81cec..950435f30 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 367/834] 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 4bebd9850..02d7b6777 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 368/834] 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 02d7b6777..f184a3539 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 @@ moves_loop: // When in check, search starts here // 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 369/834] 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 f184a3539..1f19c74db 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 370/834] 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 1f19c74db..192b837c9 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 371/834] 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 1b1c0269f..2bc0aa650 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 888612da7..a339471d2 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 372/834] 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 192b837c9..1fe89c4ac 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1198,7 +1198,7 @@ moves_loop: // When in check, search starts here // 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 373/834] 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 1fe89c4ac..44394c1d6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1198,7 +1198,8 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 374/834] 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 01cf2516d..9191148bb 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 fa180678d..556a114af 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 375/834] 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 | 58 +++++++++++++++++++++++++++++++++++------------- src/movepick.cpp | 1 - 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/history.h b/src/history.h index 17ab3481c..2ec2b7911 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; + + 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 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); + 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 96f031717..540024b7d 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 376/834] 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 e7f8ce556..39cfce8bf 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 35ad3c180..a9f70f10d 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 eb3a52d89..d6bdc275d 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 deda6da2a..8798c5701 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 c4bf18b53..6f9cca0bd 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 30ad6ba0f..d835fc8e4 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 2d17fb31d..d26844f4c 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 bc86a7420..9a523ae44 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 4604321d3..aad358321 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 2ec2b7911..edf7fdc67 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 a6a3d1c4e..e262f3875 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 47c901b4e..92cb650f2 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 eaf0261aa..4dc232878 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 7db8bd59e..06a8c624a 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 21093769b..48c49bafb 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 69b8fe6ae..8653a828f 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 f067f8808..7c6cceb7c 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 540024b7d..4c5cc11de 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 ab4e832fd..71078bdcf 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 71782a7b7..5bb0296e2 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 963497047..ca940c54e 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 59a6149f0..f5c640fb9 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 0ac557aba..cbeb507f0 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 2ee378ad8..2ad5a86a1 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 55cb7df14..70ca68a0c 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 9c20df9d6..d14f1e0ab 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 9191148bb..b96625a79 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 95253595a..f99fa1182 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 b92901e4a..0d3d94135 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 7f73f87fd..0c9f097dc 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 4bc3408f1..f21a8dec7 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 556a114af..6192cd51e 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 a2bece21d..1e2690503 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 27a93f884..a7647f846 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 1063721e3..5cadbc726 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 e907742da..f0d38ab72 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 2bc0aa650..60b7d7d3f 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 a339471d2..7dc83f251 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 179796d20..561bc23c4 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 2eb40f7e0..eda90af35 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 44394c1d6..2453679ec 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 945c66c1c..dee759410 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 9b24e700b..120e64885 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 75a185857..c34338fe3 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 5f73771ef..43ba7d9c0 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 43e2e1423..912d44335 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 1d9a834f6..fb4b2ec97 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 9de70fdc6..d0b0d0a94 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 10207a8a7..e8602bb7c 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 75689562d..50f5ca45a 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 e7bb5c452..f0936fd28 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 dfcd34689..aff96c8cb 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 ed4738cdc..4dab6bf45 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 564446011..6465dfd6b 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 8388cad8c..4b8e8b7f5 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 6adf74cb8..5c1c07f7b 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 455803cfe..56cf41edc 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 a47cc98de..c9f6787d3 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 377/834] 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 2453679ec..f53c116c8 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 378/834] 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 f53c116c8..d6748c769 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1160,7 +1160,7 @@ moves_loop: // When in check, search starts here 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 379/834] 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 ddc53ec02..f6468c561 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 9a523ae44..4fce86e3a 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 380/834] 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 d6748c769..226506616 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 381/834] 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 | 227 ++++++++++++++-------------- 1 file changed, 116 insertions(+), 111 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 6192cd51e..b047f62c4 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) + 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); + 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; + 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; + // 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; + } - 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 382/834] 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 edf7fdc67..15095cd0b 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 48c49bafb..81c7b17fe 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 4c5cc11de..844861694 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 226506616..d937c399a 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 383/834] 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 d937c399a..42a7bb971 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1408,10 +1408,6 @@ moves_loop: // When in check, search starts here << 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 384/834] 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 42a7bb971..4a72a4162 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 385/834] 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 000000000..dfdb8b771 --- /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 000000000..dfdb8b771 --- /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 386/834] 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 d51ca89ac..bc1f6b15b 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -150,6 +150,7 @@ class MiniTestFramework: 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 @@ class MiniTestFramework: 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 @@ class MiniTestFramework: 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 @@ class Stockfish: 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 @@ class Stockfish: 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 @@ class Stockfish: 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 387/834] 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 4a72a4162..61425acdc 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 388/834] 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 b047f62c4..8649d9521 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 389/834] 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 61425acdc..47e7f4bc3 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 390/834] 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 60b7d7d3f..9d883c92b 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 7dc83f251..7fdaf84d5 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 47e7f4bc3..d35121304 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { - const auto m = (ss - 1)->currentMove; - 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 391/834] 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 d35121304..1e2497f70 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 392/834] 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 1e2497f70..9ee9fb791 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here && 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 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 @@ moves_loop: // When in check, search starts here { // 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 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (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 @@ moves_loop: // When in check, search starts here || (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 393/834] 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 9ee9fb791..f20e4f023 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1070,11 +1070,16 @@ moves_loop: // When in check, search starts here 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 394/834] 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 f20e4f023..c27d93d25 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1056,7 +1056,7 @@ moves_loop: // When in check, search starts here // 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 395/834] 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 9d883c92b..49f520e01 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 7fdaf84d5..0d49a60a9 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 c27d93d25..7f064bfb3 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here &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 396/834] 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 50f5ca45a..5d8457611 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 f0936fd28..065380ca8 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 397/834] 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 7f064bfb3..3776e84b0 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 398/834] 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 caffc916e..0b6fbce0d 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 399/834] 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 3776e84b0..66c5ff439 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 400/834] 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 8649d9521..14fdecd72 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 401/834] 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 844861694..bee5ef5b7 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 402/834] 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 66c5ff439..eea48a6f7 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 @@ moves_loop: // When in check, search starts here // 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 dee759410..3983e0f33 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 403/834] 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 c6563eadf..44e0596ea 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 452c2f2a3..ab6b4350e 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 b97aaa29c..57d0d53f0 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 404/834] 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 eea48a6f7..a080daf30 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 405/834] 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 ed5fc9af0..132bd6f48 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 406/834] 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 d0b0d0a94..2aaf96680 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 407/834] 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 a080daf30..5b4415462 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 @@ moves_loop: // When in check, search starts here // 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 408/834] 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 bee5ef5b7..c762e7e45 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 5b4415462..983f387a8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -923,12 +923,9 @@ moves_loop: // When in check, search starts here && !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 409/834] 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 cbeb507f0..248e19dd0 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 410/834] 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 983f387a8..249ac56bc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -978,6 +978,10 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); + // Decrease reduction if position is or has been on the PV (~7 Elo) + 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 @@ moves_loop: // When in check, search starts here // 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 411/834] 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 249ac56bc..1d2604c26 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 2aaf96680..d073a84a9 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 412/834] 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 1d2604c26..d5e86ca94 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 413/834] Replace depth increase condition with !opponentWorsening Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 220544 W: 57417 L: 57399 D: 105728 Ptnml(0-2): 816, 26554, 55540, 26520, 842 https://tests.stockfishchess.org/tests/view/678970e38082388fa0cbfe02 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 132600 W: 33868 L: 33760 D: 64972 Ptnml(0-2): 126, 14770, 36390, 14898, 116 https://tests.stockfishchess.org/tests/view/678accabc00c743bc9e9fc7f closes https://github.com/official-stockfish/Stockfish/pull/5798 bench 1632964 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d5e86ca94..e99d5d3fc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -781,10 +781,8 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; - if (priorReduction >= 3 && ss->staticEval + (ss - 1)->staticEval < 0) - { + if (priorReduction >= 3 && !opponentWorsening) depth++; - } // Step 7. Razoring (~1 Elo) // If eval is really low, skip search entirely and return the qsearch value. From 62ecdfe82cb33f5d0d3394c07bac2c7be97ff84b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 12 Jan 2025 12:53:08 -0800 Subject: [PATCH 414/834] simplify accumulator updates After #5759 accumulator updates are strictly on a per-move basis. Therefore, the generic code for updating multiple moves at once is no longer needed. Passed Non-regression STC: LLR: 3.00 (-2.94,2.94) <-1.75,0.25> Total: 81696 W: 21204 L: 21039 D: 39453 Ptnml(0-2): 210, 8431, 23416, 8566, 225 https://tests.stockfishchess.org/tests/view/67823a24a31c4c13e83518a8 closes https://github.com/official-stockfish/Stockfish/pull/5760 no functional change --- src/nnue/nnue_feature_transformer.h | 193 ++++++++++++---------------- 1 file changed, 81 insertions(+), 112 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 14fdecd72..7a37cda82 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -472,25 +472,20 @@ class FeatureTransformer { return st; } - // Computes the accumulator of the next position. + // Given a computed accumulator, computes the accumulator of the next position. template void update_accumulator_incremental(const Position& pos, StateInfo* computed) const { assert((computed->*accPtr).computed[Perspective]); assert(computed->next != nullptr); -#ifdef VECTOR - // Gcc-10.2 unnecessarily spills AVX2 registers if this array - // is defined in the VECTOR code below, once in each branch. - vec_t acc[Tiling::NumRegs]; - psqt_vec_t psqt[Tiling::NumPsqtRegs]; -#endif - const Square ksq = pos.square(Perspective); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the // feature set's update cost calculation to be correct and never allow // updates with more added/removed features than MaxActiveDimensions. + // In this case, the maximum size of both feature addition and removal + // is 2, since we are incrementally updating one move at a time. FeatureSet::IndexList removed, added; FeatureSet::append_changed_indices(ksq, computed->next->dirtyPiece, removed, added); @@ -498,51 +493,76 @@ class FeatureTransformer { StateInfo* next = computed->next; assert(!(next->*accPtr).computed[Perspective]); -#ifdef VECTOR - if ((removed.size() == 1 || removed.size() == 2) && added.size() == 1) + if (removed.size() == 0 && added.size() == 0) { + std::memcpy((next->*accPtr).accumulation[Perspective], + (computed->*accPtr).accumulation[Perspective], + HalfDimensions * sizeof(BiasType)); + std::memcpy((next->*accPtr).psqtAccumulation[Perspective], + (computed->*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + } + else + { + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + assert(added.size() <= removed.size()); + +#ifdef VECTOR auto* accIn = reinterpret_cast(&(computed->*accPtr).accumulation[Perspective][0]); auto* accOut = reinterpret_cast(&(next->*accPtr).accumulation[Perspective][0]); + const IndexType offsetA0 = HalfDimensions * added[0]; + auto* columnA0 = reinterpret_cast(&weights[offsetA0]); const IndexType offsetR0 = HalfDimensions * removed[0]; auto* columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0]; - auto* columnA = reinterpret_cast(&weights[offsetA]); if (removed.size() == 1) { for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA[i]); + accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); } - else + else if (added.size() == 1) { const IndexType offsetR1 = HalfDimensions * removed[1]; auto* columnR1 = reinterpret_cast(&weights[offsetR1]); for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA[i]), + accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), vec_add_16(columnR0[i], columnR1[i])); } + else + { + const IndexType offsetA1 = HalfDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&weights[offsetA1]); + const IndexType offsetR1 = HalfDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&weights[offsetR1]); + + for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = + vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), + vec_add_16(columnR0[i], columnR1[i]))); + } auto* accPsqtIn = reinterpret_cast( &(computed->*accPtr).psqtAccumulation[Perspective][0]); auto* accPsqtOut = reinterpret_cast(&(next->*accPtr).psqtAccumulation[Perspective][0]); + const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; + auto* columnPsqtA0 = reinterpret_cast(&psqtWeights[offsetPsqtA0]); const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; auto* columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - const IndexType offsetPsqtA = PSQTBuckets * added[0]; - auto* columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); if (removed.size() == 1) { for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) accPsqtOut[i] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), - columnPsqtA[i]); + columnPsqtA0[i]); } - else + else if (added.size() == 1) { const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; auto* columnPsqtR1 = @@ -551,110 +571,58 @@ class FeatureTransformer { for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) accPsqtOut[i] = - vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA[i]), + vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); } - } - else - { - for (IndexType i = 0; i < HalfDimensions / Tiling::TileHeight; ++i) + else { - // Load accumulator - auto* accTileIn = reinterpret_cast( - &(computed->*accPtr).accumulation[Perspective][i * Tiling::TileHeight]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - acc[j] = vec_load(&accTileIn[j]); + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&psqtWeights[offsetPsqtA1]); + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&psqtWeights[offsetPsqtR1]); - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index + i * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - acc[j] = vec_sub_16(acc[j], column[j]); - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index + i * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - acc[j] = vec_add_16(acc[j], column[j]); - } - - // Store accumulator - auto* accTileOut = reinterpret_cast( - &(next->*accPtr).accumulation[Perspective][i * Tiling::TileHeight]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - vec_store(&accTileOut[j], acc[j]); + for (std::size_t i = 0; + i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) + accPsqtOut[i] = vec_add_psqt_32( + accPsqtIn[i], + vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); } - - for (IndexType i = 0; i < PSQTBuckets / Tiling::PsqtTileHeight; ++i) - { - // Load accumulator - auto* accTilePsqtIn = reinterpret_cast( - &(computed->*accPtr).psqtAccumulation[Perspective][i * Tiling::PsqtTileHeight]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - psqt[j] = vec_load_psqt(&accTilePsqtIn[j]); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = PSQTBuckets * index + i * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - psqt[j] = vec_sub_psqt_32(psqt[j], columnPsqt[j]); - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = PSQTBuckets * index + i * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - psqt[j] = vec_add_psqt_32(psqt[j], columnPsqt[j]); - } - - // Store accumulator - auto* accTilePsqtOut = reinterpret_cast( - &(next->*accPtr).psqtAccumulation[Perspective][i * Tiling::PsqtTileHeight]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - vec_store_psqt(&accTilePsqtOut[j], psqt[j]); - } - } #else - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); + std::memcpy((next->*accPtr).accumulation[Perspective], + (computed->*accPtr).accumulation[Perspective], + HalfDimensions * sizeof(BiasType)); + std::memcpy((next->*accPtr).psqtAccumulation[Perspective], + (computed->*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; + // Difference calculation for the deactivated features + for (const auto index : removed) + { + const IndexType offset = HalfDimensions * index; + for (IndexType i = 0; i < HalfDimensions; ++i) + (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] -= - psqtWeights[index * PSQTBuckets + i]; - } + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (next->*accPtr).psqtAccumulation[Perspective][i] -= + psqtWeights[index * PSQTBuckets + i]; + } - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] += weights[offset + i]; + // Difference calculation for the activated features + for (const auto index : added) + { + const IndexType offset = HalfDimensions * index; + for (IndexType i = 0; i < HalfDimensions; ++i) + (next->*accPtr).accumulation[Perspective][i] += weights[offset + i]; - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] += - psqtWeights[index * PSQTBuckets + i]; - } + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (next->*accPtr).psqtAccumulation[Perspective][i] += + psqtWeights[index * PSQTBuckets + i]; + } #endif + } (next->*accPtr).computed[Perspective] = true; @@ -662,6 +630,7 @@ class FeatureTransformer { update_accumulator_incremental(pos, next); } + template void update_accumulator_refresh_cache(const Position& pos, AccumulatorCaches::Cache* cache) const { From 18b3465a8668f46b7313c7ad6b4dedda7b4709bf Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 18 Jan 2025 14:26:41 -0800 Subject: [PATCH 415/834] Generate lookup indices at compile time. Credit to @Disservin: the constexpr lsb is just the De Bruijn bitscan with the xor from the cpw https://www.chessprogramming.org/BitScan closes https://github.com/official-stockfish/Stockfish/pull/5801 No functional change --- .../layers/affine_transform_sparse_input.h | 126 +++++------------- 1 file changed, 34 insertions(+), 92 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 248e19dd0..be5e30b5e 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -38,99 +38,41 @@ namespace Stockfish::Eval::NNUE::Layers { #if (USE_SSSE3 | (USE_NEON >= 8)) +static constexpr int lsb_index64[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, + 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, + 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63}; + +constexpr int constexpr_lsb(uint64_t bb) { + assert(bb != 0); + constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL; + return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; +} + +alignas(CacheLineSize) static constexpr struct OffsetIndices { #if (USE_SSE41) -alignas(CacheLineSize) static constexpr std::uint8_t + std::uint8_t offset_indices[256][8]; #else -alignas(CacheLineSize) static constexpr std::uint16_t + std::uint16_t offset_indices[256][8]; #endif - lookup_indices[256][8] = { - {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0}, - {0, 1, 0, 0, 0, 0, 0, 0}, {2, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0}, - {1, 2, 0, 0, 0, 0, 0, 0}, {0, 1, 2, 0, 0, 0, 0, 0}, {3, 0, 0, 0, 0, 0, 0, 0}, - {0, 3, 0, 0, 0, 0, 0, 0}, {1, 3, 0, 0, 0, 0, 0, 0}, {0, 1, 3, 0, 0, 0, 0, 0}, - {2, 3, 0, 0, 0, 0, 0, 0}, {0, 2, 3, 0, 0, 0, 0, 0}, {1, 2, 3, 0, 0, 0, 0, 0}, - {0, 1, 2, 3, 0, 0, 0, 0}, {4, 0, 0, 0, 0, 0, 0, 0}, {0, 4, 0, 0, 0, 0, 0, 0}, - {1, 4, 0, 0, 0, 0, 0, 0}, {0, 1, 4, 0, 0, 0, 0, 0}, {2, 4, 0, 0, 0, 0, 0, 0}, - {0, 2, 4, 0, 0, 0, 0, 0}, {1, 2, 4, 0, 0, 0, 0, 0}, {0, 1, 2, 4, 0, 0, 0, 0}, - {3, 4, 0, 0, 0, 0, 0, 0}, {0, 3, 4, 0, 0, 0, 0, 0}, {1, 3, 4, 0, 0, 0, 0, 0}, - {0, 1, 3, 4, 0, 0, 0, 0}, {2, 3, 4, 0, 0, 0, 0, 0}, {0, 2, 3, 4, 0, 0, 0, 0}, - {1, 2, 3, 4, 0, 0, 0, 0}, {0, 1, 2, 3, 4, 0, 0, 0}, {5, 0, 0, 0, 0, 0, 0, 0}, - {0, 5, 0, 0, 0, 0, 0, 0}, {1, 5, 0, 0, 0, 0, 0, 0}, {0, 1, 5, 0, 0, 0, 0, 0}, - {2, 5, 0, 0, 0, 0, 0, 0}, {0, 2, 5, 0, 0, 0, 0, 0}, {1, 2, 5, 0, 0, 0, 0, 0}, - {0, 1, 2, 5, 0, 0, 0, 0}, {3, 5, 0, 0, 0, 0, 0, 0}, {0, 3, 5, 0, 0, 0, 0, 0}, - {1, 3, 5, 0, 0, 0, 0, 0}, {0, 1, 3, 5, 0, 0, 0, 0}, {2, 3, 5, 0, 0, 0, 0, 0}, - {0, 2, 3, 5, 0, 0, 0, 0}, {1, 2, 3, 5, 0, 0, 0, 0}, {0, 1, 2, 3, 5, 0, 0, 0}, - {4, 5, 0, 0, 0, 0, 0, 0}, {0, 4, 5, 0, 0, 0, 0, 0}, {1, 4, 5, 0, 0, 0, 0, 0}, - {0, 1, 4, 5, 0, 0, 0, 0}, {2, 4, 5, 0, 0, 0, 0, 0}, {0, 2, 4, 5, 0, 0, 0, 0}, - {1, 2, 4, 5, 0, 0, 0, 0}, {0, 1, 2, 4, 5, 0, 0, 0}, {3, 4, 5, 0, 0, 0, 0, 0}, - {0, 3, 4, 5, 0, 0, 0, 0}, {1, 3, 4, 5, 0, 0, 0, 0}, {0, 1, 3, 4, 5, 0, 0, 0}, - {2, 3, 4, 5, 0, 0, 0, 0}, {0, 2, 3, 4, 5, 0, 0, 0}, {1, 2, 3, 4, 5, 0, 0, 0}, - {0, 1, 2, 3, 4, 5, 0, 0}, {6, 0, 0, 0, 0, 0, 0, 0}, {0, 6, 0, 0, 0, 0, 0, 0}, - {1, 6, 0, 0, 0, 0, 0, 0}, {0, 1, 6, 0, 0, 0, 0, 0}, {2, 6, 0, 0, 0, 0, 0, 0}, - {0, 2, 6, 0, 0, 0, 0, 0}, {1, 2, 6, 0, 0, 0, 0, 0}, {0, 1, 2, 6, 0, 0, 0, 0}, - {3, 6, 0, 0, 0, 0, 0, 0}, {0, 3, 6, 0, 0, 0, 0, 0}, {1, 3, 6, 0, 0, 0, 0, 0}, - {0, 1, 3, 6, 0, 0, 0, 0}, {2, 3, 6, 0, 0, 0, 0, 0}, {0, 2, 3, 6, 0, 0, 0, 0}, - {1, 2, 3, 6, 0, 0, 0, 0}, {0, 1, 2, 3, 6, 0, 0, 0}, {4, 6, 0, 0, 0, 0, 0, 0}, - {0, 4, 6, 0, 0, 0, 0, 0}, {1, 4, 6, 0, 0, 0, 0, 0}, {0, 1, 4, 6, 0, 0, 0, 0}, - {2, 4, 6, 0, 0, 0, 0, 0}, {0, 2, 4, 6, 0, 0, 0, 0}, {1, 2, 4, 6, 0, 0, 0, 0}, - {0, 1, 2, 4, 6, 0, 0, 0}, {3, 4, 6, 0, 0, 0, 0, 0}, {0, 3, 4, 6, 0, 0, 0, 0}, - {1, 3, 4, 6, 0, 0, 0, 0}, {0, 1, 3, 4, 6, 0, 0, 0}, {2, 3, 4, 6, 0, 0, 0, 0}, - {0, 2, 3, 4, 6, 0, 0, 0}, {1, 2, 3, 4, 6, 0, 0, 0}, {0, 1, 2, 3, 4, 6, 0, 0}, - {5, 6, 0, 0, 0, 0, 0, 0}, {0, 5, 6, 0, 0, 0, 0, 0}, {1, 5, 6, 0, 0, 0, 0, 0}, - {0, 1, 5, 6, 0, 0, 0, 0}, {2, 5, 6, 0, 0, 0, 0, 0}, {0, 2, 5, 6, 0, 0, 0, 0}, - {1, 2, 5, 6, 0, 0, 0, 0}, {0, 1, 2, 5, 6, 0, 0, 0}, {3, 5, 6, 0, 0, 0, 0, 0}, - {0, 3, 5, 6, 0, 0, 0, 0}, {1, 3, 5, 6, 0, 0, 0, 0}, {0, 1, 3, 5, 6, 0, 0, 0}, - {2, 3, 5, 6, 0, 0, 0, 0}, {0, 2, 3, 5, 6, 0, 0, 0}, {1, 2, 3, 5, 6, 0, 0, 0}, - {0, 1, 2, 3, 5, 6, 0, 0}, {4, 5, 6, 0, 0, 0, 0, 0}, {0, 4, 5, 6, 0, 0, 0, 0}, - {1, 4, 5, 6, 0, 0, 0, 0}, {0, 1, 4, 5, 6, 0, 0, 0}, {2, 4, 5, 6, 0, 0, 0, 0}, - {0, 2, 4, 5, 6, 0, 0, 0}, {1, 2, 4, 5, 6, 0, 0, 0}, {0, 1, 2, 4, 5, 6, 0, 0}, - {3, 4, 5, 6, 0, 0, 0, 0}, {0, 3, 4, 5, 6, 0, 0, 0}, {1, 3, 4, 5, 6, 0, 0, 0}, - {0, 1, 3, 4, 5, 6, 0, 0}, {2, 3, 4, 5, 6, 0, 0, 0}, {0, 2, 3, 4, 5, 6, 0, 0}, - {1, 2, 3, 4, 5, 6, 0, 0}, {0, 1, 2, 3, 4, 5, 6, 0}, {7, 0, 0, 0, 0, 0, 0, 0}, - {0, 7, 0, 0, 0, 0, 0, 0}, {1, 7, 0, 0, 0, 0, 0, 0}, {0, 1, 7, 0, 0, 0, 0, 0}, - {2, 7, 0, 0, 0, 0, 0, 0}, {0, 2, 7, 0, 0, 0, 0, 0}, {1, 2, 7, 0, 0, 0, 0, 0}, - {0, 1, 2, 7, 0, 0, 0, 0}, {3, 7, 0, 0, 0, 0, 0, 0}, {0, 3, 7, 0, 0, 0, 0, 0}, - {1, 3, 7, 0, 0, 0, 0, 0}, {0, 1, 3, 7, 0, 0, 0, 0}, {2, 3, 7, 0, 0, 0, 0, 0}, - {0, 2, 3, 7, 0, 0, 0, 0}, {1, 2, 3, 7, 0, 0, 0, 0}, {0, 1, 2, 3, 7, 0, 0, 0}, - {4, 7, 0, 0, 0, 0, 0, 0}, {0, 4, 7, 0, 0, 0, 0, 0}, {1, 4, 7, 0, 0, 0, 0, 0}, - {0, 1, 4, 7, 0, 0, 0, 0}, {2, 4, 7, 0, 0, 0, 0, 0}, {0, 2, 4, 7, 0, 0, 0, 0}, - {1, 2, 4, 7, 0, 0, 0, 0}, {0, 1, 2, 4, 7, 0, 0, 0}, {3, 4, 7, 0, 0, 0, 0, 0}, - {0, 3, 4, 7, 0, 0, 0, 0}, {1, 3, 4, 7, 0, 0, 0, 0}, {0, 1, 3, 4, 7, 0, 0, 0}, - {2, 3, 4, 7, 0, 0, 0, 0}, {0, 2, 3, 4, 7, 0, 0, 0}, {1, 2, 3, 4, 7, 0, 0, 0}, - {0, 1, 2, 3, 4, 7, 0, 0}, {5, 7, 0, 0, 0, 0, 0, 0}, {0, 5, 7, 0, 0, 0, 0, 0}, - {1, 5, 7, 0, 0, 0, 0, 0}, {0, 1, 5, 7, 0, 0, 0, 0}, {2, 5, 7, 0, 0, 0, 0, 0}, - {0, 2, 5, 7, 0, 0, 0, 0}, {1, 2, 5, 7, 0, 0, 0, 0}, {0, 1, 2, 5, 7, 0, 0, 0}, - {3, 5, 7, 0, 0, 0, 0, 0}, {0, 3, 5, 7, 0, 0, 0, 0}, {1, 3, 5, 7, 0, 0, 0, 0}, - {0, 1, 3, 5, 7, 0, 0, 0}, {2, 3, 5, 7, 0, 0, 0, 0}, {0, 2, 3, 5, 7, 0, 0, 0}, - {1, 2, 3, 5, 7, 0, 0, 0}, {0, 1, 2, 3, 5, 7, 0, 0}, {4, 5, 7, 0, 0, 0, 0, 0}, - {0, 4, 5, 7, 0, 0, 0, 0}, {1, 4, 5, 7, 0, 0, 0, 0}, {0, 1, 4, 5, 7, 0, 0, 0}, - {2, 4, 5, 7, 0, 0, 0, 0}, {0, 2, 4, 5, 7, 0, 0, 0}, {1, 2, 4, 5, 7, 0, 0, 0}, - {0, 1, 2, 4, 5, 7, 0, 0}, {3, 4, 5, 7, 0, 0, 0, 0}, {0, 3, 4, 5, 7, 0, 0, 0}, - {1, 3, 4, 5, 7, 0, 0, 0}, {0, 1, 3, 4, 5, 7, 0, 0}, {2, 3, 4, 5, 7, 0, 0, 0}, - {0, 2, 3, 4, 5, 7, 0, 0}, {1, 2, 3, 4, 5, 7, 0, 0}, {0, 1, 2, 3, 4, 5, 7, 0}, - {6, 7, 0, 0, 0, 0, 0, 0}, {0, 6, 7, 0, 0, 0, 0, 0}, {1, 6, 7, 0, 0, 0, 0, 0}, - {0, 1, 6, 7, 0, 0, 0, 0}, {2, 6, 7, 0, 0, 0, 0, 0}, {0, 2, 6, 7, 0, 0, 0, 0}, - {1, 2, 6, 7, 0, 0, 0, 0}, {0, 1, 2, 6, 7, 0, 0, 0}, {3, 6, 7, 0, 0, 0, 0, 0}, - {0, 3, 6, 7, 0, 0, 0, 0}, {1, 3, 6, 7, 0, 0, 0, 0}, {0, 1, 3, 6, 7, 0, 0, 0}, - {2, 3, 6, 7, 0, 0, 0, 0}, {0, 2, 3, 6, 7, 0, 0, 0}, {1, 2, 3, 6, 7, 0, 0, 0}, - {0, 1, 2, 3, 6, 7, 0, 0}, {4, 6, 7, 0, 0, 0, 0, 0}, {0, 4, 6, 7, 0, 0, 0, 0}, - {1, 4, 6, 7, 0, 0, 0, 0}, {0, 1, 4, 6, 7, 0, 0, 0}, {2, 4, 6, 7, 0, 0, 0, 0}, - {0, 2, 4, 6, 7, 0, 0, 0}, {1, 2, 4, 6, 7, 0, 0, 0}, {0, 1, 2, 4, 6, 7, 0, 0}, - {3, 4, 6, 7, 0, 0, 0, 0}, {0, 3, 4, 6, 7, 0, 0, 0}, {1, 3, 4, 6, 7, 0, 0, 0}, - {0, 1, 3, 4, 6, 7, 0, 0}, {2, 3, 4, 6, 7, 0, 0, 0}, {0, 2, 3, 4, 6, 7, 0, 0}, - {1, 2, 3, 4, 6, 7, 0, 0}, {0, 1, 2, 3, 4, 6, 7, 0}, {5, 6, 7, 0, 0, 0, 0, 0}, - {0, 5, 6, 7, 0, 0, 0, 0}, {1, 5, 6, 7, 0, 0, 0, 0}, {0, 1, 5, 6, 7, 0, 0, 0}, - {2, 5, 6, 7, 0, 0, 0, 0}, {0, 2, 5, 6, 7, 0, 0, 0}, {1, 2, 5, 6, 7, 0, 0, 0}, - {0, 1, 2, 5, 6, 7, 0, 0}, {3, 5, 6, 7, 0, 0, 0, 0}, {0, 3, 5, 6, 7, 0, 0, 0}, - {1, 3, 5, 6, 7, 0, 0, 0}, {0, 1, 3, 5, 6, 7, 0, 0}, {2, 3, 5, 6, 7, 0, 0, 0}, - {0, 2, 3, 5, 6, 7, 0, 0}, {1, 2, 3, 5, 6, 7, 0, 0}, {0, 1, 2, 3, 5, 6, 7, 0}, - {4, 5, 6, 7, 0, 0, 0, 0}, {0, 4, 5, 6, 7, 0, 0, 0}, {1, 4, 5, 6, 7, 0, 0, 0}, - {0, 1, 4, 5, 6, 7, 0, 0}, {2, 4, 5, 6, 7, 0, 0, 0}, {0, 2, 4, 5, 6, 7, 0, 0}, - {1, 2, 4, 5, 6, 7, 0, 0}, {0, 1, 2, 4, 5, 6, 7, 0}, {3, 4, 5, 6, 7, 0, 0, 0}, - {0, 3, 4, 5, 6, 7, 0, 0}, {1, 3, 4, 5, 6, 7, 0, 0}, {0, 1, 3, 4, 5, 6, 7, 0}, - {2, 3, 4, 5, 6, 7, 0, 0}, {0, 2, 3, 4, 5, 6, 7, 0}, {1, 2, 3, 4, 5, 6, 7, 0}, - {0, 1, 2, 3, 4, 5, 6, 7}}; + + constexpr OffsetIndices() : + offset_indices() { + for (int i = 0; i < 256; ++i) + { + std::uint64_t j = i, k = 0; + while (j) + { + offset_indices[i][k++] = constexpr_lsb(j); + j &= j - 1; + } + while (k < 8) + offset_indices[i][k++] = 0; + } + } + +} Lookup; // Find indices of nonzero numbers in an int32_t array template @@ -196,9 +138,9 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou } for (IndexType j = 0; j < OutputsPerChunk; ++j) { - const auto lookup = (nnz >> (j * 8)) & 0xFF; - const auto offsets = - vec128_load(reinterpret_cast(&lookup_indices[lookup])); + const unsigned lookup = (nnz >> (j * 8)) & 0xFF; + const vec128_t offsets = + vec128_load(reinterpret_cast(&Lookup.offset_indices[lookup])); vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); count += popcount(lookup); base = vec128_add(base, increment); From e7367cef0f5e7ccef36836ba072f165f5147e4f6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 12 Jan 2025 19:38:02 -0800 Subject: [PATCH 416/834] Clean up pack reordering closes https://github.com/official-stockfish/Stockfish/pull/5802 no functional change --- src/nnue/nnue_feature_transformer.h | 125 ++++++++++++++++------------ 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7a37cda82..4f0ce6cf6 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "../position.h" @@ -145,6 +146,46 @@ using psqt_vec_t = int32x4_t; #endif +// Returns the inverse of a permutation +template +constexpr std::array +invert_permutation(const std::array& order) { + std::array inverse{}; + for (std::size_t i = 0; i < order.size(); i++) + inverse[order[i]] = i; + return inverse; +} + +// Divide a byte region of size TotalSize to chunks of size +// BlockSize, and permute the blocks by a given order +template +void permute(T (&data)[N], const std::array& order) { + constexpr std::size_t TotalSize = N * sizeof(T); + + static_assert(TotalSize % (BlockSize * OrderSize) == 0, + "ChunkSize * OrderSize must perfectly divide TotalSize"); + + constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize; + + std::array buffer{}; + + std::byte* const bytes = reinterpret_cast(data); + + for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize) + { + std::byte* const values = &bytes[i]; + + for (std::size_t j = 0; j < OrderSize; j++) + { + auto* const buffer_chunk = &buffer[j * BlockSize]; + auto* const value_chunk = &values[order[j] * BlockSize]; + + std::copy(value_chunk, value_chunk + BlockSize, buffer_chunk); + } + + std::copy(std::begin(buffer), std::end(buffer), values); + } +} // Compute optimal SIMD register count for feature transformer accumulation. template @@ -223,62 +264,42 @@ class FeatureTransformer { // Size of forward propagation buffer static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); + // Store the order by which 128-bit blocks of a 1024-bit data must + // be permuted so that calling packus on adjacent vectors of 16-bit + // integers loaded from the data results in the pre-permutation order + static constexpr auto PackusEpi16Order = []() -> std::array { +#if defined(USE_AVX512) + // _mm512_packus_epi16 after permutation: + // | 0 | 2 | 4 | 6 | // Vector 0 + // | 1 | 3 | 5 | 7 | // Vector 1 + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 4, 6, 1, 3, 5, 7}; +#elif defined(USE_AVX2) + // _mm256_packus_epi16 after permutation: + // | 0 | 2 | | 4 | 6 | // Vector 0, 2 + // | 1 | 3 | | 5 | 7 | // Vector 1, 3 + // | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 1, 3, 4, 6, 5, 7}; +#else + return {0, 1, 2, 3, 4, 5, 6, 7}; +#endif + }(); + + static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order); + // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { return FeatureSet::HashValue ^ (OutputDimensions * 2); } - static constexpr void order_packs([[maybe_unused]] uint64_t* v) { -#if defined(USE_AVX512) // _mm512_packs_epi16 ordering - uint64_t tmp0 = v[2], tmp1 = v[3]; - v[2] = v[8], v[3] = v[9]; - v[8] = v[4], v[9] = v[5]; - v[4] = tmp0, v[5] = tmp1; - tmp0 = v[6], tmp1 = v[7]; - v[6] = v[10], v[7] = v[11]; - v[10] = v[12], v[11] = v[13]; - v[12] = tmp0, v[13] = tmp1; -#elif defined(USE_AVX2) // _mm256_packs_epi16 ordering - std::swap(v[2], v[4]); - std::swap(v[3], v[5]); -#endif + void permute_weights() { + permute<16>(biases, PackusEpi16Order); + permute<16>(weights, PackusEpi16Order); } - static constexpr void inverse_order_packs([[maybe_unused]] uint64_t* v) { -#if defined(USE_AVX512) // Inverse _mm512_packs_epi16 ordering - uint64_t tmp0 = v[2], tmp1 = v[3]; - v[2] = v[4], v[3] = v[5]; - v[4] = v[8], v[5] = v[9]; - v[8] = tmp0, v[9] = tmp1; - tmp0 = v[6], tmp1 = v[7]; - v[6] = v[12], v[7] = v[13]; - v[12] = v[10], v[13] = v[11]; - v[10] = tmp0, v[11] = tmp1; -#elif defined(USE_AVX2) // Inverse _mm256_packs_epi16 ordering - std::swap(v[2], v[4]); - std::swap(v[3], v[5]); -#endif - } - - void permute_weights([[maybe_unused]] void (*order_fn)(uint64_t*)) { -#if defined(USE_AVX2) - #if defined(USE_AVX512) - constexpr IndexType di = 16; - #else - constexpr IndexType di = 8; - #endif - uint64_t* b = reinterpret_cast(&biases[0]); - for (IndexType i = 0; i < HalfDimensions * sizeof(BiasType) / sizeof(uint64_t); i += di) - order_fn(&b[i]); - - for (IndexType j = 0; j < InputDimensions; ++j) - { - uint64_t* w = reinterpret_cast(&weights[j * HalfDimensions]); - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(uint64_t); - i += di) - order_fn(&w[i]); - } -#endif + void unpermute_weights() { + permute<16>(biases, InversePackusEpi16Order); + permute<16>(weights, InversePackusEpi16Order); } inline void scale_weights(bool read) { @@ -300,7 +321,7 @@ class FeatureTransformer { read_leb_128(stream, weights, HalfDimensions * InputDimensions); read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - permute_weights(inverse_order_packs); + permute_weights(); scale_weights(true); return !stream.fail(); } @@ -308,14 +329,14 @@ class FeatureTransformer { // Write network parameters bool write_parameters(std::ostream& stream) { - permute_weights(order_packs); + unpermute_weights(); scale_weights(false); write_leb_128(stream, biases, HalfDimensions); write_leb_128(stream, weights, HalfDimensions * InputDimensions); write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - permute_weights(inverse_order_packs); + permute_weights(); scale_weights(true); return !stream.fail(); } From 59c578ad284f057ec32afe506814aaf0f8f7b4f4 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 19 Jan 2025 18:31:31 +0100 Subject: [PATCH 417/834] Add move count based reduction. Do less reduction which is linear increasing with move count (factor = 64). Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 191488 W: 49982 L: 49432 D: 92074 Ptnml(0-2): 731, 22523, 48614, 23217, 659 https://tests.stockfishchess.org/tests/view/678d0b29d63764e34db4904b Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 90582 W: 23150 L: 22717 D: 44715 Ptnml(0-2): 73, 9936, 24822, 10405, 55 https://tests.stockfishchess.org/tests/view/678d347cd63764e34db4916f closes https://github.com/official-stockfish/Stockfish/pull/5807 Bench: 1803474 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e99d5d3fc..de4318437 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1155,7 +1155,7 @@ moves_loop: // When in check, search starts here // These reduction adjustments have no proven non-linear scaling - r += 307; + r += 307 - moveCount * 64; r -= std::abs(correctionValue) / 34112; From 4975b2bc6fb12d42a7441899a4698cf0d14914dd Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 19 Jan 2025 20:33:05 +0100 Subject: [PATCH 418/834] Increase prior countermove bonus if TT move Passed STC: https://tests.stockfishchess.org/tests/view/678c4c8bf4dc0a8b4ae8db5c LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 273408 W: 71089 L: 70415 D: 131904 Ptnml(0-2): 937, 32466, 69229, 33130, 942 Passed LTC: https://tests.stockfishchess.org/tests/view/678ccabdf4dc0a8b4ae8dd7a LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 148614 W: 38138 L: 37584 D: 72892 Ptnml(0-2): 97, 16450, 40689, 16944, 127 closes https://github.com/official-stockfish/Stockfish/pull/5809 Bench: 1582867 --- src/search.cpp | 5 ++++- src/search.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index de4318437..951d197d2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -889,6 +889,7 @@ Value Search::Worker::search( thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; + ss->isTTMove = (move == ttData.move); ss->continuationHistory = &this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1138,6 +1139,7 @@ moves_loop: // When in check, search starts here // Update the current move (this must be done after singular extension search) ss->currentMove = move; + ss->isTTMove = (move == ttData.move); ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1387,7 +1389,8 @@ moves_loop: // When in check, search starts here { int bonusScale = (118 * (depth > 5) + 37 * !allNode + 169 * ((ss - 1)->moveCount > 8) + 128 * (!ss->inCheck && bestValue <= ss->staticEval - 102) - + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82)); + + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) + + 80 * ((ss - 1)->isTTMove)); // Proportional to "how much damage we have to undo" bonusScale += std::min(-(ss - 1)->statScore / 106, 318); diff --git a/src/search.h b/src/search.h index 3983e0f33..3a1b3a77d 100644 --- a/src/search.h +++ b/src/search.h @@ -75,6 +75,7 @@ struct Stack { bool ttHit; int cutoffCnt; int reduction; + bool isTTMove; }; From aa894c0f93201cac899f000411fa59cdbc386fd6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 20 Jan 2025 00:06:26 +0300 Subject: [PATCH 419/834] Comments Tweak * Remove from comments, hardcoded exact values for parameters that are subject to tuning. * Remove the Elo worth, as they are now completely outdated, making them irrelevant and potentially misleading. * Consolidated scaling-related comments into a single section for clarity. Used asterisks (*) to highlight parameters significantly affected by scaling, given their separation in the code. closes https://github.com/official-stockfish/Stockfish/pull/5810 No functional change --- src/search.cpp | 116 +++++++++++++++++++++++------------------------- src/timeman.cpp | 2 +- 2 files changed, 56 insertions(+), 62 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 951d197d2..3c8f33b41 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -64,6 +64,12 @@ using namespace Search; namespace { +// (*Scalers): +// The values with Scaler asterisks have proven non-linear scaling. +// They are optimized to time controls of 180 + 1.8 and longer, +// so changing them or adding conditions that are similar requires +// tests at these types of time controls. + // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 112 - 26 * noTtCutNode; @@ -320,7 +326,7 @@ void Search::Worker::iterative_deepening() { alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); - // Adjust optimism based on root move's averageScore (~4 Elo) + // Adjust optimism based on root move's averageScore optimism[us] = 141 * avg / (std::abs(avg) + 83); optimism[~us] = -optimism[us]; @@ -647,15 +653,14 @@ Value Search::Worker::search( && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) && (cutNode == (ttData.value >= beta) || depth > 9)) { - // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) + // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) { - // Bonus for a quiet ttMove that fails high (~2 Elo) + // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 746 / 1024); - // Extra penalty for early quiet moves of - // the previous ply (~1 Elo on STC, ~2 Elo on LTC) + // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_malus(depth + 1) * 1042 / 1024); @@ -733,7 +738,6 @@ Value Search::Worker::search( else if (excludedMove) { // Providing the hint that this node's accumulator will be used often - // brings significant Elo gain (~13 Elo). Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); unadjustedStaticEval = eval = ss->staticEval; } @@ -748,7 +752,7 @@ Value Search::Worker::search( ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - // ttValue can be used as a better position evaluation (~7 Elo) + // ttValue can be used as a better position evaluation if (is_valid(ttData.value) && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttData.value; @@ -763,7 +767,7 @@ Value Search::Worker::search( unadjustedStaticEval, tt.generation()); } - // Use static evaluation difference to improve quiet move ordering (~9 Elo) + // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1881, 1413) + 616; @@ -784,13 +788,13 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; - // Step 7. Razoring (~1 Elo) + // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. if (!PvNode && eval < alpha - 462 - 297 * depth * depth) return qsearch(pos, ss, alpha, beta); - // Step 8. Futility pruning: child node (~40 Elo) + // Step 8. Futility pruning: child node // The depth condition is important for mate finding. if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) @@ -801,7 +805,7 @@ Value Search::Worker::search( improving |= ss->staticEval >= beta + 97; - // Step 9. Null move search with verification search (~35 Elo) + // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta && ss->staticEval >= beta - 20 * depth + 440 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) @@ -842,11 +846,9 @@ Value Search::Worker::search( } } - // Step 10. Internal iterative reductions (~9 Elo) + // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. - // This heuristic is known to scale non-linearly, current version was tested at VVLTC. - // Further improvements need to be tested at similar time control if they make IIR - // more aggressive. + // (* Scaler) Especially if they make IIR more aggressive. if ((PvNode || (cutNode && depth >= 7)) && !ttData.move) depth -= 2; @@ -854,7 +856,7 @@ Value Search::Worker::search( if (depth <= 0) return qsearch(pos, ss, alpha, beta); - // Step 11. ProbCut (~10 Elo) + // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. probCutBeta = beta + 174 - 56 * improving; @@ -919,7 +921,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here - // Step 12. A small Probcut idea (~4 Elo) + // Step 12. A small Probcut idea probCutBeta = beta + 412; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) @@ -980,15 +982,15 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); - // Decrease reduction if position is or has been on the PV (~7 Elo) + // Decrease reduction if position is or has been on the PV (*Scaler) if (ss->ttPv) r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; - // Step 14. Pruning at shallow depth (~120 Elo). + // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { - // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold if (moveCount >= futility_move_count(improving, depth)) mp.skip_quiet_moves(); @@ -1001,7 +1003,7 @@ moves_loop: // When in check, search starts here int captHist = thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; - // Futility pruning for captures (~2 Elo) + // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { Value futilityValue = ss->staticEval + 271 + 243 * lmrDepth @@ -1010,7 +1012,7 @@ moves_loop: // When in check, search starts here continue; } - // SEE based pruning for captures and checks (~11 Elo) + // SEE based pruning for captures and checks int seeHist = std::clamp(captHist / 37, -152 * depth, 141 * depth); if (!pos.see_ge(move, -156 * depth - seeHist)) continue; @@ -1022,7 +1024,7 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; - // Continuation history based pruning (~2 Elo) + // Continuation history based pruning if (history < -3901 * depth) continue; @@ -1033,7 +1035,7 @@ moves_loop: // When in check, search starts here Value futilityValue = ss->staticEval + (bestValue < ss->staticEval - 47 ? 137 : 47) + 142 * lmrDepth; - // Futility pruning: parent node (~13 Elo) + // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) @@ -1044,27 +1046,24 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); - // Prune moves with negative SEE (~4 Elo) + // Prune moves with negative SEE if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) continue; } } - // Step 15. Extensions (~100 Elo) + // Step 15. Extensions // We take care to not overdo to avoid search getting stuck. if (ss->ply < thisThread->rootDepth * 2) { - // Singular extension search (~76 Elo, ~170 nElo). If all moves but one + // Singular extension search. If all moves but one // fail low on a search of (alpha-s, beta-s), and just one fails high on // (alpha, beta), then that move is singular and should be extended. To // verify this we do a reduced search on the position excluding the ttMove // and if the result is lower than ttValue minus a margin, then we will // extend the ttMove. Recursive singular search is avoided. - // Note: the depth margin and singularBeta margin are known for having - // non-linear scaling. Their values are optimized to time controls of - // 180+1.8 and longer so changing them requires tests at these types of - // time controls. Generally, higher singularBeta (i.e closer to ttValue) + // (* Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove @@ -1112,17 +1111,17 @@ moves_loop: // When in check, search starts here // if the ttMove is singular or can do a multi-cut, so we reduce the // ttMove in favor of other moves based on some conditions: - // If the ttMove is assumed to fail high over current beta (~7 Elo) + // If the ttMove is assumed to fail high over current beta else if (ttData.value >= beta) extension = -3; // If we are on a cutNode but the ttMove is not assumed to fail high - // over current beta (~1 Elo) + // over current beta else if (cutNode) extension = -2; } - // Extension for capturing the previous moved piece (~1 Elo at LTC) + // Extension for capturing the previous moved piece else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] @@ -1146,12 +1145,7 @@ moves_loop: // When in check, search starts here &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; - // These reduction adjustments have proven non-linear scaling. - // They are optimized to time controls of 180 + 1.8 and longer, - // so changing them or adding conditions that are similar requires - // tests at these types of time controls. - - // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) + // Decrease reduction for PvNodes (*Scaler) if (PvNode) r -= 1018; @@ -1161,19 +1155,19 @@ moves_loop: // When in check, search starts here r -= std::abs(correctionValue) / 34112; - // Increase reduction for cut nodes (~4 Elo) + // Increase reduction for cut nodes if (cutNode) r += 2355 - (ttData.depth >= depth && ss->ttPv) * 1141; - // Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo) + // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) r += 1087 + (depth < 8) * 990; - // Increase reduction if next ply has a lot of fail high (~5 Elo) + // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 3) r += 940 + allNode * 887; - // For first picked move (ttMove) reduce reduction (~3 Elo) + // For first picked move (ttMove) reduce reduction else if (move == ttData.move) r -= 1960; @@ -1187,10 +1181,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - 3874; - // Decrease/increase reduction for moves with a good/bad history (~8 Elo) + // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 1451 / 16384; - // Step 17. Late moves reduction / extension (LMR, ~117 Elo) + // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) { // In general we want to cap the LMR depth search at newDepth, but when @@ -1214,15 +1208,15 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) - const bool doShallowerSearch = value < bestValue + 10; // (~2 Elo) + const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); + const bool doShallowerSearch = value < bestValue + 10; newDepth += doDeeperSearch - doShallowerSearch; if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); - // Post LMR continuation history updates (~1 Elo) + // Post LMR continuation history updates int bonus = (value >= beta) * 2048; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } @@ -1231,11 +1225,11 @@ moves_loop: // When in check, search starts here // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { - // Increase reduction if ttMove is not present (~6 Elo) + // Increase reduction if ttMove is not present if (!ttData.move) r += 2111; - // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) + // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3444), !cutNode); } @@ -1342,7 +1336,7 @@ moves_loop: // When in check, search starts here } else { - // Reduce other moves if we have found at least one score improvement (~2 Elo) + // Reduce other moves if we have found at least one score improvement if (depth > 2 && depth < 14 && !is_decisive(value)) depth -= 2; @@ -1422,7 +1416,7 @@ moves_loop: // When in check, search starts here bestValue = std::min(bestValue, maxValue); // If no good move is found and the previous position was ttPv, then the previous - // opponent move is probably good and the new position is added to the search tree. (~7 Elo) + // opponent move is probably good and the new position is added to the search tree. if (bestValue <= alpha) ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); @@ -1467,7 +1461,7 @@ moves_loop: // When in check, search starts here // Quiescence search function, which is called by the main search function with // depth zero, or recursively with further decreasing depth. With depth <= 0, we // "should" be using static eval only, but tactical moves may confuse the static eval. -// To fight this horizon effect, we implement this qsearch of tactical moves (~155 Elo). +// To fight this horizon effect, we implement this qsearch of tactical moves. // See https://www.chessprogramming.org/Horizon_Effect // and https://www.chessprogramming.org/Quiescence_Search template @@ -1479,7 +1473,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); - // Check if we have an upcoming move that draws by repetition (~1 Elo) + // Check if we have an upcoming move that draws by repetition if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) { alpha = value_draw(this->nodes); @@ -1551,7 +1545,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - // ttValue can be used as a better position evaluation (~13 Elo) + // ttValue can be used as a better position evaluation if (is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttData.value; @@ -1611,7 +1605,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Step 6. Pruning if (!is_loss(bestValue) && pos.non_pawn_material(us)) { - // Futility pruning and moveCount pruning (~10 Elo) + // Futility pruning and moveCount pruning if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase) && move.type_of() != PROMOTION) { @@ -1621,7 +1615,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Value futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())]; // If static eval + value of piece we are going to capture is - // much lower than alpha, we can prune this move. (~2 Elo) + // much lower than alpha, we can prune this move. if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); @@ -1629,7 +1623,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } // If static exchange evaluation is low enough - // we can prune this move. (~2 Elo) + // we can prune this move. if (!pos.see_ge(move, alpha - futilityBase)) { bestValue = std::min(alpha, futilityBase); @@ -1637,7 +1631,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } } - // Continuation history based pruning (~3 Elo) + // Continuation history based pruning if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] @@ -1646,7 +1640,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) <= 5228) continue; - // Do not search moves with bad enough SEE values (~5 Elo) + // Do not search moves with bad enough SEE values if (!pos.see_ge(move, -80)) continue; } diff --git a/src/timeman.cpp b/src/timeman.cpp index d073a84a9..29ebffcaa 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -87,7 +87,7 @@ void TimeManagement::init(Search::LimitsType& limits, const TimePoint scaledTime = limits.time[us] / scaleFactor; const TimePoint scaledInc = limits.inc[us] / scaleFactor; - // Maximum move horizon of 50 moves + // Maximum move horizon int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; // If less than one second, gradually reduce mtg From d606311e5508ffccb0fb7e88537c8fe899f702ac Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 19 Jan 2025 17:55:53 -0800 Subject: [PATCH 420/834] Fix undefined behavior From cppreference: "It is undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union." closes https://github.com/official-stockfish/Stockfish/pull/5811 no functional change --- src/bitboard.h | 10 +++++----- src/misc.h | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 6f9cca0bd..df15113bd 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -268,11 +269,10 @@ inline int popcount(Bitboard b) { #ifndef USE_POPCNT - union { - Bitboard bb; - uint16_t u[4]; - } v = {b}; - return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; + std::uint16_t indices[4]; + std::memcpy(indices, &b, sizeof(b)); + return PopCnt16[indices[0]] + PopCnt16[indices[1]] + PopCnt16[indices[2]] + + PopCnt16[indices[3]]; #elif defined(_MSC_VER) diff --git a/src/misc.h b/src/misc.h index 81c7b17fe..8adbac68a 100644 --- a/src/misc.h +++ b/src/misc.h @@ -120,11 +120,8 @@ void sync_cout_start(); void sync_cout_end(); // True if and only if the binary is compiled on a little-endian machine -static inline const union { - uint32_t i; - char c[4]; -} Le = {0x01020304}; -static inline const bool IsLittleEndian = (Le.c[0] == 4); +static inline const std::uint16_t Le = 1; +static inline const bool IsLittleEndian = *reinterpret_cast(&Le) == 1; template From 75b75bc16a4277a24af9587fe236555ed24599e6 Mon Sep 17 00:00:00 2001 From: pkrisz99 <5463243+pkrisz99@users.noreply.github.com> Date: Sat, 18 Jan 2025 23:58:25 +0100 Subject: [PATCH 421/834] Add improving to a condition of NMP This patch makes one of the conditions for null-move pruning depend on whether we're improving. Keep in mind that it relies on the "classical" definiton, rather than the refined one. Passed STC: https://tests.stockfishchess.org/tests/view/678d5267d63764e34db49720 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 38976 W: 10296 L: 9974 D: 18706 Ptnml(0-2): 135, 4504, 9902, 4798, 149 Passed LTC: https://tests.stockfishchess.org/tests/view/678d5891d63764e34db49731 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 275772 W: 70655 L: 69836 D: 135281 Ptnml(0-2): 217, 30615, 75394, 31452, 208 closes https://github.com/official-stockfish/Stockfish/pull/5813 Bench: 2475787 --- AUTHORS | 1 + src/search.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index f6468c561..1e5e51e63 100644 --- a/AUTHORS +++ b/AUTHORS @@ -129,6 +129,7 @@ Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) Kojirion +Krisztián Peőcz Krystian Kuzniarek (kuzkry) Leonardo Ljubičić (ICCF World Champion) Leonid Pechenik (lp--) diff --git a/src/search.cpp b/src/search.cpp index 3c8f33b41..9f29f8285 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -803,12 +803,10 @@ Value Search::Worker::search( && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; - improving |= ss->staticEval >= beta + 97; - // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 20 * depth + 440 && !excludedMove && pos.non_pawn_material(us) - && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) + && ss->staticEval >= beta - 20 * depth + 470 - 60 * improving && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); @@ -846,6 +844,8 @@ Value Search::Worker::search( } } + improving |= ss->staticEval >= beta + 97; + // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. // (* Scaler) Especially if they make IIR more aggressive. From 6c7c5c7e471c16f14518229428e51a3e00c0f1dd Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Tue, 21 Jan 2025 17:23:51 +0100 Subject: [PATCH 422/834] Do not change TB cursed wins to draws if requested If Syzygy50MoveRule is false, do not calls to is_draw() need to be guarded. Also fixes a TB rootmove ranking issue in this case. closes https://github.com/official-stockfish/Stockfish/pull/5814 No functional change --- src/position.cpp | 7 ++++--- src/position.h | 1 + src/search.cpp | 7 ++++--- src/syzygy/tbprobe.cpp | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 49f520e01..02614d13f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1187,11 +1187,12 @@ bool Position::is_draw(int ply) const { if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) return true; - // Return a draw score if a position repeats once earlier but strictly - // after the root, or repeats twice before or at the root. - return st->repetition && st->repetition < ply; + return is_repetition(ply); } +// Return a draw score if a position repeats once earlier but strictly +// after the root, or repeats twice before or at the root. +bool Position::is_repetition(int ply) const { return st->repetition && st->repetition < ply; } // Tests whether there has been at least one repetition // of positions since the last capture or pawn move. diff --git a/src/position.h b/src/position.h index 0d49a60a9..d955d9f98 100644 --- a/src/position.h +++ b/src/position.h @@ -163,6 +163,7 @@ class Position { int game_ply() const; bool is_chess960() const; bool is_draw(int ply) const; + bool is_repetition(int ply) const; bool upcoming_repetition(int ply) const; bool has_repeated() const; int rule50_count() const; diff --git a/src/search.cpp b/src/search.cpp index 9f29f8285..f23631623 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1949,6 +1949,7 @@ void syzygy_extend_pv(const OptionsMap& options, auto t_start = std::chrono::steady_clock::now(); int moveOverhead = int(options["Move Overhead"]); + bool rule50 = bool(options["Syzygy50MoveRule"]); // Do not use more than moveOverhead / 2 time, if time management is active auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool { @@ -1986,7 +1987,7 @@ void syzygy_extend_pv(const OptionsMap& options, pos.do_move(pvMove, st); // Do not allow for repetitions or drawing moves along the PV in TB regime - if (config.rootInTB && pos.is_draw(ply)) + if (config.rootInTB && ((rule50 && pos.is_draw(ply)) || pos.is_repetition(ply))) { pos.undo_move(pvMove); ply--; @@ -2005,7 +2006,7 @@ void syzygy_extend_pv(const OptionsMap& options, // Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info // using top ranked moves (minimal DTZ), which gives optimal mates only for simple // endgames e.g. KRvK. - while (!pos.is_draw(0)) + while (!(rule50 && pos.is_draw(0))) { if (time_abort()) break; @@ -2048,7 +2049,7 @@ void syzygy_extend_pv(const OptionsMap& options, pos.do_move(pvMove, st); } - // Finding a draw in this function is an exceptional case, that cannot happen + // Finding a draw in this function is an exceptional case, that cannot happen when rule50 is false or // during engine game play, since we have a winning score, and play correctly // with TB support. However, it can be that a position is draw due to the 50 move // rule if it has been been reached on the board with a non-optimal 50 move counter diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 120e64885..cbf8dce5e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1620,7 +1620,7 @@ bool Tablebases::root_probe(Position& pos, WDLScore wdl = -probe_wdl(pos, &result); dtz = dtz_before_zeroing(wdl); } - else if (pos.is_draw(1)) + else if ((rule50 && pos.is_draw(1)) || pos.is_repetition(1)) { // In case a root move leads to a draw by repetition or 50-move rule, // we set dtz to zero. Note: since we are only 1 ply from the root, From 435ba3dbb5ceb081592233d9f4b71e51c492dc22 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:18:23 +0000 Subject: [PATCH 423/834] Revert "Moving up the if position is or has been on the PV reduction" Passed VVLTC 1: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 68362 W: 17830 L: 17523 D: 33009 Ptnml(0-2): 9, 6253, 21347, 6566, 6 https://tests.stockfishchess.org/tests/view/6790271cfc8c306ba6cea2c1 Passed VVLTC 2: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 113256 W: 29158 L: 28721 D: 55377 Ptnml(0-2): 13, 10521, 35122, 10960, 12 https://tests.stockfishchess.org/tests/view/678d3e47d63764e34db491a3 closes https://github.com/official-stockfish/Stockfish/pull/5815 bench 1943998 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f23631623..990cbae33 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -982,10 +982,6 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); - // Decrease reduction if position is or has been on the PV (*Scaler) - if (ss->ttPv) - r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; - // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) @@ -1146,6 +1142,9 @@ moves_loop: // When in check, search starts here uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; // Decrease reduction for PvNodes (*Scaler) + if (ss->ttPv) + r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + if (PvNode) r -= 1018; From 889fed448c280f2f559c03672faa521ea4dcc1f2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 21 Jan 2025 17:41:33 -0800 Subject: [PATCH 424/834] Better nonpawn indexing Improves indexing scheme, by noting that both sides are likely to access the same non_pawn_index nearby. LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 75936 W: 19905 L: 19554 D: 36477 Ptnml(0-2): 190, 7863, 21554, 8128, 233 https://tests.stockfishchess.org/tests/view/67904d0cfc8c306ba6cea332 closes https://github.com/official-stockfish/Stockfish/pull/5816 No functional change Co-authored-by: Andrew Grant --- src/history.h | 7 ++++++- src/search.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/history.h b/src/history.h index 15095cd0b..4543fc55e 100644 --- a/src/history.h +++ b/src/history.h @@ -138,7 +138,7 @@ enum CorrHistType { Pawn, // By color and pawn structure Major, // By color and positions of major pieces (Queen, Rook) and King Minor, // By color and positions of minor pieces (Knight, Bishop) and King - NonPawn, // By color and non-pawn material positions + NonPawn, // By Non-pawn material positions and color PieceTo, // By [piece][to] move Continuation, // Combined history of move pairs }; @@ -150,6 +150,11 @@ struct CorrHistTypedef { using type = Stats; }; +template<> +struct CorrHistTypedef { + using type = Stats; +}; + template<> struct CorrHistTypedef { using type = Stats; diff --git a/src/search.cpp b/src/search.cpp index 990cbae33..e11da9128 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -89,8 +89,8 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(pos)]; const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)]; const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)]; - const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)]; - const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)]; + const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; + const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; @@ -1442,9 +1442,9 @@ moves_loop: // When in check, search starts here << bonus * 114 / 128; thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 163 / 128; thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 146 / 128; - thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] + thisThread->nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; - thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] + thisThread->nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) From 1b31e266b0b83f362f73eaa184840a05fc15e7a7 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Mon, 20 Jan 2025 15:40:41 +0100 Subject: [PATCH 425/834] Consider more nodes as ttPv nodes. Remove depth condition in propagation rule for ttPv state from a node to it childs. Because this change marks more nodes as ttPv, we have a time sensitive ttPv reduction rule and the STC snd LTC seems to show bad scaling. So i have also submitted a VLTC non-regression to check the scaling at higher time control. The results gives a little indication that we have perhaps good scaling with more ttPv nodes so that could be further explored. Passed non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 82528 W: 21627 L: 21453 D: 39448 Ptnml(0-2): 317, 9809, 20891, 9877, 370 https://tests.stockfishchess.org/tests/view/678e608cd63764e34db49ad7 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 310440 W: 78879 L: 78956 D: 152605 Ptnml(0-2): 255, 34915, 84938, 34876, 236 https://tests.stockfishchess.org/tests/view/678fab89ac8f8f5496155f3c Passed non-regression VLTC for scaling verification: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 59496 W: 15158 L: 14983 D: 29355 Ptnml(0-2): 15, 6039, 17470, 6204, 20 https://tests.stockfishchess.org/tests/view/6794bd1f4f7de645171fb33b closes https://github.com/official-stockfish/Stockfish/pull/5819 Bench: 1829507 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e11da9128..3a161fee1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1417,7 +1417,7 @@ moves_loop: // When in check, search starts here // If no good move is found and the previous position was ttPv, then the previous // opponent move is probably good and the new position is added to the search tree. if (bestValue <= alpha) - ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); + ss->ttPv = ss->ttPv || (ss - 1)->ttPv; // Write gathered information in transposition table. Note that the // static evaluation is saved as it was before correction history. From 27e747d1d727386bec6eea01456fcbeae604bfe3 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 23 Jan 2025 20:02:24 -0800 Subject: [PATCH 426/834] Simplify futility margin in lmr for quiets. Replace the "low bestValue condition" with whether there is a best move. Passed Simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 102560 W: 26517 L: 26367 D: 49676 Ptnml(0-2): 328, 12223, 26036, 12357, 336 https://tests.stockfishchess.org/tests/view/679310e4ca18a2c66da02af8 Passed Simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 66942 W: 17130 L: 16953 D: 32859 Ptnml(0-2): 52, 7459, 18290, 7600, 70 https://tests.stockfishchess.org/tests/view/679459a3e96bfb672ad18ddf closes https://github.com/official-stockfish/Stockfish/pull/5820 Bench: 1438043 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3a161fee1..ab8825f83 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1028,8 +1028,7 @@ moves_loop: // When in check, search starts here lmrDepth += history / 3459; - Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 47 ? 137 : 47) + 142 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 47 : 137) + 142 * lmrDepth; // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) From 69be04d38e10003853e78e4aa2b32aa252a82850 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 25 Jan 2025 23:54:09 +0300 Subject: [PATCH 427/834] Simplify cutoffCnt Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 235872 W: 61156 L: 61155 D: 113561 Ptnml(0-2): 843, 28269, 59658, 28376, 790 https://tests.stockfishchess.org/tests/view/6794dd3e4f7de645171fb380 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 61494 W: 15644 L: 15462 D: 30388 Ptnml(0-2): 61, 6822, 16788, 7026, 50 https://tests.stockfishchess.org/tests/view/6794f86a406a4efe9eb7d093 closes https://github.com/official-stockfish/Stockfish/pull/5821 Bench: 2168937 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ab8825f83..316201886 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1328,7 +1328,7 @@ moves_loop: // When in check, search starts here if (value >= beta) { - ss->cutoffCnt += !ttData.move + (extension < 2); + ss->cutoffCnt += (extension < 2); assert(value >= beta); // Fail high break; } From 831cb01cea9e41d7f09406a9a0cf0913bf205cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Nov=C3=A1k?= Date: Sat, 25 Jan 2025 21:09:45 +0100 Subject: [PATCH 428/834] Remove major corrhist Remove major correction history and slightly increase all other correction weights. Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 50080 W: 13171 L: 12959 D: 23950 Ptnml(0-2): 196, 5998, 12462, 6166, 218 https://tests.stockfishchess.org/tests/live_elo/67954526406a4efe9eb7d176 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 51504 W: 13188 L: 12995 D: 25321 Ptnml(0-2): 54, 5658, 14128, 5865, 47 https://tests.stockfishchess.org/tests/live_elo/67954961406a4efe9eb7d251 closes https://github.com/official-stockfish/Stockfish/pull/5822 Bench: 2081366 --- src/history.h | 5 ----- src/position.cpp | 24 ++++-------------------- src/position.h | 4 ---- src/search.cpp | 5 +---- src/search.h | 1 - 5 files changed, 5 insertions(+), 34 deletions(-) diff --git a/src/history.h b/src/history.h index 4543fc55e..654ee19f2 100644 --- a/src/history.h +++ b/src/history.h @@ -54,10 +54,6 @@ inline int pawn_structure_index(const Position& pos) { return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); } -inline int major_piece_index(const Position& pos) { - return pos.major_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} - inline int minor_piece_index(const Position& pos) { return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); } @@ -136,7 +132,6 @@ using PawnHistory = Statskey = st->materialKey = 0; - st->majorPieceKey = st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -360,16 +359,12 @@ void Position::set_state() const { { st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; - if (type_of(pc) >= ROOK) - st->majorPieceKey ^= Zobrist::psq[pc][s]; - - else + if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][s]; } else { - st->majorPieceKey ^= Zobrist::psq[pc][s]; st->minorPieceKey ^= Zobrist::psq[pc][s]; } } @@ -742,7 +737,6 @@ void Position::do_move(Move m, do_castling(us, from, to, rfrom, rto); k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; - st->majorPieceKey ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; captured = NO_PIECE; } @@ -773,10 +767,7 @@ void Position::do_move(Move m, st->nonPawnMaterial[them] -= PieceValue[captured]; st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq]; - if (type_of(captured) >= ROOK) - st->majorPieceKey ^= Zobrist::psq[captured][capsq]; - - else + if (type_of(captured) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[captured][capsq]; } @@ -858,10 +849,7 @@ void Position::do_move(Move m, st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; - if (promotionType >= ROOK) - st->majorPieceKey ^= Zobrist::psq[promotion][to]; - - else + if (promotionType <= BISHOP) st->minorPieceKey ^= Zobrist::psq[promotion][to]; // Update material @@ -881,14 +869,10 @@ void Position::do_move(Move m, if (type_of(pc) == KING) { - st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - else if (type_of(pc) >= ROOK) - st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - - else + else if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } diff --git a/src/position.h b/src/position.h index d955d9f98..53269c197 100644 --- a/src/position.h +++ b/src/position.h @@ -43,7 +43,6 @@ struct StateInfo { // Copied when making a move Key materialKey; Key pawnKey; - Key majorPieceKey; Key minorPieceKey; Key nonPawnKey[COLOR_NB]; Value nonPawnMaterial[COLOR_NB]; @@ -154,7 +153,6 @@ class Position { Key key() const; Key material_key() const; Key pawn_key() const; - Key major_piece_key() const; Key minor_piece_key() const; Key non_pawn_key(Color c) const; @@ -305,8 +303,6 @@ inline Key Position::pawn_key() const { return st->pawnKey; } inline Key Position::material_key() const { return st->materialKey; } -inline Key Position::major_piece_key() const { return st->majorPieceKey; } - inline Key Position::minor_piece_key() const { return st->minorPieceKey; } inline Key Position::non_pawn_key(Color c) const { return st->nonPawnKey[c]; } diff --git a/src/search.cpp b/src/search.cpp index 316201886..e07c719eb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -87,7 +87,6 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { const Color us = pos.side_to_move(); const auto m = (ss - 1)->currentMove; const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(pos)]; - const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)]; const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)]; const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; @@ -95,7 +94,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; - return (6922 * pcv + 3837 * macv + 6238 * micv + 7490 * (wnpcv + bnpcv) + 6270 * cntcv); + return (7000 * pcv + 6300 * micv + 7550 * (wnpcv + bnpcv) + 6320 * cntcv); } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -516,7 +515,6 @@ void Search::Worker::clear() { captureHistory.fill(-631); pawnHistory.fill(-1210); pawnCorrectionHistory.fill(0); - majorPieceCorrectionHistory.fill(0); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); @@ -1439,7 +1437,6 @@ moves_loop: // When in check, search starts here -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] << bonus * 114 / 128; - thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 163 / 128; thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 146 / 128; thisThread->nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; diff --git a/src/search.h b/src/search.h index 3a1b3a77d..326480e4d 100644 --- a/src/search.h +++ b/src/search.h @@ -288,7 +288,6 @@ class Worker { PawnHistory pawnHistory; CorrectionHistory pawnCorrectionHistory; - CorrectionHistory majorPieceCorrectionHistory; CorrectionHistory minorPieceCorrectionHistory; CorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; CorrectionHistory continuationCorrectionHistory; From a016abd6982d1f8f1b0f5175b0005c8749c0925b Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:16:09 +0100 Subject: [PATCH 429/834] Decrease all stats malus according to move count Passed STC: https://tests.stockfishchess.org/tests/view/6794c4634f7de645171fb341 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 28096 W: 7412 L: 7106 D: 13578 Ptnml(0-2): 97, 3194, 7148, 3524, 85 Passed LTC: https://tests.stockfishchess.org/tests/view/6794ea13406a4efe9eb7d06b LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 58086 W: 15049 L: 14684 D: 28353 Ptnml(0-2): 27, 6344, 15957, 6667, 48 closes https://github.com/official-stockfish/Stockfish/pull/5823 Bench: 1711170 --- src/search.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e07c719eb..7317b7e43 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -125,7 +125,8 @@ void update_all_stats(const Position& pos, ValueList& quietsSearched, ValueList& capturesSearched, Depth depth, - bool isTTMove); + bool isTTMove, + int moveCount); } // namespace @@ -1372,7 +1373,7 @@ moves_loop: // When in check, search starts here // we update the stats of searched moves. else if (bestMove) update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - bestMove == ttData.move); + bestMove == ttData.move, moveCount); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1792,14 +1793,15 @@ void update_all_stats(const Position& pos, ValueList& quietsSearched, ValueList& capturesSearched, Depth depth, - bool isTTMove) { + bool isTTMove, + int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; int bonus = stat_bonus(depth) + 300 * isTTMove; - int malus = stat_malus(depth); + int malus = stat_malus(depth) - 34 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { From ebdc7ba2da13b97579871b8a621124e3778bd495 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 25 Jan 2025 13:49:47 -0800 Subject: [PATCH 430/834] Refactor prior countermove bonus Passed simplification STC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 155424 W: 40252 L: 40159 D: 75013 Ptnml(0-2): 511, 18655, 39328, 18666, 552 https://tests.stockfishchess.org/tests/view/6794084fe96bfb672ad18d90 Passed rebased simplification LTC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 103944 W: 26567 L: 26427 D: 50950 Ptnml(0-2): 69, 11640, 28418, 11772, 73 https://tests.stockfishchess.org/tests/view/67955c9a406a4efe9eb7d7e4 closes https://github.com/official-stockfish/Stockfish/pull/5825 Bench: 1839554 --- src/search.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7317b7e43..5eb5049a7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1381,23 +1381,21 @@ moves_loop: // When in check, search starts here int bonusScale = (118 * (depth > 5) + 37 * !allNode + 169 * ((ss - 1)->moveCount > 8) + 128 * (!ss->inCheck && bestValue <= ss->staticEval - 102) + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) - + 80 * ((ss - 1)->isTTMove)); - - // Proportional to "how much damage we have to undo" - bonusScale += std::min(-(ss - 1)->statScore / 106, 318); + + 80 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = stat_bonus(depth) * bonusScale / 32; + const int scaledBonus = stat_bonus(depth) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 436 / 1024); + scaledBonus * 436 / 32768); - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 207 / 1024; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] + << scaledBonus * 207 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1195 / 1024; + << scaledBonus * 1195 / 32768; } else if (priorCapture && prevSq != SQ_NONE) From c180163540e18e818e6f5361e8b28d0785422a69 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sun, 26 Jan 2025 15:32:45 +0100 Subject: [PATCH 431/834] Update fastchess CI Version This PR updates the fastchess version used as part of the CI to the one used on fishtest, see https://github.com/official-stockfish/fishtest/pull/2180. Also change the name/repo from fast-chess to fastchess. closes https://github.com/official-stockfish/Stockfish/pull/5826 No functional change --- .github/workflows/games.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index f0bca442f..a50ca594b 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -19,22 +19,22 @@ jobs: working-directory: Stockfish/src run: make -j build debug=yes - - name: Checkout fast-chess repo + - name: Checkout fastchess repo uses: actions/checkout@v4 with: - repository: Disservin/fast-chess - path: fast-chess - ref: d54af1910d5479c669dc731f1f54f9108a251951 + repository: Disservin/fastchess + path: fastchess + ref: 894616028492ae6114835195f14a899f6fa237d3 persist-credentials: false - - name: fast-chess build - working-directory: fast-chess + - name: fastchess build + working-directory: fastchess run: make -j - name: Run games - working-directory: fast-chess + working-directory: fastchess run: | - ./fast-chess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\ + ./fastchess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\ -engine name=sf1 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\ -engine name=sf2 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\ -ratinginterval 1 -report penta=true -each proto=uci tc=4+0.04 -log file=fast.log | tee fast.out From f50d52aa7f0141d0a7676c68d04afaf2b44160d7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 26 Jan 2025 03:14:15 +0300 Subject: [PATCH 432/834] No Ply Restriction in the condition that limits the depth extension to a certain point No Ply Restriction in the condition that limits the depth extension to a certain point. Passed again LTC rebased: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 195108 W: 49916 L: 49872 D: 95320 Ptnml(0-2): 170, 21846, 53464, 21918, 156 https://tests.stockfishchess.org/tests/view/6795542a406a4efe9eb7d361 closes https://github.com/official-stockfish/Stockfish/pull/5824 Bench: 1767398 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5eb5049a7..9a5425fc7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1239,7 +1239,7 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && ss->ply <= thisThread->rootDepth * 2) + if (move == ttData.move && thisThread->rootDepth > 8) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); From 4a77fb213f7f620769900a3aa9c97d6745992e77 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 27 Jan 2025 15:24:37 -0800 Subject: [PATCH 433/834] Clean up corrhist Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 89056 W: 23225 L: 23067 D: 42764 Ptnml(0-2): 292, 9688, 24470, 9726, 352 https://tests.stockfishchess.org/tests/view/679816b2ae346be6da0ee8e7 closes https://github.com/official-stockfish/Stockfish/pull/5830 Bench: 1767398 --- src/history.h | 9 ++------- src/search.cpp | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/history.h b/src/history.h index 654ee19f2..bec055d52 100644 --- a/src/history.h +++ b/src/history.h @@ -133,20 +133,15 @@ using PawnHistory = Stats +template struct CorrHistTypedef { - using type = Stats; -}; - -template<> -struct CorrHistTypedef { using type = Stats; }; diff --git a/src/search.cpp b/src/search.cpp index 9a5425fc7..89233e85b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -83,11 +83,11 @@ constexpr int futility_move_count(bool improving, Depth depth) { return (3 + depth * depth) / (2 - improving); } -int correction_value(const Worker& w, const Position& pos, const Stack* ss) { +int correction_value(const Worker& w, const Position& pos, const Stack* const ss) { const Color us = pos.side_to_move(); const auto m = (ss - 1)->currentMove; - const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(pos)]; - const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)]; + const auto pcv = w.pawnCorrectionHistory[pawn_structure_index(pos)][us]; + const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; const auto cntcv = @@ -99,10 +99,31 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. -Value to_corrected_static_eval(Value v, const int cv) { +Value to_corrected_static_eval(const Value v, const int cv) { return std::clamp(v + cv / 131072, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } +void update_correction_history(const Position& pos, + Stack* const ss, + Search::Worker& workerThread, + const int bonus) { + const Move m = (ss - 1)->currentMove; + const Color us = pos.side_to_move(); + + static constexpr int nonPawnWeight = 165; + + workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] + << bonus * 114 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; + workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] + << bonus * nonPawnWeight / 128; + workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] + << bonus * nonPawnWeight / 128; + + if (m.is_ok()) + (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; +} + // History and stats update bonus, based on depth int stat_bonus(Depth d) { return std::min(154 * d - 102, 1661); } @@ -1429,21 +1450,9 @@ moves_loop: // When in check, search starts here && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { - const auto m = (ss - 1)->currentMove; - constexpr int nonPawnWeight = 165; - auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] - << bonus * 114 / 128; - thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 146 / 128; - thisThread->nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] - << bonus * nonPawnWeight / 128; - thisThread->nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] - << bonus * nonPawnWeight / 128; - - if (m.is_ok()) - (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; + update_correction_history(pos, ss, *thisThread, bonus); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); From 7684b6e4d8788d24e679d7fe737d1b34bad913b5 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Sat, 18 Jan 2025 20:58:14 +0100 Subject: [PATCH 434/834] Don't increase rule50 when doing null moves also prefetch a bit earlier while we're at it passed STC: https://tests.stockfishchess.org/tests/view/678c0860f4dc0a8b4ae8cf58 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67328 W: 17608 L: 17418 D: 32302 Ptnml(0-2): 256, 7905, 17156, 8087, 260 passed LTC: https://tests.stockfishchess.org/tests/view/678c1a56f4dc0a8b4ae8cfb1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 340896 W: 86577 L: 86685 D: 167634 Ptnml(0-2): 291, 38325, 93332, 38201, 299 closes https://github.com/official-stockfish/Stockfish/pull/5831 Bench: 1910281 --- src/position.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index e5df1a9ec..fdaec13ad 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1023,11 +1023,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { st->next = &newSt; st = &newSt; - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - if (st->epSquare != SQ_NONE) { st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; @@ -1035,9 +1030,13 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { } st->key ^= Zobrist::side; - ++st->rule50; prefetch(tt.first_entry(key())); + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; + st->pliesFromNull = 0; sideToMove = ~sideToMove; From 5ef1f2b13276c11814187b4ee115454803d399e3 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 29 Jan 2025 00:19:22 -0800 Subject: [PATCH 435/834] Refactor prior reduction Make index of reduction consistent with rest of Stack closes https://github.com/official-stockfish/Stockfish/pull/5832 No functional change --- src/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 89233e85b..fbc97bb41 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -600,8 +600,8 @@ Value Search::Worker::search( Value bestValue, value, eval, maxValue, probCutBeta; bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, ttCapture; - int priorReduction = ss->reduction; - ss->reduction = 0; + int priorReduction = (ss - 1)->reduction; + (ss - 1)->reduction = 0; Piece movedPiece; ValueList capturesSearched; @@ -1215,10 +1215,10 @@ moves_loop: // When in check, search starts here Depth d = std::max( 1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); - (ss + 1)->reduction = newDepth - d; + ss->reduction = newDepth - d; - value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); - (ss + 1)->reduction = 0; + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high From 40e0486d02fd8682c8d369c20bf24b6a5ebc9927 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 30 Jan 2025 05:36:53 +0300 Subject: [PATCH 436/834] Make IIR for PvNodes less aggressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In line with previous experiments on improving scaling of IIR. Now it disables IIR for pv nodes with depth <= 2, so disallowing for it to perform a qsearch dive. Fixed games STC: https://tests.stockfishchess.org/tests/view/679ae6a951037ccaf3e30fb3 Elo: -10.36 ± 2.5 (95%) LOS: 0.0% Total: 20020 W: 4902 L: 5499 D: 9619 Ptnml(0-2): 128, 2653, 4976, 2194, 59 Passed VVLTC with STC bounds: https://tests.stockfishchess.org/tests/view/67954f2e406a4efe9eb7d266 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 196758 W: 50725 L: 50258 D: 95775 Ptnml(0-2): 21, 18153, 61564, 18620, 21 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/6795a26bf6281b7d7b18698b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 323092 W: 83679 L: 82857 D: 156556 Ptnml(0-2): 48, 29475, 101659, 30335, 29 closes https://github.com/official-stockfish/Stockfish/pull/5834 Bench: 3464332 --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fbc97bb41..2f0b164ab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -869,13 +869,9 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. // (* Scaler) Especially if they make IIR more aggressive. - if ((PvNode || (cutNode && depth >= 7)) && !ttData.move) + if (((PvNode || cutNode) && depth >= 7 - 4 * PvNode) && !ttData.move) depth -= 2; - // Use qsearch if depth <= 0 - if (depth <= 0) - return qsearch(pos, ss, alpha, beta); - // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. From 7690fac5cf4fcce779573d2104d9a81db563de28 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 30 Jan 2025 15:24:25 -0800 Subject: [PATCH 437/834] Simp probcut disable condition Disable probcut check when we the ttValue is not at least probCutBeta, regardless of tt depth. Passed simplification STC LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 60896 W: 16030 L: 15835 D: 29031 Ptnml(0-2): 220, 7164, 15507, 7315, 242 https://tests.stockfishchess.org/tests/view/679c0a3251037ccaf3e3141e Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 76644 W: 19557 L: 19392 D: 37695 Ptnml(0-2): 50, 8486, 21104, 8613, 69 https://tests.stockfishchess.org/tests/view/679c380b0774dfd78deaf35c closes https://github.com/official-stockfish/Stockfish/pull/5840 Bench: 3543770 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2f0b164ab..d6e9bb75d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -882,7 +882,7 @@ Value Search::Worker::search( // probCut there and in further interactions with transposition table cutoff // depth is set to depth - 3 because probCut search has depth set to depth - 4 // but we also do a move before it. So effective depth is equal to depth - 3. - && !(ttData.depth >= depth - 3 && is_valid(ttData.value) && ttData.value < probCutBeta)) + && !(is_valid(ttData.value) && ttData.value < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); From c83ddd9e4b9d5c57c9398565eee52c3ea5940f80 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 30 Jan 2025 22:36:00 +0100 Subject: [PATCH 438/834] Tweak correction history factors The values are taken from this tuning https://tests.stockfishchess.org/tests/view/679c4e150774dfd78deaf376 which added also a new material correction history. The full tune doesn't work but ignoring the new history results in this changes. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 102368 W: 27057 L: 26638 D: 48673 Ptnml(0-2): 394, 12031, 25949, 12382, 428 https://tests.stockfishchess.org/tess/view/679d2ca70774dfd78deaf796 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 55044 W: 14215 L: 13855 D: 26974 Ptnml(0-2): 43, 5956, 15172, 6300, 51 https://tests.stockfishchess.org/tests/view/679d30be0774dfd78deafda2 closes https://github.com/official-stockfish/Stockfish/pull/5841 Bench: 3068583 --- src/history.h | 2 +- src/position.cpp | 13 ++----------- src/search.cpp | 11 ++++++----- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/history.h b/src/history.h index bec055d52..9ae7bdadc 100644 --- a/src/history.h +++ b/src/history.h @@ -132,7 +132,7 @@ using PawnHistory = Statskey = st->materialKey = 0; + st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -362,11 +363,6 @@ void Position::set_state() const { if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][s]; } - - else - { - st->minorPieceKey ^= Zobrist::psq[pc][s]; - } } } @@ -867,12 +863,7 @@ void Position::do_move(Move m, { st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - if (type_of(pc) == KING) - { - st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - } - - else if (type_of(pc) <= BISHOP) + if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } diff --git a/src/search.cpp b/src/search.cpp index d6e9bb75d..a35aad45a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,7 +94,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; - return (7000 * pcv + 6300 * micv + 7550 * (wnpcv + bnpcv) + 6320 * cntcv); + return 7037 * pcv + 6671 * micv + 7631 * (wnpcv + bnpcv) + 6362 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -110,18 +110,19 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - static constexpr int nonPawnWeight = 165; + static constexpr int nonPawnWeight = 159; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 114 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; + << bonus * 104 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) - (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; + (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + << bonus * 146 / 128; } // History and stats update bonus, based on depth From 344e89275abe2b67c0be6a5013def81725c5a5c2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 29 Jan 2025 13:04:01 -0800 Subject: [PATCH 439/834] Simplify Away Quadruple Extensions Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 95856 W: 24551 L: 24404 D: 46901 Ptnml(0-2): 85, 10621, 26364, 10778, 80 https://tests.stockfishchess.org/tests/view/679a9aedae346be6da0eebd6 Passed Non-regression VLTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 157536 W: 40000 L: 39921 D: 77615 Ptnml(0-2): 43, 16416, 45775, 16487, 47 https://tests.stockfishchess.org/tests/view/679aed8f51037ccaf3e30fbf Passed Non-regression VVLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 51598 W: 13345 L: 13172 D: 25081 Ptnml(0-2): 0, 4735, 16162, 4896, 6 https://tests.stockfishchess.org/tests/view/679d368b0774dfd78deb0163 closes https://github.com/official-stockfish/Stockfish/pull/5843 Bench: 2399312 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a35aad45a..9af7a115f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1097,12 +1097,9 @@ moves_loop: // When in check, search starts here int doubleMargin = 249 * PvNode - 194 * !ttCapture - corrValAdj; int tripleMargin = 94 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj; - int quadMargin = - 394 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj; extension = 1 + (value < singularBeta - doubleMargin) - + (value < singularBeta - tripleMargin) - + (value < singularBeta - quadMargin); + + (value < singularBeta - tripleMargin); depth += ((!PvNode) && (depth < 15)); } From dabffbceffee2e68352bf5865987d5b2a91ce410 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 2 Feb 2025 06:06:13 +0300 Subject: [PATCH 440/834] Make pruning at ttpv nodes more aggressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continuation of work done by @FauziAkram and @Viren6 They had a series of patches that decrease pruning for ttPv nodes - and it passed as a gainer at lower time controls while revert passed as a gainer at higher time controls. So it's a logical continuation of this work that increases pruning for ttPv nodes in hopes of scaling to longer TCs. Fixed games STC: https://tests.stockfishchess.org/tests/view/679ee3910774dfd78deb0efd Elo: -4.98 ± 2.1 (95%) LOS: 0.0% Total: 28584 W: 7229 L: 7639 D: 13716 Ptnml(0-2): 143, 3579, 7219, 3247, 104 nElo: -9.54 ± 4.0 (95%) PairsRatio: 0.90 Passed VVLTC with STC bounds: https://tests.stockfishchess.org/tests/view/679d21f70774dfd78deaf553 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 323282 W: 83729 L: 83105 D: 156448 Ptnml(0-2): 37, 29842, 101269, 30446, 47 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/679e7a970774dfd78deb0cd3 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 113712 W: 29485 L: 29051 D: 55176 Ptnml(0-2): 13, 10376, 35640, 10818, 9 closes https://github.com/official-stockfish/Stockfish/pull/5844 Bench: 2964045 --- src/search.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9af7a115f..60f716cd3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,6 +999,12 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); + // Increase reduction for ttPv nodes (*Scaler) + // Smaller or even negative value is better for short time controls + // Bigger value is better for long time controls + if (ss->ttPv) + r += 1024; + // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) @@ -1156,7 +1162,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + r -= 2061 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; if (PvNode) r -= 1018; From 65a9a391e90cf2551a7b7beb26d5ee7c3ccf585b Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 2 Feb 2025 13:49:54 +0100 Subject: [PATCH 441/834] Silence clang-format issue No functional change --- src/tt.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tt.h b/src/tt.h index 065380ca8..26b63c1ad 100644 --- a/src/tt.h +++ b/src/tt.h @@ -53,6 +53,8 @@ struct TTData { bool is_pv; TTData() = delete; + + // clang-format off TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) : move(m), value(v), @@ -60,6 +62,7 @@ struct TTData { depth(d), bound(b), is_pv(pv) {}; + // clang-format on }; From 9f0844c101fda0526637cb8468b80e67699ea451 Mon Sep 17 00:00:00 2001 From: Kenneth Lee <71492754+kennethlee33@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:14:08 -0800 Subject: [PATCH 442/834] Simplify extensions depth increase condition Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 42784 W: 11198 L: 10979 D: 20607 Ptnml(0-2): 166, 5024, 10822, 5185, 195 https://tests.stockfishchess.org/tests/view/6795b6f8f6281b7d7b1869d6 Failed LTC: LLR: -2.95 (-2.94,2.94) <-1.75,0.25> Total: 283614 W: 72046 L: 72587 D: 138981 Ptnml(0-2): 241, 32097, 77647, 31606, 216 https://tests.stockfishchess.org/tests/view/6795cbb6f6281b7d7b186a07 Passed VLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 59678 W: 15387 L: 15211 D: 29080 Ptnml(0-2): 23, 6169, 17273, 6357, 17 https://tests.stockfishchess.org/tests/view/6795c6dbf6281b7d7b1869f9 closes https://github.com/official-stockfish/Stockfish/pull/5827 Bench: 3088494 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1e5e51e63..a345bb69f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -125,6 +125,7 @@ jundery Justin Blanchard (UncombedCoconut) Kelly Wilson Ken Takusagawa +Kenneth Lee (kennethlee33) Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) diff --git a/src/search.cpp b/src/search.cpp index 60f716cd3..5b26b6657 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1107,7 +1107,7 @@ moves_loop: // When in check, search starts here extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += ((!PvNode) && (depth < 15)); + depth += (depth < 15); } // Multi-cut pruning From d46c0b6f492bc00fa0a91d91f18e474c14541330 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Mon, 27 Jan 2025 08:55:15 +0100 Subject: [PATCH 443/834] Add cursed win checks to CI matetrack tests This PR adds a run for the `matecheck.py` script from the matetrack repo with the option `--syzygy50MoveRule false`. The new tests guard against a re-introduction of the bugs recently fixed by https://github.com/official-stockfish/Stockfish/pull/5814. closes https://github.com/official-stockfish/Stockfish/pull/5829 No functional change --- .github/workflows/matetrack.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index dc8dff8d5..85c2be3e7 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -24,7 +24,7 @@ jobs: with: repository: vondele/matetrack path: matetrack - ref: 814160f82e6428ed2f6522dc06c2a6fa539cd413 + ref: 4f8a80860ed8f3607f05a9195df8b40203bdc360 persist-credentials: false - name: matetrack install deps @@ -50,5 +50,22 @@ jobs: - name: Run matetrack working-directory: matetrack run: | - python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out ! grep "issues were detected" matecheckout.out > /dev/null + + - name: Run matetrack with --syzygy50MoveRule false + working-directory: matetrack + run: | + grep 5men cursed.epd > cursed5.epd + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed.out + ! grep "issues were detected" matecheckcursed.out > /dev/null + + - name: Verify mate and TB win count for matecheckcursed.out + working-directory: matetrack + run: | + mates=$(grep "Found mates:" matecheckcursed.out | awk '{print $3}') + tbwins=$(grep "Found TB wins:" matecheckcursed.out | awk '{print $4}') + if [ $(($mates + $tbwins)) -ne 32 ]; then + echo "Sum of mates and TB wins is not 32 in matecheckcursed.out" >&2 + exit 1 + fi From 2a1ab11ab019ce588f4f4f4b175ddd6e60d1df36 Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Fri, 31 Jan 2025 15:41:03 +0100 Subject: [PATCH 444/834] Micro-optimization for SEE: remove a superfluous condition This condition can never be true, it's superfluous. It never triggers even with a bench 16 1 20 run. To met the condition it would imply that the previous recapture was done by a higher rated piece than a Queen. This is only the case when the King recaptures and that's already handled in line 1161: (return (attackers & ~pieces(stm)) ? res ^ 1). closes https://github.com/official-stockfish/Stockfish/pull/5839 No functional change --- src/position.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 439d4ae9d..37871aa24 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1137,8 +1137,9 @@ bool Position::see_ge(Move m, int threshold) const { else if ((bb = stmAttackers & pieces(QUEEN))) { - if ((swap = QueenValue - swap) < res) - break; + swap = QueenValue - swap; + // implies that the previous recapture was done by a higher rated piece than a Queen (King is excluded) + assert(swap >= res); occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) From 8c73472ac84314d69c28dd886ecbf916a75ebf96 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 31 Jan 2025 17:36:55 -0800 Subject: [PATCH 445/834] Simplify depth increase condition further Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 51232 W: 13560 L: 13351 D: 24321 Ptnml(0-2): 183, 6075, 12920, 6226, 212 https://tests.stockfishchess.org/tests/view/679d7b2b0774dfd78deb043f Passed Non-regression LTC (v. #5827): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 172398 W: 44108 L: 44042 D: 84248 Ptnml(0-2): 122, 19207, 47489, 19245, 136 https://tests.stockfishchess.org/tests/view/679d7fb10774dfd78deb05d2 Passed Non-regression VLTC (v. #5827): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 388540 W: 99314 L: 99464 D: 189762 Ptnml(0-2): 89, 40454, 113350, 40272, 105 https://tests.stockfishchess.org/tests/view/679da3be0774dfd78deb0ad4 closes https://github.com/official-stockfish/Stockfish/pull/5846 Bench: 2688175 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5b26b6657..1b5463117 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1107,7 +1107,7 @@ moves_loop: // When in check, search starts here extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += (depth < 15); + depth++; } // Multi-cut pruning From c12dbdedd9366bc7ffb29b355038bc7dea5f9c48 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 2 Feb 2025 18:05:16 +0100 Subject: [PATCH 446/834] Disallow same option being added twice Now exits during startup. ``` ./stockfish Stockfish dev-20250202-243c7c6a by the Stockfish developers (see AUTHORS file) x1,5,0,10,0.5,0.0020 Option: "x1" was already added! ``` i.e. prevents and helps debug this case ```cpp int x1 = 5; TUNE(x1); TUNE(x1); ``` closes https://github.com/official-stockfish/Stockfish/pull/5847 No functional change --- src/engine.cpp | 121 ++++++++++++++++++++++++++++------------------ src/tune.cpp | 2 +- src/ucioption.cpp | 40 +++++++-------- src/ucioption.h | 8 +-- 4 files changed, 100 insertions(+), 71 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index d835fc8e4..6c8799a15 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -59,57 +59,83 @@ Engine::Engine(std::optional path) : NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { pos.set(StartFEN, false, &states->back()); - options["Debug Log File"] << Option("", [](const Option& o) { - start_logger(o); - return std::nullopt; - }); - options["NumaPolicy"] << Option("auto", [this](const Option& o) { - set_numa_config_from_option(o); - return numa_config_information_as_string() + "\n" - + thread_allocation_information_as_string(); - }); + options.add( // + "Debug Log File", Option("", [](const Option& o) { + start_logger(o); + return std::nullopt; + })); - options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - resize_threads(); - return thread_allocation_information_as_string(); - }); + options.add( // + "NumaPolicy", Option("auto", [this](const Option& o) { + set_numa_config_from_option(o); + return numa_config_information_as_string() + "\n" + + thread_allocation_information_as_string(); + })); - options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { - set_tt_size(o); - return std::nullopt; - }); + options.add( // + "Threads", Option(1, 1, 1024, [this](const Option&) { + resize_threads(); + return thread_allocation_information_as_string(); + })); - options["Clear Hash"] << Option([this](const Option&) { - search_clear(); - return std::nullopt; - }); - options["Ponder"] << Option(false); - options["MultiPV"] << Option(1, 1, MAX_MOVES); - options["Skill Level"] << Option(20, 0, 20); - options["Move Overhead"] << Option(10, 0, 5000); - options["nodestime"] << Option(0, 0, 10000); - options["UCI_Chess960"] << Option(false); - options["UCI_LimitStrength"] << Option(false); - options["UCI_Elo"] << Option(Stockfish::Search::Skill::LowestElo, - Stockfish::Search::Skill::LowestElo, - Stockfish::Search::Skill::HighestElo); - options["UCI_ShowWDL"] << Option(false); - options["SyzygyPath"] << Option("", [](const Option& o) { - Tablebases::init(o); - return std::nullopt; - }); - options["SyzygyProbeDepth"] << Option(1, 1, 100); - options["Syzygy50MoveRule"] << Option(true); - options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) { - load_big_network(o); - return std::nullopt; - }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { - load_small_network(o); - return std::nullopt; - }); + options.add( // + "Hash", Option(16, 1, MaxHashMB, [this](const Option& o) { + set_tt_size(o); + return std::nullopt; + })); + + options.add( // + "Clear Hash", Option([this](const Option&) { + search_clear(); + return std::nullopt; + })); + + options.add( // + "Ponder", Option(false)); + + options.add( // + "MultiPV", Option(1, 1, MAX_MOVES)); + + options.add("Skill Level", Option(20, 0, 20)); + + options.add("Move Overhead", Option(10, 0, 5000)); + + options.add("nodestime", Option(0, 0, 10000)); + + options.add("UCI_Chess960", Option(false)); + + options.add("UCI_LimitStrength", Option(false)); + + options.add("UCI_Elo", + Option(Stockfish::Search::Skill::LowestElo, Stockfish::Search::Skill::LowestElo, + Stockfish::Search::Skill::HighestElo)); + + options.add("UCI_ShowWDL", Option(false)); + + options.add( // + "SyzygyPath", Option("", [](const Option& o) { + Tablebases::init(o); + return std::nullopt; + })); + + options.add("SyzygyProbeDepth", Option(1, 1, 100)); + + options.add("Syzygy50MoveRule", Option(true)); + + options.add("SyzygyProbeLimit", Option(7, 0, 7)); + + options.add( // + "EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) { + load_big_network(o); + return std::nullopt; + })); + + options.add( // + "EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) { + load_small_network(o); + return std::nullopt; + })); load_networks(); resize_threads(); @@ -340,5 +366,4 @@ std::string Engine::thread_allocation_information_as_string() const { return ss.str(); } - } diff --git a/src/tune.cpp b/src/tune.cpp index aff96c8cb..f53a0eb52 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -55,7 +55,7 @@ void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& if (TuneResults.count(n)) v = TuneResults[n]; - (*opts)[n] << Option(v, r(v).first, r(v).second, on_tune); + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); LastOption = &((*opts)[n]); // Print formatted parameters, ready to be copy-pasted in Fishtest diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 56cf41edc..a76bd3ace 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -57,17 +58,31 @@ void OptionsMap::setoption(std::istringstream& is) { sync_cout << "No such option: " << name << sync_endl; } -Option OptionsMap::operator[](const std::string& name) const { +const Option& OptionsMap::operator[](const std::string& name) const { auto it = options_map.find(name); - return it != options_map.end() ? it->second : Option(this); + assert(it != options_map.end()); + return it->second; } -Option& OptionsMap::operator[](const std::string& name) { +// Inits options and assigns idx in the correct printing order +void OptionsMap::add(const std::string& name, const Option& option) { if (!options_map.count(name)) - options_map[name] = Option(this); - return options_map[name]; + { + static size_t insert_order = 0; + + options_map[name] = option; + + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } + else + { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } } + std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } Option::Option(const OptionsMap* map) : @@ -130,19 +145,6 @@ bool Option::operator==(const char* s) const { bool Option::operator!=(const char* s) const { return !(*this == s); } -// Inits options and assigns idx in the correct printing order - -void Option::operator<<(const Option& o) { - - static size_t insert_order = 0; - - auto p = this->parent; - *this = o; - - this->parent = p; - idx = insert_order++; -} - // Updates currentValue and triggers on_change() action. It's up to // the GUI to check for option's limits, but we could receive the new value // from the user by console window, so let's check the bounds anyway. @@ -161,7 +163,7 @@ Option& Option::operator=(const std::string& v) { std::string token; std::istringstream ss(defaultValue); while (ss >> token) - comboMap[token] << Option(); + comboMap.add(token, Option()); if (!comboMap.count(v) || v == "var") return *this; } diff --git a/src/ucioption.h b/src/ucioption.h index c9f6787d3..3d7386c30 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -54,12 +54,13 @@ class Option { friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + int operator<<(const Option&) = delete; + private: friend class OptionsMap; friend class Engine; friend class Tune; - void operator<<(const Option&); std::string defaultValue, currentValue, type; int min, max; @@ -82,8 +83,9 @@ class OptionsMap { void setoption(std::istringstream&); - Option operator[](const std::string&) const; - Option& operator[](const std::string&); + const Option& operator[](const std::string&) const; + + void add(const std::string&, const Option& option); std::size_t count(const std::string&) const; From fccc6f624e0ea9bd55064ab839bbc720b2816d69 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:37:13 +0100 Subject: [PATCH 447/834] Reduce full depth search twice Passed STC: https://tests.stockfishchess.org/tests/view/679f429e0774dfd78deb10a5 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 71584 W: 18905 L: 18529 D: 34150 Ptnml(0-2): 302, 8372, 18081, 8722, 315 Passed LTC: https://tests.stockfishchess.org/tests/view/679f72a00774dfd78deb1102 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 353952 W: 91007 L: 90024 D: 172921 Ptnml(0-2): 375, 39163, 96921, 40138, 379 closes https://github.com/official-stockfish/Stockfish/pull/5848 Bench: 3642363 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1b5463117..d23e91ef8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1248,8 +1248,8 @@ moves_loop: // When in check, search starts here r += 2111; // Note that if expected reduction is high, we reduce search depth here - value = - -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3444), !cutNode); + value = -search(pos, ss + 1, -(alpha + 1), -alpha, + newDepth - (r > 3444) - (r > 5588 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, From 9ed1725e7842df98aee612201ed11f3bda724926 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:18:21 +0100 Subject: [PATCH 448/834] Simplify bonusScale formula Passed STC: https://tests.stockfishchess.org/tests/view/679ea7bc0774dfd78deb0d68 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 47680 W: 12575 L: 12364 D: 22741 Ptnml(0-2): 179, 5589, 12139, 5708, 225 Passed LTC: https://tests.stockfishchess.org/tests/view/679eb7760774dfd78deb0dbb LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 314220 W: 80110 L: 80189 D: 153921 Ptnml(0-2): 265, 35121, 86420, 35036, 268 closes https://github.com/official-stockfish/Stockfish/pull/5849 Bench: 3161782 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d23e91ef8..a33150810 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1399,10 +1399,10 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (118 * (depth > 5) + 37 * !allNode + 169 * ((ss - 1)->moveCount > 8) - + 128 * (!ss->inCheck && bestValue <= ss->staticEval - 102) - + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) - + 80 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); + int bonusScale = (125 * (depth > 5) + 176 * ((ss - 1)->moveCount > 8) + + 135 * (!ss->inCheck && bestValue <= ss->staticEval - 102) + + 122 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) + + 87 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); bonusScale = std::max(bonusScale, 0); From 09623abbe84341baf1ec52383da4d01fb683e6d0 Mon Sep 17 00:00:00 2001 From: Kenneth Lee <71492754+kennethlee33@users.noreply.github.com> Date: Sat, 1 Feb 2025 18:41:08 -0800 Subject: [PATCH 449/834] Simplify cutoffCnt further Based off [Simplify cutoffCnt](https://github.com/official-stockfish/Stockfish/commit/69be04d38e10003853e78e4aa2b32aa252a82850) commit Original [commit](https://github.com/kennethlee33/Stockfish/commit/a77a895c3b7460f86b11a3ddfe3528f5be1276b9) adding extension condition seems to not be improving strength anymore Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 54176 W: 14331 L: 14125 D: 25720 Ptnml(0-2): 261, 6340, 13676, 6554, 257 https://tests.stockfishchess.org/tests/view/679edb7c0774dfd78deb0eed Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 267198 W: 68148 L: 68179 D: 130871 Ptnml(0-2): 232, 30051, 73055, 30038, 223 https://tests.stockfishchess.org/tests/view/679ef2c70774dfd78deb0f43 closes https://github.com/official-stockfish/Stockfish/pull/5851 Bench: 3119355 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a33150810..f98098701 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1348,7 +1348,7 @@ moves_loop: // When in check, search starts here if (value >= beta) { - ss->cutoffCnt += (extension < 2); + ss->cutoffCnt++; assert(value >= beta); // Fail high break; } From 3b8bfeb38a3cfb7805e61f0877e186d09805a6e0 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 3 Feb 2025 02:52:44 +0300 Subject: [PATCH 450/834] Do less aggressive pruning for higher movecounts Move part of heuristic that makes reduction less before pruning stage. Passed STC: https://tests.stockfishchess.org/tests/view/679fdf1b0774dfd78deb13b3 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 47136 W: 12484 L: 12146 D: 22506 Ptnml(0-2): 211, 5472, 11866, 5806, 213 Passed LTC: https://tests.stockfishchess.org/tests/view/679fe6790774dfd78deb1753 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 100536 W: 25837 L: 25383 D: 49316 Ptnml(0-2): 103, 10990, 27622, 11456, 97 closes https://github.com/official-stockfish/Stockfish/pull/5853 Bench: 3265587 --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f98098701..cf7c7715e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,6 +999,8 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); + r -= 32 * moveCount; + // Increase reduction for ttPv nodes (*Scaler) // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls @@ -1169,7 +1171,7 @@ moves_loop: // When in check, search starts here // These reduction adjustments have no proven non-linear scaling - r += 307 - moveCount * 64; + r += 307 - moveCount * 32; r -= std::abs(correctionValue) / 34112; From ec7f1d622942b860d422808fc4df10104695bae2 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:06:01 +0000 Subject: [PATCH 451/834] Increment cutoffCnt less often after fail high Only increment when extension is less than 2 or it's a PvNode. Tested vs #5851. Failed STC: LLR: -2.97 (-2.94,2.94) <0.00,2.00> Total: 360064 W: 94546 L: 94271 D: 171247 Ptnml(0-2): 1835, 42826, 90314, 43343, 1714 https://tests.stockfishchess.org/tests/view/679f79cc0774dfd78deb1112 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 443076 W: 113942 L: 113081 D: 216053 Ptnml(0-2): 480, 49076, 121579, 49909, 494 https://tests.stockfishchess.org/tests/view/679fa21b0774dfd78deb1178 Passed VLTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 187184 W: 48098 L: 47495 D: 91591 Ptnml(0-2): 59, 19036, 54792, 19653, 52 https://tests.stockfishchess.org/tests/view/679fb6000774dfd78deb11e8 closes https://github.com/official-stockfish/Stockfish/pull/5855 Bench: 3018089 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index cf7c7715e..c9832c878 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1350,7 +1350,8 @@ moves_loop: // When in check, search starts here if (value >= beta) { - ss->cutoffCnt++; + // (* Scaler) Especially if they make cutoffCnt increment more often. + ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; } From 67573218e1f57155541f8f7a3a90fe809d63c868 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 4 Feb 2025 00:57:06 +0300 Subject: [PATCH 452/834] VVLTC parameters tweak Some notes: - Both tests were conducted on top of #5848. - Based on tuning suggestions, the extension for capturing the previously moved piece was removed/simplified. (Developers can attempt to reintroduce it post-merge if needed.) - Initially, bonusScale = std::max(bonusScale, -2); was included but later removed in the second test upon Viz's request, however, it was nearly non-functional anyway. Passed VVLTC under STC bounds: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 31508 W: 8153 L: 7895 D: 15460 Ptnml(0-2): 1, 2747, 10005, 2995, 6 https://tests.stockfishchess.org/tests/view/679fdc7a0774dfd78deb1350 Passed VVLTC under LTC bounds: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 55026 W: 14370 L: 14046 D: 26610 Ptnml(0-2): 7, 4957, 17262, 5279, 8 https://tests.stockfishchess.org/tests/view/679fec920774dfd78deb19b8 closes https://github.com/official-stockfish/Stockfish/pull/5856 Bench: 2757788 --- src/search.cpp | 182 ++++++++++++++++++++++++------------------------- 1 file changed, 88 insertions(+), 94 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c9832c878..b82217157 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,7 +94,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; - return 7037 * pcv + 6671 * micv + 7631 * (wnpcv + bnpcv) + 6362 * cntcv; + return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -110,11 +110,11 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - static constexpr int nonPawnWeight = 159; + static constexpr int nonPawnWeight = 165; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 104 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; + << bonus * 109 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 141 / 128; workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] @@ -122,14 +122,14 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 146 / 128; + << bonus * 138 / 128; } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(154 * d - 102, 1661); } +int stat_bonus(Depth d) { return std::min(158 * d - 98, 1622); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(831 * d - 269, 2666); } +int stat_malus(Depth d) { return std::min(802 * d - 243, 2850); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } @@ -307,7 +307,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(97); + lowPlyHistory.fill(95); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -343,13 +343,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 12991; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13000; Value avg = rootMoves[pvIdx].averageScore; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 141 * avg / (std::abs(avg) + 83); + optimism[us] = 138 * avg / (std::abs(avg) + 81); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -533,11 +533,11 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(63); - lowPlyHistory.fill(108); - captureHistory.fill(-631); - pawnHistory.fill(-1210); - pawnCorrectionHistory.fill(0); + mainHistory.fill(65); + lowPlyHistory.fill(107); + captureHistory.fill(-655); + pawnHistory.fill(-1215); + pawnCorrectionHistory.fill(4); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); @@ -550,10 +550,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-479); + h.fill(-493); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2143 / 100.0 * std::log(i)); + reductions[i] = int(2937 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -679,12 +679,12 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 746 / 1024); + update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 784 / 1024); // Extra penalty for early quiet moves of the previous ply - if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) + if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_malus(depth + 1) * 1042 / 1024); + -stat_malus(depth + 1) * 1018 / 1024); } // Partial workaround for the graph history interaction problem @@ -791,11 +791,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1881, 1413) + 616; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1151 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1906, 1450) + 638; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1136 / 1024; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1107 / 1024; + << bonus * 1195 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -804,7 +804,7 @@ Value Search::Worker::search( // false otherwise. The improving flag is used in various pruning heuristics. improving = ss->staticEval > (ss - 2)->staticEval; - opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; + opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 5; if (priorReduction >= 3 && !opponentWorsening) depth++; @@ -812,27 +812,27 @@ Value Search::Worker::search( // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 462 - 297 * depth * depth) + if (!PvNode && eval < alpha - 446 - 303 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 310 + 40 - std::abs(correctionValue) / 131072 + - (ss - 1)->statScore / 326 + 37 - std::abs(correctionValue) / 132821 >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 20 * depth + 470 - 60 * improving && !excludedMove + && ss->staticEval >= beta - 21 * depth + 455 - 60 * improving && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 215, 7) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 237, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -870,13 +870,13 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. // (* Scaler) Especially if they make IIR more aggressive. - if (((PvNode || cutNode) && depth >= 7 - 4 * PvNode) && !ttData.move) - depth -= 2; + if (((PvNode || cutNode) && depth >= 7 - 3 * PvNode) && !ttData.move) + depth--; // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 174 - 56 * improving; + probCutBeta = beta + 187 - 55 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -939,7 +939,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 412; + probCutBeta = beta + 413; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; @@ -1005,7 +1005,7 @@ moves_loop: // When in check, search starts here // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 1024; + r += 1031; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1027,15 +1027,15 @@ moves_loop: // When in check, search starts here // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 271 + 243 * lmrDepth - + PieceValue[capturedPiece] + captHist / 7; + Value futilityValue = ss->staticEval + 242 + 238 * lmrDepth + + PieceValue[capturedPiece] + 95 * captHist / 700; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 37, -152 * depth, 141 * depth); - if (!pos.see_ge(move, -156 * depth - seeHist)) + int seeHist = std::clamp(captHist / 36, -153 * depth, 134 * depth); + if (!pos.see_ge(move, -157 * depth - seeHist)) continue; } else @@ -1046,14 +1046,14 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -3901 * depth) + if (history < -4107 * depth) continue; - history += 2 * thisThread->mainHistory[us][move.from_to()]; + history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3459; + lmrDepth += history / 3576; - Value futilityValue = ss->staticEval + (bestMove ? 47 : 137) + 142 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 49 : 135) + 150 * lmrDepth; // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) @@ -1067,7 +1067,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1087,11 +1087,11 @@ moves_loop: // When in check, search starts here // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 5 - (thisThread->completedDepth > 33) + ss->ttPv + && depth >= 5 - (thisThread->completedDepth > 32) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (52 + 74 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttData.value - (55 + 81 * (ss->ttPv && !PvNode)) * depth / 58; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1101,10 +1101,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 262144; - int doubleMargin = 249 * PvNode - 194 * !ttCapture - corrValAdj; + int corrValAdj1 = std::abs(correctionValue) / 265083; + int corrValAdj2 = std::abs(correctionValue) / 253680; + int doubleMargin = 267 * PvNode - 181 * !ttCapture - corrValAdj1; int tripleMargin = - 94 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj; + 96 + 282 * PvNode - 250 * !ttCapture + 103 * ss->ttPv - corrValAdj2; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1137,13 +1138,6 @@ moves_loop: // When in check, search starts here else if (cutNode) extension = -2; } - - // Extension for capturing the previous moved piece - else if (PvNode && move.to_sq() == prevSq - && thisThread->captureHistory[movedPiece][move.to_sq()] - [type_of(pos.piece_on(move.to_sq()))] - > 4126) - extension = 1; } // Step 16. Make the move @@ -1164,45 +1158,45 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2061 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + r -= 2230 + (ttData.value > alpha) * 925 + (ttData.depth >= depth) * 971; if (PvNode) - r -= 1018; + r -= 1013; // These reduction adjustments have no proven non-linear scaling - r += 307 - moveCount * 32; + r += 316 - moveCount * 63; - r -= std::abs(correctionValue) / 34112; + r -= std::abs(correctionValue) / 31568; // Increase reduction for cut nodes if (cutNode) - r += 2355 - (ttData.depth >= depth && ss->ttPv) * 1141; + r += 2608 - (ttData.depth >= depth && ss->ttPv) * 1159; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) - r += 1087 + (depth < 8) * 990; + r += 1123 + (depth < 8) * 982; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 3) - r += 940 + allNode * 887; + r += 981 + allNode * 833; // For first picked move (ttMove) reduce reduction else if (move == ttData.move) - r -= 1960; + r -= 1982; if (capture) ss->statScore = - 7 * int(PieceValue[pos.captured_piece()]) + 688 * int(PieceValue[pos.captured_piece()]) / 100 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 4666; + - 4653; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3874; + + (*contHist[1])[movedPiece][move.to_sq()] - 3591; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 1451 / 16384; + r -= ss->statScore * 1407 / 16384; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1228,8 +1222,8 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); - const bool doShallowerSearch = value < bestValue + 10; + const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); + const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1237,7 +1231,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = (value >= beta) * 2048; + int bonus = (value >= beta) * 2010; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } @@ -1247,11 +1241,11 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 2111; + r += 2117; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3444) - (r > 5588 && newDepth > 2), !cutNode); + newDepth - (r > 3554) - (r > 5373 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1358,7 +1352,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement - if (depth > 2 && depth < 14 && !is_decisive(value)) + if (depth > 2 && depth < 15 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1402,24 +1396,24 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (125 * (depth > 5) + 176 * ((ss - 1)->moveCount > 8) - + 135 * (!ss->inCheck && bestValue <= ss->staticEval - 102) - + 122 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) - + 87 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); + int bonusScale = (118 * (depth > 5) + 36 * !allNode + 161 * ((ss - 1)->moveCount > 8) + + 133 * (!ss->inCheck && bestValue <= ss->staticEval - 107) + + 120 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 84) + + 81 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 108, 320)); bonusScale = std::max(bonusScale, 0); const int scaledBonus = stat_bonus(depth) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 436 / 32768); + scaledBonus * 416 / 32768); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << scaledBonus * 207 / 32768; + << scaledBonus * 219 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1195 / 32768; + << scaledBonus * 1103 / 32768; } else if (priorCapture && prevSq != SQ_NONE) @@ -1580,7 +1574,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 301; + futilityBase = ss->staticEval + 325; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1643,11 +1637,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 5228) + <= 5389) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -80)) + if (!pos.see_ge(move, -75)) continue; } @@ -1714,7 +1708,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 768 / rootDelta + !i * reductionScale * 108 / 300 + 1168; + return reductionScale - delta * 735 / rootDelta + !i * reductionScale * 191 / 512 + 1132; } // elapsed() returns the time elapsed since the search started. If the @@ -1810,35 +1804,35 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = stat_bonus(depth) + 300 * isTTMove; - int malus = stat_malus(depth) - 34 * (moveCount - 1); + int bonus = stat_bonus(depth) + 298 * isTTMove; + int malus = stat_malus(depth) - 32 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1216 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1202 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1062 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1152 / 1024); } else { // Increase stats for the best move in case it was a capture move captured = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1272 / 1024; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1236 / 1024; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 966 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 976 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { moved_piece = pos.moved_piece(move); captured = type_of(pos.piece_on(move.to_sq())); - captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1205 / 1024; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1224 / 1024; } } @@ -1847,7 +1841,7 @@ void update_all_stats(const Position& pos, // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { static constexpr std::array conthist_bonuses = { - {{1, 1025}, {2, 621}, {3, 325}, {4, 512}, {5, 122}, {6, 534}}}; + {{1, 1029}, {2, 656}, {3, 326}, {4, 536}, {5, 120}, {6, 537}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1868,12 +1862,12 @@ void update_quiet_histories( workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 879 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 844 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 888 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 964 / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 634 / 1024; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 615 / 1024; } } From e852d9880a6d3e25d92b6db8216f497ad98c2c57 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 4 Feb 2025 22:14:13 +0300 Subject: [PATCH 453/834] Reduce less for positions without tt move Continuation of work on scaling. In line with previous scaling patches this one massively reduces reduction for moves that don't go thru lmr for position without a tt move. Passed VVLTC with STC bounds: https://tests.stockfishchess.org/tests/view/679fd2450774dfd78deb12b2 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 74718 W: 19354 L: 19042 D: 36322 Ptnml(0-2): 5, 6724, 23595, 7024, 11 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/67a009930774dfd78deb2346 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 82638 W: 21587 L: 21212 D: 39839 Ptnml(0-2): 15, 7476, 25953, 7869, 6 closes https://github.com/official-stockfish/Stockfish/pull/5860 Bench: 2887850 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b82217157..99aff5e96 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1241,7 +1241,7 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 2117; + r += 1111; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, From 2a5b41fd12184d5ab8dedd6ed03d9c2b0fb218a3 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Feb 2025 22:35:10 +0100 Subject: [PATCH 454/834] Fixes a wrongly combined merge conflict from the previous merge wave. Passed STC: https://tests.stockfishchess.org/tests/view/67a288aaeb183d11c65945f1 LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 51424 W: 13588 L: 13237 D: 24599 Ptnml(0-2): 223, 6039, 12860, 6344, 246 Passed LTC: https://tests.stockfishchess.org/tests/view/67a28c0aeb183d11c6594609 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 54144 W: 13900 L: 13543 D: 26701 Ptnml(0-2): 42, 5881, 14870, 6236, 43 closes https://github.com/official-stockfish/Stockfish/pull/5863 Bench: 2345723 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 99aff5e96..f5d2f5cb7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1165,7 +1165,7 @@ moves_loop: // When in check, search starts here // These reduction adjustments have no proven non-linear scaling - r += 316 - moveCount * 63; + r += 316 - moveCount * 32; r -= std::abs(correctionValue) / 31568; From 4c6d2bf9215e2ceb7c22a0bd8ae40077f1751d63 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Feb 2025 22:21:44 +0100 Subject: [PATCH 455/834] Show stdout/stderr in CI/CD tests makes it easier to fix based on warnings shown with e.g. valgrind closes https://github.com/official-stockfish/Stockfish/pull/5862 No functional change --- tests/testing.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/testing.py b/tests/testing.py index bc1f6b15b..3a4b537e9 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -97,14 +97,17 @@ class Syzygy: tarball_path = os.path.join(tmpdirname, f"{file}.tar.gz") response = requests.get(url, stream=True) - with open(tarball_path, 'wb') as f: + with open(tarball_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) with tarfile.open(tarball_path, "r:gz") as tar: tar.extractall(tmpdirname) - shutil.move(os.path.join(tmpdirname, file), os.path.join(PATH, "syzygy")) + shutil.move( + os.path.join(tmpdirname, file), os.path.join(PATH, "syzygy") + ) + class OrderedClassMembers(type): @classmethod @@ -307,7 +310,10 @@ class Stockfish: text=True, ) - self.process.stdout + if self.process.returncode != 0: + print(self.process.stdout) + print(self.process.stderr) + print(f"Process failed with return code {self.process.returncode}") return From 3dfbc5de25705fadcb4b5b7a551eacb3eb75d171 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 4 Feb 2025 15:48:40 -0800 Subject: [PATCH 456/834] Remove non-pawn material check in qsearch pruning Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 47712 W: 12621 L: 12409 D: 22682 Ptnml(0-2): 224, 5349, 12480, 5597, 206 https://tests.stockfishchess.org/tests/view/67a1b4fb612069de394afc37 Passed rebased simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 188274 W: 47727 L: 47677 D: 92870 Ptnml(0-2): 171, 20429, 52867, 20519, 151 https://tests.stockfishchess.org/tests/view/67a2a761fedef70e42ac3300 closes https://github.com/official-stockfish/Stockfish/pull/5866 bench 2654242 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f5d2f5cb7..c68b3b7ac 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1490,7 +1490,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Value bestValue, value, futilityBase; bool pvHit, givesCheck, capture; int moveCount; - Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) @@ -1603,7 +1602,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) moveCount++; // Step 6. Pruning - if (!is_loss(bestValue) && pos.non_pawn_material(us)) + if (!is_loss(bestValue)) { // Futility pruning and moveCount pruning if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase) From d66e603070a4ae76dcc8aeae69882d7d10ac3846 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 4 Feb 2025 15:06:58 -0800 Subject: [PATCH 457/834] Increase PCM bonus when cutOffCnt is low Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 36832 W: 9763 L: 9438 D: 17631 Ptnml(0-2): 159, 4267, 9254, 4562, 174 https://tests.stockfishchess.org/tests/view/67a29dbafedef70e42ac329a Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 19728 W: 5124 L: 4839 D: 9765 Ptnml(0-2): 18, 2029, 5485, 2314, 18 https://tests.stockfishchess.org/tests/view/67a2a1abfedef70e42ac32b7 closes https://github.com/official-stockfish/Stockfish/pull/5865 Bench: 3197798 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c68b3b7ac..36823a08e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1399,7 +1399,8 @@ moves_loop: // When in check, search starts here int bonusScale = (118 * (depth > 5) + 36 * !allNode + 161 * ((ss - 1)->moveCount > 8) + 133 * (!ss->inCheck && bestValue <= ss->staticEval - 107) + 120 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 84) - + 81 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 108, 320)); + + 81 * ((ss - 1)->isTTMove) + 100 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 108, 320)); bonusScale = std::max(bonusScale, 0); From e089f723d87e18dc15c95a4628a65db62f44ed9c Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Tue, 7 Jan 2025 01:47:37 +0100 Subject: [PATCH 458/834] Remove two xors by setting the hash keys for unreachable squares to zero performance before: 3.6714 +- 0.20% Gcycles 3.6620 +- 0.12% Gcycles 3.6704 +- 0.26% Gcycles 3.6602 +- 0.27% Gcycles 3.6799 +- 0.37% Gcycles after: 3.6540 +- 0.30% Gcycles 3.6388 +- 0.25% Gcycles 3.6557 +- 0.17% Gcycles 3.6449 +- 0.15% Gcycles 3.6460 +- 0.26% Gcycles (every line is a different `profile-build` and shows the number of cycles needed for `./stockfish bench`, measured with `perf stat -r 10`) closes https://github.com/official-stockfish/Stockfish/pull/5754 No functional change --- src/position.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37871aa24..37e9a2eb5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -119,6 +119,9 @@ void Position::init() { for (Piece pc : Pieces) for (Square s = SQ_A1; s <= SQ_H8; ++s) Zobrist::psq[pc][s] = rng.rand(); + // pawns on these squares will promote + std::fill_n(Zobrist::psq[W_PAWN] + SQ_A8, 8, 0); + std::fill_n(Zobrist::psq[B_PAWN], 8, 0); for (File f = FILE_A; f <= FILE_H; ++f) Zobrist::enpassant[f] = rng.rand(); @@ -376,7 +379,7 @@ void Position::set_state() const { for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][cnt]; + st->materialKey ^= Zobrist::psq[pc][8 + cnt]; } @@ -776,7 +779,7 @@ void Position::do_move(Move m, remove_piece(capsq); k ^= Zobrist::psq[captured][capsq]; - st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; + st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]]; // Reset rule 50 counter st->rule50 = 0; @@ -840,10 +843,10 @@ void Position::do_move(Move m, dp.dirty_num++; // Update hash keys - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; - st->pawnKey ^= Zobrist::psq[pc][to]; - st->materialKey ^= - Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; + // Zobrist::psq[pc][to] is zero, so we don't need to clear it + k ^= Zobrist::psq[promotion][to]; + st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1] + ^ Zobrist::psq[pc][8 + pieceCount[pc]]; if (promotionType <= BISHOP) st->minorPieceKey ^= Zobrist::psq[promotion][to]; From 3a0418c0d0353ad5720e1fea83510cbee5608485 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 4 Feb 2025 15:16:28 -0800 Subject: [PATCH 459/834] Simplify opponent worsening Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 57120 W: 14712 L: 14526 D: 27882 Ptnml(0-2): 53, 6241, 15796, 6407, 63 https://tests.stockfishchess.org/tests/view/67a26153eb183d11c659454d Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 313452 W: 79893 L: 79973 D: 153586 Ptnml(0-2): 279, 35053, 86156, 34945, 293 https://tests.stockfishchess.org/tests/view/67a29fe0fedef70e42ac32ae closes https://github.com/official-stockfish/Stockfish/pull/5867 Bench: 2582245 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 36823a08e..be5117916 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -804,7 +804,7 @@ Value Search::Worker::search( // false otherwise. The improving flag is used in various pruning heuristics. improving = ss->staticEval > (ss - 2)->staticEval; - opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 5; + opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; if (priorReduction >= 3 && !opponentWorsening) depth++; From 7258567804ccd0730d7c309f150d96c8f6c3816d Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 8 Feb 2025 20:17:47 +0100 Subject: [PATCH 460/834] Refactor reduction rules Refactor reduction rules so that all ttPv/Pv related stuff is in one rule and the scaling becomes more clear. No functional change closes https://github.com/official-stockfish/Stockfish/pull/5871 No functional change --- src/search.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index be5117916..4dfdbaaf0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1158,10 +1158,8 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2230 + (ttData.value > alpha) * 925 + (ttData.depth >= depth) * 971; - - if (PvNode) - r -= 1013; + r -= 2230 + PvNode * 1013 + (ttData.value > alpha) * 925 + + (ttData.depth >= depth) * (971 + cutNode * 1159); // These reduction adjustments have no proven non-linear scaling @@ -1171,7 +1169,7 @@ moves_loop: // When in check, search starts here // Increase reduction for cut nodes if (cutNode) - r += 2608 - (ttData.depth >= depth && ss->ttPv) * 1159; + r += 2608; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) From 9cc15b30490675713466b6746d4afdce5c715bd6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 12 Feb 2025 01:53:41 +0300 Subject: [PATCH 461/834] Do more reductions for cut nodes without a tt move Logic is somewhat similar to IIR but in LMR. Usually things like reducing more in IIR scale badly but this patch does this in LMR where reducing more for cutNodes is in general good, so I believe there is no non-linear scaling. Passed STC: https://tests.stockfishchess.org/tests/view/67abc9aaa04df5eb8dbeb452 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 42304 W: 11223 L: 10892 D: 20189 Ptnml(0-2): 184, 4904, 10669, 5187, 208 Passed LTC: https://tests.stockfishchess.org/tests/view/67abcd7ba04df5eb8dbeb96c LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 32334 W: 8386 L: 8074 D: 15874 Ptnml(0-2): 26, 3446, 8916, 3748, 31 closes https://github.com/official-stockfish/Stockfish/pull/5875 Bench: 2612849 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4dfdbaaf0..4bc6109ed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1169,7 +1169,7 @@ moves_loop: // When in check, search starts here // Increase reduction for cut nodes if (cutNode) - r += 2608; + r += 2608 + 1024 * !ttData.move; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) From a4edacb87aeba72483e1524ade5bf79129c88513 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:15:32 +0100 Subject: [PATCH 462/834] Tweak the cutnode depth condition for TT cutoffs Passed STC: https://tests.stockfishchess.org/tests/view/67ab396ab5c93ee812d851f3 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 83648 W: 21964 L: 21571 D: 40113 Ptnml(0-2): 339, 9779, 21217, 10128, 361 Passed LTC: https://tests.stockfishchess.org/tests/view/67ab9647133d55b1d3bc171e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 68160 W: 17551 L: 17166 D: 33443 Ptnml(0-2): 62, 7353, 18870, 7728, 67 closes https://github.com/official-stockfish/Stockfish/pull/5876 Bench: 3087275 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4bc6109ed..02323d1e0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -672,7 +672,7 @@ Value Search::Worker::search( if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) - && (cutNode == (ttData.value >= beta) || depth > 9)) + && (cutNode == (ttData.value >= beta) || (depth > 9 || (rootDepth > 10 && depth > 5)))) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) From d54240c50af51b1f3a96b81514ec60286ae1056b Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 12 Feb 2025 15:46:07 -0800 Subject: [PATCH 463/834] Decrease lmr depth if static eval decreases a lot This tweak originally had some more conditions which have been simplified away. Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 60064 W: 15797 L: 15439 D: 28828 Ptnml(0-2): 236, 7080, 15106, 7310, 300 https://tests.stockfishchess.org/tests/view/67a2af9cfedef70e42ac3325 Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 76794 W: 19740 L: 19337 D: 37717 Ptnml(0-2): 61, 8327, 21236, 8694, 79 https://tests.stockfishchess.org/tests/view/67a2c904fedef70e42ac374d Passed Non-Regression VVLTC scaling check LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 29046 W: 7581 L: 7389 D: 14076 Ptnml(0-2): 2, 2557, 9213, 2749, 2 https://tests.stockfishchess.org/tests/view/67a54b591c4a3ea87241cb83 Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 212448 W: 55244 L: 55217 D: 101987 Ptnml(0-2): 932, 25283, 53707, 25430, 872 https://tests.stockfishchess.org/tests/view/67aaacb02554387b116f698f Passed simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 185736 W: 47270 L: 47217 D: 91249 Ptnml(0-2): 141, 20568, 51394, 20627, 138 https://tests.stockfishchess.org/tests/view/67ab8efa133d55b1d3bc1397 closes https://github.com/official-stockfish/Stockfish/pull/5878 Bench: 2512420 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 02323d1e0..cd90bbfd2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -808,6 +808,8 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; + if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 200) + depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. From fa6c30af814fe91e6a6c2d1bcaa8d951e3724ae7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 13 Feb 2025 14:47:19 +0300 Subject: [PATCH 464/834] FutilityValue formula tweak Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 29600 W: 7979 L: 7662 D: 13959 Ptnml(0-2): 138, 3446, 7324, 3745, 147 https://tests.stockfishchess.org/tests/view/67ac7dff52879dfd14d7e7da Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 49662 W: 12850 L: 12502 D: 24310 Ptnml(0-2): 41, 5354, 13689, 5710, 37 https://tests.stockfishchess.org/tests/view/67acc1b252879dfd14d7e81d closes https://github.com/official-stockfish/Stockfish/pull/5879 Bench: 2581469 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index cd90bbfd2..67e28d3e5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1055,7 +1055,10 @@ moves_loop: // When in check, search starts here lmrDepth += history / 3576; - Value futilityValue = ss->staticEval + (bestMove ? 49 : 135) + 150 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 49 : 143) + 116 * lmrDepth; + + if (bestValue < ss->staticEval - 150 && lmrDepth < 7) + futilityValue += 108; // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) From e9997afb1cb046ed1812974f27c532f6e4d8dbcc Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Mon, 3 Feb 2025 22:44:09 -0800 Subject: [PATCH 465/834] Replace hint_common_parent_position() by backwards accumulator updates Only calls to `evaluate()` now trigger NNUE accumulator updates. To make sure that we are likely to find parent positions from which to update the accumulators we perform a backwards NNUE update whenever we compute the accumulator from scratch for some position. passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 39680 W: 10474 L: 10164 D: 19042 Ptnml(0-2): 171, 4068, 11042, 4398, 161 https://tests.stockfishchess.org/tests/view/67a27f26eb183d11c65945be passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 337308 W: 86408 L: 85550 D: 165350 Ptnml(0-2): 276, 30551, 106126, 31441, 260 https://tests.stockfishchess.org/tests/view/67a287efeb183d11c65945ee then simplified: STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 28608 W: 7641 L: 7413 D: 13554 Ptnml(0-2): 132, 3036, 7744, 3256, 136 https://tests.stockfishchess.org/tests/view/67a4703719f522d3866d3345 LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 200226 W: 51026 L: 50990 D: 98210 Ptnml(0-2): 170, 18468, 62799, 18508, 168 https://tests.stockfishchess.org/tests/view/67a4f255229c1a170cc08964 The version in this PR is a bit different from the simplified version, but it's compile-time changes only. closes https://github.com/official-stockfish/Stockfish/pull/5870 No functional change --- src/nnue/network.cpp | 6 -- src/nnue/network.h | 3 - src/nnue/nnue_feature_transformer.h | 141 ++++++++++++++++++---------- src/nnue/nnue_misc.cpp | 10 -- src/nnue/nnue_misc.h | 3 - src/position.h | 2 +- src/search.cpp | 4 - 7 files changed, 92 insertions(+), 77 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index b96625a79..5ac8b8d98 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -278,12 +278,6 @@ void Network::verify(std::string } -template -void Network::hint_common_access( - const Position& pos, AccumulatorCaches::Cache* cache) const { - featureTransformer->hint_common_access(pos, cache); -} - template NnueEvalTrace Network::trace_evaluate(const Position& pos, diff --git a/src/nnue/network.h b/src/nnue/network.h index f99fa1182..764481d94 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -67,9 +67,6 @@ class Network { AccumulatorCaches::Cache* cache) const; - void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache) const; - void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, AccumulatorCaches::Cache* cache) const; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 4f0ce6cf6..931d9aed5 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -41,6 +41,11 @@ using BiasType = std::int16_t; using WeightType = std::int16_t; using PSQTWeightType = std::int32_t; +enum IncUpdateDirection { + FORWARD, + BACKWARDS +}; + // If vector instructions are enabled, we update and refresh the // accumulator tile by tile such that each tile fits in the CPU's // vector registers. @@ -249,6 +254,7 @@ class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + static constexpr bool Big = TransformedFeatureDimensions == TransformedFeatureDimensionsBig; private: using Tiling = SIMDTiling; @@ -468,38 +474,20 @@ class FeatureTransformer { return psqt; } // end of function transform() - void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache) const { - update_accumulator(pos, cache); - update_accumulator(pos, cache); - } - private: - template - StateInfo* try_find_computed_accumulator(const Position& pos) const { - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - StateInfo* st = pos.state(); - int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !(st->*accPtr).computed[Perspective]) - { - // This governs when a full feature refresh is needed and how many - // updates are better than just one full refresh. - if (FeatureSet::requires_refresh(st, Perspective) - || (gain -= FeatureSet::update_cost(st) + 1) < 0) - break; - st = st->previous; - } - return st; - } - - // Given a computed accumulator, computes the accumulator of the next position. - template - void update_accumulator_incremental(const Position& pos, StateInfo* computed) const { + // Given a computed accumulator, computes the accumulator of another position. + template + void update_accumulator_incremental(const Square ksq, + StateInfo* target_state, + const StateInfo* computed) const { + [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; + [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; assert((computed->*accPtr).computed[Perspective]); - assert(computed->next != nullptr); - const Square ksq = pos.square(Perspective); + StateInfo* next = Forward ? computed->next : computed->previous; + + assert(next != nullptr); + assert(!(next->*accPtr).computed[Perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -508,11 +496,11 @@ class FeatureTransformer { // In this case, the maximum size of both feature addition and removal // is 2, since we are incrementally updating one move at a time. FeatureSet::IndexList removed, added; - FeatureSet::append_changed_indices(ksq, computed->next->dirtyPiece, removed, - added); - - StateInfo* next = computed->next; - assert(!(next->*accPtr).computed[Perspective]); + if constexpr (Forward) + FeatureSet::append_changed_indices(ksq, next->dirtyPiece, removed, added); + else + FeatureSet::append_changed_indices(ksq, computed->dirtyPiece, added, + removed); if (removed.size() == 0 && added.size() == 0) { @@ -527,7 +515,10 @@ class FeatureTransformer { { assert(added.size() == 1 || added.size() == 2); assert(removed.size() == 1 || removed.size() == 2); - assert(added.size() <= removed.size()); + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); #ifdef VECTOR auto* accIn = @@ -539,13 +530,15 @@ class FeatureTransformer { const IndexType offsetR0 = HalfDimensions * removed[0]; auto* columnR0 = reinterpret_cast(&weights[offsetR0]); - if (removed.size() == 1) + if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) { + assert(added.size() == 1 && removed.size() == 1); for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); } - else if (added.size() == 1) + else if (Forward && added.size() == 1) { + assert(removed.size() == 2); const IndexType offsetR1 = HalfDimensions * removed[1]; auto* columnR1 = reinterpret_cast(&weights[offsetR1]); @@ -553,8 +546,19 @@ class FeatureTransformer { accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), vec_add_16(columnR0[i], columnR1[i])); } + else if (Backwards && removed.size() == 1) + { + assert(added.size() == 2); + const IndexType offsetA1 = HalfDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&weights[offsetA1]); + + for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), + vec_sub_16(columnA1[i], columnR0[i])); + } else { + assert(added.size() == 2 && removed.size() == 2); const IndexType offsetA1 = HalfDimensions * added[1]; auto* columnA1 = reinterpret_cast(&weights[offsetA1]); const IndexType offsetR1 = HalfDimensions * removed[1]; @@ -576,14 +580,15 @@ class FeatureTransformer { const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; auto* columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - if (removed.size() == 1) + if ((Forward && removed.size() == 1) + || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 { for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) accPsqtOut[i] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); } - else if (added.size() == 1) + else if (Forward && added.size() == 1) { const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; auto* columnPsqtR1 = @@ -595,6 +600,18 @@ class FeatureTransformer { vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); } + else if (Backwards && removed.size() == 1) + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&psqtWeights[offsetPsqtA1]); + + for (std::size_t i = 0; + i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) + accPsqtOut[i] = + vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); + } else { const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; @@ -647,8 +664,8 @@ class FeatureTransformer { (next->*accPtr).computed[Perspective] = true; - if (next != pos.state()) - update_accumulator_incremental(pos, next); + if (next != target_state) + update_accumulator_incremental(ksq, target_state, next); } @@ -815,16 +832,40 @@ class FeatureTransformer { template void update_accumulator(const Position& pos, AccumulatorCaches::Cache* cache) const { - if ((pos.state()->*accPtr).computed[Perspective]) - return; - StateInfo* oldest = try_find_computed_accumulator(pos); + StateInfo* st = pos.state(); + if ((st->*accPtr).computed[Perspective]) + return; // nothing to do - if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) - // Start from the oldest computed accumulator, update all the - // accumulators up to the current position. - update_accumulator_incremental(pos, oldest); - else - update_accumulator_refresh_cache(pos, cache); + [[maybe_unused]] // only used when !Big + int gain = FeatureSet::refresh_cost(pos); + // Look for a usable already computed accumulator of an earlier position. + // When computing the small accumulator, we keep track of the estimated gain in + // terms of features to be added/subtracted. + // When computing the big accumulator, we expect to be able to reuse any + // accumulators, so we always try to do an incremental update. + do + { + if (FeatureSet::requires_refresh(st, Perspective) + || (!Big && (gain -= FeatureSet::update_cost(st) < 0)) || !st->previous + || st->previous->next != st) + { + // compute accumulator from scratch for this position + update_accumulator_refresh_cache(pos, cache); + if (Big && st != pos.state()) + // when computing a big accumulator from scratch we can use it to + // efficiently compute the accumulator backwards, until we get to a king + // move. We expect that we will need these accumulators later anyway, so + // computing them now will save some work. + update_accumulator_incremental( + pos.square(Perspective), st, pos.state()); + return; + } + st = st->previous; + } while (!(st->*accPtr).computed[Perspective]); + + // Start from the oldest computed accumulator, update all the + // accumulators up to the current position. + update_accumulator_incremental(pos.square(Perspective), pos.state(), st); } template diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 1e2690503..2220684da 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -30,7 +30,6 @@ #include #include -#include "../evaluate.h" #include "../position.h" #include "../types.h" #include "../uci.h" @@ -43,15 +42,6 @@ namespace Stockfish::Eval::NNUE { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -void hint_common_parent_position(const Position& pos, - const Networks& networks, - AccumulatorCaches& caches) { - if (Eval::use_smallnet(pos)) - networks.small.hint_common_access(pos, &caches.small); - else - networks.big.hint_common_access(pos, &caches.big); -} - namespace { // Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index a7647f846..02212160a 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -54,9 +54,6 @@ struct Networks; struct AccumulatorCaches; std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches); -void hint_common_parent_position(const Position& pos, - const Networks& networks, - AccumulatorCaches& caches); } // namespace Stockfish::Eval::NNUE } // namespace Stockfish diff --git a/src/position.h b/src/position.h index 53269c197..eeea1b74c 100644 --- a/src/position.h +++ b/src/position.h @@ -63,9 +63,9 @@ struct StateInfo { int repetition; // Used by NNUE + DirtyPiece dirtyPiece; Eval::NNUE::Accumulator accumulatorBig; Eval::NNUE::Accumulator accumulatorSmall; - DirtyPiece dirtyPiece; }; diff --git a/src/search.cpp b/src/search.cpp index 67e28d3e5..f292118ed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -41,7 +41,6 @@ #include "nnue/network.h" #include "nnue/nnue_accumulator.h" #include "nnue/nnue_common.h" -#include "nnue/nnue_misc.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -759,7 +758,6 @@ Value Search::Worker::search( else if (excludedMove) { // Providing the hint that this node's accumulator will be used often - Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -768,8 +766,6 @@ Value Search::Worker::search( unadjustedStaticEval = ttData.eval; if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(pos); - else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); From 76c319f4388faabcbfbe9b6d4c2e030f766e455f Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 12 Feb 2025 15:01:35 -0800 Subject: [PATCH 466/834] Simplify ttcut depth condition Simplify ttcut depth condition in a recent tweak of Nonlinear (PR #5876) Passed simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235328 W: 61646 L: 61644 D: 112038 Ptnml(0-2): 1039, 27947, 59676, 27977, 1025 https://tests.stockfishchess.org/tests/view/67abc7fba04df5eb8dbeb442 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 63744 W: 16306 L: 16128 D: 31310 Ptnml(0-2): 58, 6918, 17748, 7084, 64 https://tests.stockfishchess.org/tests/view/67abd776a04df5eb8dbeb9c1 closes https://github.com/official-stockfish/Stockfish/pull/5877 Bench: 2667779 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f292118ed..da1ea3497 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -671,7 +671,7 @@ Value Search::Worker::search( if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) - && (cutNode == (ttData.value >= beta) || (depth > 9 || (rootDepth > 10 && depth > 5)))) + && (cutNode == (ttData.value >= beta) || depth > 5)) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) From ee7259e48bb33fd291fd972950ce7e9294d3e463 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 13 Feb 2025 23:09:30 +0300 Subject: [PATCH 467/834] Small code cleanup Use std::is_arithmetic_v as it is the more modern and concise way to check for arithmetic types. While at it, fixing a static assert in misc.h, thanks to Shawn and Disservin for helping. closes https://github.com/official-stockfish/Stockfish/pull/5883 No functional change --- src/history.h | 2 +- src/misc.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/history.h b/src/history.h index 9ae7bdadc..fd9b98b98 100644 --- a/src/history.h +++ b/src/history.h @@ -71,7 +71,7 @@ inline int non_pawn_index(const Position& pos) { template class StatsEntry { - static_assert(std::is_arithmetic::value, "Not an arithmetic type"); + static_assert(std::is_arithmetic_v, "Not an arithmetic type"); static_assert(D <= std::numeric_limits::max(), "D overflows T"); T entry; diff --git a/src/misc.h b/src/misc.h index 8adbac68a..d2cbb699d 100644 --- a/src/misc.h +++ b/src/misc.h @@ -155,6 +155,10 @@ struct MultiArrayHelper { using ChildType = T; }; +template +constexpr bool is_strictly_assignable_v = + std::is_assignable_v && (std::is_same_v || !std::is_convertible_v); + } // MultiArray is a generic N-dimensional array. @@ -212,7 +216,8 @@ class MultiArray { template void fill(const U& v) { - static_assert(std::is_assignable_v, "Cannot assign fill value to entry type"); + static_assert(Detail::is_strictly_assignable_v, + "Cannot assign fill value to entry type"); for (auto& ele : data_) { if constexpr (sizeof...(Sizes) == 0) From 095d19afea0b4f4a7f3ec91449cc7a66f7bbfc42 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 14 Feb 2025 03:07:39 +0300 Subject: [PATCH 468/834] Use neon_m128_reduce_add_epi32 for NEON vector reduction Accomplishing the entire horizontal addition in a single NEON instruction closes https://github.com/official-stockfish/Stockfish/pull/5885 No functional change --- src/nnue/layers/affine_transform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index f5c640fb9..dac727e23 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -102,7 +102,7 @@ static void affine_transform_non_ssse3(std::int32_t* output, product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); sum = vpadalq_s16(sum, product); } - output[i] = sum[0] + sum[1] + sum[2] + sum[3]; + output[i] = Simd::neon_m128_reduce_add_epi32(sum); #endif } From 45b2b06cea0eae5935baee769142002a027e3ccb Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 13 Feb 2025 23:32:53 -0800 Subject: [PATCH 469/834] Use same term for small and large net for nnue complexity adjustment Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 386496 W: 100682 L: 100850 D: 184964 Ptnml(0-2): 1686, 46399, 97218, 46287, 1658 https://tests.stockfishchess.org/tests/view/67a9cc6d851bb0f25324449e Passed rebased simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 160884 W: 41090 L: 41012 D: 78782 Ptnml(0-2): 133, 17883, 44321, 17983, 122 https://tests.stockfishchess.org/tests/view/67aef2e91a4c73ae1f930e85 closes https://github.com/official-stockfish/Stockfish/pull/5886 Bench: 2962718 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4fce86e3a..dddb56860 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -76,7 +76,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); optimism += optimism * nnueComplexity / 468; - nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879); + nnue -= nnue * nnueComplexity / 18000; int material = 535 * pos.count() + pos.non_pawn_material(); int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; From fc2139fedc8c74d52fcc641813a287b5b7d8f0b9 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 16 Feb 2025 16:40:36 +0100 Subject: [PATCH 470/834] se separate parameters for stat values The code has been refactored to remove the `stat_bonus` and `stat_malus` functions, as the code for each bonus/malus is now different. This allows for future tests to modify these formulas individually. Passed LTC with STC bounds: https://tests.stockfishchess.org/tests/view/67b115dd6c6b9e172ad1592f LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 75756 W: 19393 L: 19044 D: 37319 Ptnml(0-2): 60, 8251, 20913, 8588, 66 Passed LTC with LTC bounds: https://tests.stockfishchess.org/tests/view/67af5f5d6c6b9e172ad15765 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 108126 W: 27880 L: 27412 D: 52834 Ptnml(0-2): 85, 11786, 29866, 12228, 98 closes https://github.com/official-stockfish/Stockfish/pull/5887 Bench: 2809143 --- src/search.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index da1ea3497..d2c77365b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -124,12 +124,6 @@ void update_correction_history(const Position& pos, << bonus * 138 / 128; } -// History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(158 * d - 98, 1622); } - -// History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(802 * d - 243, 2850); } - // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } Value value_to_tt(Value v, int ply); @@ -678,12 +672,14 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 784 / 1024); + update_quiet_histories(pos, ss, *this, ttData.move, + std::min(117600 * depth - 71344, 1244992) / 1024); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_malus(depth + 1) * 1018 / 1024); + -std::min(779788 * (depth + 1) - 271806, 2958308) + / 1024); } // Partial workaround for the graph history interaction problem @@ -1403,7 +1399,7 @@ moves_loop: // When in check, search starts here bonusScale = std::max(bonusScale, 0); - const int scaledBonus = stat_bonus(depth) * bonusScale; + const int scaledBonus = std::min(160 * depth - 106, 1523) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, scaledBonus * 416 / 32768); @@ -1422,7 +1418,7 @@ moves_loop: // When in check, search starts here Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] - << stat_bonus(depth) * 2; + << std::min(330 * depth - 198, 3320); } if (PvNode) @@ -1803,8 +1799,8 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = stat_bonus(depth) + 298 * isTTMove; - int malus = stat_malus(depth) - 32 * (moveCount - 1); + int bonus = std::min(162 * depth - 92, 1587) + 298 * isTTMove; + int malus = std::min(694 * depth - 230, 2503) - 32 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { From 43b2d65d7275b11fd47c7225f8a0d19afbab4cd1 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 16 Feb 2025 20:54:34 -0800 Subject: [PATCH 471/834] Add scaling note to futility pruning Note that both patches below effectively reduces the frequency of futility pruning. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 51680 W: 13599 L: 13253 D: 24828 Ptnml(0-2): 217, 6056, 12959, 6380, 228 https://tests.stockfishchess.org/tests/view/67ac218fa04df5eb8dbebf26 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 51798 W: 13338 L: 12986 D: 25474 Ptnml(0-2): 42, 5584, 14310, 5906, 57 https://tests.stockfishchess.org/tests/view/67acf04152879dfd14d7e846 Regression at STC SMP: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 231552 W: 60226 L: 59642 D: 111684 Ptnml(0-2): 565, 25994, 62031, 26664, 522 https://tests.stockfishchess.org/tests/view/67ae390c1a4c73ae1f930dbf Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 22560 W: 6022 L: 5725 D: 10813 Ptnml(0-2): 87, 2524, 5762, 2819, 88 https://tests.stockfishchess.org/tests/view/67ac202aa04df5eb8dbebf22 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 66138 W: 16953 L: 16572 D: 32613 Ptnml(0-2): 62, 7103, 18360, 7480, 64 https://tests.stockfishchess.org/tests/view/67ad47d852879dfd14d7e899 Regression at VVLTC SMP: LLR: -2.94 (-2.94,2.94) <-1.75,0.25> Total: 29138 W: 7408 L: 7655 D: 14075 Ptnml(0-2): 0, 2816, 9189, 2559, 5 https://tests.stockfishchess.org/tests/view/67b159ce6c6b9e172ad1598f closes https://github.com/official-stockfish/Stockfish/pull/5888 No functional change --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index d2c77365b..7736c4121 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,6 +1053,8 @@ moves_loop: // When in check, search starts here futilityValue += 108; // Futility pruning: parent node + // (*Scaler): Generally, more frequent futility pruning + // scales well with respect to time and threads if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) From 291a429cdd54f5298b8dc3d19cd08c3a64de4d10 Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 17 Feb 2025 21:24:57 +0100 Subject: [PATCH 472/834] 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 5ac8b8d98..fe312fcb8 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 473/834] Add risk tolerance calculation https://tests.stockfishchess.org/tests/view/67b1db2188b11e2400eb06ae Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 123552 W: 32388 L: 31938 D: 59226 Ptnml(0-2): 487, 14520, 31345, 14904, 520 Passed LTC: https://tests.stockfishchess.org/tests/view/67b3d53f154c4df4fc4b1f43 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 206928 W: 52916 L: 52246 D: 101766 Ptnml(0-2): 159, 22546, 57394, 23196, 169 closes https://github.com/official-stockfish/Stockfish/pull/5893 Bench: 2705449 --- src/search.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 7736c4121..5ec0def6a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -96,6 +96,32 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; } +int risk_tolerance(const Position& pos, Value v) { + // Returns (some constant of) second derivative of sigmoid. + static constexpr auto sigmoid_d2 = [](int x, int y) { + return -345600 * x / (x * x + 3 * y * y); + }; + + int material = pos.count() + 3 * pos.count() + 3 * pos.count() + + 5 * pos.count() + 9 * pos.count(); + + int m = std::clamp(material, 17, 78); + + // a and b are the crude approximation of the wdl model. + // The win rate is: 1/(1+exp((a-v)/b)) + // The loss rate is 1/(1+exp((v+a)/b)) + int a = ((-m * 3220 / 256 + 2361) * m / 256 - 586) * m / 256 + 421; + int b = ((m * 7761 / 256 - 2674) * m / 256 + 314) * m / 256 + 51; + + + // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) + // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) + int winning_risk = sigmoid_d2(v - a, b); + int losing_risk = -sigmoid_d2(-v - a, b); + + return (winning_risk + losing_risk) * 60 / b; +} + // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. Value to_corrected_static_eval(const Value v, const int cv) { @@ -1166,6 +1192,9 @@ moves_loop: // When in check, search starts here r -= std::abs(correctionValue) / 31568; + if (PvNode && !is_decisive(bestValue)) + r -= risk_tolerance(pos, bestValue); + // Increase reduction for cut nodes if (cutNode) r += 2608 + 1024 * !ttData.move; From c19a6ea53cd715af97717ba687c3ad4c9c2a98c8 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 23 Feb 2025 19:52:37 +0300 Subject: [PATCH 474/834] Make Pv search shallower in some cases Conditions are the same as they are for doShallowerSearch, just that they also apply for cases where LMR wasn't reducing anything - if result is bad enough reduce search depth of PV search by 1. Passed STC: https://tests.stockfishchess.org/tests/view/67b9d2aca49c651c2caac818 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 29216 W: 7731 L: 7424 D: 14061 Ptnml(0-2): 87, 3345, 7473, 3580, 123 Passed LTC: https://tests.stockfishchess.org/tests/view/67ba538c01f3463ae1d35e69 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 33168 W: 8529 L: 8219 D: 16420 Ptnml(0-2): 12, 3505, 9262, 3771, 34 closes https://github.com/official-stockfish/Stockfish/pull/5895 Bench: 2290732 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 5ec0def6a..6a2f8f2d2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1260,6 +1260,8 @@ moves_loop: // When in check, search starts here int bonus = (value >= beta) * 2010; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } + else if (value > alpha && value < bestValue + 9) + newDepth--; } // Step 18. Full-depth search when LMR is skipped From 0f9ae0d11cd034288a49ef3892c580dfed025091 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Feb 2025 10:50:41 +0100 Subject: [PATCH 475/834] Check maximum time every iteration This fixes a TCEC timeloss, where slow DTZ TB7 access, in combination with syzygy PV extension, led to a timeloss. While the extension code correctly aborted after spending moveOverhead/2 time, the mainThread did not search sufficient nodes (512 in > 1s) to trigger the stop in check_time. At the same time, totalTime exceeded tm.maximum() due to the factors multiplying tm.optimum(). This corner case is fixed by checking also against the tm.maximum() at each iteration. Even though this problem can't be triggered on fishtest, the patch was verified there. Passed STC: https://tests.stockfishchess.org/tests/view/67b99e1be99f8640b318810d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 136832 W: 35625 L: 35518 D: 65689 Ptnml(0-2): 499, 14963, 37431, 14978, 545 closes https://github.com/official-stockfish/Stockfish/pull/5896 No functional change --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6a2f8f2d2..edd8d9eb5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -520,8 +520,8 @@ void Search::Worker::iterative_deepening() { && !mainThread->ponder) threads.stop = true; - // Stop the search if we have exceeded the totalTime - if (elapsedTime > totalTime) + // Stop the search if we have exceeded the totalTime or maximum + if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum()))) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". From 93b966829bd7d2d9d9dce49f11e20125f48d0cfd Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 24 Feb 2025 14:13:24 +0300 Subject: [PATCH 476/834] Simplify bestvalue update formula Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 124960 W: 32598 L: 32472 D: 59890 Ptnml(0-2): 480, 14852, 31694, 14970, 484 https://tests.stockfishchess.org/tests/view/67b348bae00eea114cdba37d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 150306 W: 38220 L: 38132 D: 73954 Ptnml(0-2): 98, 16430, 42005, 16526, 94 https://tests.stockfishchess.org/tests/view/67b5e37918a66624a7a3f751 closes https://github.com/official-stockfish/Stockfish/pull/5898 Bench: 2146010 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index edd8d9eb5..21b328fbe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1720,8 +1720,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return mated_in(ss->ply); // Plies to mate from the root } - if (!is_decisive(bestValue) && bestValue >= beta) - bestValue = (3 * bestValue + beta) / 4; + if (!is_decisive(bestValue) && bestValue > beta) + bestValue = (bestValue + beta) / 2; // Save gathered info in transposition table. The static evaluation // is saved as it was before adjustment by correction history. From d330b48e21f688e80b44001a912168097646671d Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Feb 2025 11:32:27 -0800 Subject: [PATCH 477/834] Handle updating the small accumulator the same way as the big one https://tests.stockfishchess.org/tests/view/67abfe1ca04df5eb8dbebf0b LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 153088 W: 40072 L: 39979 D: 73037 Ptnml(0-2): 619, 16728, 41764, 16807, 626 closes https://github.com/official-stockfish/Stockfish/pull/5901 No functional change --- src/nnue/features/half_ka_v2_hm.cpp | 4 ---- src/nnue/features/half_ka_v2_hm.h | 5 ----- src/nnue/nnue_feature_transformer.h | 15 ++++----------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5bb0296e2..81eddb060 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -77,10 +77,6 @@ template void HalfKAv2_hm::append_changed_indices(Square ksq, IndexList& removed, IndexList& added); -int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; } - -int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count(); } - bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { return st->dirtyPiece.piece[0] == make_piece(perspective, KING); } diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index ca940c54e..0a420cd1e 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -135,11 +135,6 @@ class HalfKAv2_hm { static void append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - // Returns the cost of updating one perspective, the most costly one. - // Assumes no refresh needed. - static int update_cost(const StateInfo* st); - static int refresh_cost(const Position& pos); - // Returns whether the change stored in this StateInfo means // that a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 931d9aed5..60a044158 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -254,7 +254,6 @@ class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - static constexpr bool Big = TransformedFeatureDimensions == TransformedFeatureDimensionsBig; private: using Tiling = SIMDTiling; @@ -836,23 +835,17 @@ class FeatureTransformer { if ((st->*accPtr).computed[Perspective]) return; // nothing to do - [[maybe_unused]] // only used when !Big - int gain = FeatureSet::refresh_cost(pos); // Look for a usable already computed accumulator of an earlier position. - // When computing the small accumulator, we keep track of the estimated gain in - // terms of features to be added/subtracted. - // When computing the big accumulator, we expect to be able to reuse any - // accumulators, so we always try to do an incremental update. + // Always try to do an incremental update as most accumulators will be reusable. do { - if (FeatureSet::requires_refresh(st, Perspective) - || (!Big && (gain -= FeatureSet::update_cost(st) < 0)) || !st->previous + if (FeatureSet::requires_refresh(st, Perspective) || !st->previous || st->previous->next != st) { // compute accumulator from scratch for this position update_accumulator_refresh_cache(pos, cache); - if (Big && st != pos.state()) - // when computing a big accumulator from scratch we can use it to + if (st != pos.state()) + // when computing an accumulator from scratch we can use it to // efficiently compute the accumulator backwards, until we get to a king // move. We expect that we will need these accumulators later anyway, so // computing them now will save some work. From e4d7136042f46d0002b91ac5a49602369dbe3d05 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Feb 2025 11:42:03 -0800 Subject: [PATCH 478/834] Combine last 3 add/remove operations https://tests.stockfishchess.org/tests/view/67ad587052879dfd14d7e8a5 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 45856 W: 12177 L: 11855 D: 21824 Ptnml(0-2): 176, 4845, 12588, 5119, 200 The two most common cases are when added and removed counts are equal and when they are off by 1. When they are off by 1 we currently do a pass combining 2 and then an extra pass for the last 1. This patch does a single combined pass on the final 3 instead. Tested on top of the simplification in https://github.com/official-stockfish/Stockfish/pull/5901 closes https://github.com/official-stockfish/Stockfish/pull/5902 No functional change --- src/nnue/nnue_feature_transformer.h | 61 +++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 60a044158..7e4c669ae 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -704,6 +704,8 @@ class FeatureTransformer { accumulator.computed[Perspective] = true; #ifdef VECTOR + const bool combineLast3 = std::abs((int) removed.size() - (int) added.size()) == 1 + && removed.size() + added.size() > 2; vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; @@ -717,7 +719,7 @@ class FeatureTransformer { acc[k] = entryTile[k]; std::size_t i = 0; - for (; i < std::min(removed.size(), added.size()); ++i) + for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) { IndexType indexR = removed[i]; const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; @@ -729,23 +731,56 @@ class FeatureTransformer { for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); } - for (; i < removed.size(); ++i) + if (combineLast3) { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); + IndexType indexR = removed[i]; + const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&weights[offsetA]); - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); + if (removed.size() > added.size()) + { + IndexType indexR2 = removed[i + 1]; + const IndexType offsetR2 = HalfDimensions * indexR2 + j * Tiling::TileHeight; + auto* columnR2 = reinterpret_cast(&weights[offsetR2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), + vec_add_16(columnR[k], columnR2[k])); + } + else + { + IndexType indexA2 = added[i + 1]; + const IndexType offsetA2 = HalfDimensions * indexA2 + j * Tiling::TileHeight; + auto* columnA2 = reinterpret_cast(&weights[offsetA2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), + vec_add_16(columnA[k], columnA2[k])); + } } - for (; i < added.size(); ++i) + else { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } } for (IndexType k = 0; k < Tiling::NumRegs; k++) From 09faa626210e8f72cacc35887823c4929c36a86b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 23 Feb 2025 00:40:06 -0800 Subject: [PATCH 479/834] Simplify NMP Conditions Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142400 W: 36883 L: 36779 D: 68738 Ptnml(0-2): 467, 16804, 36571, 16874, 484 https://tests.stockfishchess.org/tests/view/67bd1898e4a8d7152b974ef1 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 251868 W: 63905 L: 63920 D: 124043 Ptnml(0-2): 133, 27480, 70708, 27495, 118 https://tests.stockfishchess.org/tests/view/67bd1898e4a8d7152b974ef1 closes https://github.com/official-stockfish/Stockfish/pull/5906 Bench: 2188400 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 21b328fbe..89a44b930 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -846,8 +846,8 @@ Value Search::Worker::search( // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 455 - 60 * improving && !excludedMove - && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) + && ss->staticEval >= beta - 21 * depth + 395 && !excludedMove && pos.non_pawn_material(us) + && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); From a730b4d08b6429b5ec345f7bc607ec9df0988b71 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 26 Feb 2025 03:06:47 +0300 Subject: [PATCH 480/834] Remove two unnecessary divisions Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 280768 W: 72187 L: 72236 D: 136345 Ptnml(0-2): 815, 33131, 72550, 33064, 824 https://tests.stockfishchess.org/tests/view/67bcf7afe670525923b8a101 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 108684 W: 27666 L: 27536 D: 53482 Ptnml(0-2): 40, 11768, 30606, 11878, 50 https://tests.stockfishchess.org/tests/view/67be472ed8d5c2c657c52cb8 closes https://github.com/official-stockfish/Stockfish/pull/5908 Bench: 2400689 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 89a44b930..7e5cf5f92 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -699,13 +699,12 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(117600 * depth - 71344, 1244992) / 1024); + std::min(115 * depth - 70, 1216)); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(779788 * (depth + 1) - 271806, 2958308) - / 1024); + -std::min(762 * (depth + 1) - 266, 2889)); } // Partial workaround for the graph history interaction problem From 6d9c6f99b9439cf083175d8c4c36ccb77fd31789 Mon Sep 17 00:00:00 2001 From: Jake Senne Date: Thu, 20 Feb 2025 22:53:16 -0600 Subject: [PATCH 481/834] Replace aligned() function with line_bb() and simplify king piece detection From https://discord.com/channels/435943710472011776/813919248455827515/1342267241168900228 Saves 6 instructions closes https://github.com/official-stockfish/Stockfish/pull/5909 No functional change --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37e9a2eb5..1e19e3c8a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; + return !(line_bb(from, to) & pieces(~sideToMove, KING) || m.type_of() == CASTLING); switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From 5c617e579cb44215a8904b879b24d3e7bbd78412 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 27 Feb 2025 18:31:40 +0300 Subject: [PATCH 482/834] VVLTC Search Tune Passed VVLTC with STC bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 15788 W: 4106 L: 3868 D: 7814 Ptnml(0-2): 0, 1324, 5009, 1560, 1 https://tests.stockfishchess.org/tests/view/67bf2ddd6e569f6234102ade Passed VVLTC with LTC bounds: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 13622 W: 3620 L: 3368 D: 6634 Ptnml(0-2): 3, 1190, 4170, 1448, 0 https://tests.stockfishchess.org/tests/view/67c04308c8f7c4c0632d8055 closes https://github.com/official-stockfish/Stockfish/pull/5910 Bench: 1823605 --- src/search.cpp | 189 +++++++++++++++++++++++++------------------------ 1 file changed, 95 insertions(+), 94 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7e5cf5f92..bbd43ed69 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -71,7 +71,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 112 - 26 * noTtCutNode; + Value futilityMult = 110 - 25 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; @@ -93,25 +93,26 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; - return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; + return 7685 * pcv + 7495 * micv + 9144 * (wnpcv + bnpcv) + 6469 * cntcv; } int risk_tolerance(const Position& pos, Value v) { // Returns (some constant of) second derivative of sigmoid. static constexpr auto sigmoid_d2 = [](int x, int y) { - return -345600 * x / (x * x + 3 * y * y); + return -355752 * x / (x * x + 3 * y * y); }; - int material = pos.count() + 3 * pos.count() + 3 * pos.count() - + 5 * pos.count() + 9 * pos.count(); + int material = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() + + 337 * pos.count() + 553 * pos.count()) + / 64; int m = std::clamp(material, 17, 78); // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) // The loss rate is 1/(1+exp((v+a)/b)) - int a = ((-m * 3220 / 256 + 2361) * m / 256 - 586) * m / 256 + 421; - int b = ((m * 7761 / 256 - 2674) * m / 256 + 314) * m / 256 + 51; + int a = ((-m * 3037 / 256 + 2270) * m / 256 - 637) * m / 256 + 413; + int b = ((m * 7936 / 256 - 2255) * m / 256 + 319) * m / 256 + 83; // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) @@ -119,7 +120,7 @@ int risk_tolerance(const Position& pos, Value v) { int winning_risk = sigmoid_d2(v - a, b); int losing_risk = -sigmoid_d2(-v - a, b); - return (winning_risk + losing_risk) * 60 / b; + return (winning_risk + losing_risk) * 58 / b; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -135,11 +136,11 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - static constexpr int nonPawnWeight = 165; + static constexpr int nonPawnWeight = 162; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 109 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 141 / 128; + << bonus * 111 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] @@ -147,7 +148,7 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 138 / 128; + << bonus * 143 / 128; } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -326,7 +327,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(95); + lowPlyHistory.fill(92); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -362,13 +363,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13000; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11834; Value avg = rootMoves[pvIdx].averageScore; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 138 * avg / (std::abs(avg) + 81); + optimism[us] = 138 * avg / (std::abs(avg) + 84); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -552,27 +553,27 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(65); - lowPlyHistory.fill(107); - captureHistory.fill(-655); - pawnHistory.fill(-1215); - pawnCorrectionHistory.fill(4); + mainHistory.fill(66); + lowPlyHistory.fill(105); + captureHistory.fill(-646); + pawnHistory.fill(-1262); + pawnCorrectionHistory.fill(6); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); for (auto& to : continuationCorrectionHistory) for (auto& h : to) - h.fill(0); + h.fill(5); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-493); + h.fill(-468); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2937 / 128.0 * std::log(i)); + reductions[i] = int(2954 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -699,12 +700,12 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(115 * depth - 70, 1216)); + std::min(120 * depth - 75, 1241)); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(762 * (depth + 1) - 266, 2889)); + -std::min(809 * (depth + 1) - 249, 3052)); } // Partial workaround for the graph history interaction problem @@ -808,11 +809,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1906, 1450) + 638; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1136 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1950, 1416) + 655; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1124 / 1024; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1195 / 1024; + << bonus * 1196 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -825,33 +826,33 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 200) + if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 188) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 446 - 303 * depth * depth) + if (!PvNode && eval < alpha - 461 - 315 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 326 + 37 - std::abs(correctionValue) / 132821 + - (ss - 1)->statScore / 301 + 37 - std::abs(correctionValue) / 139878 >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 395 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 19 * depth + 418 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 237, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 232, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -884,7 +885,7 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta + 97; + improving |= ss->staticEval >= beta + 94; // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. @@ -895,7 +896,7 @@ Value Search::Worker::search( // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 187 - 55 * improving; + probCutBeta = beta + 185 - 58 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -958,7 +959,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 413; + probCutBeta = beta + 415; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; @@ -1024,7 +1025,7 @@ moves_loop: // When in check, search starts here // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 1031; + r += 979; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1046,15 +1047,15 @@ moves_loop: // When in check, search starts here // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 242 + 238 * lmrDepth - + PieceValue[capturedPiece] + 95 * captHist / 700; + Value futilityValue = ss->staticEval + 242 + 230 * lmrDepth + + PieceValue[capturedPiece] + 133 * captHist / 1024; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 36, -153 * depth, 134 * depth); - if (!pos.see_ge(move, -157 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -138 * depth, 135 * depth); + if (!pos.see_ge(move, -154 * depth - seeHist)) continue; } else @@ -1065,17 +1066,17 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4107 * depth) + if (history < -4348 * depth) continue; history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3576; + lmrDepth += history / 3593; - Value futilityValue = ss->staticEval + (bestMove ? 49 : 143) + 116 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth; - if (bestValue < ss->staticEval - 150 && lmrDepth < 7) - futilityValue += 108; + if (bestValue < ss->staticEval - 128 && lmrDepth < 8) + futilityValue += 103; // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning @@ -1091,7 +1092,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1111,11 +1112,11 @@ moves_loop: // When in check, search starts here // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 5 - (thisThread->completedDepth > 32) + ss->ttPv + && depth >= 6 - (thisThread->completedDepth > 29) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (55 + 81 * (ss->ttPv && !PvNode)) * depth / 58; + Value singularBeta = ttData.value - (59 + 77 * (ss->ttPv && !PvNode)) * depth / 54; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1125,11 +1126,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj1 = std::abs(correctionValue) / 265083; - int corrValAdj2 = std::abs(correctionValue) / 253680; - int doubleMargin = 267 * PvNode - 181 * !ttCapture - corrValAdj1; + int corrValAdj1 = std::abs(correctionValue) / 248873; + int corrValAdj2 = std::abs(correctionValue) / 255331; + int doubleMargin = 262 * PvNode - 188 * !ttCapture - corrValAdj1; int tripleMargin = - 96 + 282 * PvNode - 250 * !ttCapture + 103 * ss->ttPv - corrValAdj2; + 88 + 265 * PvNode - 256 * !ttCapture + 93 * ss->ttPv - corrValAdj2; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1182,46 +1183,46 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2230 + PvNode * 1013 + (ttData.value > alpha) * 925 - + (ttData.depth >= depth) * (971 + cutNode * 1159); + r -= 2381 + PvNode * 1008 + (ttData.value > alpha) * 880 + + (ttData.depth >= depth) * (1022 + cutNode * 1140); // These reduction adjustments have no proven non-linear scaling - r += 316 - moveCount * 32; + r += 306 - moveCount * 34; - r -= std::abs(correctionValue) / 31568; + r -= std::abs(correctionValue) / 29696; if (PvNode && !is_decisive(bestValue)) r -= risk_tolerance(pos, bestValue); // Increase reduction for cut nodes if (cutNode) - r += 2608 + 1024 * !ttData.move; + r += 2784 + 1038 * !ttData.move; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) - r += 1123 + (depth < 8) * 982; + r += 1171 + (depth < 8) * 985; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 3) - r += 981 + allNode * 833; + r += 1042 + allNode * 864; // For first picked move (ttMove) reduce reduction else if (move == ttData.move) - r -= 1982; + r -= 1937; if (capture) ss->statScore = - 688 * int(PieceValue[pos.captured_piece()]) / 100 + 846 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 4653; + - 4822; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3591; + + (*contHist[1])[movedPiece][move.to_sq()] - 3271; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 1407 / 16384; + r -= ss->statScore * 1582 / 16384; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1247,7 +1248,7 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); + const bool doDeeperSearch = value > (bestValue + 43 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1256,7 +1257,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = (value >= beta) * 2010; + int bonus = (value >= beta) * 1800; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } else if (value > alpha && value < bestValue + 9) @@ -1268,11 +1269,11 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1111; + r += 1156; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3554) - (r > 5373 && newDepth > 2), !cutNode); + newDepth - (r > 3495) - (r > 5510 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1379,7 +1380,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement - if (depth > 2 && depth < 15 && !is_decisive(value)) + if (depth > 2 && depth < 16 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1423,25 +1424,25 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (118 * (depth > 5) + 36 * !allNode + 161 * ((ss - 1)->moveCount > 8) - + 133 * (!ss->inCheck && bestValue <= ss->staticEval - 107) - + 120 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 84) - + 81 * ((ss - 1)->isTTMove) + 100 * (ss->cutoffCnt <= 3) - + std::min(-(ss - 1)->statScore / 108, 320)); + int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 112, 303)); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(160 * depth - 106, 1523) * bonusScale; + const int scaledBonus = std::min(160 * depth - 99, 1492) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 416 / 32768); + scaledBonus * 388 / 32768); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << scaledBonus * 219 / 32768; + << scaledBonus * 212 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1103 / 32768; + << scaledBonus * 1055 / 32768; } else if (priorCapture && prevSq != SQ_NONE) @@ -1450,7 +1451,7 @@ moves_loop: // When in check, search starts here Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] - << std::min(330 * depth - 198, 3320); + << std::min(300 * depth - 182, 2995); } if (PvNode) @@ -1601,7 +1602,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 325; + futilityBase = ss->staticEval + 359; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1664,7 +1665,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 5389) + <= 5923) continue; // Do not search moves with bad enough SEE values @@ -1735,7 +1736,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 735 / rootDelta + !i * reductionScale * 191 / 512 + 1132; + return reductionScale - delta * 764 / rootDelta + !i * reductionScale * 191 / 512 + 1087; } // elapsed() returns the time elapsed since the search started. If the @@ -1831,35 +1832,35 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = std::min(162 * depth - 92, 1587) + 298 * isTTMove; - int malus = std::min(694 * depth - 230, 2503) - 32 * (moveCount - 1); + int bonus = std::min(141 * depth - 89, 1613) + 311 * isTTMove; + int malus = std::min(695 * depth - 215, 2808) - 31 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1202 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1129 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1152 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1246 / 1024); } else { // Increase stats for the best move in case it was a capture move captured = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1236 / 1024; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1187 / 1024; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 976 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 987 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { moved_piece = pos.moved_piece(move); captured = type_of(pos.piece_on(move.to_sq())); - captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1224 / 1024; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1377 / 1024; } } @@ -1868,7 +1869,7 @@ void update_all_stats(const Position& pos, // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { static constexpr std::array conthist_bonuses = { - {{1, 1029}, {2, 656}, {3, 326}, {4, 536}, {5, 120}, {6, 537}}}; + {{1, 1103}, {2, 659}, {3, 323}, {4, 533}, {5, 121}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1889,12 +1890,12 @@ void update_quiet_histories( workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 844 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 829 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 964 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 1004 / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 615 / 1024; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 587 / 1024; } } From f3bfce353168b03e4fedce515de1898c691f81ec Mon Sep 17 00:00:00 2001 From: xu-shawn <50402888+xu-shawn@users.noreply.github.com> Date: Fri, 28 Feb 2025 00:50:59 -0800 Subject: [PATCH 483/834] Revert "Replace aligned() function with line_bb() and simplify king piece detection" (#5915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/official-stockfish/Stockfish/pull/5915 No functional change Co-authored-by: Robert Nürnberg --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 1e19e3c8a..37e9a2eb5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); + return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !(line_bb(from, to) & pieces(~sideToMove, KING) || m.type_of() == CASTLING); + return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From 99d32e395ee0509578553d75580234e90de89d66 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 28 Feb 2025 00:55:29 -0800 Subject: [PATCH 484/834] Reapply #5909 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This version fixes the logic of `gives_check`, which was identified to be the cause of illegal moves. closes https://github.com/official-stockfish/Stockfish/pull/5914 No functional change Co-authored-by: Robert Nürnberg Co-authored-by: gab8192 --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37e9a2eb5..14599a76d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; + return !(line_bb(from, to) & pieces(~sideToMove, KING)) || m.type_of() == CASTLING; switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From b825ea6e57f224d7f468b1562a71fa4c22fe2fd8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 28 Feb 2025 09:30:43 -0800 Subject: [PATCH 485/834] Improve Perft Testing Added #5909 problematic position and chess-library DFRC positions to perft testing. Additional work done by @Disservin to clean up and improve error reporting. closes https://github.com/official-stockfish/Stockfish/pull/5917 No functional change Co-authored-by: disservin --- tests/perft.sh | 93 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/tests/perft.sh b/tests/perft.sh index c1532c20c..97c462c57 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -1,6 +1,8 @@ #!/bin/bash # verify perft numbers (positions from https://www.chessprogramming.org/Perft_Results) +TESTS_FAILED=0 + error() { echo "perft testing failed on line $1" @@ -10,23 +12,82 @@ trap 'error ${LINENO}' ERR echo "perft testing started" -cat << EOF > perft.exp - set timeout 10 - lassign \$argv pos depth result - spawn ./stockfish - send "position \$pos\\ngo perft \$depth\\n" - expect "Nodes searched? \$result" {} timeout {exit 1} - send "quit\\n" - expect eof +EXPECT_SCRIPT=$(mktemp) + +cat << 'EOF' > $EXPECT_SCRIPT +#!/usr/bin/expect -f +set timeout 30 +lassign [lrange $argv 0 4] pos depth result chess960 logfile +log_file -noappend $logfile +spawn ./stockfish +if {$chess960 == "true"} { + send "setoption name UCI_Chess960 value true\n" +} +send "position $pos\ngo perft $depth\n" +expect { + "Nodes searched: $result" {} + timeout {puts "TIMEOUT: Expected $result nodes"; exit 1} + eof {puts "EOF: Stockfish crashed"; exit 2} +} +send "quit\n" +expect eof EOF -expect perft.exp startpos 5 4865609 > /dev/null -expect perft.exp "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 > /dev/null -expect perft.exp "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 6 11030083 > /dev/null -expect perft.exp "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 5 15833292 > /dev/null -expect perft.exp "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 > /dev/null -expect perft.exp "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 > /dev/null +chmod +x $EXPECT_SCRIPT -rm perft.exp +run_test() { + local pos="$1" + local depth="$2" + local expected="$3" + local chess960="$4" + local tmp_file=$(mktemp) -echo "perft testing OK" + echo -n "Testing depth $depth: ${pos:0:40}... " + + if $EXPECT_SCRIPT "$pos" "$depth" "$expected" "$chess960" "$tmp_file" > /dev/null 2>&1; then + echo "OK" + rm -f "$tmp_file" + else + local exit_code=$? + echo "FAILED (exit code: $exit_code)" + echo "===== Output for failed test =====" + cat "$tmp_file" + echo "==================================" + rm -f "$tmp_file" + TESTS_FAILED=1 + fi +} + +# standard positions + +run_test "startpos" 7 3195901860 "false" +run_test "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 "false" +run_test "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 7 178633661 "false" +run_test "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 6 706045033 "false" +run_test "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 "false" +run_test "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 "false" +run_test "fen r7/4p3/5p1q/3P4/4pQ2/4pP2/6pp/R3K1kr w Q - 1 3" 5 11609488 "false" + +# chess960 positions + +run_test "fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1" 6 119060324 "true" +run_test "fen 1rqbkrbn/1ppppp1p/1n6/p1N3p1/8/2P4P/PP1PPPP1/1RQBKRBN w FBfb - 0 9" 6 191762235 "true" +run_test "fen rbbqn1kr/pp2p1pp/6n1/2pp1p2/2P4P/P7/BP1PPPP1/R1BQNNKR w HAha - 0 9" 6 924181432 "true" +run_test "fen rqbbknr1/1ppp2pp/p5n1/4pp2/P7/1PP5/1Q1PPPPP/R1BBKNRN w GAga - 0 9" 6 308553169 "true" +run_test "fen 4rrb1/1kp3b1/1p1p4/pP1Pn2p/5p2/1PR2P2/2P1NB1P/2KR1B2 w D - 0 21" 6 872323796 "true" +run_test "fen 1rkr3b/1ppn3p/3pB1n1/6q1/R2P4/4N1P1/1P5P/2KRQ1B1 b Dbd - 0 14" 6 2678022813 "true" +run_test "fen qbbnrkr1/p1pppppp/1p4n1/8/2P5/6N1/PPNPPPPP/1BRKBRQ1 b FCge - 1 3" 6 521301336 "true" +run_test "fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2R2R b E - 1 20" 2 1438 "true" +run_test "fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 w E - 0 20" 3 37340 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p2Q1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 b E - 2 19" 4 2237725 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 4 2098209 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 5 79014522 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 6 2998685421 "true" + +rm -f $EXPECT_SCRIPT +echo "perft testing completed" + +if [ $TESTS_FAILED -ne 0 ]; then + echo "Some tests failed" + exit 1 +fi From e407a4f269ba4389f31e9bb71fd8b944e3056ced Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Fri, 28 Feb 2025 17:54:46 -0800 Subject: [PATCH 486/834] Simplify risk_tolerance + avoid overflow passed simplification STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 73984 W: 19058 L: 18879 D: 36047 Ptnml(0-2): 232, 8735, 18866, 8940, 219 https://tests.stockfishchess.org/tests/view/67c269a38200cf1034c9baf9 passed simplification LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 39288 W: 10033 L: 9833 D: 19422 Ptnml(0-2): 14, 4168, 11086, 4356, 20 https://tests.stockfishchess.org/tests/view/67c34f8c8200cf1034c9bda1 closes https://github.com/official-stockfish/Stockfish/pull/5919 Bench: 2050046 --- src/search.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bbd43ed69..bacd63c95 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -99,28 +100,28 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss int risk_tolerance(const Position& pos, Value v) { // Returns (some constant of) second derivative of sigmoid. static constexpr auto sigmoid_d2 = [](int x, int y) { - return -355752 * x / (x * x + 3 * y * y); + return 644800 * x / ((x * x + 3 * y * y) * y); }; - int material = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() - + 337 * pos.count() + 553 * pos.count()) - / 64; - - int m = std::clamp(material, 17, 78); + int m = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() + + 337 * pos.count() + 553 * pos.count()) + / 64; // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) // The loss rate is 1/(1+exp((v+a)/b)) - int a = ((-m * 3037 / 256 + 2270) * m / 256 - 637) * m / 256 + 413; - int b = ((m * 7936 / 256 - 2255) * m / 256 + 319) * m / 256 + 83; + int a = 356; + int b = ((65 * m - 3172) * m + 240578) / 2048; + // guard against overflow + assert(abs(v) + a <= std::numeric_limits::max() / 644800); // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) int winning_risk = sigmoid_d2(v - a, b); - int losing_risk = -sigmoid_d2(-v - a, b); + int losing_risk = sigmoid_d2(v + a, b); - return (winning_risk + losing_risk) * 58 / b; + return -(winning_risk + losing_risk) * 32; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -1192,7 +1193,7 @@ moves_loop: // When in check, search starts here r -= std::abs(correctionValue) / 29696; - if (PvNode && !is_decisive(bestValue)) + if (PvNode && std::abs(bestValue) <= 2000) r -= risk_tolerance(pos, bestValue); // Increase reduction for cut nodes From e3660b47bdd8249c3e647b11f506058a99167a69 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 2 Mar 2025 21:38:11 -0800 Subject: [PATCH 487/834] Add dbg_clear helper function closes https://github.com/official-stockfish/Stockfish/pull/5921 No functional change --- src/misc.cpp | 15 ++++++++++++++- src/misc.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 06a8c624a..f85356c59 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -287,12 +287,18 @@ namespace { template struct DebugInfo { - std::atomic data[N] = {0}; + std::array, N> data = {0}; [[nodiscard]] constexpr std::atomic& operator[](size_t index) { assert(index < N); return data[index]; } + + constexpr DebugInfo& operator=(const DebugInfo& other) { + for (size_t i = 0; i < N; i++) + data[i].store(other.data[i].load()); + return *this; + } }; struct DebugExtremes: public DebugInfo<3> { @@ -393,6 +399,13 @@ void dbg_print() { } } +void dbg_clear() { + hit.fill({}); + mean.fill({}); + stdev.fill({}); + correl.fill({}); + extremes.fill({}); +} // Used to serialize access to std::cout // to avoid multiple threads writing at the same time. diff --git a/src/misc.h b/src/misc.h index d2cbb699d..84f11d6de 100644 --- a/src/misc.h +++ b/src/misc.h @@ -73,6 +73,7 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_extremes_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); +void dbg_clear(); using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); From f9a6d4328654f31cca5414be988b1158e555e09b Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 6 Mar 2025 19:04:56 -0800 Subject: [PATCH 488/834] Simplify condition in futility pruning Passed STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 427040 W: 111061 L: 111271 D: 204708 Ptnml(0-2): 1709, 48524, 113171, 48500, 1616 https://tests.stockfishchess.org/tests/view/67af01d01a4c73ae1f930ea4 Passed rebased LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 28704 W: 7330 L: 7120 D: 14254 Ptnml(0-2): 8, 3000, 8138, 3186, 20 https://tests.stockfishchess.org/tests/view/67ca629a45214989aa0a123e closes https://github.com/official-stockfish/Stockfish/pull/5924 Bench: 2050046 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bacd63c95..440cdc8e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1074,10 +1074,8 @@ moves_loop: // When in check, search starts here lmrDepth += history / 3593; - Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth; - - if (bestValue < ss->staticEval - 128 && lmrDepth < 8) - futilityValue += 103; + Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth + + 103 * (bestValue < ss->staticEval - 128); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning From 66aee01bb1430ee25ba4df96e0c4c4a931759e4c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 2 Mar 2025 01:29:38 -0800 Subject: [PATCH 489/834] Simplify Return Value Adjustment Condition Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82112 W: 21281 L: 21110 D: 39721 Ptnml(0-2): 258, 9630, 21112, 9795, 261 https://tests.stockfishchess.org/tests/view/67c42528b7226b5d8a2dd3a0 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 182652 W: 46295 L: 46240 D: 90117 Ptnml(0-2): 103, 20025, 51003, 20104, 91 https://tests.stockfishchess.org/tests/view/67c4d56b685e87e15e7c43d8 closes https://github.com/official-stockfish/Stockfish/pull/5925 Bench: 1711791 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 440cdc8e3..baf99c0c6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1406,9 +1406,8 @@ moves_loop: // When in check, search starts here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); - // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) - && !is_decisive(alpha)) + // Adjust best value for fail high cases + if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) && !is_decisive(alpha)) bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) From fc0e0a44d407dfa440c83e86de9b338b7e2d092d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 9 Mar 2025 19:33:30 -0700 Subject: [PATCH 490/834] Refactor accumulator storage/updates Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 115840 W: 29983 L: 29854 D: 56003 Ptnml(0-2): 338, 12990, 31149, 13091, 352 https://tests.stockfishchess.org/tests/view/67d0a044166a3e8781d84223 closes https://github.com/official-stockfish/Stockfish/pull/5927 No functional change --- src/Makefile | 3 +- src/evaluate.cpp | 16 +- src/evaluate.h | 2 + src/nnue/features/half_ka_v2_hm.cpp | 4 +- src/nnue/features/half_ka_v2_hm.h | 5 +- src/nnue/network.cpp | 15 +- src/nnue/network.h | 13 +- src/nnue/nnue_accumulator.cpp | 601 ++++++++++++++++++++++++++++ src/nnue/nnue_accumulator.h | 89 +++- src/nnue/nnue_common.h | 5 + src/nnue/nnue_feature_transformer.h | 449 +-------------------- src/nnue/nnue_misc.cpp | 15 +- src/perft.h | 1 - src/position.cpp | 47 +-- src/position.h | 24 +- src/search.cpp | 44 +- src/search.h | 7 + 17 files changed, 813 insertions(+), 527 deletions(-) create mode 100644 src/nnue/nnue_accumulator.cpp diff --git a/src/Makefile b/src/Makefile index 39cfce8bf..76b94785e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,8 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp memory.cpp + nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \ + engine.cpp score.cpp memory.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ diff --git a/src/evaluate.cpp b/src/evaluate.cpp index dddb56860..ccb089d97 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -54,21 +54,22 @@ bool Eval::use_smallnet(const Position& pos) { // of the position from the point of view of the side to move. Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, int optimism) { assert(!pos.checkers()); bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) - : networks.big.evaluate(pos, &caches.big); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) + : networks.big.evaluate(pos, accumulators, &caches.big); Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (std::abs(nnue) < 236)) { - std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); nnue = (125 * psqt + 131 * positional) / 128; smallNet = false; } @@ -99,7 +100,10 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pos.checkers()) return "Final evaluation: none (in check)"; - auto caches = std::make_unique(networks); + Eval::NNUE::AccumulatorStack accumulators; + auto caches = std::make_unique(networks); + + accumulators.reset(pos, networks, *caches); std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); @@ -107,12 +111,12 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqt, positional] = networks.big.evaluate(pos, &caches->big); + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches->big); Value v = psqt + positional; v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; - v = evaluate(networks, pos, *caches, VALUE_ZERO); + v = evaluate(networks, pos, accumulators, *caches, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index aad358321..07b914007 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,6 +39,7 @@ namespace Eval { namespace NNUE { struct Networks; struct AccumulatorCaches; +class AccumulatorStack; } std::string trace(Position& pos, const Eval::NNUE::Networks& networks); @@ -47,6 +48,7 @@ int simple_eval(const Position& pos, Color c); bool use_smallnet(const Position& pos); Value evaluate(const NNUE::Networks& networks, const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, int optimism); } // namespace Eval diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 81eddb060..eb3c7e6a7 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -77,8 +77,8 @@ template void HalfKAv2_hm::append_changed_indices(Square ksq, IndexList& removed, IndexList& added); -bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { - return st->dirtyPiece.piece[0] == make_piece(perspective, KING); +bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) { + return dirtyPiece.piece[0] == make_piece(perspective, KING); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 0a420cd1e..ba122adc8 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -28,7 +28,6 @@ #include "../nnue_common.h" namespace Stockfish { -struct StateInfo; class Position; } @@ -135,9 +134,9 @@ class HalfKAv2_hm { static void append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - // Returns whether the change stored in this StateInfo means + // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. - static bool requires_refresh(const StateInfo* st, Color perspective); + static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective); }; } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index fe312fcb8..cba3abc63 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -210,6 +210,7 @@ bool Network::save(const std::optional& filename template NetworkOutput Network::evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -229,8 +230,9 @@ Network::evaluate(const Position& pos ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); return {static_cast(psqt / OutputScale), static_cast(positional / OutputScale)}; } @@ -280,6 +282,7 @@ void Network::verify(std::string template NnueEvalTrace Network::trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -303,7 +306,7 @@ Network::trace_evaluate(const Position& for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, cache, transformedFeatures, bucket); + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); @@ -447,14 +450,14 @@ bool Network::write_parameters(std::ostream& stream, return bool(stream); } -// Explicit template instantiation +// Explicit template instantiations template class Network< NetworkArchitecture, - FeatureTransformer>; + FeatureTransformer>; template class Network< NetworkArchitecture, - FeatureTransformer>; + FeatureTransformer>; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h index 764481d94..21df4b0a1 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -29,13 +29,16 @@ #include #include "../memory.h" -#include "../position.h" #include "../types.h" #include "nnue_accumulator.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Eval::NNUE { enum class EmbeddedNNUEType { @@ -64,11 +67,13 @@ class Network { bool save(const std::optional& filename) const; NetworkOutput evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const; void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const; private: @@ -100,16 +105,18 @@ class Network { template friend struct AccumulatorCaches::Cache; + + friend class AccumulatorStack; }; // Definitions of the network types using SmallFeatureTransformer = - FeatureTransformer; + FeatureTransformer; using SmallNetworkArchitecture = NetworkArchitecture; using BigFeatureTransformer = - FeatureTransformer; + FeatureTransformer; using BigNetworkArchitecture = NetworkArchitecture; using NetworkBig = Network; diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp new file mode 100644 index 000000000..0a3d95ad4 --- /dev/null +++ b/src/nnue/nnue_accumulator.cpp @@ -0,0 +1,601 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "nnue_accumulator.h" + +#include +#include +#include + +#include "../bitboard.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "network.h" +#include "nnue_common.h" +#include "nnue_feature_transformer.h" + +namespace Stockfish::Eval::NNUE { + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed); + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache); + +} + +void AccumulatorState::reset(const DirtyPiece& dp) noexcept { + dirtyPiece = dp; + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); +} + +const AccumulatorState& AccumulatorStack::latest() const noexcept { + return m_accumulators[m_current_idx - 1]; +} + +AccumulatorState& AccumulatorStack::mut_latest() noexcept { + return m_accumulators[m_current_idx - 1]; +} + +void AccumulatorStack::reset(const Position& rootPos, + const Networks& networks, + AccumulatorCaches& caches) noexcept { + m_current_idx = 1; + + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); +} + +void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { + assert(m_current_idx + 1 < m_accumulators.size()); + m_accumulators[m_current_idx].reset(dirtyPiece); + m_current_idx++; +} + +void AccumulatorStack::pop() noexcept { + assert(m_current_idx > 1); + m_current_idx--; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + evaluate_side(pos, featureTransformer, cache); + evaluate_side(pos, featureTransformer, cache); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate_side( + const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + const auto last_usable_accum = find_last_usable_accumulator(); + + if ((m_accumulators[last_usable_accum].*accPtr).computed[Perspective]) + forward_update_incremental(pos, featureTransformer, last_usable_accum); + + else + { + update_accumulator_refresh_cache(featureTransformer, pos, mut_latest(), cache); + backward_update_incremental(pos, featureTransformer, last_usable_accum); + } +} + +// Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator +// state just before a change that requires full refresh. +template AccumulatorState::*accPtr> +std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { + + for (std::size_t curr_idx = m_current_idx - 1; curr_idx > 0; curr_idx--) + { + if ((m_accumulators[curr_idx].*accPtr).computed[Perspective]) + return curr_idx; + + if (FeatureSet::requires_refresh(m_accumulators[curr_idx].dirtyPiece, Perspective)) + return curr_idx; + } + + return 0; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::forward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept { + + assert(begin < m_accumulators.size()); + assert((m_accumulators[begin].*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = begin + 1; next < m_current_idx; next++) + update_accumulator_incremental(featureTransformer, ksq, m_accumulators[next], + m_accumulators[next - 1]); + + assert((latest().*accPtr).computed[Perspective]); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::backward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept { + + assert(end < m_accumulators.size()); + assert(end < m_current_idx); + assert((latest().*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = m_current_idx - 2; next >= end; next--) + update_accumulator_incremental( + featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); + + assert((m_accumulators[end].*accPtr).computed[Perspective]); +} + +// Explicit template instantiations +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed) { + [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; + [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; + + assert(Forward != Backwards); + + assert((computed.*accPtr).computed[Perspective]); + assert(!(target_state.*accPtr).computed[Perspective]); + + // The size must be enough to contain the largest possible update. + // That might depend on the feature set and generally relies on the + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. + // In this case, the maximum size of both feature addition and removal + // is 2, since we are incrementally updating one move at a time. + FeatureSet::IndexList removed, added; + if constexpr (Forward) + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, + added); + else + FeatureSet::append_changed_indices(ksq, computed.dirtyPiece, added, removed); + + if (removed.size() == 0 && added.size() == 0) + { + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + } + else + { + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); + +#ifdef VECTOR + auto* accIn = + reinterpret_cast(&(computed.*accPtr).accumulation[Perspective][0]); + auto* accOut = + reinterpret_cast(&(target_state.*accPtr).accumulation[Perspective][0]); + + const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; + auto* columnA0 = reinterpret_cast(&featureTransformer.weights[offsetA0]); + const IndexType offsetR0 = TransformedFeatureDimensions * removed[0]; + auto* columnR0 = reinterpret_cast(&featureTransformer.weights[offsetR0]); + + if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) + { + assert(added.size() == 1 && removed.size() == 1); + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), + vec_add_16(columnR0[i], columnR1[i])); + } + else if (Backwards && removed.size() == 1) + { + assert(added.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), + vec_sub_16(columnA1[i], columnR0[i])); + } + else + { + assert(added.size() == 2 && removed.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), + vec_add_16(columnR0[i], columnR1[i]))); + } + + auto* accPsqtIn = + reinterpret_cast(&(computed.*accPtr).psqtAccumulation[Perspective][0]); + auto* accPsqtOut = + reinterpret_cast(&(target_state.*accPtr).psqtAccumulation[Perspective][0]); + + const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; + auto* columnPsqtA0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA0]); + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; + auto* columnPsqtR0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR0]); + + if ((Forward && removed.size() == 1) + || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 + { + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = + vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); + } + else if (Forward && added.size() == 1) + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); + } + else if (Backwards && removed.size() == 1) + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); + } + else + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32( + accPsqtIn[i], vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); + } +#else + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + + // Difference calculation for the deactivated features + for (const auto index : removed) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] -= + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] -= + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } + + // Difference calculation for the activated features + for (const auto index : added) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] += + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] += + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } +#endif + } + + (target_state.*accPtr).computed[Perspective] = true; +} + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache) { + using Tiling [[maybe_unused]] = SIMDTiling; + + const Square ksq = pos.square(Perspective); + auto& entry = cache[ksq][Perspective]; + FeatureSet::IndexList removed, added; + + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + } + } + + auto& accumulator = accumulatorState.*accPtr; + accumulator.computed[Perspective] = true; + +#ifdef VECTOR + const bool combineLast3 = + std::abs((int) removed.size() - (int) added.size()) == 1 && removed.size() + added.size() > 2; + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; + + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) + { + auto* accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = entryTile[k]; + + std::size_t i = 0; + for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); + } + if (combineLast3) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + if (removed.size() > added.size()) + { + IndexType indexR2 = removed[i + 1]; + const IndexType offsetR2 = Dimensions * indexR2 + j * Tiling::TileHeight; + auto* columnR2 = + reinterpret_cast(&featureTransformer.weights[offsetR2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), + vec_add_16(columnR[k], columnR2[k])); + } + else + { + IndexType indexA2 = added[i + 1]; + const IndexType offsetA2 = Dimensions * indexA2 + j * Tiling::TileHeight; + auto* columnA2 = + reinterpret_cast(&featureTransformer.weights[offsetA2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), + vec_add_16(columnA[k], columnA2[k])); + } + } + else + { + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + } + + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) + { + auto* accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + auto* entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = entryTilePsqt[k]; + + for (std::size_t i = 0; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + for (std::size_t i = 0; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&entryTilePsqt[k], psqt[k]); + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + + for (const auto index : removed) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] -= featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] -= featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + for (const auto index : added) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] += featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] += featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + + // The accumulator of the refresh entry has been updated. + // Now copy its content to the actual accumulator we were refreshing. + + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + sizeof(BiasType) * Dimensions); + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, + sizeof(int32_t) * PSQTBuckets); +#endif + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[pt] = pos.pieces(pt); +} + +} + +} diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 0d3d94135..362ea83e3 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -21,23 +21,43 @@ #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED +#include +#include #include +#include +#include +#include "../types.h" #include "nnue_architecture.h" #include "nnue_common.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Eval::NNUE { using BiasType = std::int16_t; using PSQTWeightType = std::int32_t; using IndexType = std::uint32_t; +struct Networks; + +template +struct alignas(CacheLineSize) Accumulator; + +struct AccumulatorState; + +template AccumulatorState::*accPtr> +class FeatureTransformer; + // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[COLOR_NB][Size]; - std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - bool computed[COLOR_NB]; + std::int16_t accumulation[COLOR_NB][Size]; + std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; + std::array computed; }; @@ -95,6 +115,69 @@ struct AccumulatorCaches { Cache small; }; + +struct AccumulatorState { + Accumulator accumulatorBig; + Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; + + void reset(const DirtyPiece& dp) noexcept; +}; + + +class AccumulatorStack { + public: + AccumulatorStack() : + m_accumulators(MAX_PLY + 1), + m_current_idx{} {} + + [[nodiscard]] const AccumulatorState& latest() const noexcept; + + void + reset(const Position& rootPos, const Networks& networks, AccumulatorCaches& caches) noexcept; + void push(const DirtyPiece& dirtyPiece) noexcept; + void pop() noexcept; + + template AccumulatorState::*accPtr> + void evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + private: + [[nodiscard]] AccumulatorState& mut_latest() noexcept; + + template AccumulatorState::*accPtr> + void evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + template AccumulatorState::*accPtr> + [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + + template AccumulatorState::*accPtr> + void + forward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept; + + template AccumulatorState::*accPtr> + void + backward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept; + + std::vector m_accumulators; + std::size_t m_current_idx; +}; + } // namespace Stockfish::Eval::NNUE #endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f21a8dec7..b217c3583 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -279,6 +279,11 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size flush(); } +enum IncUpdateDirection { + FORWARD, + BACKWARDS +}; + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7e4c669ae..20e85be3c 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -22,12 +22,9 @@ #define NNUE_FEATURE_TRANSFORMER_H_INCLUDED #include -#include #include #include #include -#include -#include #include "../position.h" #include "../types.h" @@ -41,11 +38,6 @@ using BiasType = std::int16_t; using WeightType = std::int16_t; using PSQTWeightType = std::int32_t; -enum IncUpdateDirection { - FORWARD, - BACKWARDS -}; - // If vector instructions are enabled, we update and refresh the // accumulator tile by tile such that each tile fits in the CPU's // vector registers. @@ -249,15 +241,12 @@ class SIMDTiling { // Input feature converter template StateInfo::*accPtr> + Accumulator AccumulatorState::*accPtr> class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - private: - using Tiling = SIMDTiling; - public: // Output type using OutputType = TransformedFeatureType; @@ -348,19 +337,21 @@ class FeatureTransformer { // Convert input features std::int32_t transform(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache, OutputType* output, int bucket) const { - update_accumulator(pos, cache); - update_accumulator(pos, cache); + + accumulatorStack.evaluate(pos, *this, *cache); + const auto& accumulatorState = accumulatorStack.latest(); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; + const auto& psqtAccumulation = (accumulatorState.*accPtr).psqtAccumulation; const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; - const auto& accumulation = (pos.state()->*accPtr).accumulation; + const auto& accumulation = (accumulatorState.*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) { @@ -473,432 +464,6 @@ class FeatureTransformer { return psqt; } // end of function transform() - private: - // Given a computed accumulator, computes the accumulator of another position. - template - void update_accumulator_incremental(const Square ksq, - StateInfo* target_state, - const StateInfo* computed) const { - [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; - [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; - assert((computed->*accPtr).computed[Perspective]); - - StateInfo* next = Forward ? computed->next : computed->previous; - - assert(next != nullptr); - assert(!(next->*accPtr).computed[Perspective]); - - // The size must be enough to contain the largest possible update. - // That might depend on the feature set and generally relies on the - // feature set's update cost calculation to be correct and never allow - // updates with more added/removed features than MaxActiveDimensions. - // In this case, the maximum size of both feature addition and removal - // is 2, since we are incrementally updating one move at a time. - FeatureSet::IndexList removed, added; - if constexpr (Forward) - FeatureSet::append_changed_indices(ksq, next->dirtyPiece, removed, added); - else - FeatureSet::append_changed_indices(ksq, computed->dirtyPiece, added, - removed); - - if (removed.size() == 0 && added.size() == 0) - { - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - } - else - { - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); - if (Forward) - assert(added.size() <= removed.size()); - else - assert(removed.size() <= added.size()); - -#ifdef VECTOR - auto* accIn = - reinterpret_cast(&(computed->*accPtr).accumulation[Perspective][0]); - auto* accOut = reinterpret_cast(&(next->*accPtr).accumulation[Perspective][0]); - - const IndexType offsetA0 = HalfDimensions * added[0]; - auto* columnA0 = reinterpret_cast(&weights[offsetA0]); - const IndexType offsetR0 = HalfDimensions * removed[0]; - auto* columnR0 = reinterpret_cast(&weights[offsetR0]); - - if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) - { - assert(added.size() == 1 && removed.size() == 1); - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); - } - else if (Forward && added.size() == 1) - { - assert(removed.size() == 2); - const IndexType offsetR1 = HalfDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), - vec_add_16(columnR0[i], columnR1[i])); - } - else if (Backwards && removed.size() == 1) - { - assert(added.size() == 2); - const IndexType offsetA1 = HalfDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&weights[offsetA1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), - vec_sub_16(columnA1[i], columnR0[i])); - } - else - { - assert(added.size() == 2 && removed.size() == 2); - const IndexType offsetA1 = HalfDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&weights[offsetA1]); - const IndexType offsetR1 = HalfDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = - vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), - vec_add_16(columnR0[i], columnR1[i]))); - } - - auto* accPsqtIn = reinterpret_cast( - &(computed->*accPtr).psqtAccumulation[Perspective][0]); - auto* accPsqtOut = - reinterpret_cast(&(next->*accPtr).psqtAccumulation[Perspective][0]); - - const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; - auto* columnPsqtA0 = reinterpret_cast(&psqtWeights[offsetPsqtA0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; - auto* columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - - if ((Forward && removed.size() == 1) - || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 - { - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), - columnPsqtA0[i]); - } - else if (Forward && added.size() == 1) - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = - vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); - } - else if (Backwards && removed.size() == 1) - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&psqtWeights[offsetPsqtA1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = - vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); - } - else - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&psqtWeights[offsetPsqtA1]); - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = vec_add_psqt_32( - accPsqtIn[i], - vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); - } -#else - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] -= - psqtWeights[index * PSQTBuckets + i]; - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] += weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] += - psqtWeights[index * PSQTBuckets + i]; - } -#endif - } - - (next->*accPtr).computed[Perspective] = true; - - if (next != target_state) - update_accumulator_incremental(ksq, target_state, next); - } - - - template - void update_accumulator_refresh_cache(const Position& pos, - AccumulatorCaches::Cache* cache) const { - assert(cache != nullptr); - - Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq][Perspective]; - FeatureSet::IndexList removed, added; - - for (Color c : {WHITE, BLACK}) - { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; - - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - } - } - - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; - -#ifdef VECTOR - const bool combineLast3 = std::abs((int) removed.size() - (int) added.size()) == 1 - && removed.size() + added.size() > 2; - vec_t acc[Tiling::NumRegs]; - psqt_vec_t psqt[Tiling::NumPsqtRegs]; - - for (IndexType j = 0; j < HalfDimensions / Tiling::TileHeight; ++j) - { - auto* accTile = reinterpret_cast( - &accumulator.accumulation[Perspective][j * Tiling::TileHeight]); - auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = entryTile[k]; - - std::size_t i = 0; - for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) - { - IndexType indexR = removed[i]; - const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&weights[offsetA]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); - } - if (combineLast3) - { - IndexType indexR = removed[i]; - const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&weights[offsetA]); - - if (removed.size() > added.size()) - { - IndexType indexR2 = removed[i + 1]; - const IndexType offsetR2 = HalfDimensions * indexR2 + j * Tiling::TileHeight; - auto* columnR2 = reinterpret_cast(&weights[offsetR2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), - vec_add_16(columnR[k], columnR2[k])); - } - else - { - IndexType indexA2 = added[i + 1]; - const IndexType offsetA2 = HalfDimensions * indexA2 + j * Tiling::TileHeight; - auto* columnA2 = reinterpret_cast(&weights[offsetA2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), - vec_add_16(columnA[k], columnA2[k])); - } - } - else - { - for (; i < removed.size(); ++i) - { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - for (; i < added.size(); ++i) - { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - } - - for (IndexType k = 0; k < Tiling::NumRegs; k++) - vec_store(&entryTile[k], acc[k]); - for (IndexType k = 0; k < Tiling::NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) - { - auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); - auto* entryTilePsqt = - reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = entryTilePsqt[k]; - - for (std::size_t i = 0; i < removed.size(); ++i) - { - IndexType index = removed[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } - for (std::size_t i = 0; i < added.size(); ++i) - { - IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - vec_store_psqt(&entryTilePsqt[k], psqt[k]); - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - -#else - - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] -= weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; - } - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; - } - - // The accumulator of the refresh entry has been updated. - // Now copy its content to the actual accumulator we were refreshing. - - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, - sizeof(BiasType) * HalfDimensions); - - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, - sizeof(int32_t) * PSQTBuckets); -#endif - - for (Color c : {WHITE, BLACK}) - entry.byColorBB[c] = pos.pieces(c); - - for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[pt] = pos.pieces(pt); - } - - - template - void update_accumulator(const Position& pos, - AccumulatorCaches::Cache* cache) const { - StateInfo* st = pos.state(); - if ((st->*accPtr).computed[Perspective]) - return; // nothing to do - - // Look for a usable already computed accumulator of an earlier position. - // Always try to do an incremental update as most accumulators will be reusable. - do - { - if (FeatureSet::requires_refresh(st, Perspective) || !st->previous - || st->previous->next != st) - { - // compute accumulator from scratch for this position - update_accumulator_refresh_cache(pos, cache); - if (st != pos.state()) - // when computing an accumulator from scratch we can use it to - // efficiently compute the accumulator backwards, until we get to a king - // move. We expect that we will need these accumulators later anyway, so - // computing them now will save some work. - update_accumulator_incremental( - pos.square(Perspective), st, pos.state()); - return; - } - st = st->previous; - } while (!(st->*accPtr).computed[Perspective]); - - // Start from the oldest computed accumulator, update all the - // accumulators up to the current position. - update_accumulator_incremental(pos.square(Perspective), pos.state(), st); - } - - template - friend struct AccumulatorCaches::Cache; - alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 2220684da..809d454b5 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -120,9 +120,12 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat format_cp_compact(value, &board[y + 2][x + 2], pos); }; + AccumulatorStack accumulators; + accumulators.reset(pos, networks, caches); + // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - auto [psqt, positional] = networks.big.evaluate(pos, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -135,18 +138,15 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat if (pc != NO_PIECE && type_of(pc) != KING) { - auto st = pos.state(); - pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; - std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); + accumulators.reset(pos, networks, caches); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); @@ -157,7 +157,8 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << board[row] << '\n'; ss << '\n'; - auto t = networks.big.trace_evaluate(pos, &caches.big); + accumulators.reset(pos, networks, caches); + auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl diff --git a/src/perft.h b/src/perft.h index f0d38ab72..229debd40 100644 --- a/src/perft.h +++ b/src/perft.h @@ -34,7 +34,6 @@ template uint64_t perft(Position& pos, Depth depth) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); uint64_t cnt, nodes = 0; const bool leaf = (depth == 2); diff --git a/src/position.cpp b/src/position.cpp index 14599a76d..52e1004e8 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -34,7 +34,6 @@ #include "bitboard.h" #include "misc.h" #include "movegen.h" -#include "nnue/nnue_common.h" #include "syzygy/tbprobe.h" #include "tt.h" #include "uci.h" @@ -83,7 +82,6 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Position p; p.set(pos.fen(), pos.is_chess960(), &st); @@ -685,10 +683,10 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position // will be prefetched -void Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +DirtyPiece Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -709,11 +707,7 @@ void Position::do_move(Move m, ++st->rule50; ++st->pliesFromNull; - // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - - auto& dp = st->dirtyPiece; + DirtyPiece dp; dp.dirty_num = 1; Color us = sideToMove; @@ -733,7 +727,7 @@ void Position::do_move(Move m, assert(captured == make_piece(us, ROOK)); Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); + do_castling(us, from, to, rfrom, rto, &dp); k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; @@ -906,6 +900,8 @@ void Position::do_move(Move m, } assert(pos_is_ok()); + + return dp; } @@ -975,23 +971,25 @@ void Position::undo_move(Move m) { // Helper used to do/undo a castling move. This is a bit // tricky in Chess960 where from/to squares can overlap. template -void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { +void Position::do_castling( + Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) { bool kingSide = to > from; rfrom = to; // Castling is encoded as "king captures friendly rook" rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + assert(!Do || dp); + if (Do) { - auto& dp = st->dirtyPiece; - dp.piece[0] = make_piece(us, KING); - dp.from[0] = from; - dp.to[0] = to; - dp.piece[1] = make_piece(us, ROOK); - dp.from[1] = rfrom; - dp.to[1] = rto; - dp.dirty_num = 2; + dp->piece[0] = make_piece(us, KING); + dp->from[0] = from; + dp->to[0] = to; + dp->piece[1] = make_piece(us, ROOK); + dp->from[1] = rfrom; + dp->to[1] = rto; + dp->dirty_num = 2; } // Remove both pieces first since squares could overlap in Chess960 @@ -1011,7 +1009,7 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { assert(!checkers()); assert(&newSt != st); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulatorBig)); + std::memcpy(&newSt, st, sizeof(StateInfo)); newSt.previous = st; st->next = &newSt; @@ -1026,11 +1024,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { st->key ^= Zobrist::side; prefetch(tt.first_entry(key())); - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - st->pliesFromNull = 0; sideToMove = ~sideToMove; diff --git a/src/position.h b/src/position.h index eeea1b74c..75f22c7df 100644 --- a/src/position.h +++ b/src/position.h @@ -26,8 +26,6 @@ #include #include "bitboard.h" -#include "nnue/nnue_accumulator.h" -#include "nnue/nnue_architecture.h" #include "types.h" namespace Stockfish { @@ -61,11 +59,6 @@ struct StateInfo { Bitboard checkSquares[PIECE_TYPE_NB]; Piece capturedPiece; int repetition; - - // Used by NNUE - DirtyPiece dirtyPiece; - Eval::NNUE::Accumulator accumulatorBig; - Eval::NNUE::Accumulator accumulatorSmall; }; @@ -140,11 +133,11 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - void do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); - void undo_move(Move m); - void do_null_move(StateInfo& newSt, const TranspositionTable& tt); - void undo_null_move(); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + DirtyPiece do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -187,7 +180,12 @@ class Position { // Other helpers void move_piece(Square from, Square to); template - void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + void do_castling(Color us, + Square from, + Square& to, + Square& rfrom, + Square& rto, + DirtyPiece* const dp = nullptr); template Key adjust_key50(Key k) const; diff --git a/src/search.cpp b/src/search.cpp index baf99c0c6..8bc73b710 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -41,7 +41,6 @@ #include "movepick.h" #include "nnue/network.h" #include "nnue/nnue_accumulator.h" -#include "nnue/nnue_common.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -197,6 +196,8 @@ void Search::Worker::ensure_network_replicated() { void Search::Worker::start_searching() { + accumulatorStack.reset(rootPos, networks[numaAccessToken], refreshTable); + // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -552,6 +553,26 @@ void Search::Worker::iterative_deepening() { skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); } + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st) { + do_move(pos, move, st, pos.gives_check(move)); +} + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck) { + DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); + accumulatorStack.push(dp); +} + +void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } + +void Search::Worker::undo_move(Position& pos, const Move move) { + pos.undo_move(move); + accumulatorStack.pop(); +} + +void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } + + // Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(66); @@ -614,7 +635,6 @@ Value Search::Worker::search( Move pv[MAX_PLY + 1]; StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Key posKey; Move move, excludedMove, bestMove; @@ -859,11 +879,11 @@ Value Search::Worker::search( ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0]; - pos.do_null_move(st, tt); + do_null_move(pos, st); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); - pos.undo_null_move(); + undo_null_move(pos); // Do not return unproven mate or TB scores if (nullValue >= beta && !is_win(nullValue)) @@ -925,7 +945,7 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); - pos.do_move(move, st, &tt); + do_move(pos, move, st); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; @@ -943,7 +963,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth, !cutNode); - pos.undo_move(move); + undo_move(pos, move); if (value >= probCutBeta) { @@ -1165,7 +1185,7 @@ moves_loop: // When in check, search starts here } // Step 16. Make the move - pos.do_move(move, st, givesCheck, &tt); + do_move(pos, move, st, givesCheck); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Add extension to new depth @@ -1290,7 +1310,7 @@ moves_loop: // When in check, search starts here } // Step 19. Undo move - pos.undo_move(move); + undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1510,7 +1530,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Move pv[MAX_PLY + 1]; StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Key posKey; Move move, bestMove; @@ -1674,7 +1693,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Step 7. Make and search the move Piece movedPiece = pos.moved_piece(move); - pos.do_move(move, st, givesCheck, &tt); + do_move(pos, move, st, givesCheck); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Update the current move @@ -1685,7 +1704,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; value = -qsearch(pos, ss + 1, -beta, -alpha); - pos.undo_move(move); + undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1752,7 +1771,7 @@ TimePoint Search::Worker::elapsed() const { TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } Value Search::Worker::evaluate(const Position& pos) { - return Eval::evaluate(networks[numaAccessToken], pos, refreshTable, + return Eval::evaluate(networks[numaAccessToken], pos, accumulatorStack, refreshTable, optimism[pos.side_to_move()]); } @@ -2178,7 +2197,6 @@ void SearchManager::pv(Search::Worker& worker, bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); assert(pv.size() == 1); if (pv[0] == Move::none()) diff --git a/src/search.h b/src/search.h index 326480e4d..cd3b6ad98 100644 --- a/src/search.h +++ b/src/search.h @@ -295,6 +295,12 @@ class Worker { private: void iterative_deepening(); + void do_move(Position& pos, const Move move, StateInfo& st); + void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck); + void do_null_move(Position& pos, StateInfo& st); + void undo_move(Position& pos, const Move move); + void undo_null_move(Position& pos); + // This is the main search function, for both PV and non-PV nodes template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); @@ -347,6 +353,7 @@ class Worker { const LazyNumaReplicated& networks; // Used by NNUE + Eval::NNUE::AccumulatorStack accumulatorStack; Eval::NNUE::AccumulatorCaches refreshTable; friend class Stockfish::ThreadPool; From 4afd7f1a7b2379f9094a973676f1f03b2058428b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 9 Mar 2025 18:35:14 +0300 Subject: [PATCH 491/834] Removing contHist[1] from pruning formula Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 51552 W: 13297 L: 13091 D: 25164 Ptnml(0-2): 166, 6009, 13215, 6225, 161 https://tests.stockfishchess.org/tests/view/67c4de79685e87e15e7c43f5 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 244554 W: 62135 L: 62142 D: 120277 Ptnml(0-2): 137, 26612, 68794, 26589, 145 https://tests.stockfishchess.org/tests/view/67c50982685e87e15e7c443d closes https://github.com/official-stockfish/Stockfish/pull/5928 Bench: 1980385 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8bc73b710..bb0156c5d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1679,10 +1679,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Continuation history based pruning if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 5923) + <= 6290) continue; // Do not search moves with bad enough SEE values From 652a8874b523360a3b19c5003c8ba9894ac54d0f Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Fri, 14 Mar 2025 20:32:38 +0100 Subject: [PATCH 492/834] Allow more than 1024 threads on high-end machines closes https://github.com/official-stockfish/Stockfish/pull/5929 No functional change --- src/engine.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 6c8799a15..a4c0bb1eb 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -18,6 +18,7 @@ #include "engine.h" +#include #include #include #include @@ -32,6 +33,7 @@ #include "misc.h" #include "nnue/network.h" #include "nnue/nnue_common.h" +#include "numa.h" #include "perft.h" #include "position.h" #include "search.h" @@ -44,8 +46,9 @@ namespace Stockfish { namespace NN = Eval::NNUE; -constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency())); Engine::Engine(std::optional path) : binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), @@ -74,7 +77,7 @@ Engine::Engine(std::optional path) : })); options.add( // - "Threads", Option(1, 1, 1024, [this](const Option&) { + "Threads", Option(1, 1, MaxThreads, [this](const Option&) { resize_threads(); return thread_allocation_information_as_string(); })); From 8fc5e92005aad8f952817161818ec0082f9642e6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 16 Mar 2025 01:48:49 +0300 Subject: [PATCH 493/834] Use slightly different formula for stat score when position is in check Use formula that is closer to movepicker one. Passed STC: https://tests.stockfishchess.org/tests/view/67cffb337be98c1ad9b021ee LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 250432 W: 64978 L: 64343 D: 121111 Ptnml(0-2): 795, 29390, 64159, 30129, 743 Passed LTC: https://tests.stockfishchess.org/tests/view/67d3905d517865b4a2dfce8a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 140004 W: 35742 L: 35215 D: 69047 Ptnml(0-2): 60, 15111, 39151, 15602, 78 closes https://github.com/official-stockfish/Stockfish/pull/5930 Bench: 2147336 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index bb0156c5d..3eab34be4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1235,6 +1235,9 @@ moves_loop: // When in check, search starts here 846 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - 4822; + else if (ss->inCheck) + ss->statScore = thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] - 2771; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From 9045f17c3f19a7b779e4baf71689ddd650f5d64c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 14 Mar 2025 02:21:48 -0700 Subject: [PATCH 494/834] Simplify captures PCM Passed Non-regression STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 229856 W: 59258 L: 59252 D: 111346 Ptnml(0-2): 746, 27330, 58714, 27448, 690 https://tests.stockfishchess.org/tests/view/67d3fdac517865b4a2dfcef4 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 107652 W: 27470 L: 27338 D: 52844 Ptnml(0-2): 56, 11646, 30280, 11798, 46 https://tests.stockfishchess.org/tests/view/67d5f972517865b4a2dfd2ec closes https://github.com/official-stockfish/Stockfish/pull/5931 Bench: 1842520 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3eab34be4..4b6ad52e4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1442,7 +1442,7 @@ moves_loop: // When in check, search starts here update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, bestMove == ttData.move, moveCount); - // Bonus for prior countermove that caused the fail low + // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) @@ -1466,13 +1466,12 @@ moves_loop: // When in check, search starts here << scaledBonus * 1055 / 32768; } + // Bonus for prior capture countermove that caused the fail low else if (priorCapture && prevSq != SQ_NONE) { - // bonus for prior countermoves that caused the fail low Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] - << std::min(300 * depth - 182, 2995); + thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1100; } if (PvNode) From 43d8ccf85641addd49a5c7e295919fa16c88b72c Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:02:16 +0100 Subject: [PATCH 495/834] change the bonusScale depth component Passed STC: https://tests.stockfishchess.org/tests/view/67d35f66517865b4a2dfc801 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 110816 W: 28875 L: 28449 D: 53492 Ptnml(0-2): 329, 13064, 28231, 13420, 364 Passed LTC: https://tests.stockfishchess.org/tests/view/67d4bdf0517865b4a2dfd131 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 121824 W: 31047 L: 30559 D: 60218 Ptnml(0-2): 52, 13056, 34214, 13532, 58 closes https://github.com/official-stockfish/Stockfish/pull/5932 Bench: 2128807 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4b6ad52e4..a91fb3c1b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1445,7 +1445,8 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + int bonusScale = (std::clamp(160 * (depth - 4) / 2, 0, 200) + 34 * !allNode + + 164 * ((ss - 1)->moveCount > 8) + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) From 0dabf4f3fab2356111cbcf058be167bd43ac2479 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 16 Mar 2025 15:37:42 +0300 Subject: [PATCH 496/834] Removing the conditional bonus calculation The new value is just a guessed value. Passed STC: https://tests.stockfishchess.org/tests/view/67d5ee09517865b4a2dfd2df LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 52128 W: 13516 L: 13312 D: 25300 Ptnml(0-2): 157, 6044, 13451, 6262, 150 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 42384 W: 10855 L: 10657 D: 20872 Ptnml(0-2): 19, 4554, 11852, 4744, 23 https://tests.stockfishchess.org/tests/view/67d5f9d3517865b4a2dfd2ef closes https://github.com/official-stockfish/Stockfish/pull/5933 Bench: 2030154 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a91fb3c1b..2da19f32e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1279,7 +1279,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = (value >= beta) * 1800; + int bonus = 1600; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } else if (value > alpha && value < bestValue + 9) From 6ceaca4c7b2cc1fa87617b1b9e83d38d8e880924 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 20 Mar 2025 12:40:27 -0700 Subject: [PATCH 497/834] Change layout of CorrectionHistory https://tests.stockfishchess.org/tests/view/67da5b158c7f315cc372a9d2 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 150368 W: 38874 L: 38401 D: 73093 Ptnml(0-2): 424, 16821, 40262, 17212, 465 Make CorrectionHistory\ handle both black and white internally. A follow up to https://github.com/official-stockfish/Stockfish/pull/5816 closes https://github.com/official-stockfish/Stockfish/pull/5934 No functional change --- src/history.h | 6 ++++++ src/search.cpp | 11 +++++------ src/search.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/history.h b/src/history.h index fd9b98b98..ec245230a 100644 --- a/src/history.h +++ b/src/history.h @@ -155,6 +155,12 @@ struct CorrHistTypedef { using type = MultiArray::type, PIECE_NB, SQUARE_NB>; }; +template<> +struct CorrHistTypedef { + using type = + Stats; +}; + } template diff --git a/src/search.cpp b/src/search.cpp index 2da19f32e..0c543c308 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -87,8 +87,8 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss const auto m = (ss - 1)->currentMove; const auto pcv = w.pawnCorrectionHistory[pawn_structure_index(pos)][us]; const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; - const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; - const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; + const auto wnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us]; + const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; @@ -141,9 +141,9 @@ void update_correction_history(const Position& pos, workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] << bonus * 111 / 128; workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; - workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; - workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) @@ -581,8 +581,7 @@ void Search::Worker::clear() { pawnHistory.fill(-1262); pawnCorrectionHistory.fill(6); minorPieceCorrectionHistory.fill(0); - nonPawnCorrectionHistory[WHITE].fill(0); - nonPawnCorrectionHistory[BLACK].fill(0); + nonPawnCorrectionHistory.fill(0); for (auto& to : continuationCorrectionHistory) for (auto& h : to) diff --git a/src/search.h b/src/search.h index cd3b6ad98..071773f81 100644 --- a/src/search.h +++ b/src/search.h @@ -289,7 +289,7 @@ class Worker { CorrectionHistory pawnCorrectionHistory; CorrectionHistory minorPieceCorrectionHistory; - CorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; + CorrectionHistory nonPawnCorrectionHistory; CorrectionHistory continuationCorrectionHistory; private: From 12d023ed0635e7dd4ada28ad764ea975de55103b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Mar 2025 09:28:58 +0100 Subject: [PATCH 498/834] Update CPU contributors closes https://github.com/official-stockfish/Stockfish/pull/5937 No functional change --- Top CPU Contributors.txt | 217 +++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 98 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 3d8c52361..4e598ecfc 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,118 +1,126 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2024-08-31. +Contributors to Fishtest with >10,000 CPU hours, as of 2025-03-22. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 40428649 3164740143 -technologov 23581394 1076895482 -vdv 19425375 718302718 -linrock 10034115 643194527 +noobpwnftw 41712226 3294628533 +vdv 28993864 954145232 +technologov 24984442 1115931964 +linrock 11463033 741692823 mlang 3026000 200065824 -okrout 2572676 237511408 -pemo 1836785 62226157 +okrout 2726068 248285678 +olafm 2420096 161297116 +pemo 1838361 62294199 +TueRens 1804847 80170868 dew 1689162 100033738 -TueRens 1648780 77891164 -sebastronomy 1468328 60859092 -grandphish2 1466110 91776075 +sebastronomy 1655637 67294942 +grandphish2 1474752 92156319 JojoM 1130625 73666098 -olafm 1067009 74807270 +rpngn 973590 59996557 +oz 921203 60370346 tvijlbrief 796125 51897690 -oz 781847 53910686 -rpngn 768460 49812975 -gvreuls 751085 52177668 +gvreuls 792215 55184194 mibere 703840 46867607 -leszek 566598 42024615 -cw 519601 34988161 +leszek 599745 44681421 +cw 519602 34988289 fastgm 503862 30260818 -CSU_Dynasty 468784 31385034 -maximmasiutin 439192 27893522 -ctoks 435148 28541909 +CSU_Dynasty 474794 31654170 +maximmasiutin 441753 28129452 +robal 437950 28869118 +ctoks 435150 28542141 crunchy 427414 27371625 bcross 415724 29061187 -robal 371112 24642270 -mgrabiak 367963 26464704 +mgrabiak 380202 27586936 velislav 342588 22140902 ncfish1 329039 20624527 Fisherman 327231 21829379 +Sylvain27 317021 11494912 +marrco 310446 19587107 Dantist 296386 18031762 -tolkki963 262050 22049676 -Sylvain27 255595 8864404 +Fifis 289595 14969251 +tolkki963 286043 23596996 +Calis007 272677 17281620 +cody 258835 13301710 nordlandia 249322 16420192 -Fifis 237657 13065577 -marrco 234581 17714473 -Calis007 217537 14450582 +javran 212141 16507618 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 +Wencey 198537 9606420 Thanar 179852 12365359 -javran 169679 13481966 -armo9494 162863 10937118 +sschnee 170521 10891112 +armo9494 168141 11177514 +DesolatedDodo 160605 10392474 spams 157128 10319326 -DesolatedDodo 156683 10211206 -Wencey 152308 8375444 +maposora 155839 13963260 sqrt2 147963 9724586 -vdbergh 140311 9225125 +vdbergh 140514 9242985 jcAEie 140086 10603658 CoffeeOne 137100 5024116 malala 136182 8002293 +Goatminola 134893 11640524 xoto 133759 9159372 -Dubslow 129614 8519312 +markkulix 132104 11000548 +naclosagc 131472 4660806 +Dubslow 129685 8527664 davar 129023 8376525 DMBK 122960 8980062 dsmith 122059 7570238 -CypressChess 120784 8672620 -sschnee 120526 7547722 -maposora 119734 10749710 +Wolfgang 120919 8619168 +CypressChess 120902 8683904 amicic 119661 7938029 -Wolfgang 115713 8159062 +cuistot 116864 7828864 +sterni1971 113754 6054022 Data 113305 8220352 BrunoBanani 112960 7436849 -markkulix 112897 9133168 -cuistot 109802 7121030 +megaman7de 109139 7360928 skiminki 107583 7218170 -sterni1971 104431 5938282 +zeryl 104523 6618969 MaZePallas 102823 6633619 sunu 100167 7040199 -zeryl 99331 6221261 -thirdlife 99156 2245320 +thirdlife 99178 2246544 ElbertoOne 99028 7023771 -megaman7de 98456 6675076 -Goatminola 96765 8257832 +TataneSan 97257 4239502 +romangol 95662 7784954 bigpen0r 94825 6529241 brabos 92118 6186135 Maxim 90818 3283364 psk 89957 5984901 +szupaw 89775 7800606 +jromang 87260 5988073 racerschmacer 85805 6122790 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 -szupaw 82495 7151686 +Spprtr 82103 5663635 BRAVONE 81239 5054681 +MarcusTullius 78930 5189659 +Mineta 78731 4947996 +Torom 77978 2651656 nssy 76497 5259388 -cody 76126 4492126 -jromang 76106 5236025 -MarcusTullius 76103 5061991 -woutboat 76072 6022922 -Spprtr 75977 5252287 +woutboat 76379 6031688 teddybaer 75125 5407666 Pking_cda 73776 5293873 +Viren6 73664 1356502 yurikvelo 73611 5046822 -Mineta 71130 4711422 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 manap 66273 4121774 tinker 64333 4268790 qurashee 61208 3429862 -AGI 58195 4329580 +DanielMiao1 60181 1317252 +AGI 58316 4336328 +jojo2357 57435 4944212 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 ttruscott 56010 3680085 rkl 55132 4164467 -jmdana 54697 4012593 +jmdana 54988 4041917 notchris 53936 4184018 renouve 53811 3501516 +CounterFlow 52536 3203740 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 @@ -122,33 +130,36 @@ GPUex 48686 3684998 OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 -oryx 45639 3546530 +oryx 46141 3583236 +jibarbosa 45890 4541218 +DeepnessFulled 45734 3944282 +abdicj 45577 2631772 VoyagerOne 45476 3452465 +mecevdimitar 44240 2584396 speedycpu 43842 3003273 jbwiebe 43305 2805433 +gopeto 43046 2821514 +YvesKn 42628 2177630 Antihistamine 41788 2761312 mhunt 41735 2691355 -jibarbosa 41640 4145702 +somethingintheshadows 41502 3330418 homyur 39893 2850481 gri 39871 2515779 -DeepnessFulled 39020 3323102 +vidar808 39774 1656372 Garf 37741 2999686 SC 37299 2731694 -Gaster319 37118 3279678 -naclosagc 36562 1279618 +Gaster319 37229 3289674 csnodgrass 36207 2688994 +ZacHFX 35528 2486328 +icewulf 34782 2415146 strelock 34716 2074055 -gopeto 33717 2245606 EthanOConnor 33370 2090311 slakovv 32915 2021889 -jojo2357 32890 2826662 -shawnxu 32019 2802552 +shawnxu 32144 2814668 Gelma 31771 1551204 -vidar808 31560 1351810 +srowen 31181 1732120 kdave 31157 2198362 manapbk 30987 1810399 -ZacHFX 30966 2272416 -TataneSan 30713 1513402 votoanthuan 30691 2460856 Prcuvu 30377 2170122 anst 30301 2190091 @@ -157,145 +168,155 @@ spcc 29925 1901692 hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 +WoodMan777 29300 2579864 belzedar94 28846 1811530 -mecevdimitar 27610 1721382 chriswk 26902 1868317 xwziegtm 26897 2124586 +Jopo12321 26818 1816482 achambord 26582 1767323 -somethingintheshadows 26496 2186404 Patrick_G 26276 1801617 yorkman 26193 1992080 -srowen 25743 1490684 -Ulysses 25413 1702830 -Jopo12321 25227 1652482 +Ulysses 25517 1711634 SFTUser 25182 1675689 nabildanial 25068 1531665 Sharaf_DG 24765 1786697 rodneyc 24376 1416402 jsys14 24297 1721230 +AndreasKrug 24235 1934711 agg177 23890 1395014 -AndreasKrug 23754 1890115 Ente 23752 1678188 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 -WoodMan777 23253 2023048 Nullvalue 23155 2022752 +fishtester 23115 1581502 +wizardassassin 23073 1789536 +Skiff84 22984 1053680 cisco2015 22920 1763301 +ols 22914 1322047 +Hjax 22561 1566151 Zirie 22542 1472937 team-oh 22272 1636708 +mkstockfishtester 22253 2029566 Roady 22220 1465606 MazeOfGalious 21978 1629593 sg4032 21950 1643373 -tsim67 21747 1330880 +tsim67 21939 1343944 ianh2105 21725 1632562 -Skiff84 21711 1014212 +Serpensin 21704 1809188 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 +IslandLambda 21468 1239756 user213718 21454 1404128 -Serpensin 21452 1790510 sphinx 21211 1384728 qoo_charly_cai 21136 1514927 -IslandLambda 21062 1220838 jjoshua2 21001 1423089 Zake9298 20938 1565848 horst.prack 20878 1465656 -fishtester 20729 1348888 0xB00B1ES 20590 1208666 -ols 20477 1195945 Dinde 20459 1292774 +t3hf1sht3ster 20456 670646 j3corre 20405 941444 +0x539 20332 1039516 Adrian.Schmidt123 20316 1281436 +malfoy 20313 1350694 +purpletree 20019 1461026 wei 19973 1745989 teenychess 19819 1762006 rstoesser 19569 1293588 eudhan 19274 1283717 +nalanzeyu 19211 396674 vulcan 18871 1729392 -wizardassassin 18795 1376884 Karpovbot 18766 1053178 jundery 18445 1115855 -mkstockfishtester 18350 1690676 +Farseer 18281 1074642 +sebv15 18267 1262588 +whelanh 17887 347974 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17414 981289 iisiraider 17275 1049015 +Karby 17177 1030688 DragonLord 17014 1162790 -Karby 17008 1013160 -pirt 16965 1271519 +pirt 16991 1274215 redstone59 16842 1461780 Alb11747 16787 1213990 Naven94 16414 951718 -scuzzi 16115 994341 +scuzzi 16155 995347 IgorLeMasson 16064 1147232 ako027ako 15671 1173203 +xuhdev 15516 1528278 infinigon 15285 965966 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 OssumOpossum 14857 1007129 LunaticBFF57 14525 1190310 enedene 14476 905279 -Hjax 14394 1005013 +YELNAMRON 14475 1141330 +RickGroszkiewicz 14272 1385984 +joendter 14269 982014 bpfliegel 14233 882523 -YELNAMRON 14230 1128094 mpx86 14019 759568 jpulman 13982 870599 getraideBFF 13871 1172846 +crocogoat 13817 1119086 Nesa92 13806 1116101 -crocogoat 13803 1117422 joster 13710 946160 mbeier 13650 1044928 Pablohn26 13552 1088532 wxt9861 13550 1312306 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 +Jackfish 13177 894206 +MooTheCow 13091 892304 Machariel 13010 863104 -nalanzeyu 12996 232590 mabichito 12903 749391 -Jackfish 12895 868928 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 -whelanh 12682 266404 +szczur90 12684 977536 +Kyrega 12661 456438 mschmidt 12644 863193 korposzczur 12606 838168 fatmurphy 12547 853210 Oakwen 12532 855759 -icewulf 12447 854878 SapphireBrand 12416 969604 deflectooor 12386 579392 modolief 12386 896470 -Farseer 12249 694108 +ckaz 12273 754644 Hongildong 12201 648712 pgontarz 12151 848794 dbernier 12103 860824 -szczur90 12035 942376 -FormazChar 12019 910409 +FormazChar 12051 913497 +shreven 12044 884734 rensonthemove 11999 971993 stocky 11954 699440 -MooTheCow 11923 779432 3cho 11842 1036786 -ckaz 11792 732276 +ImperiumAeternum 11482 979142 infinity 11470 727027 aga 11412 695127 +Def9Infinity 11408 700682 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 -Def9Infinity 11345 696552 d64 11263 789184 ali-al-zhrani 11245 779246 -ImperiumAeternum 11155 952000 +vaskoul 11144 953906 snicolet 11106 869170 dapper 11032 771402 Ethnikoi 10993 945906 Snuuka 10938 435504 Karmatron 10871 678306 +gerbil 10871 1005842 +OliverClarke 10696 942654 basepi 10637 744851 +michaelrpg 10624 748179 Cubox 10621 826448 -gerbil 10519 971688 -michaelrpg 10509 739239 +dragon123118 10421 936506 OIVAS7572 10420 995586 +GBx3TV 10388 339952 Garruk 10365 706465 dzjp 10343 732529 -RickGroszkiewicz 10263 990798 +borinot 10026 902130 From 6028264cb991b99b3a51cf2fd01b6f1f6ce24cc3 Mon Sep 17 00:00:00 2001 From: Michel Van den Bergh Date: Sat, 22 Mar 2025 09:58:47 +0100 Subject: [PATCH 499/834] Delay check for curl/wget until really needed closes https://github.com/official-stockfish/Stockfish/pull/5938 No functional change --- scripts/net.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/net.sh b/scripts/net.sh index 0bc57a19e..1aa1fbfb1 100755 --- a/scripts/net.sh +++ b/scripts/net.sh @@ -3,11 +3,6 @@ wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \ (command -v curl > /dev/null 2>&1 && echo "curl -skL")) -if [ -z "$wget_or_curl" ]; then - >&2 printf "%s\n" "Neither wget or curl is installed." \ - "Install one of these tools to download NNUE files automatically." - exit 1 -fi sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \ (command -v sha256sum > /dev/null 2>&1 && echo "sha256sum")) @@ -47,6 +42,12 @@ fetch_network() { fi fi + if [ -z "$wget_or_curl" ]; then + >&2 printf "%s\n" "Neither wget or curl is installed." \ + "Install one of these tools to download NNUE files automatically." + exit 1 + fi + for url in \ "https://tests.stockfishchess.org/api/nn/$_filename" \ "https://github.com/official-stockfish/networks/raw/master/$_filename"; do From 4a869f41c6113f1ccdd0f11551858fdc849a245a Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 22 Mar 2025 11:09:33 +0100 Subject: [PATCH 500/834] Update the WDL model This PR updates the internal WDL model, using data from 2.6M games played by the revisions since f3bfce353168b03e4fedce515de1898c691f81ec. Note that the normalizing constant increases only moderately from 372 to 377. ``` > ./updateWDL.sh --firstrev f3bfce353168b03e4fedce515de1898c691f81ec Running: ./updateWDL.sh --firstrev f3bfce353168b03e4fedce515de1898c691f81ec --lasttrev HEAD --materialMin 17 --EloDiffMax 5 started at: Sat 22 Mar 11:02:14 CET 2025 Look recursively in directory pgns for games with max nElo difference 5 using books matching "UHO_Lichess_4852_v..epd" for SF revisions between f3bfce353168b03e4fedce515de1898c691f81ec (from 2025-02-28 09:50:59 +0100) and HEAD (from 2025-03-21 11:22:59 +0100). Based on 138253430 positions from 2579360 games, NormalizeToPawnValue should change from 372 to 377. ended at: Sat 22 Mar 11:04:00 CET 2025 ``` ``` > cat scoreWDL.log Converting evals with NormalizeData = {'momType': 'material', 'momMin': 17, 'momMax': 78, 'momTarget': 58, 'as': [-37.45051876, 121.19101539, -132.78783573, 420.70576692]}. Reading eval stats from updateWDL.json. Retained (W,D,L) = (33794348, 69943262, 34515820) positions. Saved distribution plot to updateWDLdistro.png. Fit WDL model based on material. Initial objective function: 0.3648260131692729 Final objective function: 0.36482338611818094 Optimization terminated successfully. const int NormalizeToPawnValue = 377; Corresponding spread = 71; Corresponding normalized spread = 0.1879431202530567; Draw rate at 0.0 eval at material 58 = 0.9902694872976331; Parameters in internal value units: p_a = ((-13.500 * x / 58 + 40.928) * x / 58 + -36.828) * x / 58 + 386.830 p_b = ((96.534 * x / 58 + -165.791) * x / 58 + 90.897) * x / 58 + 49.296 constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; Preparing plots. Saved graphics to updateWDL.png. Total elapsed time = 46.92s. ``` Only affects displayed `cp` and `wdl` values. closes https://github.com/official-stockfish/Stockfish/pull/5939 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 4b8e8b7f5..500e88184 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -513,8 +513,8 @@ WinRateParams win_rate_params(const Position& pos) { double m = std::clamp(material, 17, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-37.45051876, 121.19101539, -132.78783573, 420.70576692}; - constexpr double bs[] = {90.26261072, -137.26549898, 71.10130540, 51.35259597}; + constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; + constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From aafc732bcbfbf847e2ce15bb5371902c1801de36 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 29 Mar 2025 11:42:26 +0100 Subject: [PATCH 501/834] Silence "may be used uninitialized" GCC warning Helps gcc silence the warning about ``` warning: 'added' may be used uninitialized [-Wmaybe-uninitialized] const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; ``` closes https://github.com/official-stockfish/Stockfish/pull/5950 No functional change --- src/nnue/nnue_accumulator.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 0a3d95ad4..d693cc031 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -25,13 +25,25 @@ #include "../bitboard.h" #include "../position.h" #include "../types.h" -#include "nnue_architecture.h" #include "network.h" +#include "nnue_architecture.h" #include "nnue_common.h" #include "nnue_feature_transformer.h" namespace Stockfish::Eval::NNUE { +#if defined(__GNUC__) && !defined(__clang__) + #define sf_assume(cond) \ + do \ + { \ + if (!(cond)) \ + __builtin_unreachable(); \ + } while (0) +#else + // do nothing for other compilers + #define sf_assume(cond) +#endif + namespace { template(&(computed.*accPtr).accumulation[Perspective][0]); From 03e27488f3d21d8ff4dbf3065603afa21dbd0ef3 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 29 Mar 2025 12:44:36 +0100 Subject: [PATCH 502/834] Stockfish 17.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 17.1 Bench: 2030154 --- Stockfish 17.1 Today, we have the pleasure to announce Stockfish 17.1. As always, you can **freely** download it at [stockfishchess.org/download][1] and use it in the [GUI of your choice][2]. Join our [Discord server][3] to get in touch with the community of developers and users of the project! Quality of chess play In our testing against its predecessor, Stockfish 17.1 shows a consistent improvement in performance, with an [Elo gain of up to 20 points][4] and winning close to 2 times more game pairs than it loses. Update highlights New speedtest command The new `speedtest` command benchmarks your computer's performance (measured in nodes per second) using a realistic and stable test. To run it, you'll need [command line access][5]—give it a try and share your results with the community! Improved hardware support Stockfish is [no longer limited to 1024 threads][6] and will allow users to specify whatever their system is capable of. Additionally, hardware such as ppc64 and Loongson is now better supported at build time. Bug fixes for tablebase support Our previous release introduced improved engine lines (principal variations) ending in mate as soon as a mate score is announced. A side effect of this improvement was a [rare corner case][7] involving cursed tablebase wins, only relevant in correspondence chess when the 50-move rule does not apply, which has now been fixed. Relatedly, [time management][8] has also been improved to avoid potential time losses. Shoutouts Download page redesign We've redesigned the [download page][1] to address unclear wording and simplify a previously cluttered experience. The page now features a modernized layout, streamlined navigation, and clearer guidance to help you select the right binary for your system. Fishtest framework Our testing framework has been improved in various ways, both on the worker side, including the adoption of [Fastchess][9] as a new game manager, and on the server side, such as streamlined configuration. The reliable availability of testing resources is key for the progress of the engine. Thank you The Stockfish project builds on a thriving community of enthusiasts (thanks everybody!) who contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We would like to express our gratitude for the [12k stars][10] that light up our GitHub project! Thank you for your support and encouragement – your recognition means a lot to us. We invite our chess fans to [join the Fishtest testing framework][11] to contribute compute resources needed for development. Programmers can contribute to the project either directly to [Stockfish][12] (C++), to [Fishtest][13] (HTML, CSS, JavaScript, and Python), to our trainer [nnue-pytorch][14] (C++ and Python), or to our [website][15] (HTML, CSS/SCSS, and JavaScript). The Stockfish team [1]: https://stockfishchess.org/download [2]: https://official-stockfish.github.io/docs/stockfish-wiki/Download-and-usage.html#download-a-chess-gui [3]: https://discord.gg/GWDRS3kU6R [4]: https://tests.stockfishchess.org/tests/view/67e7d2fd6682f97da2178fbd [5]: https://official-stockfish.github.io/docs/stockfish-wiki/UCI-&-Commands.html#speedtest [6]: https://github.com/official-stockfish/Stockfish/commit/652a8874b523360a3b19c5003c8ba9894ac54d0f [7]: https://github.com/official-stockfish/Stockfish/commit/6c7c5c7e471c16f14518229428e51a3e00c0f1dd [8]: https://github.com/official-stockfish/Stockfish/commit/0f9ae0d11cd034288a49ef3892c580dfed025091 [9]: https://github.com/Disservin/fastchess [10]: https://github.com/official-stockfish/Stockfish/stargazers [11]: https://official-stockfish.github.io/docs/fishtest-wiki/Running-the-Worker.html [12]: https://github.com/official-stockfish/Stockfish [13]: https://github.com/official-stockfish/fishtest [14]: https://github.com/official-stockfish/nnue-pytorch [15]: https://github.com/official-stockfish/stockfish-web --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index f85356c59..c5ac45f5c 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "dev"; +constexpr std::string_view version = "17.1"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 0475c8653f6d7d6940918120d3e994845906b6d1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 30 Mar 2025 14:47:31 +0200 Subject: [PATCH 503/834] Restore development closes https://github.com/official-stockfish/Stockfish/pull/5956 No functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index c5ac45f5c..f85356c59 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "17.1"; +constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From c2ff7a95c3ffee1c964735a0a4bd0e34cf76cab8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 21 Mar 2025 11:24:11 -0700 Subject: [PATCH 504/834] Cleanup fused updates Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 70656 W: 18257 L: 18077 D: 34322 Ptnml(0-2): 217, 7912, 18879, 8114, 206 https://tests.stockfishchess.org/tests/view/67e23ae78888403457d876d4 closes https://github.com/official-stockfish/Stockfish/pull/5941 No functional change --- src/nnue/nnue_accumulator.cpp | 280 +++++++++++----------------- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 54 ++++++ src/types.h | 9 + 4 files changed, 169 insertions(+), 176 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index d693cc031..efa8df905 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -21,8 +21,10 @@ #include #include #include +#include #include "../bitboard.h" +#include "../misc.h" #include "../position.h" #include "../types.h" #include "network.h" @@ -185,7 +187,7 @@ void AccumulatorStack::backward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = m_current_idx - 2; next >= end; next--) - update_accumulator_incremental( + update_accumulator_incremental( featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); assert((m_accumulators[end].*accPtr).computed[Perspective]); @@ -208,6 +210,67 @@ AccumulatorStack::evaluate, bool> = true> +void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const... rows) { + constexpr IndexType size = Width * sizeof(ElementType) / sizeof(typename VectorWrapper::type); + + auto* vecIn = reinterpret_cast(in); + auto* vecOut = reinterpret_cast(out); + + for (IndexType i = 0; i < size; ++i) + vecOut[i] = fused( + vecIn[i], reinterpret_cast(rows)[i]...); +} + +template AccumulatorState::*accPtr> +struct AccumulatorUpdateContext { + const FeatureTransformer& featureTransformer; + const AccumulatorState& from; + AccumulatorState& to; + + AccumulatorUpdateContext(const FeatureTransformer& ft, + const AccumulatorState& accF, + AccumulatorState& accT) noexcept : + featureTransformer{ft}, + from{accF}, + to{accT} {} + + template, bool> = true> + void apply(const Ts... indices) { + auto to_weight_vector = [&](const IndexType index) { + return &featureTransformer.weights[index * Dimensions]; + }; + + auto to_psqt_weight_vector = [&](const IndexType index) { + return &featureTransformer.psqtWeights[index * PSQTBuckets]; + }; + + fused_row_reduce((from.*accPtr).accumulation[Perspective], + (to.*accPtr).accumulation[Perspective], + to_weight_vector(indices)...); + + fused_row_reduce( + (from.*accPtr).psqtAccumulation[Perspective], (to.*accPtr).psqtAccumulation[Perspective], + to_psqt_weight_vector(indices)...); + } +}; + +template AccumulatorState::*accPtr> +auto make_accumulator_update_context( + const FeatureTransformer& featureTransformer, + const AccumulatorState& accumulatorFrom, + AccumulatorState& accumulatorTo) noexcept { + return AccumulatorUpdateContext{ + featureTransformer, accumulatorFrom, accumulatorTo}; +} + template(ksq, computed.dirtyPiece, added, removed); - if (removed.size() == 0 && added.size() == 0) + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1 || added.size() == 2); + sf_assume(removed.size() == 1 || removed.size() == 2); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + if ((Forward && removed.size() == 1) || (Backward && added.size() == 1)) { - std::memcpy((target_state.*accPtr).accumulation[Perspective], - (computed.*accPtr).accumulation[Perspective], - TransformedFeatureDimensions * sizeof(BiasType)); - std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], - (computed.*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); + assert(added.size() == 1 && removed.size() == 1); + updateContext.template apply(added[0], removed[0]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + updateContext.template apply(added[0], removed[0], removed[1]); + } + else if (Backward && removed.size() == 1) + { + assert(added.size() == 2); + updateContext.template apply(added[0], added[1], removed[0]); } else { - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); - - if (Forward) - assert(added.size() <= removed.size()); - else - assert(removed.size() <= added.size()); - - // Workaround compiler warning for uninitialized variables, replicated on - // profile builds on windows with gcc 14.2.0. - // TODO remove once unneeded - sf_assume(added.size() == 1 || added.size() == 2); - sf_assume(removed.size() == 1 || removed.size() == 2); - -#ifdef VECTOR - auto* accIn = - reinterpret_cast(&(computed.*accPtr).accumulation[Perspective][0]); - auto* accOut = - reinterpret_cast(&(target_state.*accPtr).accumulation[Perspective][0]); - - const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; - auto* columnA0 = reinterpret_cast(&featureTransformer.weights[offsetA0]); - const IndexType offsetR0 = TransformedFeatureDimensions * removed[0]; - auto* columnR0 = reinterpret_cast(&featureTransformer.weights[offsetR0]); - - if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) - { - assert(added.size() == 1 && removed.size() == 1); - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); - } - else if (Forward && added.size() == 1) - { - assert(removed.size() == 2); - const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), - vec_add_16(columnR0[i], columnR1[i])); - } - else if (Backwards && removed.size() == 1) - { - assert(added.size() == 2); - const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), - vec_sub_16(columnA1[i], columnR0[i])); - } - else - { - assert(added.size() == 2 && removed.size() == 2); - const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); - const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), - vec_add_16(columnR0[i], columnR1[i]))); - } - - auto* accPsqtIn = - reinterpret_cast(&(computed.*accPtr).psqtAccumulation[Perspective][0]); - auto* accPsqtOut = - reinterpret_cast(&(target_state.*accPtr).psqtAccumulation[Perspective][0]); - - const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; - auto* columnPsqtA0 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; - auto* columnPsqtR0 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR0]); - - if ((Forward && removed.size() == 1) - || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 - { - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = - vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); - } - else if (Forward && added.size() == 1) - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); - } - else if (Backwards && removed.size() == 1) - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); - } - else - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_add_psqt_32( - accPsqtIn[i], vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); - } -#else - std::memcpy((target_state.*accPtr).accumulation[Perspective], - (computed.*accPtr).accumulation[Perspective], - TransformedFeatureDimensions * sizeof(BiasType)); - std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], - (computed.*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = TransformedFeatureDimensions * index; - for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) - (target_state.*accPtr).accumulation[Perspective][i] -= - featureTransformer.weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (target_state.*accPtr).psqtAccumulation[Perspective][i] -= - featureTransformer.psqtWeights[index * PSQTBuckets + i]; - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = TransformedFeatureDimensions * index; - for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) - (target_state.*accPtr).accumulation[Perspective][i] += - featureTransformer.weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (target_state.*accPtr).psqtAccumulation[Perspective][i] += - featureTransformer.psqtWeights[index * PSQTBuckets + i]; - } -#endif + assert(added.size() == 2 && removed.size() == 2); + updateContext.template apply(added[0], added[1], removed[0], + removed[1]); } (target_state.*accPtr).computed[Perspective] = true; @@ -477,7 +407,7 @@ void update_accumulator_refresh_cache( auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); + acc[k] = fused(acc[k], columnA[k], columnR[k]); } if (combineLast3) { @@ -496,8 +426,8 @@ void update_accumulator_refresh_cache( reinterpret_cast(&featureTransformer.weights[offsetR2]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), - vec_add_16(columnR[k], columnR2[k])); + acc[k] = fused(acc[k], columnA[k], columnR[k], + columnR2[k]); } else { @@ -507,8 +437,8 @@ void update_accumulator_refresh_cache( reinterpret_cast(&featureTransformer.weights[offsetA2]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), - vec_add_16(columnA[k], columnA2[k])); + acc[k] = fused(acc[k], columnA[k], columnA2[k], + columnR[k]); } } else diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index b217c3583..e6e3017d2 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -281,7 +281,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size enum IncUpdateDirection { FORWARD, - BACKWARDS + BACKWARD }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 20e85be3c..9dee29c19 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -143,6 +143,60 @@ using psqt_vec_t = int32x4_t; #endif +struct Vec16Wrapper { +#ifdef VECTOR + using type = vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); } +#else + using type = BiasType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +struct Vec32Wrapper { +#ifdef VECTOR + using type = psqt_vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); } +#else + using type = PSQTWeightType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +enum UpdateOperation { + Add, + Sub +}; + +template = true> +typename VecWrapper::type fused(const typename VecWrapper::type& in) { + return in; +} + +template, bool> = true, + std::enable_if_t = true> +typename VecWrapper::type +fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) { + switch (update_op) + { + case Add : + return fused(VecWrapper::add(in, operand), operands...); + case Sub : + return fused(VecWrapper::sub(in, operand), operands...); + } +} + // Returns the inverse of a permutation template constexpr std::array diff --git a/src/types.h b/src/types.h index 6465dfd6b..d6af929e5 100644 --- a/src/types.h +++ b/src/types.h @@ -38,6 +38,7 @@ #include #include + #include #if defined(_MSC_VER) // Disable some silly and noisy warnings from MSVC compiler @@ -429,6 +430,14 @@ class Move { std::uint16_t data; }; +template +struct is_all_same { + static constexpr bool value = (std::is_same_v && ...); +}; + +template +constexpr auto is_all_same_v = is_all_same::value; + } // namespace Stockfish #endif // #ifndef TYPES_H_INCLUDED From ee35a51c40eff7fb237f217d87d9ca7c73874924 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 26 Mar 2025 16:19:43 -0500 Subject: [PATCH 505/834] Remove extra division closes https://github.com/official-stockfish/Stockfish/pull/5943 No functional change --- src/search.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0c543c308..8e8e93538 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1444,12 +1444,12 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (std::clamp(160 * (depth - 4) / 2, 0, 200) + 34 * !allNode - + 164 * ((ss - 1)->moveCount > 8) - + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) - + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) - + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) - + std::min(-(ss - 1)->statScore / 112, 303)); + int bonusScale = + (std::clamp(80 * depth - 320, 0, 200) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 112, 303)); bonusScale = std::max(bonusScale, 0); From dfef7e75209a6d14f16618dd6040c0898522d89d Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 28 Mar 2025 17:57:04 +0300 Subject: [PATCH 506/834] Remove redundant assignment closes https://github.com/official-stockfish/Stockfish/pull/5944 No functional change --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8e8e93538..99bf47825 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -298,14 +298,10 @@ void Search::Worker::iterative_deepening() { &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; (ss - i)->staticEval = VALUE_NONE; - (ss - i)->reduction = 0; } for (int i = 0; i <= MAX_PLY + 2; ++i) - { - (ss + i)->ply = i; - (ss + i)->reduction = 0; - } + (ss + i)->ply = i; ss->pv = pv; From d2cb927a04829e5bfa3e91ce3c1da327ed65d520 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 19 Mar 2025 17:06:11 -0700 Subject: [PATCH 507/834] Simplify TT cutoff conthist updates Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 86304 W: 22251 L: 22084 D: 41969 Ptnml(0-2): 250, 10214, 22123, 10249, 316 https://tests.stockfishchess.org/tests/view/67db60cd8c7f315cc372aae7 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 199158 W: 50655 L: 50617 D: 97886 Ptnml(0-2): 103, 21579, 56182, 21607, 108 https://tests.stockfishchess.org/tests/view/67dcdc5b8c7f315cc372ac12 closes https://github.com/official-stockfish/Stockfish/pull/5945 Bench: 2069191 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 99bf47825..ee4e89556 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -720,8 +720,7 @@ Value Search::Worker::search( // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(809 * (depth + 1) - 249, 3052)); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2200); } // Partial workaround for the graph history interaction problem From 1a395f1b565e4140a3f15bdd7b8add92fd11537a Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 28 Mar 2025 14:39:11 -0700 Subject: [PATCH 508/834] Remove pawn_attacks_bb() Generalize attacks_bb() to handle pawns and remove pawn_attacks_bb() https://tests.stockfishchess.org/tests/view/67e9496231d7cf8afdc44a2e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134688 W: 34660 L: 34553 D: 65475 Ptnml(0-2): 298, 14619, 37462, 14608, 357 closes https://github.com/official-stockfish/Stockfish/pull/5947 No functional change --- src/bitboard.cpp | 5 ++--- src/bitboard.h | 12 +++--------- src/movegen.cpp | 2 +- src/position.cpp | 18 +++++++++--------- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 8798c5701..9b1d674c0 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -32,7 +32,6 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; alignas(64) Magic Magics[SQUARE_NB][2]; @@ -86,8 +85,8 @@ void Bitboards::init() { for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { - PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + PseudoAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PseudoAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) PseudoAttacks[KING][s1] |= safe_destination(s1, step); diff --git a/src/bitboard.h b/src/bitboard.h index df15113bd..941299c0d 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -62,7 +62,6 @@ extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square @@ -155,11 +154,6 @@ constexpr Bitboard pawn_attacks_bb(Bitboard b) { : shift(b) | shift(b); } -inline Bitboard pawn_attacks_bb(Color c, Square s) { - - assert(is_ok(s)); - return PawnAttacks[c][s]; -} // Returns a bitboard representing an entire line (from board edge // to board edge) that intersects the two given squares. If the given squares @@ -216,10 +210,10 @@ inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } // Returns the pseudo attacks of the given piece type // assuming an empty board. template -inline Bitboard attacks_bb(Square s) { +inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) { - assert((Pt != PAWN) && (is_ok(s))); - return PseudoAttacks[Pt][s]; + assert((Pt != PAWN || c < COLOR_NB) && (is_ok(s))); + return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s]; } diff --git a/src/movegen.cpp b/src/movegen.cpp index 8653a828f..a73bd8501 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -134,7 +134,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta if (Type == EVASIONS && (target & (pos.ep_square() + Up))) return moveList; - b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); + b1 = pawnsNotOn7 & attacks_bb(pos.ep_square(), Them); assert(b1); diff --git a/src/position.cpp b/src/position.cpp index 52e1004e8..02fd6c7a9 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -270,7 +270,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { // a) side to move have a pawn threatening epSquare // b) there is an enemy pawn in front of epSquare // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + enpassant = attacks_bb(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN) && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); } @@ -321,7 +321,7 @@ void Position::set_check_info() const { Square ksq = square(~sideToMove); - st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[PAWN] = attacks_bb(ksq, ~sideToMove); st->checkSquares[KNIGHT] = attacks_bb(ksq); st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); @@ -487,8 +487,8 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) + | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING)); } @@ -498,7 +498,7 @@ bool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const { && (attacks_bb(s, occupied) & pieces(c, ROOK, QUEEN))) || ((attacks_bb(s) & pieces(c, BISHOP, QUEEN)) && (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN))) - || (((pawn_attacks_bb(~c, s) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) + || (((attacks_bb(s, ~c) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING))) & pieces(c)); } @@ -597,9 +597,9 @@ bool Position::pseudo_legal(const Move m) const { if ((Rank8BB | Rank1BB) & to) return false; - if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !((from + 2 * pawn_push(us) == to) // Not a double push + if (!(attacks_bb(from, us) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !((from + 2 * pawn_push(us) == to) // Not a double push && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) return false; } @@ -812,7 +812,7 @@ DirtyPiece Position::do_move(Move m, { // Set en passant square if the moved pawn can be captured if ((int(to) ^ int(from)) == 16 - && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + && (attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN))) { st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; From ed89817f626d4abad0b2e90d8ccd29f68377dff2 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 28 Mar 2025 18:09:38 -0700 Subject: [PATCH 509/834] Various cleanups Various simplifications, cleanups, consistancy improvements, and warning mitigations. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/67e7dd2d6682f97da2178fd8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 386848 W: 99593 L: 99751 D: 187504 Ptnml(0-2): 1024, 41822, 107973, 41498, 1107 closes https://github.com/official-stockfish/Stockfish/pull/5948 No functional change --- src/bitboard.h | 18 +++++++++--------- src/evaluate.cpp | 10 ++++------ src/evaluate.h | 2 +- src/movepick.cpp | 2 +- src/nnue/layers/affine_transform.h | 4 ---- src/position.cpp | 7 +++---- src/search.cpp | 17 ++++++++--------- src/types.h | 14 +++++++------- 8 files changed, 33 insertions(+), 41 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 941299c0d..f959bcb86 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -102,17 +102,17 @@ constexpr Bitboard square_bb(Square s) { // Overloads of bitwise operators between a Bitboard and a Square for testing // whether a given bit is set in a bitboard, and for setting and clearing bits. -inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } -inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } -inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } -inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } -inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } +constexpr Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } +constexpr Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } +constexpr Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } +constexpr Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } +constexpr Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } -inline Bitboard operator&(Square s, Bitboard b) { return b & s; } -inline Bitboard operator|(Square s, Bitboard b) { return b | s; } -inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } +constexpr Bitboard operator&(Square s, Bitboard b) { return b & s; } +constexpr Bitboard operator|(Square s, Bitboard b) { return b | s; } +constexpr Bitboard operator^(Square s, Bitboard b) { return b ^ s; } -inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } +constexpr Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } diff --git a/src/evaluate.cpp b/src/evaluate.cpp index ccb089d97..92b03af37 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -38,17 +38,15 @@ namespace Stockfish { // Returns a static, purely materialistic evaluation of the position from -// the point of view of the given color. It can be divided by PawnValue to get +// the point of view of the side to move. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. -int Eval::simple_eval(const Position& pos, Color c) { +int Eval::simple_eval(const Position& pos) { + Color c = pos.side_to_move(); return PawnValue * (pos.count(c) - pos.count(~c)) + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } -bool Eval::use_smallnet(const Position& pos) { - int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 962; -} +bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; } // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. diff --git a/src/evaluate.h b/src/evaluate.h index 07b914007..2a6c9afa9 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -44,7 +44,7 @@ class AccumulatorStack; std::string trace(Position& pos, const Eval::NNUE::Networks& networks); -int simple_eval(const Position& pos, Color c); +int simple_eval(const Position& pos); bool use_smallnet(const Position& pos); Value evaluate(const NNUE::Networks& networks, const Position& pos, diff --git a/src/movepick.cpp b/src/movepick.cpp index c762e7e45..6ee5fad7c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -176,7 +176,7 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 + m.value -= (pt == QUEEN && bool(to & threatenedByRook) ? 49000 : pt == ROOK && bool(to & threatenedByMinor) ? 24335 : 0); diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index dac727e23..3920efb17 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -245,19 +245,16 @@ class AffineTransform { #if defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero() _mm256_setzero_si256() - #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero() _mm_setzero_si128() - #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 #define vec_hadd Simd::m128_hadd #elif defined(USE_NEON_DOTPROD) using vec_t = int32x4_t; #define vec_setzero() vdupq_n_s32(0) - #define vec_set_32 vdupq_n_s32 #define vec_add_dpbusd_32(acc, a, b) \ Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ vreinterpretq_s8_s32(b)) @@ -282,7 +279,6 @@ class AffineTransform { output[0] = vec_hadd(sum0, biases[0]); #undef vec_setzero - #undef vec_set_32 #undef vec_add_dpbusd_32 #undef vec_hadd } diff --git a/src/position.cpp b/src/position.cpp index 02fd6c7a9..0e5748e6a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -54,8 +54,8 @@ namespace { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; +static constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; } // namespace @@ -733,8 +733,7 @@ DirtyPiece Position::do_move(Move m, st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; captured = NO_PIECE; } - - if (captured) + else if (captured) { Square capsq = to; diff --git a/src/search.cpp b/src/search.cpp index ee4e89556..10e5047f1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -492,7 +492,8 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - int nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); + uint64_t nodesEffort = + rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); double fallingEval = (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) @@ -929,10 +930,7 @@ Value Search::Worker::search( { assert(move.is_ok()); - if (move == excludedMove) - continue; - - if (!pos.legal(move)) + if (move == excludedMove || !pos.legal(move)) continue; assert(pos.capture_stage(move)); @@ -1572,12 +1570,13 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return ttData.value; // Step 4. Static evaluation of the position - Value unadjustedStaticEval = VALUE_NONE; - const auto correctionValue = correction_value(*thisThread, pos, ss); + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; else { + const auto correctionValue = correction_value(*thisThread, pos, ss); + if (ss->ttHit) { // Never assume anything about values stored in TT @@ -1930,8 +1929,8 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = (weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) + int push = int(weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) / 128; if (rootMoves[i].score + push >= maxScore) diff --git a/src/types.h b/src/types.h index d6af929e5..5f9cb421e 100644 --- a/src/types.h +++ b/src/types.h @@ -290,8 +290,8 @@ struct DirtyPiece { }; #define ENABLE_INCR_OPERATORS_ON(T) \ - inline T& operator++(T& d) { return d = T(int(d) + 1); } \ - inline T& operator--(T& d) { return d = T(int(d) - 1); } + constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \ + constexpr T& operator--(T& d) { return d = T(int(d) - 1); } ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) @@ -304,10 +304,10 @@ constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } // Additional operators to add a Direction to a Square -constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } -constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } -inline Square& operator+=(Square& s, Direction d) { return s = s + d; } -inline Square& operator-=(Square& s, Direction d) { return s = s - d; } +constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +constexpr Square& operator+=(Square& s, Direction d) { return s = s + d; } +constexpr Square& operator-=(Square& s, Direction d) { return s = s - d; } // Toggle color constexpr Color operator~(Color c) { return Color(c ^ BLACK); } @@ -335,7 +335,7 @@ constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } -inline Color color_of(Piece pc) { +constexpr Color color_of(Piece pc) { assert(pc != NO_PIECE); return Color(pc >> 3); } From 3d61f932cbdbcca8f4a5f20459e706cfa2415648 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 29 Mar 2025 15:34:48 -0400 Subject: [PATCH 510/834] Squash out post-lmr bonus variable Squash out bonus variable for post-lmr now that the bonus is constant. closes https://github.com/official-stockfish/Stockfish/pull/5953 No functional change --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 10e5047f1..7a1a36100 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1271,8 +1271,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = 1600; - update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); + update_continuation_histories(ss, movedPiece, move.to_sq(), 1600); } else if (value > alpha && value < bestValue + 9) newDepth--; From d942e13398aa5de55224c7d81bfad6b0f5b9e488 Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Mon, 31 Mar 2025 18:27:36 +0200 Subject: [PATCH 511/834] Less fail high cnt in the condition Passed STC: https://tests.stockfishchess.org/tests/view/67e027538888403457d87535 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 164000 W: 42535 L: 42034 D: 79431 Ptnml(0-2): 478, 19228, 42113, 19677, 504 Passed LTC: https://tests.stockfishchess.org/tests/view/67e3c21b8888403457d87808 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 139176 W: 35500 L: 34975 D: 68701 Ptnml(0-2): 54, 15038, 38898, 15525, 73 closes https://github.com/official-stockfish/Stockfish/pull/5960 Bench: 1921404 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index a345bb69f..092980fe5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -58,6 +58,7 @@ Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) Daniel Monroe (Ergodice) Dan Schmidt (dfannius) +DanSamek Dariusz Orzechowski (dorzechowski) David (dav1312) David Zar diff --git a/src/search.cpp b/src/search.cpp index 7a1a36100..5f5934a49 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1215,7 +1215,7 @@ moves_loop: // When in check, search starts here r += 1171 + (depth < 8) * 985; // Increase reduction if next ply has a lot of fail high - if ((ss + 1)->cutoffCnt > 3) + if ((ss + 1)->cutoffCnt > 2) r += 1042 + allNode * 864; // For first picked move (ttMove) reduce reduction From d7c04a942950f1fe3f655bf8b608e8ef21c07628 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 31 Mar 2025 21:15:56 -0700 Subject: [PATCH 512/834] 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 ec245230a..f2ef7661f 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 5f5934a49..291b99bd1 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 @@ moves_loop: // When in check, search starts here { 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 @@ moves_loop: // When in check, search starts here // 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 071773f81..6ef8322a8 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 513/834] 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 | 172 +++++++++++++--------------- src/nnue/nnue_accumulator.h | 73 +++++++----- src/nnue/nnue_feature_transformer.h | 7 +- 5 files changed, 135 insertions(+), 145 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index cba3abc63..e23294e4f 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 21df4b0a1..cd32c5312 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 efa8df905..37af7a0fa 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); + 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); +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 362ea83e3..d83a5a446 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 9dee29c19..d2abd40fa 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 514/834] 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 092980fe5..dadff1422 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 515/834] 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 291b99bd1..1c3622884 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 516/834] 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 37af7a0fa..8b2585b91 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 e6e3017d2..f21a8dec7 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 517/834] 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 5f9cb421e..d7f9dfb74 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 518/834] 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 1c3622884..7b8fbb144 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 519/834] 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 dadff1422..54c5f8a21 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 520/834] 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 d2abd40fa..b9b422a65 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 2b4926e091cb423d34447b9f995578b4da74906b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 31 Mar 2025 21:33:40 -0700 Subject: [PATCH 521/834] 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 f2ef7661f..259d6eefb 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 3f24f86c5..0fecbce65 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 5f8e67a544e0427696e2e7f950f221ef0d1c6ed4 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 2 Apr 2025 19:43:55 +0300 Subject: [PATCH 522/834] 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 8b2585b91..2153cd4a6 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]); + 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_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) + acc[k] = vec_add_16(acc[k], column[k]); } for (IndexType k = 0; k < Tiling::NumRegs; k++) From 698c069bba69912d576075bf3d63e32ac5032d9d Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Mon, 14 Apr 2025 15:00:39 +0200 Subject: [PATCH 523/834] 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 0fecbce65..fcbac3078 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 @@ moves_loop: // When in check, search starts here // 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 904a016396013cacdf36d9c7fe4c562a330b33b4 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 28 Mar 2025 12:38:59 -0400 Subject: [PATCH 524/834] 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 6ee5fad7c..11317f113 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 3d18ad719b2e7103e27936561f8276fbed9a354b Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 2 Apr 2025 11:57:06 -0700 Subject: [PATCH 525/834] 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 fcbac3078..92998b026 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 44efbaddea909e146c6c41afaf458da8c9e4b4e4 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 5 Apr 2025 14:23:33 +0300 Subject: [PATCH 526/834] 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 7b8fbb144..7efd8499f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1446,7 +1446,7 @@ moves_loop: // When in check, search starts here 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 527/834] 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 11317f113..cc6d47901 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 @@ top: 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 71078bdcf..dfafe69a5 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 7efd8499f..3f24f86c5 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 f9459e4c8e5b595db0fa76c3ca7b475bf03858a9 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 31 Mar 2025 08:15:10 +0200 Subject: [PATCH 528/834] 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 76b94785e..17badefde 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 529/834] 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 92b03af37..23f4b8c2b 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 2153cd4a6..2bf76f530 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]; -} +const AccumulatorState& AccumulatorStack::latest() const noexcept { return accumulators[size - 1]; } -AccumulatorState& AccumulatorStack::mut_latest() noexcept { - return m_accumulators[m_current_idx - 1]; -} +AccumulatorState& AccumulatorStack::mut_latest() noexcept { return accumulators[size - 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::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 d83a5a446..aa9e2a676 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 809d454b5..c99874076 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 92998b026..fe0e340b4 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 530/834] 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 44e0596ea..fa720a1c3 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 531/834] 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 0e5748e6a..85ade69a9 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 75f22c7df..724165b00 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 532/834] 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 fe0e340b4..9a106e673 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1298,6 +1298,8 @@ moves_loop: // When in check, search starts here 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 533/834] 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 9a106e673..071a9d18a 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 @@ moves_loop: // When in check, search starts here 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 534/834] 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 071a9d18a..2a5bf1b91 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1286,7 +1286,11 @@ moves_loop: // When in check, search starts here 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 535/834] 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 2a5bf1b91..54d220cd1 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 536/834] 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 cc6d47901..6c8eb09c7 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 537/834] 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 54d220cd1..b8a4b32a6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1099,7 +1099,7 @@ moves_loop: // When in check, search starts here 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 538/834] 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 b8a4b32a6..85e4d0077 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && 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 @@ moves_loop: // When in check, search starts here 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 539/834] 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 e23294e4f..957dc7bff 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)]; - - 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 d7f9dfb74..a76d00336 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 540/834] 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 54c5f8a21..f16758609 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 85e4d0077..d08418813 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 541/834] 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 4dc232878..e4a59fec5 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 542/834] 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 6c8eb09c7..cac4abe4a 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 543/834] 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 d08418813..79bb44c63 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 544/834] 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 79bb44c63..01f0c2c23 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 @@ moves_loop: // When in check, search starts here // 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 6ef8322a8..6ab98f7c4 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 545/834] 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 01f0c2c23..bafccd0f5 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = - (std::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 546/834] Introduce double incremental accumulator updates when we need to update an accumulator by two moves and the second move captures the piece moved in the first move, we can skip computing the middle accumulator and cancel a feature add with a feature remove to save work. Passed STC https://tests.stockfishchess.org/tests/view/67f70b1c31d7cf8afdc45f51 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 72800 W: 18878 L: 18529 D: 35393 Ptnml(0-2): 160, 7711, 20374, 7930, 225 closes https://github.com/official-stockfish/Stockfish/pull/5988 No functional change --- src/nnue/nnue_accumulator.cpp | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 2bf76f530..5c1128539 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -46,6 +46,13 @@ namespace Stockfish::Eval::NNUE { namespace { +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed); + template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, @@ -142,8 +149,27 @@ void AccumulatorStack::forward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = begin + 1; next < size; next++) + { + if (next + 1 < size) + { + auto& dp1 = accumulators[next].dirtyPiece; + auto& dp2 = accumulators[next + 1].dirtyPiece; + + if (dp2.dirty_num >= 2 && dp1.piece[0] == dp2.piece[1] && dp1.to[0] == dp2.from[1]) + { + const Square captureSq = dp1.to[0]; + dp1.to[0] = dp2.from[1] = SQ_NONE; + double_inc_update(featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); + dp1.to[0] = dp2.from[1] = captureSq; + + next++; + continue; + } + } update_accumulator_incremental( featureTransformer, ksq, accumulators[next], accumulators[next - 1]); + } assert((latest().acc()).computed[Perspective]); } @@ -240,6 +266,49 @@ auto make_accumulator_update_context(const FeatureTransformer& featu accumulatorTo}; } +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed) { + + assert(computed.acc().computed[Perspective]); + assert(!middle_state.acc().computed[Perspective]); + assert(!target_state.acc().computed[Perspective]); + + FeatureSet::IndexList removed, added; + FeatureSet::append_changed_indices(ksq, middle_state.dirtyPiece, removed, added); + // you can't capture a piece that was just involved in castling since the rook ends up + // in a square that the king passed + assert(added.size() < 2); + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, added); + + assert(added.size() == 1); + assert(removed.size() == 2 || removed.size() == 3); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1); + sf_assume(removed.size() == 2 || removed.size() == 3); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + if (removed.size() == 2) + { + updateContext.template apply(added[0], removed[0], removed[1]); + } + else + { + updateContext.template apply(added[0], removed[0], removed[1], + removed[2]); + } + + target_state.acc().computed[Perspective] = true; +} + template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, From f0de8dc0349bac56021a900910f14a00a729dbc6 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Mon, 21 Apr 2025 18:26:46 -0700 Subject: [PATCH 547/834] 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 cac4abe4a..71a5f9959 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 548/834] 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 bafccd0f5..bd2cb1cbc 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 549/834] 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 bd2cb1cbc..e9dfb7172 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 550/834] 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 e9dfb7172..fab0fd617 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 551/834] 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 71a5f9959..c8738c568 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 552/834] 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 fab0fd617..a6a189fca 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 553/834] 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 a6a189fca..35a2b5de2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1049,8 +1049,6 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); - r -= 32 * moveCount; - // Increase reduction for ttPv nodes (*Scaler) // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls @@ -1230,7 +1228,7 @@ moves_loop: // When in check, search starts here // 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 554/834] 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 35a2b5de2..b370f7ca9 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 555/834] 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 b370f7ca9..f673ba340 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 @@ moves_loop: // When in check, search starts here // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 979; + r += 968; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1069,15 +1069,15 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 43 + 2 * newDepth); + const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1290,12 +1290,12 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1156; + r += 1128; r -= ttMoveHistory / 8; @@ -1314,7 +1314,7 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here ttData.move, moveCount); if (!PvNode) { - int bonus = ss->isTTMove ? 800 : -870; + int bonus = ss->isTTMove ? 800 : -879; ttMoveHistory << bonus; } } @@ -1471,28 +1471,28 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = std::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 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 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 556/834] 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 c8738c568..0360c2bde 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 @@ top: case QSEARCH_TT : case PROBCUT_TT : ++stage; + cur = moves + 1; return ttMove; case CAPTURE_INIT : @@ -238,9 +239,12 @@ top: 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 @@ top: case QUIET_INIT : if (!skipQuiets) { - cur = endBadCaptures; endMoves = beginBadQuiets = endBadQuiets = generate(pos, cur); score(); @@ -317,30 +320,23 @@ top: 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) + // 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(capturesSearched[i])) != KING) + if (type_of(pos.moved_piece(*m)) != 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)) + 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 dfafe69a5..72a6f4e1c 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 f673ba340..05d522b19 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1011,8 +1011,10 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here && 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 557/834] 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 05d522b19..870cff54b 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 558/834] 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 | 18 +++++------- 4 files changed, 44 insertions(+), 48 deletions(-) diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index eb3c7e6a7..c91da2cc8 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 5c1128539..831284364 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 85ade69a9..9f023b5fe 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 a76d00336..6cd6ee744 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 { + Piece pc; // this is never allowed to be NO_PIECE + Square from, to; // to should be SQ_NONE for promotions - // 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]; + // 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 559/834] 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 870cff54b..12de7a679 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 @@ moves_loop: // When in check, search starts here 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 560/834] 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 12de7a679..b7c6d8e3b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1244,6 +1244,8 @@ moves_loop: // When in check, search starts here ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high + // (*Scaler) Usually doing more shallower searches + // doesn't scale well to longer TCs if (value > alpha && d < newDepth) { // Adjust full-depth search based on LMR results - if the result was @@ -1260,11 +1262,7 @@ moves_loop: // When in check, search starts here 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 561/834] 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 0360c2bde..39caf8e67 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 562/834] 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 b7c6d8e3b..81c25fecc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1200,7 +1200,7 @@ moves_loop: // When in check, search starts here 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 563/834] 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 39caf8e67..882ca67d5 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 564/834] 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 81c25fecc..2cab7bd9d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1237,7 +1237,7 @@ moves_loop: // When in check, search starts here // 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 565/834] 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 882ca67d5..1c278147f 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 566/834] 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 2cab7bd9d..45515555e 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 567/834] 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 45515555e..7c83cae24 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1077,9 +1077,8 @@ moves_loop: // When in check, search starts here 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 568/834] 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 | 114 ++++++++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7c83cae24..d5e58eb5b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1100,72 +1100,68 @@ moves_loop: // When in check, search starts here } // 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. + Value singularBeta = ttData.value - (58 + 76 * (ss->ttPv && !PvNode)) * depth / 57; + Depth singularDepth = newDepth / 2; - // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) - // and lower extension margins scale well. + ss->excludedMove = move; + value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = Move::none(); - 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) + if (value < singularBeta) { - Value singularBeta = ttData.value - (58 + 76 * (ss->ttPv && !PvNode)) * depth / 57; - Depth singularDepth = newDepth / 2; + 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; - ss->excludedMove = move; - value = - search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); - ss->excludedMove = Move::none(); + extension = + 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - 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); - - 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; + 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 569/834] 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 d5e58eb5b..6827f5127 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 570/834] 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 9f023b5fe..5e2c27822 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 571/834] 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 6827f5127..4fca84f95 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 @@ moves_loop: // When in check, search starts here // Update the current move (this must be done after singular extension search) ss->currentMove = move; - ss->isTTMove = (move == ttData.move); ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1203,7 +1201,7 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (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 @@ moves_loop: // When in check, search starts here ttData.move, moveCount); if (!PvNode) { - int bonus = ss->isTTMove ? 800 : -879; + int bonus = bestMove == ttData.move ? 800 : -879; ttMoveHistory << bonus; } } @@ -1431,11 +1429,11 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = std::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 6ab98f7c4..0c041c826 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 572/834] 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 29ebffcaa..f0894a262 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 e8602bb7c..a2d1a4364 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 6cd6ee744..6c7975807 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 5c1c07f7b..1686b3a72 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 573/834] 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 | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 4fca84f95..857a8d47e 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; + Value futilityMult = 93 - 20 * (cutNode && !ss->ttHit); - return futilityMult * d // - - improvingDeduction // - - worseningDeduction // - + (ss - 1)->statScore / 335 // - + std::abs(correctionValue) / 149902; + 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 574/834] 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 f16758609..8caa22858 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 857a8d47e..0af54b4f5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1004,6 +1004,8 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 0c041c826..5caff10e8 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 575/834] 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 0af54b4f5..5d92e1b1d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1233,7 +1233,7 @@ moves_loop: // When in check, search starts here // 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 576/834] 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 5d92e1b1d..d71f78ac1 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 @@ moves_loop: // When in check, search starts here // 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 577/834] 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 d71f78ac1..bb9ce68ba 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1237,13 +1237,10 @@ moves_loop: // When in check, search starts here 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 578/834] 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 1c278147f..b0059bd31 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 @@ top: 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 @@ top: 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 72a6f4e1c..7da7c3a7e 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 579/834] 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 b0059bd31..1df0b0ccf 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -249,7 +249,7 @@ top: 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 @@ top: 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 bb9ce68ba..5f20e5938 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 580/834] 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 5f20e5938..cce01867b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1292,9 +1292,6 @@ moves_loop: // When in check, search starts here 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 581/834] 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 259d6eefb..46914789e 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 1df0b0ccf..45bb8afe8 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 582/834] 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 5d8457611..d7f7dbdef 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 583/834] 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 | 38 ++++++++++++++++---------------- 3 files changed, 44 insertions(+), 54 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 45bb8afe8..6d00dd941 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 @@ top: case QSEARCH_TT : case PROBCUT_TT : ++stage; - cur = moves + 1; return ttMove; case CAPTURE_INIT : @@ -237,12 +236,10 @@ top: 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 @@ top: 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 7da7c3a7e..4218ab5a3 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 cce01867b..f59358182 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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)); + 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(); - if (skip) + // 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 584/834] 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 fa720a1c3..c414c51fc 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,12 +140,78 @@ "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": { "os": "windows-2022" } }, + { + "binaries": "apple-silicon", + "config": { + "os": "windows-11-arm" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "ubuntu-20.04" + } + }, + { + "binaries": "apple-silicon", + "config": { + "os": "ubuntu-22.04" + } + }, { "binaries": "apple-silicon", "config": { @@ -139,10 +219,64 @@ } }, { - "binaries": "apple-silicon", + "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 5878adecb..67b2e1c55 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 57d0d53f0..6d35a183d 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 132bd6f48..773d6c271 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 17badefde..fc27044f8 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 585/834] 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 f59358182..775aaceff 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1451,10 +1451,9 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -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 586/834] 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 831284364..83b09637a 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 587/834] 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 c71f0368e..a470cc0b8 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 ab6b4350e..46607c1e9 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 fc27044f8..72d06f09e 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 b9b422a65..beb0c7f1c 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 912d44335..00616097a 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 588/834] 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 72d06f09e..9ea3e01b7 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 84f11d6de..756433290 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 c91da2cc8..70e9beeb1 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 3920efb17..b4440c1e3 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 be5e30b5e..7c74d5e64 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 70ca68a0c..000000000 --- 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 cd32c5312..c9358823b 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 83b09637a..d13105aa4 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 aa9e2a676..10aadc917 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 0c9f097dc..c020ce05b 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 f21a8dec7..35cc8a5fe 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 beb0c7f1c..37634cbc6 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 000000000..560223000 --- /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 589/834] 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 6d00dd941..f3d6fef8b 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 775aaceff..ee876f855 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1073,9 +1073,7 @@ moves_loop: // When in check, search starts here 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 590/834] 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 f3d6fef8b..faef753be 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 @@ top: 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 @@ top: 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 @@ top: 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 @@ top: 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 @@ top: 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 4218ab5a3..2634fdd78 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 ee876f855..673caacb9 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 591/834] 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 d01ed41fe..a338083cf 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 592/834] 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 673caacb9..8d2d5720d 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 593/834] 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 8d2d5720d..8c31fb3a7 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 594/834] 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 8c31fb3a7..d7abef3d2 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 @@ moves_loop: // When in check, search starts here 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 595/834] 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 d7abef3d2..5b16b269d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1223,7 +1223,7 @@ moves_loop: // When in check, search starts here 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 596/834] 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 8caa22858..be88a8e96 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 5b16b269d..10e244829 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1095,8 +1095,9 @@ moves_loop: // When in check, search starts here 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 597/834] 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 70f2efaa2..b53fe03af 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 5bf2a93e5..781bd8070 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 6d35a183d..b269bd742 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 9ea3e01b7..87bf4d697 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 598/834] 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 10e244829..14f235b5c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1147,7 +1147,7 @@ moves_loop: // When in check, search starts here 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 599/834] 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 6c7975807..5d7672153 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 06bd1892e..0f6dd7585 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 600/834] 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 14f235b5c..c6f61ec90 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 5d7672153..d40e1e292 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 601/834] 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 c6f61ec90..670d343e8 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 602/834] 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 7c74d5e64..51f86fd6e 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 560223000..f37eeb930 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 603/834] 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 faef753be..d2764fa84 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 @@ top: 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 2634fdd78..b6784fb78 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 35cc8a5fe..550bcaad4 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 f0894a262..29ebffcaa 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 604/834] 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 724165b00..cf6b1c472 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 605/834] 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 670d343e8..a58ac3e87 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 606/834] 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 0b6fbce0d..07fe9f542 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 607/834] 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 a58ac3e87..93fe7e588 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 608/834] 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 93fe7e588..197da7a4f 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 609/834] 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 197da7a4f..8b8c3d6d0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1223,7 +1223,7 @@ moves_loop: // When in check, search starts here 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 610/834] 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 8b8c3d6d0..f7390bc2c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1431,11 +1431,10 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -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 611/834] 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 f7390bc2c..b686808f3 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 612/834] 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 d7f7dbdef..953348987 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 613/834] 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 b686808f3..e3cb1cdb8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1412,7 +1412,7 @@ moves_loop: // When in check, search starts here 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 614/834] 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 e3cb1cdb8..25c173f23 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1131,13 +1131,12 @@ moves_loop: // When in check, search starts here 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 615/834] 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 25c173f23..ddc27156e 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 616/834] 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 ddc27156e..9f44defbe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1380,15 +1380,13 @@ moves_loop: // When in check, search starts here 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 617/834] 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 b269bd742..95ca12092 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 618/834] 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 d2764fa84..3e7c10160 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 @@ top: 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 @@ top: 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 @@ top: [[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 @@ top: 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 b6784fb78..bf0c96c7c 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 619/834] 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 9f44defbe..820bc8cdd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1083,7 +1083,7 @@ moves_loop: // When in check, search starts here 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 620/834] 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 820bc8cdd..a82b4edf0 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 621/834] 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 a82b4edf0..cdbc95318 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1206,8 +1206,7 @@ moves_loop: // When in check, search starts here 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 622/834] 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 cdbc95318..4d0e64b32 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 @@ moves_loop: // When in check, search starts here // 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 623/834] 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 87bf4d697..50bb2082a 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 624/834] 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 50bb2082a..14c3c50c9 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 51f86fd6e..e77c98f8c 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 625/834] 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 4d0e64b32..394070458 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 626/834] 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 29ebffcaa..5840e2556 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 627/834] 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 394070458..6430c3e32 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 @@ moves_loop: // When in check, search starts here // 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 5caff10e8..e0b57e30b 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 628/834] 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 6430c3e32..8decb5d1e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1202,7 +1202,7 @@ moves_loop: // When in check, search starts here // 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 629/834] 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 8decb5d1e..cfd3583bc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1192,7 +1192,7 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 630/834] 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 cfd3583bc..1b5a2bbbc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1271,11 +1271,9 @@ moves_loop: // When in check, search starts here 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 631/834] 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 be88a8e96..b43a9e5b6 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 773d6c271..e8c8f23f2 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 14c3c50c9..40dceae0b 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 f85356c59..3bdde000f 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 a73bd8501..10adc70be 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 7c6cceb7c..287fd8927 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 3e7c10160..79b6f55a2 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 @@ top: 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 @@ top: 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 @@ top: 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 bf0c96c7c..9d6c02b0e 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 632/834] 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 1b5a2bbbc..fae3fc39f 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 @@ moves_loop: // When in check, search starts here (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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here } 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here { 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1080; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1080; } if (PvNode) @@ -1465,7 +1453,7 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here { 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 633/834] 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 fae3fc39f..6c4d125b2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1189,7 +1189,7 @@ moves_loop: // When in check, search starts here // 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 634/834] 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 6c4d125b2..60ee42aba 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 635/834] 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 60ee42aba..65f1a79a5 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 636/834] 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 4dab6bf45..d3c9ebaa3 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 637/834] 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 65f1a79a5..e698e2c7f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1047,8 +1047,8 @@ moves_loop: // When in check, search starts here } // 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 638/834] 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 b43a9e5b6..76b4f71ca 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 e698e2c7f..c5534924b 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 639/834] 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 c5534924b..41177b81c 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 @@ moves_loop: // When in check, search starts here // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 968; + r += 931; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1038,7 +1038,7 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1109,11 +1109,11 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 826 / 8192; + r -= ss->statScore * 789 / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1237,7 +1237,7 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); + const bool doDeeperSearch = value > (bestValue + 43 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1246,7 +1246,7 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 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 640/834] 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 41177b81c..cd84eb240 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 @@ moves_loop: // When in check, search starts here } // 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 e0b57e30b..a43649e94 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 641/834] 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 cd84eb240..27a4bea67 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 642/834] 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 27a4bea67..9855d12e1 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 a43649e94..07fc74317 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 643/834] 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 9855d12e1..24ba1c018 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 644/834] 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 24ba1c018..c517fd496 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1040,8 +1040,9 @@ moves_loop: // When in check, search starts here // 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 645/834] 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 c517fd496..b0efea869 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1229,7 +1229,7 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here // 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 646/834] 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 229debd40..e249a8e49 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 647/834] 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 76b4f71ca..273cab33b 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 b0efea869..71a46ea7b 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 648/834] 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 71a46ea7b..743f4bce7 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 649/834] 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 743f4bce7..6503cc14d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1219,9 +1219,7 @@ moves_loop: // When in check, search starts here // 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 650/834] 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 e77c98f8c..069210304 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 651/834] 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 6503cc14d..e3044bf6a 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 652/834] 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 97c462c57..bb32bee56 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 653/834] 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 500e88184..9b1dd865c 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 654/834] 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 e3044bf6a..9775e46b1 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 655/834] 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 273cab33b..15614effd 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 9775e46b1..268f9e2cc 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 656/834] 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 268f9e2cc..e151cc796 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 657/834] 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 f959bcb86..27ef89c0e 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 658/834] 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 e151cc796..baf6dfb62 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1233,7 +1233,7 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 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 659/834] 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 40dceae0b..047a9e71c 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 660/834] 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 46914789e..b2abd9cc0 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 79b6f55a2..eb7c45837 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 baf6dfb62..3c17042ca 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 @@ moves_loop: // When in check, search starts here { 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 @@ moves_loop: // When in check, search starts here 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 661/834] 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 3c17042ca..bf454bbe8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1183,7 +1183,7 @@ moves_loop: // When in check, search starts here // 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 662/834] 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 bf454bbe8..32ad708c8 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 663/834] 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 15614effd..6bf0745ad 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 32ad708c8..b5e6e1be9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1251,9 +1251,13 @@ moves_loop: // When in check, search starts here 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 664/834] 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 047a9e71c..cec623f52 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 5e2c27822..4ac1369d4 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 665/834] 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 b5e6e1be9..39d9862be 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 @@ moves_loop: // When in check, search starts here 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 666/834] 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 6bf0745ad..e606e5a8d 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 39d9862be..b807ae7d1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1039,9 +1039,10 @@ moves_loop: // When in check, search starts here // 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 667/834] 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 c414c51fc..57c97686c 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 668/834] 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 069210304..685cf8ea1 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 669/834] 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 b2abd9cc0..faf4af3d7 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 670/834] 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 b807ae7d1..3bf460a1a 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 671/834] 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 685cf8ea1..a073c6196 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 672/834] 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 3bf460a1a..b4b85ee76 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1178,27 +1178,27 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 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 @@ moves_loop: // When in check, search starts here { // 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 673/834] 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 b4b85ee76..c81012625 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 674/834] 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 e606e5a8d..804824d8c 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 c81012625..b4569d9a5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1112,9 +1112,8 @@ moves_loop: // When in check, search starts here // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. - if (!rootNode && move == ttData.move && !excludedMove - && depth >= 6 - (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 675/834] 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 b4569d9a5..6687ecb22 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1251,13 +1251,12 @@ moves_loop: // When in check, search starts here 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 676/834] 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 6687ecb22..aa78a673f 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 677/834] 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 57c97686c..436cb4b84 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 67b2e1c55..7805b24d6 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 678/834] 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 eb7c45837..dd6d71167 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 9d6c02b0e..6a3305c6c 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 679/834] 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 10adc70be..697a83cdf 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 680/834] 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 aa78a673f..446f81379 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 681/834] 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 446f81379..e54f7c439 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 @@ moves_loop: // When in check, search starts here // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 931; + r += 946; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1041,15 +1041,15 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here + 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 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1116,7 +1116,7 @@ moves_loop: // When in check, search starts here && 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * (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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { // 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 @@ moves_loop: // When in check, search starts here } // 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 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 682/834] 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 e54f7c439..286b339c0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1177,27 +1177,27 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * (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 @@ moves_loop: // When in check, search starts here { // 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 683/834] 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 dd6d71167..0d2a72306 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 684/834] 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 286b339c0..7068c332d 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 685/834] 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 950435f30..d9b783eb6 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 686/834] 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 7068c332d..f5fad184a 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 687/834] 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 f5fad184a..47409b3a8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1252,9 +1252,6 @@ moves_loop: // When in check, search starts here 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 688/834] 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 47409b3a8..aafc33789 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1049,7 +1049,7 @@ moves_loop: // When in check, search starts here } // 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 689/834] 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 0d2a72306..fa447e189 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -316,23 +316,4 @@ top: 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 6a3305c6c..922a9069b 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 aafc33789..1396a817d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1056,8 +1056,7 @@ moves_loop: // When in check, search starts here 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 690/834] 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 1396a817d..6a17e1624 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1006,7 +1006,7 @@ moves_loop: // When in check, search starts here 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 691/834] 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 6a17e1624..40e8ef7cf 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 692/834] 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 fa447e189..4b01beb67 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 693/834] 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 40e8ef7cf..7704d530e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1405,7 +1405,7 @@ moves_loop: // When in check, search starts here 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 694/834] 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 7704d530e..d786b572a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1177,6 +1177,7 @@ moves_loop: // When in check, search starts here // 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 695/834] 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 d786b572a..5f2f63c65 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1218,8 +1218,7 @@ moves_loop: // When in check, search starts here // 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 696/834] 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 a76bd3ace..ff6235695 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 3d7386c30..0c957fda1 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 697/834] 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 e8c8f23f2..180c15d15 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 698/834] 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 5f2f63c65..01155e260 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 699/834] 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 4b01beb67..b4523463f 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 700/834] 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 01155e260..41f650e79 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1041,9 +1041,8 @@ moves_loop: // When in check, search starts here 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 701/834] 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 41f650e79..e7c5e6ac1 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 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here 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 702/834] 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 922a9069b..5b3190594 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 e7c5e6ac1..de2ab7a3e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,10 +1053,7 @@ moves_loop: // When in check, search starts here 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 703/834] 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 cf6b1c472..56bb7b5a4 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 704/834] 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 4ac1369d4..f59632475 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 705/834] 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 804824d8c..6bd323d2c 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 56bb7b5a4..dde496fe0 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 706/834] 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 de2ab7a3e..d2afc480b 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 707/834] 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 d2afc480b..70124c589 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 708/834] 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 70124c589..634492682 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1049,16 +1049,11 @@ moves_loop: // When in check, search starts here } // SEE based pruning for captures and checks + // Avoid pruning sacrifices of our last piece for stalemate int margin = std::max(157 * depth + captHist / 29, 0); - 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 709/834] 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 634492682..5a0185618 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1167,8 +1167,7 @@ moves_loop: // When in check, search starts here // 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 710/834] 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 5a0185618..40676e2e2 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 711/834] 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 b4523463f..4b01beb67 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 712/834] 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 40676e2e2..9c4a94df8 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 713/834] 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 9c4a94df8..40676e2e2 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 714/834] 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 1067f6e76..073e40a13 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 715/834] 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 85c2be3e7..c4d14fc7b 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 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 --syzygy50MoveRule false | tee matecheckcursed1.out + ! grep "issues were detected" matecheckcursed1.out > /dev/null - - name: Verify mate and TB win count for matecheckcursed.out + - name: Run matetrack th4 with --syzygy50MoveRule false working-directory: matetrack run: | - mates=$(grep "Found mates:" matecheckcursed.out | awk '{print $3}') - tbwins=$(grep "Found TB wins:" matecheckcursed.out | awk '{print $4}') + 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 --threads 4 --syzygy50MoveRule false | tee matecheckcursed4.out + ! grep "issues were detected" matecheckcursed4.out > /dev/null + + - name: Verify mate and TB win count for matecheckcursed[14].out + working-directory: matetrack + run: | + 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 matecheckcursed.out" >&2 + 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 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 716/834] 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 436cb4b84..d916fd071 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 95ca12092..c2280f0b4 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 717/834] 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 40676e2e2..0a4a390bb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1137,7 +1137,10 @@ moves_loop: // When in check, search starts here // 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 718/834] 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 faf4af3d7..1f7abc542 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 0a4a390bb..25102b131 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 719/834] 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 25102b131..fbf493486 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 720/834] 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 fbf493486..1ef01e5a6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1260,7 +1260,7 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && 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 721/834] 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 6bd323d2c..1fb91adaf 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 a073c6196..11e460666 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 722/834] 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 1ef01e5a6..9b6bbe188 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1010,8 +1010,6 @@ moves_loop: // When in check, search starts here 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 @@ moves_loop: // When in check, search starts here // 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 @@ moves_loop: // When in check, search starts here 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 07fc74317..d4bbca5c6 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 723/834] 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 9b6bbe188..888a8d870 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 724/834] 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 888a8d870..13d86125a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1067,6 +1067,7 @@ moves_loop: // When in check, search starts here 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 725/834] 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 13d86125a..0684ea5cb 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 726/834] 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 4b01beb67..e7ac44c15 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 727/834] 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 e7ac44c15..2eec3556b 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 728/834] 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 1fb91adaf..0429f9f0a 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 0684ea5cb..04c04b5be 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 729/834] 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 04c04b5be..64f8ea189 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1449,9 +1449,11 @@ moves_loop: // When in check, search starts here && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { - 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 730/834] 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 11e460666..effda826b 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 731/834] 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 64f8ea189..6b2c28da2 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 732/834] 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 d916fd071..f72451f5b 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 180c15d15..dec5998da 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 cec623f52..7244f7040 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 733/834] 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 f59632475..d0cad3e7f 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 734/834] 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 6b2c28da2..8dcdd5ed7 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 735/834] 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 dec5998da..67dd60ca5 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 736/834] 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 effda826b..472da834f 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 737/834] 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 0429f9f0a..8d57062cc 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 1f7abc542..940e98991 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 738/834] 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 8d57062cc..4729afeab 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 8dcdd5ed7..fa2355130 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 739/834] 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 c4d14fc7b..43d35ca18 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 740/834] 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 4729afeab..31ff51def 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 43ba7d9c0..f87d7a94d 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 00616097a..79376b10a 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 741/834] 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 fa2355130..e6ee4274e 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 742/834] 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 d13105aa4..3096758b9 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 743/834] 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 d0cad3e7f..1551eb9df 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 744/834] 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 3096758b9..b1743329f 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 10aadc917..56de0b7d0 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 dde496fe0..579121bf3 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 745/834] 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 e6ee4274e..ef87c828e 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 @@ moves_loop: // When in check, search starts here 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 746/834] 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 ef87c828e..151b4c6ee 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1260,7 +1260,10 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && 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 747/834] 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 c9358823b..6855831d5 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 56de0b7d0..7ff95b6ef 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 748/834] 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 151b4c6ee..3cd10ad9b 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 749/834] 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 3cd10ad9b..734372004 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 750/834] 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 734372004..50585eb13 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 751/834] 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 50585eb13..b9321de75 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1443,10 +1443,10 @@ moves_loop: // When in check, search starts here 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 752/834] 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 b9321de75..4a55da301 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 753/834] 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 70e9beeb1..5bcfb0849 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 ba122adc8..b72bbbce4 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_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, + 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 754/834] 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 4a55da301..212e14fcf 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1451,8 +1451,7 @@ moves_loop: // When in check, search starts here 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 755/834] 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 212e14fcf..a0c6eafe3 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 756/834] 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 7244f7040..b30e16c07 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 a4c0bb1eb..9edff4864 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 d26844f4c..7315b8881 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 23f4b8c2b..23bc70d0e 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 e262f3875..9988680aa 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 92cb650f2..f4aa8fc26 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 e4a59fec5..b9be6f170 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 756433290..b1707e742 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 b4440c1e3..7ead09327 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 472da834f..85c0dbfec 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 2ad5a86a1..a8679d14d 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 d14f1e0ab..4218c0fe2 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 957dc7bff..48aeafdf6 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 6855831d5..c701ac6f5 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 7ff95b6ef..95cf74fa3 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 c020ce05b..5965f24aa 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 550bcaad4..3617a85eb 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 37634cbc6..3439bc43c 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 c99874076..957e3453f 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 02212160a..7ecfd58a2 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 5cadbc726..261b6005d 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 d4bbca5c6..4c4357d3e 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 000000000..ae5429676 --- /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 000000000..a8b5404b2 --- /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 757/834] 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 | 42 +- src/nnue/features/half_ka_v2_hm.h | 5 +- src/nnue/network.cpp | 2 +- src/nnue/nnue_accumulator.cpp | 589 ++++++++++++++++++++++------ src/nnue/nnue_accumulator.h | 51 ++- src/nnue/nnue_architecture.h | 6 +- src/nnue/nnue_common.h | 23 +- src/nnue/nnue_feature_transformer.h | 211 ++++++++-- src/nnue/simd.h | 32 +- src/position.cpp | 157 +++++++- src/position.h | 89 +++-- src/search.cpp | 17 +- src/types.h | 43 ++ 23 files changed, 1468 insertions(+), 279 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 31ff51def..db552a3a5 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 b30e16c07..cc85ac78e 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 a9f70f10d..38cafaec7 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 9b1d674c0..6c97d7863 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 27ef89c0e..1cd717b8f 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 2a6c9afa9..c8dc64ace 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 9988680aa..6ab3507f3 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 b1707e742..fce6f17df 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 000000000..6fc6b00ec --- /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 000000000..458b04dd1 --- /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 5bcfb0849..f652ba0b8 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)); +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 (dp.remove_sq != SQ_NONE) - removed.push_back(make_index(dp.remove_sq, dp.remove_pc, ksq)); + if (diff.remove_sq != SQ_NONE) + removed.push_back(make_index(diff.remove_sq, diff.remove_pc, ksq)); - if (dp.add_sq != SQ_NONE) - added.push_back(make_index(dp.add_sq, dp.add_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); +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 DirtyPiece& dirtyPiece, Color perspective) { - return dirtyPiece.pc == make_piece(perspective, KING); +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 b72bbbce4..e695b273a 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 48aeafdf6..b91763f06 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 b1743329f..0444b6e40 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); } -void AccumulatorState::reset(const DirtyPiece& dp) noexcept { - dirtyPiece = dp; - accumulatorBig.computed.fill(false); - accumulatorSmall.computed.fill(false); +template +const AccumulatorState& AccumulatorStack::latest() const noexcept { + return accumulators()[size - 1]; } -const AccumulatorState& AccumulatorStack::latest() const noexcept { return accumulators[size - 1]; } +// Explicit template instantiations +template const AccumulatorState& AccumulatorStack::latest() const noexcept; +template const AccumulatorState& AccumulatorStack::latest() const noexcept; -AccumulatorState& AccumulatorStack::mut_latest() noexcept { return accumulators[size - 1]; } +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; +} + +template +std::array, AccumulatorStack::MaxSize>& +AccumulatorStack::mut_accumulators() 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; +} 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; - if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq) + auto& accumulators = mut_accumulators(); + + 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; + 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; + } + } - 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 95cf74fa3..4c907c7e3 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 + template + [[nodiscard]] const std::array, MaxSize>& accumulators() const noexcept; + + template + [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; + + 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 5965f24aa..5093abdd7 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 3617a85eb..8a877ae2b 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 3439bc43c..5ad2d3371 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); - read_leb_128(stream, biases, HalfDimensions); - read_leb_128(stream, weights, HalfDimensions * InputDimensions); - read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + if (UseThreats) + { + auto combinedWeights = + std::make_unique>(); + auto combinedPsqtWeights = + std::make_unique>(); + + 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* 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 pa = vec_mulhi_16(sum0a, sum1a); - const vec_t pb = vec_mulhi_16(sum0b, sum1b); + 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); - out[j] = vec_packus_16(pa, pb); + 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 f37eeb930..6160221b9 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 1551eb9df..a515876b6 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 579121bf3..9afdb17f9 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)]++; + + if (dts) + update_piece_threats(pc, s, dts); } -inline void Position::remove_piece(Square s) { - +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 a0c6eafe3..ce7d41008 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 d40e1e292..46aa16a03 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 758/834] Refine constant in correction history update Passed STC LLR: 3.04 (-2.94,2.94) <0.00,2.00> Total: 250112 W: 65277 L: 64635 D: 120200 Ptnml(0-2): 841, 29326, 64134, 29860, 895 https://tests.stockfishchess.org/tests/view/69096c46ea4b268f1fac2a39 Passed LTC LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 142908 W: 37141 L: 36604 D: 69163 Ptnml(0-2): 100, 15478, 39742, 16053, 81 https://tests.stockfishchess.org/tests/view/690e7dfbec1d00d2c195c351 closes https://github.com/official-stockfish/Stockfish/pull/6405 bench 2613869 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ce7d41008..afd862273 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1452,9 +1452,9 @@ moves_loop: // When in check, search starts here if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) && (bestValue < ss->staticEval) == !bestMove) { - auto bonus = - std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), - -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth + / (8 + 2 * (bestValue > ss->staticEval)), + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); update_correction_history(pos, ss, *this, bonus); } From 3ae76847145b31553a958ff87c52c280e4f784be Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:08:23 +0000 Subject: [PATCH 759/834] Improve Threats Speed Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 23168 W: 6132 L: 5845 D: 11191 Ptnml(0-2): 77, 2405, 6325, 2708, 69 https://tests.stockfishchess.org/tests/view/69148c3c7ca8781852331831 ``` Result of 50 runs ================== base (./stockfish.master ) = 985641 +/- 4249 test (./stockfish.patch ) = 1038567 +/- 5679 diff = +52926 +/- 4473 speedup = +0.0537 P(speedup > 0) = 1.0000 CPU: 16 x AMD Ryzen 9 3950X 16-Core Processor Hyperthreading: on ``` closes https://github.com/official-stockfish/Stockfish/pull/6415 No functional change --- src/nnue/features/full_threats.cpp | 81 +++++++++++++++++------------- src/nnue/nnue_accumulator.cpp | 18 ++++++- src/position.cpp | 71 ++++++++++++++++---------- 3 files changed, 107 insertions(+), 63 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 6fc6b00ec..994d2160c 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -58,6 +58,35 @@ constexpr std::array AllPieces = { PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] +namespace { + +template +IndexType make_index_with_orientation( + Piece attacker, Square from, Square to, Piece attacked, int orientation) { + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); + + if constexpr (Perspective == BLACK) + { + attacker = ~attacker; + attacked = ~attacked; + } + + const auto piecePairData = index_lut1[attacker][attacked]; + + const bool less_than = static_cast(from) < static_cast(to); + if ((piecePairData.excluded_pair_info() + less_than) & 2) + return FullThreats::Dimensions; + + const IndexType index = + piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; + + sf_assume(index != FullThreats::Dimensions); + return index; +} + +} // namespace + static void init_index_luts() { for (Piece attacker : AllPieces) { @@ -129,32 +158,8 @@ void init_threat_offsets() { template IndexType FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - from = (Square) (int(from) ^ OrientTBL[Perspective][ksq]); - to = (Square) (int(to) ^ OrientTBL[Perspective][ksq]); - - if (Perspective == BLACK) - { - attacker = ~attacker; - attacked = ~attacked; - } - - auto piecePairData = index_lut1[attacker][attacked]; - - // Some threats imply the existence of the corresponding ones in the opposite - // direction. We filter them here to ensure only one such threat is active. - - // In the below addition, the 2nd lsb gets set iff either the pair is always excluded, - // or the pair is semi-excluded and from < to. By using an unsigned compare, the following - // sequence can use an add-with-carry instruction. - bool less_than = static_cast(from) < static_cast(to); - if ((piecePairData.excluded_pair_info() + less_than) & 2) - return Dimensions; - - IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != Dimensions); - return index; + return make_index_with_orientation(attacker, from, to, attacked, + OrientTBL[Perspective][ksq]); } // Get a list of indices for active features in ascending order @@ -162,8 +167,9 @@ template void FullThreats::append_active_indices(const Position& pos, IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(Perspective); - Bitboard occupied = pos.pieces(); + Square ksq = pos.square(Perspective); + const int orientation = OrientTBL[Perspective][ksq]; + Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { @@ -187,7 +193,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -198,7 +205,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -215,8 +223,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = - make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -243,7 +251,9 @@ void FullThreats::append_changed_indices(Square ksq, IndexList& added, FusedUpdateData* fusedData, bool first) { - for (const auto dirty : diff.list) + const int orientation = OrientTBL[Perspective][ksq]; + + for (const auto& dirty : diff.list) { auto attacker = dirty.pc(); auto attacked = dirty.threatened_pc(); @@ -282,9 +292,10 @@ void FullThreats::append_changed_indices(Square ksq, } } - IndexType index = make_index(attacker, from, to, attacked, ksq); + const IndexType index = + make_index_with_orientation(attacker, from, to, attacked, orientation); - if (index != Dimensions) + if (index < Dimensions) (add ? added : removed).push_back(index); } } diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 0444b6e40..7f723c61f 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -336,7 +336,8 @@ struct AccumulatorUpdateContext { to_psqt_weight_vector(indices)...); } - void apply(typename FeatureSet::IndexList added, typename FeatureSet::IndexList removed) { + void apply(const typename FeatureSet::IndexList& added, + const typename FeatureSet::IndexList& removed) { const auto fromAcc = from.template acc().accumulation[Perspective]; const auto toAcc = to.template acc().accumulation[Perspective]; @@ -573,6 +574,21 @@ void update_accumulator_incremental( FeatureSet::template append_changed_indices(ksq, computed.diff, added, removed); + if (!added.size() && !removed.size()) + { + auto& targetAcc = target_state.template acc(); + const auto& sourceAcc = computed.template acc(); + + std::memcpy(targetAcc.accumulation[Perspective], sourceAcc.accumulation[Perspective], + sizeof(targetAcc.accumulation[Perspective])); + std::memcpy(targetAcc.psqtAccumulation[Perspective], + sourceAcc.psqtAccumulation[Perspective], + sizeof(targetAcc.psqtAccumulation[Perspective])); + + targetAcc.computed[Perspective] = true; + return; + } + auto updateContext = make_accumulator_update_context(featureTransformer, computed, target_state); diff --git a/src/position.cpp b/src/position.cpp index a515876b6..58edbcc33 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1051,12 +1051,22 @@ inline void add_dirty_threat( template void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { - // Add newly threatened pieces - Bitboard occupied = pieces(); + const Bitboard occupied = pieces(); + const Bitboard rookQueens = pieces(ROOK, QUEEN); + const Bitboard bishopQueens = pieces(BISHOP, QUEEN); + const Bitboard knights = pieces(KNIGHT); + const Bitboard kings = pieces(KING); + const Bitboard whitePawns = pieces(WHITE, PAWN); + const Bitboard blackPawns = pieces(BLACK, PAWN); - Bitboard rAttacks = attacks_bb(s, occupied); - Bitboard bAttacks = attacks_bb(s, occupied); - Bitboard qAttacks = rAttacks | bAttacks; + const Bitboard rAttacks = attacks_bb(s, occupied); + const Bitboard bAttacks = attacks_bb(s, occupied); + + Bitboard qAttacks = Bitboard(0); + if constexpr (ComputeRay) + qAttacks = rAttacks | bAttacks; + else if (type_of(pc) == QUEEN) + qAttacks = rAttacks | bAttacks; Bitboard threatened; @@ -1092,35 +1102,42 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); } - Bitboard sliders = (pieces(ROOK, QUEEN) & rAttacks) | (pieces(BISHOP, QUEEN) & bAttacks); + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); - Bitboard incoming_threats = (attacks_bb(s, occupied) & pieces(KNIGHT)) - | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) - | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) - | (attacks_bb(s, occupied) & pieces(KING)); - - while (sliders) + if constexpr (ComputeRay) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); - - Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; - threatened = ray & qAttacks & occupied; - - assert(!more_than_one(threatened)); - if (ComputeRay && threatened) + while (sliders) { - Square threatened_sq = lsb(threatened); + Square slider_sq = pop_lsb(sliders); + Piece slider = piece_on(slider_sq); - Piece threatened_pc = piece_on(threatened_sq); - add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + const Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; + const Bitboard discovered = ray & qAttacks & occupied; + + assert(!more_than_one(discovered)); + if (discovered) + { + const Square threatened_sq = lsb(discovered); + const Piece threatened_pc = piece_on(threatened_sq); + add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + } + + add_dirty_threat(dts, slider, pc, slider_sq, s); + } + } + else + { + while (sliders) + { + Square slider_sq = pop_lsb(sliders); + Piece slider = piece_on(slider_sq); + add_dirty_threat(dts, slider, pc, slider_sq, s); } - - add_dirty_threat(dts, slider, pc, slider_sq, s); } - // Add threats of sliders that were already threatening s, - // sliders are already handled in the loop above + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); while (incoming_threats) { From fa4f05d3ef2dc393da73b0709e6c2c59a09652c5 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 9 Nov 2025 03:50:15 -0800 Subject: [PATCH 760/834] Don't copy around DirtyThreats The contents of DirtyThreats gets memcpyed twice for each call to do_move. (Return value optimization doesn't apply to the do_move function itself because it constructs a std::pair, so it gets copied; and the calls to reset also require a copy.) This patch inserts the dirty info in place. Sometimes the caller of do_move ignores the DirtyThreats info, so we pass in scratch objects. I found that stack-allocating these scratch objects was bad on Fishtest, so I begrudgingly put them in the Position struct. Both templating the do_move function on whether dirty threats are needed and putting a null-check branch for each use of dirty threats were slowdowns locally. Of course, nothing prevents a future attempt at cleaning this up. passed STC LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 68448 W: 17770 L: 17418 D: 33260 Ptnml(0-2): 198, 7425, 18630, 7769, 202 https://tests.stockfishchess.org/tests/view/6914c01a7ca87818523318ba closes https://github.com/official-stockfish/Stockfish/pull/6414 No functional change --- src/nnue/nnue_accumulator.cpp | 9 ++++++--- src/nnue/nnue_accumulator.h | 14 ++++++++++--- src/position.cpp | 24 +++++++++++------------ src/position.h | 37 ++++++++++++++++++++++------------- src/search.cpp | 8 ++++---- 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 7f723c61f..47f09afce 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "../bitboard.h" @@ -122,11 +123,13 @@ void AccumulatorStack::reset() noexcept { size = 1; } -void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept { +std::pair AccumulatorStack::push() noexcept { assert(size < MaxSize); - psq_accumulators[size].reset(dirtyBoardData.dp); - threat_accumulators[size].reset(dirtyBoardData.dts); + auto& dp = psq_accumulators[size].reset(); + auto& dts = threat_accumulators[size].reset(); + new (&dts) DirtyThreats; size++; + return {dp, dts}; } void AccumulatorStack::pop() noexcept { diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 4c907c7e3..341960b30 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "../types.h" #include "nnue_architecture.h" @@ -141,6 +142,12 @@ struct AccumulatorState { accumulatorBig.computed.fill(false); accumulatorSmall.computed.fill(false); } + + typename FeatureSet::DiffType& reset() noexcept { + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); + return diff; + } }; class AccumulatorStack { @@ -150,9 +157,10 @@ class AccumulatorStack { template [[nodiscard]] const AccumulatorState& latest() const noexcept; - void reset() noexcept; - void push(const DirtyBoardData& dirtyBoardData) noexcept; - void pop() noexcept; + void reset() noexcept; + void push(const DirtyBoardData& dirtyBoardData) noexcept; + std::pair push() noexcept; + void pop() noexcept; template void evaluate(const Position& pos, diff --git a/src/position.cpp b/src/position.cpp index 58edbcc33..347bbcfc8 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -201,7 +201,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { Square sq = SQ_A8; std::istringstream ss(fenStr); - std::memset(this, 0, sizeof(Position)); + std::memset(reinterpret_cast(this), 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); st = si; @@ -688,10 +688,12 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position // will be prefetched -DirtyBoardData Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +void Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -720,12 +722,10 @@ DirtyBoardData Position::do_move(Move m, bool checkEP = false; - DirtyPiece dp; - dp.pc = pc; - dp.from = from; - dp.to = to; - dp.add_sq = SQ_NONE; - DirtyThreats dts; + dp.pc = pc; + dp.from = from; + dp.to = to; + dp.add_sq = SQ_NONE; dts.us = us; dts.prevKsq = square(us); dts.threatenedSqs = dts.threateningSqs = 0; @@ -970,8 +970,6 @@ DirtyBoardData Position::do_move(Move m, assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE)); assert(dp.from != SQ_NONE); assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING)); - - return {dp, dts}; } diff --git a/src/position.h b/src/position.h index 9afdb17f9..c95697d19 100644 --- a/src/position.h +++ b/src/position.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "bitboard.h" @@ -133,11 +134,16 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - DirtyBoardData do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); - void undo_move(Move m); - void do_null_move(StateInfo& newSt, const TranspositionTable& tt); - void undo_null_move(); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + void do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -196,14 +202,16 @@ class Position { std::array byTypeBB; std::array byColorBB; - int pieceCount[PIECE_NB]; - int castlingRightsMask[SQUARE_NB]; - Square castlingRookSquare[CASTLING_RIGHT_NB]; - Bitboard castlingPath[CASTLING_RIGHT_NB]; - StateInfo* st; - int gamePly; - Color sideToMove; - bool chess960; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; + DirtyPiece scratch_dp; + DirtyThreats scratch_dts; }; std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -389,7 +397,8 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { } inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { - do_move(m, newSt, gives_check(m), tt); + new (&scratch_dts) DirtyThreats; + do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt); } inline StateInfo* Position::state() const { return st; } diff --git a/src/search.cpp b/src/search.cpp index afd862273..43d96bf24 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -531,16 +531,16 @@ void Search::Worker::do_move( bool capture = pos.capture_stage(move); nodes.fetch_add(1, std::memory_order_relaxed); - DirtyBoardData dirtyBoardData = pos.do_move(move, st, givesCheck, &tt); - accumulatorStack.push(dirtyBoardData); + auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); + pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); if (ss != nullptr) { ss->currentMove = move; ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][dirtyBoardData.dp.pc][move.to_sq()]; + &continuationHistory[ss->inCheck][capture][dirtyPiece.pc][move.to_sq()]; ss->continuationCorrectionHistory = - &continuationCorrectionHistory[dirtyBoardData.dp.pc][move.to_sq()]; + &continuationCorrectionHistory[dirtyPiece.pc][move.to_sq()]; } } From 84148586e53f4bd4f3177a98c7f201f63245fd99 Mon Sep 17 00:00:00 2001 From: mstembera <5421953+mstembera@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:27:33 -0800 Subject: [PATCH 761/834] Fix MSVC compile broken after Shared Memory patch. closes https://github.com/official-stockfish/Stockfish/pull/6397 No functional change --- src/memory.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/memory.h b/src/memory.h index b9be6f170..dad07df1d 100644 --- a/src/memory.h +++ b/src/memory.h @@ -41,6 +41,13 @@ #endif #include + // Some Windows headers (RPC/old headers) define short macros such + // as 'small' expanding to 'char', which breaks identifiers in the code. + // Undefine those macros immediately after including . + #ifdef small + #undef small + #endif + #include extern "C" { From 4784ff2b3be97a0364b77a677e21a644403f7b3a Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Mon, 3 Nov 2025 08:55:26 +0100 Subject: [PATCH 762/834] Unify do_move & do_null_move workload While being there also remove and unused assignment. Tested for non-regression at STC: https://tests.stockfishchess.org/tests/view/69060d81ea4b268f1fac1f36 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 94496 W: 24570 L: 24421 D: 45505 Ptnml(0-2): 264, 10145, 26275, 10306, 258 closes https://github.com/official-stockfish/Stockfish/pull/6383 no functional change --- src/search.cpp | 17 ++++++++--------- src/search.h | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 43d96bf24..d28ac5f27 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -544,7 +544,12 @@ void Search::Worker::do_move( } } -void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } +void Search::Worker::do_null_move(Position& pos, StateInfo& st, Stack* const ss) { + pos.do_null_move(st, tt); + ss->currentMove = Move::null(); + ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; + ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; +} void Search::Worker::undo_move(Position& pos, const Move move) { pos.undo_move(move); @@ -868,12 +873,7 @@ Value Search::Worker::search( // Null move dynamic reduction based on depth Depth R = 6 + depth / 3 + improving; - - ss->currentMove = Move::null(); - ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; - ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; - - do_null_move(pos, st); + do_null_move(pos, st, ss); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); @@ -1580,8 +1580,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) futilityBase = ss->staticEval + 352; } - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, - (ss - 2)->continuationHistory}; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; diff --git a/src/search.h b/src/search.h index 4c4357d3e..f3d99d415 100644 --- a/src/search.h +++ b/src/search.h @@ -299,7 +299,7 @@ class Worker { void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss); void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); - void do_null_move(Position& pos, StateInfo& st); + void do_null_move(Position& pos, StateInfo& st, Stack* const ss); void undo_move(Position& pos, const Move move); void undo_null_move(Position& pos); From 8551f86efce1d55c3cf5bb639247212a3c290bdf Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 25 Oct 2025 03:23:06 -0400 Subject: [PATCH 763/834] Remove check term in capture movepick Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 248448 W: 64697 L: 64708 D: 119043 Ptnml(0-2): 784, 29393, 63971, 29202, 874 https://tests.stockfishchess.org/tests/view/68fc7afb637acd2a11e72d86 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 193626 W: 49808 L: 49764 D: 94054 Ptnml(0-2): 162, 21415, 53621, 21447, 168 https://tests.stockfishchess.org/tests/view/6901ad09637acd2a11e73828 closes https://github.com/official-stockfish/Stockfish/pull/6395 bench 2920273 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 2eec3556b..b5b02609e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -153,7 +153,7 @@ ExtMove* MovePicker::score(MoveList& ml) { if constexpr (Type == CAPTURES) m.value = (*captureHistory)[pc][to][type_of(capturedPiece)] - + 7 * int(PieceValue[capturedPiece]) + 1024 * bool(pos.check_squares(pt) & to); + + 7 * int(PieceValue[capturedPiece]); else if constexpr (Type == QUIETS) { From 55643baa3f6be3265598fba7c7e8b308fe6ccbcf Mon Sep 17 00:00:00 2001 From: Didier Durand Date: Fri, 7 Nov 2025 17:23:26 +0100 Subject: [PATCH 764/834] Fix some doc typos closes https://github.com/official-stockfish/Stockfish/pull/6400 No functional change --- src/history.h | 2 +- src/numa.h | 2 +- tests/instrumented.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/history.h b/src/history.h index 940e98991..445d54181 100644 --- a/src/history.h +++ b/src/history.h @@ -104,7 +104,7 @@ using Stats = MultiArray, Sizes...>; // see https://www.chessprogramming.org/Butterfly_Boards using ButterflyHistory = Stats; -// LowPlyHistory is adressed by play and move's from and to squares, used +// LowPlyHistory is addressed by play and move's from and to squares, used // to improve move ordering near the root using LowPlyHistory = Stats; diff --git a/src/numa.h b/src/numa.h index 261b6005d..76d265af2 100644 --- a/src/numa.h +++ b/src/numa.h @@ -1346,7 +1346,7 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase { std::size_t get_discriminator(NumaIndex idx) const { const NumaConfig& cfg = get_numa_config(); const NumaConfig& cfg_sys = NumaConfig::from_system(false); - // as a descriminator, locate the hardware/system numadomain this cpuindex belongs to + // as a discriminator, locate the hardware/system numadomain this cpuindex belongs to CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx); diff --git a/tests/instrumented.py b/tests/instrumented.py index db5ec8e08..23de446ef 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -508,7 +508,7 @@ if __name__ == "__main__": framework = MiniTestFramework() - # Each test suite will be ran inside a temporary directory + # Each test suite will be run inside a temporary directory framework.run([TestCLI, TestInteractive, TestSyzygy]) EPD.delete_bench_epd() From 9e38023a8c2c67204f9a5d03eac5d5f00ea93d3f Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 31 Oct 2025 02:18:22 -0400 Subject: [PATCH 765/834] Simplify threat term in movepick Passed simplification STC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 71296 W: 18605 L: 18419 D: 34272 Ptnml(0-2): 250, 8374, 18183, 8622, 219 https://tests.stockfishchess.org/tests/view/690454c7ea4b268f1fac1bbe Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 240552 W: 61870 L: 61874 D: 116808 Ptnml(0-2): 113, 26300, 67460, 26284, 119 https://tests.stockfishchess.org/tests/view/69063956ea4b268f1fac1f66 closes https://github.com/official-stockfish/Stockfish/pull/6401 bench 2697501 --- src/movepick.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b5b02609e..0b18cf565 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -171,9 +171,8 @@ ExtMove* MovePicker::score(MoveList& ml) { // penalty for moving to a square threatened by a lesser piece // or bonus for escaping an attack by a lesser piece. - static constexpr int bonus[KING + 1] = {0, 0, 144, 144, 256, 517, 10000}; - int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); - m.value += bonus[pt] * v; + int v = threatByLesser[pt] & to ? -19 : 20 * bool(threatByLesser[pt] & from); + m.value += PieceValue[pt] * v; if (ply < LOW_PLY_HISTORY_SIZE) From 3f2405bf4e4c87d67e3939f2469419bcccb8a707 Mon Sep 17 00:00:00 2001 From: Torsten Hellwig Date: Sun, 9 Nov 2025 15:54:20 +0100 Subject: [PATCH 766/834] Print NEON before POPCNT in compilation settings Normally, Stockfish outputs the compilation settings from advanced to less advanced, e.g.: `AVX512ICL VNNI AVX512 BMI2 AVX2 SSE41 SSSE3 SSE2 POPCNT` With NEON, however, POPCNT is printed first, followed by the more advanced NEON options: `POPCNT NEON_DOTPROD` This PR places POPCNT behind the NEON options as well. closes https://github.com/official-stockfish/Stockfish/pull/6402 no functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 3bdde000f..d21497280 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -259,12 +259,12 @@ std::string compiler_info() { #if defined(USE_SSE2) compiler += " SSE2"; #endif - compiler += (HasPopCnt ? " POPCNT" : ""); #if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; #elif defined(USE_NEON) compiler += " NEON"; #endif + compiler += (HasPopCnt ? " POPCNT" : ""); #if !defined(NDEBUG) compiler += " DEBUG"; From 229bd1e2e350b4b67ed5a962ae99d939c0a27d29 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Tue, 11 Nov 2025 06:09:26 -0800 Subject: [PATCH 767/834] check for material key validity in tbprobe During recent work on threat inputs, there was a hard-to-debug crash in tbprobe caused by incorrectly computing st->materialKey. This PR adds correctness checking to pos_is_ok and a faster check in tbprobe that will actually run in CI (because pos_is_ok checking is disabled even in debug mode, for performance purposes). closes https://github.com/official-stockfish/Stockfish/pull/6410 No functional change --- src/position.cpp | 16 +++++++++++++--- src/position.h | 2 ++ src/syzygy/tbprobe.cpp | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 347bbcfc8..c34ceb400 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -336,8 +336,8 @@ void Position::set_check_info() const { // The function is only used when a new position is set up void Position::set_state() const { - st->key = st->materialKey = 0; - st->minorPieceKey = 0; + st->key = 0; + st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -375,10 +375,15 @@ void Position::set_state() const { st->key ^= Zobrist::side; st->key ^= Zobrist::castling[st->castlingRights]; + st->materialKey = compute_material_key(); +} +Key Position::compute_material_key() const { + Key k = 0; for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][8 + cnt]; + k ^= Zobrist::psq[pc][8 + cnt]; + return k; } @@ -1447,6 +1452,9 @@ void Position::flip() { } +bool Position::material_key_is_ok() const { return compute_material_key() == st->materialKey; } + + // Performs some consistency checks for the position object // and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. @@ -1496,6 +1504,8 @@ bool Position::pos_is_ok() const { assert(0 && "pos_is_ok: Castling"); } + assert(material_key_is_ok() && "pos_is_ok: materialKey"); + return true; } diff --git a/src/position.h b/src/position.h index c95697d19..711c4e444 100644 --- a/src/position.h +++ b/src/position.h @@ -169,6 +169,7 @@ class Position { // Position consistency check, for debugging bool pos_is_ok() const; + bool material_key_is_ok() const; void flip(); StateInfo* state() const; @@ -180,6 +181,7 @@ class Position { private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); + Key compute_material_key() const; void set_state() const; void set_check_info() const; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index cbf8dce5e..657866ba4 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1214,6 +1214,8 @@ template void* mapped(TBTable& e, const Position& pos) { static std::mutex mutex; + // Because TB is the only usage of materialKey, check it here in debug mode + assert(pos.material_key_is_ok()); // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). From df88db16c611516c494facadae2ec9ff0d0b6c06 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 14 Nov 2025 01:02:06 +0300 Subject: [PATCH 768/834] Simplify NMP reduction formula Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 178912 W: 46625 L: 46559 D: 85728 Ptnml(0-2): 540, 21167, 45975, 21235, 539 https://tests.stockfishchess.org/tests/view/69060677ea4b268f1fac1f25 Passed LTC: LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 140988 W: 36278 L: 36176 D: 68534 Ptnml(0-2): 82, 15520, 39215, 15568, 109 https://tests.stockfishchess.org/tests/view/6908bc32ea4b268f1fac288f closes https://github.com/official-stockfish/Stockfish/pull/6416 bench: 2959834 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d28ac5f27..b88c428e2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -872,7 +872,7 @@ Value Search::Worker::search( assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 6 + depth / 3 + improving; + Depth R = 7 + depth / 3; do_null_move(pos, st, ss); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); From 7b7a9485d6d36b246b8eb3f278570e52d929aa51 Mon Sep 17 00:00:00 2001 From: AliceRoselia <63040919+AliceRoselia@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:18:29 +0100 Subject: [PATCH 769/834] Simplify indexing Removes 1 function (from_to) from the Move type, change them to simpler raw. Remove the "and" use in the correction history structure calculation. Adjust the indexing. Passed simplification STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 42880 W: 11327 L: 11113 D: 20440 Ptnml(0-2): 161, 4852, 11212, 5042, 173 https://tests.stockfishchess.org/tests/view/690593b8ea4b268f1fac1e8a Passed simplification LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 43560 W: 11276 L: 11079 D: 21205 Ptnml(0-2): 17, 4679, 12197, 4864, 23 https://tests.stockfishchess.org/tests/view/6906f819ea4b268f1fac216b closes https://github.com/official-stockfish/Stockfish/pull/6391 Bench: 2523092 --- src/history.h | 27 +++++++++++---------------- src/movepick.cpp | 8 ++++---- src/search.cpp | 12 ++++++------ src/types.h | 2 -- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/history.h b/src/history.h index 445d54181..a605ae417 100644 --- a/src/history.h +++ b/src/history.h @@ -33,32 +33,28 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 +constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits::max() + 1; constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, +static_assert((UINT_16_HISTORY_SIZE & (UINT_16_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); inline int pawn_history_index(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } -inline int pawn_correction_history_index(const Position& pos) { - return pos.pawn_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t pawn_correction_history_index(const Position& pos) { return pos.pawn_key(); } -inline int minor_piece_index(const Position& pos) { - return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t minor_piece_index(const Position& pos) { return pos.minor_piece_key(); } template -inline int non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); +inline uint16_t non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c); } // StatsEntry is the container of various numerical statistics. We use a class @@ -102,12 +98,11 @@ using Stats = MultiArray, Sizes...>; // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, // see https://www.chessprogramming.org/Butterfly_Boards -using ButterflyHistory = Stats; +using ButterflyHistory = Stats; // LowPlyHistory is addressed by play and move's from and to squares, used // to improve move ordering near the root -using LowPlyHistory = - Stats; +using LowPlyHistory = Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; @@ -139,7 +134,7 @@ namespace Detail { template struct CorrHistTypedef { - using type = Stats; + using type = Stats; }; template<> @@ -155,7 +150,7 @@ struct CorrHistTypedef { template<> struct CorrHistTypedef { using type = - Stats; + Stats; }; } diff --git a/src/movepick.cpp b/src/movepick.cpp index 0b18cf565..7de11fa1f 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -158,7 +158,7 @@ ExtMove* MovePicker::score(MoveList& ml) { else if constexpr (Type == QUIETS) { // histories - m.value = 2 * (*mainHistory)[us][m.from_to()]; + m.value = 2 * (*mainHistory)[us][m.raw()]; m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -176,7 +176,7 @@ ExtMove* MovePicker::score(MoveList& ml) { if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); + m.value += 8 * (*lowPlyHistory)[ply][m.raw()] / (1 + ply); } else // Type == EVASIONS @@ -185,9 +185,9 @@ ExtMove* MovePicker::score(MoveList& ml) { m.value = PieceValue[capturedPiece] + (1 << 28); else { - m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; + m.value = (*mainHistory)[us][m.raw()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += (*lowPlyHistory)[ply][m.from_to()]; + m.value += (*lowPlyHistory)[ply][m.raw()]; } } } diff --git a/src/search.cpp b/src/search.cpp index b88c428e2..7404b939c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -824,7 +824,7 @@ Value Search::Worker::search( if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << evalDiff * 9; + mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; @@ -1066,7 +1066,7 @@ moves_loop: // When in check, search starts here if (history < -4312 * depth) continue; - history += 76 * mainHistory[us][move.from_to()] / 32; + history += 76 * mainHistory[us][move.raw()] / 32; // (*Scaler): Generally, a lower divisor scales well lmrDepth += history / 3220; @@ -1196,7 +1196,7 @@ moves_loop: // When in check, search starts here ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else - ss->statScore = 2 * mainHistory[us][move.from_to()] + ss->statScore = 2 * mainHistory[us][move.raw()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()]; @@ -1414,7 +1414,7 @@ moves_loop: // When in check, search starts here update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, scaledBonus * 400 / 32768); - mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 220 / 32768; + mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 220 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] @@ -1862,10 +1862,10 @@ void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { Color us = pos.side_to_move(); - workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort + workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 761 / 1024; + workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 761 / 1024; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); diff --git a/src/types.h b/src/types.h index 46aa16a03..a2171b638 100644 --- a/src/types.h +++ b/src/types.h @@ -448,8 +448,6 @@ class Move { return Square(data & 0x3F); } - constexpr int from_to() const { return data & 0xFFF; } - constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } From bd82b9e01f9e5280b5e3771891e4c471d16cfc08 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:19:15 -0800 Subject: [PATCH 770/834] Cleanup, fix style issues use naming convention for variables, functions, constants closes https://github.com/official-stockfish/Stockfish/pull/6416 no functional change --- src/nnue/nnue_accumulator.cpp | 37 ++++++++++++++++---------------- src/position.cpp | 40 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 47f09afce..fa059463e 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -636,25 +636,26 @@ void update_accumulator_incremental( (target_state.template acc()).computed[Perspective] = true; } -Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) { +Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { #if defined(USE_AVX512) || defined(USE_AVX2) static_assert(sizeof(Piece) == 1); - Bitboard same_bb = 0; + Bitboard sameBB = 0; + for (int i = 0; i < 64; i += 32) { - const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(old + i)); - const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(new_ + i)); - const __m256i cmp_equal = _mm256_cmpeq_epi8(old_v, new_v); - const std::uint32_t equal_mask = _mm256_movemask_epi8(cmp_equal); - same_bb |= static_cast(equal_mask) << i; + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(oldPieces + i)); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(newPieces + i)); + const __m256i cmpEqual = _mm256_cmpeq_epi8(old_v, new_v); + const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual); + sameBB |= static_cast(equalMask) << i; } - return ~same_bb; + return ~sameBB; #else Bitboard changed = 0; + for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq) - { - changed |= static_cast(old[sq] != new_[sq]) << sq; - } + changed |= static_cast(oldPieces[sq] != newPieces[sq]) << sq; + return changed; #endif } @@ -671,18 +672,18 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat auto& entry = cache[ksq][Perspective]; PSQFeatureSet::IndexList removed, added; - const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array().data()); - Bitboard removed_bb = changed_bb & entry.pieceBB; - Bitboard added_bb = changed_bb & pos.pieces(); + const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); + Bitboard removedBB = changedBB & entry.pieceBB; + Bitboard addedBB = changedBB & pos.pieces(); - while (removed_bb) + while (removedBB) { - Square sq = pop_lsb(removed_bb); + Square sq = pop_lsb(removedBB); removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); } - while (added_bb) + while (addedBB) { - Square sq = pop_lsb(added_bb); + Square sq = pop_lsb(addedBB); added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); } diff --git a/src/position.cpp b/src/position.cpp index c34ceb400..8993c2406 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1096,13 +1096,13 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) while (threatened) { - Square threatened_sq = pop_lsb(threatened); - Piece threatened_pc = piece_on(threatened_sq); + Square threatenedSq = pop_lsb(threatened); + Piece threatenedPc = piece_on(threatenedSq); - assert(threatened_sq != s); - assert(threatened_pc); + assert(threatenedSq != s); + assert(threatenedPc); - add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); + add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); } Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); @@ -1111,30 +1111,30 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { while (sliders) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); + Square sliderSq = pop_lsb(sliders); + Piece slider = piece_on(sliderSq); - const Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; + const Bitboard ray = RayPassBB[sliderSq][s] & ~BetweenBB[sliderSq][s]; const Bitboard discovered = ray & qAttacks & occupied; assert(!more_than_one(discovered)); if (discovered) { - const Square threatened_sq = lsb(discovered); - const Piece threatened_pc = piece_on(threatened_sq); - add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + const Square threatenedSq = lsb(discovered); + const Piece threatenedPc = piece_on(threatenedSq); + add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); } - add_dirty_threat(dts, slider, pc, slider_sq, s); + add_dirty_threat(dts, slider, pc, sliderSq, s); } } else { while (sliders) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); - add_dirty_threat(dts, slider, pc, slider_sq, s); + Square sliderSq = pop_lsb(sliders); + Piece slider = piece_on(sliderSq); + add_dirty_threat(dts, slider, pc, sliderSq, s); } } @@ -1144,13 +1144,13 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) while (incoming_threats) { - Square src_sq = pop_lsb(incoming_threats); - Piece src_pc = piece_on(src_sq); + Square srcSq = pop_lsb(incoming_threats); + Piece srcPc = piece_on(srcSq); - assert(src_sq != s); - assert(src_pc != NO_PIECE); + assert(srcSq != s); + assert(srcPc != NO_PIECE); - add_dirty_threat(dts, src_pc, pc, src_sq, s); + add_dirty_threat(dts, srcPc, pc, srcSq, s); } } From a191791f46735817e76d1ba29ec81a5304e16430 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 13 Nov 2025 22:53:48 -0500 Subject: [PATCH 771/834] Clean up code and comments closes https://github.com/official-stockfish/Stockfish/pull/6416 No functional change --- src/search.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7404b939c..e4ef2d42e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -834,9 +834,12 @@ Value Search::Worker::search( // bigger than the previous static evaluation at our turn (if we were in // check at our previous move we go back until we weren't in check) and is // false otherwise. The improving flag is used in various pruning heuristics. + // Similarly, opponentWorsening is true if our static evaluation is better + // for us than at the last ply. improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; + // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) @@ -856,7 +859,7 @@ Value Search::Worker::search( return futilityMult * d // - 2094 * improving * futilityMult / 1024 // - - 1324 * opponentWorsening * futilityMult / 4096 // + - 331 * opponentWorsening * futilityMult / 1024 // + std::abs(correctionValue) / 158105; }; @@ -904,7 +907,7 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. - // (*Scaler) Especially if they make IIR less aggressive. + // (*Scaler) Making IIR more aggressive scales poorly. if (!allNode && depth >= 6 && !ttData.move && priorReduction <= 3) depth--; @@ -1018,12 +1021,11 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); // Increase reduction for ttPv nodes (*Scaler) - // Smaller or even negative value is better for short time controls - // Bigger value is better for long time controls + // Larger values scale well if (ss->ttPv) r += 946; - // Step 14. Pruning at shallow depth. + // Step 14. Pruning at shallow depths. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { @@ -1068,7 +1070,7 @@ moves_loop: // When in check, search starts here history += 76 * mainHistory[us][move.raw()] / 32; - // (*Scaler): Generally, a lower divisor scales well + // (*Scaler): Generally, lower divisors scales well lmrDepth += history / 3220; Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth @@ -1076,7 +1078,7 @@ moves_loop: // When in check, search starts here // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning - // scales well with respect to time and threads + // scales well if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) @@ -1218,8 +1220,7 @@ moves_loop: // When in check, search starts here ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high - // (*Scaler) Usually doing more shallower searches - // doesn't scale well to longer TCs + // (*Scaler) Shallower searches here don't scale well if (value > alpha) { // Adjust full-depth search based on LMR results - if the result was @@ -1347,7 +1348,7 @@ moves_loop: // When in check, search starts here if (value >= beta) { - // (*Scaler) Especially if they make cutoffCnt increment more often. + // (*Scaler) Infrequent and small updates scale well ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; @@ -1450,10 +1451,9 @@ moves_loop: // When in check, search starts here // Adjust correction history if the best move is not a capture // and the error direction matches whether we are above/below bounds. if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) - && (bestValue < ss->staticEval) == !bestMove) + && (bestValue > ss->staticEval) == bool(bestMove)) { - auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth - / (8 + 2 * (bestValue > ss->staticEval)), + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (bestMove ? 10 : 8), -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); update_correction_history(pos, ss, *this, bonus); } From db824e26bebd08c70d1ccfb03f2c4ef06af82fef Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:46:47 -0800 Subject: [PATCH 772/834] Pass accumulator caches by reference closes https://github.com/official-stockfish/Stockfish/pull/6416 No functional change --- src/evaluate.cpp | 8 ++++---- src/nnue/network.cpp | 4 ++-- src/nnue/network.h | 4 ++-- src/nnue/nnue_feature_transformer.h | 4 ++-- src/nnue/nnue_misc.cpp | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 23bc70d0e..4bbbcaac6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,15 +59,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) - : networks.big.evaluate(pos, accumulators, &caches.big); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small) + : networks.big.evaluate(pos, accumulators, caches.big); Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (std::abs(nnue) < 236)) { - std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); nnue = (125 * psqt + 131 * positional) / 128; smallNet = false; } @@ -107,7 +107,7 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches->big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches->big); Value v = psqt + positional; v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index b91763f06..a4d464df0 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -172,7 +172,7 @@ template NetworkOutput Network::evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; @@ -234,7 +234,7 @@ template NnueEvalTrace Network::trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; diff --git a/src/nnue/network.h b/src/nnue/network.h index c701ac6f5..ba8f469f7 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -76,13 +76,13 @@ class Network { NetworkOutput evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; private: void load_user_net(const std::string&, const std::string&); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 5ad2d3371..1f328fff4 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -264,12 +264,12 @@ class FeatureTransformer { // Convert input features std::int32_t transform(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache, + AccumulatorCaches::Cache& cache, OutputType* output, int bucket) const { using namespace SIMD; - accumulatorStack.evaluate(pos, *this, *cache); + accumulatorStack.evaluate(pos, *this, cache); const auto& accumulatorState = accumulatorStack.latest(); const auto& threatAccumulatorState = accumulatorStack.latest(); diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 957e3453f..220140e5e 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -124,7 +124,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -140,7 +140,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat pos.remove_piece(sq); accumulators->reset(); - std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; @@ -157,7 +157,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << '\n'; accumulators->reset(); - auto t = networks.big.trace_evaluate(pos, *accumulators, &caches.big); + auto t = networks.big.trace_evaluate(pos, *accumulators, caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl From 4b71d8e20293bdc5db5dbb07ae75f3ef65497642 Mon Sep 17 00:00:00 2001 From: Robert Nurnberg Date: Fri, 14 Nov 2025 10:07:14 +0100 Subject: [PATCH 773/834] Allow time checking after each DTZ probe This PR allows for time checking within Tablebases::rank_root_moves(). The principal application for now is syzygy_extend_pv(). In the past, Stockfish suffered some time losses at e.g. TCEC when the HDD with the DTZ tables was too slow for the chosen move overhead setting, see #5894. ``` > ./fastchess -engine name="patch" cmd=./stockfish.patch -engine name="master" cmd=./stockfish.master -each tc=10+0.1 option.SyzygyPath=/disk1/syzygy/3-4-5-6/WDL:/disk2/syzygy/3-4-5-6/DTZ -draw movenumber=34 movecount=8 score=20 -openings file=UHO_Lichess_4852_v1.epd format=epd -rounds 50 -repeat -concurrency 16 ... Results of patch vs master (10+0.1, 1t, 16MB, UHO_Lichess_4852_v1.epd): Elo: 59.64 +/- 35.35, nElo: 117.61 +/- 68.10 LOS: 99.96 %, DrawRatio: 56.00 %, PairsRatio: 4.50 Games: 100, Wins: 34, Losses: 17, Draws: 49, Points: 58.5 (58.50 %) Ptnml(0-2): [0, 4, 28, 15, 3], WL/DD Ratio: 0.87 -------------------------------------------------- Player: master Timeouts: 19 Crashed: 0 Finished match ``` closes https://github.com/official-stockfish/Stockfish/pull/6422 No functional change --- src/search.cpp | 12 +++++++----- src/syzygy/tbprobe.cpp | 27 +++++++++++++++------------ src/syzygy/tbprobe.h | 17 ++++++++++++----- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e4ef2d42e..9fb7b702f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -857,8 +857,8 @@ Value Search::Worker::search( auto futility_margin = [&](Depth d) { Value futilityMult = 91 - 21 * !ss->ttHit; - return futilityMult * d // - - 2094 * improving * futilityMult / 1024 // + return futilityMult * d // + - 2094 * improving * futilityMult / 1024 // - 331 * opponentWorsening * futilityMult / 1024 // + std::abs(correctionValue) / 158105; }; @@ -1980,8 +1980,9 @@ void syzygy_extend_pv(const OptionsMap& options, for (const auto& m : MoveList(pos)) legalMoves.emplace_back(m); - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves); - RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, false, time_abort); + RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); if (legalMoves[0].tbRank != rm.tbRank) break; @@ -2040,7 +2041,8 @@ void syzygy_extend_pv(const OptionsMap& options, [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); // The winning side tries to minimize DTZ, the losing side maximizes it - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, true, time_abort); // If DTZ is not available we might not find a mate, so we bail out if (!config.rootInTB || config.cardinality > 0) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 657866ba4..c8ff60739 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1594,10 +1594,11 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, - Search::RootMoves& rootMoves, - bool rule50, - bool rankDTZ) { +bool Tablebases::root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort) { ProbeState result = OK; StateInfo st; @@ -1642,7 +1643,7 @@ bool Tablebases::root_probe(Position& pos, pos.undo_move(m.pv[0]); - if (result == FAIL) + if (time_abort() || result == FAIL) return false; // Better moves are ranked higher. Certain wins are ranked equally. @@ -1707,10 +1708,11 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo return true; } -Config Tablebases::rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ) { +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ, + const std::function& time_abort) { Config config; if (rootMoves.empty()) @@ -1733,10 +1735,11 @@ Config Tablebases::rank_root_moves(const OptionsMap& options, if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { - // Rank moves using DTZ tables - config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ); + // Rank moves using DTZ tables, bail out if time_abort flags zeitnot + config.rootInTB = + root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort); - if (!config.rootInTB) + if (!config.rootInTB && !time_abort()) { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index c34338fe3..4a6c3b763 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,6 +19,7 @@ #ifndef TBPROBE_H #define TBPROBE_H +#include #include #include @@ -66,12 +67,18 @@ extern int MaxCardinality; void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ); +bool root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); -Config rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ = false); +Config rank_root_moves( + const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ = false, + const std::function& time_abort = []() { return false; }); } // namespace Stockfish::Tablebases From 1d504b927fde7c3227a0c32f26fd49810c0fcd55 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:57:58 -0800 Subject: [PATCH 774/834] Fix undefined behavior C++ standard does not define `uint8_t` as an allowed pointer alias type. Despite `uint8_t` being `unsigned char` on most platforms, this is not enforced by the standard. https://eel.is/c++draft/basic.lval#11 https://stackoverflow.com/a/16138470 closes https://github.com/official-stockfish/Stockfish/pull/6420 No functional change --- src/nnue/nnue_accumulator.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 341960b30..c0a912f58 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -77,10 +77,9 @@ struct AccumulatorCaches { // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top void clear(const std::array& biases) { - accumulation = biases; - std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, - sizeof(Entry) - offsetof(Entry, psqtAccumulation)); + std::memset(reinterpret_cast(this) + offsetof(Entry, psqtAccumulation), + 0, sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; From b083049fe0c3671d20d195aa8dea72687cd6e44c Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Mon, 17 Nov 2025 13:24:05 +0100 Subject: [PATCH 775/834] Use non-locking operations for nodes count and tbHits fetch_add and friends must produce a locking instruction because they guarantee consistency in the presence of multiple writers. Because only one thread ever writes to nodes and tbHits, we may use relaxed load and store to emit regular loads and stores (on both x86-64 and arm64), while avoiding undefined behavior and miscounting nodes. Credit must go to Arseniy Surkov of Reckless (codedeliveryservice) for uncovering this performance gotcha. failed yellow as a gainer on fishtest: LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 219616 W: 57165 L: 57105 D: 105346 Ptnml(0-2): 732, 24277, 59720, 24357, 722 https://tests.stockfishchess.org/tests/view/69086abfea4b268f1fac2809 potential small speedup on large core system: ``` ==== master ==== 1 Nodes/second : 294269736 2 Nodes/second : 295217629 Average (over 2): 294743682 ==== patch ==== 1 Nodes/second : 299003603 2 Nodes/second : 298111320 Average (over 2): 298557461 ``` closes https://github.com/official-stockfish/Stockfish/pull/6392 No functional change --- src/search.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9fb7b702f..53ff9f5d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -529,7 +529,8 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac void Search::Worker::do_move( Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { bool capture = pos.capture_stage(move); - nodes.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); @@ -749,7 +750,8 @@ Value Search::Worker::search( if (err != TB::ProbeState::FAIL) { - tbHits.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); int drawScore = tbConfig.useRule50 ? 1 : 0; From 2084d94266f76f1ab5631f32708916a4d5cca246 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Thu, 13 Nov 2025 11:45:45 -0800 Subject: [PATCH 776/834] inline make_index() and avoid templating perspective combination of two patches. pass perspective as argument passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 52832 W: 13725 L: 13528 D: 25579 Ptnml(0-2): 154, 5756, 14412, 5927, 167 https://tests.stockfishchess.org/tests/view/69162e307ca8781852331c6a inline make_index passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 68768 W: 17786 L: 17607 D: 33375 Ptnml(0-2): 187, 7591, 18694, 7680, 232 https://tests.stockfishchess.org/tests/view/6916859e7ca8781852331d36 closes https://github.com/official-stockfish/Stockfish/pull/6429 No functional change --- src/misc.h | 9 + src/nnue/features/full_threats.cpp | 113 +++++-------- src/nnue/features/full_threats.h | 15 +- src/nnue/features/half_ka_v2_hm.cpp | 47 ++--- src/nnue/features/half_ka_v2_hm.h | 18 +- src/nnue/nnue_accumulator.cpp | 254 +++++++++++++++------------- src/nnue/nnue_accumulator.h | 19 ++- 7 files changed, 231 insertions(+), 244 deletions(-) diff --git a/src/misc.h b/src/misc.h index fce6f17df..66c03b806 100644 --- a/src/misc.h +++ b/src/misc.h @@ -412,6 +412,15 @@ void move_to_front(std::vector& vec, Predicate pred) { } } +#if defined(__GNUC__) + #define sf_always_inline __attribute__((always_inline)) +#elif defined(__MSVC) + #define sf_always_inline __forceinline +#else + // do nothign for other compilers + #define sf_always_inline +#endif + #if defined(__GNUC__) && !defined(__clang__) #if __GNUC__ >= 13 #define sf_assume(cond) __attribute__((assume(cond))) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 994d2160c..122478dc6 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -58,35 +58,6 @@ constexpr std::array AllPieces = { PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] -namespace { - -template -IndexType make_index_with_orientation( - Piece attacker, Square from, Square to, Piece attacked, int orientation) { - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); - - if constexpr (Perspective == BLACK) - { - attacker = ~attacker; - attacked = ~attacked; - } - - const auto piecePairData = index_lut1[attacker][attacked]; - - const bool less_than = static_cast(from) < static_cast(to); - if ((piecePairData.excluded_pair_info() + less_than) & 2) - return FullThreats::Dimensions; - - const IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != FullThreats::Dimensions); - return index; -} - -} // namespace - static void init_index_luts() { for (Piece attacker : AllPieces) { @@ -155,27 +126,49 @@ void init_threat_offsets() { } // Index of a feature for a given king position and another piece on some square -template -IndexType -FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - return make_index_with_orientation(attacker, from, to, attacked, - OrientTBL[Perspective][ksq]); +inline sf_always_inline +IndexType FullThreats::make_index(Color perspective, + Piece attacker, + Square from, + Square to, + Piece attacked, + Square ksq) { + const int orientation = OrientTBL[perspective][ksq]; + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); + + std::int8_t swap = 8 * perspective; + attacker = Piece(attacker ^ swap); + attacked = Piece(attacked ^ swap); + + const auto piecePairData = index_lut1[attacker][attacked]; + + const bool less_than = static_cast(from) < static_cast(to); + if ((piecePairData.excluded_pair_info() + less_than) & 2) + return FullThreats::Dimensions; + + const IndexType index = piecePairData.feature_index_base() + offsets[attacker][from] + + index_lut2[attacker][from][to]; + + sf_assume(index != FullThreats::Dimensions); + return index; } // Get a list of indices for active features in ascending order -template -void FullThreats::append_active_indices(const Position& pos, IndexList& active) { + +void FullThreats::append_active_indices(Color perspective, + const Position& pos, + IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(Perspective); - const int orientation = OrientTBL[Perspective][ksq]; + Square ksq = pos.square(perspective); Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { for (PieceType pt = PAWN; pt <= KING; ++pt) { - Color c = order[Perspective][color]; + Color c = order[perspective][color]; Piece attacker = make_piece(c, pt); Bitboard bb = pos.pieces(c, pt); @@ -193,8 +186,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -205,8 +197,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -223,8 +214,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index( + perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -235,23 +226,15 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) } } -// Explicit template instantiations -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); - // Get a list of indices for recently changed features -template -void FullThreats::append_changed_indices(Square ksq, + +void FullThreats::append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, FusedUpdateData* fusedData, bool first) { - const int orientation = OrientTBL[Perspective][ksq]; for (const auto& dirty : diff.list) { @@ -292,28 +275,14 @@ void FullThreats::append_changed_indices(Square ksq, } } - const IndexType index = - make_index_with_orientation(attacker, from, to, attacked, orientation); + const IndexType index = make_index(perspective, + attacker, from, to, attacked, ksq); if (index < Dimensions) (add ? added : removed).push_back(index); } } -// Explicit template instantiations -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); - bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { return perspective == diff.us && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index 458b04dd1..d5c91d8c2 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -89,16 +89,19 @@ class FullThreats { using IndexList = ValueList; using DiffType = DirtyThreats; - template - static IndexType make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); + static IndexType make_index(Color perspective, + Piece attkr, + Square from, + Square to, + Piece attkd, + Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - template - static void append_changed_indices(Square ksq, + static void append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index f652ba0b8..5a6f610cf 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -28,58 +28,45 @@ namespace Stockfish::Eval::NNUE::Features { // Index of a feature for a given king position and another piece on some square -template -IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - const IndexType flip = 56 * Perspective; - return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[Perspective][pc] + +IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) { + const IndexType flip = 56 * perspective; + return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[perspective][pc] + KingBuckets[int(ksq) ^ flip]; } // Get a list of indices for active features -template -void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { - Square ksq = pos.square(Perspective); + +void HalfKAv2_hm::append_active_indices(Color perspective, + const Position& pos, + IndexList& active) { + Square ksq = pos.square(perspective); Bitboard bb = pos.pieces(); while (bb) { Square s = pop_lsb(bb); - active.push_back(make_index(s, pos.piece_on(s), ksq)); + active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); } } -// Explicit template instantiations -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); - // Get a list of indices for recently changed features -template -void HalfKAv2_hm::append_changed_indices(Square ksq, + +void HalfKAv2_hm::append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) { - removed.push_back(make_index(diff.from, diff.pc, ksq)); + removed.push_back(make_index(perspective, diff.from, diff.pc, ksq)); if (diff.to != SQ_NONE) - added.push_back(make_index(diff.to, diff.pc, ksq)); + added.push_back(make_index(perspective, diff.to, diff.pc, ksq)); if (diff.remove_sq != SQ_NONE) - removed.push_back(make_index(diff.remove_sq, diff.remove_pc, ksq)); + removed.push_back(make_index(perspective, diff.remove_sq, diff.remove_pc, ksq)); if (diff.add_sq != SQ_NONE) - added.push_back(make_index(diff.add_sq, diff.add_pc, ksq)); + added.push_back(make_index(perspective, diff.add_sq, diff.add_pc, ksq)); } -// Explicit template instantiations -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); - bool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) { return diff.pc == make_piece(perspective, KING); } diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index e695b273a..e9448f838 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -107,17 +107,21 @@ class HalfKAv2_hm { using DiffType = DirtyPiece; // Index of a feature for a given king position and another piece on some square - template - static IndexType make_index(Square s, Piece pc, Square ksq); + + static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + + static void append_active_indices(Color perspective, + const Position& pos, + IndexList& active); // Get a list of indices for recently changed features - template - static void - append_changed_indices(Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); + static void append_changed_indices(Color perspective, + Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index fa059463e..3d99b8063 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -40,39 +40,43 @@ using namespace SIMD; namespace { -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed); -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2); -template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed); -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState); } @@ -143,71 +147,73 @@ void AccumulatorStack::evaluate(const Position& pos, AccumulatorCaches::Cache& cache) noexcept { constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); } -template -void AccumulatorStack::evaluate_side(const Position& pos, +template +void AccumulatorStack::evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept { const auto last_usable_accum = - find_last_usable_accumulator(); + find_last_usable_accumulator(perspective); if ((accumulators()[last_usable_accum].template acc()) - .computed[Perspective]) - forward_update_incremental(pos, featureTransformer, - last_usable_accum); + .computed[perspective]) + forward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); else { if constexpr (std::is_same_v) - update_accumulator_refresh_cache(featureTransformer, pos, - mut_latest(), cache); + update_accumulator_refresh_cache(perspective, featureTransformer, pos, + mut_latest(), cache); else - update_threats_accumulator_full(featureTransformer, pos, - mut_latest()); + update_threats_accumulator_full(perspective, featureTransformer, pos, + mut_latest()); - backward_update_incremental(pos, featureTransformer, - last_usable_accum); + backward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); } } // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator // state just before a change that requires full refresh. -template -std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { +template +std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) const noexcept { for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) { - if ((accumulators()[curr_idx].template acc()).computed[Perspective]) + if ((accumulators()[curr_idx].template acc()).computed[perspective]) return curr_idx; - if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, Perspective)) + if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, perspective)) return curr_idx; } return 0; } -template +template void AccumulatorStack::forward_update_incremental( + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { assert(begin < accumulators().size()); - assert((accumulators()[begin].template acc()).computed[Perspective]); + assert((accumulators()[begin].template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::size_t next = begin + 1; next < size; next++) { @@ -223,9 +229,8 @@ void AccumulatorStack::forward_update_incremental( if (dp2.remove_sq != SQ_NONE && (accumulators[next].diff.threateningSqs & square_bb(dp2.remove_sq))) { - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1], - dp2); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1], dp2); next++; continue; } @@ -237,8 +242,8 @@ void AccumulatorStack::forward_update_incremental( { const Square captureSq = dp1.to; dp1.to = dp2.remove_sq = SQ_NONE; - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1]); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); dp1.to = dp2.remove_sq = captureSq; next++; continue; @@ -246,32 +251,33 @@ void AccumulatorStack::forward_update_incremental( } } - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next - 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next - 1]); } - assert((latest().acc()).computed[Perspective]); + assert((latest().acc()).computed[perspective]); } -template -void AccumulatorStack::backward_update_incremental( +template +void AccumulatorStack::backward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept { assert(end < accumulators().size()); assert(end < size); - assert((latest().template acc()).computed[Perspective]); + assert((latest().template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--) - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next + 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next + 1]); - assert((accumulators()[end].template acc()).computed[Perspective]); + assert((accumulators()[end].template acc()).computed[perspective]); } // Explicit template instantiations @@ -304,15 +310,18 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const.. vecIn[i], reinterpret_cast(rows)[i]...); } -template +template struct AccumulatorUpdateContext { + Color perspective; const FeatureTransformer& featureTransformer; const AccumulatorState& from; AccumulatorState& to; - AccumulatorUpdateContext(const FeatureTransformer& ft, + AccumulatorUpdateContext(Color persp, + const FeatureTransformer& ft, const AccumulatorState& accF, AccumulatorState& accT) noexcept : + perspective{persp}, featureTransformer{ft}, from{accF}, to{accT} {} @@ -330,22 +339,22 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.template acc()).accumulation[Perspective], - (to.template acc()).accumulation[Perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[perspective], + (to.template acc()).accumulation[perspective], to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[Perspective], - (to.template acc()).psqtAccumulation[Perspective], + (from.template acc()).psqtAccumulation[perspective], + (to.template acc()).psqtAccumulation[perspective], to_psqt_weight_vector(indices)...); } void apply(const typename FeatureSet::IndexList& added, const typename FeatureSet::IndexList& removed) { - const auto fromAcc = from.template acc().accumulation[Perspective]; - const auto toAcc = to.template acc().accumulation[Perspective]; + const auto fromAcc = from.template acc().accumulation[perspective]; + const auto toAcc = to.template acc().accumulation[perspective]; - const auto fromPsqtAcc = from.template acc().psqtAccumulation[Perspective]; - const auto toPsqtAcc = to.template acc().psqtAccumulation[Perspective]; + const auto fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; + const auto toPsqtAcc = to.template acc().psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; @@ -469,31 +478,33 @@ struct AccumulatorUpdateContext { } }; -template -auto make_accumulator_update_context(const FeatureTransformer& featureTransformer, +template +auto make_accumulator_update_context(Color perspective, + const FeatureTransformer& featureTransformer, const AccumulatorState& accumulatorFrom, AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - featureTransformer, accumulatorFrom, accumulatorTo}; + return AccumulatorUpdateContext{ + perspective, featureTransformer, accumulatorFrom, accumulatorTo}; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed) { - assert(computed.acc().computed[Perspective]); - assert(!middle_state.acc().computed[Perspective]); - assert(!target_state.acc().computed[Perspective]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); PSQFeatureSet::IndexList removed, added; - PSQFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added); // you can't capture a piece that was just involved in castling since the rook ends up // in a square that the king passed assert(added.size() < 2); - PSQFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); assert(added.size() == 1); assert(removed.size() == 2 || removed.size() == 3); @@ -505,7 +516,7 @@ void double_inc_update(const FeatureTransformer& f sf_assume(removed.size() == 2 || removed.size() == 3); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if (removed.size() == 2) { @@ -517,51 +528,52 @@ void double_inc_update(const FeatureTransformer& f removed[2]); } - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, +const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2) { - assert(computed.acc().computed[Perspective]); - assert(!middle_state.acc().computed[Perspective]); - assert(!target_state.acc().computed[Perspective]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); ThreatFeatureSet::FusedUpdateData fusedData; fusedData.dp2removed = dp2.remove_sq; ThreatFeatureSet::IndexList removed, added; - ThreatFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added, - &fusedData, true); - ThreatFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added, - &fusedData, false); + ThreatFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added, + &fusedData, true); + ThreatFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added, + &fusedData, false); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); updateContext.apply(added, removed); - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed) { - assert((computed.template acc()).computed[Perspective]); - assert(!(target_state.template acc()).computed[Perspective]); + assert((computed.template acc()).computed[perspective]); + assert(!(target_state.template acc()).computed[perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -571,29 +583,27 @@ void update_accumulator_incremental( // is 2, since we are incrementally updating one move at a time. typename FeatureSet::IndexList removed, added; if constexpr (Forward) - FeatureSet::template append_changed_indices(ksq, target_state.diff, removed, - added); + FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); else - FeatureSet::template append_changed_indices(ksq, computed.diff, added, - removed); + FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed); if (!added.size() && !removed.size()) { auto& targetAcc = target_state.template acc(); const auto& sourceAcc = computed.template acc(); - std::memcpy(targetAcc.accumulation[Perspective], sourceAcc.accumulation[Perspective], - sizeof(targetAcc.accumulation[Perspective])); - std::memcpy(targetAcc.psqtAccumulation[Perspective], - sourceAcc.psqtAccumulation[Perspective], - sizeof(targetAcc.psqtAccumulation[Perspective])); + std::memcpy(targetAcc.accumulation[perspective], sourceAcc.accumulation[perspective], + sizeof(targetAcc.accumulation[perspective])); + std::memcpy(targetAcc.psqtAccumulation[perspective], + sourceAcc.psqtAccumulation[perspective], + sizeof(targetAcc.psqtAccumulation[perspective])); - targetAcc.computed[Perspective] = true; + targetAcc.computed[perspective] = true; return; } auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if constexpr (std::is_same_v) updateContext.apply(added, removed); @@ -633,7 +643,7 @@ void update_accumulator_incremental( } } - (target_state.template acc()).computed[Perspective] = true; + (target_state.template acc()).computed[perspective] = true; } Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { @@ -660,16 +670,17 @@ Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPie #endif } -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache) { using Tiling [[maybe_unused]] = SIMDTiling; - const Square ksq = pos.square(Perspective); - auto& entry = cache[ksq][Perspective]; + const Square ksq = pos.square(perspective); + auto& entry = cache[ksq][perspective]; PSQFeatureSet::IndexList removed, added; const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); @@ -679,19 +690,19 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat while (removedBB) { Square sq = pop_lsb(removedBB); - removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); + removed.push_back(PSQFeatureSet::make_index(perspective, sq, entry.pieces[sq], ksq)); } while (addedBB) { Square sq = pop_lsb(addedBB); - added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); + added.push_back(PSQFeatureSet::make_index(perspective, sq, pos.piece_on(sq), ksq)); } entry.pieceBB = pos.pieces(); std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -700,7 +711,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) @@ -747,7 +758,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); auto* entryTilePsqt = reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); @@ -805,25 +816,26 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation.data(), + std::memcpy(accumulator.accumulation[perspective], entry.accumulation.data(), sizeof(BiasType) * Dimensions); - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation.data(), + std::memcpy(accumulator.psqtAccumulation[perspective], entry.psqtAccumulation.data(), sizeof(int32_t) * PSQTBuckets); #endif } -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState) { using Tiling [[maybe_unused]] = SIMDTiling; ThreatFeatureSet::IndexList active; - ThreatFeatureSet::append_active_indices(pos, active); + ThreatFeatureSet::append_active_indices(perspective, pos, active); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -832,7 +844,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_zero(); @@ -865,7 +877,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); @@ -888,21 +900,21 @@ void update_threats_accumulator_full(const FeatureTransformer& featu #else for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] = 0; + accumulator.accumulation[perspective][j] = 0; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; + accumulator.psqtAccumulation[perspective][k] = 0; for (const auto index : active) { const IndexType offset = Dimensions * index; for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] += + accumulator.accumulation[perspective][j] += featureTransformer.threatWeights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += + accumulator.psqtAccumulation[perspective][k] += featureTransformer.threatPsqtWeights[index * PSQTBuckets + k]; } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index c0a912f58..181412b43 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -176,21 +176,24 @@ class AccumulatorStack { template [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; - template - void evaluate_side(const Position& pos, + template + void evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; - template - [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + template + [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept; - template - void forward_update_incremental(const Position& pos, + template + void forward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept; - template - void backward_update_incremental(const Position& pos, + template + void backward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; From 4b8fffe3b3cde2f351aff0afb0a0a5c33f376b40 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 15 Nov 2025 09:44:08 +0100 Subject: [PATCH 777/834] Silence warning for systems without shared memory support Fixes https://github.com/official-stockfish/Stockfish/issues/6425 closes https://github.com/official-stockfish/Stockfish/pull/6426 No functional change --- src/shm.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shm.h b/src/shm.h index ae5429676..b870afc24 100644 --- a/src/shm.h +++ b/src/shm.h @@ -455,7 +455,8 @@ class SharedMemoryBackend { public: SharedMemoryBackend() = default; - SharedMemoryBackend(const std::string& shm_name, const T& value) {} + SharedMemoryBackend([[maybe_unused]] const std::string& shm_name, + [[maybe_unused]] const T& value) {} void* get() const { return nullptr; } From 563b4a9c4d649a78c3c4ee45cc779fb5caaed385 Mon Sep 17 00:00:00 2001 From: Andreas Matthies Date: Sat, 15 Nov 2025 16:46:55 +0100 Subject: [PATCH 778/834] Cleanup benchmark Avoid unnecessary consecutive ucinewgame commands in benchmark setup. Remove measuring time and nodes in speedtest warmup. closes https://github.com/official-stockfish/Stockfish/pull/6427 No functional change --- src/benchmark.cpp | 1 - src/uci.cpp | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 38cafaec7..136b4031e 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -483,7 +483,6 @@ BenchmarkSetup setup_benchmark(std::istream& is) { float totalTime = 0; for (const auto& game : BenchmarkPositions) { - setup.commands.emplace_back("ucinewgame"); int ply = 1; for (int i = 0; i < static_cast(game.size()); ++i) { diff --git a/src/uci.cpp b/src/uci.cpp index 9b1dd865c..5bd235823 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -339,16 +339,9 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); - TimePoint elapsed = now(); - // Run with silenced network verification engine.go(limits); engine.wait_for_search_finished(); - - totalTime += now() - elapsed; - - nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); @@ -396,6 +389,7 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); + nodesSearched = 0; TimePoint elapsed = now(); // Run with silenced network verification @@ -407,7 +401,6 @@ void UCIEngine::benchmark(std::istream& args) { updateHashfullReadings(); nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); From a27fcd6274a8538bf6d07844c7f799d2a0401db6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 17 Nov 2025 13:57:53 +0100 Subject: [PATCH 779/834] Fix format No functional change --- src/nnue/features/full_threats.cpp | 40 ++++++++++++----------------- src/nnue/features/full_threats.h | 8 ++---- src/nnue/features/half_ka_v2_hm.cpp | 11 +++----- src/nnue/features/half_ka_v2_hm.h | 11 +++----- src/nnue/nnue_accumulator.cpp | 35 ++++++++++++------------- 5 files changed, 40 insertions(+), 65 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 122478dc6..9c818a8e0 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -126,20 +126,15 @@ void init_threat_offsets() { } // Index of a feature for a given king position and another piece on some square -inline sf_always_inline -IndexType FullThreats::make_index(Color perspective, - Piece attacker, - Square from, - Square to, - Piece attacked, - Square ksq) { +inline sf_always_inline IndexType FullThreats::make_index( + Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { const int orientation = OrientTBL[perspective][ksq]; - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); std::int8_t swap = 8 * perspective; - attacker = Piece(attacker ^ swap); - attacked = Piece(attacked ^ swap); + attacker = Piece(attacker ^ swap); + attacked = Piece(attacked ^ swap); const auto piecePairData = index_lut1[attacker][attacked]; @@ -147,8 +142,8 @@ IndexType FullThreats::make_index(Color perspective, if ((piecePairData.excluded_pair_info() + less_than) & 2) return FullThreats::Dimensions; - const IndexType index = piecePairData.feature_index_base() + offsets[attacker][from] - + index_lut2[attacker][from][to]; + const IndexType index = + piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; sf_assume(index != FullThreats::Dimensions); return index; @@ -156,13 +151,11 @@ IndexType FullThreats::make_index(Color perspective, // Get a list of indices for active features in ascending order -void FullThreats::append_active_indices(Color perspective, - const Position& pos, - IndexList& active) { +void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(perspective); - Bitboard occupied = pos.pieces(); + Square ksq = pos.square(perspective); + Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { @@ -186,7 +179,7 @@ void FullThreats::append_active_indices(Color perspective, Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -197,7 +190,7 @@ void FullThreats::append_active_indices(Color perspective, Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -214,8 +207,8 @@ void FullThreats::append_active_indices(Color perspective, { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = make_index( - perspective, attacker, from, to, attacked, ksq); + IndexType index = + make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -275,8 +268,7 @@ void FullThreats::append_changed_indices(Color perspective, } } - const IndexType index = make_index(perspective, - attacker, from, to, attacked, ksq); + const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) (add ? added : removed).push_back(index); diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index d5c91d8c2..bc8a1ce32 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -89,12 +89,8 @@ class FullThreats { using IndexList = ValueList; using DiffType = DirtyThreats; - static IndexType make_index(Color perspective, - Piece attkr, - Square from, - Square to, - Piece attkd, - Square ksq); + static IndexType + make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq); // Get a list of indices for active features static void append_active_indices(Color perspective, const Position& pos, IndexList& active); diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5a6f610cf..56779ddce 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -37,9 +37,7 @@ IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square // Get a list of indices for active features -void HalfKAv2_hm::append_active_indices(Color perspective, - const Position& pos, - IndexList& active) { +void HalfKAv2_hm::append_active_indices(Color perspective, const Position& pos, IndexList& active) { Square ksq = pos.square(perspective); Bitboard bb = pos.pieces(); while (bb) @@ -51,11 +49,8 @@ void HalfKAv2_hm::append_active_indices(Color perspective, // Get a list of indices for recently changed features -void HalfKAv2_hm::append_changed_indices(Color perspective, - Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added) { +void HalfKAv2_hm::append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) { removed.push_back(make_index(perspective, diff.from, diff.pc, ksq)); if (diff.to != SQ_NONE) added.push_back(make_index(perspective, diff.to, diff.pc, ksq)); diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index e9448f838..c58a3246b 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -112,16 +112,11 @@ class HalfKAv2_hm { // Get a list of indices for active features - static void append_active_indices(Color perspective, - const Position& pos, - IndexList& active); + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - static void append_changed_indices(Color perspective, - Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added); + static void append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 3d99b8063..3ed729ffb 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -41,7 +41,7 @@ using namespace SIMD; namespace { template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -49,7 +49,7 @@ void double_inc_update(Color p const AccumulatorState& computed); template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -57,9 +57,7 @@ void double_inc_update(Color p const AccumulatorState& computed, const DirtyPiece& dp2); -template +template void update_accumulator_incremental( Color perspective, const FeatureTransformer& featureTransformer, @@ -68,14 +66,14 @@ void update_accumulator_incremental( const AccumulatorState& computed); template -void update_accumulator_refresh_cache(Color perspective, +void update_accumulator_refresh_cache(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); template -void update_threats_accumulator_full(Color perspective, +void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState); @@ -205,7 +203,7 @@ std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) co template void AccumulatorStack::forward_update_incremental( - Color perspective, + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { @@ -260,7 +258,8 @@ void AccumulatorStack::forward_update_incremental( } template -void AccumulatorStack::backward_update_incremental(Color perspective, +void AccumulatorStack::backward_update_incremental( + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, @@ -479,16 +478,16 @@ struct AccumulatorUpdateContext { }; template -auto make_accumulator_update_context(Color perspective, +auto make_accumulator_update_context(Color perspective, const FeatureTransformer& featureTransformer, const AccumulatorState& accumulatorFrom, AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - perspective, featureTransformer, accumulatorFrom, accumulatorTo}; + return AccumulatorUpdateContext{perspective, featureTransformer, + accumulatorFrom, accumulatorTo}; } template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -532,8 +531,8 @@ void double_inc_update(Color p } template -void double_inc_update(Color perspective, -const FeatureTransformer& featureTransformer, +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, @@ -562,9 +561,7 @@ const FeatureTransformer& featureTransformer, target_state.acc().computed[perspective] = true; } -template +template void update_accumulator_incremental( Color perspective, const FeatureTransformer& featureTransformer, @@ -825,7 +822,7 @@ void update_accumulator_refresh_cache(Color pers } template -void update_threats_accumulator_full(Color perspective, +void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState) { From 61149ac9ee59b1d2ea96f9a5a36fa5e74bcf5359 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 12 Nov 2025 17:38:27 +0100 Subject: [PATCH 780/834] Update main net to nn-c0ae49f08b40.nnue Trained using the recipe https://github.com/vondele/nettest/blob/d39f72420504e474a716b9977c1380541a7b482e/threats.yaml fix CI check for negative evals. fix format passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 84032 W: 21876 L: 21490 D: 40666 Ptnml(0-2): 239, 9821, 21553, 10121, 282 https://tests.stockfishchess.org/tests/view/6914b81e7ca87818523318aa passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 126420 W: 32429 L: 31927 D: 62064 Ptnml(0-2): 56, 13748, 35118, 14214, 74 https://tests.stockfishchess.org/tests/view/6916c1277ca8781852331dd8 closes https://github.com/official-stockfish/Stockfish/pull/6431 Bench: 2513286 --- src/evaluate.h | 2 +- tests/instrumented.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index c8dc64ace..fc7784364 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-49c1193b131c.nnue" +#define EvalFileDefaultNameBig "nn-c0ae49f08b40.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { diff --git a/tests/instrumented.py b/tests/instrumented.py index 23de446ef..80831ce3f 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -67,7 +67,6 @@ def Stockfish(*args, **kwargs): class TestCLI(metaclass=OrderedClassMembers): - def beforeAll(self): pass @@ -141,7 +140,7 @@ class TestCLI(metaclass=OrderedClassMembers): def test_bench_128_threads_3_bench_tmp_epd_depth(self): self.stockfish = Stockfish( - f"bench 128 {get_threads()} 3 {os.path.join(PATH,'bench_tmp.epd')} depth".split( + f"bench 128 {get_threads()} 3 {os.path.join(PATH, 'bench_tmp.epd')} depth".split( " " ), True, @@ -167,7 +166,7 @@ class TestCLI(metaclass=OrderedClassMembers): def test_export_net_verify_nnue(self): current_path = os.path.abspath(os.getcwd()) self.stockfish = Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) assert self.stockfish.process.returncode == 0 @@ -254,7 +253,7 @@ class TestInteractive(metaclass=OrderedClassMembers): self.stockfish.send_command("go depth 5") def callback(output): - regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp -?\d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth") and not re.match(regex, output): assert False if output.startswith("bestmove"): @@ -274,7 +273,7 @@ class TestInteractive(metaclass=OrderedClassMembers): def callback(output): nonlocal depth - regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp -?\d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth"): if not re.match(regex, output): @@ -390,7 +389,7 @@ class TestInteractive(metaclass=OrderedClassMembers): def test_verify_nnue_network(self): current_path = os.path.abspath(os.getcwd()) Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) self.stockfish.send_command("setoption name EvalFile value verify.nnue") @@ -469,7 +468,7 @@ class TestSyzygy(metaclass=OrderedClassMembers): self.stockfish.send_command("go depth 5") def check_output(output): - if "score cp -20000" in output or "score mate" in output: + if "score cp -20000" in output or "score mate -" in output: return True self.stockfish.check_output(check_output) From 035cb146d430b601e94c13e3f2fefcc7688ec1d6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:30:59 -0800 Subject: [PATCH 781/834] Do more futility pruning closes https://github.com/official-stockfish/Stockfish/pull/6433 Bench: 2469519 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 53ff9f5d9..1c3875cdc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -857,7 +857,7 @@ Value Search::Worker::search( // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 91 - 21 * !ss->ttHit; + Value futilityMult = 81 - 21 * !ss->ttHit; return futilityMult * d // - 2094 * improving * futilityMult / 1024 // From d9fd516547849bd5ca2a05c491aadc66fc750a39 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 22 Nov 2025 00:17:02 -0500 Subject: [PATCH 782/834] Post-NNUEv10 tune Tune search parameters after the switch to NNUEv10. The change is neutral at STC but increases with the TC. The main changes are more aggressive corrections, futility pruning, and extensions. Failed STC LLR: -2.96 (-2.94,2.94) <0.00,2.00> Total: 108320 W: 27616 L: 27719 D: 52985 Ptnml(0-2): 332, 12833, 27884, 12828, 283 https://tests.stockfishchess.org/tests/view/69212e623b03dd3a060e6114 Passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 43272 W: 11117 L: 10788 D: 21367 Ptnml(0-2): 20, 4543, 12180, 4874, 19 https://tests.stockfishchess.org/tests/view/692130813b03dd3a060e6123 Passed VLTC LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 22714 W: 5840 L: 5581 D: 11293 Ptnml(0-2): 2, 2152, 6795, 2401, 7 https://tests.stockfishchess.org/tests/view/6921387b3b03dd3a060e6191 Passed VLTC SMP LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 11868 W: 3142 L: 2896 D: 5830 Ptnml(0-2): 0, 1007, 3676, 1249, 2 https://tests.stockfishchess.org/tests/view/69212e953b03dd3a060e611b closes https://github.com/official-stockfish/Stockfish/pull/6444 bench 2907929 --- src/evaluate.cpp | 12 ++-- src/search.cpp | 184 ++++++++++++++++++++++++----------------------- 2 files changed, 101 insertions(+), 95 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4bbbcaac6..d20843e85 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -65,7 +65,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (std::abs(nnue) < 236)) + if (smallNet && (std::abs(nnue) < 277)) { std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); nnue = (125 * psqt + 131 * positional) / 128; @@ -74,14 +74,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); - optimism += optimism * nnueComplexity / 468; - nnue -= nnue * nnueComplexity / 18000; + optimism += optimism * nnueComplexity / 476; + nnue -= nnue * nnueComplexity / 18236; - int material = 535 * pos.count() + pos.non_pawn_material(); - int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; + int material = 534 * pos.count() + pos.non_pawn_material(); + int v = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871; // Damp down the evaluation linearly when shuffling - v -= v * pos.rule50_count() / 212; + v -= v * pos.rule50_count() / 199; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/search.cpp b/src/search.cpp index 1c3875cdc..d2a90283c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -88,7 +88,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 8; - return 9536 * pcv + 8494 * micv + 10132 * (wnpcv + bnpcv) + 7156 * cntcv; + return 10347 * pcv + 8821 * micv + 11168 * (wnpcv + bnpcv) + 7841 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -104,10 +104,10 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - constexpr int nonPawnWeight = 165; + constexpr int nonPawnWeight = 178; workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 156 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] @@ -117,8 +117,8 @@ void update_correction_history(const Position& pos, { const Square to = m.to_sq(); const Piece pc = pos.piece_on(m.to_sq()); - (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 137 / 128; - (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 64 / 128; + (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 127 / 128; + (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 59 / 128; } } @@ -338,7 +338,7 @@ void Search::Worker::iterative_deepening() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 137 * avg / (std::abs(avg) + 91); + optimism[us] = 142 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -467,20 +467,23 @@ void Search::Worker::iterative_deepening() { uint64_t nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); - double fallingEval = - (11.325 + 2.115 * (mainThread->bestPreviousAverageScore - bestValue) - + 0.987 * (mainThread->iterValue[iterIdx] - bestValue)) - / 100.0; - fallingEval = std::clamp(fallingEval, 0.5688, 1.5698); + double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.93 * (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; + + fallingEval = std::clamp(fallingEval, 0.57, 1.70); // If the bestMove is stable over several iterations, reduce time accordingly - double k = 0.5189; - double center = lastBestMoveDepth + 11.57; - timeReduction = 0.723 + 0.79 / (1.104 + std::exp(-k * (completedDepth - center))); - double reduction = - (1.455 + mainThread->previousTimeReduction) / (2.2375 * timeReduction); - double bestMoveInstability = 1.04 + 1.8956 * totBestMoveChanges / threads.size(); - double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 92425 ? 0.666 : 1.0; + double k = 0.51; + double center = lastBestMoveDepth + 12.15; + + timeReduction = 0.66 + 0.85 / (0.98 + std::exp(-k * (completedDepth - center))); + + double reduction = (1.43 + mainThread->previousTimeReduction) / (2.28 * timeReduction); + + double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size(); + + double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 93337 ? 0.75 : 1.0; double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * highBestMoveEffort; @@ -502,7 +505,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.503; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.50; } mainThread->iterValue[iterIdx] = bestValue; @@ -582,7 +585,7 @@ void Search::Worker::clear() { h.fill(-529); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2809 / 128.0 * std::log(i)); + reductions[i] = int(2747 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -702,11 +705,12 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(130 * depth - 71, 1043)); + std::min(132 * depth - 72, 985)); + // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2142); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060); } // Partial workaround for the graph history interaction problem @@ -825,11 +829,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; + int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -209, 167) + 59; mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13; } // Set up the improving flag, which is true if current static evaluation is @@ -844,25 +848,25 @@ Value Search::Worker::search( // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 514 - 294 * depth * depth) + if (!PvNode && eval < alpha - 485 - 281 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 81 - 21 * !ss->ttHit; + Value futilityMult = 76 - 23 * !ss->ttHit; return futilityMult * d // - - 2094 * improving * futilityMult / 1024 // + - 2474 * improving * futilityMult / 1024 // - 331 * opponentWorsening * futilityMult / 1024 // - + std::abs(correctionValue) / 158105; + + std::abs(correctionValue) / 174665; }; if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta @@ -871,7 +875,7 @@ Value Search::Worker::search( } // Step 9. Null move search with verification search - if (cutNode && ss->staticEval >= beta - 18 * depth + 390 && !excludedMove + if (cutNode && ss->staticEval >= beta - 18 * depth + 350 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { assert((ss - 1)->currentMove != Move::null()); @@ -905,6 +909,7 @@ Value Search::Worker::search( } } + improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions @@ -916,7 +921,7 @@ Value Search::Worker::search( // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 224 - 64 * improving; + probCutBeta = beta + 235 - 63 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -926,7 +931,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 306, 0, depth); + Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 315, 0, depth); while ((move = mp.next_move()) != Move::none()) { @@ -1046,8 +1051,8 @@ moves_loop: // When in check, search starts here // Futility pruning for captures if (!givesCheck && lmrDepth < 7) { - Value futilityValue = ss->staticEval + 231 + 211 * lmrDepth - + PieceValue[capturedPiece] + 130 * captHist / 1024; + Value futilityValue = ss->staticEval + 232 + 217 * lmrDepth + + PieceValue[capturedPiece] + 131 * captHist / 1024; if (futilityValue <= alpha) continue; @@ -1055,7 +1060,7 @@ moves_loop: // When in check, search starts here // SEE based pruning for captures and checks // Avoid pruning sacrifices of our last piece for stalemate - int margin = std::max(157 * depth + captHist / 29, 0); + int margin = std::max(166 * depth + captHist / 29, 0); if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece]) && !pos.see_ge(move, -margin)) continue; @@ -1067,21 +1072,21 @@ moves_loop: // When in check, search starts here + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4312 * depth) + if (history < -4083 * depth) continue; - history += 76 * mainHistory[us][move.raw()] / 32; + history += 69 * mainHistory[us][move.raw()] / 32; // (*Scaler): Generally, lower divisors scales well - lmrDepth += history / 3220; + lmrDepth += history / 3208; - Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth - + 90 * (ss->staticEval > alpha); + Value futilityValue = ss->staticEval + 42 + 161 * !bestMove + 127 * lmrDepth + + 85 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning // scales well - if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) @@ -1092,7 +1097,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) continue; } } @@ -1107,12 +1112,11 @@ moves_loop: // When in check, search starts here // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. - if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (56 + 81 * (ss->ttPv && !PvNode)) * depth / 60; + Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1121,11 +1125,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 229958; - int doubleMargin = -4 + 198 * PvNode - 212 * !ttCapture - corrValAdj - - 921 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 45; - int tripleMargin = 76 + 308 * PvNode - 250 * !ttCapture + 92 * ss->ttPv - corrValAdj - - (ss->ply * 2 > rootDepth * 3) * 52; + int corrValAdj = std::abs(correctionValue) / 230673; + int doubleMargin = -4 + 199 * PvNode - 201 * !ttCapture - corrValAdj + - 897 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 42; + int tripleMargin = 73 + 302 * PvNode - 248 * !ttCapture + 90 * ss->ttPv - corrValAdj + - (ss->ply * 2 > rootDepth * 3) * 50; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1171,33 +1175,32 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2618 + PvNode * 991 + (ttData.value > alpha) * 903 - + (ttData.depth >= depth) * (978 + cutNode * 1051); - + r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922 + + (ttData.depth >= depth) * (934 + cutNode * 1011); // These reduction adjustments have no proven non-linear scaling - r += 843; // Base reduction offset to compensate for other tweaks - r -= moveCount * 66; - r -= std::abs(correctionValue) / 30450; + r += 714; // Base reduction offset to compensate for other tweaks + r -= moveCount * 73; + r -= std::abs(correctionValue) / 30370; // Increase reduction for cut nodes if (cutNode) - r += 3094 + 1056 * !ttData.move; + r += 3372 + 997 * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1415; + r += 1119; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1051 + allNode * 814; + r += 991 + allNode * 923; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2018; + r -= 2151; if (capture) - ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + ss->statScore = 868 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else ss->statScore = 2 * mainHistory[us][move.raw()] @@ -1205,7 +1208,8 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 794 / 8192; + r -= ss->statScore * 850 / 8192; + // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1245,13 +1249,12 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1118; + r += 1140; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3212) - (r > 4784 && newDepth > 2), !cutNode); + newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode); } - // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. if (PvNode && (moveCount == 1 || value > alpha)) @@ -1403,25 +1406,25 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -228; - bonusScale -= (ss - 1)->statScore / 104; - bonusScale += std::min(63 * depth, 508); + int bonusScale = -215; + bonusScale -= (ss - 1)->statScore / 100; + bonusScale += std::min(56 * depth, 489); bonusScale += 184 * ((ss - 1)->moveCount > 8); - bonusScale += 143 * (!ss->inCheck && bestValue <= ss->staticEval - 92); - bonusScale += 149 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 70); + bonusScale += 147 * (!ss->inCheck && bestValue <= ss->staticEval - 107); + bonusScale += 156 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 65); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(144 * depth - 92, 1365) * bonusScale; + const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 400 / 32768); + scaledBonus * 406 / 32768); - mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 220 / 32768; + mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1164 / 32768; + << scaledBonus * 1160 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1429,9 +1432,10 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 964; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012; } + if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1579,9 +1583,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 352; + futilityBase = ss->staticEval + 351; } + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; @@ -1640,7 +1645,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -78)) + if (!pos.see_ge(move, -80)) continue; } @@ -1713,9 +1718,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 757 / rootDelta + !i * reductionScale * 218 / 512 + 1200; + return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182; } + // elapsed() returns the time elapsed since the search started. If the // 'nodestime' option is enabled, it will return the count of nodes searched // instead. This function is called to check whether the search should be @@ -1809,35 +1815,35 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(121 * depth - 77, 1633) + 375 * (bestMove == ttMove); - int malus = std::min(825 * depth - 196, 2159) - 16 * moveCount; + int bonus = std::min(116 * depth - 81, 1515) + 347 * (bestMove == ttMove); + int malus = std::min(848 * depth - 207, 2446) - 17 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 881 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 910 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1083 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1085 / 1024); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1482 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1395 / 1024; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 614 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 602 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1397 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1448 / 1024; } } @@ -1845,8 +1851,8 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - static constexpr std::array conthist_bonuses = { - {{1, 1157}, {2, 648}, {3, 288}, {4, 576}, {5, 140}, {6, 441}}}; + static std::array conthist_bonuses = { + {{1, 1133}, {2, 683}, {3, 312}, {4, 582}, {5, 149}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1867,13 +1873,13 @@ void update_quiet_histories( workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 761 / 1024; + workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 805 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 896 / 1024); int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << bonus * (bonus > 0 ? 850 : 550) / 1024; + << bonus * (bonus > 0 ? 905 : 505) / 1024; } } From 1132d893e09d6dae78a452b841375545aca9e03e Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:57:08 -0800 Subject: [PATCH 783/834] Simplify Accumulator Updates Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 146848 W: 37915 L: 37820 D: 71113 Ptnml(0-2): 415, 16159, 40186, 16244, 420 https://tests.stockfishchess.org/tests/view/691772217ca878185233202b closes https://github.com/official-stockfish/Stockfish/pull/6421 No functional change --- src/nnue/nnue_accumulator.cpp | 50 +++++++++++++++-------------------- src/nnue/nnue_accumulator.h | 8 +++--- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 3ed729ffb..e63a68cb7 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -18,7 +18,6 @@ #include "nnue_accumulator.h" -#include #include #include #include @@ -338,22 +337,23 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.template acc()).accumulation[perspective], - (to.template acc()).accumulation[perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[perspective].data(), + (to.template acc()).accumulation[perspective].data(), + to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[perspective], - (to.template acc()).psqtAccumulation[perspective], + (from.template acc()).psqtAccumulation[perspective].data(), + (to.template acc()).psqtAccumulation[perspective].data(), to_psqt_weight_vector(indices)...); } void apply(const typename FeatureSet::IndexList& added, const typename FeatureSet::IndexList& removed) { - const auto fromAcc = from.template acc().accumulation[perspective]; - const auto toAcc = to.template acc().accumulation[perspective]; + const auto& fromAcc = from.template acc().accumulation[perspective]; + auto& toAcc = to.template acc().accumulation[perspective]; - const auto fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; - const auto toPsqtAcc = to.template acc().psqtAccumulation[perspective]; + const auto& fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; + auto& toPsqtAcc = to.template acc().psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; @@ -448,8 +448,8 @@ struct AccumulatorUpdateContext { #else - std::copy_n(fromAcc, Dimensions, toAcc); - std::copy_n(fromPsqtAcc, PSQTBuckets, toPsqtAcc); + toAcc = fromAcc; + toPsqtAcc = fromPsqtAcc; for (const auto index : removed) { @@ -589,13 +589,10 @@ void update_accumulator_incremental( auto& targetAcc = target_state.template acc(); const auto& sourceAcc = computed.template acc(); - std::memcpy(targetAcc.accumulation[perspective], sourceAcc.accumulation[perspective], - sizeof(targetAcc.accumulation[perspective])); - std::memcpy(targetAcc.psqtAccumulation[perspective], - sourceAcc.psqtAccumulation[perspective], - sizeof(targetAcc.psqtAccumulation[perspective])); + targetAcc.accumulation[perspective] = sourceAcc.accumulation[perspective]; + targetAcc.psqtAccumulation[perspective] = sourceAcc.psqtAccumulation[perspective]; + targetAcc.computed[perspective] = true; - targetAcc.computed[perspective] = true; return; } @@ -643,15 +640,16 @@ void update_accumulator_incremental( (target_state.template acc()).computed[perspective] = true; } -Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { +Bitboard get_changed_pieces(const std::array& oldPieces, + const std::array& newPieces) { #if defined(USE_AVX512) || defined(USE_AVX2) static_assert(sizeof(Piece) == 1); Bitboard sameBB = 0; for (int i = 0; i < 64; i += 32) { - const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(oldPieces + i)); - const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(newPieces + i)); + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(&oldPieces[i])); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(&newPieces[i])); const __m256i cmpEqual = _mm256_cmpeq_epi8(old_v, new_v); const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual); sameBB |= static_cast(equalMask) << i; @@ -680,7 +678,7 @@ void update_accumulator_refresh_cache(Color pers auto& entry = cache[ksq][perspective]; PSQFeatureSet::IndexList removed, added; - const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); + const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array()); Bitboard removedBB = changedBB & entry.pieceBB; Bitboard addedBB = changedBB & pos.pieces(); @@ -696,7 +694,7 @@ void update_accumulator_refresh_cache(Color pers } entry.pieceBB = pos.pieces(); - std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); + entry.pieces = pos.piece_array(); auto& accumulator = accumulatorState.acc(); accumulator.computed[perspective] = true; @@ -812,12 +810,8 @@ void update_accumulator_refresh_cache(Color pers // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - - std::memcpy(accumulator.accumulation[perspective], entry.accumulation.data(), - sizeof(BiasType) * Dimensions); - - std::memcpy(accumulator.psqtAccumulation[perspective], entry.psqtAccumulation.data(), - sizeof(int32_t) * PSQTBuckets); + accumulator.accumulation[perspective] = entry.accumulation; + accumulator.psqtAccumulation[perspective] = entry.psqtAccumulation; #endif } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 181412b43..69ab49632 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -46,9 +46,9 @@ class FeatureTransformer; // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[COLOR_NB][Size]; - std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - std::array computed = {}; + std::array, COLOR_NB> accumulation; + std::array, COLOR_NB> psqtAccumulation; + std::array computed = {}; }; @@ -71,7 +71,7 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Entry { std::array accumulation; std::array psqtAccumulation; - Piece pieces[SQUARE_NB]; + std::array pieces; Bitboard pieceBB; // To initialize a refresh entry, we set all its bitboards empty, From 93f2d14d95ca72ec2ec96c6e3de0b9a6121fe118 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:09:06 -0800 Subject: [PATCH 784/834] Simplify Incremental Updates Passed Non-regression STC: LLR: 3.03 (-2.94,2.94) <-1.75,0.25> Total: 277856 W: 71575 L: 71611 D: 134670 Ptnml(0-2): 842, 30719, 75836, 30695, 836 https://tests.stockfishchess.org/tests/view/69169dc17ca8781852331d76 Supersedes #6430 closes https://github.com/official-stockfish/Stockfish/pull/6434 No functional change --- src/nnue/nnue_accumulator.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index e63a68cb7..358a4b278 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -584,18 +584,6 @@ void update_accumulator_incremental( else FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed); - if (!added.size() && !removed.size()) - { - auto& targetAcc = target_state.template acc(); - const auto& sourceAcc = computed.template acc(); - - targetAcc.accumulation[perspective] = sourceAcc.accumulation[perspective]; - targetAcc.psqtAccumulation[perspective] = sourceAcc.psqtAccumulation[perspective]; - targetAcc.computed[perspective] = true; - - return; - } - auto updateContext = make_accumulator_update_context(perspective, featureTransformer, computed, target_state); From f4244e13e41b936f73076c51eac38511f406d8e4 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Sat, 15 Nov 2025 20:46:14 -0800 Subject: [PATCH 785/834] Some more work on FullThreats::make_index [Passed STC](https://tests.stockfishchess.org/tests/live_elo/691fc321acb6dbdf23d07e4b): ``` LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 215360 W: 55651 L: 55108 D: 104601 Ptnml(0-2): 520, 22399, 61290, 22960, 511 ``` This PR is on top of ces42's work so I'll rebase if that PR changes. Essentially, it comprises my adjustments to `make_index` that remained unmerged before threat inputs was finalized. The unsigned intermediate variables let us skip a bunch of useless sign extensions (because Square and Piece inherit from `int8_t`, and C/C++ semantics require narrow integers to be promoted to `int` before doing arithmetic on them). Additionally, by removing the special usage of `offsets[][64]` and `[65]` the indexing becomes more efficient. (This usage was a temporary hack from sscg anyway, so I think he'll like that it's gone.) Finally, the `sf_assume` condition was fixed so that it actually makes a difference to the compiler. The speedup is fairly nice locally (combining both ces42's and these changes): ``` Result of 100 runs ================== base (...kfish.master) = 1691982 +/- 1907 test (./stockfish ) = 1714349 +/- 1789 diff = +22367 +/- 2465 speedup = +0.0132 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/6445 No functional change --- src/nnue/features/full_threats.cpp | 50 ++++++++++++++++-------------- src/nnue/features/full_threats.h | 15 ++------- src/types.h | 8 ++--- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 9c818a8e0..645bb7090 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -32,7 +32,12 @@ namespace Stockfish::Eval::NNUE::Features { // Lookup array for indexing threats -IndexType offsets[PIECE_NB][SQUARE_NB + 2]; +IndexType offsets[PIECE_NB][SQUARE_NB]; + +struct HelperOffsets { + int cumulativePieceOffset, cumulativeOffset; +}; +std::array helper_offsets; // Information on a particular pair of pieces and whether they should be excluded struct PiecePairData { @@ -69,9 +74,9 @@ static void init_index_luts() { int map = FullThreats::map[attackerType - 1][attackedType - 1]; bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN); - IndexType feature = offsets[attacker][65] + IndexType feature = helper_offsets[attacker].cumulativeOffset + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) - * offsets[attacker][64]; + * helper_offsets[attacker].cumulativePieceOffset; bool excluded = map < 0; index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); @@ -116,8 +121,7 @@ void init_threat_offsets() { } } - offsets[pieceIdx][64] = cumulativePieceOffset; - offsets[pieceIdx][65] = cumulativeOffset; + helper_offsets[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; } @@ -128,32 +132,30 @@ void init_threat_offsets() { // Index of a feature for a given king position and another piece on some square inline sf_always_inline IndexType FullThreats::make_index( Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - const int orientation = OrientTBL[perspective][ksq]; - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); + const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective); + unsigned from_oriented = uint8_t(from) ^ orientation; + unsigned to_oriented = uint8_t(to) ^ orientation; - std::int8_t swap = 8 * perspective; - attacker = Piece(attacker ^ swap); - attacked = Piece(attacked ^ swap); + std::int8_t swap = 8 * perspective; + unsigned attacker_oriented = attacker ^ swap; + unsigned attacked_oriented = attacked ^ swap; - const auto piecePairData = index_lut1[attacker][attacked]; + const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented]; - const bool less_than = static_cast(from) < static_cast(to); + const bool less_than = from_oriented < to_oriented; if ((piecePairData.excluded_pair_info() + less_than) & 2) return FullThreats::Dimensions; - const IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != FullThreats::Dimensions); + const IndexType index = piecePairData.feature_index_base() + + offsets[attacker_oriented][from_oriented] + + index_lut2[attacker_oriented][from_oriented][to_oriented]; + sf_assume(index < Dimensions); return index; } // Get a list of indices for active features in ascending order void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) { - static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(perspective); Bitboard occupied = pos.pieces(); @@ -161,7 +163,7 @@ void FullThreats::append_active_indices(Color perspective, const Position& pos, { for (PieceType pt = PAWN; pt <= KING; ++pt) { - Color c = order[perspective][color]; + Color c = Color(perspective ^ color); Piece attacker = make_piece(c, pt); Bitboard bb = pos.pieces(c, pt); @@ -268,16 +270,16 @@ void FullThreats::append_changed_indices(Color perspective, } } - const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + auto& insert = add ? added : removed; + const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) - (add ? added : removed).push_back(index); + insert.push_back(index); } } bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { - return perspective == diff.us - && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; + return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index bc8a1ce32..177e9fabe 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -32,7 +32,6 @@ namespace Stockfish::Eval::NNUE::Features { static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0, 0, 6, 12, 10, 10, 12, 8, 0}; -extern IndexType offsets[PIECE_NB][SQUARE_NB + 2]; void init_threat_offsets(); class FullThreats { @@ -48,23 +47,15 @@ class FullThreats { // clang-format off // Orient a square according to perspective (rotates by 180 for black) - static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { - { SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + static constexpr std::int8_t OrientTBL[SQUARE_NB] = { + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, - SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1 }, - { SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8 } }; static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = { diff --git a/src/types.h b/src/types.h index a2171b638..3aeb2bcc4 100644 --- a/src/types.h +++ b/src/types.h @@ -295,18 +295,14 @@ struct DirtyPiece { struct DirtyThreat { DirtyThreat() { /* don't initialize data */ } DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 28) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); } Piece pc() const { return static_cast(data >> 20 & 0xf); } Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { - uint32_t b = data >> 28; - sf_assume(b == 0 || b == 1); - return b; - } + bool add() const { return data >> 31; } private: uint32_t data; From c73f21df97e98c960773d0eb0fcf765582c06fed Mon Sep 17 00:00:00 2001 From: pb00067 Date: Sat, 22 Nov 2025 13:24:03 +0100 Subject: [PATCH 786/834] Fix for SF getting stuck during search (issue #5023) Among several passed similar versions this is the simpliest one. Passed STC non-regression https://tests.stockfishchess.org/tests/view/691f3b4aacb6dbdf23d07d11 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 126368 W: 32607 L: 32489 D: 61272 Ptnml(0-2): 408, 13974, 34298, 14100, 404 Passed LTC non-regression https://tests.stockfishchess.org/tests/view/69209712acb6dbdf23d0836a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 114786 W: 29097 L: 28976 D: 56713 Ptnml(0-2): 53, 11841, 33488, 11954, 57 closes https://github.com/official-stockfish/Stockfish/pull/6447 Bench: 2941767 --- src/search.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d2a90283c..9924b0cec 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -141,6 +141,16 @@ void update_all_stats(const Position& pos, Move TTMove, int moveCount); +bool isShuffling(Move move, Stack* const ss, const Position& pos) { + if (type_of(pos.moved_piece(move)) == PAWN || pos.capture_stage(move) + || pos.rule50_count() < 10) + return false; + if (pos.state()->pliesFromNull <= 6 || ss->ply < 20) + return false; + return move.from_sq() == (ss - 2)->currentMove.to_sq() + && (ss - 2)->currentMove.from_sq() == (ss - 4)->currentMove.to_sq(); +} + } // namespace Search::Worker::Worker(SharedState& sharedState, @@ -1114,7 +1124,7 @@ moves_loop: // When in check, search starts here // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3) + && ttData.depth >= depth - 3 && !isShuffling(move, ss, pos)) { Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; From 45d034fa504c0262c9be958e6139cabcc65aeaf6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 21 Nov 2025 23:11:16 -0800 Subject: [PATCH 787/834] Formatting fixups closes https://github.com/official-stockfish/Stockfish/pull/6446 No functional change --- src/search.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9924b0cec..f5133905e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -480,7 +480,6 @@ void Search::Worker::iterative_deepening() { double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue) + 0.93 * (mainThread->iterValue[iterIdx] - bestValue)) / 100.0; - fallingEval = std::clamp(fallingEval, 0.57, 1.70); // If the bestMove is stable over several iterations, reduce time accordingly @@ -717,7 +716,6 @@ Value Search::Worker::search( update_quiet_histories(pos, ss, *this, ttData.move, std::min(132 * depth - 72, 985)); - // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060); @@ -738,6 +736,7 @@ Value Search::Worker::search( // Check that the ttValue after the tt move would also trigger a cutoff if (!is_valid(ttDataNext.value)) return ttData.value; + if ((ttData.value >= beta) == (-ttDataNext.value >= beta)) return ttData.value; } @@ -858,6 +857,7 @@ Value Search::Worker::search( // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) depth--; @@ -919,7 +919,6 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions @@ -1187,7 +1186,6 @@ moves_loop: // When in check, search starts here if (ss->ttPv) r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922 + (ttData.depth >= depth) * (934 + cutNode * 1011); - // These reduction adjustments have no proven non-linear scaling r += 714; // Base reduction offset to compensate for other tweaks r -= moveCount * 73; @@ -1220,7 +1218,6 @@ moves_loop: // When in check, search starts here // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 850 / 8192; - // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) { @@ -1265,6 +1262,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode); } + // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. if (PvNode && (moveCount == 1 || value > alpha)) @@ -1445,7 +1443,6 @@ moves_loop: // When in check, search starts here captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012; } - if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1560,8 +1557,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(pos); + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); @@ -1573,8 +1572,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) else { unadjustedStaticEval = evaluate(pos); - - ss->staticEval = bestValue = + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); } @@ -1583,6 +1581,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { if (!is_decisive(bestValue)) bestValue = (bestValue + beta) / 2; + if (!ss->ttHit) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, @@ -1596,7 +1595,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) futilityBase = ss->staticEval + 351; } - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; @@ -1699,7 +1697,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!is_decisive(bestValue) && bestValue > beta) bestValue = (bestValue + beta) / 2; - Color us = pos.side_to_move(); if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us) && type_of(pos.captured_piece()) >= ROOK) @@ -1731,7 +1728,6 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182; } - // elapsed() returns the time elapsed since the search started. If the // 'nodestime' option is enabled, it will return the count of nodes searched // instead. This function is called to check whether the search should be @@ -1869,6 +1865,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; + if (((ss - i)->currentMove).is_ok()) (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 88 * (i < 2); } @@ -1925,7 +1922,6 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { return best; } - // Used to print debug info and, more importantly, to detect // when we are out of available time and thus stop the search. void SearchManager::check_time(Search::Worker& worker) { From 7c7c574e6b572ff7e23388d9f767471e597cb872 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sat, 22 Nov 2025 14:15:50 -0800 Subject: [PATCH 788/834] Fix msvc macro closes https://github.com/official-stockfish/Stockfish/pull/6449 No functional change --- src/misc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.h b/src/misc.h index 66c03b806..54598cd0e 100644 --- a/src/misc.h +++ b/src/misc.h @@ -414,7 +414,7 @@ void move_to_front(std::vector& vec, Predicate pred) { #if defined(__GNUC__) #define sf_always_inline __attribute__((always_inline)) -#elif defined(__MSVC) +#elif defined(_MSC_VER) #define sf_always_inline __forceinline #else // do nothign for other compilers From c95386256ef79c2a415eeb4ab83ae5ec9e5b1532 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 26 Nov 2025 17:43:38 +0300 Subject: [PATCH 789/834] Simplify the highBestMoveEffort formula in Time management Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 22272 W: 5885 L: 5655 D: 10732 Ptnml(0-2): 76, 2329, 6108, 2535, 88 https://tests.stockfishchess.org/tests/view/69244d07ba083df4ca63e02b Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 48690 W: 12405 L: 12221 D: 24064 Ptnml(0-2): 29, 4755, 14597, 4931, 33 https://tests.stockfishchess.org/tests/view/6924de9aba083df4ca63e327 closes https://github.com/official-stockfish/Stockfish/pull/6451 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f5133905e..8e30f3dc3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -492,7 +492,7 @@ void Search::Worker::iterative_deepening() { double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size(); - double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 93337 ? 0.75 : 1.0; + double highBestMoveEffort = nodesEffort >= 93340 ? 0.76 : 1.0; double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * highBestMoveEffort; From 4fe04a2cc6efdd3ce1a859cd4d0bd61ca875b4e2 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 23 Nov 2025 17:57:33 +0100 Subject: [PATCH 790/834] Update main network to nn-87a9d7857d88.nnue trained with https://github.com/vondele/nettest/blob/842d95177f882c0e9b5262247c38d8fcb3e0f3db/threats.yaml passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 114880 W: 30291 L: 29851 D: 54738 Ptnml(0-2): 499, 13547, 28947, 13909, 538 https://tests.stockfishchess.org/tests/view/6923ec4bba083df4ca63dd39 passed LTC: LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 113268 W: 29115 L: 28634 D: 55519 Ptnml(0-2): 114, 12295, 31340, 12766, 119 https://tests.stockfishchess.org/tests/view/6925cd01b23dfeae38cfe134 closes https://github.com/official-stockfish/Stockfish/pull/6452 Bench: 3028457 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index fc7784364..9eff46709 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-c0ae49f08b40.nnue" +#define EvalFileDefaultNameBig "nn-87a9d7857d88.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 74303ca7f9ccba952c1937e2b9e09a341c6c0c34 Mon Sep 17 00:00:00 2001 From: KazApps Date: Sat, 29 Nov 2025 18:11:46 +0900 Subject: [PATCH 791/834] Update AUTHORS (KazApps) closes https://github.com/official-stockfish/Stockfish/pull/6454 No functional change --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index db552a3a5..fac1e7d8d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -131,6 +131,7 @@ Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery Justin Blanchard (UncombedCoconut) +Kazuki Yamashita (KazApps) Kelly Wilson Ken Takusagawa Kenneth Lee (kennethlee33) From 9e2ee13e775274ff5faab3fa12a7c0fd0370df3a Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 28 Nov 2025 07:42:26 +0100 Subject: [PATCH 792/834] Update main network to nn-2962dca31855.nnue trained with https://github.com/vondele/nettest/blob/dd921672bd4ad8eb09fa45248078450857e81884/threats.yaml on top off and tested against https://github.com/official-stockfish/Stockfish/pull/6452 passed STC LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 130720 W: 34028 L: 33570 D: 63122 Ptnml(0-2): 435, 15348, 33384, 15710, 483 https://tests.stockfishchess.org/tests/view/69294539b23dfeae38cff3ad passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 51144 W: 13140 L: 12794 D: 25210 Ptnml(0-2): 38, 5370, 14408, 5720, 36 https://tests.stockfishchess.org/tests/view/692a9c37b23dfeae38cffa4f closes https://github.com/official-stockfish/Stockfish/pull/6457 Bench: 2823033 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 9eff46709..853aeb5b2 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-87a9d7857d88.nnue" +#define EvalFileDefaultNameBig "nn-2962dca31855.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From abd835dcbc3a28481224f6253b00b7420d062513 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 30 Nov 2025 13:17:39 -0800 Subject: [PATCH 793/834] Improve update_piece_threats passed on avx512ICL: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 30240 W: 8026 L: 7726 D: 14488 Ptnml(0-2): 95, 3235, 8171, 3513, 106 https://tests.stockfishchess.org/tests/view/69281d9ab23dfeae38cfeeb8 passed on generic architectures: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 73184 W: 19183 L: 18821 D: 35180 Ptnml(0-2): 258, 7988, 19744, 8338, 264 https://tests.stockfishchess.org/tests/view/6928ba11b23dfeae38cff276 subsequent cleanups tested as: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 72480 W: 18678 L: 18502 D: 35300 Ptnml(0-2): 242, 7925, 19718, 8125, 230 https://tests.stockfishchess.org/tests/view/692a26adb23dfeae38cff566 We add an argument noRaysContaining, which skips all discoveries which contain all bits in the argument; if the argument is from | to, then this will eliminate the discovery. Separately, on AVX512ICL we can speed up the computation of DirtyThreats by moving from a pop_lsb loop to a vector extraction with vpcompressb. See PR for details. closes https://github.com/official-stockfish/Stockfish/pull/6453 No functional change --- src/misc.h | 7 ++++ src/position.cpp | 94 ++++++++++++++++++++++++++++++++++++++++-------- src/position.h | 9 +++-- src/types.h | 23 ++++++++---- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/misc.h b/src/misc.h index 54598cd0e..db5f701e9 100644 --- a/src/misc.h +++ b/src/misc.h @@ -142,6 +142,13 @@ class ValueList { const T* end() const { return values_ + size_; } const T& operator[](int index) const { return values_[index]; } + T* make_space(size_t count) { + T* result = &values_[size_]; + size_ += count; + assert(size_ <= MaxSize); + return result; + } + private: T values_[MaxSize]; std::size_t size_ = 0; diff --git a/src/position.cpp b/src/position.cpp index 8993c2406..049c72682 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1052,8 +1052,51 @@ inline void add_dirty_threat( dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); } +#ifdef USE_AVX512ICL +// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats +// present at the given bitboard. +template +void write_multiple_dirties(const Position& p, + Bitboard mask, + DirtyThreat dt_template, + DirtyThreats* dts) { + static_assert(sizeof(DirtyThreat) == 4); + + const __m512i board = _mm512_loadu_si512(p.piece_array().data()); + const __m512i AllSquares = _mm512_set_epi8( + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, + 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + const int dt_count = popcount(mask); + assert(dt_count <= 16); + + const __m512i template_v = _mm512_set1_epi32(dt_template.raw()); + auto* write = dts->list.make_space(dt_count); + + // Extract the list of squares and upconvert to 32 bits. There are never more than 16 + // incoming threats so this is sufficient. + __m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares); + threat_squares = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares)); + + __m512i threat_pieces = + _mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board); + + // Shift the piece and square into place + threat_squares = _mm512_slli_epi32(threat_squares, SqShift); + threat_pieces = _mm512_slli_epi32(threat_pieces, PcShift); + + const __m512i dirties = + _mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */); + _mm512_storeu_si512(reinterpret_cast<__m512i*>(write), dirties); +} +#endif + template -void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { +void Position::update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining) { const Bitboard occupied = pieces(); const Bitboard rookQueens = pieces(ROOK, QUEEN); const Bitboard bishopQueens = pieces(BISHOP, QUEEN); @@ -1093,7 +1136,36 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) } threatened &= occupied; + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); +#ifdef USE_AVX512ICL + if (threatened) + { + if constexpr (PutPiece) + { + dts->threatenedSqs |= threatened; + dts->threateningSqs |= square_bb(s); + } + + DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece}; + write_multiple_dirties( + *this, threatened, dt_template, dts); + } + + Bitboard all_attackers = sliders | incoming_threats; + if (!all_attackers) + return; // Square s is threatened iff there's at least one attacker + + dts->threatenedSqs |= square_bb(s); + dts->threateningSqs |= all_attackers; + + DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece}; + write_multiple_dirties(*this, all_attackers, + dt_template, dts); +#else while (threatened) { Square threatenedSq = pop_lsb(threatened); @@ -1104,8 +1176,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); } - - Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); +#endif if constexpr (ComputeRay) { @@ -1118,30 +1189,24 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) const Bitboard discovered = ray & qAttacks & occupied; assert(!more_than_one(discovered)); - if (discovered) + if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining) { const Square threatenedSq = lsb(discovered); const Piece threatenedPc = piece_on(threatenedSq); add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); } +#ifndef USE_AVX512ICL // for ICL, direct threats were processed earlier (all_attackers) add_dirty_threat(dts, slider, pc, sliderSq, s); +#endif } } else { - while (sliders) - { - Square sliderSq = pop_lsb(sliders); - Piece slider = piece_on(sliderSq); - add_dirty_threat(dts, slider, pc, sliderSq, s); - } + incoming_threats |= sliders; } - Bitboard incoming_threats = - (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) - | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); - +#ifndef USE_AVX512ICL while (incoming_threats) { Square srcSq = pop_lsb(incoming_threats); @@ -1152,6 +1217,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, srcPc, pc, srcSq, s); } +#endif } // Helper used to do/undo a castling move. This is a bit diff --git a/src/position.h b/src/position.h index 711c4e444..7a029ce18 100644 --- a/src/position.h +++ b/src/position.h @@ -187,7 +187,10 @@ class Position { // Other helpers template - void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts); + void update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining = -1ULL); void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); template void do_castling(Color us, @@ -372,7 +375,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts Bitboard fromTo = from | to; if (dts) - update_piece_threats(pc, from, dts); + update_piece_threats(pc, from, dts, fromTo); byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; @@ -381,7 +384,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts board[to] = pc; if (dts) - update_piece_threats(pc, to, dts); + update_piece_threats(pc, to, dts, fromTo); } inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { diff --git a/src/types.h b/src/types.h index 3aeb2bcc4..c6613f2f2 100644 --- a/src/types.h +++ b/src/types.h @@ -293,22 +293,31 @@ struct DirtyPiece { // Keep track of what threats change on the board (used by NNUE) struct DirtyThreat { + static constexpr int PcSqOffset = 0; + static constexpr int ThreatenedSqOffset = 8; + static constexpr int ThreatenedPcOffset = 16; + static constexpr int PcOffset = 20; + DirtyThreat() { /* don't initialize data */ } + DirtyThreat(uint32_t raw) : + data(raw) {} DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset) + | (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset); } - Piece pc() const { return static_cast(data >> 20 & 0xf); } - Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } - Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } - Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { return data >> 31; } + Piece pc() const { return static_cast(data >> 20 & 0xf); } + Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } + Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } + Square pc_sq() const { return static_cast(data & 0xff); } + bool add() const { return data >> 31; } + uint32_t raw() const { return data; } private: uint32_t data; }; -using DirtyThreatList = ValueList; +using DirtyThreatList = ValueList; // A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. // Moving a piece also can reveal at most 8 discovered attacks. From 5297ba0a1a1aa0a15332e0d64ce6b32952342cac Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 22 Nov 2025 12:43:34 -0500 Subject: [PATCH 794/834] 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 8e30f3dc3..c31a08c49 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 795/834] 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 049c72682..b08cabf3f 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 796/834] 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 a605ae417..f0161b74e 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 d21497280..886544b6c 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 db5f701e9..32f5b047e 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 b08cabf3f..092e40fd4 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 c31a08c49..5f60bafdc 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 797/834] 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 092e40fd4..3125d1f7a 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 798/834] 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 69ab49632..1ccab5f2f 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 3125d1f7a..cd778011f 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 7a029ce18..e5d3f6d28 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 c6613f2f2..1bb8bd3cc 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 799/834] 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 5f60bafdc..c9d30ce30 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 @@ moves_loop: // When in check, search starts here // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3 && !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 800/834] 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 136b4031e..039e384c9 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 f0161b74e..87004ead9 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 1f328fff4..091ae074d 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 c8ff60739..e3f7c0a18 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 f87d7a94d..58840a874 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 5840e2556..e82a1f6bf 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 5bd235823..be7de97d7 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 801/834] 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 32f5b047e..c9951e555 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 358a4b278..763fb7a5e 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 802/834] 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 cd778011f..a4286b86a 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 803/834] 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 f72451f5b..e9215269b 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 c2280f0b4..f0da51e7e 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 804/834] 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 c9d30ce30..64f85bcbe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1269,7 +1269,7 @@ moves_loop: // When in check, search starts here // 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 805/834] 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 853aeb5b2..8ed2eb994 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 091ae074d..ce23bdf0e 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 806/834] 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 953348987..d7f7dbdef 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 807/834] 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 64f85bcbe..adb7985d2 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 808/834] 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 7de11fa1f..d20ab151e 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 809/834] 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 9edff4864..40466c8f8 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 7315b8881..6fd1ce040 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 87004ead9..127fb9ef3 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 a4286b86a..7377b2029 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 e5d3f6d28..e49e10f96 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 adb7985d2..7c08bb0fa 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 f3d99d415..9644f6aa5 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 58840a874..eaf1d09bd 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 79376b10a..2368c3069 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 810/834] 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 7c08bb0fa..377b96343 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 811/834] 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 377b96343..9c52592e0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1241,7 +1241,7 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = 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 812/834] 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 | 196 ++++++++++++++++++-------- src/nnue/features/full_threats.h | 1 - src/syzygy/tbprobe.cpp | 1 + 6 files changed, 293 insertions(+), 173 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 6c97d7863..350e56c92 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 1cd717b8f..1da11d45c 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 6ab3507f3..107b5e43d 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 645bb7090..63b7f8e13 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,12 +62,119 @@ 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); + + std::array, SQUARE_NB> out{}; + + for (int from = 0; from < SQUARE_NB; ++from) + { + Bitboard attacks = PseudoAttacks[PT][Square(from)]; + + for (int to = 0; to < SQUARE_NB; ++to) + { + out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks); + } + } + + 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) + { + Bitboard attacks = PseudoAttacks[C][from]; + + for (int to = 0; to < SQUARE_NB; ++to) + { + 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; +} + +constexpr auto init_threat_offsets() { + std::array indices{}; + std::array, PIECE_NB> 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 = 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 += constexpr_popcount(attacks); + } + } + + indices[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; + + cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; + } + + 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{}; -static void init_index_luts() { for (Piece attacker : AllPieces) { for (Piece attacked : AllPieces) @@ -78,56 +189,21 @@ static void init_index_luts() { + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) * helper_offsets[attacker].cumulativePieceOffset; - bool excluded = map < 0; - index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); + bool excluded = map < 0; + indices[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); - } - } - } + return indices; } -void init_threat_offsets() { - int cumulativeOffset = 0; - for (Piece piece : AllPieces) - { - int pieceIdx = piece; - int cumulativePieceOffset = 0; +// The final index is calculated from summing data found in these two LUTs, as well +// as offsets[attacker][from] - 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); - } - } - - helper_offsets[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; - - cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; - } - - init_index_luts(); -} +// [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( diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index 177e9fabe..e4fa3331b 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 e3f7c0a18..c371259d6 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 813/834] 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 9c52592e0..c42fcd4ad 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 bbdab9889a6ed0999055f94c1db7ddf5de993b95 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Thu, 25 Dec 2025 23:01:08 +0100 Subject: [PATCH 814/834] Unbreak MPI compilation. 3c2c7b01bd deleted the TTCache default constructor, but we need it when MPI is active. This wasn't discovered during the merging, and it is hard to put it properly into the commit history due to rebase/rerere shortcomings. (This means that some of the merge commits will not compile with MPI on, and this patch needs to be added on top if bisecting.) --- src/tt.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tt.h b/src/tt.h index a92de2856..6676193f9 100644 --- a/src/tt.h +++ b/src/tt.h @@ -56,7 +56,12 @@ struct TTData { Bound bound; bool is_pv; +#ifdef USE_MPI + // We need this for TTCache to be constructible. + TTData() = default; +#else TTData() = delete; +#endif // clang-format off TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) : From cd3a8373243d145291abe9e546fd0396fc0e73fd Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 28 Dec 2025 14:48:56 +0100 Subject: [PATCH 815/834] 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 c42fcd4ad..8ac58fbab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1203,8 +1203,9 @@ moves_loop: // When in check, search starts here 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 816/834] 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 d20843e85..e7133f2df 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 817/834] 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 4e598ecfc..f8134a199 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 818/834] 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 8ac58fbab..41bbcd6d3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1242,7 +1242,7 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = 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 819/834] 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 7377b2029..cfbb85e84 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 41bbcd6d3..86b69db57 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 820/834] 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 86b69db57..95dd97196 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1222,6 +1222,10 @@ moves_loop: // When in check, search starts here // 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 821/834] 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 127fb9ef3..9811d0397 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 d20ab151e..15f64d1cb 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 5b3190594..b1e041c17 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 cfbb85e84..f32530fdd 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 95dd97196..87a96cab8 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 @@ moves_loop: // When in check, search starts here MovePicker mp(pos, ttData.move, depth, &mainHistory, &lowPlyHistory, &captureHistory, contHist, - &pawnHistory, ss->ply); + &sharedHistory, ss->ply); value = bestValue; @@ -1081,7 +1077,7 @@ moves_loop: // When in check, search starts here { 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 @@ moves_loop: // When in check, search starts here 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 9644f6aa5..eb4dda77c 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 822/834] 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 cc85ac78e..bf3fbe80f 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 823/834] 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 9811d0397..3aa9ef623 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 87a96cab8..c1a7d5880 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 824/834] 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 a8b5404b2..52abe8ec4 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 825/834] 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 c1a7d5880..05f9b47fa 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 826/834] 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 763fb7a5e..338d291e8 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 05f9b47fa..a880a741f 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 827/834] 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 781bd8070..86d222182 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 7805b24d6..473665ec2 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 bf3fbe80f..fa6297936 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 0f6dd7585..ef781a0f4 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 828/834] 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 85c0dbfec..789ee454e 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 829/834] 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 ce23bdf0e..98b031f6b 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 830/834] 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 63b7f8e13..9006d851b 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 831/834] 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 fa6297936..dcd3f1fea 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 039e384c9..4e266db89 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 d6bdc275d..a6606e78c 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 350e56c92..4decb8d66 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 1da11d45c..f97ff3219 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 40466c8f8..355103c7b 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 6fd1ce040..10c92d759 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 e7133f2df..745bd3e4d 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 8ed2eb994..b4f54a381 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 3aa9ef623..c98a7ee22 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 107b5e43d..9a7376efb 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 f4aa8fc26..94a599399 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 dad07df1d..c307a1317 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 886544b6c..3ddae503c 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 c9951e555..f1016b496 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 697a83cdf..d22faad25 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 287fd8927..7f209f92a 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 15f64d1cb..415d252f3 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 b1e041c17..08bd9a539 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 9006d851b..4e3ba81cf 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 e4fa3331b..5b2582954 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 56779ddce..a82e89de4 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 c58a3246b..49b0a87a4 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 7ead09327..a3d072f67 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 789ee454e..5e0551f69 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 a8679d14d..7284c1033 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 4218c0fe2..53412d014 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 a4d464df0..d1f2b14c3 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 ba8f469f7..d0e3218ca 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 338d291e8..16af8d5f6 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 1ccab5f2f..438074f43 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 5093abdd7..71fce9bd5 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 8a877ae2b..febe7ca70 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 98b031f6b..99cda2a69 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 220140e5e..66a6764a3 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 7ecfd58a2..ecece5589 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 6160221b9..25891163e 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 76d265af2..99169c211 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 e249a8e49..24d125cbf 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 f32530fdd..d8b02e8ab 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 e49e10f96..a136c0729 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 561bc23c4..ea62577b9 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 eda90af35..cf89d3cdd 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 a880a741f..afdda262c 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 eb4dda77c..202f7c8db 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 b870afc24..9bf13f234 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 52abe8ec4..1e344e93f 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 c371259d6..8db007194 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 4a6c3b763..7b60d6e20 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 eaf1d09bd..c485697da 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 2368c3069..f97e2b3f8 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 fb4b2ec97..5a8d43a2e 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 e82a1f6bf..4e98081bc 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 a2d1a4364..e72cc102a 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 d7f7dbdef..ef602809f 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 26b63c1ad..38f6c8f4f 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 f53a0eb52..f930c267e 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 d3c9ebaa3..4ce6e759f 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 1bb8bd3cc..b5a0498a8 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 be7de97d7..139d97b60 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 1686b3a72..c9b594393 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 ff6235695..8db796749 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 0c957fda1..4f6d7541c 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 bd3fd8ba790b32efe040d6c49257a37037c0046a Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Fri, 2 Jan 2026 09:24:56 +0100 Subject: [PATCH 832/834] Extract bestMove and ponderMove after the PV. Extracting ponderMove may have the unintentional effect of extending the PV by one move (which should not be displayed), so we need to extract it after we've extracted the PV. We still need to both before the MPI exchange, though. Patch by vondele, merely committed by me. --- src/search.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fd9151407..bc5c3c3a5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -246,12 +246,6 @@ void Search::Worker::start_searching() { main_manager()->bestPreviousScore = bestThread->rootMoves[0].score; main_manager()->bestPreviousAverageScore = bestThread->rootMoves[0].averageScore; - Move bestMove = bestThread->rootMoves[0].pv[0]; - Move ponderMove = Move::none(); - if (bestThread->rootMoves[0].pv.size() > 1 - || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) - ponderMove = bestThread->rootMoves[0].pv[1]; - // Temporarily switch out onUpdateFull to capture the PV information that we need, // so that we can exchange it through MPI. (We may end up not actually printing // it out.) @@ -264,6 +258,12 @@ void Search::Worker::start_searching() { assert(!serializedInfo.empty()); main_manager()->updates.onUpdateFull = std::move(oldOnUpdateFull); + Move bestMove = bestThread->rootMoves[0].pv[0]; + Move ponderMove = Move::none(); + if (bestThread->rootMoves[0].pv.size() > 1 + || bestThread->rootMoves[0].extract_ponder_from_tt(tt, rootPos)) + ponderMove = bestThread->rootMoves[0].pv[1]; + // Exchange info as needed Distributed::MoveInfo mi{bestMove.raw(), ponderMove.raw(), bestThread->completedDepth, bestThread->rootMoves[0].score, Distributed::rank()}; From d3a46e40b227f0cbd93228846beba3d94ce4d794 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Fri, 2 Jan 2026 09:27:28 +0100 Subject: [PATCH 833/834] Update cluster copyright year. No functional change. --- src/cluster.cpp | 2 +- src/cluster.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cluster.cpp b/src/cluster.cpp index dc117359b..8657617d1 100644 --- a/src/cluster.cpp +++ b/src/cluster.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 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/cluster.h b/src/cluster.h index 2763969fb..185cb0cf7 100644 --- a/src/cluster.h +++ b/src/cluster.h @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2020 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 7ed6af120a9becdfbf8ae9e7c45fb7dcd618d23e Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Fri, 2 Jan 2026 09:28:41 +0100 Subject: [PATCH 834/834] Fix -Wunused-parameter warning. When compiling the cluster branch but in non-MPI mode, we'd get some warnings in cluster.h about unused ThreadPool parameters. No functional change. --- src/cluster.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cluster.h b/src/cluster.h index 185cb0cf7..27fdcf0b3 100644 --- a/src/cluster.h +++ b/src/cluster.h @@ -147,8 +147,8 @@ uint64_t tb_hits(const ThreadPool&); uint64_t TT_saves(const ThreadPool&); inline void cluster_info(const ThreadPool&, Depth, TimePoint) {} inline void signals_init() {} -inline void signals_poll(ThreadPool& threads) {} -inline void signals_sync(ThreadPool& threads) {} +inline void signals_poll(ThreadPool&) {} +inline void signals_sync(ThreadPool&) {} #endif /* USE_MPI */