diff --git a/src/nnue.cpp b/src/nnue.cpp index 7616f1c..13ab35f 100644 --- a/src/nnue.cpp +++ b/src/nnue.cpp @@ -7,7 +7,9 @@ #include #include +#ifndef NO_EMBED_NET INCBIN(EmbeddedNNUE, EvalFile); +#endif #define AsVecI(x) *(VecI*)(&x) #define AsVecF(x) *(VecF*)(&x) @@ -159,13 +161,16 @@ namespace NNUE { acc.reset(BLACK); } - void loadWeights() { - - Weights = (Net*) Util::allocAlign(sizeof(Net)); + bool netLoaded = false; + + static void prepareWeights(const void* data) { + + if (!Weights) + Weights = (Net*) Util::allocAlign(sizeof(Net)); // dpbusd preprocessing: Net* rawContent = new Net(); - memcpy(rawContent, gEmbeddedNNUEData, sizeof(Net)); + memcpy(rawContent, data, sizeof(Net)); memcpy(Weights, rawContent, sizeof(Net)); for (int bucket = 0; bucket < OutputBuckets; bucket++) for (int i = 0; i < L1; i += 4) @@ -213,6 +218,40 @@ namespace NNUE { } } + void loadWeights() { +#ifndef NO_EMBED_NET + prepareWeights(gEmbeddedNNUEData); + netLoaded = true; +#endif + } + + // Rama Chess: load the net from a file (UCI option EvalFile); it must be exactly sizeof(Net) bytes. + bool loadWeightsFromFile(const std::string& path) { + std::ifstream f(path, std::ios::binary | std::ios::ate); + if (!f) { + std::cout << "info string ERROR: EvalFile " << path << ": cannot open file" << std::endl; + return false; + } + std::streamsize size = f.tellg(); + if (size != std::streamsize(sizeof(Net))) { + std::cout << "info string ERROR: EvalFile " << path << ": wrong size " << size + << " (expected " << sizeof(Net) << ")" << std::endl; + return false; + } + f.seekg(0); + char* buf = new char[sizeof(Net)]; + if (!f.read(buf, sizeof(Net))) { + delete[] buf; + std::cout << "info string ERROR: EvalFile " << path << ": read error" << std::endl; + return false; + } + prepareWeights(buf); + delete[] buf; + netLoaded = true; + std::cout << "info string EvalFile loaded: " << path << std::endl; + return true; + } + Score evaluate(Position& pos, Accumulator& accumulator) { constexpr int divisor = (32 + OutputBuckets - 1) / OutputBuckets; diff --git a/src/nnue.h b/src/nnue.h index 4f7e5d9..2a7bf51 100644 --- a/src/nnue.h +++ b/src/nnue.h @@ -3,6 +3,8 @@ #include "simd.h" #include "types.h" +#include + using namespace SIMD; struct Position; @@ -84,5 +86,9 @@ namespace NNUE { void loadWeights(); + // Rama Chess: external net (UCI option EvalFile) + extern bool netLoaded; + bool loadWeightsFromFile(const std::string& path); + Score evaluate(Position& pos, Accumulator& accumulator); } \ No newline at end of file diff --git a/src/simd.h b/src/simd.h index 33c951c..9f5566b 100644 --- a/src/simd.h +++ b/src/simd.h @@ -63,7 +63,7 @@ namespace SIMD { return _mm512_reduce_add_ps(vec); } -#elif defined(__AVX2__) +#elif defined(__AVX2__) || defined(OBSIDIAN_SIMDE) // Rama Chess: SIMDe maps AVX2 to NEON / SSE4.1 using VecI = __m256i; using VecF = __m256; @@ -94,7 +94,11 @@ namespace SIMD { inline VecI maddubsEpi16(VecI x, VecI y) { return _mm256_maddubs_epi16(x, y); } +#ifdef OBSIDIAN_SIMDE + inline VecI slliEpi16(VecI x, int y) { return _mm256_sll_epi16(x, _mm_cvtsi32_si128(y)); } +#else inline VecI slliEpi16(VecI x, int y) { return _mm256_slli_epi16(x, y); } +#endif inline VecI mulhiEpi16(VecI x, VecI y) { return _mm256_mulhi_epi16(x, y); } diff --git a/src/uci.cpp b/src/uci.cpp index 3664664..fc32c27 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -185,8 +185,20 @@ namespace { std::cout << "No such option: " << name << std::endl; } + bool requireNet() { + if (NNUE::netLoaded) + return true; + std::cout << "info string ERROR: no network loaded, send setoption name EvalFile value " << std::endl; + return false; + } + void go(Position& pos, std::istringstream& is) { + if (!requireNet()) { + std::cout << "bestmove 0000" << std::endl; + return; + } + std::string token; int perftPlies = 0; @@ -261,8 +273,8 @@ void UCI::loop(int argc, char* argv[]) { << "uciok" << std::endl; } else if (token == "qc") qc(pos); - else if (token == "bench") bench(); - else if (token == "benccch") benccch(is); + else if (token == "bench") { if (requireNet()) bench(); } + else if (token == "benccch") { if (requireNet()) benccch(is); } else if (token == "setoption") setoption(is); else if (token == "go") go(pos, is); else if (token == "position") position(pos, is); @@ -270,7 +282,7 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "isready") std::cout << "readyok" << std::endl; else if (token == "d") std::cout << pos << std::endl; else if (token == "tune") std::cout << paramsToSpsaInput(); - else if (token == "eval") { + else if (token == "eval" && requireNet()) { NNUE::Accumulator tempAcc; tempAcc.refresh(pos, WHITE); tempAcc.refresh(pos, BLACK); diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 58645ec..dcfc1df 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -86,6 +86,14 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const } +#ifdef NO_EMBED_NET +void evalFileChanged(const Option& o) { + std::string path = o; + if (!path.empty() && path != "") + NNUE::loadWeightsFromFile(path); +} +#endif + void init() { constexpr int MaxHashMB = 33554432; @@ -100,6 +108,9 @@ void init() { Options["Minimal"] = Option("false"); Options["MultiPV"] = Option(1, 1, MAX_MOVES); Options["UCI_Opponent"] = Option("", refreshContempt); +#ifdef NO_EMBED_NET + Options["EvalFile"] = Option("", evalFileChanged); +#endif } diff --git a/src/util.h b/src/util.h index 0e48b97..ec7b8f3 100644 --- a/src/util.h +++ b/src/util.h @@ -14,7 +14,11 @@ namespace Util { constexpr size_t align = 4096; #endif size = ((size + align - 1) / align) * align; // not actually required +#ifdef OBSIDIAN_SIMDE + void* result = std::aligned_alloc(align, size); +#else void* result = _mm_malloc(size, align); +#endif #if defined(__linux__) madvise(result, size, MADV_HUGEPAGE); #endif @@ -22,6 +26,10 @@ namespace Util { } inline void freeAlign(void* ptr) { +#ifdef OBSIDIAN_SIMDE + std::free(ptr); +#else _mm_free(ptr); +#endif } } \ No newline at end of file