diff --git a/src/eval/nnue.cpp b/src/eval/nnue.cpp index f815c50..910c903 100644 --- a/src/eval/nnue.cpp +++ b/src/eval/nnue.cpp @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #include "../../3rdparty/zstd/zstd.h" @@ -48,9 +50,17 @@ #undef SP_MSVC #endif +#ifndef SP_NO_EMBED_NET namespace { INCBIN(std::byte, defaultNet, SP_NETWORK_FILE); } // namespace +#else +// Rama Chess Android build: no embedded network, load with "setoption name EvalFile value " +namespace { + const std::byte* const g_defaultNetData = nullptr; + const unsigned int g_defaultNetSize = 0; +} // namespace +#endif namespace stormphrax::eval { namespace { @@ -198,6 +208,10 @@ namespace stormphrax::eval { } // namespace void init() { +#ifdef SP_NO_EMBED_NET + s_networkLoaded = false; + return; +#endif if (g_defaultNetSize < sizeof(NetworkHeader)) { eprintln("Missing default network?"); return; @@ -320,7 +334,98 @@ namespace stormphrax::eval { #endif } + bool loadNetworkFile(const std::string& path) { + s_networkLoaded = false; + + std::ifstream in{path, std::ios::binary | std::ios::ate}; + if (!in) { + println("info string ERROR: EvalFile {}: cannot open file", path); + return false; + } + + const auto fileSize = static_cast(in.tellg()); + in.seekg(0); + + if (fileSize < sizeof(NetworkHeader)) { + println("info string ERROR: EvalFile {}: file too small", path); + return false; + } + + NetworkHeader header{}; + if (!in.read(reinterpret_cast(&header), sizeof(NetworkHeader)) || !validate(header)) { + println("info string ERROR: EvalFile {}: wrong network header", path); + return false; + } + + const auto networkSize = Network::byteSize(); + const auto dataSize = fileSize - sizeof(NetworkHeader); + const bool compressed = testFlags(header.flags, NetworkFlags::kZstdCompressed); + + if (!compressed && dataSize != networkSize) { + println("info string ERROR: EvalFile {}: wrong size {} (expected {})", path, dataSize, networkSize); + return false; + } + + std::vector raw(dataSize); + if (!in.read(raw.data(), static_cast(dataSize))) { + println("info string ERROR: EvalFile {}: read failed", path); + return false; + } + + if (s_loadedNetworkData) { + util::alignedFree(s_loadedNetworkData); + s_loadedNetworkData = nullptr; + } + + s_loadedNetworkData = util::alignedAlloc(util::simd::kAlignment, networkSize); + if (!s_loadedNetworkData) { + println("info string ERROR: EvalFile {}: out of memory", path); + return false; + } + + if (compressed) { + const auto decompressedSize = ZSTD_decompress(s_loadedNetworkData, networkSize, raw.data(), dataSize); + if (ZSTD_isError(decompressedSize) || decompressedSize < networkSize) { + println("info string ERROR: EvalFile {}: decompression failed", path); + return false; + } + } else { + std::memcpy(s_loadedNetworkData, raw.data(), networkSize); + } + + raw.clear(); + raw.shrink_to_fit(); + + // the file holds the original (unpermuted) weights, permute in place for this CPU +#ifdef SP_USE_LIBNUMA + s_networks = std::make_unique>(); + s_networkData = std::make_unique>(networkSize); + for (i32 node = 0; node < numa::nodeCount(); ++node) { + auto* target = s_networkData->get(node); + std::memcpy(target, s_loadedNetworkData, networkSize); + nnue::NetworkLoader loader{target, networkSize}; + if (!s_networks->get(node)->loadFrom(loader, false)) { + println("info string ERROR: EvalFile {}: load failed", path); + return false; + } + } +#else + nnue::NetworkLoader loader{s_loadedNetworkData, networkSize}; + if (!s_network.loadFrom(loader, false)) { + println("info string ERROR: EvalFile {}: load failed", path); + return false; + } +#endif + + s_networkLoaded = true; + println("info string EvalFile loaded: {} ({})", path, std::string_view{header.name.data(), header.nameLen}); + return true; + } + std::string_view defaultNetworkName() { + if (g_defaultNetSize < sizeof(NetworkHeader)) { + return "none"; + } const auto& header = *reinterpret_cast(g_defaultNetData); return {header.name.data(), header.nameLen}; } diff --git a/src/eval/nnue.h b/src/eval/nnue.h index 92b7e43..b60f13c 100644 --- a/src/eval/nnue.h +++ b/src/eval/nnue.h @@ -20,6 +20,8 @@ #include "../types.h" +#include + #include "../position.h" #include "arch.h" #include "nnue/arch/multilayer.h" @@ -39,6 +41,7 @@ namespace stormphrax::eval { void shutdown(); [[nodiscard]] bool isNetworkLoaded(); + bool loadNetworkFile(const std::string& path); const Network* getNetwork(u32 numaId); diff --git a/src/uci/uci.cpp b/src/uci/uci.cpp index e733336..2454f80 100644 --- a/src/uci/uci.cpp +++ b/src/uci/uci.cpp @@ -203,6 +203,16 @@ namespace stormphrax { search::kSyzygyProbeLimitRange ); registerCheckOption("SyzygyProbeRootOnly", &opts.syzygyProbeRootOnly, s_defaultOpts.syzygyProbeRootOnly); + registerStringOption("EvalFile", nullptr, "", [&](std::string_view value) { + if (value.empty() || value == "") { + return; + } + if (m_searcher.searching()) { + println("info string ERROR: cannot change EvalFile while searching"); + return; + } + eval::loadNetworkFile(std::string{value}); + }); } UciHandler::~UciHandler() { @@ -435,7 +445,8 @@ namespace stormphrax { void UciHandler::handleGo(std::span args, Instant startTime) { if (!eval::isNetworkLoaded()) { - eprintln("No network loaded"); + println("info string ERROR: no network loaded, use setoption name EvalFile value "); + println("bestmove 0000"); return; }