diff --git a/src/nnue.cpp b/src/nnue.cpp index ecf36ce..a20b6a9 100644 --- a/src/nnue.cpp +++ b/src/nnue.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -23,7 +24,12 @@ // const unsigned char gNETWORKData[]; // const unsigned char *const gNETWORKEnd; // const unsigned int gNETWORKSize; +#ifndef NO_EMBED_NET INCBIN(NETWORK, EVALFILE); +#else +#include +#include +#endif #ifdef SP_MSVC #pragma pop_macro("_MSC_VER") @@ -48,8 +54,169 @@ void initNetworkData() { } } +#ifndef NO_EMBED_NET globalNetworkData = (NetworkData*)gNETWORKData; +#else + // zero-filled, page aligned, committed lazily; filled by loadNetworkFile() + void* mem = mmap(nullptr, sizeof(NetworkData), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mem == MAP_FAILED) { + std::cout << "info string ERROR: cannot allocate network memory" << std::endl; + std::exit(1); + } + globalNetworkData = (NetworkData*)mem; +#endif +} + +#ifndef NO_EMBED_NET +bool networkLoaded = true; +bool loadNetworkFile(const std::string& path) { + std::cout << "info string EvalFile ignored (embedded net): " << path << std::endl; + return false; +} +#else +bool networkLoaded = false; + +namespace { + // Same decoding + layout transform as tools/process_net.cpp (compressed input), done in the engine. + struct LebReader { + const uint8_t* p; + const uint8_t* end; + bool bad = false; + uint8_t next() { if (p >= end) { bad = true; return 0; } return *p++; } + uint64_t uleb() { + uint64_t result = 0; int shift = 0; + for (int iter = 0; iter < 16; iter++) { + uint8_t byte = next(); + if (shift < 64) result |= (uint64_t)(byte & 0x7F) << shift; + if ((byte & 0x80) == 0) return result; + shift += 7; + } + bad = true; return result; + } + int64_t sleb() { + int64_t result = 0; int shift = 0; uint8_t byte; + int iter = 0; + do { + byte = next(); + if (shift < 64) result |= (int64_t)(byte & 0x7F) << shift; + shift += 7; + } while ((byte & 0x80) && ++iter < 16); + if (byte & 0x80) bad = true; + if ((shift < 64) && (byte & 0x40)) result |= -((int64_t)1 << shift); + return result; + } + float flt() { uint32_t v = (uint32_t)uleb(); float f; std::memcpy(&f, &v, sizeof(f)); return f; } + }; +} + +static bool decodeCompressedNet(const uint8_t* data, size_t size, NetworkData* out) { + constexpr int PAWN_PAIR_SIZE = ThreatInputs::PAWN_PAIR_FEATURE_COUNT * L1_SIZE; + constexpr int THREAT_SIZE = ThreatInputs::FEATURE_COUNT * L1_SIZE; + LebReader r{data, data + size}; + + // tool order: psq, pawn-pair, threat (pawn-pair + threat = out->inputThreatWeights), biases, l1..l3 + for (auto& v : out->inputPsqWeights) v = (int16_t)r.sleb(); + for (int i = 0; i < PAWN_PAIR_SIZE; i++) out->inputThreatWeights[i] = (int8_t)r.sleb(); + for (int i = 0; i < THREAT_SIZE; i++) out->inputThreatWeights[PAWN_PAIR_SIZE + i] = (int8_t)r.sleb(); + for (auto& v : out->inputBiases) v = (int16_t)r.sleb(); + + std::vector l1(OUTPUT_BUCKETS * L1_SIZE * L2_SIZE); + std::vector l2(OUTPUT_BUCKETS * 2 * L2_SIZE * L3_SIZE); + std::vector l3(OUTPUT_BUCKETS * (L3_SIZE + 2 * L2_SIZE)); + for (auto& v : l1) v = (int8_t)r.sleb(); + for (auto& b : out->l1Biases) for (auto& v : b) v = r.flt(); + for (auto& v : l2) v = r.flt(); + for (auto& b : out->l2Biases) for (auto& v : b) v = r.flt(); + for (auto& v : l3) v = r.flt(); + for (auto& v : out->l3Biases) v = r.flt(); + if (r.bad || r.p != r.end) return false; + +#if defined(__AVX2__) || (defined(__AVX512F__) && defined(__AVX512BW__)) + // Transpose input weights for packus (as in tools/process_net.cpp) + constexpr int weightsPerBlock = sizeof(__m128i) / sizeof(int16_t); +#if (defined(__AVX512F__) && defined(__AVX512BW__)) + constexpr int packusBlocks = 8; + constexpr int permutation[packusBlocks] = { 0, 2, 4, 6, 1, 3, 5, 7 }; +#else + constexpr int packusBlocks = 4; + constexpr int permutation[packusBlocks] = { 0, 2, 1, 3 }; +#endif + __m128i regs[packusBlocks]; + for (int limit : { 1, 768 * KING_BUCKETS }) { + __m128i* vec = reinterpret_cast<__m128i*>(limit == 1 ? (int16_t*)out->inputBiases : (int16_t*)out->inputPsqWeights); + for (int i = 0; i < limit * L1_SIZE / weightsPerBlock; i += packusBlocks) { + for (int j = 0; j < packusBlocks; j++) regs[j] = vec[i + j]; + for (int j = 0; j < packusBlocks; j++) vec[i + j] = regs[permutation[j]]; + } + } + uint64_t weightsRegs[packusBlocks]; + for (int part = 0; part < 2; part++) { + int8_t* base = part == 0 ? out->inputThreatWeights + PAWN_PAIR_SIZE : out->inputThreatWeights; + int count = part == 0 ? ThreatInputs::FEATURE_COUNT : ThreatInputs::PAWN_PAIR_FEATURE_COUNT; + uint64_t* weightsVec = reinterpret_cast(base); + for (int i = 0; i < count * L1_SIZE / weightsPerBlock; i += packusBlocks) { + for (int j = 0; j < packusBlocks; j++) weightsRegs[j] = weightsVec[i + j]; + for (int j = 0; j < packusBlocks; j++) weightsVec[i + j] = weightsRegs[permutation[j]]; + } + } +#endif + + for (int b = 0; b < OUTPUT_BUCKETS; b++) { +#if defined(__SSSE3__) || defined(__AVX2__) || (defined(__AVX512F__) && defined(__AVX512BW__)) || defined(ARCH_ARM) + for (int i = 0; i < L1_SIZE / INT8_PER_INT32; i++) + for (int j = 0; j < L2_SIZE; j++) + for (int c = 0; c < INT8_PER_INT32; c++) + out->l1Weights[b][i * INT8_PER_INT32 * L2_SIZE + j * INT8_PER_INT32 + c] = l1[(i * INT8_PER_INT32 + c) * OUTPUT_BUCKETS * L2_SIZE + b * L2_SIZE + j]; +#else + for (int i = 0; i < L1_SIZE; i++) + for (int j = 0; j < L2_SIZE; j++) + out->l1Weights[b][i * L2_SIZE + j] = l1[i * OUTPUT_BUCKETS * L2_SIZE + b * L2_SIZE + j]; +#endif + for (int i = 0; i < 2 * L2_SIZE; i++) + for (int j = 0; j < L3_SIZE; j++) + out->l2Weights[b][i * L3_SIZE + j] = l2[i * OUTPUT_BUCKETS * L3_SIZE + b * L3_SIZE + j]; + for (int i = 0; i < L3_SIZE + 2 * L2_SIZE; i++) + out->l3Weights[b][i] = l3[i * OUTPUT_BUCKETS + b]; + } + return true; +} + +bool loadNetworkFile(const std::string& path) { + networkLoaded = false; + 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(); + f.seekg(0); + if (size == (std::streamsize)sizeof(NetworkData)) { + // already processed net (output of tools/process_net for this architecture) + if (!f.read(reinterpret_cast(globalNetworkData), sizeof(NetworkData))) { + std::cout << "info string ERROR: EvalFile " << path << ": read error" << std::endl; + return false; + } + } + else { + if (size <= 0 || size > (std::streamsize)(4 * sizeof(NetworkData))) { + std::cout << "info string ERROR: EvalFile " << path << ": wrong size " << size << std::endl; + return false; + } + std::vector buf((size_t)size); + if (!f.read(reinterpret_cast(buf.data()), size)) { + std::cout << "info string ERROR: EvalFile " << path << ": read error" << std::endl; + return false; + } + if (!decodeCompressedNet(buf.data(), buf.size(), globalNetworkData)) { + std::cout << "info string ERROR: EvalFile " << path << ": not a PlentyChess network" << std::endl; + return false; + } + } + networkLoaded = true; + std::cout << "info string EvalFile loaded: " << path << std::endl; + return true; } +#endif void NNUE::reset(Board* board) { if (!networkData) { diff --git a/src/nnue.h b/src/nnue.h index 39dbe2d..d2e03ea 100644 --- a/src/nnue.h +++ b/src/nnue.h @@ -2,6 +2,7 @@ #include #include +#include #include "types.h" #include "threat-inputs.h" @@ -117,6 +118,11 @@ extern NetworkData* globalNetworkData; void initNetworkData(); +// Rama Chess: external net (UCI option EvalFile). Accepts the official compressed net (e.g. 0178r.bin) +// or an already processed net (exactly sizeof(NetworkData) bytes). +extern bool networkLoaded; +bool loadNetworkFile(const std::string& path); + struct Board; class NNUE { diff --git a/src/uci.cpp b/src/uci.cpp index ee335cd..9e0fb01 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -257,6 +257,10 @@ void setoption(std::string line) { if (matchesToken(line, "value")) { line = line.substr(6); +#ifdef NO_EMBED_NET + if (name == "EvalFile") value = line; // whole rest of the line (path) + else +#endif value = line.find(' ') != std::string::npos ? line.substr(0, line.find(' ')) : line; } @@ -267,6 +271,17 @@ void setoption(std::string line) { UCI::Options.forEach(setUciOption(name, value)); SPSA::trySetParam(name, value); +#ifdef NO_EMBED_NET + if (name == "EvalFile") { + while (!value.empty() && (value.back() == '\r' || value.back() == ' ')) value.pop_back(); + UCI::Options.evalFile.value = value; + if (!value.empty() && value != "") { + threads.waitForSearchFinished(); + loadNetworkFile(value); + } + } +#endif + if (name == "SyzygyPath") { std::string path = UCI::Options.syzygyPath.value; tb_init(path.c_str()); @@ -275,7 +290,17 @@ void setoption(std::string line) { } } +static bool requireNetwork() { + if (networkLoaded) return true; + std::cout << "info string ERROR: no network loaded, send setoption name EvalFile value " << std::endl; + return false; +} + void go(std::string line, Board& board, std::vector& boardHistory) { + if (!requireNetwork()) { + std::cout << "bestmove 0000" << std::endl; + return; + } SearchParameters parameters; if (line.size() == 2) { line = ""; @@ -475,7 +500,7 @@ void uciLoop(int argc, char* argv[]) { return; } if (argc > 1 && matchesToken(argv[1], "bench")) { - bench(board, boardHistory); + if (requireNetwork()) bench(board, boardHistory); Debug::show(); return; } @@ -530,10 +555,11 @@ void uciLoop(int argc, char* argv[]) { else if (matchesToken(line, "setoption")) setoption(line.substr(10)); /* NON UCI COMMANDS */ - else if (matchesToken(line, "bench")) bench(board, boardHistory); + else if (matchesToken(line, "bench")) { if (requireNetwork()) bench(board, boardHistory); } else if (matchesToken(line, "perfttest")) perfttest(board, boardHistory); else if (matchesToken(line, "debug")) board.debugBoard(); else if (matchesToken(line, "eval")) { + if (!requireNetwork()) continue; UCI::nnue.reset(&board); std::cout << UCI::nnue.evaluate(&board) << std::endl; } diff --git a/src/uci.h b/src/uci.h index 83bea61..4924fbe 100644 --- a/src/uci.h +++ b/src/uci.h @@ -137,9 +137,21 @@ namespace UCI { 7 }; +#ifdef NO_EMBED_NET + UCIOption evalFile = { + "EvalFile", + "", + "" + }; +#endif + template void forEach(Func&& f) { +#ifdef NO_EMBED_NET + auto optionsTuple = std::make_tuple(&hash, &threads, &multiPV, &moveOverhead, &chess960, &ponder, &datagen, &minimal, &syzygyPath, &syzygyProbeLimit, &evalFile); +#else auto optionsTuple = std::make_tuple(&hash, &threads, &multiPV, &moveOverhead, &chess960, &ponder, &datagen, &minimal, &syzygyPath, &syzygyProbeLimit); +#endif for_each_in_tuple(optionsTuple, f); } };