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
+13 -36
View File
@@ -26,7 +26,6 @@
#include <iostream>
#include <memory>
#include <sstream>
#include <tuple>
#include "nnue/network.h"
#include "nnue/nnue_misc.h"
@@ -46,11 +45,9 @@ int Eval::simple_eval(const Position& pos) {
- pos.non_pawn_material(~c);
}
bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; }
// Evaluate is the evaluator for the outer world. It returns a static evaluation
// of the position from the point of view of the side to move.
Value Eval::evaluate(const Eval::NNUE::Networks& networks,
Value Eval::evaluate(const Eval::NNUE::Network& network,
const Position& pos,
Eval::NNUE::AccumulatorStack& accumulators,
Eval::NNUE::AccumulatorCaches& caches,
@@ -58,20 +55,10 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
assert(!pos.checkers());
bool smallNet = use_smallnet(pos);
auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small)
: networks.big.evaluate(pos, accumulators, caches.big);
auto [psqt, positional] = network.evaluate(pos, accumulators, caches);
Value nnue = (125 * psqt + 131 * positional) / 128;
// Re-evaluate the position when higher eval accuracy is worth the time spent
if (smallNet && (std::abs(nnue) < 277))
{
std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big);
nnue = (125 * psqt + 131 * positional) / 128;
smallNet = false;
}
// Blend optimism and eval with nnue complexity
int nnueComplexity = std::abs(psqt - positional);
optimism += optimism * nnueComplexity / 476;
@@ -93,43 +80,33 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
// a string (suitable for outputting to stdout) that contains the detailed
// descriptions and values of each evaluation term. Useful for debugging.
// Trace scores are from white's point of view
std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) {
std::string Eval::trace(Position& pos, const Eval::NNUE::Network& network) {
if (pos.checkers())
return "Final evaluation: none (in check)";
auto accumulators = std::make_unique<Eval::NNUE::AccumulatorStack>();
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(network);
std::stringstream ss;
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n';
ss << '\n' << NNUE::trace(pos, network, *caches) << '\n';
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
auto [psqtSmall, positionalSmall] = networks.small.evaluate(pos, *accumulators, caches->small);
Value vSmall = psqtSmall + positionalSmall;
bool useBig = std::abs(vSmall) < 277;
ss << "(Small net) NNUE evaluation " << vSmall << " (side to move, internal units)\n";
vSmall = pos.side_to_move() == WHITE ? vSmall : -vSmall;
ss << "(Small net) NNUE evaluation " << 0.01 * UCIEngine::to_cp(vSmall, pos)
<< " (white side)\n";
auto [psqtBig, positionalBig] = networks.big.evaluate(pos, *accumulators, caches->big);
Value vBig = psqtBig + positionalBig;
ss << "(Big net) NNUE evaluation " << vBig << " (side to move, internal units)\n";
vBig = pos.side_to_move() == WHITE ? vBig : -vBig;
ss << "(Big net) NNUE evaluation " << 0.01 * UCIEngine::to_cp(vBig, pos)
<< " (white side)\n";
auto [psqt, positional] = network.evaluate(pos, *accumulators, *caches);
Value v = psqt + positional;
ss << "NNUE evaluation " << v << " (side to move, internal units)\n";
v = pos.side_to_move() == WHITE ? v : -v;
ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n";
ss << "SimpleEval " << simple_eval(pos)
<< " (side to move, internal units)\n\n";
Value v = evaluate(networks, pos, *accumulators, *caches, VALUE_ZERO);
v = pos.side_to_move() == WHITE ? v : -v;
v = evaluate(network, pos, *accumulators, *caches, VALUE_ZERO);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "Final evaluation " << "(using "
<< (use_smallnet(pos) && !useBig ? "small net) " : "big net) ");
ss << "Final evaluation ";
ss << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
ss << " [with scaled NNUE, ...]\n";