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
+36 -104
View File
@@ -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<typename Arch, typename Transformer>
void Network<Arch, Transformer>::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<std::string> dirs = {"<internal>", "", rootDirectory,
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<Arch, Transformer>::save(const std::optional<std::string>& filename) const {
bool Network::save(const std::optional<std::string>& filename) const {
std::string actualFilename;
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<Arch, Transformer>::evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const {
NetworkOutput Network::evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const {
constexpr uint64_t alignment = CacheLineSize;
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
ASSERT_ALIGNED(transformedFeatures, alignment);
@@ -198,9 +162,8 @@ Network<Arch, Transformer>::evaluate(const Position& pos
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::verify(std::string evalfilePath,
const std::function<void(std::string_view)>& f) const {
void Network::verify(std::string evalfilePath,
const std::function<void(std::string_view)>& f) const {
if (evalfilePath.empty())
evalfilePath = evalFile.defaultName;
@@ -229,9 +192,9 @@ void Network<Arch, Transformer>::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<Arch, Transformer>::verify(std::string
}
template<typename Arch, typename Transformer>
NnueEvalTrace
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const {
NnueEvalTrace Network::trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const {
constexpr uint64_t alignment = CacheLineSize;
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
ASSERT_ALIGNED(transformedFeatures, alignment);
@@ -268,9 +228,7 @@ Network<Arch, Transformer>::trace_evaluate(const Position&
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::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<Arch, Transformer>::load_user_net(const std::string& dir,
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::load_internal() {
void Network::load_internal() {
// C++ way to prepare a buffer for a memory stream
class MemoryBuffer: public std::basic_streambuf<char> {
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*>(embedded.data)),
size_t(embedded.size));
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
size_t(gEmbeddedNNUESize));
std::istream stream(&buffer);
auto description = load(stream);
@@ -309,16 +264,12 @@ void Network<Arch, Transformer>::load_internal() {
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::initialize() {
initialized = true;
}
void Network::initialize() { initialized = true; }
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::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<Arch, Transformer>::save(std::ostream& stream,
}
template<typename Arch, typename Transformer>
std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream) {
std::optional<std::string> Network::load(std::istream& stream) {
initialize();
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<Arch, Transformer>::get_content_hash() const {
std::size_t Network::get_content_hash() const {
if (!initialized)
return 0;
@@ -345,15 +294,11 @@ std::size_t Network<Arch, Transformer>::get_content_hash() const {
for (auto&& layerstack : network)
hash_combine(h, layerstack);
hash_combine(h, evalFile);
hash_combine(h, static_cast<int>(embeddedType));
return h;
}
// Read network header
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::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<std::uint32_t>(stream);
@@ -368,10 +313,9 @@ bool Network<Arch, Transformer>::read_header(std::istream& stream,
// Write network header
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::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<std::uint32_t>(stream, Version);
write_little_endian<std::uint32_t>(stream, hashValue);
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<Arch, Transformer>::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<Arch, Transformer>::read_parameters(std::istream& stream,
}
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::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<Arch, Transformer>::write_parameters(std::ostream& 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