S人ving Privàte Ryän

Fixes problems with non-ASCII characters in paths. Fixes #3424.

Changes
- Bump the minimum supported Clang version to 11 to support std::filesystem
- Use std::optional for empty values instead of string sentinels like "None"
- Remove the FixedString class
- Move the EvalFile network path from the network struct to the engine
- Simplify parts of the command-line handling
- Remove the old dual-net logic from export_net
- And ofc we now support non-ascii paths for windows

Behavior Change
- Because the EvalFile is no longer part of the network, it won't be included in the hashing, thus the network sharing is no longer dependent on the EvalFile path
- As a result, if the binary version and network are the same, changing only the EvalFile location will now reuse the same shared network

This also fixes a master bug where:

```
export_net None
setoption name EvalFile value None
export_net None
Failed to export a net
```

would fail on the second export. With this patch, the second export succeeds.

Quick Fishtest Check that nets load like normal.
https://tests.stockfishchess.org/tests/view/6a455593f97ff95f787954da

<img width="833" height="1137" alt="image" src="https://github.com/user-attachments/assets/fffee63b-c63a-4d5e-b13d-87d58bc28c88" />

closes https://github.com/official-stockfish/Stockfish/pull/6937

No functional change

Co-authored-by: anematode <timothy.herchen@gmail.com>
This commit is contained in:
Disservin
2026-07-06 11:30:51 +02:00
committed by Joost VandeVondele
co-authored by anematode
parent d5bbc6b67d
commit 22548cb0c0
14 changed files with 240 additions and 254 deletions
-1
View File
@@ -22,7 +22,6 @@ jobs:
- { name: gcc-15, comp: gcc, cxx: g++, image: "gcc:15" } - { name: gcc-15, comp: gcc, cxx: g++, image: "gcc:15" }
# Using silkeh/clang for older versions # Using silkeh/clang for older versions
- { name: clang-10, comp: clang, cxx: clang++, image: "silkeh/clang:10", is_clang: true, ver: "10" }
- { name: clang-11, comp: clang, cxx: clang++, image: "silkeh/clang:11", is_clang: true, ver: "11" } - { name: clang-11, comp: clang, cxx: clang++, image: "silkeh/clang:11", is_clang: true, ver: "11" }
- { name: clang-12, comp: clang, cxx: clang++, image: "silkeh/clang:12", is_clang: true, ver: "12" } - { name: clang-12, comp: clang, cxx: clang++, image: "silkeh/clang:12", is_clang: true, ver: "12" }
- { name: clang-13, comp: clang, cxx: clang++, image: "silkeh/clang:13", is_clang: true, ver: "13" } - { name: clang-13, comp: clang, cxx: clang++, image: "silkeh/clang:13", is_clang: true, ver: "13" }
+18 -14
View File
@@ -20,6 +20,7 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <filesystem>
#include <deque> #include <deque>
#include <iosfwd> #include <iosfwd>
#include <memory> #include <memory>
@@ -33,7 +34,6 @@
#include "misc.h" #include "misc.h"
#include "nnue/network.h" #include "nnue/network.h"
#include "nnue/nnue_common.h" #include "nnue/nnue_common.h"
#include "nnue/nnue_misc.h"
#include "numa.h" #include "numa.h"
#include "perft.h" #include "perft.h"
#include "position.h" #include "position.h"
@@ -57,18 +57,19 @@ int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
// PR#6526). The user can always explicitly override this behavior. // PR#6526). The user can always explicitly override this behavior.
constexpr NumaAutoPolicy DefaultNumaPolicy = BundledL3Policy{32}; constexpr NumaAutoPolicy DefaultNumaPolicy = BundledL3Policy{32};
Engine::Engine(std::optional<std::string> path) : Engine::Engine(std::optional<std::filesystem::path> path) :
binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), binaryDirectory(path ? CommandLine::get_binary_directory(*path) : std::filesystem::path{}),
numaContext(NumaConfig::from_system(DefaultNumaPolicy)), numaContext(NumaConfig::from_system(DefaultNumaPolicy)),
states(new std::deque<StateInfo>(1)), states(new std::deque<StateInfo>(1)),
threads(), threads(),
network(numaContext, get_default_network()) { networkFile{std::nullopt, ""},
network(numaContext) {
pos.set(StartFEN, false, &states->back()); pos.set(StartFEN, false, &states->back());
options.add( // options.add( //
"Debug Log File", Option("", [](const Option& o) { "Debug Log File", Option("", [](const Option& o) {
start_logger(o); start_logger(path_from_utf8(std::string(o)));
return std::nullopt; return std::nullopt;
})); }));
@@ -133,10 +134,11 @@ Engine::Engine(std::optional<std::string> path) :
options.add( // options.add( //
"EvalFile", Option(EvalFileDefaultName, [this](const Option& o) { "EvalFile", Option(EvalFileDefaultName, [this](const Option& o) {
load_network(o); load_network(path_from_utf8(std::string(o)));
return std::nullopt; return std::nullopt;
})); }));
network = get_default_network();
threads.clear(); threads.clear();
threads.ensure_network_replicated(); threads.ensure_network_replicated();
resize_threads(); resize_threads();
@@ -256,7 +258,8 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }
// network related // network related
void Engine::verify_network() const { void Engine::verify_network() const {
network->verify(options["EvalFile"], onVerifyNetwork); const auto file = path_from_utf8(std::string(options["EvalFile"]));
network->verify(onVerifyNetwork, networkFile, file);
auto statuses = network.get_status_and_errors(); auto statuses = network.get_status_and_errors();
for (usize i = 0; i < statuses.size(); ++i) for (usize i = 0; i < statuses.size(); ++i)
@@ -289,24 +292,25 @@ void Engine::verify_network() const {
} }
} }
std::unique_ptr<Eval::NNUE::Network> Engine::get_default_network() const { std::unique_ptr<Eval::NNUE::Network> Engine::get_default_network() {
auto network_ = std::make_unique<NN::Network>(NN::EvalFile{EvalFileDefaultName, "None", ""}); auto network_ = std::make_unique<NN::Network>();
network_->load(binaryDirectory, ""); network_->load(binaryDirectory, std::filesystem::path{}, networkFile);
return network_; return network_;
} }
void Engine::load_network(const std::string& file) { void Engine::load_network(const std::filesystem::path& file) {
network.modify_and_replicate( network.modify_and_replicate(
[this, &file](NN::Network& network_) { network_.load(binaryDirectory, file); }); [this, &file](NN::Network& network_) { network_.load(binaryDirectory, file, networkFile); });
threads.clear(); threads.clear();
threads.ensure_network_replicated(); threads.ensure_network_replicated();
} }
void Engine::save_network(const std::pair<std::optional<std::string>, std::string> file) { void Engine::save_network(const std::optional<std::filesystem::path>& file) {
network.modify_and_replicate([&file](NN::Network& network_) { network_.save(file.first); }); network.modify_and_replicate(
[&file, this](NN::Network& network_) { network_.save(networkFile, file); });
} }
// utility functions // utility functions
+8 -5
View File
@@ -20,6 +20,7 @@
#define ENGINE_H_INCLUDED #define ENGINE_H_INCLUDED
#include <functional> #include <functional>
#include <filesystem>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -31,6 +32,7 @@
#include "misc.h" #include "misc.h"
#include "history.h" #include "history.h"
#include "nnue/network.h" #include "nnue/network.h"
#include "nnue/nnue_misc.h"
#include "numa.h" #include "numa.h"
#include "position.h" #include "position.h"
#include "search.h" #include "search.h"
@@ -47,7 +49,7 @@ class Engine {
using InfoFull = Search::InfoFull; using InfoFull = Search::InfoFull;
using InfoIter = Search::InfoIteration; using InfoIter = Search::InfoIteration;
Engine(std::optional<std::string> path = std::nullopt); Engine(std::optional<std::filesystem::path> path = std::nullopt);
// Cannot be movable due to components holding backreferences to fields // Cannot be movable due to components holding backreferences to fields
Engine(const Engine&) = delete; Engine(const Engine&) = delete;
@@ -87,9 +89,9 @@ class Engine {
// network related // network related
void verify_network() const; void verify_network() const;
std::unique_ptr<Eval::NNUE::Network> get_default_network() const; std::unique_ptr<Eval::NNUE::Network> get_default_network();
void load_network(const std::string& file); void load_network(const std::filesystem::path& file);
void save_network(std::pair<std::optional<std::string>, std::string> file); void save_network(const std::optional<std::filesystem::path>& file);
// utility functions // utility functions
@@ -110,7 +112,7 @@ class Engine {
std::string thread_binding_information_as_string() const; std::string thread_binding_information_as_string() const;
private: private:
const std::string binaryDirectory; const std::filesystem::path binaryDirectory;
NumaReplicationContext numaContext; NumaReplicationContext numaContext;
@@ -120,6 +122,7 @@ class Engine {
OptionsMap options; OptionsMap options;
ThreadPool threads; ThreadPool threads;
TranspositionTable tt; TranspositionTable tt;
Eval::NNUE::EvalFile networkFile;
LazyNumaReplicatedSystemWide<Eval::NNUE::Network> network; LazyNumaReplicatedSystemWide<Eval::NNUE::Network> network;
Search::SearchManager::UpdateContext updateContext; Search::SearchManager::UpdateContext updateContext;
+3 -1
View File
@@ -18,6 +18,7 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <utility>
#include "attacks.h" #include "attacks.h"
#include "bitboard.h" #include "bitboard.h"
@@ -43,7 +44,8 @@ int main(int argc, char* argv[]) {
Attacks::init(); Attacks::init();
Position::init(); Position::init();
auto uci = std::make_unique<UCIEngine>(argc, argv); auto cli = CommandLine(argc, argv);
auto uci = std::make_unique<UCIEngine>(std::move(cli));
Tune::init(uci->engine_options()); Tune::init(uci->engine_options());
+79 -40
View File
@@ -22,8 +22,10 @@
#include <atomic> #include <atomic>
#include <cassert> #include <cassert>
#include <cctype> #include <cctype>
#include <cstring>
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
#include <filesystem>
#include <fstream> #include <fstream>
#include <iomanip> #include <iomanip>
#include <iostream> #include <iostream>
@@ -33,10 +35,21 @@
#include <sstream> #include <sstream>
#include <string_view> #include <string_view>
#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <direct.h>
#include <windows.h>
#include <shellapi.h>
#endif
#include "types.h" #include "types.h"
namespace Stockfish { namespace Stockfish {
namespace fs = std::filesystem;
namespace { namespace {
// Version number or dev. // Version number or dev.
@@ -83,7 +96,7 @@ class Logger {
Tie in, out; Tie in, out;
public: public:
static void start(const std::string& fname) { static void start(const fs::path& fname) {
static Logger l; static Logger l;
@@ -474,16 +487,61 @@ u64 hash_bytes(const char* data, usize size) {
} }
// Trampoline helper to avoid moving Logger to misc.h // Trampoline helper to avoid moving Logger to misc.h
void start_logger(const std::string& fname) { Logger::start(fname); } void start_logger(const fs::path& fname) { Logger::start(fname); }
std::string utf8_from_wstring(std::wstring_view s) {
#ifdef _WIN32 #ifdef _WIN32
#include <direct.h> if (s.empty())
#define GETCWD _getcwd return {};
int size =
WideCharToMultiByte(CP_UTF8, 0, s.data(), int(s.size()), nullptr, 0, nullptr, nullptr);
if (size <= 0)
return {};
std::string out(size, '\0');
WideCharToMultiByte(CP_UTF8, 0, s.data(), int(s.size()), out.data(), size, nullptr, nullptr);
return out;
#else #else
#include <unistd.h> return std::string(s.begin(), s.end());
#define GETCWD getcwd
#endif #endif
}
fs::path path_from_utf8(const std::string& path) {
#ifdef _WIN32
int u8len = static_cast<int>(path.size());
int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), u8len, NULL, 0);
std::wstring wstr(static_cast<usize>(wlen), L'\0');
MultiByteToWideChar(CP_UTF8, 0, path.c_str(), u8len, wstr.data(), wlen);
return {wstr};
#else
return {path};
#endif
}
CommandLine::CommandLine(int _argc, char** _argv) :
argc(_argc),
argv(_argv) {
#ifdef _WIN32
// Convert any non-ANSI characters passed on the command line to UTF-8
int wargc = 0;
if (LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &wargc))
{
for (int i = 0; i < wargc; ++i)
argv_storage.push_back(utf8_from_wstring(wargv[i]));
LocalFree(wargv);
for (std::string& s : argv_storage)
argv_utf8.push_back(s.data());
argv_utf8.push_back(nullptr);
argc = wargc;
argv = argv_utf8.data();
}
#endif
}
usize str_to_size_t(const std::string& s) { usize str_to_size_t(const std::string& s) {
unsigned long long value = std::stoull(s); unsigned long long value = std::stoull(s);
@@ -507,49 +565,30 @@ bool is_whitespace(std::string_view s) {
return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); }); return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); });
} }
std::string CommandLine::get_binary_directory(std::string argv0) { fs::path CommandLine::get_binary_directory(fs::path argv0) {
std::string pathSeparator;
#ifdef _WIN32 #ifdef _WIN32
pathSeparator = "\\";
#ifdef _MSC_VER #ifdef _MSC_VER
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had // Prefer the executable path reported by the CRT when available.
// issues in some Windows 10 versions, so check returned values carefully. wchar_t* pgmptr = nullptr;
char* pgmptr = nullptr; if (!_get_wpgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) argv0 = fs::path(pgmptr);
argv0 = pgmptr;
#endif #endif
#else
pathSeparator = "/";
#endif #endif
// Extract the working directory auto binaryDirectory = argv0.parent_path();
auto workingDirectory = CommandLine::get_working_directory(); if (binaryDirectory.empty())
binaryDirectory = fs::path(".");
// Extract the binary directory path from argv0
auto binaryDirectory = argv0;
usize pos = binaryDirectory.find_last_of("\\/");
if (pos == std::string::npos)
binaryDirectory = "." + pathSeparator;
else
binaryDirectory.resize(pos + 1);
// Pattern replacement: "./" at the start of path is replaced by the working directory
if (binaryDirectory.find("." + pathSeparator) == 0)
binaryDirectory.replace(0, 1, workingDirectory);
return binaryDirectory; return binaryDirectory;
} }
std::string CommandLine::get_working_directory() { fs::path CommandLine::get_working_directory() { return std::filesystem::current_path(); }
std::string workingDirectory = "";
char buff[40000];
char* cwd = GETCWD(buff, 40000);
if (cwd)
workingDirectory = cwd;
return workingDirectory; void set_console_utf8() {
#ifdef _WIN32
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
#endif
} }
} // namespace Stockfish } // namespace Stockfish
+21 -86
View File
@@ -30,10 +30,9 @@
#include <exception> // IWYU pragma: keep #include <exception> // IWYU pragma: keep
// IWYU pragma: no_include <__exception/terminate.h> // IWYU pragma: no_include <__exception/terminate.h>
#include <functional> #include <functional>
#include <filesystem>
#include <iosfwd> #include <iosfwd>
#include <optional> #include <optional>
#include <cstring>
#include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <type_traits> #include <type_traits>
@@ -132,10 +131,13 @@ void prefetch(const void* addr) {
} }
#endif #endif
void start_logger(const std::string& fname); void start_logger(const std::filesystem::path& fname);
usize str_to_size_t(const std::string& s); usize str_to_size_t(const std::string& s);
std::string utf8_from_wstring(std::wstring_view s);
std::filesystem::path path_from_utf8(const std::string& path);
#if defined(__linux__) #if defined(__linux__)
struct PipeDeleter { struct PipeDeleter {
@@ -524,88 +526,26 @@ inline void hash_combine(usize& seed, const T& v) {
inline u64 hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); } inline u64 hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); }
template<usize Capacity>
class FixedString {
public:
FixedString() :
length_(0) {
data_[0] = '\0';
}
FixedString(const char* str) {
usize len = std::strlen(str);
if (len > Capacity)
std::terminate();
std::memcpy(data_, str, len);
length_ = len;
data_[length_] = '\0';
}
FixedString(const std::string& str) {
if (str.size() > Capacity)
std::terminate();
std::memcpy(data_, str.data(), str.size());
length_ = str.size();
data_[length_] = '\0';
}
usize size() const { return length_; }
usize capacity() const { return Capacity; }
const char* c_str() const { return data_; }
const char* data() const { return data_; }
char& operator[](usize i) { return data_[i]; }
const char& operator[](usize i) const { return data_[i]; }
FixedString& operator+=(const char* str) {
usize len = std::strlen(str);
if (length_ + len > Capacity)
std::terminate();
std::memcpy(data_ + length_, str, len);
length_ += len;
data_[length_] = '\0';
return *this;
}
FixedString& operator+=(const FixedString& other) { return (*this += other.c_str()); }
operator std::string() const { return std::string(data_, length_); }
operator std::string_view() const { return std::string_view(data_, length_); }
template<typename T>
bool operator==(const T& other) const noexcept {
return (std::string_view) (*this) == other;
}
template<typename T>
bool operator!=(const T& other) const noexcept {
return (std::string_view) (*this) != other;
}
void clear() {
length_ = 0;
data_[0] = '\0';
}
private:
char data_[Capacity + 1]; // +1 for null terminator
usize length_;
};
struct CommandLine { struct CommandLine {
public: public:
CommandLine(int _argc, char** _argv) : CommandLine(int _argc, char** _argv);
argc(_argc),
argv(_argv) {}
static std::string get_binary_directory(std::string argv0); CommandLine(const CommandLine&) = delete;
static std::string get_working_directory(); CommandLine& operator=(const CommandLine&) = delete;
CommandLine(CommandLine&&) = default;
CommandLine& operator=(CommandLine&&) = default;
static std::filesystem::path get_binary_directory(std::filesystem::path argv0);
static std::filesystem::path get_working_directory();
int argc; int argc;
char** argv; char** argv;
private:
#ifdef _WIN32
std::vector<std::string> argv_storage;
std::vector<char*> argv_utf8;
#endif
}; };
namespace Utility { namespace Utility {
@@ -666,13 +606,8 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
#define RESTRICT #define RESTRICT
#endif #endif
void set_console_utf8();
} // namespace Stockfish } // namespace Stockfish
template<Stockfish::usize N>
struct std::hash<Stockfish::FixedString<N>> {
Stockfish::usize operator()(const Stockfish::FixedString<N>& fstr) const noexcept {
return Stockfish::hash_bytes(fstr.data(), fstr.size());
}
};
#endif // #ifndef MISC_H_INCLUDED #endif // #ifndef MISC_H_INCLUDED
+42 -46
View File
@@ -24,6 +24,7 @@
#include <optional> #include <optional>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
#include <filesystem>
#define INCBIN_SILENCE_BITCODE_WARNING #define INCBIN_SILENCE_BITCODE_WARNING
#include "../incbin/incbin.h" #include "../incbin/incbin.h"
@@ -58,8 +59,10 @@ const unsigned char gEmbeddedNNUEData[1] = {0x0};
const unsigned int gEmbeddedNNUESize = 1; const unsigned int gEmbeddedNNUESize = 1;
#endif #endif
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
namespace fs = std::filesystem;
namespace Detail { namespace Detail {
@@ -84,60 +87,56 @@ bool write_parameters(std::ostream& stream, const T& reference) {
} // namespace Detail } // namespace Detail
void Network::load(const std::string& rootDirectory, std::string evalfilePath) { void Network::load(const fs::path& rootDirectory, fs::path evalfilePath, EvalFile& evalFile) {
#if defined(DEFAULT_NNUE_DIRECTORY) #if defined(DEFAULT_NNUE_DIRECTORY)
std::vector<std::string> dirs = {"<internal>", "", rootDirectory, std::vector<fs::path> dirs = {fs::path{}, rootDirectory,
stringify(DEFAULT_NNUE_DIRECTORY)}; fs::path(stringify(DEFAULT_NNUE_DIRECTORY))};
#else #else
std::vector<std::string> dirs = {"<internal>", "", rootDirectory}; std::vector<fs::path> dirs = {fs::path{}, rootDirectory};
#endif #endif
if (evalfilePath.empty()) if (evalfilePath.empty())
evalfilePath = evalFile.defaultName; evalfilePath = evalFile.defaultName;
if (evalFile.current != evalfilePath && evalfilePath == evalFile.defaultName)
load_internal(evalFile);
for (const auto& directory : dirs) for (const auto& directory : dirs)
{ {
if (std::string(evalFile.current) != evalfilePath) if (evalFile.current != evalfilePath)
{ load_external(directory, evalfilePath, evalFile);
if (directory != "<internal>")
load_user_net(directory, evalfilePath);
else if (evalfilePath == std::string(evalFile.defaultName))
load_internal();
}
} }
} }
bool Network::save(const EvalFile& evalFile, const std::optional<fs::path>& filename) const {
bool Network::save(const std::optional<std::string>& filename) const { if (!evalFile.current.has_value())
std::string actualFilename;
std::string msg;
if (filename.has_value())
actualFilename = filename.value();
else
{ {
if (std::string(evalFile.current) != std::string(evalFile.defaultName)) sync_cout << "Failed to export a net. No network file is currently loaded. "
{ "Please load a network file first."
msg = "Failed to export a net. " << sync_endl;
"A non-embedded net can only be saved if the filename is specified";
sync_cout << msg << sync_endl;
return false; return false;
} }
actualFilename = evalFile.defaultName; if (!filename.has_value() && evalFile.current != evalFile.defaultName)
{
sync_cout << "Failed to export a net. A non-embedded net can only be "
"saved if the filename is specified"
<< sync_endl;
return false;
} }
fs::path actualFilename = filename.value_or(evalFile.defaultName);
std::ofstream stream(actualFilename, std::ios_base::binary); std::ofstream stream(actualFilename, std::ios_base::binary);
bool saved = save(stream, evalFile.current, evalFile.netDescription);
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; bool saved = save(stream, evalFile.netDescription);
sync_cout << (saved ? "Network saved successfully to " + actualFilename.string()
: "Failed to export a net")
<< sync_endl;
sync_cout << msg << sync_endl;
return saved; return saved;
} }
NetworkOutput Network::evaluate(const Position& pos, NetworkOutput Network::evaluate(const Position& pos,
AccumulatorStack& accumulatorStack, AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const { AccumulatorCaches& cache) const {
@@ -158,18 +157,20 @@ NetworkOutput Network::evaluate(const Position& pos,
} }
void Network::verify(std::string evalfilePath, void Network::verify(const std::function<void(std::string_view)>& f,
const std::function<void(std::string_view)>& f) const { const EvalFile& evalFile,
fs::path evalfilePath) const {
if (evalfilePath.empty()) if (evalfilePath.empty())
evalfilePath = evalFile.defaultName; evalfilePath = evalFile.defaultName;
if (std::string(evalFile.current) != evalfilePath) if (evalFile.current != evalfilePath)
{ {
if (f) if (f)
{ {
std::string msg1 = std::string msg1 =
"Network evaluation parameters compatible with the engine must be available."; "Network evaluation parameters compatible with the engine must be available.";
std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; std::string msg2 =
"The network file " + evalfilePath.string() + " was not loaded successfully.";
std::string msg3 = "The UCI option EvalFile might need to specify the full path, " std::string msg3 = "The UCI option EvalFile might need to specify the full path, "
"including the directory name, to the network file."; "including the directory name, to the network file.";
std::string msg4 = "The default net can be downloaded from: " std::string msg4 = "The default net can be downloaded from: "
@@ -189,8 +190,9 @@ void Network::verify(std::string evalfilePath,
if (f) if (f)
{ {
usize size = sizeof(featureTransformer) + sizeof(NetworkArchitecture) * LayerStacks; usize size = sizeof(featureTransformer) + sizeof(NetworkArchitecture) * LayerStacks;
f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024)) f("NNUE evaluation using " + evalfilePath.string() + " ("
+ "MiB, (" + std::to_string(featureTransformer.InputDimensions) + ", " + std::to_string(size / (1024 * 1024)) + "MiB, ("
+ std::to_string(featureTransformer.InputDimensions) + ", "
+ std::to_string(network[0].TransformedFeatureDimensions) + ", " + std::to_string(network[0].TransformedFeatureDimensions) + ", "
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
+ ", 1))"); + ", 1))");
@@ -225,8 +227,8 @@ NnueEvalTrace Network::trace_evaluate(const Position& pos,
} }
void Network::load_user_net(const std::string& dir, const std::string& evalfilePath) { void Network::load_external(const fs::path& dir, const fs::path& evalfilePath, EvalFile& evalFile) {
std::ifstream stream(dir + evalfilePath, std::ios::binary); std::ifstream stream(dir / evalfilePath, std::ios::binary);
auto description = load(stream); auto description = load(stream);
if (description.has_value()) if (description.has_value())
@@ -237,7 +239,7 @@ void Network::load_user_net(const std::string& dir, const std::string& evalfileP
} }
void Network::load_internal() { void Network::load_internal(EvalFile& evalFile) {
// C++ way to prepare a buffer for a memory stream // C++ way to prepare a buffer for a memory stream
class MemoryBuffer: public std::basic_streambuf<char> { class MemoryBuffer: public std::basic_streambuf<char> {
public: public:
@@ -269,12 +271,7 @@ void Network::load_internal() {
void Network::initialize() { initialized = true; } void Network::initialize() { initialized = true; }
bool Network::save(std::ostream& stream, bool Network::save(std::ostream& stream, const std::string& netDescription) const {
const std::string& name,
const std::string& netDescription) const {
if (name.empty() || name == "None")
return false;
return write_parameters(stream, netDescription); return write_parameters(stream, netDescription);
} }
@@ -295,7 +292,6 @@ usize Network::get_content_hash() const {
hash_combine(h, featureTransformer); hash_combine(h, featureTransformer);
for (auto&& layerstack : network) for (auto&& layerstack : network)
hash_combine(h, layerstack); hash_combine(h, layerstack);
hash_combine(h, evalFile);
return h; return h;
} }
+14 -11
View File
@@ -26,6 +26,7 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
#include <filesystem>
#include "../types.h" #include "../types.h"
#include "../misc.h" #include "../misc.h"
@@ -49,8 +50,7 @@ using NetworkOutput = std::tuple<Value, Value>;
// there is no way to run destructors. // there is no way to run destructors.
class Network { class Network {
public: public:
Network(EvalFile file) : Network() = default;
evalFile(file) {}
Network(const Network& other) = default; Network(const Network& other) = default;
Network(Network&& other) = default; Network(Network&& other) = default;
@@ -58,8 +58,10 @@ class Network {
Network& operator=(const Network& other) = default; Network& operator=(const Network& other) = default;
Network& operator=(Network&& other) = default; Network& operator=(Network&& other) = default;
void load(const std::string& rootDirectory, std::string evalfilePath); void load(const std::filesystem::path& rootDirectory,
bool save(const std::optional<std::string>& filename) const; std::filesystem::path evalfilePath,
EvalFile& evalFile);
bool save(const EvalFile& evalFile, const std::optional<std::filesystem::path>& filename) const;
usize get_content_hash() const; usize get_content_hash() const;
@@ -68,18 +70,21 @@ class Network {
AccumulatorCaches& cache) const; AccumulatorCaches& cache) const;
void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const; void verify(const std::function<void(std::string_view)>& f,
const EvalFile& evalFile,
std::filesystem::path evalfilePath) const;
NnueEvalTrace trace_evaluate(const Position& pos, NnueEvalTrace trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack, AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const; AccumulatorCaches& cache) const;
private: void load_external(const std::filesystem::path&, const std::filesystem::path&, EvalFile&);
void load_user_net(const std::string&, const std::string&); void load_internal(EvalFile&);
void load_internal();
private:
void initialize(); void initialize();
bool save(std::ostream&, const std::string&, const std::string&) const; bool save(std::ostream&, const std::string&) const;
std::optional<std::string> load(std::istream&); std::optional<std::string> load(std::istream&);
bool read_header(std::istream&, u32*, std::string*) const; bool read_header(std::istream&, u32*, std::string*) const;
@@ -94,8 +99,6 @@ class Network {
// Evaluation function // Evaluation function
NetworkArchitecture network[LayerStacks]; NetworkArchitecture network[LayerStacks];
EvalFile evalFile;
bool initialized = false; bool initialized = false;
// Hash value of evaluation function structure // Hash value of evaluation function structure
+1
View File
@@ -25,6 +25,7 @@
#include <iomanip> #include <iomanip>
#include <iosfwd> #include <iosfwd>
#include <iostream> #include <iostream>
#include <memory>
#include <sstream> #include <sstream>
#include "../position.h" #include "../position.h"
+9 -16
View File
@@ -19,12 +19,15 @@
#ifndef NNUE_MISC_H_INCLUDED #ifndef NNUE_MISC_H_INCLUDED
#define NNUE_MISC_H_INCLUDED #define NNUE_MISC_H_INCLUDED
#include <memory>
#include <string> #include <string>
#include <string_view>
#include <optional>
#include <filesystem>
#include "../misc.h" #include "../misc.h"
#include "../types.h" #include "../types.h"
#include "nnue_architecture.h" #include "nnue_architecture.h"
#include "../evaluate.h"
namespace Stockfish { namespace Stockfish {
@@ -32,15 +35,16 @@ class Position;
namespace Eval::NNUE { namespace Eval::NNUE {
// EvalFile uses fixed string types because it's part of the network structure which must be trivial. // NNUE file metadata uses fixed string types so it stays trivially copyable and cheap to move
// around between the engine and the network loader.
struct EvalFile { struct EvalFile {
// Default net name, will use one of the EvalFileDefaultName* macros defined // Default net name, will use one of the EvalFileDefaultName* macros defined
// in evaluate.h // in evaluate.h
FixedString<256> defaultName; constexpr static std::string_view defaultName = EvalFileDefaultName;
// Selected net name, either via uci option or default // Selected net name, either via uci option or default
FixedString<256> current; std::optional<std::filesystem::path> current;
// Net description extracted from the net file // Net description extracted from the net file
FixedString<256> netDescription; std::string netDescription;
}; };
struct NnueEvalTrace { struct NnueEvalTrace {
@@ -59,15 +63,4 @@ std::string trace(Position& pos, const Network& network, AccumulatorCaches& cach
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
} // namespace Stockfish } // namespace Stockfish
template<>
struct std::hash<Stockfish::Eval::NNUE::EvalFile> {
Stockfish::usize operator()(const Stockfish::Eval::NNUE::EvalFile& evalFile) const noexcept {
Stockfish::usize h = 0;
Stockfish::hash_combine(h, evalFile.defaultName);
Stockfish::hash_combine(h, evalFile.current);
Stockfish::hash_combine(h, evalFile.netDescription);
return h;
}
};
#endif // #ifndef NNUE_MISC_H_INCLUDED #endif // #ifndef NNUE_MISC_H_INCLUDED
+29 -21
View File
@@ -32,6 +32,7 @@
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <string_view> #include <string_view>
#include <filesystem>
#include <sys/stat.h> #include <sys/stat.h>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
@@ -227,7 +228,7 @@ static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");
// time only existence of the file is checked. // time only existence of the file is checked.
class TBFile: public std::ifstream { class TBFile: public std::ifstream {
std::string fname; std::filesystem::path fname;
public: public:
// Look for and open the file among the Paths directories where the .rtbw // Look for and open the file among the Paths directories where the .rtbw
@@ -236,21 +237,13 @@ class TBFile: public std::ifstream {
// //
// Example: // Example:
// C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6 // C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6
static std::string Paths; static std::vector<std::filesystem::path> Paths;
TBFile(const std::string& f) { TBFile(const std::string& f) {
#ifndef _WIN32 for (const auto& path : Paths)
constexpr char SepChar = ':';
#else
constexpr char SepChar = ';';
#endif
std::stringstream ss(Paths);
std::string path;
while (std::getline(ss, path, SepChar))
{ {
fname = path + "/" + f; fname = path / std::filesystem::path(f);
std::ifstream::open(fname); std::ifstream::open(fname);
if (is_open()) if (is_open())
return; return;
@@ -273,7 +266,7 @@ class TBFile: public std::ifstream {
if (statbuf.st_size % 64 != 16) if (statbuf.st_size % 64 != 16)
{ {
std::cerr << "Corrupt tablebase file " << fname << std::endl; std::cerr << "Corrupt tablebase file " << fname.string() << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -286,12 +279,12 @@ class TBFile: public std::ifstream {
if (*baseAddress == MAP_FAILED) if (*baseAddress == MAP_FAILED)
{ {
std::cerr << "Could not mmap() " << fname << std::endl; std::cerr << "Could not mmap() " << fname.string() << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
#else #else
// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, HANDLE fd = CreateFileW(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
if (fd == INVALID_HANDLE_VALUE) if (fd == INVALID_HANDLE_VALUE)
@@ -302,7 +295,7 @@ class TBFile: public std::ifstream {
if (size_low % 64 != 16) if (size_low % 64 != 16)
{ {
std::cerr << "Corrupt tablebase file " << fname << std::endl; std::cerr << "Corrupt tablebase file " << fname.string() << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -320,7 +313,7 @@ class TBFile: public std::ifstream {
if (!*baseAddress) if (!*baseAddress)
{ {
std::cerr << "MapViewOfFile() failed, name = " << fname std::cerr << "MapViewOfFile() failed, name = " << fname.string()
<< ", error = " << GetLastError() << std::endl; << ", error = " << GetLastError() << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -331,7 +324,7 @@ class TBFile: public std::ifstream {
if (memcmp(data, Magics[type == WDL], 4)) if (memcmp(data, Magics[type == WDL], 4))
{ {
std::cerr << "Corrupted table in file " << fname << std::endl; std::cerr << "Corrupted table in file " << fname.string() << std::endl;
unmap(*baseAddress, *mapping); unmap(*baseAddress, *mapping);
return *baseAddress = nullptr, nullptr; return *baseAddress = nullptr, nullptr;
} }
@@ -350,7 +343,7 @@ class TBFile: public std::ifstream {
} }
}; };
std::string TBFile::Paths; std::vector<std::filesystem::path> TBFile::Paths;
// struct PairsData contains low-level indexing information to access TB data. // struct PairsData contains low-level indexing information to access TB data.
// There are 8, 4, or 2 PairsData records for each TBTable, according to the type // There are 8, 4, or 2 PairsData records for each TBTable, according to the type
@@ -1405,9 +1398,24 @@ void Tablebases::init(const std::string& paths) {
TBTables.clear(); TBTables.clear();
MaxCardinality = 0; MaxCardinality = 0;
TBFile::Paths = paths; TBFile::Paths.clear();
if (paths.empty()) if (!paths.empty())
{
#ifndef _WIN32
constexpr char SepChar = ':';
#else
constexpr char SepChar = ';';
#endif
std::stringstream ss(paths);
std::string path;
while (std::getline(ss, path, SepChar))
if (!path.empty())
TBFile::Paths.emplace_back(path_from_utf8(path));
}
if (TBFile::Paths.empty())
return; return;
// MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27
+2 -2
View File
@@ -65,8 +65,8 @@
#endif #endif
// Enforce minimum Clang version // Enforce minimum Clang version
#if defined(__clang__) && (__clang_major__ < 10) #if defined(__clang__) && (__clang_major__ < 11)
#error "Stockfish requires Clang 10.0 or later for correct compilation" #error "Stockfish requires Clang 11.0 or later for correct compilation"
#endif #endif
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0) #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
+10 -7
View File
@@ -26,6 +26,7 @@
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <string_view> #include <string_view>
#include <filesystem>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -63,9 +64,9 @@ void UCIEngine::print_info_string(std::string_view str) {
sync_cout_end(); sync_cout_end();
} }
UCIEngine::UCIEngine(int argc, char** argv) : UCIEngine::UCIEngine(CommandLine cli_) :
engine(argv[0]), engine(cli_.argc > 0 ? std::optional{path_from_utf8(cli_.argv[0])} : std::nullopt),
cli(argc, argv) { cli(std::move(cli_)) {
engine.get_options().add_info_listener([](const std::optional<std::string>& str) { engine.get_options().add_info_listener([](const std::optional<std::string>& str) {
if (str.has_value()) if (str.has_value())
@@ -85,6 +86,7 @@ void UCIEngine::init_search_update_listeners() {
} }
void UCIEngine::loop() { void UCIEngine::loop() {
set_console_utf8();
std::string token, cmd; std::string token, cmd;
for (int i = 1; i < cli.argc; ++i) for (int i = 1; i < cli.argc; ++i)
@@ -151,10 +153,11 @@ void UCIEngine::loop() {
sync_cout << compiler_info() << sync_endl; sync_cout << compiler_info() << sync_endl;
else if (token == "export_net") else if (token == "export_net")
{ {
std::pair<std::optional<std::string>, std::string> file; std::optional<std::filesystem::path> file;
std::string filename;
if (is >> file.second) if (is >> filename)
file.first = file.second; file = path_from_utf8(filename);
engine.save_network(file); engine.save_network(file);
} }
@@ -171,7 +174,7 @@ void UCIEngine::loop() {
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
<< sync_endl; << sync_endl;
} while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot } while (token != "quit" && cli.argc <= 1); // The command-line arguments are one-shot
} }
Search::LimitsType UCIEngine::parse_limits(std::istream& is) { Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
+1 -1
View File
@@ -39,7 +39,7 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -
class UCIEngine { class UCIEngine {
public: public:
UCIEngine(int argc, char** argv); UCIEngine(CommandLine cli);
void loop(); void loop();