/* 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 "evaluate.h" #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/nnue_architecture.h" #include "position.h" #include "types.h" #include "uci.h" #include "ucioption.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). // This macro invocation will declare the following three variables // const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data // const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); #else const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; const unsigned int gEmbeddedNNUEBigSize = 1; const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; const unsigned int gEmbeddedNNUESmallSize = 1; #endif namespace Stockfish { namespace Eval { // Tries to load a NNUE network at startup time, or when the engine // receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" // The name of the NNUE network is always retrieved from the EvalFile option. // We search the given network in three locations: internally (the default // network may be embedded in the binary), in the active working directory and // in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY // variable to have the engine search in a special directory in their distro. NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, const OptionsMap& options, NNUE::EvalFiles evalFiles) { for (auto& [netSize, evalFile] : evalFiles) { // Replace with // options[evalFile.optionName] // once fishtest supports the uci option EvalFileSmall std::string user_eval_file = netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; if (user_eval_file.empty()) user_eval_file = evalFile.defaultName; #if defined(DEFAULT_NNUE_DIRECTORY) std::vector dirs = {"", "", rootDirectory, stringify(DEFAULT_NNUE_DIRECTORY)}; #else std::vector dirs = {"", "", rootDirectory}; #endif for (const std::string& directory : dirs) { if (evalFile.current != user_eval_file) { if (directory != "") { std::ifstream stream(directory + user_eval_file, std::ios::binary); auto description = NNUE::load_eval(stream, netSize); if (description.has_value()) { evalFile.current = user_eval_file; evalFile.netDescription = description.value(); } } if (directory == "" && user_eval_file == evalFile.defaultName) { // C++ way to prepare a buffer for a memory stream class MemoryBuffer: public std::basic_streambuf { public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } }; MemoryBuffer buffer( const_cast(reinterpret_cast( netSize == Small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)), size_t(netSize == Small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize)); (void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable (void) gEmbeddedNNUESmallEnd; std::istream stream(&buffer); auto description = NNUE::load_eval(stream, netSize); if (description.has_value()) { evalFile.current = user_eval_file; evalFile.netDescription = description.value(); } } } } } return evalFiles; } // Verifies that the last net used was loaded successfully void NNUE::verify(const OptionsMap& options, const std::unordered_map& evalFiles) { for (const auto& [netSize, evalFile] : evalFiles) { // Replace with // options[evalFile.optionName] // once fishtest supports the uci option EvalFileSmall std::string user_eval_file = netSize == Small ? evalFile.defaultName : options[evalFile.optionName]; if (user_eval_file.empty()) user_eval_file = evalFile.defaultName; if (evalFile.current != user_eval_file) { std::string msg1 = "Network evaluation parameters compatible with the engine must be available."; std::string msg2 = "The network file " + user_eval_file + " was not loaded successfully."; std::string msg3 = "The UCI option EvalFile might need to specify the full path, " "including the directory name, to the network file."; std::string msg4 = "The default net can be downloaded from: " "https://tests.stockfishchess.org/api/nn/" + evalFile.defaultName; std::string msg5 = "The engine will be terminated now."; sync_cout << "info string ERROR: " << msg1 << sync_endl; sync_cout << "info string ERROR: " << msg2 << sync_endl; sync_cout << "info string ERROR: " << msg3 << sync_endl; sync_cout << "info string ERROR: " << msg4 << sync_endl; sync_cout << "info string ERROR: " << msg5 << sync_endl; exit(EXIT_FAILURE); } sync_cout << "info string NNUE evaluation using " << user_eval_file << sync_endl; } } } // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. 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. Value Eval::evaluate(const Position& pos, int optimism) { assert(!pos.checkers()); int v; Color stm = pos.side_to_move(); int shuffling = pos.rule50_count(); int simpleEval = simple_eval(pos, stm); bool lazy = std::abs(simpleEval) > 2550; if (lazy) v = simpleEval; else { bool smallNet = std::abs(simpleEval) > 1050; int nnueComplexity; Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity) : NNUE::evaluate(pos, true, &nnueComplexity); // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768; int npm = pos.non_pawn_material() / 64; v = (nnue * (915 + npm + 9 * pos.count()) + optimism * (154 + npm)) / 1024; } // Damp down the evaluation linearly when shuffling v = v * (200 - shuffling) / 214; // Guarantee evaluation does not hit the tablebase range v = std::clamp(int(v), VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); return v; } // Like evaluate(), but instead of returning a value, it returns // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view std::string Eval::trace(Position& pos) { if (pos.checkers()) return "Final evaluation: none (in check)"; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); ss << '\n' << NNUE::trace(pos) << '\n'; ss << 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"; v = evaluate(pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; ss << "\n"; return ss.str(); } } // namespace Stockfish