Move options into the engine

Move the engine options into the engine class, also avoid duplicated
initializations after startup.  UCIEngine needs to register an add_listener to
listen to all option changes and print these.  Also avoid a double
initialization of the TT, which was the case with the old state.

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

No functional change
This commit is contained in:
Disservin
2024-06-04 22:29:27 +02:00
committed by Joost VandeVondele
parent c8213ba0d0
commit 7013a22b74
8 changed files with 183 additions and 116 deletions

View File

@@ -45,6 +45,7 @@ namespace Stockfish {
namespace NN = Eval::NNUE; namespace NN = Eval::NNUE;
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
Engine::Engine(std::string path) : Engine::Engine(std::string path) :
binaryDirectory(CommandLine::get_binary_directory(path)), binaryDirectory(CommandLine::get_binary_directory(path)),
@@ -58,6 +59,58 @@ Engine::Engine(std::string path) :
NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) {
pos.set(StartFEN, false, &states->back()); pos.set(StartFEN, false, &states->back());
capSq = SQ_NONE; capSq = SQ_NONE;
options["Debug Log File"] << Option("", [](const Option& o) {
start_logger(o);
return std::nullopt;
});
options["NumaPolicy"] << Option("auto", [this](const Option& o) {
set_numa_config_from_option(o);
return numa_config_information_as_string() + "\n" + thread_binding_information_as_string();
});
options["Threads"] << Option(1, 1, 1024, [this](const Option&) {
resize_threads();
return thread_binding_information_as_string();
});
options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) {
set_tt_size(o);
return std::nullopt;
});
options["Clear Hash"] << Option([this](const Option&) {
search_clear();
return std::nullopt;
});
options["Ponder"] << Option(false);
options["MultiPV"] << Option(1, 1, MAX_MOVES);
options["Skill Level"] << Option(20, 0, 20);
options["Move Overhead"] << Option(10, 0, 5000);
options["nodestime"] << Option(0, 0, 10000);
options["UCI_Chess960"] << Option(false);
options["UCI_LimitStrength"] << Option(false);
options["UCI_Elo"] << Option(1320, 1320, 3190);
options["UCI_ShowWDL"] << Option(false);
options["SyzygyPath"] << Option("<empty>", [](const Option& o) {
Tablebases::init(o);
return std::nullopt;
});
options["SyzygyProbeDepth"] << Option(1, 1, 100);
options["Syzygy50MoveRule"] << Option(true);
options["SyzygyProbeLimit"] << Option(7, 0, 7);
options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) {
load_big_network(o);
return std::nullopt;
});
options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) {
load_small_network(o);
return std::nullopt;
});
load_networks();
resize_threads();
} }
std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) { std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
@@ -212,6 +265,7 @@ void Engine::trace_eval() const {
sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl; sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl;
} }
const OptionsMap& Engine::get_options() const { return options; }
OptionsMap& Engine::get_options() { return options; } OptionsMap& Engine::get_options() { return options; }
std::string Engine::fen() const { return pos.fen(); } std::string Engine::fen() const { return pos.fen(); }
@@ -241,4 +295,30 @@ std::string Engine::get_numa_config_as_string() const {
return numaContext.get_numa_config().to_string(); return numaContext.get_numa_config().to_string();
} }
std::string Engine::numa_config_information_as_string() const {
auto cfgStr = get_numa_config_as_string();
return "Available Processors: " + cfgStr;
}
std::string Engine::thread_binding_information_as_string() const {
auto boundThreadsByNode = get_bound_thread_count_by_numa_node();
if (boundThreadsByNode.empty())
return "";
std::stringstream ss;
ss << "NUMA Node Thread Binding: ";
bool isFirst = true;
for (auto&& [current, total] : boundThreadsByNode)
{
if (!isFirst)
ss << ":";
ss << current << "/" << total;
isFirst = false;
}
return ss.str();
}
} }

View File

@@ -29,13 +29,13 @@
#include <vector> #include <vector>
#include "nnue/network.h" #include "nnue/network.h"
#include "numa.h"
#include "position.h" #include "position.h"
#include "search.h" #include "search.h"
#include "syzygy/tbprobe.h" // for Stockfish::Depth #include "syzygy/tbprobe.h" // for Stockfish::Depth
#include "thread.h" #include "thread.h"
#include "tt.h" #include "tt.h"
#include "ucioption.h" #include "ucioption.h"
#include "numa.h"
namespace Stockfish { namespace Stockfish {
@@ -93,12 +93,17 @@ class Engine {
// utility functions // utility functions
void trace_eval() const; void trace_eval() const;
const OptionsMap& get_options() const;
OptionsMap& get_options(); OptionsMap& get_options();
std::string fen() const; std::string fen() const;
void flip(); void flip();
std::string visualize() const; std::string visualize() const;
std::vector<std::pair<size_t, size_t>> get_bound_thread_count_by_numa_node() const; std::vector<std::pair<size_t, size_t>> get_bound_thread_count_by_numa_node() const;
std::string get_numa_config_as_string() const; std::string get_numa_config_as_string() const;
std::string numa_config_information_as_string() const;
std::string thread_binding_information_as_string() const;
private: private:
const std::string binaryDirectory; const std::string binaryDirectory;

View File

@@ -21,6 +21,7 @@
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <optional>
#include <sstream> #include <sstream>
#include <string> #include <string>
@@ -33,19 +34,19 @@ namespace Stockfish {
bool Tune::update_on_last; bool Tune::update_on_last;
const Option* LastOption = nullptr; const Option* LastOption = nullptr;
OptionsMap* Tune::options; OptionsMap* Tune::options;
namespace { namespace {
std::map<std::string, int> TuneResults; std::map<std::string, int> TuneResults;
void on_tune(const Option& o) { std::optional<std::string> on_tune(const Option& o) {
if (!Tune::update_on_last || LastOption == &o) if (!Tune::update_on_last || LastOption == &o)
Tune::read_options(); Tune::read_options();
return std::nullopt;
}
} }
void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& r) {
void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) {
// Do not generate option when there is nothing to tune (ie. min = max) // Do not generate option when there is nothing to tune (ie. min = max)
if (r(v).first == r(v).second) if (r(v).first == r(v).second)
@@ -54,8 +55,8 @@ void make_option(OptionsMap* options, const string& n, int v, const SetRange& r)
if (TuneResults.count(n)) if (TuneResults.count(n))
v = TuneResults[n]; v = TuneResults[n];
(*options)[n] << Option(v, r(v).first, r(v).second, on_tune); (*opts)[n] << Option(v, r(v).first, r(v).second, on_tune);
LastOption = &((*options)[n]); LastOption = &((*opts)[n]);
// Print formatted parameters, ready to be copy-pasted in Fishtest // Print formatted parameters, ready to be copy-pasted in Fishtest
std::cout << n << "," // std::cout << n << "," //
@@ -65,7 +66,6 @@ void make_option(OptionsMap* options, const string& n, int v, const SetRange& r)
<< (r(v).second - r(v).first) / 20.0 << "," // << (r(v).second - r(v).first) / 20.0 << "," //
<< "0.0020" << std::endl; << "0.0020" << std::endl;
} }
}
string Tune::next(string& names, bool pop) { string Tune::next(string& names, bool pop) {

View File

@@ -145,6 +145,8 @@ class Tune {
return add(value, (next(names), std::move(names)), args...); return add(value, (next(names), std::move(names)), args...);
} }
static void make_option(OptionsMap* options, const std::string& n, int v, const SetRange& r);
std::vector<std::unique_ptr<EntryBase>> list; std::vector<std::unique_ptr<EntryBase>> list;
public: public:

View File

@@ -30,20 +30,16 @@
#include "benchmark.h" #include "benchmark.h"
#include "engine.h" #include "engine.h"
#include "evaluate.h"
#include "movegen.h" #include "movegen.h"
#include "position.h" #include "position.h"
#include "score.h" #include "score.h"
#include "search.h" #include "search.h"
#include "syzygy/tbprobe.h"
#include "types.h" #include "types.h"
#include "ucioption.h" #include "ucioption.h"
namespace Stockfish { namespace Stockfish {
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
template<typename... Ts> template<typename... Ts>
struct overload: Ts... { struct overload: Ts... {
using Ts::operator()...; using Ts::operator()...;
@@ -56,55 +52,25 @@ UCIEngine::UCIEngine(int argc, char** argv) :
engine(argv[0]), engine(argv[0]),
cli(argc, argv) { cli(argc, argv) {
auto& options = engine.get_options(); engine.get_options().add_info_listener([](const std::optional<std::string>& str) {
if (!str || (*str).empty())
return;
options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); }); // split all lines
auto ss = std::istringstream{*str};
options["NumaPolicy"] << Option("auto", [this](const Option& o) { for (std::string line; std::getline(ss, line, '\n');)
engine.set_numa_config_from_option(o); sync_cout << "info string " << line << sync_endl;
print_numa_config_information();
print_thread_binding_information();
}); });
options["Threads"] << Option(1, 1, 1024, [this](const Option&) {
engine.resize_threads();
print_thread_binding_information();
});
options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { engine.set_tt_size(o); });
options["Clear Hash"] << Option([this](const Option&) { engine.search_clear(); });
options["Ponder"] << Option(false);
options["MultiPV"] << Option(1, 1, MAX_MOVES);
options["Skill Level"] << Option(20, 0, 20);
options["Move Overhead"] << Option(10, 0, 5000);
options["nodestime"] << Option(0, 0, 10000);
options["UCI_Chess960"] << Option(false);
options["UCI_LimitStrength"] << Option(false);
options["UCI_Elo"] << Option(1320, 1320, 3190);
options["UCI_ShowWDL"] << Option(false);
options["SyzygyPath"] << Option("<empty>", [](const Option& o) { Tablebases::init(o); });
options["SyzygyProbeDepth"] << Option(1, 1, 100);
options["Syzygy50MoveRule"] << Option(true);
options["SyzygyProbeLimit"] << Option(7, 0, 7);
options["EvalFile"] << Option(EvalFileDefaultNameBig,
[this](const Option& o) { engine.load_big_network(o); });
options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall,
[this](const Option& o) { engine.load_small_network(o); });
engine.set_on_iter([](const auto& i) { on_iter(i); }); engine.set_on_iter([](const auto& i) { on_iter(i); });
engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); }); engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); });
engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); }); engine.set_on_update_full(
[this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); });
engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); }); engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); });
engine.load_networks();
engine.resize_threads();
engine.search_clear(); // After threads are up
} }
void UCIEngine::loop() { void UCIEngine::loop() {
std::string token, cmd; std::string token, cmd;
for (int i = 1; i < cli.argc; ++i) for (int i = 1; i < cli.argc; ++i)
@@ -136,8 +102,9 @@ void UCIEngine::loop() {
sync_cout << "id name " << engine_info(true) << "\n" sync_cout << "id name " << engine_info(true) << "\n"
<< engine.get_options() << sync_endl; << engine.get_options() << sync_endl;
print_numa_config_information(); sync_cout << "info string " << engine.numa_config_information_as_string() << sync_endl;
print_thread_binding_information(); sync_cout << "info string " << engine.thread_binding_information_as_string()
<< sync_endl;
sync_cout << "uciok" << sync_endl; sync_cout << "uciok" << sync_endl;
} }
@@ -193,28 +160,6 @@ void UCIEngine::loop() {
} 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
} }
void UCIEngine::print_numa_config_information() const {
auto cfgStr = engine.get_numa_config_as_string();
sync_cout << "info string Available Processors: " << cfgStr << sync_endl;
}
void UCIEngine::print_thread_binding_information() const {
auto boundThreadsByNode = engine.get_bound_thread_count_by_numa_node();
if (!boundThreadsByNode.empty())
{
sync_cout << "info string NUMA Node Thread Binding: ";
bool isFirst = true;
for (auto&& [current, total] : boundThreadsByNode)
{
if (!isFirst)
std::cout << ":";
std::cout << current << "/" << total;
isFirst = false;
}
std::cout << sync_endl;
}
}
Search::LimitsType UCIEngine::parse_limits(std::istream& is) { Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
Search::LimitsType limits; Search::LimitsType limits;
std::string token; std::string token;

View File

@@ -19,10 +19,10 @@
#ifndef UCI_H_INCLUDED #ifndef UCI_H_INCLUDED
#define UCI_H_INCLUDED #define UCI_H_INCLUDED
#include <cstdint>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <cstdint>
#include "engine.h" #include "engine.h"
#include "misc.h" #include "misc.h"
@@ -42,9 +42,6 @@ class UCIEngine {
void loop(); void loop();
void print_numa_config_information() const;
void print_thread_binding_information() const;
static int to_cp(Value v, const Position& pos); static int to_cp(Value v, const Position& pos);
static std::string format_score(const Score& s); static std::string format_score(const Score& s);
static std::string square(Square s); static std::string square(Square s);

View File

@@ -36,6 +36,8 @@ bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
} }
void OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); }
void OptionsMap::setoption(std::istringstream& is) { void OptionsMap::setoption(std::istringstream& is) {
std::string token, name, value; std::string token, name, value;
@@ -57,13 +59,20 @@ void OptionsMap::setoption(std::istringstream& is) {
Option OptionsMap::operator[](const std::string& name) const { Option OptionsMap::operator[](const std::string& name) const {
auto it = options_map.find(name); auto it = options_map.find(name);
return it != options_map.end() ? it->second : Option(); return it != options_map.end() ? it->second : Option(this);
} }
Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; } Option& OptionsMap::operator[](const std::string& name) {
if (!options_map.count(name))
options_map[name] = Option(this);
return options_map[name];
}
std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }
Option::Option(const OptionsMap* map) :
parent(map) {}
Option::Option(const char* v, OnChange f) : Option::Option(const char* v, OnChange f) :
type("string"), type("string"),
min(0), min(0),
@@ -127,11 +136,13 @@ void Option::operator<<(const Option& o) {
static size_t insert_order = 0; static size_t insert_order = 0;
auto p = this->parent;
*this = o; *this = o;
this->parent = p;
idx = insert_order++; idx = insert_order++;
} }
// Updates currentValue and triggers on_change() action. It's up to // Updates currentValue and triggers on_change() action. It's up to
// the GUI to check for option's limits, but we could receive the new value // the GUI to check for option's limits, but we could receive the new value
// from the user by console window, so let's check the bounds anyway. // from the user by console window, so let's check the bounds anyway.
@@ -159,7 +170,12 @@ Option& Option::operator=(const std::string& v) {
currentValue = v; currentValue = v;
if (on_change) if (on_change)
on_change(*this); {
const auto ret = on_change(*this);
if (ret && parent != nullptr && parent->info != nullptr)
parent->info(ret);
}
return *this; return *this;
} }

View File

@@ -23,6 +23,7 @@
#include <functional> #include <functional>
#include <iosfwd> #include <iosfwd>
#include <map> #include <map>
#include <optional>
#include <string> #include <string>
namespace Stockfish { namespace Stockfish {
@@ -31,31 +32,14 @@ struct CaseInsensitiveLess {
bool operator()(const std::string&, const std::string&) const; bool operator()(const std::string&, const std::string&) const;
}; };
class Option; class OptionsMap;
class OptionsMap {
public:
void setoption(std::istringstream&);
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
Option operator[](const std::string&) const;
Option& operator[](const std::string&);
std::size_t count(const std::string&) const;
private:
// The options container is defined as a std::map
using OptionsStore = std::map<std::string, Option, CaseInsensitiveLess>;
OptionsStore options_map;
};
// The Option class implements each option as specified by the UCI protocol // The Option class implements each option as specified by the UCI protocol
class Option { class Option {
public: public:
using OnChange = std::function<void(const Option&)>; using OnChange = std::function<std::optional<std::string>(const Option&)>;
Option(const OptionsMap*);
Option(OnChange = nullptr); Option(OnChange = nullptr);
Option(bool v, OnChange = nullptr); Option(bool v, OnChange = nullptr);
Option(const char* v, OnChange = nullptr); Option(const char* v, OnChange = nullptr);
@@ -63,7 +47,6 @@ class Option {
Option(const char* v, const char* cur, OnChange = nullptr); Option(const char* v, const char* cur, OnChange = nullptr);
Option& operator=(const std::string&); Option& operator=(const std::string&);
void operator<<(const Option&);
operator int() const; operator int() const;
operator std::string() const; operator std::string() const;
bool operator==(const char*) const; bool operator==(const char*) const;
@@ -72,10 +55,49 @@ class Option {
friend std::ostream& operator<<(std::ostream&, const OptionsMap&); friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
private: private:
friend class OptionsMap;
friend class Engine;
friend class Tune;
void operator<<(const Option&);
std::string defaultValue, currentValue, type; std::string defaultValue, currentValue, type;
int min, max; int min, max;
size_t idx; size_t idx;
OnChange on_change; OnChange on_change;
const OptionsMap* parent = nullptr;
};
class OptionsMap {
public:
using InfoListener = std::function<void(std::optional<std::string>)>;
OptionsMap() = default;
OptionsMap(const OptionsMap&) = delete;
OptionsMap(OptionsMap&&) = delete;
OptionsMap& operator=(const OptionsMap&) = delete;
OptionsMap& operator=(OptionsMap&&) = delete;
void add_info_listener(InfoListener&&);
void setoption(std::istringstream&);
Option operator[](const std::string&) const;
Option& operator[](const std::string&);
std::size_t count(const std::string&) const;
private:
friend class Engine;
friend class Option;
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
// The options container is defined as a std::map
using OptionsStore = std::map<std::string, Option, CaseInsensitiveLess>;
OptionsStore options_map;
InfoListener info;
}; };
} }