Skip to content

Commit

Permalink
Dual net NNUE w/ L1=256 small net by @linrock
Browse files Browse the repository at this point in the history
bench: 1380121
  • Loading branch information
mstembera committed Dec 10, 2023
1 parent 36db936 commit 28a4655
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 207 deletions.
57 changes: 52 additions & 5 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,7 @@ help:
@echo "profile-build > standard build with profile-guided optimization"
@echo "build > skip profile-guided optimization"
@echo "net > Download the default nnue net"
@echo "net2 > Download the smaller nnue net"
@echo "strip > Strip executable"
@echo "install > Install executable"
@echo "clean > Clean up"
Expand Down Expand Up @@ -857,13 +858,13 @@ endif
clang-profile-use clang-profile-make FORCE \
format analyze

analyze: net config-sanity objclean
analyze: net net2 config-sanity objclean
$(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)

build: net config-sanity
build: net net2 config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all

profile-build: net config-sanity objclean profileclean
profile-build: net net2 config-sanity objclean profileclean
@echo ""
@echo "Step 1/4. Building instrumented executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
Expand Down Expand Up @@ -907,7 +908,7 @@ profileclean:

# set up shell variables for the net stuff
netvariables:
$(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
$(eval nnuenet := $(shell grep EvalFileDefaultNameBig evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
$(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet))
$(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet))
$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi))
Expand Down Expand Up @@ -951,6 +952,52 @@ net: netvariables
fi; \
fi; \

netvariables2:
$(eval nnuenet := $(shell grep EvalFileDefaultNameSmall evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
$(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet))
$(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet))
$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi))
$(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi))

# evaluation network (nnue)
net2: netvariables2
@echo "Default net: $(nnuenet)"
@if [ "x$(curl_or_wget)" = "x" ]; then \
echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \
fi
@if [ "x$(shasum_command)" = "x" ]; then \
echo "shasum / sha256sum not found, skipping net validation"; \
elif test -f "$(nnuenet)"; then \
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
echo "Removing invalid network"; rm -f $(nnuenet); \
fi; \
fi;
@for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \
if test -f "$(nnuenet)"; then \
echo "$(nnuenet) available : OK"; break; \
else \
if [ "x$(curl_or_wget)" != "x" ]; then \
echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\
else \
echo "No net found and download not possible"; exit 1;\
fi; \
fi; \
if [ "x$(shasum_command)" != "x" ]; then \
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
echo "Removing failed download"; rm -f $(nnuenet); \
fi; \
fi; \
done
@if ! test -f "$(nnuenet)"; then \
echo "Failed to download $(nnuenet)."; \
fi;
@if [ "x$(shasum_command)" != "x" ]; then \
if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
echo "Network validated"; break; \
fi; \
fi; \


format:
$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file

Expand Down Expand Up @@ -1073,6 +1120,6 @@ icx-profile-use:
.depend: $(SRCS)
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null

ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity))
ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net net2 objclean profileclean config-sanity))
-include .depend
endif
167 changes: 91 additions & 76 deletions src/evaluate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,25 @@
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
// Note that this does not work in Microsoft Visual Studio.
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig);
INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall);
#else
const unsigned char gEmbeddedNNUEData[1] = {0x0};
const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
const unsigned int gEmbeddedNNUESize = 1;
const unsigned char gEmbeddedNNUEBigData[1] = {0x0};
const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1];
const unsigned int gEmbeddedNNUEBigSize = 1;
const unsigned char gEmbeddedNNUESmallData[1] = {0x0};
const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1];
const unsigned int gEmbeddedNNUESmallSize = 1;
#endif


namespace Stockfish {

namespace Eval {

std::string currentEvalFileName = "None";
std::string currentEvalFileName[2] = {"None", "None"};
const std::string EvFiles[2] = {"EvalFileBig", "EvalFileSmall"};
const std::string EvFileNames[2] = {EvalFileDefaultNameBig, EvalFileDefaultNameSmall};

// Tries to load a NNUE network at startup time, or when the engine
// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
Expand All @@ -66,9 +72,11 @@ std::string currentEvalFileName = "None";
// variable to have the engine search in a special directory in their distro.
void NNUE::init() {

std::string eval_file = std::string(Options["EvalFile"]);
if (eval_file.empty())
eval_file = EvalFileDefaultName;
for (bool small : {false, true})
{
std::string eval_file = std::string(Options[EvFiles[small]]);
if (eval_file.empty())
eval_file = EvFileNames[small];

#if defined(DEFAULT_NNUE_DIRECTORY)
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory,
Expand All @@ -77,82 +85,79 @@ void NNUE::init() {
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory};
#endif

for (const std::string& directory : dirs)
if (currentEvalFileName != eval_file)
for (const std::string& directory : dirs)
{
if (directory != "<internal>")
if (currentEvalFileName[small] != eval_file)
{
std::ifstream stream(directory + eval_file, std::ios::binary);
if (NNUE::load_eval(eval_file, stream))
currentEvalFileName = eval_file;
}

if (directory == "<internal>" && eval_file == EvalFileDefaultName)
{
// C++ way to prepare a buffer for a memory stream
class MemoryBuffer: public std::basic_streambuf<char> {
public:
MemoryBuffer(char* p, size_t n) {
setg(p, p, p + n);
setp(p, p + n);
}
};

MemoryBuffer buffer(
const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
size_t(gEmbeddedNNUESize));
(void) gEmbeddedNNUEEnd; // Silence warning on unused variable

std::istream stream(&buffer);
if (NNUE::load_eval(eval_file, stream))
currentEvalFileName = eval_file;
if (directory != "<internal>")
{
std::ifstream stream(directory + eval_file, std::ios::binary);
if (NNUE::load_eval(eval_file, stream, small))
currentEvalFileName[small] = eval_file;
}

if (directory == "<internal>" && eval_file == EvFileNames[small])
{
// C++ way to prepare a buffer for a memory stream
class MemoryBuffer: public std::basic_streambuf<char> {
public:
MemoryBuffer(char* p, size_t n) {
setg(p, p, p + n);
setp(p, p + n);
}
};

MemoryBuffer buffer(
const_cast<char*>(reinterpret_cast<const char*>(
small ? gEmbeddedNNUESmallData : gEmbeddedNNUEBigData)),
size_t(small ? gEmbeddedNNUESmallSize : gEmbeddedNNUEBigSize));
(void) gEmbeddedNNUEBigEnd; // Silence warning on unused variable
(void) gEmbeddedNNUESmallEnd;

std::istream stream(&buffer);
if (NNUE::load_eval(eval_file, stream, small))
currentEvalFileName[small] = eval_file;
}
}
}
}
}

// Verifies that the last net used was loaded successfully
void NNUE::verify() {

std::string eval_file = std::string(Options["EvalFile"]);
if (eval_file.empty())
eval_file = EvalFileDefaultName;

if (currentEvalFileName != eval_file)
for (bool small : {false, true})
{
std::string eval_file = std::string(Options[EvFiles[small]]);
if (eval_file.empty())
eval_file = EvFileNames[small];

std::string msg1 =
"Network evaluation parameters compatible with the engine must be available.";
std::string msg2 = "The network file " + eval_file + " was not loaded successfully.";
std::string msg3 = "The UCI option EvalFile might need to specify the full path, "
"including the directory name, to the network file.";
std::string msg4 = "The default net can be downloaded from: "
"https://tests.stockfishchess.org/api/nn/"
+ std::string(EvalFileDefaultName);
std::string msg5 = "The engine will be terminated now.";

sync_cout << "info string ERROR: " << msg1 << sync_endl;
sync_cout << "info string ERROR: " << msg2 << sync_endl;
sync_cout << "info string ERROR: " << msg3 << sync_endl;
sync_cout << "info string ERROR: " << msg4 << sync_endl;
sync_cout << "info string ERROR: " << msg5 << sync_endl;

exit(EXIT_FAILURE);
}
if (currentEvalFileName[small] != eval_file)
{
std::string msg1 =
"Network evaluation parameters compatible with the engine must be available.";
std::string msg2 = "The network file " + eval_file + " was not loaded successfully.";
std::string msg3 = "The UCI option EvalFile might need to specify the full path, "
"including the directory name, to the network file.";
std::string msg4 = "The default net can be downloaded from: "
"https://tests.stockfishchess.org/api/nn/"
+ std::string(EvFileNames[small]);
std::string msg5 = "The engine will be terminated now.";

sync_cout << "info string ERROR: " << msg1 << sync_endl;
sync_cout << "info string ERROR: " << msg2 << sync_endl;
sync_cout << "info string ERROR: " << msg3 << sync_endl;
sync_cout << "info string ERROR: " << msg4 << sync_endl;
sync_cout << "info string ERROR: " << msg5 << sync_endl;

exit(EXIT_FAILURE);
}

sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl;
}
sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl;
}
}


// Returns a static, purely materialistic evaluation of the position from
// the point of view of the given color. It can be divided by PawnValue to get
// an approximation of the material advantage on the board in terms of pawns.
Value Eval::simple_eval(const Position& pos, Color c) {
return PawnValue * (pos.count<PAWN>(c) - pos.count<PAWN>(~c))
+ (pos.non_pawn_material(c) - pos.non_pawn_material(~c));
}


// Evaluate is the evaluator for the outer world. It returns a static evaluation
// of the position from the point of view of the side to move.
Value Eval::evaluate(const Position& pos) {
Expand All @@ -162,18 +167,28 @@ Value Eval::evaluate(const Position& pos) {
Value v;
Color stm = pos.side_to_move();
int shuffling = pos.rule50_count();
int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3);
int simpleEval = pos.simple_eval() + (int(pos.key() & 7) - 3);

int lazyThreshold = RookValue + KnightValue + 16 * shuffling * shuffling
+ abs(pos.this_thread()->bestValue)
+ abs(pos.this_thread()->rootSimpleEval);

bool lazy = abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling
+ abs(pos.this_thread()->bestValue)
+ abs(pos.this_thread()->rootSimpleEval);
bool lazy = abs(simpleEval) > lazyThreshold * 105 / 100;

if (lazy)
v = Value(simpleEval);
else
{
int nnueComplexity;
Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
int accBias = pos.state()->accumulatorBig.computed[0]
+ pos.state()->accumulatorBig.computed[1]
- pos.state()->accumulatorSmall.computed[0]
- pos.state()->accumulatorSmall.computed[1];

int nnueComplexity;
bool smallNet = abs(simpleEval) > lazyThreshold * (90 + accBias) / 100;

Value nnue = smallNet ? NNUE::evaluate<true>(pos, true, &nnueComplexity)
: NNUE::evaluate<false>(pos, true, &nnueComplexity);

Value optimism = pos.this_thread()->optimism[stm];

Expand Down Expand Up @@ -216,7 +231,7 @@ std::string Eval::trace(Position& pos) {
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);

Value v;
v = NNUE::evaluate(pos, false);
v = NNUE::evaluate<false>(pos, false);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n";

Expand Down
6 changes: 3 additions & 3 deletions src/evaluate.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ namespace Eval {

std::string trace(Position& pos);

Value simple_eval(const Position& pos, Color c);
Value evaluate(const Position& pos);

extern std::string currentEvalFileName;
extern std::string currentEvalFileName[2];

// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-0000000000a0.nnue"
#define EvalFileDefaultNameBig "nn-0000000000a0.nnue"
#define EvalFileDefaultNameSmall "nn-ecb35f70ff2a.nnue"

namespace NNUE {

Expand Down
Loading

0 comments on commit 28a4655

Please sign in to comment.