X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=inline;f=src%2Fevaluate.cpp;h=a5c049a88adb8446cb14720a51b3ab109effa45d;hb=HEAD;hp=25a6545564a1c4a065e265f5babbec282733394a;hpb=3c0e86a91e48baea273306e45fb6cf13a59373cf;p=stockfish diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 25a65455..221ccde8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -1,6 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file) + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,191 +20,110 @@ #include #include +#include #include -#include #include #include +#include #include -#include +#include -#include "incbin/incbin.h" -#include "misc.h" -#include "nnue/evaluate_nnue.h" +#include "nnue/network.h" +#include "nnue/nnue_misc.h" #include "position.h" -#include "thread.h" #include "types.h" #include "uci.h" - -// Macro to embed the default efficiently updatable neural network (NNUE) file -// data in the engine binary (using incbin.h, by Dale Weiler). -// This macro invocation will declare the following three variables -// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data -// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end -// const unsigned int gEmbeddedNNUESize; // the size of the embedded file -// Note that this does not work in Microsoft Visual Studio. -#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) - INCBIN(EmbeddedNNUE, EvalFileDefaultName); -#else - const unsigned char gEmbeddedNNUEData[1] = {0x0}; - const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; - const unsigned int gEmbeddedNNUESize = 1; -#endif - - -using namespace std; +#include "nnue/nnue_accumulator.h" namespace Stockfish { -namespace Eval { - - string currentEvalFileName = "None"; - - /// NNUE::init() tries to load a NNUE network at startup time, or when the engine - /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" - /// The name of the NNUE network is always retrieved from the EvalFile option. - /// We search the given network in three locations: internally (the default - /// network may be embedded in the binary), in the active working directory and - /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY - /// variable to have the engine search in a special directory in their distro. - - void NNUE::init() { - - string eval_file = string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; - - #if defined(DEFAULT_NNUE_DIRECTORY) - vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; - #else - vector dirs = { "" , "" , CommandLine::binaryDirectory }; - #endif - - for (const string& directory : dirs) - if (currentEvalFileName != eval_file) - { - if (directory != "") - { - ifstream stream(directory + eval_file, ios::binary); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; - } - - if (directory == "" && eval_file == EvalFileDefaultName) - { - // C++ way to prepare a buffer for a memory stream - class MemoryBuffer : public basic_streambuf { - public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } - }; - - MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), - size_t(gEmbeddedNNUESize)); - (void) gEmbeddedNNUEEnd; // Silence warning on unused variable - - istream stream(&buffer); - if (NNUE::load_eval(eval_file, stream)) - currentEvalFileName = eval_file; - } - } - } - - /// NNUE::verify() verifies that the last net used was loaded successfully - void NNUE::verify() { - - string eval_file = string(Options["EvalFile"]); - if (eval_file.empty()) - eval_file = EvalFileDefaultName; - - if (currentEvalFileName != eval_file) - { - - string msg1 = "Network evaluation parameters compatible with the engine must be available."; - string msg2 = "The network file " + eval_file + " was not loaded successfully."; - string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; - string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName); - string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - exit(EXIT_FAILURE); - } - - sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl; - } +// Returns a static, purely materialistic evaluation of the position from +// the point of view of the given color. It can be divided by PawnValue to get +// an approximation of the material advantage on the board in terms of pawns. +int Eval::simple_eval(const Position& pos, Color c) { + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } -/// evaluate() is the evaluator for the outer world. It returns a static -/// evaluation of the position from the point of view of the side to move. +bool Eval::use_smallnet(const Position& pos) { + int simpleEval = simple_eval(pos, pos.side_to_move()); + return std::abs(simpleEval) > 962; +} -Value Eval::evaluate(const Position& pos) { +// 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, + Eval::NNUE::AccumulatorCaches& caches, + int optimism) { - assert(!pos.checkers()); + assert(!pos.checkers()); - Value v; + bool smallNet = use_smallnet(pos); + int v; - int nnueComplexity; - int npm = pos.non_pawn_material() / 64; + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) + : networks.big.evaluate(pos, &caches.big); - Color stm = pos.side_to_move(); - Value optimism = pos.this_thread()->optimism[stm]; + Value nnue = (125 * psqt + 131 * positional) / 128; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + // Re-evaluate the position when higher eval accuracy is worth the time spent + 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; + smallNet = false; + } - int material = pos.non_pawn_material(stm) - pos.non_pawn_material(~stm) - + 126 * (pos.count(stm) - pos.count(~stm)); + // 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); - // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + abs(material - nnue)) / 512; - nnue -= nnue * (nnueComplexity + abs(material - nnue)) / 32768; + int material = (smallNet ? 553 : 532) * pos.count() + pos.non_pawn_material(); + v = (nnue * (73921 + material) + optimism * (8112 + material)) / (smallNet ? 68104 : 74715); - v = ( nnue * (915 + npm + 9 * pos.count()) - + optimism * (154 + npm + pos.count())) / 1024; + // 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 * (200 - pos.rule50_count()) / 214; + // Damp down the evaluation linearly when shuffling + 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); + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - return v; + return v; } -/// trace() is like evaluate(), but instead of returning a value, it returns -/// a string (suitable for outputting to stdout) that contains the detailed -/// descriptions and values of each evaluation term. Useful for debugging. -/// Trace scores are from white's point of view - -std::string Eval::trace(Position& pos) { +// Like evaluate(), but instead of returning a value, it returns +// a string (suitable for outputting to stdout) that contains the detailed +// descriptions and values of each evaluation term. Useful for debugging. +// Trace scores are from white's point of view +std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { - if (pos.checkers()) - return "Final evaluation: none (in check)"; + if (pos.checkers()) + return "Final evaluation: none (in check)"; - // Reset any global variable used in eval - pos.this_thread()->bestValue = VALUE_ZERO; - pos.this_thread()->optimism[WHITE] = VALUE_ZERO; - pos.this_thread()->optimism[BLACK] = VALUE_ZERO; + auto caches = std::make_unique(networks); - std::stringstream ss; - ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; - ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; + 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(pos); - v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; - ss << " [with scaled NNUE, ...]"; - ss << "\n"; + v = evaluate(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, ...]"; + ss << "\n"; - return ss.str(); + return ss.str(); } -} // namespace Stockfish +} // namespace Stockfish