From ab8f901d25def6c17f83b7a1b4b84eaec404c0f4 Mon Sep 17 00:00:00 2001 From: anematode Date: Mon, 4 May 2026 08:32:42 +0200 Subject: [PATCH] Remove small net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Failed VVLTC non-regression https://tests.stockfishchess.org/tests/view/69d562b84088e069540a2288 LLR: -2.96 (-2.94,2.94) <-1.75,0.25> Total: 386998 W: 99181 L: 99760 D: 188057 Ptnml(0-2): 35, 35792, 122429, 35203, 40 Failed STC non-regression https://tests.stockfishchess.org/tests/view/69f3c6601e5788938e86a99e LLR: -2.93 (-2.94,2.94) <-1.75,0.25> Total: 33696 W: 8492 L: 8795 D: 16409 Ptnml(0-2): 124, 4209, 8504, 3868, 143 Many thanks to Dubslow, Torom, ces42, Shawn, vondele, Disservin and others for discussion. ## Summary The venerable small net has been around for quite some time now, and while the big net architecture has substantially advanced with TI, the small net has stayed with plain HalfKA. It therefore presents a few burdens: multiple net architectures to maintain, multiple nets to train, and a whole lot of templates to deal with the variable L1 size. Locally I measure a slowdown of -2.5% in NPS with this branch – and it's probably more on non-AVX512 architectures – but a pure slowdown of that magnitude would lead to more dramatic losses (even at VVLTC) than exhibited in the above tests, suggesting that the small net's lower eval quality is deleterious. Bonus: Shawn found this interesting PGN among the VVLTC games: https://lichess.org/study/hvo8jflc/OeTOityv `master` seems to misevaluate the fortress because all positions go to small net (the material difference is larger than the threshold). closes https://github.com/official-stockfish/Stockfish/pull/6796 Bench: 2877007 --- scripts/net.sh | 4 +- src/engine.cpp | 66 +++------ src/engine.h | 21 ++- src/evaluate.cpp | 49 ++----- src/evaluate.h | 9 +- src/nnue/network.cpp | 140 +++++------------- src/nnue/network.h | 80 +++-------- src/nnue/nnue_accumulator.cpp | 213 +++++++++++++--------------- src/nnue/nnue_accumulator.h | 140 +++++++----------- src/nnue/nnue_architecture.h | 18 +-- src/nnue/nnue_feature_transformer.h | 193 +++++++++---------------- src/nnue/nnue_misc.cpp | 45 ++---- src/nnue/nnue_misc.h | 4 +- src/position.cpp | 9 +- src/search.cpp | 10 +- src/search.h | 30 ++-- src/uci.cpp | 15 +- src/universal/nnue_embed.cpp | 14 +- 18 files changed, 365 insertions(+), 695 deletions(-) diff --git a/scripts/net.sh b/scripts/net.sh index 79a0e0088..d14e2be46 100755 --- a/scripts/net.sh +++ b/scripts/net.sh @@ -74,5 +74,5 @@ fetch_network() { return 1 } -fetch_network EvalFileDefaultNameBig && - fetch_network EvalFileDefaultNameSmall +fetch_network EvalFileDefaultName + diff --git a/src/engine.cpp b/src/engine.cpp index 786e88e21..361d8457d 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -62,7 +62,7 @@ Engine::Engine(std::optional path) : numaContext(NumaConfig::from_system(DefaultNumaPolicy)), states(new std::deque(1)), threads(), - networks(numaContext, get_default_networks()) { + network(numaContext, get_default_network()) { pos.set(StartFEN, false, &states->back()); @@ -132,14 +132,8 @@ Engine::Engine(std::optional path) : options.add("SyzygyProbeLimit", Option(7, 0, 7)); options.add( // - "EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) { - load_big_network(o); - return std::nullopt; - })); - - options.add( // - "EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) { - load_small_network(o); + "EvalFile", Option(EvalFileDefaultName, [this](const Option& o) { + load_network(o); return std::nullopt; })); @@ -149,14 +143,14 @@ Engine::Engine(std::optional path) : } std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { - verify_networks(); + verify_network(); return Benchmark::perft(fen, depth, isChess960); } void Engine::go(Search::LimitsType& limits) { assert(limits.perft == 0); - verify_networks(); + verify_network(); threads.start_thinking(options, pos, states, limits); } @@ -188,8 +182,8 @@ void Engine::set_on_bestmove(std::function&& f) { - onVerifyNetworks = std::move(f); +void Engine::set_on_verify_network(std::function&& f) { + onVerifyNetwork = std::move(f); } void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } @@ -244,7 +238,7 @@ void Engine::set_numa_config_from_option(const std::string& o) { void Engine::resize_threads() { threads.wait_for_search_finished(); - threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, networks}, + threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, network}, updateContext); // Reallocate the hash with the new threadpool size @@ -261,11 +255,10 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; } // network related -void Engine::verify_networks() const { - networks->big.verify(options["EvalFile"], onVerifyNetworks); - networks->small.verify(options["EvalFileSmall"], onVerifyNetworks); +void Engine::verify_network() const { + network->verify(options["EvalFile"], onVerifyNetwork); - auto statuses = networks.get_status_and_errors(); + auto statuses = network.get_status_and_errors(); for (size_t i = 0; i < statuses.size(); ++i) { const auto [status, error] = statuses[i]; @@ -292,41 +285,28 @@ void Engine::verify_networks() const { message += " " + *error; } - onVerifyNetworks(message); + onVerifyNetwork(message); } } -std::unique_ptr Engine::get_default_networks() const { +std::unique_ptr Engine::get_default_network() const { - auto networks_ = - std::make_unique(NN::EvalFile{EvalFileDefaultNameBig, "None", ""}, - NN::EvalFile{EvalFileDefaultNameSmall, "None", ""}); + auto network_ = std::make_unique(NN::EvalFile{EvalFileDefaultName, "None", ""}); - networks_->big.load(binaryDirectory, ""); - networks_->small.load(binaryDirectory, ""); + network_->load(binaryDirectory, ""); - return networks_; + return network_; } -void Engine::load_big_network(const std::string& file) { - networks.modify_and_replicate( - [this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); }); +void Engine::load_network(const std::string& file) { + network.modify_and_replicate( + [this, &file](NN::Network& network_) { network_.load(binaryDirectory, file); }); threads.clear(); threads.ensure_network_replicated(); } -void Engine::load_small_network(const std::string& file) { - networks.modify_and_replicate( - [this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); }); - threads.clear(); - threads.ensure_network_replicated(); -} - -void Engine::save_network(const std::pair, std::string> files[2]) { - networks.modify_and_replicate([&files](NN::Networks& networks_) { - networks_.big.save(files[0].first); - networks_.small.save(files[1].first); - }); +void Engine::save_network(const std::pair, std::string> file) { + network.modify_and_replicate([&file](NN::Network& network_) { network_.save(file.first); }); } // utility functions @@ -336,9 +316,9 @@ void Engine::trace_eval() const { Position p; p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back()); - verify_networks(); + verify_network(); - sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl; + sync_cout << "\n" << Eval::trace(p, *network) << sync_endl; } const OptionsMap& Engine::get_options() const { return options; } diff --git a/src/engine.h b/src/engine.h index e22466991..f5d09d4e1 100644 --- a/src/engine.h +++ b/src/engine.h @@ -83,15 +83,14 @@ class Engine { void set_on_update_full(std::function&&); void set_on_iter(std::function&&); void set_on_bestmove(std::function&&); - void set_on_verify_networks(std::function&&); + void set_on_verify_network(std::function&&); // network related - void verify_networks() const; - std::unique_ptr get_default_networks() const; - void load_big_network(const std::string& file); - void load_small_network(const std::string& file); - void save_network(const std::pair, std::string> files[2]); + void verify_network() const; + std::unique_ptr get_default_network() const; + void load_network(const std::string& file); + void save_network(std::pair, std::string> file); // utility functions @@ -119,13 +118,13 @@ class Engine { Position pos; StateListPtr states; - OptionsMap options; - ThreadPool threads; - TranspositionTable tt; - LazyNumaReplicatedSystemWide networks; + OptionsMap options; + ThreadPool threads; + TranspositionTable tt; + LazyNumaReplicatedSystemWide network; Search::SearchManager::UpdateContext updateContext; - std::function onVerifyNetworks; + std::function onVerifyNetwork; std::map sharedHists; }; diff --git a/src/evaluate.cpp b/src/evaluate.cpp index d93282326..dbd35d720 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include "nnue/network.h" #include "nnue/nnue_misc.h" @@ -46,11 +45,9 @@ int Eval::simple_eval(const Position& pos) { - pos.non_pawn_material(~c); } -bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; } - // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. -Value Eval::evaluate(const Eval::NNUE::Networks& networks, +Value Eval::evaluate(const Eval::NNUE::Network& network, const Position& pos, Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, @@ -58,20 +55,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); - bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small) - : networks.big.evaluate(pos, accumulators, caches.big); + auto [psqt, positional] = network.evaluate(pos, accumulators, caches); Value nnue = (125 * psqt + 131 * positional) / 128; - // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (std::abs(nnue) < 277)) - { - std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); - nnue = (125 * psqt + 131 * positional) / 128; - smallNet = false; - } - // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); optimism += optimism * nnueComplexity / 476; @@ -93,43 +80,33 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // a string (suitable for outputting to stdout) that contains the detailed // descriptions and values of each evaluation term. Useful for debugging. // Trace scores are from white's point of view -std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { +std::string Eval::trace(Position& pos, const Eval::NNUE::Network& network) { if (pos.checkers()) return "Final evaluation: none (in check)"; auto accumulators = std::make_unique(); - auto caches = std::make_unique(networks); + auto caches = std::make_unique(network); std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); - ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; + ss << '\n' << NNUE::trace(pos, network, *caches) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqtSmall, positionalSmall] = networks.small.evaluate(pos, *accumulators, caches->small); - Value vSmall = psqtSmall + positionalSmall; - bool useBig = std::abs(vSmall) < 277; - ss << "(Small net) NNUE evaluation " << vSmall << " (side to move, internal units)\n"; - vSmall = pos.side_to_move() == WHITE ? vSmall : -vSmall; - ss << "(Small net) NNUE evaluation " << 0.01 * UCIEngine::to_cp(vSmall, pos) - << " (white side)\n"; - - auto [psqtBig, positionalBig] = networks.big.evaluate(pos, *accumulators, caches->big); - Value vBig = psqtBig + positionalBig; - ss << "(Big net) NNUE evaluation " << vBig << " (side to move, internal units)\n"; - vBig = pos.side_to_move() == WHITE ? vBig : -vBig; - ss << "(Big net) NNUE evaluation " << 0.01 * UCIEngine::to_cp(vBig, pos) - << " (white side)\n"; + auto [psqt, positional] = network.evaluate(pos, *accumulators, *caches); + Value v = psqt + positional; + ss << "NNUE evaluation " << v << " (side to move, internal units)\n"; + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; ss << "SimpleEval " << simple_eval(pos) << " (side to move, internal units)\n\n"; - Value v = evaluate(networks, pos, *accumulators, *caches, VALUE_ZERO); - v = pos.side_to_move() == WHITE ? v : -v; + v = evaluate(network, pos, *accumulators, *caches, VALUE_ZERO); + v = pos.side_to_move() == WHITE ? v : -v; - ss << "Final evaluation " << "(using " - << (use_smallnet(pos) && !useBig ? "small net) " : "big net) "); + ss << "Final evaluation "; ss << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]\n"; diff --git a/src/evaluate.h b/src/evaluate.h index fc82a2465..2a93e70bc 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,20 +33,19 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-f68ec79f0fe3.nnue" -#define EvalFileDefaultNameSmall "nn-47fc8b7fff06.nnue" +#define EvalFileDefaultName "nn-f68ec79f0fe3.nnue" namespace NNUE { -struct Networks; +class Network; struct AccumulatorCaches; class AccumulatorStack; } -std::string trace(Position& pos, const Eval::NNUE::Networks& networks); +std::string trace(Position& pos, const Eval::NNUE::Network& network); int simple_eval(const Position& pos); bool use_smallnet(const Position& pos); -Value evaluate(const NNUE::Networks& networks, +Value evaluate(const NNUE::Network& network, const Position& pos, Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index b77d87178..b63789e1e 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -44,52 +44,21 @@ // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(UNIVERSAL_BINARY) && !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) -INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); -INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); +INCBIN(EmbeddedNNUE, EvalFileDefaultName); #elif defined(UNIVERSAL_BINARY) // When building for the universal binary, use C++26 #embed with weak symbols so that a // separate, non-LTO nnue_embed.o (with strong symbols) can override them during the LTO link, // (INCBIN can't deduplicate.) #define WEAK_SYM __attribute__((weak)) -extern const unsigned char gEmbeddedNNUEBigData[] WEAK_SYM = { - #embed EvalFileDefaultNameBig +extern const unsigned char gEmbeddedNNUEData[] WEAK_SYM = { + #embed EvalFileDefaultName }; -extern const unsigned int gEmbeddedNNUEBigSize WEAK_SYM = sizeof(gEmbeddedNNUEBigData); -extern const unsigned char gEmbeddedNNUESmallData[] WEAK_SYM = { - #embed EvalFileDefaultNameSmall -}; -extern const unsigned int gEmbeddedNNUESmallSize WEAK_SYM = sizeof(gEmbeddedNNUESmallData); +extern const unsigned int gEmbeddedNNUESize WEAK_SYM = sizeof(gEmbeddedNNUEData); #else -const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; -const unsigned int gEmbeddedNNUEBigSize = 1; -const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; -const unsigned int gEmbeddedNNUESmallSize = 1; +const unsigned char gEmbeddedNNUEData[1] = {0x0}; +const unsigned int gEmbeddedNNUESize = 1; #endif -namespace { - -struct EmbeddedNNUE { - EmbeddedNNUE(const unsigned char* embeddedData, const unsigned int embeddedSize) : - data(embeddedData), - end(embeddedData + embeddedSize), - size(embeddedSize) {} - const unsigned char* data; - const unsigned char* end; - const unsigned int size; -}; - -using namespace Stockfish::Eval::NNUE; - -EmbeddedNNUE get_embedded(EmbeddedNNUEType type) { - if (type == EmbeddedNNUEType::BIG) - return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigSize); - else - return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallSize); -} - -} - - namespace Stockfish::Eval::NNUE { @@ -116,8 +85,7 @@ bool write_parameters(std::ostream& stream, const T& reference) { } // namespace Detail -template -void Network::load(const std::string& rootDirectory, std::string evalfilePath) { +void Network::load(const std::string& rootDirectory, std::string evalfilePath) { #if defined(DEFAULT_NNUE_DIRECTORY) std::vector dirs = {"", "", rootDirectory, stringify(DEFAULT_NNUE_DIRECTORY)}; @@ -146,8 +114,7 @@ void Network::load(const std::string& rootDirectory, std::str } -template -bool Network::save(const std::optional& filename) const { +bool Network::save(const std::optional& filename) const { std::string actualFilename; std::string msg; @@ -177,16 +144,13 @@ bool Network::save(const std::optional& filename } -template -NetworkOutput -Network::evaluate(const Position& pos, - AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache& cache) const { +NetworkOutput Network::evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches& cache) const { constexpr uint64_t alignment = CacheLineSize; - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; ASSERT_ALIGNED(transformedFeatures, alignment); @@ -198,9 +162,8 @@ Network::evaluate(const Position& pos } -template -void Network::verify(std::string evalfilePath, - const std::function& f) const { +void Network::verify(std::string evalfilePath, + const std::function& f) const { if (evalfilePath.empty()) evalfilePath = evalFile.defaultName; @@ -229,9 +192,9 @@ void Network::verify(std::string if (f) { - size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks; + size_t size = sizeof(featureTransformer) + sizeof(NetworkArchitecture) * LayerStacks; f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024)) - + "MiB, (" + std::to_string(featureTransformer.TotalInputDimensions) + ", " + + "MiB, (" + std::to_string(featureTransformer.InputDimensions) + ", " + std::to_string(network[0].TransformedFeatureDimensions) + ", " + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + ", 1))"); @@ -239,16 +202,13 @@ void Network::verify(std::string } -template -NnueEvalTrace -Network::trace_evaluate(const Position& pos, - AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache& cache) const { +NnueEvalTrace Network::trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches& cache) const { constexpr uint64_t alignment = CacheLineSize; - alignas(alignment) - TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; ASSERT_ALIGNED(transformedFeatures, alignment); @@ -268,9 +228,7 @@ Network::trace_evaluate(const Position& } -template -void Network::load_user_net(const std::string& dir, - const std::string& evalfilePath) { +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); @@ -282,8 +240,7 @@ void Network::load_user_net(const std::string& dir, } -template -void Network::load_internal() { +void Network::load_internal() { // C++ way to prepare a buffer for a memory stream class MemoryBuffer: public std::basic_streambuf { public: @@ -293,10 +250,8 @@ void Network::load_internal() { } }; - const auto embedded = get_embedded(embeddedType); - - MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), - size_t(embedded.size)); + MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), + size_t(gEmbeddedNNUESize)); std::istream stream(&buffer); auto description = load(stream); @@ -309,16 +264,12 @@ void Network::load_internal() { } -template -void Network::initialize() { - initialized = true; -} +void Network::initialize() { initialized = true; } -template -bool Network::save(std::ostream& stream, - const std::string& name, - const std::string& netDescription) const { +bool Network::save(std::ostream& stream, + const std::string& name, + const std::string& netDescription) const { if (name.empty() || name == "None") return false; @@ -326,8 +277,7 @@ bool Network::save(std::ostream& stream, } -template -std::optional Network::load(std::istream& stream) { +std::optional Network::load(std::istream& stream) { initialize(); std::string description; @@ -335,8 +285,7 @@ std::optional Network::load(std::istream& stream } -template -std::size_t Network::get_content_hash() const { +std::size_t Network::get_content_hash() const { if (!initialized) return 0; @@ -345,15 +294,11 @@ std::size_t Network::get_content_hash() const { for (auto&& layerstack : network) hash_combine(h, layerstack); hash_combine(h, evalFile); - hash_combine(h, static_cast(embeddedType)); return h; } // Read network header -template -bool Network::read_header(std::istream& stream, - std::uint32_t* hashValue, - std::string* desc) const { +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); @@ -368,10 +313,9 @@ bool Network::read_header(std::istream& stream, // Write network header -template -bool Network::write_header(std::ostream& stream, - std::uint32_t hashValue, - const std::string& desc) const { +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())); @@ -380,9 +324,7 @@ bool Network::write_header(std::ostream& stream, } -template -bool Network::read_parameters(std::istream& stream, - std::string& netDescription) { +bool Network::read_parameters(std::istream& stream, std::string& netDescription) { std::uint32_t hashValue; if (!read_header(stream, &hashValue, &netDescription)) return false; @@ -399,9 +341,7 @@ bool Network::read_parameters(std::istream& stream, } -template -bool Network::write_parameters(std::ostream& stream, - const std::string& netDescription) const { +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)) @@ -414,12 +354,4 @@ bool Network::write_parameters(std::ostream& stream, return bool(stream); } -// Explicit template instantiations - -template class Network, - FeatureTransformer>; - -template class Network, - FeatureTransformer>; - } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h index cb433718d..5d7ba066a 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -29,11 +29,8 @@ #include #include -#include "../misc.h" #include "../types.h" -#include "nnue_accumulator.h" #include "nnue_architecture.h" -#include "nnue_common.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" @@ -43,24 +40,18 @@ class Position; namespace Stockfish::Eval::NNUE { -enum class EmbeddedNNUEType { - BIG, - SMALL, -}; +class AccumulatorStack; +struct AccumulatorCaches; using NetworkOutput = std::tuple; // The network must be a trivial type, i.e. the memory must be in-line. // This is required to allow sharing the network via shared memory, as // there is no way to run destructors. -template class Network { - static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions; - public: - Network(EvalFile file, EmbeddedNNUEType type) : - evalFile(file), - embeddedType(type) {} + Network(EvalFile file) : + evalFile(file) {} Network(const Network& other) = default; Network(Network&& other) = default; @@ -73,15 +64,15 @@ class Network { std::size_t get_content_hash() const; - NetworkOutput evaluate(const Position& pos, - AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache& cache) const; + NetworkOutput evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches& cache) const; void verify(std::string evalfilePath, const std::function&) const; - NnueEvalTrace trace_evaluate(const Position& pos, - AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache& cache) const; + NnueEvalTrace trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches& cache) const; private: void load_user_net(const std::string&, const std::string&); @@ -99,63 +90,30 @@ class Network { bool write_parameters(std::ostream&, const std::string&) const; // Input feature converter - Transformer featureTransformer; + FeatureTransformer featureTransformer; // Evaluation function - Arch network[LayerStacks]; + NetworkArchitecture network[LayerStacks]; - EvalFile evalFile; - EmbeddedNNUEType embeddedType; + EvalFile evalFile; bool initialized = false; // Hash value of evaluation function structure - static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); + static constexpr std::uint32_t hash = + FeatureTransformer::get_hash_value() ^ NetworkArchitecture::get_hash_value(); - template - friend struct AccumulatorCaches::Cache; -}; - -// Definitions of the network types -using SmallFeatureTransformer = FeatureTransformer; -using SmallNetworkArchitecture = - NetworkArchitecture; - -using BigFeatureTransformer = FeatureTransformer; -using BigNetworkArchitecture = NetworkArchitecture; - -using NetworkBig = Network; -using NetworkSmall = Network; - - -struct Networks { - Networks(EvalFile bigFile, EvalFile smallFile) : - big(bigFile, EmbeddedNNUEType::BIG), - small(smallFile, EmbeddedNNUEType::SMALL) {} - - NetworkBig big; - NetworkSmall small; + friend struct AccumulatorCaches; }; } // namespace Stockfish -template -struct std::hash> { - std::size_t operator()( - const Stockfish::Eval::NNUE::Network& network) const noexcept { +template<> +struct std::hash { + std::size_t operator()(const Stockfish::Eval::NNUE::Network& network) const noexcept { return network.get_content_hash(); } }; -template<> -struct std::hash { - std::size_t operator()(const Stockfish::Eval::NNUE::Networks& networks) const noexcept { - std::size_t h = 0; - Stockfish::hash_combine(h, networks.big); - Stockfish::hash_combine(h, networks.small); - return h; - } -}; - #endif diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index c1ac1722b..dc6813416 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -38,26 +38,23 @@ using namespace SIMD; namespace { -template -void update_accumulator_incremental( - Color perspective, - const FeatureTransformer& featureTransformer, - const Square ksq, - AccumulatorState& target_state, - const AccumulatorState& computed); +template +void update_accumulator_incremental(Color perspective, + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed); -template -void update_accumulator_refresh_cache(Color perspective, - const FeatureTransformer& featureTransformer, - const Position& pos, - AccumulatorState& accumulatorState, - AccumulatorCaches::Cache& cache); +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches& cache); -template -void update_threats_accumulator_full(Color perspective, - const FeatureTransformer& featureTransformer, - const Position& pos, - AccumulatorState& accumulatorState); +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState); } template @@ -120,33 +117,26 @@ void AccumulatorStack::pop() noexcept { size--; } -template -void AccumulatorStack::evaluate(const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept { - constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig); - +void AccumulatorStack::evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + // Silence spurious warning on GCC 10 + [[maybe_unused]] AccumulatorCaches& cache) noexcept { evaluate_side(WHITE, pos, featureTransformer, cache); evaluate_side(BLACK, pos, featureTransformer, cache); - if (UseThreats) - { - evaluate_side(WHITE, pos, featureTransformer, cache); - evaluate_side(BLACK, pos, featureTransformer, cache); - } + evaluate_side(WHITE, pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); } -template -void AccumulatorStack::evaluate_side(Color perspective, - const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept { +template +void AccumulatorStack::evaluate_side(Color perspective, + const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches& cache) noexcept { - const auto last_usable_accum = - find_last_usable_accumulator(perspective); + const auto last_usable_accum = find_last_usable_accumulator(perspective); - if ((accumulators()[last_usable_accum].template acc()) - .computed[perspective]) + if (accumulators()[last_usable_accum].computed[perspective]) forward_update_incremental(perspective, pos, featureTransformer, last_usable_accum); @@ -166,12 +156,12 @@ void AccumulatorStack::evaluate_side(Color persp // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator // state just before a change that requires full refresh. -template +template std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) const noexcept { for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) { - if ((accumulators()[curr_idx].template acc()).computed[perspective]) + if (accumulators()[curr_idx].computed[perspective]) return curr_idx; if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, perspective)) @@ -181,15 +171,14 @@ std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) co return 0; } -template -void AccumulatorStack::forward_update_incremental( - Color perspective, - const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t begin) noexcept { +template +void AccumulatorStack::forward_update_incremental(Color perspective, + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept { assert(begin < accumulators().size()); - assert((accumulators()[begin].template acc()).computed[perspective]); + assert(accumulators()[begin].computed[perspective]); const Square ksq = pos.square(perspective); @@ -200,20 +189,19 @@ void AccumulatorStack::forward_update_incremental( accumulators()[next - 1]); } - assert((latest().acc()).computed[perspective]); + assert(latest().computed[perspective]); } -template -void AccumulatorStack::backward_update_incremental( - Color perspective, +template +void AccumulatorStack::backward_update_incremental(Color perspective, - const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t end) noexcept { + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept { assert(end < accumulators().size()); assert(end < size); - assert((latest().template acc()).computed[perspective]); + assert(latest().computed[perspective]); const Square ksq = pos.square(perspective); @@ -222,20 +210,9 @@ void AccumulatorStack::backward_update_incremental( mut_accumulators()[next], accumulators()[next + 1]); - assert((accumulators()[end].template acc()).computed[perspective]); + assert(accumulators()[end].computed[perspective]); } -// Explicit template instantiations -template void AccumulatorStack::evaluate( - const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept; -template void AccumulatorStack::evaluate( - const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept; - - namespace { template(rows)[i]...); } -template +template struct AccumulatorUpdateContext { - Color perspective; - const FeatureTransformer& featureTransformer; - const AccumulatorState& from; - AccumulatorState& to; + Color perspective; + const FeatureTransformer& featureTransformer; + const AccumulatorState& from; + AccumulatorState& to; - AccumulatorUpdateContext(Color persp, - const FeatureTransformer& ft, - const AccumulatorState& accF, - AccumulatorState& accT) noexcept : + AccumulatorUpdateContext(Color persp, + const FeatureTransformer& ft, + const AccumulatorState& accF, + AccumulatorState& accT) noexcept : perspective{persp}, featureTransformer{ft}, from{accF}, @@ -275,6 +252,8 @@ struct AccumulatorUpdateContext { typename... Ts, std::enable_if_t, bool> = true> void apply(const Ts... indices) { + constexpr IndexType Dimensions = FeatureTransformer::OutputDimensions; + auto to_weight_vector = [&](const IndexType index) { return &featureTransformer.weights[index * Dimensions]; }; @@ -283,27 +262,28 @@ struct AccumulatorUpdateContext { return &featureTransformer.psqtWeights[index * PSQTBuckets]; }; - fused_row_reduce( - (from.template acc()).accumulation[perspective].data(), - (to.template acc()).accumulation[perspective].data(), - to_weight_vector(indices)...); + fused_row_reduce(from.accumulation[perspective].data(), + to.accumulation[perspective].data(), + to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[perspective].data(), - (to.template acc()).psqtAccumulation[perspective].data(), + from.psqtAccumulation[perspective].data(), to.psqtAccumulation[perspective].data(), to_psqt_weight_vector(indices)...); } void apply(const typename FeatureSet::IndexList& added, const typename FeatureSet::IndexList& removed) { - const auto& fromAcc = from.template acc().accumulation[perspective]; - auto& toAcc = to.template acc().accumulation[perspective]; + constexpr IndexType Dimensions = FeatureTransformer::OutputDimensions; - const auto& fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; - auto& toPsqtAcc = to.template acc().psqtAccumulation[perspective]; + const auto& fromAcc = from.accumulation[perspective]; + auto& toAcc = to.accumulation[perspective]; + + const auto& fromPsqtAcc = from.psqtAccumulation[perspective]; + auto& toPsqtAcc = to.psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; + vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; @@ -426,25 +406,24 @@ struct AccumulatorUpdateContext { } }; -template -auto make_accumulator_update_context(Color perspective, - const FeatureTransformer& featureTransformer, - const AccumulatorState& accumulatorFrom, - AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{perspective, featureTransformer, - accumulatorFrom, accumulatorTo}; +template +auto make_accumulator_update_context(Color perspective, + const FeatureTransformer& featureTransformer, + const AccumulatorState& accumulatorFrom, + AccumulatorState& accumulatorTo) noexcept { + return AccumulatorUpdateContext{perspective, featureTransformer, accumulatorFrom, + accumulatorTo}; } -template -void update_accumulator_incremental( - Color perspective, - const FeatureTransformer& featureTransformer, - const Square ksq, - AccumulatorState& target_state, - const AccumulatorState& computed) { +template +void update_accumulator_incremental(Color perspective, + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed) { - assert((computed.template acc()).computed[perspective]); - assert(!(target_state.template acc()).computed[perspective]); + assert(computed.computed[perspective]); + assert(!target_state.computed[perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -456,7 +435,7 @@ void update_accumulator_incremental( if constexpr (std::is_same_v) { const auto* pfBase = &featureTransformer.threatWeights[0]; - auto pfStride = static_cast(TransformedFeatureDimensions); + IndexType pfStride = FeatureTransformer::OutputDimensions; if constexpr (Forward) FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added, nullptr, false, pfBase, pfStride); @@ -519,7 +498,7 @@ void update_accumulator_incremental( } } - (target_state.template acc()).computed[perspective] = true; + target_state.computed[perspective] = true; } Bitboard get_changed_pieces(const std::array& oldPieces, @@ -559,12 +538,12 @@ Bitboard get_changed_pieces(const std::array& oldPieces, #endif } -template -void update_accumulator_refresh_cache(Color perspective, - const FeatureTransformer& featureTransformer, - const Position& pos, - AccumulatorState& accumulatorState, - AccumulatorCaches::Cache& cache) { +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulator, + AccumulatorCaches& cache) { + constexpr auto Dimensions = FeatureTransformer::OutputDimensions; using Tiling [[maybe_unused]] = SIMDTiling; @@ -595,7 +574,6 @@ void update_accumulator_refresh_cache(Color pers entry.pieceBB = pos.pieces(); entry.pieces = pos.piece_array(); - auto& accumulator = accumulatorState.acc(); accumulator.computed[perspective] = true; #ifdef VECTOR @@ -705,17 +683,16 @@ void update_accumulator_refresh_cache(Color pers #endif } -template -void update_threats_accumulator_full(Color perspective, - const FeatureTransformer& featureTransformer, - const Position& pos, - AccumulatorState& accumulatorState) { - using Tiling [[maybe_unused]] = SIMDTiling; +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulator) { + constexpr IndexType Dimensions = FeatureTransformer::OutputDimensions; + using Tiling [[maybe_unused]] = SIMDTiling; ThreatFeatureSet::IndexList active; ThreatFeatureSet::append_active_indices(perspective, pos, active); - auto& accumulator = accumulatorState.acc(); accumulator.computed[perspective] = true; #ifdef VECTOR diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 438074f43..6673d3c6f 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -37,16 +37,13 @@ class Position; namespace Stockfish::Eval::NNUE { -template struct alignas(CacheLineSize) Accumulator; -template class FeatureTransformer; // Class that holds the result of affine transformation of input features -template struct alignas(CacheLineSize) Accumulator { - std::array, COLOR_NB> accumulation; + std::array, COLOR_NB> accumulation; std::array, COLOR_NB> psqtAccumulation; std::array computed = {}; }; @@ -59,92 +56,50 @@ struct alignas(CacheLineSize) Accumulator { // This idea, was first described by Luecx (author of Koivisto) and // is commonly referred to as "Finny Tables". struct AccumulatorCaches { - - template - AccumulatorCaches(const Networks& networks) { - clear(networks); + template + AccumulatorCaches(const Network& network) { + clear(network); } - template - struct alignas(CacheLineSize) Cache { + struct alignas(CacheLineSize) Entry { + std::array accumulation; + std::array psqtAccumulation; + std::array pieces; + Bitboard pieceBB; - struct alignas(CacheLineSize) Entry { - std::array accumulation; - std::array psqtAccumulation; - std::array pieces; - Bitboard pieceBB; - - // To initialize a refresh entry, we set all its bitboards empty, - // so we put the biases in the accumulation, without any weights on top - void clear(const std::array& biases) { - accumulation = biases; - std::memset(reinterpret_cast(this) + offsetof(Entry, psqtAccumulation), - 0, sizeof(Entry) - offsetof(Entry, psqtAccumulation)); - } - }; - - template - void clear(const Network& network) { - for (auto& entries1D : entries) - for (auto& entry : entries1D) - entry.clear(network.featureTransformer.biases); + // To initialize a refresh entry, we set all its bitboards empty, + // so we put the biases in the accumulation, without any weights on top + void clear(const std::array& biases) { + accumulation = biases; + std::memset(reinterpret_cast(this) + offsetof(Entry, psqtAccumulation), 0, + sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } - - std::array& operator[](Square sq) { return entries[sq]; } - - std::array, SQUARE_NB> entries; }; - template - void clear(const Networks& networks) { - big.clear(networks.big); - small.clear(networks.small); + template + void clear(const Network& network) { + for (auto& entries1D : entries) + for (auto& entry : entries1D) + entry.clear(network.featureTransformer.biases); } - Cache big; - Cache small; + std::array& operator[](Square sq) { return entries[sq]; } + + std::array, SQUARE_NB> entries; }; template -struct AccumulatorState { - Accumulator accumulatorBig; - Accumulator accumulatorSmall; - typename FeatureSet::DiffType diff; - - template - auto& acc() noexcept { - static_assert(Size == TransformedFeatureDimensionsBig - || Size == TransformedFeatureDimensionsSmall, - "Invalid size for accumulator"); - - if constexpr (Size == TransformedFeatureDimensionsBig) - return accumulatorBig; - else if constexpr (Size == TransformedFeatureDimensionsSmall) - return accumulatorSmall; - } - - template - const auto& acc() const noexcept { - static_assert(Size == TransformedFeatureDimensionsBig - || Size == TransformedFeatureDimensionsSmall, - "Invalid size for accumulator"); - - if constexpr (Size == TransformedFeatureDimensionsBig) - return accumulatorBig; - else if constexpr (Size == TransformedFeatureDimensionsSmall) - return accumulatorSmall; - } +struct AccumulatorState: public Accumulator { + typename FeatureSet::DiffType diff; void reset(const typename FeatureSet::DiffType& dp) noexcept { diff = dp; - accumulatorBig.computed.fill(false); - accumulatorSmall.computed.fill(false); + computed.fill(false); } typename FeatureSet::DiffType& reset() noexcept { - accumulatorBig.computed.fill(false); - accumulatorSmall.computed.fill(false); + computed.fill(false); return diff; } }; @@ -160,10 +115,10 @@ class AccumulatorStack { std::pair push() noexcept; void pop() noexcept; - template - void evaluate(const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept; + void evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + // Silence spurious warning on GCC 10 + [[maybe_unused]] AccumulatorCaches& cache) noexcept; private: template @@ -175,26 +130,27 @@ class AccumulatorStack { template [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; - template - void evaluate_side(Color perspective, - const Position& pos, - const FeatureTransformer& featureTransformer, - AccumulatorCaches::Cache& cache) noexcept; + template + void evaluate_side(Color perspective, + const Position& pos, + const FeatureTransformer& featureTransformer, + // Silence spurious warning on GCC 10 + [[maybe_unused]] AccumulatorCaches& cache) noexcept; - template + template [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept; - template - void forward_update_incremental(Color perspective, - const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t begin) noexcept; + template + void forward_update_incremental(Color perspective, + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept; - template - void backward_update_incremental(Color perspective, - const Position& pos, - const FeatureTransformer& featureTransformer, - const std::size_t end) noexcept; + template + void backward_update_incremental(Color perspective, + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept; std::array, MaxSize> psq_accumulators; std::array, MaxSize> threat_accumulators; diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h index f81dcc0f2..02c38df6d 100644 --- a/src/nnue/nnue_architecture.h +++ b/src/nnue/nnue_architecture.h @@ -40,13 +40,9 @@ using ThreatFeatureSet = Features::FullThreats; using PSQFeatureSet = Features::HalfKAv2_hm; // Number of input feature dimensions after conversion -constexpr IndexType TransformedFeatureDimensionsBig = 1024; -constexpr int L2Big = 31; -constexpr int L3Big = 32; - -constexpr IndexType TransformedFeatureDimensionsSmall = 128; -constexpr int L2Small = 15; -constexpr int L3Small = 32; +constexpr IndexType L1 = 1024; +constexpr int L2 = 31; +constexpr int L3 = 32; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; @@ -57,7 +53,6 @@ constexpr IndexType LayerStacks = 8; static_assert(PSQTBuckets % 8 == 0, "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); -template struct NetworkArchitecture { static constexpr IndexType TransformedFeatureDimensions = L1; static constexpr int FC_0_OUTPUTS = L2; @@ -147,10 +142,9 @@ struct NetworkArchitecture { } // namespace Stockfish::Eval::NNUE -template -struct std::hash> { - std::size_t - operator()(const Stockfish::Eval::NNUE::NetworkArchitecture& arch) const noexcept { +template<> +struct std::hash { + std::size_t operator()(const Stockfish::Eval::NNUE::NetworkArchitecture& arch) const noexcept { return arch.get_content_hash(); } }; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 798c0fa11..6b913e80f 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -78,22 +78,17 @@ void permute(std::array& data, const std::array& o } // Input feature converter -template class FeatureTransformer { - static constexpr bool UseThreats = - (TransformedFeatureDimensions == TransformedFeatureDimensionsBig); // Number of output dimensions for one side - static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + static constexpr IndexType HalfDimensions = L1; public: // Output type using OutputType = TransformedFeatureType; // Number of input/output dimensions - static constexpr IndexType InputDimensions = PSQFeatureSet::Dimensions; - static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions; - static constexpr IndexType TotalInputDimensions = - InputDimensions + (UseThreats ? ThreatInputDimensions : 0); + static constexpr IndexType InputDimensions = + PSQFeatureSet::Dimensions + ThreatFeatureSet::Dimensions; static constexpr IndexType OutputDimensions = HalfDimensions; // Size of forward propagation buffer @@ -134,8 +129,7 @@ class FeatureTransformer { // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { - return (UseThreats ? combine_hash({ThreatFeatureSet::HashValue, PSQFeatureSet::HashValue}) - : PSQFeatureSet::HashValue) + return combine_hash({ThreatFeatureSet::HashValue, PSQFeatureSet::HashValue}) ^ (OutputDimensions * 2); } @@ -143,35 +137,23 @@ class FeatureTransformer { permute<16>(biases, PackusEpi16Order); permute<16>(weights, PackusEpi16Order); - if constexpr (UseThreats) - permute<8>(threatWeights, PackusEpi16Order); + permute<8>(threatWeights, PackusEpi16Order); } void unpermute_weights() { permute<16>(biases, InversePackusEpi16Order); permute<16>(weights, InversePackusEpi16Order); - - if constexpr (UseThreats) - permute<8>(threatWeights, InversePackusEpi16Order); + permute<8>(threatWeights, InversePackusEpi16Order); } // Read network parameters bool read_parameters(std::istream& stream) { read_leb_128(stream, biases); - if constexpr (UseThreats) - { - read_little_endian(stream, threatWeights.data(), - ThreatInputDimensions * HalfDimensions); - read_leb_128(stream, weights); - - read_leb_128(stream, threatPsqtWeights, psqtWeights); - } - else - { - read_leb_128(stream, weights); - read_leb_128(stream, psqtWeights); - } + read_little_endian(stream, threatWeights.data(), + ThreatFeatureSet::Dimensions * HalfDimensions); + read_leb_128(stream, weights); + read_leb_128(stream, threatPsqtWeights, psqtWeights); permute_weights(); @@ -186,30 +168,22 @@ class FeatureTransformer { write_leb_128(stream, copy->biases); - if constexpr (UseThreats) - { - write_little_endian(stream, copy->threatWeights.data(), - ThreatInputDimensions * HalfDimensions); - write_leb_128(stream, copy->weights); + write_little_endian(stream, copy->threatWeights.data(), + ThreatFeatureSet::Dimensions * HalfDimensions); + write_leb_128(stream, copy->weights); - auto combinedPsqtWeights = - std::make_unique>(); + auto combinedPsqtWeights = + std::make_unique>(); - std::copy(std::begin(copy->threatPsqtWeights), - std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets, - combinedPsqtWeights->begin()); + std::copy(std::begin(copy->threatPsqtWeights), + std::begin(copy->threatPsqtWeights) + ThreatFeatureSet::Dimensions * PSQTBuckets, + combinedPsqtWeights->begin()); - std::copy(std::begin(copy->psqtWeights), - std::begin(copy->psqtWeights) + InputDimensions * PSQTBuckets, - combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets); + std::copy(std::begin(copy->psqtWeights), + std::begin(copy->psqtWeights) + PSQFeatureSet::Dimensions * PSQTBuckets, + combinedPsqtWeights->begin() + ThreatFeatureSet::Dimensions * PSQTBuckets); - write_leb_128(stream, *combinedPsqtWeights); - } - else - { - write_leb_128(stream, copy->weights); - write_leb_128(stream, copy->psqtWeights); - } + write_leb_128(stream, *combinedPsqtWeights); return !stream.fail(); } @@ -221,11 +195,8 @@ class FeatureTransformer { hash_combine(h, get_raw_data_hash(weights)); hash_combine(h, get_raw_data_hash(psqtWeights)); - if constexpr (UseThreats) - { - hash_combine(h, get_raw_data_hash(threatWeights)); - hash_combine(h, get_raw_data_hash(threatPsqtWeights)); - } + hash_combine(h, get_raw_data_hash(threatWeights)); + hash_combine(h, get_raw_data_hash(threatPsqtWeights)); hash_combine(h, get_hash_value()); @@ -233,11 +204,11 @@ class FeatureTransformer { } // Convert input features - std::int32_t transform(const Position& pos, - AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache& cache, - OutputType* output, - int bucket) const { + std::int32_t transform(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches& cache, + OutputType* output, + int bucket) const { using namespace SIMD; accumulatorStack.evaluate(pos, *this, cache); @@ -245,24 +216,17 @@ class FeatureTransformer { const auto& threatAccumulatorState = accumulatorStack.latest(); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& psqtAccumulation = (accumulatorState.acc()).psqtAccumulation; + const auto& psqtAccumulation = accumulatorState.psqtAccumulation; auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]); - if constexpr (UseThreats) - { - const auto& threatPsqtAccumulation = - (threatAccumulatorState.acc()).psqtAccumulation; - psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket] - - threatPsqtAccumulation[perspectives[1]][bucket]) - / 2; - } - else - psqt /= 2; + const auto& threatPsqtAccumulation = threatAccumulatorState.psqtAccumulation; + psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket] + - threatPsqtAccumulation[perspectives[1]][bucket]) + / 2; - const auto& accumulation = (accumulatorState.acc()).accumulation; - const auto& threatAccumulation = - (threatAccumulatorState.acc()).accumulation; + const auto& accumulation = accumulatorState.accumulation; + const auto& threatAccumulation = threatAccumulatorState.accumulation; for (IndexType p = 0; p < 2; ++p) { @@ -341,48 +305,27 @@ class FeatureTransformer { #else 6; #endif - if constexpr (UseThreats) + + const vec_t* tin0 = + reinterpret_cast(&(threatAccumulation[perspectives[p]][0])); + const vec_t* tin1 = reinterpret_cast( + &(threatAccumulation[perspectives[p]][HalfDimensions / 2])); + for (IndexType j = 0; j < NumOutputChunks; ++j) { - const vec_t* tin0 = - reinterpret_cast(&(threatAccumulation[perspectives[p]][0])); - const vec_t* tin1 = reinterpret_cast( - &(threatAccumulation[perspectives[p]][HalfDimensions / 2])); - for (IndexType j = 0; j < NumOutputChunks; ++j) - { - const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]); - const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]); - const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]); - const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]); + const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]); + const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]); + const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]); + const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]); - const vec_t sum0a = - vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift); - const vec_t sum0b = - vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift); - const vec_t sum1a = vec_min_16(acc1a, One); - const vec_t sum1b = vec_min_16(acc1b, One); + const vec_t sum0a = vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift); + const vec_t sum0b = vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift); + const vec_t sum1a = vec_min_16(acc1a, One); + const vec_t sum1b = vec_min_16(acc1b, One); - const vec_t pa = vec_mulhi_16(sum0a, sum1a); - const vec_t pb = vec_mulhi_16(sum0b, sum1b); + const vec_t pa = vec_mulhi_16(sum0a, sum1a); + const vec_t pb = vec_mulhi_16(sum0b, sum1b); - out[j] = vec_packus_16(pa, pb); - } - } - else - { - for (IndexType j = 0; j < NumOutputChunks; ++j) - { - const vec_t sum0a = - vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift); - const vec_t sum0b = - vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift); - const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One); - const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One); - - const vec_t pa = vec_mulhi_16(sum0a, sum1a); - const vec_t pb = vec_mulhi_16(sum0b, sum1b); - - out[j] = vec_packus_16(pa, pb); - } + out[j] = vec_packus_16(pa, pb); } #else @@ -393,12 +336,9 @@ class FeatureTransformer { BiasType sum1 = accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - if constexpr (UseThreats) - { - sum0 += threatAccumulation[static_cast(perspectives[p])][j + 0]; - sum1 += - threatAccumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; - } + sum0 += threatAccumulation[static_cast(perspectives[p])][j + 0]; + sum1 += + threatAccumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; sum0 = std::clamp(sum0, 0, 255); sum1 = std::clamp(sum1, 0, 255); @@ -413,24 +353,21 @@ class FeatureTransformer { } // end of function transform() alignas(CacheLineSize) std::array biases; - alignas(CacheLineSize) std::array weights; + alignas( + CacheLineSize) std::array weights; alignas(CacheLineSize) - std::array threatWeights; - alignas(CacheLineSize) std::array psqtWeights; + std::array threatWeights; alignas(CacheLineSize) - std::array threatPsqtWeights; + std::array psqtWeights; + alignas(CacheLineSize) + std::array threatPsqtWeights; }; } // namespace Stockfish::Eval::NNUE - -template -struct std::hash> { - std::size_t - operator()(const Stockfish::Eval::NNUE::FeatureTransformer& ft) - const noexcept { +template<> +struct std::hash { + std::size_t operator()(const Stockfish::Eval::NNUE::FeatureTransformer& ft) const noexcept { return ft.get_content_hash(); } }; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 883a22383..1a02ef7cf 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -55,16 +55,16 @@ void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& p // Returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. std::string -trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) { +trace(Position& pos, const Eval::NNUE::Network& network, Eval::NNUE::AccumulatorCaches& caches) { std::stringstream ss; auto accumulators = std::make_unique(); - accumulators->reset(); - auto tSmall = networks.small.trace_evaluate(pos, *accumulators, caches.small); - ss << "(Small net) NNUE network contributions (Normalized, " + auto t = network.trace_evaluate(pos, *accumulators, caches); + + ss << "NNUE network contributions (Normalized, " << (pos.side_to_move() == WHITE ? "White to move)" : "Black to move)") << std::endl << "+------------+------------+------------+------------+\n" << "| Bucket | Material | Positional | Total |\n" @@ -75,45 +75,16 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat { ss << "| " << bucket << " " // << " | "; - format_cp_aligned_dot(tSmall.psqt[bucket], ss, pos); + format_cp_aligned_dot(t.psqt[bucket], ss, pos); ss << " " // << " | "; - format_cp_aligned_dot(tSmall.positional[bucket], ss, pos); + format_cp_aligned_dot(t.positional[bucket], ss, pos); ss << " " // << " | "; - format_cp_aligned_dot(tSmall.psqt[bucket] + tSmall.positional[bucket], ss, pos); + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); ss << " " // << " |"; - if (bucket == tSmall.correctBucket) - ss << " <-- this bucket is used"; - ss << '\n'; - } - - ss << "+------------+------------+------------+------------+\n\n"; - - auto tBig = networks.big.trace_evaluate(pos, *accumulators, caches.big); - - ss << "(Big net) NNUE network contributions (Normalized, " - << (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 << " " // - << " | "; - format_cp_aligned_dot(tBig.psqt[bucket], ss, pos); - ss << " " // - << " | "; - format_cp_aligned_dot(tBig.positional[bucket], ss, pos); - ss << " " // - << " | "; - format_cp_aligned_dot(tBig.psqt[bucket] + tBig.positional[bucket], ss, pos); - ss << " " // - << " |"; - if (bucket == tBig.correctBucket) + if (bucket == t.correctBucket) ss << " <-- this bucket is used"; ss << '\n'; } diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index ecece5589..8b2a1e789 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -52,10 +52,10 @@ struct NnueEvalTrace { std::size_t correctBucket; }; -struct Networks; +class Network; struct AccumulatorCaches; -std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches); +std::string trace(Position& pos, const Network& network, AccumulatorCaches& caches); } // namespace Stockfish::Eval::NNUE } // namespace Stockfish diff --git a/src/position.cpp b/src/position.cpp index c58c0f03f..71a56cb1b 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1195,10 +1195,11 @@ void write_multiple_dirties(const Position& p, #endif template -void Position::update_piece_threats(Piece pc, - bool putPiece, - Square s, - DirtyThreats* const dts, +void Position::update_piece_threats(Piece pc, + bool putPiece, + Square s, + DirtyThreats* const dts, + // Silence spurious warning on GCC 10 [[maybe_unused]] Bitboard noRaysContaining) const { const Bitboard occupied = pieces(); const Bitboard rookQueens = pieces(ROOK, QUEEN); diff --git a/src/search.cpp b/src/search.cpp index d0f8d55c7..57fc20c73 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -171,15 +171,15 @@ Search::Worker::Worker(SharedState& sharedState, options(sharedState.options), threads(sharedState.threads), tt(sharedState.tt), - networks(sharedState.networks), - refreshTable(networks[token]) { + network(sharedState.network), + refreshTable(network[token]) { clear(); } void Search::Worker::ensure_network_replicated() { // Access once to force lazy initialization. // We do this because we want to avoid initialization during search. - (void) (networks[numaAccessToken]); + (void) (network[numaAccessToken]); } void Search::Worker::start_searching() { @@ -639,7 +639,7 @@ void Search::Worker::clear() { for (size_t i = 1; i < reductions.size(); ++i) reductions[i] = int(2763 / 128.0 * std::log(i)); - refreshTable.clear(networks[numaAccessToken]); + refreshTable.clear(network[numaAccessToken]); } @@ -1785,7 +1785,7 @@ TimePoint Search::Worker::elapsed() const { TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } Value Search::Worker::evaluate(const Position& pos) { - return Eval::evaluate(networks[numaAccessToken], pos, accumulatorStack, refreshTable, + return Eval::evaluate(network[numaAccessToken], pos, accumulatorStack, refreshTable, optimism[pos.side_to_move()]); } diff --git a/src/search.h b/src/search.h index 50aef6aa9..23f6a2b3d 100644 --- a/src/search.h +++ b/src/search.h @@ -175,22 +175,22 @@ 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, - std::map& sharedHists, - const LazyNumaReplicatedSystemWide& nets) : + SharedState(const OptionsMap& optionsMap, + ThreadPool& threadPool, + TranspositionTable& transpositionTable, + std::map& sharedHists, + const LazyNumaReplicatedSystemWide& net) : options(optionsMap), threads(threadPool), tt(transpositionTable), sharedHistories(sharedHists), - networks(nets) {} + network(net) {} - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - std::map& sharedHistories; - const LazyNumaReplicatedSystemWide& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + std::map& sharedHistories; + const LazyNumaReplicatedSystemWide& network; }; class Worker; @@ -396,10 +396,10 @@ class Worker { Tablebases::Config tbConfig; - const OptionsMap& options; - ThreadPool& threads; - TranspositionTable& tt; - const LazyNumaReplicatedSystemWide& networks; + const OptionsMap& options; + ThreadPool& threads; + TranspositionTable& tt; + const LazyNumaReplicatedSystemWide& network; // Used by NNUE Eval::NNUE::AccumulatorStack accumulatorStack; diff --git a/src/uci.cpp b/src/uci.cpp index 2f2364736..77d62e916 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -82,7 +82,7 @@ void UCIEngine::init_search_update_listeners() { engine.set_on_update_full( [this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); }); engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); }); - engine.set_on_verify_networks([](const auto& s) { print_info_string(s); }); + engine.set_on_verify_network([](const auto& s) { print_info_string(s); }); } void UCIEngine::loop() { @@ -152,15 +152,12 @@ void UCIEngine::loop() { sync_cout << compiler_info() << sync_endl; else if (token == "export_net") { - std::pair, std::string> files[2]; + std::pair, std::string> file; - if (is >> files[0].second) - files[0].first = files[0].second; + if (is >> file.second) + file.first = file.second; - if (is >> files[1].second) - files[1].first = files[1].second; - - engine.save_network(files); + engine.save_network(file); } else if (token == "--help" || token == "help" || token == "--license" || token == "license") sync_cout @@ -309,7 +306,7 @@ void UCIEngine::benchmark(std::istream& args) { engine.set_on_iter([](const auto&) {}); engine.set_on_update_no_moves([](const auto&) {}); engine.set_on_bestmove([](const auto&, const auto&) {}); - engine.set_on_verify_networks([](const auto&) {}); + engine.set_on_verify_network([](const auto&) {}); Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args); diff --git a/src/universal/nnue_embed.cpp b/src/universal/nnue_embed.cpp index 738cd8ac7..e9d5879f1 100644 --- a/src/universal/nnue_embed.cpp +++ b/src/universal/nnue_embed.cpp @@ -2,15 +2,7 @@ #include "../evaluate.h" -extern const unsigned char gEmbeddedNNUEBigData[] = { -#embed EvalFileDefaultNameBig +extern const unsigned char gEmbeddedNNUEData[] = { +#embed EvalFileDefaultName }; -extern const unsigned int gEmbeddedNNUEBigSize = sizeof(gEmbeddedNNUEBigData); -extern const unsigned char* const gEmbeddedNNUEBigEnd = gEmbeddedNNUEBigData + gEmbeddedNNUEBigSize; - -extern const unsigned char gEmbeddedNNUESmallData[] = { -#embed EvalFileDefaultNameSmall -}; -extern const unsigned int gEmbeddedNNUESmallSize = sizeof(gEmbeddedNNUESmallData); -extern const unsigned char* const gEmbeddedNNUESmallEnd = - gEmbeddedNNUESmallData + gEmbeddedNNUESmallSize; +extern const unsigned int gEmbeddedNNUESize = sizeof(gEmbeddedNNUEData);