From aa8ff6059b5b6048d73216b736be5f84c99770e1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 9 Mar 2024 14:42:37 +0100 Subject: [PATCH 1/2] Refactor Network Usage Continuing from PR #4968, this update improves how Stockfish handles network usage, making it easier to manage and modify networks in the future. With the introduction of a dedicated Network class, creating networks has become straightforward. See uci.cpp: ```cpp NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig) ``` The new `Network` encapsulates all network-related logic, significantly reducing the complexity previously required to support multiple network types, such as the distinction between small and big networks #4915. _This PR builds on top of https://github.com/official-stockfish/Stockfish/pull/5098 to avoid merge conflicts._ Non-Regression STC: https://tests.stockfishchess.org/tests/view/65edd26c0ec64f0526c43584 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 33760 W: 8887 L: 8661 D: 16212 Ptnml(0-2): 143, 3795, 8808, 3961, 173 Non-Regression SMP STC: https://tests.stockfishchess.org/tests/view/65ed71970ec64f0526c42fdd LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 59088 W: 15121 L: 14931 D: 29036 Ptnml(0-2): 110, 6640, 15829, 6880, 85 Compiled with `make -j profile-build` ``` bash ./bench_parallel.sh ./stockfish ./stockfish-nnue 13 50 sf_base = 1568540 +/- 7637 (95%) sf_test = 1573129 +/- 7301 (95%) diff = 4589 +/- 8720 (95%) speedup = 0.29260% +/- 0.556% (95%) ``` Compiled with `make -j build` ``` bash ./bench_parallel.sh ./stockfish ./stockfish-nnue 13 50 sf_base = 1472653 +/- 7293 (95%) sf_test = 1491928 +/- 7661 (95%) diff = 19275 +/- 7154 (95%) speedup = 1.30886% +/- 0.486% (95%) ``` closes https://github.com/official-stockfish/Stockfish/pull/5100 No functional change --- src/Makefile | 8 +- src/evaluate.cpp | 164 +----------- src/evaluate.h | 29 +-- src/main.cpp | 3 - src/misc.h | 25 ++ src/nnue/evaluate_nnue.cpp | 488 ----------------------------------- src/nnue/evaluate_nnue.h | 89 ------- src/nnue/network.cpp | 422 ++++++++++++++++++++++++++++++ src/nnue/network.h | 128 +++++++++ src/nnue/nnue_architecture.h | 7 +- src/nnue/nnue_misc.cpp | 202 +++++++++++++++ src/nnue/nnue_misc.h | 62 +++++ src/search.cpp | 31 ++- src/search.h | 34 ++- src/thread.cpp | 10 +- src/thread.h | 17 +- src/uci.cpp | 45 ++-- src/uci.h | 8 +- 18 files changed, 947 insertions(+), 825 deletions(-) delete mode 100644 src/nnue/evaluate_nnue.cpp delete mode 100644 src/nnue/evaluate_nnue.h create mode 100644 src/nnue/network.cpp create mode 100644 src/nnue/network.h create mode 100644 src/nnue/nnue_misc.cpp create mode 100644 src/nnue/nnue_misc.h diff --git a/src/Makefile b/src/Makefile index 907b6155028..bd04d2c410e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,15 +55,15 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp + nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \ - nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ + nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \ - tt.h tune.h types.h uci.h ucioption.h perft.h + tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.cpp OBJS = $(notdir $(SRCS:.cpp=.o)) @@ -502,7 +502,7 @@ endif # In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. # Currently we don't know how to make PGO builds with the NDK yet. ifeq ($(COMP),ndk) - CXXFLAGS += -stdlib=libc++ -fPIE + CXXFLAGS += -stdlib=libc++ -fPIE -mcmodel=large comp=clang ifeq ($(arch),armv7) CXX=armv7a-linux-androideabi16-clang++ diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1eb58fd24b3..56abe6cb9c8 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -22,161 +22,18 @@ #include #include #include -#include #include #include -#include #include -#include -#include -#include "incbin/incbin.h" -#include "misc.h" -#include "nnue/evaluate_nnue.h" -#include "nnue/nnue_architecture.h" +#include "nnue/network.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "types.h" #include "uci.h" -#include "ucioption.h" - -// Macro to embed the default efficiently updatable neural network (NNUE) file -// data in the engine binary (using incbin.h, by Dale Weiler). -// This macro invocation will declare the following three variables -// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data -// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end -// const unsigned int gEmbeddedNNUESize; // the size of the embedded file -// Note that this does not work in Microsoft Visual Studio. -#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) -INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); -INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); -#else -const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; -const unsigned int gEmbeddedNNUEBigSize = 1; -const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; -const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; -const unsigned int gEmbeddedNNUESmallSize = 1; -#endif - namespace Stockfish { -namespace Eval { - - -// Tries to load a NNUE network at startup time, or when the engine -// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" -// The name of the NNUE network is always retrieved from the EvalFile option. -// We search the given network in three locations: internally (the default -// network may be embedded in the binary), in the active working directory and -// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY -// variable to have the engine search in a special directory in their distro. -NNUE::EvalFiles NNUE::load_networks(const std::string& rootDirectory, - const OptionsMap& options, - NNUE::EvalFiles evalFiles) { - - for (auto& [netSize, evalFile] : evalFiles) - { - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - -#if defined(DEFAULT_NNUE_DIRECTORY) - std::vector dirs = {"", "", rootDirectory, - stringify(DEFAULT_NNUE_DIRECTORY)}; -#else - std::vector dirs = {"", "", rootDirectory}; -#endif - - for (const std::string& directory : dirs) - { - if (evalFile.current != user_eval_file) - { - if (directory != "") - { - std::ifstream stream(directory + user_eval_file, std::ios::binary); - auto description = NNUE::load_eval(stream, netSize); - - if (description.has_value()) - { - evalFile.current = user_eval_file; - evalFile.netDescription = description.value(); - } - } - - if (directory == "" && user_eval_file == evalFile.defaultName) - { - // C++ way to prepare a buffer for a memory stream - class MemoryBuffer: public std::basic_streambuf { - public: - MemoryBuffer(char* p, size_t n) { - setg(p, p, p + n); - setp(p, p + n); - } - }; - - MemoryBuffer buffer( - const_cast(reinterpret_cast( - netSize == Small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)), - size_t(netSize == Small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize)); - (void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable - (void) gEmbeddedNNUESmallEnd; - - std::istream stream(&buffer); - auto description = NNUE::load_eval(stream, netSize); - - if (description.has_value()) - { - evalFile.current = user_eval_file; - evalFile.netDescription = description.value(); - } - } - } - } - } - - return evalFiles; -} - -// Verifies that the last net used was loaded successfully -void NNUE::verify(const OptionsMap& options, - const std::unordered_map& evalFiles) { - - for (const auto& [netSize, evalFile] : evalFiles) - { - std::string user_eval_file = options[evalFile.optionName]; - - if (user_eval_file.empty()) - user_eval_file = evalFile.defaultName; - - if (evalFile.current != user_eval_file) - { - std::string msg1 = - "Network evaluation parameters compatible with the engine must be available."; - std::string msg2 = - "The network file " + user_eval_file + " was not loaded successfully."; - std::string msg3 = "The UCI option EvalFile might need to specify the full path, " - "including the directory name, to the network file."; - std::string msg4 = "The default net can be downloaded from: " - "https://tests.stockfishchess.org/api/nn/" - + evalFile.defaultName; - std::string msg5 = "The engine will be terminated now."; - - sync_cout << "info string ERROR: " << msg1 << sync_endl; - sync_cout << "info string ERROR: " << msg2 << sync_endl; - sync_cout << "info string ERROR: " << msg3 << sync_endl; - sync_cout << "info string ERROR: " << msg4 << sync_endl; - sync_cout << "info string ERROR: " << msg5 << sync_endl; - - exit(EXIT_FAILURE); - } - - sync_cout << "info string NNUE evaluation using " << user_eval_file << sync_endl; - } -} -} - // Returns a static, purely materialistic evaluation of the position from // the point of view of the given color. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. @@ -188,7 +45,7 @@ int Eval::simple_eval(const Position& pos, Color c) { // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Position& pos, int optimism) { +Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int optimism) { assert(!pos.checkers()); @@ -198,8 +55,8 @@ Value Eval::evaluate(const Position& pos, int optimism) { int nnueComplexity; - Value nnue = smallNet ? NNUE::evaluate(pos, true, &nnueComplexity, psqtOnly) - : NNUE::evaluate(pos, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly) + : networks.big.evaluate(pos, true, &nnueComplexity, false); // Blend optimism and eval with nnue complexity and material imbalance optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512; @@ -222,23 +79,22 @@ Value Eval::evaluate(const Position& pos, int optimism) { // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos) { +std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pos.checkers()) return "Final evaluation: none (in check)"; std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos) << '\n'; + ss << '\n' << NNUE::trace(pos, networks) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - Value v; - v = NNUE::evaluate(pos, false); - v = pos.side_to_move() == WHITE ? v : -v; + Value v = networks.big.evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n"; - v = evaluate(pos, VALUE_ZERO); + v = evaluate(networks, pos, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index a690a3bbaf8..e6543893c3f 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -20,51 +20,32 @@ #define EVALUATE_H_INCLUDED #include -#include #include "types.h" namespace Stockfish { class Position; -class OptionsMap; namespace Eval { constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; -std::string trace(Position& pos); - -int simple_eval(const Position& pos, Color c); -Value evaluate(const Position& pos, int optimism); - // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro, as it is used in the Makefile. #define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" -struct EvalFile { - // UCI option name - std::string optionName; - // Default net name, will use one of the macros above - std::string defaultName; - // Selected net name, either via uci option or default - std::string current; - // Net description extracted from the net file - std::string netDescription; -}; - namespace NNUE { +struct Networks; +} -enum NetSize : int; - -using EvalFiles = std::unordered_map; +std::string trace(Position& pos, const Eval::NNUE::Networks& networks); -EvalFiles load_networks(const std::string&, const OptionsMap&, EvalFiles); -void verify(const OptionsMap&, const EvalFiles&); +int simple_eval(const Position& pos, Color c); +Value evaluate(const NNUE::Networks& networks, const Position& pos, int optimism); -} // namespace NNUE } // namespace Eval diff --git a/src/main.cpp b/src/main.cpp index 6ce656d7fe8..33d5d375fca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,6 @@ #include #include "bitboard.h" -#include "evaluate.h" #include "misc.h" #include "position.h" #include "tune.h" @@ -39,8 +38,6 @@ int main(int argc, char* argv[]) { Tune::init(uci.options); - uci.evalFiles = Eval::NNUE::load_networks(uci.working_directory(), uci.options, uci.evalFiles); - uci.loop(); return 0; diff --git a/src/misc.h b/src/misc.h index f73e7889a8d..9ad5c3ca57e 100644 --- a/src/misc.h +++ b/src/misc.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -49,6 +50,30 @@ void* aligned_large_pages_alloc(size_t size); // nop if mem == nullptr void aligned_large_pages_free(void* mem); +// Deleter for automating release of memory area +template +struct AlignedDeleter { + void operator()(T* ptr) const { + ptr->~T(); + std_aligned_free(ptr); + } +}; + +template +struct LargePageDeleter { + void operator()(T* ptr) const { + ptr->~T(); + aligned_large_pages_free(ptr); + } +}; + +template +using AlignedPtr = std::unique_ptr>; + +template +using LargePagePtr = std::unique_ptr>; + + void dbg_hit_on(bool cond, int slot = 0); void dbg_mean_of(int64_t value, int slot = 0); void dbg_stdev_of(int64_t value, int slot = 0); diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp deleted file mode 100644 index 854fed06e82..00000000000 --- a/src/nnue/evaluate_nnue.cpp +++ /dev/null @@ -1,488 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// Code for calculating NNUE evaluation function - -#include "evaluate_nnue.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../evaluate.h" -#include "../misc.h" -#include "../position.h" -#include "../types.h" -#include "../uci.h" -#include "nnue_accumulator.h" -#include "nnue_common.h" - -namespace Stockfish::Eval::NNUE { - -// Input feature converter -LargePagePtr> - featureTransformerBig; -LargePagePtr> - featureTransformerSmall; - -// Evaluation function -AlignedPtr> networkBig[LayerStacks]; -AlignedPtr> networkSmall[LayerStacks]; - -// Evaluation function file names - -namespace Detail { - -// Initialize the evaluation function parameters -template -void initialize(AlignedPtr& pointer) { - - pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - -template -void initialize(LargePagePtr& pointer) { - - static_assert(alignof(T) <= 4096, - "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); - pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); - std::memset(pointer.get(), 0, sizeof(T)); -} - -// Read evaluation function parameters -template -bool read_parameters(std::istream& stream, T& reference) { - - std::uint32_t header; - header = read_little_endian(stream); - if (!stream || header != T::get_hash_value()) - return false; - return reference.read_parameters(stream); -} - -// Write evaluation function parameters -template -bool write_parameters(std::ostream& stream, const T& reference) { - - write_little_endian(stream, T::get_hash_value()); - return reference.write_parameters(stream); -} - -} // namespace Detail - - -// Initialize the evaluation function parameters -static void initialize(NetSize netSize) { - - if (netSize == Small) - { - Detail::initialize(featureTransformerSmall); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(networkSmall[i]); - } - else - { - Detail::initialize(featureTransformerBig); - for (std::size_t i = 0; i < LayerStacks; ++i) - Detail::initialize(networkBig[i]); - } -} - -// Read network header -static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { - std::uint32_t version, size; - - version = read_little_endian(stream); - *hashValue = read_little_endian(stream); - size = read_little_endian(stream); - if (!stream || version != Version) - return false; - desc->resize(size); - stream.read(&(*desc)[0], size); - return !stream.fail(); -} - -// Write network header -static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { - write_little_endian(stream, Version); - write_little_endian(stream, hashValue); - write_little_endian(stream, std::uint32_t(desc.size())); - stream.write(&desc[0], desc.size()); - return !stream.fail(); -} - -// Read network parameters -static bool read_parameters(std::istream& stream, NetSize netSize, std::string& netDescription) { - - std::uint32_t hashValue; - if (!read_header(stream, &hashValue, &netDescription)) - return false; - if (hashValue != HashValue[netSize]) - return false; - if (netSize == Big && !Detail::read_parameters(stream, *featureTransformerBig)) - return false; - if (netSize == Small && !Detail::read_parameters(stream, *featureTransformerSmall)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (netSize == Big && !Detail::read_parameters(stream, *(networkBig[i]))) - return false; - if (netSize == Small && !Detail::read_parameters(stream, *(networkSmall[i]))) - return false; - } - return stream && stream.peek() == std::ios::traits_type::eof(); -} - -// Write network parameters -static bool -write_parameters(std::ostream& stream, NetSize netSize, const std::string& netDescription) { - - if (!write_header(stream, HashValue[netSize], netDescription)) - return false; - if (netSize == Big && !Detail::write_parameters(stream, *featureTransformerBig)) - return false; - if (netSize == Small && !Detail::write_parameters(stream, *featureTransformerSmall)) - return false; - for (std::size_t i = 0; i < LayerStacks; ++i) - { - if (netSize == Big && !Detail::write_parameters(stream, *(networkBig[i]))) - return false; - if (netSize == Small && !Detail::write_parameters(stream, *(networkSmall[i]))) - return false; - } - return bool(stream); -} - -void hint_common_parent_position(const Position& pos) { - - int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); - if (simpleEvalAbs > Eval::SmallNetThreshold) - featureTransformerSmall->hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); - else - featureTransformerBig->hint_common_access(pos, false); -} - -// Evaluation function. Perform differential calculation. -template -Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly) { - - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. - - constexpr uint64_t alignment = CacheLineSize; - constexpr int delta = 24; - -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr > ::BufferSize + alignment / sizeof(TransformedFeatureType)]; - - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else - - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer < Net_Size == Small ? TransformedFeatureDimensionsSmall - : TransformedFeatureDimensionsBig, - nullptr > ::BufferSize]; -#endif - - ASSERT_ALIGNED(transformedFeatures, alignment); - - const int bucket = (pos.count() - 1) / 4; - const auto psqt = - Net_Size == Small - ? featureTransformerSmall->transform(pos, transformedFeatures, bucket, psqtOnly) - : featureTransformerBig->transform(pos, transformedFeatures, bucket, psqtOnly); - - const auto positional = - !psqtOnly ? (Net_Size == Small ? networkSmall[bucket]->propagate(transformedFeatures) - : networkBig[bucket]->propagate(transformedFeatures)) - : 0; - - if (complexity) - *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; - - // Give more value to positional evaluation when adjusted flag is set - if (adjusted) - return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) - / (1024 * OutputScale)); - else - return static_cast((psqt + positional) / OutputScale); -} - -template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); -template Value evaluate(const Position& pos, bool adjusted, int* complexity, bool psqtOnly); - -struct NnueEvalTrace { - static_assert(LayerStacks == PSQTBuckets); - - Value psqt[LayerStacks]; - Value positional[LayerStacks]; - std::size_t correctBucket; -}; - -static NnueEvalTrace trace_evaluate(const Position& pos) { - - // We manually align the arrays on the stack because with gcc < 9.3 - // overaligning stack variables with alignas() doesn't work correctly. - constexpr uint64_t alignment = CacheLineSize; - -#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) - TransformedFeatureType transformedFeaturesUnaligned - [FeatureTransformer::BufferSize - + alignment / sizeof(TransformedFeatureType)]; - - auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); -#else - alignas(alignment) TransformedFeatureType - transformedFeatures[FeatureTransformer::BufferSize]; -#endif - - ASSERT_ALIGNED(transformedFeatures, alignment); - - NnueEvalTrace t{}; - t.correctBucket = (pos.count() - 1) / 4; - for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) - { - const auto materialist = - featureTransformerBig->transform(pos, transformedFeatures, bucket, false); - const auto positional = networkBig[bucket]->propagate(transformedFeatures); - - t.psqt[bucket] = static_cast(materialist / OutputScale); - t.positional[bucket] = static_cast(positional / OutputScale); - } - - return t; -} - -constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); - - -// Converts a Value into (centi)pawns and writes it in a buffer. -// The buffer must have capacity for at least 5 chars. -static void format_cp_compact(Value v, char* buffer) { - - buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); - - int cp = std::abs(UCI::to_cp(v)); - if (cp >= 10000) - { - buffer[1] = '0' + cp / 10000; - cp %= 10000; - buffer[2] = '0' + cp / 1000; - cp %= 1000; - buffer[3] = '0' + cp / 100; - buffer[4] = ' '; - } - else if (cp >= 1000) - { - buffer[1] = '0' + cp / 1000; - cp %= 1000; - buffer[2] = '0' + cp / 100; - cp %= 100; - buffer[3] = '.'; - buffer[4] = '0' + cp / 10; - } - else - { - buffer[1] = '0' + cp / 100; - cp %= 100; - buffer[2] = '.'; - buffer[3] = '0' + cp / 10; - cp %= 10; - buffer[4] = '0' + cp / 1; - } -} - - -// Converts a Value into pawns, always keeping two decimals -static void format_cp_aligned_dot(Value v, std::stringstream& stream) { - - const double pawns = std::abs(0.01 * UCI::to_cp(v)); - - stream << (v < 0 ? '-' - : v > 0 ? '+' - : ' ') - << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; -} - - -// Returns a string with the value of each piece on a board, -// and a table for (PSQT, Layers) values bucket by bucket. -std::string trace(Position& pos) { - - std::stringstream ss; - - char board[3 * 8 + 1][8 * 8 + 2]; - std::memset(board, ' ', sizeof(board)); - for (int row = 0; row < 3 * 8 + 1; ++row) - board[row][8 * 8 + 1] = '\0'; - - // A lambda to output one box of the board - auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { - const int x = int(file) * 8; - const int y = (7 - int(rank)) * 3; - for (int i = 1; i < 8; ++i) - board[y][x + i] = board[y + 3][x + i] = '-'; - for (int i = 1; i < 3; ++i) - board[y + i][x] = board[y + i][x + 8] = '|'; - board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; - if (pc != NO_PIECE) - board[y + 1][x + 4] = PieceToChar[pc]; - if (value != VALUE_NONE) - format_cp_compact(value, &board[y + 2][x + 2]); - }; - - // We estimate the value of each piece by doing a differential evaluation from - // the current base eval, simulating the removal of the piece from its square. - Value base = evaluate(pos); - base = pos.side_to_move() == WHITE ? base : -base; - - for (File f = FILE_A; f <= FILE_H; ++f) - for (Rank r = RANK_1; r <= RANK_8; ++r) - { - Square sq = make_square(f, r); - Piece pc = pos.piece_on(sq); - Value v = VALUE_NONE; - - if (pc != NO_PIECE && type_of(pc) != KING) - { - auto st = pos.state(); - - pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; - - Value eval = evaluate(pos); - eval = pos.side_to_move() == WHITE ? eval : -eval; - v = base - eval; - - pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; - } - - writeSquare(f, r, pc, v); - } - - ss << " NNUE derived piece values:\n"; - for (int row = 0; row < 3 * 8 + 1; ++row) - ss << board[row] << '\n'; - ss << '\n'; - - auto t = trace_evaluate(pos); - - ss << " NNUE network contributions " - << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl - << "+------------+------------+------------+------------+\n" - << "| Bucket | Material | Positional | Total |\n" - << "| | (PSQT) | (Layers) | |\n" - << "+------------+------------+------------+------------+\n"; - - for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) - { - ss << "| " << bucket << " "; - ss << " | "; - format_cp_aligned_dot(t.psqt[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.positional[bucket], ss); - ss << " " - << " | "; - format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); - ss << " " - << " |"; - if (bucket == t.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; - } - - ss << "+------------+------------+------------+------------+\n"; - - return ss.str(); -} - - -// Load eval, from a file stream or a memory stream -std::optional load_eval(std::istream& stream, NetSize netSize) { - - initialize(netSize); - std::string netDescription; - return read_parameters(stream, netSize, netDescription) ? std::make_optional(netDescription) - : std::nullopt; -} - -// Save eval, to a file stream or a memory stream -bool save_eval(std::ostream& stream, - NetSize netSize, - const std::string& name, - const std::string& netDescription) { - - if (name.empty() || name == "None") - return false; - - return write_parameters(stream, netSize, netDescription); -} - -// Save eval, to a file given by its name -bool save_eval(const std::optional& filename, - NetSize netSize, - const EvalFiles& evalFiles) { - - std::string actualFilename; - std::string msg; - - if (filename.has_value()) - actualFilename = filename.value(); - else - { - if (evalFiles.at(netSize).current - != (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig)) - { - msg = "Failed to export a net. " - "A non-embedded net can only be saved if the filename is specified"; - - sync_cout << msg << sync_endl; - return false; - } - actualFilename = (netSize == Small ? EvalFileDefaultNameSmall : EvalFileDefaultNameBig); - } - - std::ofstream stream(actualFilename, std::ios_base::binary); - bool saved = save_eval(stream, netSize, evalFiles.at(netSize).current, - evalFiles.at(netSize).netDescription); - - msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; - - sync_cout << msg << sync_endl; - return saved; -} - - -} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h deleted file mode 100644 index febe8f9d9b9..00000000000 --- a/src/nnue/evaluate_nnue.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -// header used in NNUE evaluation function - -#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED -#define NNUE_EVALUATE_NNUE_H_INCLUDED - -#include -#include -#include -#include -#include - -#include "../evaluate.h" -#include "../misc.h" -#include "../types.h" -#include "nnue_architecture.h" -#include "nnue_feature_transformer.h" - -namespace Stockfish { -class Position; -} - -namespace Stockfish::Eval::NNUE { - -// Hash value of evaluation function structure -constexpr std::uint32_t HashValue[2] = { - FeatureTransformer::get_hash_value() - ^ Network::get_hash_value(), - FeatureTransformer::get_hash_value() - ^ Network::get_hash_value()}; - -// Deleter for automating release of memory area -template -struct AlignedDeleter { - void operator()(T* ptr) const { - ptr->~T(); - std_aligned_free(ptr); - } -}; - -template -struct LargePageDeleter { - void operator()(T* ptr) const { - ptr->~T(); - aligned_large_pages_free(ptr); - } -}; - -template -using AlignedPtr = std::unique_ptr>; - -template -using LargePagePtr = std::unique_ptr>; - -std::string trace(Position& pos); -template -Value evaluate(const Position& pos, - bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false); -void hint_common_parent_position(const Position& pos); - -std::optional load_eval(std::istream& stream, NetSize netSize); -bool save_eval(std::ostream& stream, - NetSize netSize, - const std::string& name, - const std::string& netDescription); -bool save_eval(const std::optional& filename, NetSize netSize, const EvalFiles&); - -} // namespace Stockfish::Eval::NNUE - -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp new file mode 100644 index 00000000000..e7a1567d550 --- /dev/null +++ b/src/nnue/network.cpp @@ -0,0 +1,422 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "network.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../incbin/incbin.h" +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "nnue_misc.h" + +namespace { +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). +// This macro invocation will declare the following three variables +// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data +// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end +// const unsigned int gEmbeddedNNUESize; // the size of the embedded file +// Note that this does not work in Microsoft Visual Studio. +#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); +#else +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; +#endif +} + + +namespace Stockfish::Eval::NNUE { + +const EmbeddedNNUE embeddedNNUEBig(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); +const EmbeddedNNUE + embeddedNNUESmall(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); + + +namespace Detail { + +// Initialize the evaluation function parameters +template +void initialize(AlignedPtr& pointer) { + + pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); +} + +template +void initialize(LargePagePtr& pointer) { + + static_assert(alignof(T) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); +} + +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { + + std::uint32_t header; + header = read_little_endian(stream); + if (!stream || header != T::get_hash_value()) + return false; + return reference.read_parameters(stream); +} + +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, const T& reference) { + + write_little_endian(stream, T::get_hash_value()); + return reference.write_parameters(stream); +} + +} // namespace Detail + + +template +void Network::load(const std::string& rootDirectory, std::string evalfilePath) { +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", rootDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", rootDirectory}; +#endif + + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + for (const std::string& directory : dirs) + { + if (evalFile.current != evalfilePath) + { + if (directory != "") + { + load_user_net(directory, evalfilePath); + } + + if (directory == "" && evalfilePath == evalFile.defaultName) + { + load_internal(); + } + } + } +} + + +template +bool Network::save(const std::optional& filename) const { + std::string actualFilename; + std::string msg; + + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (evalFile.current != (evalFile.defaultName)) + { + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; + return false; + } + + actualFilename = evalFile.defaultName; + } + + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save(stream, evalFile.current, evalFile.netDescription); + + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; +} + + +template +Value Network::evaluate(const Position& pos, + bool adjusted, + int* complexity, + bool psqtOnly) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + constexpr int delta = 24; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType transformedFeatures + [FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket, psqtOnly); + const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; + + if (complexity) + *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; + + // Give more value to positional evaluation when adjusted flag is set + if (adjusted) + return static_cast(((1024 - delta) * psqt + (1024 + delta) * positional) + / (1024 * OutputScale)); + else + return static_cast((psqt + positional) / OutputScale); +} + + +template +void Network::verify(std::string evalfilePath) const { + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + if (evalFile.current != evalfilePath) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + exit(EXIT_FAILURE); + } + + sync_cout << "info string NNUE evaluation using " << evalfilePath << sync_endl; +} + + +template +void Network::hint_common_access(const Position& pos, bool psqtOnl) const { + featureTransformer->hint_common_access(pos, psqtOnl); +} + + +template +NnueEvalTrace Network::trace_evaluate(const Position& pos) const { + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned + [FeatureTransformer::BufferSize + + alignment / sizeof(TransformedFeatureType)]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); +#else + alignas(alignment) TransformedFeatureType transformedFeatures + [FeatureTransformer::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + + NnueEvalTrace t{}; + t.correctBucket = (pos.count() - 1) / 4; + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = + featureTransformer->transform(pos, transformedFeatures, bucket, false); + const auto positional = network[bucket]->propagate(transformedFeatures); + + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); + } + + return t; +} + + +template +void Network::load_user_net(const std::string& dir, + const std::string& evalfilePath) { + std::ifstream stream(dir + evalfilePath, std::ios::binary); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalfilePath; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::load_internal() { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; + + MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), + size_t(embedded.size)); + + std::istream stream(&buffer); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalFile.defaultName; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::initialize() { + Detail::initialize(featureTransformer); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(network[i]); +} + + +template +bool Network::save(std::ostream& stream, + const std::string& name, + const std::string& netDescription) const { + if (name.empty() || name == "None") + return false; + + return write_parameters(stream, netDescription); +} + + +template +std::optional Network::load(std::istream& stream) { + initialize(); + std::string description; + + return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt; +} + + +// Read network header +template +bool Network::read_header(std::istream& stream, + std::uint32_t* hashValue, + std::string* desc) const { + std::uint32_t version, size; + + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); +} + + +// Write network header +template +bool Network::write_header(std::ostream& stream, + std::uint32_t hashValue, + const std::string& desc) const { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, std::uint32_t(desc.size())); + stream.write(&desc[0], desc.size()); + return !stream.fail(); +} + + +template +bool Network::read_parameters(std::istream& stream, + std::string& netDescription) const { + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != Network::hash) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::read_parameters(stream, *(network[i]))) + return false; + } + return stream && stream.peek() == std::ios::traits_type::eof(); +} + + +template +bool Network::write_parameters(std::ostream& stream, + const std::string& netDescription) const { + if (!write_header(stream, Network::hash, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::write_parameters(stream, *(network[i]))) + return false; + } + return bool(stream); +} + +// Explicit template instantiation + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +template class Network< + NetworkArchitecture, + FeatureTransformer>; + +} // namespace Stockfish::Eval::NNUE \ No newline at end of file diff --git a/src/nnue/network.h b/src/nnue/network.h new file mode 100644 index 00000000000..929b5c9eaa9 --- /dev/null +++ b/src/nnue/network.h @@ -0,0 +1,128 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NETWORK_H_INCLUDED +#define NETWORK_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_feature_transformer.h" +#include "nnue_misc.h" + +namespace Stockfish::Eval::NNUE { + +struct EmbeddedNNUE { + EmbeddedNNUE(const unsigned char* embeddedData, + const unsigned char* embeddedEnd, + const unsigned int embeddedSize) : + data(embeddedData), + end(embeddedEnd), + size(embeddedSize) {} + const unsigned char* data; + const unsigned char* end; + const unsigned int size; +}; + +extern const EmbeddedNNUE embeddedNNUEBig; +extern const EmbeddedNNUE embeddedNNUESmall; + +template +class Network { + public: + Network(EvalFile file, EmbeddedNNUE embeddedEval) : + evalFile(file), + embedded(embeddedEval) {} + + void load(const std::string& rootDirectory, std::string evalfilePath); + bool save(const std::optional& filename) const; + + + Value evaluate(const Position& pos, + bool adjusted = false, + int* complexity = nullptr, + bool psqtOnly = false) const; + + + void hint_common_access(const Position& pos, bool psqtOnl) const; + + void verify(std::string evalfilePath) const; + NnueEvalTrace trace_evaluate(const Position& pos) const; + + private: + void load_user_net(const std::string&, const std::string&); + void load_internal(); + + void initialize(); + + bool save(std::ostream&, const std::string&, const std::string&) const; + std::optional load(std::istream&); + + bool read_header(std::istream&, std::uint32_t*, std::string*) const; + bool write_header(std::ostream&, std::uint32_t, const std::string&) const; + + bool read_parameters(std::istream&, std::string&) const; + bool write_parameters(std::ostream&, const std::string&) const; + + // Input feature converter + LargePagePtr featureTransformer; + + // Evaluation function + AlignedPtr network[LayerStacks]; + + EvalFile evalFile; + EmbeddedNNUE embedded; + + // Hash value of evaluation function structure + static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); +}; + +using SmallFeatureTransformer = + FeatureTransformer; +using SmallNetworkArchitecture = + NetworkArchitecture; + +using BigFeatureTransformer = + FeatureTransformer; +using BigNetworkArchitecture = NetworkArchitecture; + +using NetworkBig = Network; + +using NetworkSmall = Network; + + +struct Networks { + Networks(NetworkBig&& nB, NetworkSmall&& nS) : + big(std::move(nB)), + small(std::move(nS)) {} + + NetworkBig big; + NetworkSmall small; +}; + + +} // namespace Stockfish + +#endif \ No newline at end of file diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index b222ab997db..05efb813754 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -37,11 +37,6 @@ namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2_hm; -enum NetSize : int { - Big, - Small -}; - // Number of input feature dimensions after conversion constexpr IndexType TransformedFeatureDimensionsBig = 2560; constexpr int L2Big = 15; @@ -55,7 +50,7 @@ constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; template -struct Network { +struct NetworkArchitecture { static constexpr IndexType TransformedFeatureDimensions = L1; static constexpr int FC_0_OUTPUTS = L2; static constexpr int FC_1_OUTPUTS = L3; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp new file mode 100644 index 00000000000..c443aaf19b2 --- /dev/null +++ b/src/nnue/nnue_misc.cpp @@ -0,0 +1,202 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include "nnue_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../position.h" +#include "../types.h" +#include "../uci.h" +#include "network.h" +#include "nnue_accumulator.h" + +namespace Stockfish::Eval::NNUE { + + +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); + + +void hint_common_parent_position(const Position& pos, const Networks& networks) { + + int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); + if (simpleEvalAbs > Eval::SmallNetThreshold) + networks.small.hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold); + else + networks.big.hint_common_access(pos, false); +} + + +// Converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +static void format_cp_compact(Value v, char* buffer) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(UCI::to_cp(v)); + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; + buffer[3] = '0' + cp / 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; + cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; + cp %= 10; + buffer[4] = '0' + cp / 1; + } +} + + +// Converts a Value into pawns, always keeping two decimals +static void format_cp_aligned_dot(Value v, std::stringstream& stream) { + + const double pawns = std::abs(0.01 * UCI::to_cp(v)); + + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} + + +// Returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string trace(Position& pos, const Eval::NNUE::Networks& networks) { + + std::stringstream ss; + + char board[3 * 8 + 1][8 * 8 + 2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) { + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y + 2][x + 2]); + }; + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + Value base = networks.big.evaluate(pos); + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= FILE_H; ++f) + for (Rank r = RANK_1; r <= RANK_8; ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != KING) + { + auto st = pos.state(); + + pos.remove_piece(sq); + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; + + Value eval = networks.big.evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = + false; + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3 * 8 + 1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + auto t = networks.big.trace_evaluate(pos); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + ss << "| " << bucket << " "; + ss << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss); + ss << " " + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); + ss << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); +} + + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h new file mode 100644 index 00000000000..749171c32cb --- /dev/null +++ b/src/nnue/nnue_misc.h @@ -0,0 +1,62 @@ +/* + 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 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; +}; + + +struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; +}; + + +struct Networks; + + +std::string trace(Position& pos, const Networks& networks); +void hint_common_parent_position(const Position& pos, const Networks& networks); + +} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish + +#endif // #ifndef NNUE_MISC_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 16f45df16dd..f4ec8fb16dc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -27,15 +27,15 @@ #include #include #include -#include #include +#include #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" -#include "nnue/evaluate_nnue.h" #include "nnue/nnue_common.h" +#include "nnue/nnue_misc.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -135,7 +135,8 @@ Search::Worker::Worker(SharedState& sharedState, manager(std::move(sm)), options(sharedState.options), threads(sharedState.threads), - tt(sharedState.tt) { + tt(sharedState.tt), + networks(sharedState.networks) { clear(); } @@ -566,8 +567,9 @@ Value Search::Worker::search( // Step 2. Check for aborted search and immediate draw if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) - : value_draw(thisThread->nodes); + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(networks, pos, thisThread->optimism[us]) + : value_draw(thisThread->nodes); // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply + 1), but if alpha is already bigger because @@ -700,7 +702,7 @@ Value Search::Worker::search( { // Providing the hint that this node's accumulator will be used often // brings significant Elo gain (~13 Elo). - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, networks); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -708,9 +710,9 @@ Value Search::Worker::search( // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, networks); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -720,7 +722,7 @@ Value Search::Worker::search( } else { - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); // Static evaluation is saved as it was before adjustment by correction history @@ -877,7 +879,7 @@ Value Search::Worker::search( } } - Eval::NNUE::hint_common_parent_position(pos); + Eval::NNUE::hint_common_parent_position(pos, networks); } moves_loop: // When in check, search starts here @@ -1413,8 +1415,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Step 2. Check for an immediate draw or maximum ply reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos, thisThread->optimism[us]) - : VALUE_DRAW; + return (ss->ply >= MAX_PLY && !ss->inCheck) + ? evaluate(networks, pos, thisThread->optimism[us]) + : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1445,7 +1448,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, // Never assume anything about values stored in TT unadjustedStaticEval = tte->eval(); if (unadjustedStaticEval == VALUE_NONE) - unadjustedStaticEval = evaluate(pos, thisThread->optimism[us]); + unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]); ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); @@ -1458,7 +1461,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, { // In case of null move search, use previous static eval with a different sign unadjustedStaticEval = (ss - 1)->currentMove != Move::null() - ? evaluate(pos, thisThread->optimism[us]) + ? evaluate(networks, pos, thisThread->optimism[us]) : -(ss - 1)->staticEval; ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos); diff --git a/src/search.h b/src/search.h index bb9f63fff82..0a702b8dbca 100644 --- a/src/search.h +++ b/src/search.h @@ -25,8 +25,8 @@ #include #include #include -#include #include +#include #include "misc.h" #include "movepick.h" @@ -37,6 +37,12 @@ namespace Stockfish { +namespace Eval { +namespace NNUE { +struct Networks; +} +} + // Different node types, used as a template parameter enum NodeType { NonPV, @@ -125,16 +131,20 @@ struct LimitsType { // The UCI stores the uci options, thread pool, and transposition table. // This struct is used to easily forward data to the Search::Worker class. struct SharedState { - SharedState(const OptionsMap& optionsMap, - ThreadPool& threadPool, - TranspositionTable& transpositionTable) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + const Eval::NNUE::Networks& nets) : options(optionsMap), threads(threadPool), - tt(transpositionTable) {} + tt(transpositionTable), + networks(nets) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Networks& networks; }; class Worker; @@ -176,6 +186,7 @@ class NullSearchManager: public ISearchManager { void check_time(Search::Worker&) override {} }; + // Search::Worker is the class that does the actual search. // It is instantiated once per thread, and it is responsible for keeping track // of the search history, and storing data required for the search. @@ -247,9 +258,10 @@ class Worker { Tablebases::Config tbConfig; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const Eval::NNUE::Networks& networks; friend class Stockfish::ThreadPool; friend class SearchManager; diff --git a/src/thread.cpp b/src/thread.cpp index b62f5d8a39d..a3823d0cfd0 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -19,12 +19,12 @@ #include "thread.h" #include +#include #include #include #include #include #include -#include #include "misc.h" #include "movegen.h" @@ -62,6 +62,7 @@ Thread::~Thread() { stdThread.join(); } + // Wakes up the thread that will start the search void Thread::start_searching() { mutex.lock(); @@ -109,6 +110,13 @@ void Thread::idle_loop() { } } +Search::SearchManager* ThreadPool::main_manager() { + return static_cast(main_thread()->worker.get()->manager.get()); +} + +uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); } +uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); } + // Creates/destroys threads to match the requested number. // Created and launched threads will immediately go to sleep in idle_loop. // Upon resizing, threads are recreated to allow for binding if necessary. diff --git a/src/thread.h b/src/thread.h index 0d4c252ccc3..81fcc72a7ee 100644 --- a/src/thread.h +++ b/src/thread.h @@ -33,6 +33,7 @@ namespace Stockfish { + class OptionsMap; using Value = int; @@ -83,15 +84,13 @@ class ThreadPool { void clear(); void set(Search::SharedState); - Search::SearchManager* main_manager() const { - return static_cast(main_thread()->worker.get()->manager.get()); - }; - Thread* main_thread() const { return threads.front(); } - uint64_t nodes_searched() const { return accumulate(&Search::Worker::nodes); } - uint64_t tb_hits() const { return accumulate(&Search::Worker::tbHits); } - Thread* get_best_thread() const; - void start_searching(); - void wait_for_search_finished() const; + Search::SearchManager* main_manager(); + Thread* main_thread() const { return threads.front(); } + uint64_t nodes_searched() const; + uint64_t tb_hits() const; + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; std::atomic_bool stop, abortedSearch, increaseDepth; diff --git a/src/uci.cpp b/src/uci.cpp index 357369bf562..fda336b0d8e 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -22,25 +22,25 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include "benchmark.h" #include "evaluate.h" #include "movegen.h" -#include "nnue/evaluate_nnue.h" -#include "nnue/nnue_architecture.h" +#include "nnue/network.h" +#include "nnue/nnue_common.h" +#include "perft.h" #include "position.h" #include "search.h" #include "syzygy/tbprobe.h" #include "types.h" #include "ucioption.h" -#include "perft.h" namespace Stockfish { @@ -48,17 +48,20 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKB constexpr int NormalizeToPawnValue = 356; constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; -UCI::UCI(int argc, char** argv) : - cli(argc, argv) { - evalFiles = {{Eval::NNUE::Big, {"EvalFile", EvalFileDefaultNameBig, "None", ""}}, - {Eval::NNUE::Small, {"EvalFileSmall", EvalFileDefaultNameSmall, "None", ""}}}; +namespace NN = Eval::NNUE; +UCI::UCI(int argc, char** argv) : + networks(NN::Networks( + NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::embeddedNNUEBig), + NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::embeddedNNUESmall))), + cli(argc, argv) { + options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - threads.set({options, threads, tt}); + threads.set({options, threads, tt, networks}); }); options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { @@ -80,14 +83,17 @@ UCI::UCI(int argc, char** argv) : options["SyzygyProbeDepth"] << Option(1, 1, 100); options["Syzygy50MoveRule"] << Option(true); options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option&) { - evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) { + networks.big.load(cli.binaryDirectory, o); }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option&) { - evalFiles = Eval::NNUE::load_networks(cli.binaryDirectory, options, evalFiles); + options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { + networks.small.load(cli.binaryDirectory, o); }); - threads.set({options, threads, tt}); + networks.big.load(cli.binaryDirectory, options["EvalFile"]); + networks.small.load(cli.binaryDirectory, options["EvalFileSmall"]); + + threads.set({options, threads, tt, networks}); search_clear(); // After threads are up } @@ -157,7 +163,7 @@ void UCI::loop() { std::string f; if (is >> std::skipws >> f) filename = f; - Eval::NNUE::save_eval(filename, Eval::NNUE::Big, evalFiles); + networks.big.save(filename); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout @@ -218,7 +224,8 @@ void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) { Search::LimitsType limits = parse_limits(pos, is); - Eval::NNUE::verify(options, evalFiles); + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); if (limits.perft) { @@ -283,9 +290,11 @@ void UCI::trace_eval(Position& pos) { Position p; p.set(pos.fen(), options["UCI_Chess960"], &states->back()); - Eval::NNUE::verify(options, evalFiles); + networks.big.verify(options["EvalFile"]); + networks.small.verify(options["EvalFileSmall"]); + - sync_cout << "\n" << Eval::trace(p) << sync_endl; + sync_cout << "\n" << Eval::trace(p, networks) << sync_endl; } void UCI::search_clear() { diff --git a/src/uci.h b/src/uci.h index f25bb8d517f..dd55862ad17 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,13 +22,13 @@ #include #include -#include "evaluate.h" #include "misc.h" +#include "nnue/network.h" #include "position.h" +#include "search.h" #include "thread.h" #include "tt.h" #include "ucioption.h" -#include "search.h" namespace Stockfish { @@ -53,8 +53,8 @@ class UCI { const std::string& working_directory() const { return cli.workingDirectory; } - OptionsMap options; - Eval::NNUE::EvalFiles evalFiles; + OptionsMap options; + Eval::NNUE::Networks networks; private: TranspositionTable tt; From 131dc64183a8ecf101023faf651d05dc0d88885e Mon Sep 17 00:00:00 2001 From: Disservin Date: Mon, 11 Mar 2024 16:21:47 +0100 Subject: [PATCH 2/2] Small cleanup No functional change --- src/evaluate.h | 3 ++- src/nnue/network.cpp | 6 +++--- src/nnue/network.h | 6 +++--- src/nnue/nnue_misc.h | 3 ++- src/search.h | 4 +--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index e6543893c3f..754a92eb7eb 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,8 @@ constexpr inline int SmallNetThreshold = 1139, PsqtOnlyThreshold = 2500; // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. Do not change the -// name of the macro, as it is used in the Makefile. +// name of the macro or the location where this macro is defined, as it is used +// in the Makefile/Fishtest. #define EvalFileDefaultNameBig "nn-1ceb1ade0001.nnue" #define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue" diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index e7a1567d550..5d4e0954d78 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -118,7 +118,7 @@ void Network::load(const std::string& rootDirectory, std::str if (evalfilePath.empty()) evalfilePath = evalFile.defaultName; - for (const std::string& directory : dirs) + for (const auto& directory : dirs) { if (evalFile.current != evalfilePath) { @@ -145,7 +145,7 @@ bool Network::save(const std::optional& filename actualFilename = filename.value(); else { - if (evalFile.current != (evalFile.defaultName)) + if (evalFile.current != evalFile.defaultName) { msg = "Failed to export a net. " "A non-embedded net can only be saved if the filename is specified"; @@ -419,4 +419,4 @@ template class Network< NetworkArchitecture, FeatureTransformer>; -} // namespace Stockfish::Eval::NNUE \ No newline at end of file +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h index 929b5c9eaa9..c1ed7717914 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -99,6 +99,7 @@ class Network { static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); }; +// Definitions of the network types using SmallFeatureTransformer = FeatureTransformer; using SmallNetworkArchitecture = @@ -108,8 +109,7 @@ using BigFeatureTransformer = FeatureTransformer; using BigNetworkArchitecture = NetworkArchitecture; -using NetworkBig = Network; - +using NetworkBig = Network; using NetworkSmall = Network; @@ -125,4 +125,4 @@ struct Networks { } // namespace Stockfish -#endif \ No newline at end of file +#endif diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index 749171c32cb..5eab02184c6 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -32,7 +32,8 @@ class Position; namespace Eval::NNUE { struct EvalFile { - // Default net name, will use one of the macros above + // 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; diff --git a/src/search.h b/src/search.h index 0a702b8dbca..4908e535c6a 100644 --- a/src/search.h +++ b/src/search.h @@ -37,11 +37,9 @@ namespace Stockfish { -namespace Eval { -namespace NNUE { +namespace Eval::NNUE { struct Networks; } -} // Different node types, used as a template parameter enum NodeType {