Remove small net

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
This commit is contained in:
anematode
2026-05-04 08:32:42 +02:00
committed by Joost VandeVondele
parent dc1686345c
commit ab8f901d25
18 changed files with 365 additions and 695 deletions
+2 -2
View File
@@ -74,5 +74,5 @@ fetch_network() {
return 1 return 1
} }
fetch_network EvalFileDefaultNameBig && fetch_network EvalFileDefaultName
fetch_network EvalFileDefaultNameSmall
+23 -43
View File
@@ -62,7 +62,7 @@ Engine::Engine(std::optional<std::string> path) :
numaContext(NumaConfig::from_system(DefaultNumaPolicy)), numaContext(NumaConfig::from_system(DefaultNumaPolicy)),
states(new std::deque<StateInfo>(1)), states(new std::deque<StateInfo>(1)),
threads(), threads(),
networks(numaContext, get_default_networks()) { network(numaContext, get_default_network()) {
pos.set(StartFEN, false, &states->back()); pos.set(StartFEN, false, &states->back());
@@ -132,14 +132,8 @@ Engine::Engine(std::optional<std::string> path) :
options.add("SyzygyProbeLimit", Option(7, 0, 7)); options.add("SyzygyProbeLimit", Option(7, 0, 7));
options.add( // options.add( //
"EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) { "EvalFile", Option(EvalFileDefaultName, [this](const Option& o) {
load_big_network(o); load_network(o);
return std::nullopt;
}));
options.add( //
"EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) {
load_small_network(o);
return std::nullopt; return std::nullopt;
})); }));
@@ -149,14 +143,14 @@ Engine::Engine(std::optional<std::string> path) :
} }
std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
verify_networks(); verify_network();
return Benchmark::perft(fen, depth, isChess960); return Benchmark::perft(fen, depth, isChess960);
} }
void Engine::go(Search::LimitsType& limits) { void Engine::go(Search::LimitsType& limits) {
assert(limits.perft == 0); assert(limits.perft == 0);
verify_networks(); verify_network();
threads.start_thinking(options, pos, states, limits); threads.start_thinking(options, pos, states, limits);
} }
@@ -188,8 +182,8 @@ void Engine::set_on_bestmove(std::function<void(std::string_view, std::string_vi
updateContext.onBestmove = std::move(f); updateContext.onBestmove = std::move(f);
} }
void Engine::set_on_verify_networks(std::function<void(std::string_view)>&& f) { void Engine::set_on_verify_network(std::function<void(std::string_view)>&& f) {
onVerifyNetworks = std::move(f); onVerifyNetwork = std::move(f);
} }
void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); } 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() { void Engine::resize_threads() {
threads.wait_for_search_finished(); 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); updateContext);
// Reallocate the hash with the new threadpool size // 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 // network related
void Engine::verify_networks() const { void Engine::verify_network() const {
networks->big.verify(options["EvalFile"], onVerifyNetworks); network->verify(options["EvalFile"], onVerifyNetwork);
networks->small.verify(options["EvalFileSmall"], onVerifyNetworks);
auto statuses = networks.get_status_and_errors(); auto statuses = network.get_status_and_errors();
for (size_t i = 0; i < statuses.size(); ++i) for (size_t i = 0; i < statuses.size(); ++i)
{ {
const auto [status, error] = statuses[i]; const auto [status, error] = statuses[i];
@@ -292,41 +285,28 @@ void Engine::verify_networks() const {
message += " " + *error; message += " " + *error;
} }
onVerifyNetworks(message); onVerifyNetwork(message);
} }
} }
std::unique_ptr<Eval::NNUE::Networks> Engine::get_default_networks() const { std::unique_ptr<Eval::NNUE::Network> Engine::get_default_network() const {
auto networks_ = auto network_ = std::make_unique<NN::Network>(NN::EvalFile{EvalFileDefaultName, "None", ""});
std::make_unique<NN::Networks>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
NN::EvalFile{EvalFileDefaultNameSmall, "None", ""});
networks_->big.load(binaryDirectory, ""); network_->load(binaryDirectory, "");
networks_->small.load(binaryDirectory, "");
return networks_; return network_;
} }
void Engine::load_big_network(const std::string& file) { void Engine::load_network(const std::string& file) {
networks.modify_and_replicate( network.modify_and_replicate(
[this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); }); [this, &file](NN::Network& network_) { network_.load(binaryDirectory, file); });
threads.clear(); threads.clear();
threads.ensure_network_replicated(); threads.ensure_network_replicated();
} }
void Engine::load_small_network(const std::string& file) { void Engine::save_network(const std::pair<std::optional<std::string>, std::string> file) {
networks.modify_and_replicate( network.modify_and_replicate([&file](NN::Network& network_) { network_.save(file.first); });
[this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); });
threads.clear();
threads.ensure_network_replicated();
}
void Engine::save_network(const std::pair<std::optional<std::string>, std::string> files[2]) {
networks.modify_and_replicate([&files](NN::Networks& networks_) {
networks_.big.save(files[0].first);
networks_.small.save(files[1].first);
});
} }
// utility functions // utility functions
@@ -336,9 +316,9 @@ void Engine::trace_eval() const {
Position p; Position p;
p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back()); 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; } const OptionsMap& Engine::get_options() const { return options; }
+10 -11
View File
@@ -83,15 +83,14 @@ class Engine {
void set_on_update_full(std::function<void(const InfoFull&)>&&); void set_on_update_full(std::function<void(const InfoFull&)>&&);
void set_on_iter(std::function<void(const InfoIter&)>&&); void set_on_iter(std::function<void(const InfoIter&)>&&);
void set_on_bestmove(std::function<void(std::string_view, std::string_view)>&&); void set_on_bestmove(std::function<void(std::string_view, std::string_view)>&&);
void set_on_verify_networks(std::function<void(std::string_view)>&&); void set_on_verify_network(std::function<void(std::string_view)>&&);
// network related // network related
void verify_networks() const; void verify_network() const;
std::unique_ptr<Eval::NNUE::Networks> get_default_networks() const; std::unique_ptr<Eval::NNUE::Network> get_default_network() const;
void load_big_network(const std::string& file); void load_network(const std::string& file);
void load_small_network(const std::string& file); void save_network(std::pair<std::optional<std::string>, std::string> file);
void save_network(const std::pair<std::optional<std::string>, std::string> files[2]);
// utility functions // utility functions
@@ -119,13 +118,13 @@ class Engine {
Position pos; Position pos;
StateListPtr states; StateListPtr states;
OptionsMap options; OptionsMap options;
ThreadPool threads; ThreadPool threads;
TranspositionTable tt; TranspositionTable tt;
LazyNumaReplicatedSystemWide<Eval::NNUE::Networks> networks; LazyNumaReplicatedSystemWide<Eval::NNUE::Network> network;
Search::SearchManager::UpdateContext updateContext; Search::SearchManager::UpdateContext updateContext;
std::function<void(std::string_view)> onVerifyNetworks; std::function<void(std::string_view)> onVerifyNetwork;
std::map<NumaIndex, SharedHistories> sharedHists; std::map<NumaIndex, SharedHistories> sharedHists;
}; };
+13 -36
View File
@@ -26,7 +26,6 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <tuple>
#include "nnue/network.h" #include "nnue/network.h"
#include "nnue/nnue_misc.h" #include "nnue/nnue_misc.h"
@@ -46,11 +45,9 @@ int Eval::simple_eval(const Position& pos) {
- pos.non_pawn_material(~c); - 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 // 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. // 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, const Position& pos,
Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorStack& accumulators,
Eval::NNUE::AccumulatorCaches& caches, Eval::NNUE::AccumulatorCaches& caches,
@@ -58,20 +55,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
assert(!pos.checkers()); assert(!pos.checkers());
bool smallNet = use_smallnet(pos); auto [psqt, positional] = network.evaluate(pos, accumulators, caches);
auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small)
: networks.big.evaluate(pos, accumulators, caches.big);
Value nnue = (125 * psqt + 131 * positional) / 128; 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 // Blend optimism and eval with nnue complexity
int nnueComplexity = std::abs(psqt - positional); int nnueComplexity = std::abs(psqt - positional);
optimism += optimism * nnueComplexity / 476; 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 // a string (suitable for outputting to stdout) that contains the detailed
// descriptions and values of each evaluation term. Useful for debugging. // descriptions and values of each evaluation term. Useful for debugging.
// Trace scores are from white's point of view // 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()) if (pos.checkers())
return "Final evaluation: none (in check)"; return "Final evaluation: none (in check)";
auto accumulators = std::make_unique<Eval::NNUE::AccumulatorStack>(); auto accumulators = std::make_unique<Eval::NNUE::AccumulatorStack>();
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks); auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(network);
std::stringstream ss; std::stringstream ss;
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); 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); ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
auto [psqtSmall, positionalSmall] = networks.small.evaluate(pos, *accumulators, caches->small); auto [psqt, positional] = network.evaluate(pos, *accumulators, *caches);
Value vSmall = psqtSmall + positionalSmall; Value v = psqt + positional;
bool useBig = std::abs(vSmall) < 277; ss << "NNUE evaluation " << v << " (side to move, internal units)\n";
ss << "(Small net) NNUE evaluation " << vSmall << " (side to move, internal units)\n"; v = pos.side_to_move() == WHITE ? v : -v;
vSmall = pos.side_to_move() == WHITE ? vSmall : -vSmall; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n";
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";
ss << "SimpleEval " << simple_eval(pos) ss << "SimpleEval " << simple_eval(pos)
<< " (side to move, internal units)\n\n"; << " (side to move, internal units)\n\n";
Value v = evaluate(networks, pos, *accumulators, *caches, VALUE_ZERO); v = evaluate(network, pos, *accumulators, *caches, VALUE_ZERO);
v = pos.side_to_move() == WHITE ? v : -v; v = pos.side_to_move() == WHITE ? v : -v;
ss << "Final evaluation " << "(using " ss << "Final evaluation ";
<< (use_smallnet(pos) && !useBig ? "small net) " : "big net) ");
ss << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
ss << " [with scaled NNUE, ...]\n"; ss << " [with scaled NNUE, ...]\n";
+4 -5
View File
@@ -33,20 +33,19 @@ namespace Eval {
// for the build process (profile-build and fishtest) to work. Do not change the // 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 // name of the macro or the location where this macro is defined, as it is used
// in the Makefile/Fishtest. // in the Makefile/Fishtest.
#define EvalFileDefaultNameBig "nn-f68ec79f0fe3.nnue" #define EvalFileDefaultName "nn-f68ec79f0fe3.nnue"
#define EvalFileDefaultNameSmall "nn-47fc8b7fff06.nnue"
namespace NNUE { namespace NNUE {
struct Networks; class Network;
struct AccumulatorCaches; struct AccumulatorCaches;
class AccumulatorStack; 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); int simple_eval(const Position& pos);
bool use_smallnet(const Position& pos); bool use_smallnet(const Position& pos);
Value evaluate(const NNUE::Networks& networks, Value evaluate(const NNUE::Network& network,
const Position& pos, const Position& pos,
Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorStack& accumulators,
Eval::NNUE::AccumulatorCaches& caches, Eval::NNUE::AccumulatorCaches& caches,
+36 -104
View File
@@ -44,52 +44,21 @@
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file // const unsigned int gEmbeddedNNUESize; // the size of the embedded file
// Note that this does not work in Microsoft Visual Studio. // Note that this does not work in Microsoft Visual Studio.
#if !defined(UNIVERSAL_BINARY) && !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) #if !defined(UNIVERSAL_BINARY) && !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); INCBIN(EmbeddedNNUE, EvalFileDefaultName);
INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall);
#elif defined(UNIVERSAL_BINARY) #elif defined(UNIVERSAL_BINARY)
// When building for the universal binary, use C++26 #embed with weak symbols so that a // 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, // separate, non-LTO nnue_embed.o (with strong symbols) can override them during the LTO link,
// (INCBIN can't deduplicate.) // (INCBIN can't deduplicate.)
#define WEAK_SYM __attribute__((weak)) #define WEAK_SYM __attribute__((weak))
extern const unsigned char gEmbeddedNNUEBigData[] WEAK_SYM = { extern const unsigned char gEmbeddedNNUEData[] WEAK_SYM = {
#embed EvalFileDefaultNameBig #embed EvalFileDefaultName
}; };
extern const unsigned int gEmbeddedNNUEBigSize WEAK_SYM = sizeof(gEmbeddedNNUEBigData); extern const unsigned int gEmbeddedNNUESize WEAK_SYM = sizeof(gEmbeddedNNUEData);
extern const unsigned char gEmbeddedNNUESmallData[] WEAK_SYM = {
#embed EvalFileDefaultNameSmall
};
extern const unsigned int gEmbeddedNNUESmallSize WEAK_SYM = sizeof(gEmbeddedNNUESmallData);
#else #else
const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; const unsigned char gEmbeddedNNUEData[1] = {0x0};
const unsigned int gEmbeddedNNUEBigSize = 1; const unsigned int gEmbeddedNNUESize = 1;
const unsigned char gEmbeddedNNUESmallData[1] = {0x0};
const unsigned int gEmbeddedNNUESmallSize = 1;
#endif #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 { namespace Stockfish::Eval::NNUE {
@@ -116,8 +85,7 @@ bool write_parameters(std::ostream& stream, const T& reference) {
} // namespace Detail } // namespace Detail
template<typename Arch, typename Transformer> void Network::load(const std::string& rootDirectory, std::string evalfilePath) {
void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::string evalfilePath) {
#if defined(DEFAULT_NNUE_DIRECTORY) #if defined(DEFAULT_NNUE_DIRECTORY)
std::vector<std::string> dirs = {"<internal>", "", rootDirectory, std::vector<std::string> dirs = {"<internal>", "", rootDirectory,
stringify(DEFAULT_NNUE_DIRECTORY)}; stringify(DEFAULT_NNUE_DIRECTORY)};
@@ -146,8 +114,7 @@ void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::str
} }
template<typename Arch, typename Transformer> bool Network::save(const std::optional<std::string>& filename) const {
bool Network<Arch, Transformer>::save(const std::optional<std::string>& filename) const {
std::string actualFilename; std::string actualFilename;
std::string msg; std::string msg;
@@ -177,16 +144,13 @@ bool Network<Arch, Transformer>::save(const std::optional<std::string>& filename
} }
template<typename Arch, typename Transformer> NetworkOutput Network::evaluate(const Position& pos,
NetworkOutput AccumulatorStack& accumulatorStack,
Network<Arch, Transformer>::evaluate(const Position& pos, AccumulatorCaches& cache) const {
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const {
constexpr uint64_t alignment = CacheLineSize; constexpr uint64_t alignment = CacheLineSize;
alignas(alignment) alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
ASSERT_ALIGNED(transformedFeatures, alignment); ASSERT_ALIGNED(transformedFeatures, alignment);
@@ -198,9 +162,8 @@ Network<Arch, Transformer>::evaluate(const Position& pos
} }
template<typename Arch, typename Transformer> void Network::verify(std::string evalfilePath,
void Network<Arch, Transformer>::verify(std::string evalfilePath, const std::function<void(std::string_view)>& f) const {
const std::function<void(std::string_view)>& f) const {
if (evalfilePath.empty()) if (evalfilePath.empty())
evalfilePath = evalFile.defaultName; evalfilePath = evalFile.defaultName;
@@ -229,9 +192,9 @@ void Network<Arch, Transformer>::verify(std::string
if (f) 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)) 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].TransformedFeatureDimensions) + ", "
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
+ ", 1))"); + ", 1))");
@@ -239,16 +202,13 @@ void Network<Arch, Transformer>::verify(std::string
} }
template<typename Arch, typename Transformer> NnueEvalTrace Network::trace_evaluate(const Position& pos,
NnueEvalTrace AccumulatorStack& accumulatorStack,
Network<Arch, Transformer>::trace_evaluate(const Position& pos, AccumulatorCaches& cache) const {
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const {
constexpr uint64_t alignment = CacheLineSize; constexpr uint64_t alignment = CacheLineSize;
alignas(alignment) alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
ASSERT_ALIGNED(transformedFeatures, alignment); ASSERT_ALIGNED(transformedFeatures, alignment);
@@ -268,9 +228,7 @@ Network<Arch, Transformer>::trace_evaluate(const Position&
} }
template<typename Arch, typename Transformer> void Network::load_user_net(const std::string& dir, const std::string& evalfilePath) {
void Network<Arch, Transformer>::load_user_net(const std::string& dir,
const std::string& evalfilePath) {
std::ifstream stream(dir + evalfilePath, std::ios::binary); std::ifstream stream(dir + evalfilePath, std::ios::binary);
auto description = load(stream); auto description = load(stream);
@@ -282,8 +240,7 @@ void Network<Arch, Transformer>::load_user_net(const std::string& dir,
} }
template<typename Arch, typename Transformer> void Network::load_internal() {
void Network<Arch, Transformer>::load_internal() {
// C++ way to prepare a buffer for a memory stream // C++ way to prepare a buffer for a memory stream
class MemoryBuffer: public std::basic_streambuf<char> { class MemoryBuffer: public std::basic_streambuf<char> {
public: public:
@@ -293,10 +250,8 @@ void Network<Arch, Transformer>::load_internal() {
} }
}; };
const auto embedded = get_embedded(embeddedType); MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
size_t(gEmbeddedNNUESize));
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(embedded.data)),
size_t(embedded.size));
std::istream stream(&buffer); std::istream stream(&buffer);
auto description = load(stream); auto description = load(stream);
@@ -309,16 +264,12 @@ void Network<Arch, Transformer>::load_internal() {
} }
template<typename Arch, typename Transformer> void Network::initialize() { initialized = true; }
void Network<Arch, Transformer>::initialize() {
initialized = true;
}
template<typename Arch, typename Transformer> bool Network::save(std::ostream& stream,
bool Network<Arch, Transformer>::save(std::ostream& stream, const std::string& name,
const std::string& name, const std::string& netDescription) const {
const std::string& netDescription) const {
if (name.empty() || name == "None") if (name.empty() || name == "None")
return false; return false;
@@ -326,8 +277,7 @@ bool Network<Arch, Transformer>::save(std::ostream& stream,
} }
template<typename Arch, typename Transformer> std::optional<std::string> Network::load(std::istream& stream) {
std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream) {
initialize(); initialize();
std::string description; std::string description;
@@ -335,8 +285,7 @@ std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream
} }
template<typename Arch, typename Transformer> std::size_t Network::get_content_hash() const {
std::size_t Network<Arch, Transformer>::get_content_hash() const {
if (!initialized) if (!initialized)
return 0; return 0;
@@ -345,15 +294,11 @@ std::size_t Network<Arch, Transformer>::get_content_hash() const {
for (auto&& layerstack : network) for (auto&& layerstack : network)
hash_combine(h, layerstack); hash_combine(h, layerstack);
hash_combine(h, evalFile); hash_combine(h, evalFile);
hash_combine(h, static_cast<int>(embeddedType));
return h; return h;
} }
// Read network header // Read network header
template<typename Arch, typename Transformer> bool Network::read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) const {
bool Network<Arch, Transformer>::read_header(std::istream& stream,
std::uint32_t* hashValue,
std::string* desc) const {
std::uint32_t version, size; std::uint32_t version, size;
version = read_little_endian<std::uint32_t>(stream); version = read_little_endian<std::uint32_t>(stream);
@@ -368,10 +313,9 @@ bool Network<Arch, Transformer>::read_header(std::istream& stream,
// Write network header // Write network header
template<typename Arch, typename Transformer> bool Network::write_header(std::ostream& stream,
bool Network<Arch, Transformer>::write_header(std::ostream& stream, std::uint32_t hashValue,
std::uint32_t hashValue, const std::string& desc) const {
const std::string& desc) const {
write_little_endian<std::uint32_t>(stream, Version); write_little_endian<std::uint32_t>(stream, Version);
write_little_endian<std::uint32_t>(stream, hashValue); write_little_endian<std::uint32_t>(stream, hashValue);
write_little_endian<std::uint32_t>(stream, std::uint32_t(desc.size())); write_little_endian<std::uint32_t>(stream, std::uint32_t(desc.size()));
@@ -380,9 +324,7 @@ bool Network<Arch, Transformer>::write_header(std::ostream& stream,
} }
template<typename Arch, typename Transformer> bool Network::read_parameters(std::istream& stream, std::string& netDescription) {
bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
std::string& netDescription) {
std::uint32_t hashValue; std::uint32_t hashValue;
if (!read_header(stream, &hashValue, &netDescription)) if (!read_header(stream, &hashValue, &netDescription))
return false; return false;
@@ -399,9 +341,7 @@ bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
} }
template<typename Arch, typename Transformer> bool Network::write_parameters(std::ostream& stream, const std::string& netDescription) const {
bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
const std::string& netDescription) const {
if (!write_header(stream, Network::hash, netDescription)) if (!write_header(stream, Network::hash, netDescription))
return false; return false;
if (!Detail::write_parameters(stream, featureTransformer)) if (!Detail::write_parameters(stream, featureTransformer))
@@ -414,12 +354,4 @@ bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
return bool(stream); return bool(stream);
} }
// Explicit template instantiations
template class Network<NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,
FeatureTransformer<TransformedFeatureDimensionsBig>>;
template class Network<NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,
FeatureTransformer<TransformedFeatureDimensionsSmall>>;
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
+19 -61
View File
@@ -29,11 +29,8 @@
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
#include "../misc.h"
#include "../types.h" #include "../types.h"
#include "nnue_accumulator.h"
#include "nnue_architecture.h" #include "nnue_architecture.h"
#include "nnue_common.h"
#include "nnue_feature_transformer.h" #include "nnue_feature_transformer.h"
#include "nnue_misc.h" #include "nnue_misc.h"
@@ -43,24 +40,18 @@ class Position;
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
enum class EmbeddedNNUEType { class AccumulatorStack;
BIG, struct AccumulatorCaches;
SMALL,
};
using NetworkOutput = std::tuple<Value, Value>; using NetworkOutput = std::tuple<Value, Value>;
// The network must be a trivial type, i.e. the memory must be in-line. // 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 // This is required to allow sharing the network via shared memory, as
// there is no way to run destructors. // there is no way to run destructors.
template<typename Arch, typename Transformer>
class Network { class Network {
static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions;
public: public:
Network(EvalFile file, EmbeddedNNUEType type) : Network(EvalFile file) :
evalFile(file), evalFile(file) {}
embeddedType(type) {}
Network(const Network& other) = default; Network(const Network& other) = default;
Network(Network&& other) = default; Network(Network&& other) = default;
@@ -73,15 +64,15 @@ class Network {
std::size_t get_content_hash() const; std::size_t get_content_hash() const;
NetworkOutput evaluate(const Position& pos, NetworkOutput evaluate(const Position& pos,
AccumulatorStack& accumulatorStack, AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const; AccumulatorCaches& cache) const;
void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const; void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const;
NnueEvalTrace trace_evaluate(const Position& pos, NnueEvalTrace trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack, AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const; AccumulatorCaches& cache) const;
private: private:
void load_user_net(const std::string&, const std::string&); 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; bool write_parameters(std::ostream&, const std::string&) const;
// Input feature converter // Input feature converter
Transformer featureTransformer; FeatureTransformer featureTransformer;
// Evaluation function // Evaluation function
Arch network[LayerStacks]; NetworkArchitecture network[LayerStacks];
EvalFile evalFile; EvalFile evalFile;
EmbeddedNNUEType embeddedType;
bool initialized = false; bool initialized = false;
// Hash value of evaluation function structure // 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<IndexType Size> friend struct AccumulatorCaches;
friend struct AccumulatorCaches::Cache;
};
// Definitions of the network types
using SmallFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsSmall>;
using SmallNetworkArchitecture =
NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>;
using BigFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsBig>;
using BigNetworkArchitecture = NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>;
using NetworkBig = Network<BigNetworkArchitecture, BigFeatureTransformer>;
using NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;
struct Networks {
Networks(EvalFile bigFile, EvalFile smallFile) :
big(bigFile, EmbeddedNNUEType::BIG),
small(smallFile, EmbeddedNNUEType::SMALL) {}
NetworkBig big;
NetworkSmall small;
}; };
} // namespace Stockfish } // namespace Stockfish
template<typename ArchT, typename FeatureTransformerT> template<>
struct std::hash<Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>> { struct std::hash<Stockfish::Eval::NNUE::Network> {
std::size_t operator()( std::size_t operator()(const Stockfish::Eval::NNUE::Network& network) const noexcept {
const Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>& network) const noexcept {
return network.get_content_hash(); return network.get_content_hash();
} }
}; };
template<>
struct std::hash<Stockfish::Eval::NNUE::Networks> {
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 #endif
+95 -118
View File
@@ -38,26 +38,23 @@ using namespace SIMD;
namespace { namespace {
template<bool Forward, typename FeatureSet, IndexType TransformedFeatureDimensions> template<bool Forward, typename FeatureSet>
void update_accumulator_incremental( void update_accumulator_incremental(Color perspective,
Color perspective, const FeatureTransformer& featureTransformer,
const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer, const Square ksq,
const Square ksq, AccumulatorState<FeatureSet>& target_state,
AccumulatorState<FeatureSet>& target_state, const AccumulatorState<FeatureSet>& computed);
const AccumulatorState<FeatureSet>& computed);
template<IndexType Dimensions> void update_accumulator_refresh_cache(Color perspective,
void update_accumulator_refresh_cache(Color perspective, const FeatureTransformer& featureTransformer,
const FeatureTransformer<Dimensions>& featureTransformer, const Position& pos,
const Position& pos, AccumulatorState<PSQFeatureSet>& accumulatorState,
AccumulatorState<PSQFeatureSet>& accumulatorState, AccumulatorCaches& cache);
AccumulatorCaches::Cache<Dimensions>& cache);
template<IndexType Dimensions> void update_threats_accumulator_full(Color perspective,
void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer,
const FeatureTransformer<Dimensions>& featureTransformer, const Position& pos,
const Position& pos, AccumulatorState<ThreatFeatureSet>& accumulatorState);
AccumulatorState<ThreatFeatureSet>& accumulatorState);
} }
template<typename T> template<typename T>
@@ -120,33 +117,26 @@ void AccumulatorStack::pop() noexcept {
size--; size--;
} }
template<IndexType Dimensions> void AccumulatorStack::evaluate(const Position& pos,
void AccumulatorStack::evaluate(const Position& pos, const FeatureTransformer& featureTransformer,
const FeatureTransformer<Dimensions>& featureTransformer, // Silence spurious warning on GCC 10
AccumulatorCaches::Cache<Dimensions>& cache) noexcept { [[maybe_unused]] AccumulatorCaches& cache) noexcept {
constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig);
evaluate_side<PSQFeatureSet>(WHITE, pos, featureTransformer, cache); evaluate_side<PSQFeatureSet>(WHITE, pos, featureTransformer, cache);
evaluate_side<PSQFeatureSet>(BLACK, pos, featureTransformer, cache); evaluate_side<PSQFeatureSet>(BLACK, pos, featureTransformer, cache);
if (UseThreats) evaluate_side<ThreatFeatureSet>(WHITE, pos, featureTransformer, cache);
{ evaluate_side<ThreatFeatureSet>(BLACK, pos, featureTransformer, cache);
evaluate_side<ThreatFeatureSet>(WHITE, pos, featureTransformer, cache);
evaluate_side<ThreatFeatureSet>(BLACK, pos, featureTransformer, cache);
}
} }
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
void AccumulatorStack::evaluate_side(Color perspective, void AccumulatorStack::evaluate_side(Color perspective,
const Position& pos, const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer, const FeatureTransformer& featureTransformer,
AccumulatorCaches::Cache<Dimensions>& cache) noexcept { AccumulatorCaches& cache) noexcept {
const auto last_usable_accum = const auto last_usable_accum = find_last_usable_accumulator<FeatureSet>(perspective);
find_last_usable_accumulator<FeatureSet, Dimensions>(perspective);
if ((accumulators<FeatureSet>()[last_usable_accum].template acc<Dimensions>()) if (accumulators<FeatureSet>()[last_usable_accum].computed[perspective])
.computed[perspective])
forward_update_incremental<FeatureSet>(perspective, pos, featureTransformer, forward_update_incremental<FeatureSet>(perspective, pos, featureTransformer,
last_usable_accum); 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 // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator
// state just before a change that requires full refresh. // state just before a change that requires full refresh.
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) const noexcept { 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--) for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--)
{ {
if ((accumulators<FeatureSet>()[curr_idx].template acc<Dimensions>()).computed[perspective]) if (accumulators<FeatureSet>()[curr_idx].computed[perspective])
return curr_idx; return curr_idx;
if (FeatureSet::requires_refresh(accumulators<FeatureSet>()[curr_idx].diff, perspective)) if (FeatureSet::requires_refresh(accumulators<FeatureSet>()[curr_idx].diff, perspective))
@@ -181,15 +171,14 @@ std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) co
return 0; return 0;
} }
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
void AccumulatorStack::forward_update_incremental( void AccumulatorStack::forward_update_incremental(Color perspective,
Color perspective, const Position& pos,
const Position& pos, const FeatureTransformer& featureTransformer,
const FeatureTransformer<Dimensions>& featureTransformer, const std::size_t begin) noexcept {
const std::size_t begin) noexcept {
assert(begin < accumulators<FeatureSet>().size()); assert(begin < accumulators<FeatureSet>().size());
assert((accumulators<FeatureSet>()[begin].template acc<Dimensions>()).computed[perspective]); assert(accumulators<FeatureSet>()[begin].computed[perspective]);
const Square ksq = pos.square<KING>(perspective); const Square ksq = pos.square<KING>(perspective);
@@ -200,20 +189,19 @@ void AccumulatorStack::forward_update_incremental(
accumulators<FeatureSet>()[next - 1]); accumulators<FeatureSet>()[next - 1]);
} }
assert((latest<PSQFeatureSet>().acc<Dimensions>()).computed[perspective]); assert(latest<FeatureSet>().computed[perspective]);
} }
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
void AccumulatorStack::backward_update_incremental( void AccumulatorStack::backward_update_incremental(Color perspective,
Color perspective,
const Position& pos, const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer, const FeatureTransformer& featureTransformer,
const std::size_t end) noexcept { const std::size_t end) noexcept {
assert(end < accumulators<FeatureSet>().size()); assert(end < accumulators<FeatureSet>().size());
assert(end < size); assert(end < size);
assert((latest<FeatureSet>().template acc<Dimensions>()).computed[perspective]); assert(latest<FeatureSet>().computed[perspective]);
const Square ksq = pos.square<KING>(perspective); const Square ksq = pos.square<KING>(perspective);
@@ -222,20 +210,9 @@ void AccumulatorStack::backward_update_incremental(
mut_accumulators<FeatureSet>()[next], mut_accumulators<FeatureSet>()[next],
accumulators<FeatureSet>()[next + 1]); accumulators<FeatureSet>()[next + 1]);
assert((accumulators<FeatureSet>()[end].template acc<Dimensions>()).computed[perspective]); assert(accumulators<FeatureSet>()[end].computed[perspective]);
} }
// Explicit template instantiations
template void AccumulatorStack::evaluate<TransformedFeatureDimensionsBig>(
const Position& pos,
const FeatureTransformer<TransformedFeatureDimensionsBig>& featureTransformer,
AccumulatorCaches::Cache<TransformedFeatureDimensionsBig>& cache) noexcept;
template void AccumulatorStack::evaluate<TransformedFeatureDimensionsSmall>(
const Position& pos,
const FeatureTransformer<TransformedFeatureDimensionsSmall>& featureTransformer,
AccumulatorCaches::Cache<TransformedFeatureDimensionsSmall>& cache) noexcept;
namespace { namespace {
template<typename VectorWrapper, template<typename VectorWrapper,
@@ -255,17 +232,17 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const..
vecIn[i], reinterpret_cast<const typename VectorWrapper::type*>(rows)[i]...); vecIn[i], reinterpret_cast<const typename VectorWrapper::type*>(rows)[i]...);
} }
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
struct AccumulatorUpdateContext { struct AccumulatorUpdateContext {
Color perspective; Color perspective;
const FeatureTransformer<Dimensions>& featureTransformer; const FeatureTransformer& featureTransformer;
const AccumulatorState<FeatureSet>& from; const AccumulatorState<FeatureSet>& from;
AccumulatorState<FeatureSet>& to; AccumulatorState<FeatureSet>& to;
AccumulatorUpdateContext(Color persp, AccumulatorUpdateContext(Color persp,
const FeatureTransformer<Dimensions>& ft, const FeatureTransformer& ft,
const AccumulatorState<FeatureSet>& accF, const AccumulatorState<FeatureSet>& accF,
AccumulatorState<FeatureSet>& accT) noexcept : AccumulatorState<FeatureSet>& accT) noexcept :
perspective{persp}, perspective{persp},
featureTransformer{ft}, featureTransformer{ft},
from{accF}, from{accF},
@@ -275,6 +252,8 @@ struct AccumulatorUpdateContext {
typename... Ts, typename... Ts,
std::enable_if_t<is_all_same_v<IndexType, Ts...>, bool> = true> std::enable_if_t<is_all_same_v<IndexType, Ts...>, bool> = true>
void apply(const Ts... indices) { void apply(const Ts... indices) {
constexpr IndexType Dimensions = FeatureTransformer::OutputDimensions;
auto to_weight_vector = [&](const IndexType index) { auto to_weight_vector = [&](const IndexType index) {
return &featureTransformer.weights[index * Dimensions]; return &featureTransformer.weights[index * Dimensions];
}; };
@@ -283,27 +262,28 @@ struct AccumulatorUpdateContext {
return &featureTransformer.psqtWeights[index * PSQTBuckets]; return &featureTransformer.psqtWeights[index * PSQTBuckets];
}; };
fused_row_reduce<Vec16Wrapper, Dimensions, ops...>( fused_row_reduce<Vec16Wrapper, Dimensions, ops...>(from.accumulation[perspective].data(),
(from.template acc<Dimensions>()).accumulation[perspective].data(), to.accumulation[perspective].data(),
(to.template acc<Dimensions>()).accumulation[perspective].data(), to_weight_vector(indices)...);
to_weight_vector(indices)...);
fused_row_reduce<Vec32Wrapper, PSQTBuckets, ops...>( fused_row_reduce<Vec32Wrapper, PSQTBuckets, ops...>(
(from.template acc<Dimensions>()).psqtAccumulation[perspective].data(), from.psqtAccumulation[perspective].data(), to.psqtAccumulation[perspective].data(),
(to.template acc<Dimensions>()).psqtAccumulation[perspective].data(),
to_psqt_weight_vector(indices)...); to_psqt_weight_vector(indices)...);
} }
void apply(const typename FeatureSet::IndexList& added, void apply(const typename FeatureSet::IndexList& added,
const typename FeatureSet::IndexList& removed) { const typename FeatureSet::IndexList& removed) {
const auto& fromAcc = from.template acc<Dimensions>().accumulation[perspective]; constexpr IndexType Dimensions = FeatureTransformer::OutputDimensions;
auto& toAcc = to.template acc<Dimensions>().accumulation[perspective];
const auto& fromPsqtAcc = from.template acc<Dimensions>().psqtAccumulation[perspective]; const auto& fromAcc = from.accumulation[perspective];
auto& toPsqtAcc = to.template acc<Dimensions>().psqtAccumulation[perspective]; auto& toAcc = to.accumulation[perspective];
const auto& fromPsqtAcc = from.psqtAccumulation[perspective];
auto& toPsqtAcc = to.psqtAccumulation[perspective];
#ifdef VECTOR #ifdef VECTOR
using Tiling = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>; using Tiling = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;
vec_t acc[Tiling::NumRegs]; vec_t acc[Tiling::NumRegs];
psqt_vec_t psqt[Tiling::NumPsqtRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs];
@@ -426,25 +406,24 @@ struct AccumulatorUpdateContext {
} }
}; };
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
auto make_accumulator_update_context(Color perspective, auto make_accumulator_update_context(Color perspective,
const FeatureTransformer<Dimensions>& featureTransformer, const FeatureTransformer& featureTransformer,
const AccumulatorState<FeatureSet>& accumulatorFrom, const AccumulatorState<FeatureSet>& accumulatorFrom,
AccumulatorState<FeatureSet>& accumulatorTo) noexcept { AccumulatorState<FeatureSet>& accumulatorTo) noexcept {
return AccumulatorUpdateContext<FeatureSet, Dimensions>{perspective, featureTransformer, return AccumulatorUpdateContext<FeatureSet>{perspective, featureTransformer, accumulatorFrom,
accumulatorFrom, accumulatorTo}; accumulatorTo};
} }
template<bool Forward, typename FeatureSet, IndexType TransformedFeatureDimensions> template<bool Forward, typename FeatureSet>
void update_accumulator_incremental( void update_accumulator_incremental(Color perspective,
Color perspective, const FeatureTransformer& featureTransformer,
const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer, const Square ksq,
const Square ksq, AccumulatorState<FeatureSet>& target_state,
AccumulatorState<FeatureSet>& target_state, const AccumulatorState<FeatureSet>& computed) {
const AccumulatorState<FeatureSet>& computed) {
assert((computed.template acc<TransformedFeatureDimensions>()).computed[perspective]); assert(computed.computed[perspective]);
assert(!(target_state.template acc<TransformedFeatureDimensions>()).computed[perspective]); assert(!target_state.computed[perspective]);
// The size must be enough to contain the largest possible update. // The size must be enough to contain the largest possible update.
// That might depend on the feature set and generally relies on the // 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<FeatureSet, ThreatFeatureSet>) if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)
{ {
const auto* pfBase = &featureTransformer.threatWeights[0]; const auto* pfBase = &featureTransformer.threatWeights[0];
auto pfStride = static_cast<IndexType>(TransformedFeatureDimensions); IndexType pfStride = FeatureTransformer::OutputDimensions;
if constexpr (Forward) if constexpr (Forward)
FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added, FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added,
nullptr, false, pfBase, pfStride); nullptr, false, pfBase, pfStride);
@@ -519,7 +498,7 @@ void update_accumulator_incremental(
} }
} }
(target_state.template acc<TransformedFeatureDimensions>()).computed[perspective] = true; target_state.computed[perspective] = true;
} }
Bitboard get_changed_pieces(const std::array<Piece, SQUARE_NB>& oldPieces, Bitboard get_changed_pieces(const std::array<Piece, SQUARE_NB>& oldPieces,
@@ -559,12 +538,12 @@ Bitboard get_changed_pieces(const std::array<Piece, SQUARE_NB>& oldPieces,
#endif #endif
} }
template<IndexType Dimensions> void update_accumulator_refresh_cache(Color perspective,
void update_accumulator_refresh_cache(Color perspective, const FeatureTransformer& featureTransformer,
const FeatureTransformer<Dimensions>& featureTransformer, const Position& pos,
const Position& pos, AccumulatorState<PSQFeatureSet>& accumulator,
AccumulatorState<PSQFeatureSet>& accumulatorState, AccumulatorCaches& cache) {
AccumulatorCaches::Cache<Dimensions>& cache) { constexpr auto Dimensions = FeatureTransformer::OutputDimensions;
using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>; using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;
@@ -595,7 +574,6 @@ void update_accumulator_refresh_cache(Color pers
entry.pieceBB = pos.pieces(); entry.pieceBB = pos.pieces();
entry.pieces = pos.piece_array(); entry.pieces = pos.piece_array();
auto& accumulator = accumulatorState.acc<Dimensions>();
accumulator.computed[perspective] = true; accumulator.computed[perspective] = true;
#ifdef VECTOR #ifdef VECTOR
@@ -705,17 +683,16 @@ void update_accumulator_refresh_cache(Color pers
#endif #endif
} }
template<IndexType Dimensions> void update_threats_accumulator_full(Color perspective,
void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer,
const FeatureTransformer<Dimensions>& featureTransformer, const Position& pos,
const Position& pos, AccumulatorState<ThreatFeatureSet>& accumulator) {
AccumulatorState<ThreatFeatureSet>& accumulatorState) { constexpr IndexType Dimensions = FeatureTransformer::OutputDimensions;
using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>; using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;
ThreatFeatureSet::IndexList active; ThreatFeatureSet::IndexList active;
ThreatFeatureSet::append_active_indices(perspective, pos, active); ThreatFeatureSet::append_active_indices(perspective, pos, active);
auto& accumulator = accumulatorState.acc<Dimensions>();
accumulator.computed[perspective] = true; accumulator.computed[perspective] = true;
#ifdef VECTOR #ifdef VECTOR
+48 -92
View File
@@ -37,16 +37,13 @@ class Position;
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
template<IndexType Size>
struct alignas(CacheLineSize) Accumulator; struct alignas(CacheLineSize) Accumulator;
template<IndexType TransformedFeatureDimensions>
class FeatureTransformer; class FeatureTransformer;
// Class that holds the result of affine transformation of input features // Class that holds the result of affine transformation of input features
template<IndexType Size>
struct alignas(CacheLineSize) Accumulator { struct alignas(CacheLineSize) Accumulator {
std::array<std::array<std::int16_t, Size>, COLOR_NB> accumulation; std::array<std::array<std::int16_t, L1>, COLOR_NB> accumulation;
std::array<std::array<std::int32_t, PSQTBuckets>, COLOR_NB> psqtAccumulation; std::array<std::array<std::int32_t, PSQTBuckets>, COLOR_NB> psqtAccumulation;
std::array<bool, COLOR_NB> computed = {}; std::array<bool, COLOR_NB> computed = {};
}; };
@@ -59,92 +56,50 @@ struct alignas(CacheLineSize) Accumulator {
// This idea, was first described by Luecx (author of Koivisto) and // This idea, was first described by Luecx (author of Koivisto) and
// is commonly referred to as "Finny Tables". // is commonly referred to as "Finny Tables".
struct AccumulatorCaches { struct AccumulatorCaches {
template<typename Network>
template<typename Networks> AccumulatorCaches(const Network& network) {
AccumulatorCaches(const Networks& networks) { clear(network);
clear(networks);
} }
template<IndexType Size> struct alignas(CacheLineSize) Entry {
struct alignas(CacheLineSize) Cache { std::array<BiasType, L1> accumulation;
std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation;
std::array<Piece, SQUARE_NB> pieces;
Bitboard pieceBB;
struct alignas(CacheLineSize) Entry { // To initialize a refresh entry, we set all its bitboards empty,
std::array<BiasType, Size> accumulation; // so we put the biases in the accumulation, without any weights on top
std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation; void clear(const std::array<BiasType, L1>& biases) {
std::array<Piece, SQUARE_NB> pieces; accumulation = biases;
Bitboard pieceBB; std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation), 0,
sizeof(Entry) - offsetof(Entry, psqtAccumulation));
// 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<BiasType, Size>& biases) {
accumulation = biases;
std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation),
0, sizeof(Entry) - offsetof(Entry, psqtAccumulation));
}
};
template<typename Network>
void clear(const Network& network) {
for (auto& entries1D : entries)
for (auto& entry : entries1D)
entry.clear(network.featureTransformer.biases);
} }
std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }
std::array<std::array<Entry, COLOR_NB>, SQUARE_NB> entries;
}; };
template<typename Networks> template<typename Network>
void clear(const Networks& networks) { void clear(const Network& network) {
big.clear(networks.big); for (auto& entries1D : entries)
small.clear(networks.small); for (auto& entry : entries1D)
entry.clear(network.featureTransformer.biases);
} }
Cache<TransformedFeatureDimensionsBig> big; std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }
Cache<TransformedFeatureDimensionsSmall> small;
std::array<std::array<Entry, COLOR_NB>, SQUARE_NB> entries;
}; };
template<typename FeatureSet> template<typename FeatureSet>
struct AccumulatorState { struct AccumulatorState: public Accumulator {
Accumulator<TransformedFeatureDimensionsBig> accumulatorBig; typename FeatureSet::DiffType diff;
Accumulator<TransformedFeatureDimensionsSmall> accumulatorSmall;
typename FeatureSet::DiffType diff;
template<IndexType Size>
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<IndexType Size>
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;
}
void reset(const typename FeatureSet::DiffType& dp) noexcept { void reset(const typename FeatureSet::DiffType& dp) noexcept {
diff = dp; diff = dp;
accumulatorBig.computed.fill(false); computed.fill(false);
accumulatorSmall.computed.fill(false);
} }
typename FeatureSet::DiffType& reset() noexcept { typename FeatureSet::DiffType& reset() noexcept {
accumulatorBig.computed.fill(false); computed.fill(false);
accumulatorSmall.computed.fill(false);
return diff; return diff;
} }
}; };
@@ -160,10 +115,10 @@ class AccumulatorStack {
std::pair<DirtyPiece&, DirtyThreats&> push() noexcept; std::pair<DirtyPiece&, DirtyThreats&> push() noexcept;
void pop() noexcept; void pop() noexcept;
template<IndexType Dimensions> void evaluate(const Position& pos,
void evaluate(const Position& pos, const FeatureTransformer& featureTransformer,
const FeatureTransformer<Dimensions>& featureTransformer, // Silence spurious warning on GCC 10
AccumulatorCaches::Cache<Dimensions>& cache) noexcept; [[maybe_unused]] AccumulatorCaches& cache) noexcept;
private: private:
template<typename T> template<typename T>
@@ -175,26 +130,27 @@ class AccumulatorStack {
template<typename T> template<typename T>
[[nodiscard]] std::array<AccumulatorState<T>, MaxSize>& mut_accumulators() noexcept; [[nodiscard]] std::array<AccumulatorState<T>, MaxSize>& mut_accumulators() noexcept;
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
void evaluate_side(Color perspective, void evaluate_side(Color perspective,
const Position& pos, const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer, const FeatureTransformer& featureTransformer,
AccumulatorCaches::Cache<Dimensions>& cache) noexcept; // Silence spurious warning on GCC 10
[[maybe_unused]] AccumulatorCaches& cache) noexcept;
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
[[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept; [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept;
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
void forward_update_incremental(Color perspective, void forward_update_incremental(Color perspective,
const Position& pos, const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer, const FeatureTransformer& featureTransformer,
const std::size_t begin) noexcept; const std::size_t begin) noexcept;
template<typename FeatureSet, IndexType Dimensions> template<typename FeatureSet>
void backward_update_incremental(Color perspective, void backward_update_incremental(Color perspective,
const Position& pos, const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer, const FeatureTransformer& featureTransformer,
const std::size_t end) noexcept; const std::size_t end) noexcept;
std::array<AccumulatorState<PSQFeatureSet>, MaxSize> psq_accumulators; std::array<AccumulatorState<PSQFeatureSet>, MaxSize> psq_accumulators;
std::array<AccumulatorState<ThreatFeatureSet>, MaxSize> threat_accumulators; std::array<AccumulatorState<ThreatFeatureSet>, MaxSize> threat_accumulators;
+6 -12
View File
@@ -40,13 +40,9 @@ using ThreatFeatureSet = Features::FullThreats;
using PSQFeatureSet = Features::HalfKAv2_hm; using PSQFeatureSet = Features::HalfKAv2_hm;
// Number of input feature dimensions after conversion // Number of input feature dimensions after conversion
constexpr IndexType TransformedFeatureDimensionsBig = 1024; constexpr IndexType L1 = 1024;
constexpr int L2Big = 31; constexpr int L2 = 31;
constexpr int L3Big = 32; constexpr int L3 = 32;
constexpr IndexType TransformedFeatureDimensionsSmall = 128;
constexpr int L2Small = 15;
constexpr int L3Small = 32;
constexpr IndexType PSQTBuckets = 8; constexpr IndexType PSQTBuckets = 8;
constexpr IndexType LayerStacks = 8; constexpr IndexType LayerStacks = 8;
@@ -57,7 +53,6 @@ constexpr IndexType LayerStacks = 8;
static_assert(PSQTBuckets % 8 == 0, static_assert(PSQTBuckets % 8 == 0,
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); "Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
template<IndexType L1, int L2, int L3>
struct NetworkArchitecture { struct NetworkArchitecture {
static constexpr IndexType TransformedFeatureDimensions = L1; static constexpr IndexType TransformedFeatureDimensions = L1;
static constexpr int FC_0_OUTPUTS = L2; static constexpr int FC_0_OUTPUTS = L2;
@@ -147,10 +142,9 @@ struct NetworkArchitecture {
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
template<Stockfish::Eval::NNUE::IndexType L1, int L2, int L3> template<>
struct std::hash<Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>> { struct std::hash<Stockfish::Eval::NNUE::NetworkArchitecture> {
std::size_t std::size_t operator()(const Stockfish::Eval::NNUE::NetworkArchitecture& arch) const noexcept {
operator()(const Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>& arch) const noexcept {
return arch.get_content_hash(); return arch.get_content_hash();
} }
}; };
+65 -128
View File
@@ -78,22 +78,17 @@ void permute(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& o
} }
// Input feature converter // Input feature converter
template<IndexType TransformedFeatureDimensions>
class FeatureTransformer { class FeatureTransformer {
static constexpr bool UseThreats =
(TransformedFeatureDimensions == TransformedFeatureDimensionsBig);
// Number of output dimensions for one side // Number of output dimensions for one side
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; static constexpr IndexType HalfDimensions = L1;
public: public:
// Output type // Output type
using OutputType = TransformedFeatureType; using OutputType = TransformedFeatureType;
// Number of input/output dimensions // Number of input/output dimensions
static constexpr IndexType InputDimensions = PSQFeatureSet::Dimensions; static constexpr IndexType InputDimensions =
static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions; PSQFeatureSet::Dimensions + ThreatFeatureSet::Dimensions;
static constexpr IndexType TotalInputDimensions =
InputDimensions + (UseThreats ? ThreatInputDimensions : 0);
static constexpr IndexType OutputDimensions = HalfDimensions; static constexpr IndexType OutputDimensions = HalfDimensions;
// Size of forward propagation buffer // Size of forward propagation buffer
@@ -134,8 +129,7 @@ class FeatureTransformer {
// Hash value embedded in the evaluation file // Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() { static constexpr std::uint32_t get_hash_value() {
return (UseThreats ? combine_hash({ThreatFeatureSet::HashValue, PSQFeatureSet::HashValue}) return combine_hash({ThreatFeatureSet::HashValue, PSQFeatureSet::HashValue})
: PSQFeatureSet::HashValue)
^ (OutputDimensions * 2); ^ (OutputDimensions * 2);
} }
@@ -143,35 +137,23 @@ class FeatureTransformer {
permute<16>(biases, PackusEpi16Order); permute<16>(biases, PackusEpi16Order);
permute<16>(weights, PackusEpi16Order); permute<16>(weights, PackusEpi16Order);
if constexpr (UseThreats) permute<8>(threatWeights, PackusEpi16Order);
permute<8>(threatWeights, PackusEpi16Order);
} }
void unpermute_weights() { void unpermute_weights() {
permute<16>(biases, InversePackusEpi16Order); permute<16>(biases, InversePackusEpi16Order);
permute<16>(weights, InversePackusEpi16Order); permute<16>(weights, InversePackusEpi16Order);
permute<8>(threatWeights, InversePackusEpi16Order);
if constexpr (UseThreats)
permute<8>(threatWeights, InversePackusEpi16Order);
} }
// Read network parameters // Read network parameters
bool read_parameters(std::istream& stream) { bool read_parameters(std::istream& stream) {
read_leb_128(stream, biases); read_leb_128(stream, biases);
if constexpr (UseThreats) read_little_endian<ThreatWeightType>(stream, threatWeights.data(),
{ ThreatFeatureSet::Dimensions * HalfDimensions);
read_little_endian<ThreatWeightType>(stream, threatWeights.data(), read_leb_128(stream, weights);
ThreatInputDimensions * HalfDimensions); read_leb_128(stream, threatPsqtWeights, psqtWeights);
read_leb_128(stream, weights);
read_leb_128(stream, threatPsqtWeights, psqtWeights);
}
else
{
read_leb_128(stream, weights);
read_leb_128(stream, psqtWeights);
}
permute_weights(); permute_weights();
@@ -186,30 +168,22 @@ class FeatureTransformer {
write_leb_128<BiasType>(stream, copy->biases); write_leb_128<BiasType>(stream, copy->biases);
if constexpr (UseThreats) write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(),
{ ThreatFeatureSet::Dimensions * HalfDimensions);
write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(), write_leb_128<WeightType>(stream, copy->weights);
ThreatInputDimensions * HalfDimensions);
write_leb_128<WeightType>(stream, copy->weights);
auto combinedPsqtWeights = auto combinedPsqtWeights =
std::make_unique<std::array<PSQTWeightType, TotalInputDimensions * PSQTBuckets>>(); std::make_unique<std::array<PSQTWeightType, InputDimensions * PSQTBuckets>>();
std::copy(std::begin(copy->threatPsqtWeights), std::copy(std::begin(copy->threatPsqtWeights),
std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets, std::begin(copy->threatPsqtWeights) + ThreatFeatureSet::Dimensions * PSQTBuckets,
combinedPsqtWeights->begin()); combinedPsqtWeights->begin());
std::copy(std::begin(copy->psqtWeights), std::copy(std::begin(copy->psqtWeights),
std::begin(copy->psqtWeights) + InputDimensions * PSQTBuckets, std::begin(copy->psqtWeights) + PSQFeatureSet::Dimensions * PSQTBuckets,
combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets); combinedPsqtWeights->begin() + ThreatFeatureSet::Dimensions * PSQTBuckets);
write_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights); write_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights);
}
else
{
write_leb_128<WeightType>(stream, copy->weights);
write_leb_128<PSQTWeightType>(stream, copy->psqtWeights);
}
return !stream.fail(); return !stream.fail();
} }
@@ -221,11 +195,8 @@ class FeatureTransformer {
hash_combine(h, get_raw_data_hash(weights)); hash_combine(h, get_raw_data_hash(weights));
hash_combine(h, get_raw_data_hash(psqtWeights)); 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()); hash_combine(h, get_hash_value());
@@ -233,11 +204,11 @@ class FeatureTransformer {
} }
// Convert input features // Convert input features
std::int32_t transform(const Position& pos, std::int32_t transform(const Position& pos,
AccumulatorStack& accumulatorStack, AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<HalfDimensions>& cache, AccumulatorCaches& cache,
OutputType* output, OutputType* output,
int bucket) const { int bucket) const {
using namespace SIMD; using namespace SIMD;
accumulatorStack.evaluate(pos, *this, cache); accumulatorStack.evaluate(pos, *this, cache);
@@ -245,24 +216,17 @@ class FeatureTransformer {
const auto& threatAccumulatorState = accumulatorStack.latest<ThreatFeatureSet>(); const auto& threatAccumulatorState = accumulatorStack.latest<ThreatFeatureSet>();
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
const auto& psqtAccumulation = (accumulatorState.acc<HalfDimensions>()).psqtAccumulation; const auto& psqtAccumulation = accumulatorState.psqtAccumulation;
auto psqt = auto psqt =
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]); (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);
if constexpr (UseThreats) const auto& threatPsqtAccumulation = threatAccumulatorState.psqtAccumulation;
{ psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket]
const auto& threatPsqtAccumulation = - threatPsqtAccumulation[perspectives[1]][bucket])
(threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation; / 2;
psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket]
- threatPsqtAccumulation[perspectives[1]][bucket])
/ 2;
}
else
psqt /= 2;
const auto& accumulation = (accumulatorState.acc<HalfDimensions>()).accumulation; const auto& accumulation = accumulatorState.accumulation;
const auto& threatAccumulation = const auto& threatAccumulation = threatAccumulatorState.accumulation;
(threatAccumulatorState.acc<HalfDimensions>()).accumulation;
for (IndexType p = 0; p < 2; ++p) for (IndexType p = 0; p < 2; ++p)
{ {
@@ -341,48 +305,27 @@ class FeatureTransformer {
#else #else
6; 6;
#endif #endif
if constexpr (UseThreats)
const vec_t* tin0 =
reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0]));
const vec_t* tin1 = reinterpret_cast<const vec_t*>(
&(threatAccumulation[perspectives[p]][HalfDimensions / 2]));
for (IndexType j = 0; j < NumOutputChunks; ++j)
{ {
const vec_t* tin0 = const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]);
reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0])); const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]);
const vec_t* tin1 = reinterpret_cast<const vec_t*>( const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]);
&(threatAccumulation[perspectives[p]][HalfDimensions / 2])); const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]);
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 sum0a = const vec_t sum0a = vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift);
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 sum0b = const vec_t sum1a = vec_min_16(acc1a, One);
vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift); const vec_t sum1b = vec_min_16(acc1b, One);
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 pa = vec_mulhi_16(sum0a, sum1a);
const vec_t pb = vec_mulhi_16(sum0b, sum1b); const vec_t pb = vec_mulhi_16(sum0b, sum1b);
out[j] = vec_packus_16(pa, pb); 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);
}
} }
#else #else
@@ -393,12 +336,9 @@ class FeatureTransformer {
BiasType sum1 = BiasType sum1 =
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2]; accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
if constexpr (UseThreats) sum0 += threatAccumulation[static_cast<int>(perspectives[p])][j + 0];
{ sum1 +=
sum0 += threatAccumulation[static_cast<int>(perspectives[p])][j + 0]; threatAccumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
sum1 +=
threatAccumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
}
sum0 = std::clamp<BiasType>(sum0, 0, 255); sum0 = std::clamp<BiasType>(sum0, 0, 255);
sum1 = std::clamp<BiasType>(sum1, 0, 255); sum1 = std::clamp<BiasType>(sum1, 0, 255);
@@ -413,24 +353,21 @@ class FeatureTransformer {
} // end of function transform() } // end of function transform()
alignas(CacheLineSize) std::array<BiasType, HalfDimensions> biases; alignas(CacheLineSize) std::array<BiasType, HalfDimensions> biases;
alignas(CacheLineSize) std::array<WeightType, HalfDimensions * InputDimensions> weights; alignas(
CacheLineSize) std::array<WeightType, HalfDimensions * PSQFeatureSet::Dimensions> weights;
alignas(CacheLineSize) alignas(CacheLineSize)
std::array<ThreatWeightType, std::array<ThreatWeightType, HalfDimensions * ThreatFeatureSet::Dimensions> threatWeights;
UseThreats ? HalfDimensions * ThreatInputDimensions : 0> threatWeights;
alignas(CacheLineSize) std::array<PSQTWeightType, InputDimensions * PSQTBuckets> psqtWeights;
alignas(CacheLineSize) alignas(CacheLineSize)
std::array<PSQTWeightType, std::array<PSQTWeightType, PSQFeatureSet::Dimensions * PSQTBuckets> psqtWeights;
UseThreats ? ThreatInputDimensions * PSQTBuckets : 0> threatPsqtWeights; alignas(CacheLineSize)
std::array<PSQTWeightType, ThreatFeatureSet::Dimensions * PSQTBuckets> threatPsqtWeights;
}; };
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
template<>
template<Stockfish::Eval::NNUE::IndexType TransformedFeatureDimensions> struct std::hash<Stockfish::Eval::NNUE::FeatureTransformer> {
struct std::hash<Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>> { std::size_t operator()(const Stockfish::Eval::NNUE::FeatureTransformer& ft) const noexcept {
std::size_t
operator()(const Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>& ft)
const noexcept {
return ft.get_content_hash(); return ft.get_content_hash();
} }
}; };
+8 -37
View File
@@ -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, // Returns a string with the value of each piece on a board,
// and a table for (PSQT, Layers) values bucket by bucket. // and a table for (PSQT, Layers) values bucket by bucket.
std::string 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; std::stringstream ss;
auto accumulators = std::make_unique<AccumulatorStack>(); auto accumulators = std::make_unique<AccumulatorStack>();
accumulators->reset(); 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 << (pos.side_to_move() == WHITE ? "White to move)" : "Black to move)") << std::endl
<< "+------------+------------+------------+------------+\n" << "+------------+------------+------------+------------+\n"
<< "| Bucket | Material | Positional | Total |\n" << "| Bucket | Material | Positional | Total |\n"
@@ -75,45 +75,16 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat
{ {
ss << "| " << bucket << " " // ss << "| " << bucket << " " //
<< " | "; << " | ";
format_cp_aligned_dot(tSmall.psqt[bucket], ss, pos); format_cp_aligned_dot(t.psqt[bucket], ss, pos);
ss << " " // ss << " " //
<< " | "; << " | ";
format_cp_aligned_dot(tSmall.positional[bucket], ss, pos); format_cp_aligned_dot(t.positional[bucket], ss, pos);
ss << " " // 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 << " " // ss << " " //
<< " |"; << " |";
if (bucket == tSmall.correctBucket) if (bucket == t.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)
ss << " <-- this bucket is used"; ss << " <-- this bucket is used";
ss << '\n'; ss << '\n';
} }
+2 -2
View File
@@ -52,10 +52,10 @@ struct NnueEvalTrace {
std::size_t correctBucket; std::size_t correctBucket;
}; };
struct Networks; class Network;
struct AccumulatorCaches; 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::Eval::NNUE
} // namespace Stockfish } // namespace Stockfish
+5 -4
View File
@@ -1195,10 +1195,11 @@ void write_multiple_dirties(const Position& p,
#endif #endif
template<bool ComputeRay> template<bool ComputeRay>
void Position::update_piece_threats(Piece pc, void Position::update_piece_threats(Piece pc,
bool putPiece, bool putPiece,
Square s, Square s,
DirtyThreats* const dts, DirtyThreats* const dts,
// Silence spurious warning on GCC 10
[[maybe_unused]] Bitboard noRaysContaining) const { [[maybe_unused]] Bitboard noRaysContaining) const {
const Bitboard occupied = pieces(); const Bitboard occupied = pieces();
const Bitboard rookQueens = pieces(ROOK, QUEEN); const Bitboard rookQueens = pieces(ROOK, QUEEN);
+5 -5
View File
@@ -171,15 +171,15 @@ Search::Worker::Worker(SharedState& sharedState,
options(sharedState.options), options(sharedState.options),
threads(sharedState.threads), threads(sharedState.threads),
tt(sharedState.tt), tt(sharedState.tt),
networks(sharedState.networks), network(sharedState.network),
refreshTable(networks[token]) { refreshTable(network[token]) {
clear(); clear();
} }
void Search::Worker::ensure_network_replicated() { void Search::Worker::ensure_network_replicated() {
// Access once to force lazy initialization. // Access once to force lazy initialization.
// We do this because we want to avoid initialization during search. // We do this because we want to avoid initialization during search.
(void) (networks[numaAccessToken]); (void) (network[numaAccessToken]);
} }
void Search::Worker::start_searching() { void Search::Worker::start_searching() {
@@ -639,7 +639,7 @@ void Search::Worker::clear() {
for (size_t i = 1; i < reductions.size(); ++i) for (size_t i = 1; i < reductions.size(); ++i)
reductions[i] = int(2763 / 128.0 * std::log(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(); } TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); }
Value Search::Worker::evaluate(const Position& pos) { 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()]); optimism[pos.side_to_move()]);
} }
+15 -15
View File
@@ -175,22 +175,22 @@ struct LimitsType {
// The UCI stores the uci options, thread pool, and transposition table. // The UCI stores the uci options, thread pool, and transposition table.
// This struct is used to easily forward data to the Search::Worker class. // This struct is used to easily forward data to the Search::Worker class.
struct SharedState { struct SharedState {
SharedState(const OptionsMap& optionsMap, SharedState(const OptionsMap& optionsMap,
ThreadPool& threadPool, ThreadPool& threadPool,
TranspositionTable& transpositionTable, TranspositionTable& transpositionTable,
std::map<NumaIndex, SharedHistories>& sharedHists, std::map<NumaIndex, SharedHistories>& sharedHists,
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& nets) : const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& net) :
options(optionsMap), options(optionsMap),
threads(threadPool), threads(threadPool),
tt(transpositionTable), tt(transpositionTable),
sharedHistories(sharedHists), sharedHistories(sharedHists),
networks(nets) {} network(net) {}
const OptionsMap& options; const OptionsMap& options;
ThreadPool& threads; ThreadPool& threads;
TranspositionTable& tt; TranspositionTable& tt;
std::map<NumaIndex, SharedHistories>& sharedHistories; std::map<NumaIndex, SharedHistories>& sharedHistories;
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks; const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& network;
}; };
class Worker; class Worker;
@@ -396,10 +396,10 @@ class Worker {
Tablebases::Config tbConfig; Tablebases::Config tbConfig;
const OptionsMap& options; const OptionsMap& options;
ThreadPool& threads; ThreadPool& threads;
TranspositionTable& tt; TranspositionTable& tt;
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks; const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& network;
// Used by NNUE // Used by NNUE
Eval::NNUE::AccumulatorStack accumulatorStack; Eval::NNUE::AccumulatorStack accumulatorStack;
+6 -9
View File
@@ -82,7 +82,7 @@ void UCIEngine::init_search_update_listeners() {
engine.set_on_update_full( engine.set_on_update_full(
[this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); }); [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_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() { void UCIEngine::loop() {
@@ -152,15 +152,12 @@ void UCIEngine::loop() {
sync_cout << compiler_info() << sync_endl; sync_cout << compiler_info() << sync_endl;
else if (token == "export_net") else if (token == "export_net")
{ {
std::pair<std::optional<std::string>, std::string> files[2]; std::pair<std::optional<std::string>, std::string> file;
if (is >> files[0].second) if (is >> file.second)
files[0].first = files[0].second; file.first = file.second;
if (is >> files[1].second) engine.save_network(file);
files[1].first = files[1].second;
engine.save_network(files);
} }
else if (token == "--help" || token == "help" || token == "--license" || token == "license") else if (token == "--help" || token == "help" || token == "--license" || token == "license")
sync_cout sync_cout
@@ -309,7 +306,7 @@ void UCIEngine::benchmark(std::istream& args) {
engine.set_on_iter([](const auto&) {}); engine.set_on_iter([](const auto&) {});
engine.set_on_update_no_moves([](const auto&) {}); engine.set_on_update_no_moves([](const auto&) {});
engine.set_on_bestmove([](const auto&, 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); Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args);
+3 -11
View File
@@ -2,15 +2,7 @@
#include "../evaluate.h" #include "../evaluate.h"
extern const unsigned char gEmbeddedNNUEBigData[] = { extern const unsigned char gEmbeddedNNUEData[] = {
#embed EvalFileDefaultNameBig #embed EvalFileDefaultName
}; };
extern const unsigned int gEmbeddedNNUEBigSize = sizeof(gEmbeddedNNUEBigData); extern const unsigned int gEmbeddedNNUESize = sizeof(gEmbeddedNNUEData);
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;