diff --git a/src/Makefile b/src/Makefile index c53e5f22..c46edf4f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,16 +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 \ - nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp \ + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \ external/zip.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h magics.h \ - nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ + nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h \ + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.cpp \ external/zip.h external/miniz.h OBJS = $(notdir $(SRCS:.cpp=.o)) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 54d30a7a..8c703672 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -22,99 +22,18 @@ #include #include #include -#include #include #include -#include #include -#include -#include "misc.h" -#include "nnue/evaluate_nnue.h" +#include "nnue/network.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "types.h" #include "uci.h" -#include "ucioption.h" namespace Stockfish { -namespace Eval { - -std::string currentEvalFileName = "None"; - -// Tries to load a NNUE network at startup time, or when the engine -// receives a UCI command "setoption name EvalFile value .*.nnue" -// The name of the NNUE network is always retrieved from the EvalFile option. -// We search the given network in two locations: in the active working directory and -// in the engine directory. -EvalFile NNUE::load_networks(const std::string& rootDirectory, - const OptionsMap& options, - EvalFile evalFile) { - - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - - std::vector dirs = {"", rootDirectory}; - - for (const std::string& directory : dirs) - if (evalFile.current != user_eval_file) - { - std::stringstream ss = read_zipped_nnue(directory + user_eval_file); - auto description = NNUE::load_eval(ss); - if (!description.has_value()) - { - std::ifstream stream(directory + user_eval_file, std::ios::binary); - description = NNUE::load_eval(stream); - } - - if (description.has_value()) - { - evalFile.current = user_eval_file; - evalFile.netDescription = description.value(); - } - } - - return evalFile; -} - -// Verifies that the last net used was loaded successfully -void NNUE::verify(const OptionsMap& options, const EvalFile& evalFile) { - - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - - if (evalFile.current != user_eval_file) - { - - std::string msg1 = - "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = "The network file " + user_eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, " - "including the directory name, to the network file."; - std::string msg4 = - "The default net can be downloaded from: " - "https://github.com/official-pikafish/Networks/releases/download/master-net/" - + 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 << " enabled" << 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. @@ -127,7 +46,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, int optimism) { +Value Eval::evaluate(const Eval::NNUE::Network& network, const Position& pos, int optimism) { assert(!pos.checkers()); @@ -137,7 +56,7 @@ Value Eval::evaluate(const Position& pos, int optimism) { int simpleEval = simple_eval(pos, stm); int nnueComplexity; - Value nnue = NNUE::evaluate(pos, true, &nnueComplexity); + Value nnue = network.evaluate(pos, true, &nnueComplexity); // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 781; @@ -159,7 +78,7 @@ Value Eval::evaluate(const Position& pos, int optimism) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos) { +std::string Eval::trace(Position& pos, const Eval::NNUE::Network& network) { if (pos.checkers()) return "Final evaluation: none (in check)"; @@ -167,16 +86,15 @@ std::string Eval::trace(Position& pos) { std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + ss << '\n' << NNUE::trace(pos, network) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos); - v = pos.side_to_move() == WHITE ? v : -v; + Value v = network.evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos, VALUE_ZERO); + v = evaluate(network, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index 912957fe..60fea5f6 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -26,37 +26,23 @@ namespace Stockfish { class Position; -class OptionsMap; namespace Eval { -std::string trace(Position& pos); - -int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos, int optimism); - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the -// name of the macro, as it is used in the Makefile. +// name of the macro or the location where this macro is defined, as it is used +// in the Makefile/Fishtest. #define EvalFileDefaultName "pikafish.nnue" -struct EvalFile { - // UCI option name - std::string optionName; - // Default net name, will use one of the macros above - std::string defaultName; - // Selected net name, either via uci option or default - std::string current; - // Net description extracted from the net file - std::string netDescription; -}; - namespace NNUE { +class Network; +} -EvalFile load_networks(const std::string&, const OptionsMap&, EvalFile); -void verify(const OptionsMap&, const EvalFile&); +std::string trace(Position& pos, const Eval::NNUE::Network& network); -} // namespace NNUE +int simple_eval(const Position& pos, Color c); +Value evaluate(const NNUE::Network& network, const Position& pos, int optimism); } // namespace Eval diff --git a/src/main.cpp b/src/main.cpp index 2f120ba2..a06f0b66 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,7 +16,6 @@ #include #include "bitboard.h" -#include "evaluate.h" #include "misc.h" #include "position.h" #include "tune.h" @@ -36,8 +35,6 @@ int main(int argc, char* argv[]) { Tune::init(uci.options); - uci.evalFile = Eval::NNUE::load_networks(uci.working_directory(), uci.options, uci.evalFile); - uci.loop(); return 0; diff --git a/src/misc.h b/src/misc.h index 75424bb9..20526189 100644 --- a/src/misc.h +++ b/src/misc.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,30 @@ void aligned_large_pages_free(void* mem); std::stringstream read_zipped_nnue(const std::string& fpath); +// Deleter for automating release of memory area +template +struct AlignedDeleter { + void operator()(T* ptr) const { + ptr->~T(); + std_aligned_free(ptr); + } +}; + +template +struct LargePageDeleter { + void operator()(T* ptr) const { + ptr->~T(); + aligned_large_pages_free(ptr); + } +}; + +template +using AlignedPtr = std::unique_ptr>; + +template +using LargePagePtr = std::unique_ptr>; + + void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); void dbg_stdev_of(int64_t value, int slot = 0); diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h deleted file mode 100644 index e773ae9b..00000000 --- a/src/nnue/evaluate_nnue.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// header used in NNUE evaluation function - -#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED -#define NNUE_EVALUATE_NNUE_H_INCLUDED - -#include -#include -#include -#include -#include - -#include "../evaluate.h" -#include "../misc.h" -#include "../types.h" -#include "nnue_architecture.h" -#include "nnue_feature_transformer.h" - -namespace Stockfish { -class Position; -} - -namespace Stockfish::Eval::NNUE { - -// Hash value of evaluation function structure -constexpr std::uint32_t HashValue = - FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); - - -// Deleter for automating release of memory area -template -struct AlignedDeleter { - void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); - } -}; - -template -struct LargePageDeleter { - void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); - } -}; - -template -using AlignedPtr = std::unique_ptr>; - -template -using LargePagePtr = std::unique_ptr>; - -std::string trace(Position& pos); -Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr); -void hint_common_parent_position(const Position& pos); - -std::optional load_eval(std::istream& stream); -bool save_eval(std::ostream& stream, const std::string& name, const std::string& netDescription); -bool save_eval(const std::optional& filename, const EvalFile&); - -} // namespace Stockfish::Eval::NNUE - -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/network.cpp similarity index 51% rename from src/nnue/evaluate_nnue.cpp rename to src/nnue/network.cpp index f7c6c3be..c18b29ad 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/network.cpp @@ -1,51 +1,40 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along with this program. If not, see . */ -// Code for calculating NNUE evaluation function - -#include "evaluate_nnue.h" +#include "network.h" #include #include #include #include -#include #include -#include #include -#include +#include #include +#include #include "../evaluate.h" #include "../misc.h" #include "../position.h" #include "../types.h" -#include "../uci.h" -#include "nnue_accumulator.h" +#include "nnue_architecture.h" #include "nnue_common.h" +#include "nnue_misc.h" -namespace Stockfish::Eval::NNUE { - -// Input feature converter -LargePagePtr featureTransformer; -// Evaluation function -AlignedPtr network[LayerStacks]; +namespace Stockfish::Eval::NNUE { namespace Detail { @@ -88,73 +77,57 @@ bool write_parameters(std::ostream& stream, const T& reference) { } // namespace Detail -// Initialize the evaluation function parameters -static void initialize() { - - Detail::initialize(featureTransformer); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(network[i]); -} +void Network::load(const std::string& rootDirectory, std::string evalfilePath) { +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", rootDirectory, stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", rootDirectory}; +#endif -// Read network header -static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { - std::uint32_t version, size; + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; - version = read_little_endian(stream); - *hashValue = read_little_endian(stream); - size = read_little_endian(stream); - if (!stream || version != Version) - return false; - desc->resize(size); - stream.read(&(*desc)[0], size); - return !stream.fail(); + for (const auto& directory : dirs) + { + if (evalFile.current != evalfilePath) + { + load_user_net(directory, evalfilePath); + } + } } -// Write network header -static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { - write_little_endian(stream, Version); - write_little_endian(stream, hashValue); - write_little_endian(stream, std::uint32_t(desc.size())); - stream.write(&desc[0], desc.size()); - return !stream.fail(); -} -// Read network parameters -static bool read_parameters(std::istream& stream, std::string& netDescription) { +bool Network::save(const std::optional& filename) const { + std::string actualFilename; + std::string msg; - std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) - return false; - if (hashValue != HashValue) - return false; - if (!Detail::read_parameters(stream, *featureTransformer)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::read_parameters(stream, *(network[i]))) + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (evalFile.current != evalFile.defaultName) + { + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; return false; - return stream && stream.peek() == std::ios::traits_type::eof(); -} + } -// Write network parameters -static bool write_parameters(std::ostream& stream, const std::string& netDescription) { + actualFilename = evalFile.defaultName; + } - if (!write_header(stream, HashValue, netDescription)) - return false; - if (!Detail::write_parameters(stream, *featureTransformer)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - if (!Detail::write_parameters(stream, *(network[i]))) - return false; - return bool(stream); -} + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save(stream, evalFile.current, evalFile.netDescription); -void hint_common_parent_position(const Position& pos) { - featureTransformer->hint_common_access(pos); + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; } -// Evaluation function. Perform differential calculation. -Value evaluate(const Position& pos, bool adjusted, int* complexity) { +Value Network::evaluate(const Position& pos, bool adjusted, 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. @@ -186,16 +159,42 @@ Value evaluate(const Position& pos, bool adjusted, int* complexity) { return static_cast((psqt + positional) / OutputScale); } -struct NnueEvalTrace { - static_assert(LayerStacks == PSQTBuckets); - Value psqt[LayerStacks]; - Value positional[LayerStacks]; - std::size_t correctBucket; -}; +void Network::verify(std::string evalfilePath) const { + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + if (evalFile.current != evalfilePath) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = + "The default net can be downloaded from: " + "https://github.com/official-pikafish/Networks/releases/download/master-net/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + exit(EXIT_FAILURE); + } + + sync_cout << "info string NNUE evaluation using " << evalfilePath << sync_endl; +} + + +void Network::hint_common_access(const Position& pos) const { + featureTransformer->hint_common_access(pos); +} -static NnueEvalTrace trace_evaluate(const Position& pos) { +NnueEvalTrace Network::trace_evaluate(const Position& pos) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; @@ -226,200 +225,105 @@ static NnueEvalTrace trace_evaluate(const Position& pos) { return t; } -constexpr std::string_view PieceToChar(" RACPNBK racpnbk"); - - -// Converts a Value into (centi)pawns and writes it in a buffer. -// The buffer must have capacity for at least 5 chars. -static void format_cp_compact(Value v, char* buffer) { - buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); +void Network::load_user_net(const std::string& dir, const std::string& evalfilePath) { + std::stringstream sstream = read_zipped_nnue(dir + evalfilePath); + auto description = load(sstream); - int cp = std::abs(UCI::to_cp(v)); - if (cp >= 10000) + if (!description.has_value()) { - buffer[1] = '0' + cp / 10000; - cp %= 10000; - buffer[2] = '0' + cp / 1000; - cp %= 1000; - buffer[3] = '0' + cp / 100; - buffer[4] = ' '; + std::ifstream stream(dir + evalfilePath, std::ios::binary); + description = load(stream); } - else if (cp >= 1000) - { - buffer[1] = '0' + cp / 1000; - cp %= 1000; - buffer[2] = '0' + cp / 100; - cp %= 100; - buffer[3] = '.'; - buffer[4] = '0' + cp / 10; - } - else + + if (description.has_value()) { - buffer[1] = '0' + cp / 100; - cp %= 100; - buffer[2] = '.'; - buffer[3] = '0' + cp / 10; - cp %= 10; - buffer[4] = '0' + cp / 1; + evalFile.current = evalfilePath; + evalFile.netDescription = description.value(); } } -// Converts a Value into pawns, always keeping two decimals -static void format_cp_aligned_dot(Value v, std::stringstream& stream) { - - const double pawns = std::abs(0.01 * UCI::to_cp(v)); - - stream << (v < 0 ? '-' - : v > 0 ? '+' - : ' ') - << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +void Network::initialize() { + Detail::initialize(featureTransformer); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(network[i]); } -// Returns a string with the value of each piece on a board, -// and a table for (PSQT, Layers) values bucket by bucket. -std::string trace(Position& pos) { - - std::stringstream ss; - - char board[3 * RANK_NB + 1][8 * FILE_NB + 2]; - std::memset(board, ' ', sizeof(board)); - for (int row = 0; row < 3 * RANK_NB + 1; ++row) - board[row][8 * FILE_NB + 1] = '\0'; - - // A lambda to output one box of the board - auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - const int x = int(file) * 8; - const int y = (RANK_9 - int(rank)) * 3; - for (int i = 1; i < 8; ++i) - board[y][x + i] = board[y + 3][x + i] = '-'; - for (int i = 1; i < 3; ++i) - board[y + i][x] = board[y + i][x + 8] = '|'; - board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; - if (pc != NO_PIECE) - board[y + 1][x + 4] = PieceToChar[pc]; - if (value != VALUE_NONE) - format_cp_compact(value, &board[y + 2][x + 2]); - }; - - // We estimate the value of each piece by doing a differential evaluation from - // the current base eval, simulating the removal of the piece from its square. - Value base = evaluate(pos); - base = pos.side_to_move() == WHITE ? base : -base; - - for (File f = FILE_A; f <= FILE_I; ++f) - for (Rank r = RANK_0; r <= RANK_9; ++r) - { - Square sq = make_square(f, r); - Piece pc = pos.piece_on(sq); - Value v = VALUE_NONE; - - if (pc != NO_PIECE && type_of(pc) != KING) - { - auto st = pos.state(); - - pos.remove_piece(sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - - Value eval = evaluate(pos); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; - - pos.put_piece(pc, sq); - st->accumulator.computed[WHITE] = false; - st->accumulator.computed[BLACK] = false; - } +bool Network::save(std::ostream& stream, + const std::string& name, + const std::string& netDescription) const { + if (name.empty() || name == "None") + return false; - writeSquare(f, r, pc, v); - } + return write_parameters(stream, netDescription); +} - ss << " NNUE derived piece values:\n"; - for (int row = 0; row < 3 * RANK_NB + 1; ++row) - ss << board[row] << '\n'; - ss << '\n'; - auto t = trace_evaluate(pos); +std::optional Network::load(std::istream& stream) { + initialize(); + std::string description; - ss << " NNUE network contributions " - << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl - << "+------------+------------+------------+------------+\n" - << "| Bucket | Material | Positional | Total |\n" - << "| | (PSQT) | (Layers) | |\n" - << "+------------+------------+------------+------------+\n"; + return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt; +} - for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) - { - ss << "| " << bucket << " "; - ss << " | "; - format_cp_aligned_dot(t.psqt[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.positional[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); - ss << " " - << " |"; - if (bucket == t.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; - } - ss << "+------------+------------+------------+------------+\n"; +// Read network header +bool Network::read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) const { + std::uint32_t version, size; - return ss.str(); + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); } -// Load eval, from a file stream or a memory stream -std::optional load_eval(std::istream& stream) { - - initialize(); - std::string netDescription; - return read_parameters(stream, netDescription) ? std::make_optional(netDescription) - : std::nullopt; +// Write network header +bool Network::write_header(std::ostream& stream, + std::uint32_t hashValue, + const std::string& desc) const { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, std::uint32_t(desc.size())); + stream.write(&desc[0], desc.size()); + return !stream.fail(); } -// Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream, const std::string& name, const std::string& netDescription) { - if (name.empty() || name == "None") +bool Network::read_parameters(std::istream& stream, std::string& netDescription) const { + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) return false; - - return write_parameters(stream, netDescription); + if (hashValue != Network::hash) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::read_parameters(stream, *(network[i]))) + return false; + } + return stream && stream.peek() == std::ios::traits_type::eof(); } -// Save eval, to a file given by its name -bool save_eval(const std::optional& filename, const EvalFile& evalFile) { - std::string actualFilename; - std::string msg; - if (filename.has_value()) - actualFilename = filename.value(); - else +bool Network::write_parameters(std::ostream& stream, const std::string& netDescription) const { + if (!write_header(stream, Network::hash, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) { - if (evalFile.current != EvalFileDefaultName) - { - msg = "Failed to export a net. " - "A net can only be saved if the filename is specified"; - - sync_cout << msg << sync_endl; + if (!Detail::write_parameters(stream, *(network[i]))) return false; - } - actualFilename = EvalFileDefaultName; } - - std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream, evalFile.current, evalFile.netDescription); - - msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; - - sync_cout << msg << sync_endl; - return saved; + return bool(stream); } - } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h new file mode 100644 index 00000000..1d92f243 --- /dev/null +++ b/src/nnue/network.h @@ -0,0 +1,80 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NETWORK_H_INCLUDED +#define NETWORK_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_feature_transformer.h" +#include "nnue_misc.h" + +namespace Stockfish::Eval::NNUE { + +class Network { + public: + Network(EvalFile file) : + evalFile(file) {} + + 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) const; + + + void hint_common_access(const Position& pos) const; + + void verify(std::string evalfilePath) const; + NnueEvalTrace trace_evaluate(const Position& pos) const; + + private: + void load_user_net(const std::string&, const std::string&); + + void initialize(); + + bool save(std::ostream&, const std::string&, const std::string&) const; + std::optional load(std::istream&); + + bool read_header(std::istream&, std::uint32_t*, std::string*) const; + bool write_header(std::ostream&, std::uint32_t, const std::string&) const; + + bool read_parameters(std::istream&, std::string&) const; + bool write_parameters(std::ostream&, const std::string&) const; + + // Input feature converter + LargePagePtr featureTransformer; + + // Evaluation function + AlignedPtr network[LayerStacks]; + + EvalFile evalFile; + + // Hash value of evaluation function structure + static constexpr std::uint32_t hash = + FeatureTransformer::get_hash_value() ^ NetworkArchitecture::get_hash_value(); +}; + +} // namespace Stockfish + +#endif diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index 3ee79403..5a679028 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -42,7 +42,7 @@ constexpr IndexType TransformedFeatureDimensions = 1024; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; -struct Network { +struct NetworkArchitecture { static constexpr int FC_0_OUTPUTS = 15; static constexpr int FC_1_OUTPUTS = 32; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp new file mode 100644 index 00000000..4ca17e18 --- /dev/null +++ b/src/nnue/nnue_misc.cpp @@ -0,0 +1,193 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include "nnue_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../position.h" +#include "../types.h" +#include "../uci.h" +#include "network.h" +#include "nnue_accumulator.h" + +namespace Stockfish::Eval::NNUE { + + +constexpr std::string_view PieceToChar(" RACPNBK racpnbk"); + + +void hint_common_parent_position(const Position& pos, const Network& network) { + + network.hint_common_access(pos); +} + + +// Converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +static void format_cp_compact(Value v, char* buffer) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(UCI::to_cp(v)); + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; + buffer[3] = '0' + cp / 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; + cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; + cp %= 10; + buffer[4] = '0' + cp / 1; + } +} + + +// Converts a Value into pawns, always keeping two decimals +static void format_cp_aligned_dot(Value v, std::stringstream& stream) { + + const double pawns = std::abs(0.01 * UCI::to_cp(v)); + + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} + + +// Returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string trace(Position& pos, const Eval::NNUE::Network& network) { + + std::stringstream ss; + + char board[3 * RANK_NB + 1][8 * FILE_NB + 2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3 * RANK_NB + 1; ++row) + board[row][8 * FILE_NB + 1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { + const int x = int(file) * 8; + const int y = (RANK_9 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y + 2][x + 2]); + }; + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + Value base = network.evaluate(pos); + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= FILE_I; ++f) + for (Rank r = RANK_0; r <= RANK_9; ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); + + pos.remove_piece(sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + + Value eval = network.evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3 * RANK_NB + 1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + auto t = network.trace_evaluate(pos); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + ss << "| " << bucket << " "; + ss << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + ss << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); +} + + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h new file mode 100644 index 00000000..ce9960a3 --- /dev/null +++ b/src/nnue/nnue_misc.h @@ -0,0 +1,60 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_MISC_H_INCLUDED +#define NNUE_MISC_H_INCLUDED + +#include +#include + +#include "../types.h" +#include "nnue_architecture.h" + +namespace Stockfish { + +class Position; + +namespace Eval::NNUE { + +struct EvalFile { + // Default net name, will use one of the EvalFileDefaultName* macros defined + // in evaluate.h + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; + + +struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; +}; + + +class Network; + + +std::string trace(Position& pos, const Network& network); +void hint_common_parent_position(const Position& pos, const Network& network); + +} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish + +#endif // #ifndef NNUE_MISC_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 1557ba47..56b95531 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -33,8 +33,8 @@ #include "misc.h" #include "movegen.h" #include "movepick.h" -#include "nnue/evaluate_nnue.h" #include "nnue/nnue_common.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "thread.h" #include "timeman.h" @@ -107,7 +107,8 @@ Search::Worker::Worker(SharedState& sharedState, manager(std::move(sm)), options(sharedState.options), threads(sharedState.threads), - tt(sharedState.tt) { + tt(sharedState.tt), + network(sharedState.network) { clear(); } @@ -513,8 +514,9 @@ Value Search::Worker::search( } if (threads.stop.load(std::memory_order_relaxed) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(network, pos, thisThread->optimism[us]) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -594,7 +596,7 @@ Value Search::Worker::search( { // Providing the hint that this node's accumulator will be used often // brings significant Elo gain (~13 Elo). - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, network); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -602,9 +604,9 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(network, pos, thisThread->optimism[us]); else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, network); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -614,7 +616,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(network, pos, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -771,7 +773,7 @@ Value Search::Worker::search( } } - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, network); } moves_loop: // When in check, search starts here @@ -1309,7 +1311,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, } if (ss->ply >= MAX_PLY) - return !ss->inCheck ? evaluate(pos, thisThread->optimism[us]) : VALUE_DRAW; + return !ss->inCheck ? evaluate(network, pos, thisThread->optimism[us]) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1340,7 +1342,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(network, pos, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1353,7 +1355,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = (ss - 1)->currentMove != Move::null() - ? evaluate(pos, thisThread->optimism[us]) + ? evaluate(network, pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); diff --git a/src/search.h b/src/search.h index 95e88a07..42f4d175 100644 --- a/src/search.h +++ b/src/search.h @@ -25,8 +25,8 @@ #include #include #include -#include #include +#include #include "misc.h" #include "movepick.h" @@ -36,6 +36,10 @@ namespace Stockfish { +namespace Eval::NNUE { +class Network; +} + // Different node types, used as a template parameter enum NodeType { NonPV, @@ -121,16 +125,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) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const Eval::NNUE::Network& net) : options(optionsMap), threads(threadPool), - tt(transpositionTable) {} + tt(transpositionTable), + network(net) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Network& network; }; class Worker; @@ -172,6 +179,7 @@ class NullSearchManager: public ISearchManager { void check_time(Search::Worker&) override {} }; + // Search::Worker is the class that does the actual search. // It is instantiated once per thread, and it is responsible for keeping track // of the search history, and storing data required for the search. @@ -241,9 +249,10 @@ class Worker { // The main thread has a SearchManager, the others have a NullSearchManager std::unique_ptr manager; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Network& network; friend class Stockfish::ThreadPool; friend class SearchManager; diff --git a/src/thread.cpp b/src/thread.cpp index 6b49427f..01cfe432 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -19,12 +19,12 @@ #include "thread.h" #include +#include #include #include #include #include #include -#include #include "misc.h" #include "movegen.h" @@ -61,6 +61,7 @@ Thread::~Thread() { stdThread.join(); } + // Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -107,6 +108,12 @@ void Thread::idle_loop() { } } +Search::SearchManager* ThreadPool::main_manager() { + return static_cast(main_thread()->worker.get()->manager.get()); +} + +uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); } + // 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. @@ -158,9 +165,7 @@ void ThreadPool::clear() { // Wakes up main thread waiting in idle_loop() and // returns immediately. Main thread will wake up other threads and start the search. -void ThreadPool::start_thinking(Position& pos, - StateListPtr& states, - Search::LimitsType limits) { +void ThreadPool::start_thinking(Position& pos, StateListPtr& states, Search::LimitsType limits) { main_thread()->wait_for_search_finished(); diff --git a/src/thread.h b/src/thread.h index 4a6a500a..9196352d 100644 --- a/src/thread.h +++ b/src/thread.h @@ -33,6 +33,7 @@ namespace Stockfish { + class OptionsMap; using Value = int; @@ -83,14 +84,12 @@ class ThreadPool { void clear(); void set(Search::SharedState); - Search::SearchManager* main_manager() const { - return static_cast(main_thread()->worker.get()->manager.get()); - }; - Thread* main_thread() const { return threads.front(); } - uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; + Search::SearchManager* main_manager(); + Thread* main_thread() const { return threads.front(); } + uint64_t nodes_searched() const; + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; std::atomic_bool stop, abortedSearch, increaseDepth; diff --git a/src/uci.cpp b/src/uci.cpp index 7f803224..956414a8 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,23 +22,24 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include "benchmark.h" #include "evaluate.h" #include "movegen.h" -#include "nnue/evaluate_nnue.h" +#include "nnue/network.h" +#include "nnue/nnue_common.h" +#include "perft.h" #include "position.h" #include "search.h" #include "types.h" #include "ucioption.h" -#include "perft.h" namespace Stockfish { @@ -46,15 +47,16 @@ constexpr auto StartFEN = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNB constexpr int NormalizeToPawnValue = 345; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +namespace NN = Eval::NNUE; + UCI::UCI(int argc, char** argv) : + network(NN::Network({EvalFileDefaultName, "None", ""})), cli(argc, argv) { - evalFile = {"EvalFile", EvalFileDefaultName, "None", ""}; - options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - threads.set({options, threads, tt}); + threads.set({options, threads, tt, network}); }); options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { @@ -68,11 +70,12 @@ UCI::UCI(int argc, char** argv) : options["Move Overhead"] << Option(10, 0, 5000); options["nodestime"] << Option(0, 0, 10000); options["UCI_ShowWDL"] << Option(false); - options["EvalFile"] << Option(EvalFileDefaultName, [this](const Option&) { - evalFile = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFile); - }); + options["EvalFile"] << Option( + EvalFileDefaultName, [this](const Option& o) { network.load(cli.binaryDirectory, o); }); + + network.load(cli.binaryDirectory, options["EvalFile"]); - threads.set({options, threads, tt}); + threads.set({options, threads, tt, network}); search_clear(); // After threads are up } @@ -144,7 +147,7 @@ void UCI::loop() { std::string f; if (is >> std::skipws >> f) filename = f; - Eval::NNUE::save_eval(filename, evalFile); + network.save(filename); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout @@ -205,7 +208,7 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits = parse_limits(pos, is); - Eval::NNUE::verify(options, evalFile); + network.verify(options["EvalFile"]); if (limits.perft) { @@ -270,9 +273,9 @@ void UCI::trace_eval(Position& pos) { Position p; p.set(pos.fen(), &states->back()); - Eval::NNUE::verify(options, evalFile); + network.verify(options["EvalFile"]); - sync_cout << "\n" << Eval::trace(p) << sync_endl; + sync_cout << "\n" << Eval::trace(p, network) << sync_endl; } void UCI::search_clear() { diff --git a/src/uci.h b/src/uci.h index 489cc345..dc8dc9f8 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,13 +22,13 @@ #include #include -#include "evaluate.h" #include "misc.h" +#include "nnue/network.h" #include "position.h" +#include "search.h" #include "thread.h" #include "tt.h" #include "ucioption.h" -#include "search.h" namespace Stockfish { @@ -53,8 +53,8 @@ class UCI { const std::string& working_directory() const { return cli.workingDirectory; } - OptionsMap options; - Eval::EvalFile evalFile; + OptionsMap options; + Eval::NNUE::Network network; private: TranspositionTable tt;