Merge branch 'trainer' into merge_attempt

This commit is contained in:
Tomasz Sobczyk
2020-09-24 20:45:23 +02:00
47 changed files with 1199 additions and 1165 deletions
+3 -4
View File
@@ -41,7 +41,7 @@ BINDIR = $(PREFIX)/bin
### Built-in benchmark for pgo-builds ### Built-in benchmark for pgo-builds
PGO_TRAINING_DATA_FILE = pgo_training_data.bin PGO_TRAINING_DATA_FILE = pgo_training_data.bin
PGOBENCH = ./$(EXE) bench PGOBENCH = ./$(EXE) bench
PGOGENSFEN = ./$(EXE) gensfen depth 3 loop 1000 output_file_name $(PGO_TRAINING_DATA_FILE) PGOGENSFEN = ./$(EXE) gensfen depth 6 loop 10000 output_file_name $(PGO_TRAINING_DATA_FILE)
### Source and object files ### Source and object files
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
@@ -60,7 +60,6 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp
learn/learn.cpp \ learn/learn.cpp \
learn/gensfen.cpp \ learn/gensfen.cpp \
learn/convert.cpp \ learn/convert.cpp \
learn/learning_tools.cpp \
learn/multi_think.cpp learn/multi_think.cpp
OBJS = $(notdir $(SRCS:.cpp=.o)) OBJS = $(notdir $(SRCS:.cpp=.o))
@@ -747,10 +746,10 @@ endif
config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
clang-profile-use clang-profile-make clang-profile-use clang-profile-make
build: config-sanity net build: config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
profile-build: net config-sanity objclean profileclean profile-build: config-sanity objclean profileclean
@echo "" @echo ""
@echo "Step 1/4. Building instrumented executable ..." @echo "Step 1/4. Building instrumented executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
-22
View File
@@ -1,22 +0,0 @@
#ifndef _EVALUATE_COMMON_H_
#define _EVALUATE_COMMON_H_
// A common header-like function for modern evaluation functions.
#include <string>
namespace Eval
{
// --------------------------
// for learning
// --------------------------
// Save the evaluation function parameters to a file.
// You can specify the extension added to the end of the file.
void save_eval(std::string suffix);
// Get the current eta.
double get_eta();
}
#endif // _EVALUATE_KPPT_COMMON_H_
+26 -53
View File
@@ -36,42 +36,30 @@
#include "uci.h" #include "uci.h"
#include "incbin/incbin.h" #include "incbin/incbin.h"
// Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler).
// This macro invocation will declare the following three variables
// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
// Note that this does not work in Microsof Visual Studio.
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
#else
const unsigned char gEmbeddedNNUEData[1] = {0x0};
const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
const unsigned int gEmbeddedNNUESize = 1;
#endif
using namespace std; using namespace std;
using namespace Eval::NNUE; using namespace Eval::NNUE;
namespace Eval { namespace Eval {
bool useNNUE; UseNNUEMode useNNUE;
string eval_file_loaded = "None"; string eval_file_loaded = "None";
/// NNUE::init() tries to load a nnue network at startup time, or when the engine static UseNNUEMode nnue_mode_from_option(const UCI::Option& mode)
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" {
/// The name of the nnue network is always retrieved from the EvalFile option. if (mode == "false")
/// We search the given network in three locations: internally (the default return UseNNUEMode::False;
/// network may be embedded in the binary), in the active working directory and else if (mode == "true")
/// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY return UseNNUEMode::True;
/// variable to have the engine search in a special directory in their distro. else if (mode == "pure")
return UseNNUEMode::Pure;
return UseNNUEMode::False;
}
void NNUE::init() { void NNUE::init() {
useNNUE = Options["Use NNUE"]; useNNUE = nnue_mode_from_option(Options["Use NNUE"]);
if (!useNNUE) if (useNNUE == UseNNUEMode::False)
return; return;
string eval_file = string(Options["EvalFile"]); string eval_file = string(Options["EvalFile"]);
@@ -79,35 +67,17 @@ namespace Eval {
#if defined(DEFAULT_NNUE_DIRECTORY) #if defined(DEFAULT_NNUE_DIRECTORY)
#define stringify2(x) #x #define stringify2(x) #x
#define stringify(x) stringify2(x) #define stringify(x) stringify2(x)
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; vector<string> dirs = { "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
#else #else
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory }; vector<string> dirs = { "" , CommandLine::binaryDirectory };
#endif #endif
for (string directory : dirs) for (string directory : dirs)
if (eval_file_loaded != eval_file) if (eval_file_loaded != eval_file)
{ {
if (directory != "<internal>") ifstream stream(directory + eval_file, ios::binary);
{ if (load_eval(eval_file, stream))
ifstream stream(directory + eval_file, ios::binary); eval_file_loaded = eval_file;
if (load_eval(eval_file, stream))
eval_file_loaded = eval_file;
}
if (directory == "<internal>" && eval_file == EvalFileDefaultName)
{
// C++ way to prepare a buffer for a memory stream
class MemoryBuffer : public basic_streambuf<char> {
public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
};
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
size_t(gEmbeddedNNUESize));
istream stream(&buffer);
if (load_eval(eval_file, stream))
eval_file_loaded = eval_file;
}
} }
} }
@@ -116,7 +86,7 @@ namespace Eval {
string eval_file = string(Options["EvalFile"]); string eval_file = string(Options["EvalFile"]);
if (useNNUE && eval_file_loaded != eval_file) if (useNNUE != UseNNUEMode::False && eval_file_loaded != eval_file)
{ {
UCI::OptionsMap defaults; UCI::OptionsMap defaults;
UCI::init(defaults); UCI::init(defaults);
@@ -136,7 +106,7 @@ namespace Eval {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if (useNNUE) if (useNNUE != UseNNUEMode::False)
sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
else else
sync_cout << "info string classical evaluation enabled" << sync_endl; sync_cout << "info string classical evaluation enabled" << sync_endl;
@@ -1017,7 +987,10 @@ Value Eval::evaluate(const Position& pos) {
Value v; Value v;
if (!Eval::useNNUE) if (Eval::useNNUE == UseNNUEMode::Pure) {
v = NNUE::evaluate(pos);
}
else if (Eval::useNNUE == UseNNUEMode::False)
v = Evaluation<NO_TRACE>(pos).value(); v = Evaluation<NO_TRACE>(pos).value();
else else
{ {
@@ -1092,7 +1065,7 @@ std::string Eval::trace(const Position& pos) {
ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n"; ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n";
if (Eval::useNNUE) if (useNNUE != UseNNUEMode::False)
{ {
v = NNUE::evaluate(pos); v = NNUE::evaluate(pos);
v = pos.side_to_move() == WHITE ? v : -v; v = pos.side_to_move() == WHITE ? v : -v;
+1 -1
View File
@@ -42,7 +42,7 @@ namespace Eval {
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the // for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile. // name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-03744f8d56d8.nnue" #define EvalFileDefaultName "nn.bin"
namespace NNUE { namespace NNUE {
+2 -5
View File
@@ -8,9 +8,6 @@
#include "position.h" #include "position.h"
#include "tt.h" #include "tt.h"
// evaluate header for learning
#include "eval/evaluate_common.h"
#include "extra/nnue_data_binpack_format.h" #include "extra/nnue_data_binpack_format.h"
#include "syzygy/tbprobe.h" #include "syzygy/tbprobe.h"
@@ -122,7 +119,7 @@ namespace Learner
else if (token == "score") { else if (token == "score") {
double score; double score;
ss >> score; ss >> score;
// Training Formula · Issue #71 · nodchip/Stockfish https://github.com/nodchip/Stockfish/issues/71 // Training Formula ?Issue #71 ?nodchip/Stockfish https://github.com/nodchip/Stockfish/issues/71
// Normalize to [0.0, 1.0]. // Normalize to [0.0, 1.0].
score = (score - src_score_min_value) / (src_score_max_value - src_score_min_value); score = (score - src_score_min_value) / (src_score_max_value - src_score_min_value);
// Scale to [dest_score_min_value, dest_score_max_value]. // Scale to [dest_score_min_value, dest_score_max_value].
@@ -480,7 +477,7 @@ namespace Learner
{ {
if (fs.read((char*)&p, sizeof(PackedSfenValue))) { if (fs.read((char*)&p, sizeof(PackedSfenValue))) {
StateInfo si; StateInfo si;
tpos.set_from_packed_sfen(p.sfen, &si, th, false); tpos.set_from_packed_sfen(p.sfen, &si, th);
// write as plain text // write as plain text
ofs << "fen " << tpos.fen() << std::endl; ofs << "fen " << tpos.fen() << std::endl;
+49 -42
View File
@@ -2,6 +2,7 @@
#include "packed_sfen.h" #include "packed_sfen.h"
#include "multi_think.h" #include "multi_think.h"
#include "../syzygy/tbprobe.h"
#include "misc.h" #include "misc.h"
#include "position.h" #include "position.h"
@@ -9,8 +10,6 @@
#include "tt.h" #include "tt.h"
#include "uci.h" #include "uci.h"
#include "eval/evaluate_common.h"
#include "extra/nnue_data_binpack_format.h" #include "extra/nnue_data_binpack_format.h"
#include "nnue/evaluate_nnue_learner.h" #include "nnue/evaluate_nnue_learner.h"
@@ -48,6 +47,7 @@ namespace Learner
static bool detect_draw_by_consecutive_low_score = false; static bool detect_draw_by_consecutive_low_score = false;
static bool detect_draw_by_insufficient_mating_material = false; static bool detect_draw_by_insufficient_mating_material = false;
static std::vector<std::string> bookStart;
static SfenOutputType sfen_output_type = SfenOutputType::Bin; static SfenOutputType sfen_output_type = SfenOutputType::Bin;
static bool ends_with(const std::string& lhs, const std::string& end) static bool ends_with(const std::string& lhs, const std::string& end)
@@ -392,7 +392,6 @@ namespace Learner
Position& pos, Position& pos,
std::vector<StateInfo, AlignedAllocator<StateInfo>>& states, std::vector<StateInfo, AlignedAllocator<StateInfo>>& states,
int ply, int ply,
int depth,
vector<Move>& pv); vector<Move>& pv);
// Min and max depths for search during gensfen // Min and max depths for search during gensfen
@@ -749,7 +748,6 @@ namespace Learner
Position& pos, Position& pos,
std::vector<StateInfo, AlignedAllocator<StateInfo>>& states, std::vector<StateInfo, AlignedAllocator<StateInfo>>& states,
int ply, int ply,
int depth,
vector<Move>& pv) vector<Move>& pv)
{ {
auto rootColor = pos.side_to_move(); auto rootColor = pos.side_to_move();
@@ -763,15 +761,6 @@ namespace Learner
} }
pos.do_move(m, states[ply++]); pos.do_move(m, states[ply++]);
// Because the difference calculation of evaluate() cannot be
// performed unless each node evaluate() is called!
// If the depth is 8 or more, it seems
// faster not to calculate this difference.
if (depth < 8)
{
Eval::NNUE::update_eval(pos);
}
} }
// Reach leaf // Reach leaf
@@ -828,8 +817,10 @@ namespace Learner
auto th = Threads[thread_id]; auto th = Threads[thread_id];
auto& pos = th->rootPos; auto& pos = th->rootPos;
pos.set(StartFEN, false, &si, th); pos.set(bookStart[prng.rand(bookStart.size())], false, &si, th);
int resign_counter = 0;
bool should_resign = prng.rand(10) > 1;
// Vector for holding the sfens in the current simulated game. // Vector for holding the sfens in the current simulated game.
PSVector a_psv; PSVector a_psv;
a_psv.reserve(write_maxply + MAX_PLY); a_psv.reserve(write_maxply + MAX_PLY);
@@ -871,11 +862,14 @@ namespace Learner
// Also because of this we don't have to check for TB/MATE scores // Also because of this we don't have to check for TB/MATE scores
if (abs(search_value) >= eval_limit) if (abs(search_value) >= eval_limit)
{ {
const auto wdl = (search_value >= eval_limit) ? 1 : -1; resign_counter++;
flush_psv(wdl); if ((should_resign && resign_counter >= 4) || abs(search_value) >= 10000) {
break; flush_psv((search_value >= eval_limit) ? 1 : -1);
break;
}
} else {
resign_counter = 0;
} }
// Verification of a strange move // Verification of a strange move
if (search_pv.size() > 0 if (search_pv.size() > 0
&& (search_pv[0] == MOVE_NONE || search_pv[0] == MOVE_NULL)) && (search_pv[0] == MOVE_NONE || search_pv[0] == MOVE_NULL))
@@ -917,7 +911,6 @@ namespace Learner
auto old_key = hash[hash_index]; auto old_key = hash[hash_index];
if (key == old_key) if (key == old_key)
{ {
a_psv.clear();
goto SKIP_SAVE; goto SKIP_SAVE;
} }
else else
@@ -936,20 +929,7 @@ namespace Learner
// Result is added after the whole game is done. // Result is added after the whole game is done.
pos.sfen_pack(psv.sfen); pos.sfen_pack(psv.sfen);
// Get the value of evaluate() as seen from the psv.score = search_value;
// root color on the leaf node of the PV line.
// I don't know the goodness and badness of using the
// return value of search() as it is.
// TODO: Consider using search value instead of evaluate_leaf.
// Maybe give it as an option.
// Use PV moves to reach the leaf node and use the value
// that evaluated() is called on that leaf node.
const auto leaf_value = evaluate_leaf(pos, states, ply, depth, search_pv);
// If for some reason the leaf node couldn't yield an eval
// we fallback to search value.
psv.score = leaf_value == VALUE_NONE ? search_value : leaf_value;
psv.gamePly = ply; psv.gamePly = ply;
@@ -983,18 +963,11 @@ namespace Learner
{ {
break; break;
} }
// Clear the sfens that were written before the random move.
// (???) why?
a_psv.clear();
} }
// Do move. // Do move.
pos.do_move(next_move, states[ply]); pos.do_move(next_move, states[ply]);
// Call node evaluate() for each difference calculation.
Eval::NNUE::update_eval(pos);
} // for (int ply = 0; ; ++ply) } // for (int ply = 0; ; ++ply)
} // while(!quit) } // while(!quit)
@@ -1154,12 +1127,28 @@ namespace Learner
output_file_name = output_file_name + "_" + to_hex(r.rand<uint64_t>()) + to_hex(r.rand<uint64_t>()); output_file_name = output_file_name + "_" + to_hex(r.rand<uint64_t>()) + to_hex(r.rand<uint64_t>());
} }
bookStart.clear();
{
std::string line;
std::ifstream myfile ("3moves_v2.epd");
if (myfile.is_open())
{
while (getline(myfile,line))
{
bookStart.push_back(line);
}
myfile.close();
} else {
bookStart.push_back(StartFEN);
}
}
std::cout << "gensfen : " << endl std::cout << "gensfen : " << endl
<< " search_depth_min = " << search_depth_min << " to " << search_depth_max << endl << " search_depth_min = " << search_depth_min << " to " << search_depth_max << endl
<< " nodes = " << nodes << endl << " nodes = " << nodes << endl
<< " loop_max = " << loop_max << endl << " loop_max = " << loop_max << endl
<< " eval_limit = " << eval_limit << endl << " eval_limit = " << eval_limit << endl
<< " thread_num (set by USI setoption) = " << thread_num << endl << " thread_num = " << thread_num << endl
<< " bookStart = " << bookStart.size() << endl
<< " random_move_minply = " << random_move_minply << endl << " random_move_minply = " << random_move_minply << endl
<< " random_move_maxply = " << random_move_maxply << endl << " random_move_maxply = " << random_move_maxply << endl
<< " random_move_count = " << random_move_count << endl << " random_move_count = " << random_move_count << endl
@@ -1177,10 +1166,28 @@ namespace Learner
<< " detect_draw_by_insufficient_mating_material = " << detect_draw_by_insufficient_mating_material << endl; << " detect_draw_by_insufficient_mating_material = " << detect_draw_by_insufficient_mating_material << endl;
// Show if the training data generator uses NNUE. // Show if the training data generator uses NNUE.
Eval::verify_NNUE(); Eval::NNUE::verify();
Threads.main()->ponder = false; Threads.main()->ponder = false;
// About Search::Limits
// Be careful because this member variable is global and affects other threads.
{
auto& limits = Search::Limits;
// Make the search equivalent to the "go infinite" command. (Because it is troublesome if time management is done)
limits.infinite = true;
// Since PV is an obstacle when displayed, erase it.
limits.silent = true;
// If you use this, it will be compared with the accumulated nodes of each thread. Therefore, do not use it.
limits.nodes = 0;
// depth is also processed by the one passed as an argument of Learner::search().
limits.depth = 0;
}
// Create and execute threads as many as Options["Threads"]. // Create and execute threads as many as Options["Threads"].
{ {
SfenWriter sfen_writer(output_file_name, thread_num); SfenWriter sfen_writer(output_file_name, thread_num);
+68 -226
View File
@@ -29,8 +29,6 @@
#include "uci.h" #include "uci.h"
#include "search.h" #include "search.h"
#include "eval/evaluate_common.h"
#include "extra/nnue_data_binpack_format.h" #include "extra/nnue_data_binpack_format.h"
#include "nnue/evaluate_nnue_learner.h" #include "nnue/evaluate_nnue_learner.h"
@@ -58,6 +56,7 @@
#include <omp.h> #include <omp.h>
#endif #endif
extern double global_learning_rate;
using namespace std; using namespace std;
@@ -92,12 +91,6 @@ namespace Learner
static double dest_score_min_value = 0.0; static double dest_score_min_value = 0.0;
static double dest_score_max_value = 1.0; static double dest_score_max_value = 1.0;
// Assume teacher signals are the scores of deep searches,
// and convert them into winning probabilities in the trainer.
// Sometimes we want to use the winning probabilities in the training
// data directly. In those cases, we set false to this variable.
static bool convert_teacher_signal_to_winning_probability = true;
// Using stockfish's WDL with win rate model instead of sigmoid // Using stockfish's WDL with win rate model instead of sigmoid
static bool use_wdl = false; static bool use_wdl = false;
@@ -164,14 +157,6 @@ namespace Learner
return ((y2 - y1) / epsilon) / winning_probability_coefficient; return ((y2 - y1) / epsilon) / winning_probability_coefficient;
} }
// A constant used in elmo (WCSC27). Adjustment required.
// Since elmo does not internally divide the expression, the value is different.
// You can set this value with the learn command.
// 0.33 is equivalent to the constant (0.5) used in elmo (WCSC27)
double ELMO_LAMBDA = 0.33;
double ELMO_LAMBDA2 = 0.33;
double ELMO_LAMBDA_LIMIT = 32000;
// Training Formula · Issue #71 · nodchip/Stockfish https://github.com/nodchip/Stockfish/issues/71 // Training Formula · Issue #71 · nodchip/Stockfish https://github.com/nodchip/Stockfish/issues/71
double get_scaled_signal(double signal) double get_scaled_signal(double signal)
{ {
@@ -194,26 +179,7 @@ namespace Learner
double calculate_p(double teacher_signal, int ply) double calculate_p(double teacher_signal, int ply)
{ {
const double scaled_teacher_signal = get_scaled_signal(teacher_signal); const double scaled_teacher_signal = get_scaled_signal(teacher_signal);
return winning_percentage(scaled_teacher_signal, ply);
double p = scaled_teacher_signal;
if (convert_teacher_signal_to_winning_probability)
{
p = winning_percentage(scaled_teacher_signal, ply);
}
return p;
}
double calculate_lambda(double teacher_signal)
{
// If the evaluation value in deep search exceeds ELMO_LAMBDA_LIMIT
// then apply ELMO_LAMBDA2 instead of ELMO_LAMBDA.
const double lambda =
(std::abs(teacher_signal) >= ELMO_LAMBDA_LIMIT)
? ELMO_LAMBDA2
: ELMO_LAMBDA;
return lambda;
} }
double calculate_t(int game_result) double calculate_t(int game_result)
@@ -226,32 +192,6 @@ namespace Learner
return t; return t;
} }
double calc_grad(Value teacher_signal, Value shallow, const PackedSfenValue& psv)
{
// elmo (WCSC27) method
// Correct with the actual game wins and losses.
const double q = winning_percentage(shallow, psv.gamePly);
const double p = calculate_p(teacher_signal, psv.gamePly);
const double t = calculate_t(psv.game_result);
const double lambda = calculate_lambda(teacher_signal);
double grad;
if (use_wdl)
{
const double dce_p = calc_d_cross_entropy_of_winning_percentage(p, shallow, psv.gamePly);
const double dce_t = calc_d_cross_entropy_of_winning_percentage(t, shallow, psv.gamePly);
grad = lambda * dce_p + (1.0 - lambda) * dce_t;
}
else
{
// Use the actual win rate as a correction term.
// This is the idea of elmo (WCSC27), modern O-parts.
grad = lambda * (q - p) + (1.0 - lambda) * (q - t);
}
return grad;
}
// Calculate cross entropy during learning // Calculate cross entropy during learning
// The individual cross entropy of the win/loss term and win // The individual cross entropy of the win/loss term and win
// rate term of the elmo expression is returned // rate term of the elmo expression is returned
@@ -262,21 +202,16 @@ namespace Learner
const PackedSfenValue& psv, const PackedSfenValue& psv,
double& cross_entropy_eval, double& cross_entropy_eval,
double& cross_entropy_win, double& cross_entropy_win,
double& cross_entropy,
double& entropy_eval, double& entropy_eval,
double& entropy_win, double& entropy_win)
double& entropy)
{ {
// Teacher winning probability. // Teacher winning probability.
const double q = winning_percentage(shallow, psv.gamePly); const double q = winning_percentage(shallow, psv.gamePly);
const double p = calculate_p(teacher_signal, psv.gamePly); const double p = calculate_p(teacher_signal, psv.gamePly);
const double t = calculate_t(psv.game_result); const double t = calculate_t(psv.game_result);
const double lambda = calculate_lambda(teacher_signal);
constexpr double epsilon = 0.000001; constexpr double epsilon = 0.000001;
const double m = (1.0 - lambda) * t + lambda * p;
cross_entropy_eval = cross_entropy_eval =
(-p * std::log(q + epsilon) - (1.0 - p) * std::log(1.0 - q + epsilon)); (-p * std::log(q + epsilon) - (1.0 - p) * std::log(1.0 - q + epsilon));
cross_entropy_win = cross_entropy_win =
@@ -285,17 +220,12 @@ namespace Learner
(-p * std::log(p + epsilon) - (1.0 - p) * std::log(1.0 - p + epsilon)); (-p * std::log(p + epsilon) - (1.0 - p) * std::log(1.0 - p + epsilon));
entropy_win = entropy_win =
(-t * std::log(t + epsilon) - (1.0 - t) * std::log(1.0 - t + epsilon)); (-t * std::log(t + epsilon) - (1.0 - t) * std::log(1.0 - t + epsilon));
cross_entropy =
(-m * std::log(q + epsilon) - (1.0 - m) * std::log(1.0 - q + epsilon));
entropy =
(-m * std::log(m + epsilon) - (1.0 - m) * std::log(1.0 - m + epsilon));
} }
// Other objective functions may be considered in the future... // Other objective functions may be considered in the future...
double calc_grad(Value shallow, const PackedSfenValue& psv) double calc_grad(Value shallow, const PackedSfenValue& psv)
{ {
return calc_grad((Value)psv.score, shallow, psv); return (double)(shallow - (Value)psv.score) / 2400.0;
} }
struct BasicSfenInputStream struct BasicSfenInputStream
@@ -787,15 +717,9 @@ namespace Learner
std::atomic<bool> stop_flag; std::atomic<bool> stop_flag;
// Discount rate
double discount_rate;
// Option to exclude early stage from learning // Option to exclude early stage from learning
int reduction_gameply; int reduction_gameply;
// Option not to learn kk/kkp/kpp/kppp
std::array<bool, 4> freeze;
// If the absolute value of the evaluation value of the deep search // If the absolute value of the evaluation value of the deep search
// of the teacher phase exceeds this value, discard the teacher phase. // of the teacher phase exceeds this value, discard the teacher phase.
int eval_limit; int eval_limit;
@@ -825,7 +749,6 @@ namespace Learner
uint64_t eval_save_interval; uint64_t eval_save_interval;
uint64_t loss_output_interval; uint64_t loss_output_interval;
uint64_t mirror_percentage;
// Loss calculation. // Loss calculation.
// done: Number of phases targeted this time // done: Number of phases targeted this time
@@ -849,7 +772,6 @@ namespace Learner
for (size_t i = 0; i < pv.size(); ++i) for (size_t i = 0; i < pv.size(); ++i)
{ {
task_pos.do_move(pv[i], states[i]); task_pos.do_move(pv[i], states[i]);
Eval::NNUE::update_eval(task_pos);
} }
const Value shallow_value = const Value shallow_value =
@@ -870,20 +792,18 @@ namespace Learner
// It doesn't matter if you have disabled the substitution table. // It doesn't matter if you have disabled the substitution table.
TT.new_search(); TT.new_search();
std::cout << "PROGRESS: " << now_string() << ", "; cout << "PROGRESS: " << now_string() << ", ";
std::cout << sr.total_done << " sfens"; cout << sr.total_done << " sfens";
std::cout << ", iteration " << epoch; cout << ", iteration " << epoch;
std::cout << ", eta = " << Eval::get_eta() << ", "; cout << ", learning rate = " << global_learning_rate << ", ";
// For calculation of verification data loss // For calculation of verification data loss
atomic<double> test_sum_cross_entropy_eval, test_sum_cross_entropy_win, test_sum_cross_entropy; atomic<double> test_sum_cross_entropy_eval, test_sum_cross_entropy_win;
atomic<double> test_sum_entropy_eval, test_sum_entropy_win, test_sum_entropy; atomic<double> test_sum_entropy_eval, test_sum_entropy_win;
test_sum_cross_entropy_eval = 0; test_sum_cross_entropy_eval = 0;
test_sum_cross_entropy_win = 0; test_sum_cross_entropy_win = 0;
test_sum_cross_entropy = 0;
test_sum_entropy_eval = 0; test_sum_entropy_eval = 0;
test_sum_entropy_win = 0; test_sum_entropy_win = 0;
test_sum_entropy = 0;
// norm for learning // norm for learning
atomic<double> sum_norm; atomic<double> sum_norm;
@@ -899,7 +819,7 @@ namespace Learner
auto& pos = th->rootPos; auto& pos = th->rootPos;
StateInfo si; StateInfo si;
pos.set(StartFEN, false, &si, th); pos.set(StartFEN, false, &si, th);
std::cout << "hirate eval = " << Eval::evaluate(pos); cout << "hirate eval = " << Eval::evaluate(pos) << endl;
// It's better to parallelize here, but it's a bit // It's better to parallelize here, but it's a bit
// troublesome because the search before slave has not finished. // troublesome because the search before slave has not finished.
@@ -923,10 +843,8 @@ namespace Learner
&ps, &ps,
&test_sum_cross_entropy_eval, &test_sum_cross_entropy_eval,
&test_sum_cross_entropy_win, &test_sum_cross_entropy_win,
&test_sum_cross_entropy,
&test_sum_entropy_eval, &test_sum_entropy_eval,
&test_sum_entropy_win, &test_sum_entropy_win,
&test_sum_entropy,
&sum_norm, &sum_norm,
&task_count, &task_count,
&move_accord_count &move_accord_count
@@ -954,26 +872,22 @@ namespace Learner
// For the time being, regarding the win rate and loss terms only in the elmo method // For the time being, regarding the win rate and loss terms only in the elmo method
// Calculate and display the cross entropy. // Calculate and display the cross entropy.
double test_cross_entropy_eval, test_cross_entropy_win, test_cross_entropy; double test_cross_entropy_eval, test_cross_entropy_win;
double test_entropy_eval, test_entropy_win, test_entropy; double test_entropy_eval, test_entropy_win;
calc_cross_entropy( calc_cross_entropy(
deep_value, deep_value,
shallow_value, shallow_value,
ps, ps,
test_cross_entropy_eval, test_cross_entropy_eval,
test_cross_entropy_win, test_cross_entropy_win,
test_cross_entropy,
test_entropy_eval, test_entropy_eval,
test_entropy_win, test_entropy_win);
test_entropy);
// The total cross entropy need not be abs() by definition. // The total cross entropy need not be abs() by definition.
test_sum_cross_entropy_eval += test_cross_entropy_eval; test_sum_cross_entropy_eval += test_cross_entropy_eval;
test_sum_cross_entropy_win += test_cross_entropy_win; test_sum_cross_entropy_win += test_cross_entropy_win;
test_sum_cross_entropy += test_cross_entropy;
test_sum_entropy_eval += test_entropy_eval; test_sum_entropy_eval += test_entropy_eval;
test_sum_entropy_win += test_entropy_win; test_sum_entropy_win += test_entropy_win;
test_sum_entropy += test_entropy;
sum_norm += (double)abs(shallow_value); sum_norm += (double)abs(shallow_value);
// Determine if the teacher's move and the score of the shallow search match // Determine if the teacher's move and the score of the shallow search match
@@ -998,7 +912,7 @@ namespace Learner
while (task_count) while (task_count)
sleep(1); sleep(1);
latest_loss_sum += test_sum_cross_entropy - test_sum_entropy; latest_loss_sum += test_sum_cross_entropy_eval - test_sum_entropy_eval;
latest_loss_count += sr.sfen_for_mse.size(); latest_loss_count += sr.sfen_for_mse.size();
// learn_cross_entropy may be called train cross // learn_cross_entropy may be called train cross
@@ -1008,27 +922,24 @@ namespace Learner
if (sr.sfen_for_mse.size() && done) if (sr.sfen_for_mse.size() && done)
{ {
cout cout << "INFO: "
<< " , test_cross_entropy_eval = " << test_sum_cross_entropy_eval / sr.sfen_for_mse.size() << "test_cross_entropy_eval = " << test_sum_cross_entropy_eval / sr.sfen_for_mse.size()
<< " , test_cross_entropy_win = " << test_sum_cross_entropy_win / sr.sfen_for_mse.size() << " , test_cross_entropy_win = " << test_sum_cross_entropy_win / sr.sfen_for_mse.size()
<< " , test_entropy_eval = " << test_sum_entropy_eval / sr.sfen_for_mse.size() << " , test_entropy_eval = " << test_sum_entropy_eval / sr.sfen_for_mse.size()
<< " , test_entropy_win = " << test_sum_entropy_win / sr.sfen_for_mse.size() << " , test_entropy_win = " << test_sum_entropy_win / sr.sfen_for_mse.size()
<< " , test_cross_entropy = " << test_sum_cross_entropy / sr.sfen_for_mse.size()
<< " , test_entropy = " << test_sum_entropy / sr.sfen_for_mse.size()
<< " , norm = " << sum_norm << " , norm = " << sum_norm
<< " , move accuracy = " << (move_accord_count * 100.0 / sr.sfen_for_mse.size()) << "%"; << " , move accuracy = " << (move_accord_count * 100.0 / sr.sfen_for_mse.size()) << "%"
<< endl;
if (done != static_cast<uint64_t>(-1)) if (done != static_cast<uint64_t>(-1))
{ {
cout cout << "INFO: "
<< " , learn_cross_entropy_eval = " << learn_sum_cross_entropy_eval / done << "learn_cross_entropy_eval = " << learn_sum_cross_entropy_eval / done
<< " , learn_cross_entropy_win = " << learn_sum_cross_entropy_win / done << " , learn_cross_entropy_win = " << learn_sum_cross_entropy_win / done
<< " , learn_entropy_eval = " << learn_sum_entropy_eval / done << " , learn_entropy_eval = " << learn_sum_entropy_eval / done
<< " , learn_entropy_win = " << learn_sum_entropy_win / done << " , learn_entropy_win = " << learn_sum_entropy_win / done
<< " , learn_cross_entropy = " << learn_sum_cross_entropy / done << endl;
<< " , learn_entropy = " << learn_sum_entropy / done;
} }
cout << endl;
} }
else else
{ {
@@ -1038,10 +949,8 @@ namespace Learner
// Clear 0 for next time. // Clear 0 for next time.
learn_sum_cross_entropy_eval = 0.0; learn_sum_cross_entropy_eval = 0.0;
learn_sum_cross_entropy_win = 0.0; learn_sum_cross_entropy_win = 0.0;
learn_sum_cross_entropy = 0.0;
learn_sum_entropy_eval = 0.0; learn_sum_entropy_eval = 0.0;
learn_sum_entropy_win = 0.0; learn_sum_entropy_win = 0.0;
learn_sum_entropy = 0.0;
} }
void LearnerThink::thread_worker(size_t thread_id) void LearnerThink::thread_worker(size_t thread_id)
@@ -1058,7 +967,7 @@ namespace Learner
// display mse (this is sometimes done only for thread 0) // display mse (this is sometimes done only for thread 0)
// Immediately after being read from the file... // Immediately after being read from the file...
// Lock the evaluation function so that it is not used during updating. // Lock the evaluation function so that it is not used during updating.
shared_lock<shared_timed_mutex> read_lock(nn_mutex, defer_lock); shared_lock<shared_timed_mutex> read_lock(nn_mutex, defer_lock);
if (sr.next_update_weights <= sr.total_done || if (sr.next_update_weights <= sr.total_done ||
(thread_id != 0 && !read_lock.try_lock())) (thread_id != 0 && !read_lock.try_lock()))
@@ -1090,7 +999,7 @@ namespace Learner
// Lock the evaluation function so that it is not used during updating. // Lock the evaluation function so that it is not used during updating.
lock_guard<shared_timed_mutex> write_lock(nn_mutex); lock_guard<shared_timed_mutex> write_lock(nn_mutex);
Eval::NNUE::UpdateParameters(epoch); Eval::NNUE::UpdateParameters();
} }
++epoch; ++epoch;
@@ -1167,8 +1076,7 @@ namespace Learner
goto RETRY_READ; goto RETRY_READ;
StateInfo si; StateInfo si;
const bool mirror = prng.rand(100) < mirror_percentage; if (pos.set_from_packed_sfen(ps.sfen, &si, th) != 0)
if (pos.set_from_packed_sfen(ps.sfen, &si, th, mirror) != 0)
{ {
// I got a strange sfen. Should be debugged! // I got a strange sfen. Should be debugged!
// Since it is an illegal sfen, it may not be // Since it is an illegal sfen, it may not be
@@ -1177,18 +1085,30 @@ namespace Learner
goto RETRY_READ; goto RETRY_READ;
} }
// There is a possibility that all the pieces are blocked and stuck.
// Also, the declaration win phase is excluded from
// learning because you cannot go to leaf with PV moves.
// (shouldn't write out such teacher aspect itself,
// but may have written it out with an old generation routine)
// Skip the position if there are no legal moves (=checkmated or stalemate).
if (MoveList<LEGAL>(pos).size() == 0)
goto RETRY_READ;
// I can read it, so try displaying it. // I can read it, so try displaying it.
// cout << pos << value << endl; // cout << pos << value << endl;
const auto rootColor = pos.side_to_move();
int ply = 0;
StateInfo state[MAX_PLY]; // PV of qsearch cannot be so long.
if (!pos.pseudo_legal((Move)ps.move) || !pos.legal((Move)ps.move))
{
goto RETRY_READ;
}
pos.do_move((Move)ps.move, state[ply++]);
// There is a possibility that all the pieces are blocked and stuck.
// Also, the declaration win phase is excluded from
// learning because you cannot go to leaf with PV moves.
// (shouldn't write out such teacher aspect itself,
// but may have written it out with an old generation routine)
// Skip the position if there are no legal moves (=checkmated or stalemate).
if (MoveList<LEGAL>(pos).size() == 0)
goto RETRY_READ;
// Evaluation value of shallow search (qsearch) // Evaluation value of shallow search (qsearch)
const auto [_, pv] = qsearch(pos); const auto [_, pv] = qsearch(pos);
@@ -1199,13 +1119,11 @@ namespace Learner
// Go to the leaf node as it is, add only to the gradient array, // Go to the leaf node as it is, add only to the gradient array,
// and later try AdaGrad at the time of rmse aggregation. // and later try AdaGrad at the time of rmse aggregation.
const auto rootColor = pos.side_to_move();
// If the initial PV is different, it is better not to use it for learning. // If the initial PV is different, it is better not to use it for learning.
// If it is the result of searching a completely different place, it may become noise. // If it is the result of searching a completely different place, it may become noise.
// It may be better not to study where the difference in evaluation values is too large. // It may be better not to study where the difference in evaluation values is too large.
int ply = 0;
// A helper function that adds the gradient to the current phase. // A helper function that adds the gradient to the current phase.
auto pos_add_grad = [&]() { auto pos_add_grad = [&]() {
@@ -1224,35 +1142,28 @@ namespace Learner
: -Eval::evaluate(pos); : -Eval::evaluate(pos);
// Calculate loss for training data // Calculate loss for training data
double learn_cross_entropy_eval, learn_cross_entropy_win, learn_cross_entropy; double learn_cross_entropy_eval, learn_cross_entropy_win;
double learn_entropy_eval, learn_entropy_win, learn_entropy; double learn_entropy_eval, learn_entropy_win;
calc_cross_entropy( calc_cross_entropy(
deep_value, deep_value,
shallow_value, shallow_value,
ps, ps,
learn_cross_entropy_eval, learn_cross_entropy_eval,
learn_cross_entropy_win, learn_cross_entropy_win,
learn_cross_entropy,
learn_entropy_eval, learn_entropy_eval,
learn_entropy_win, learn_entropy_win);
learn_entropy);
learn_sum_cross_entropy_eval += learn_cross_entropy_eval; learn_sum_cross_entropy_eval += learn_cross_entropy_eval;
learn_sum_cross_entropy_win += learn_cross_entropy_win; learn_sum_cross_entropy_win += learn_cross_entropy_win;
learn_sum_cross_entropy += learn_cross_entropy;
learn_sum_entropy_eval += learn_entropy_eval; learn_sum_entropy_eval += learn_entropy_eval;
learn_sum_entropy_win += learn_entropy_win; learn_sum_entropy_win += learn_entropy_win;
learn_sum_entropy += learn_entropy;
const double example_weight = Eval::NNUE::AddExample(pos, rootColor, ps, 1.0);
(discount_rate != 0 && ply != (int)pv.size()) ? discount_rate : 1.0;
Eval::NNUE::AddExample(pos, rootColor, ps, example_weight);
// Since the processing is completed, the counter of the processed number is incremented // Since the processing is completed, the counter of the processed number is incremented
sr.total_done++; sr.total_done++;
}; };
StateInfo state[MAX_PLY]; // PV of qsearch cannot be so long.
bool illegal_move = false; bool illegal_move = false;
for (auto m : pv) for (auto m : pv)
{ {
@@ -1266,29 +1177,16 @@ namespace Learner
break; break;
} }
// Processing when adding the gradient to the node on each PV.
//If discount_rate is 0, this process is not performed.
if (discount_rate != 0)
pos_add_grad();
pos.do_move(m, state[ply++]); pos.do_move(m, state[ply++]);
// Since the value of evaluate in leaf is used, the difference is updated.
Eval::NNUE::update_eval(pos);
} }
if (illegal_move) if (illegal_move)
{ {
sync_cout << "An illegal move was detected... Excluded the position from the learning data..." << sync_endl; goto RETRY_READ;
continue;
} }
// Since we have reached the end phase of PV, add the slope here. // Since we have reached the end phase of PV, add the slope here.
pos_add_grad(); pos_add_grad();
// rewind the phase
for (auto it = pv.rbegin(); it != pv.rend(); ++it)
pos.undo_move(*it);
} }
} }
@@ -1303,18 +1201,18 @@ namespace Learner
{ {
// When EVAL_SAVE_ONLY_ONCE is defined, // When EVAL_SAVE_ONLY_ONCE is defined,
// Do not dig a subfolder because I want to save it only once. // Do not dig a subfolder because I want to save it only once.
Eval::save_eval(""); Eval::NNUE::save_eval("");
} }
else if (is_final) else if (is_final)
{ {
Eval::save_eval("final"); Eval::NNUE::save_eval("final");
return true; return true;
} }
else else
{ {
static int dir_number = 0; static int dir_number = 0;
const std::string dir_name = std::to_string(dir_number++); const std::string dir_name = std::to_string(dir_number++);
Eval::save_eval(dir_name); Eval::NNUE::save_eval(dir_name);
if (newbob_decay != 1.0 && latest_loss_count > 0) { if (newbob_decay != 1.0 && latest_loss_count > 0) {
static int trials = newbob_num_trials; static int trials = newbob_num_trials;
@@ -1332,25 +1230,17 @@ namespace Learner
else else
{ {
cout << " >= best (" << best_loss << "), rejected" << endl; cout << " >= best (" << best_loss << "), rejected" << endl;
if (best_nn_directory.empty()) best_nn_directory = Path::Combine((std::string)Options["EvalSaveDir"], dir_name);
{
cout << "WARNING: no improvement from initial model" << endl;
}
else
{
cout << "restoring parameters from " << best_nn_directory << endl;
Eval::NNUE::RestoreParameters(best_nn_directory);
}
if (--trials > 0 && !is_final) if (--trials > 0 && !is_final)
{ {
cout cout
<< "reducing learning rate scale from " << newbob_scale << "reducing learning rate from " << newbob_scale
<< " to " << (newbob_scale * newbob_decay) << " to " << (newbob_scale * newbob_decay)
<< " (" << trials << " more trials)" << endl; << " (" << trials << " more trials)" << endl;
newbob_scale *= newbob_decay; newbob_scale *= newbob_decay;
Eval::NNUE::SetGlobalLearningRateScale(newbob_scale); global_learning_rate = newbob_scale;
} }
} }
@@ -1628,13 +1518,6 @@ namespace Learner
string target_dir; string target_dir;
// If 0, it will be the default value.
double eta1 = 0.0;
double eta2 = 0.0;
double eta3 = 0.0;
uint64_t eta1_epoch = 0; // eta2 is not applied by default
uint64_t eta2_epoch = 0; // eta3 is not applied by default
// --- Function that only shuffles the teacher aspect // --- Function that only shuffles the teacher aspect
// normal shuffle // normal shuffle
@@ -1675,24 +1558,13 @@ namespace Learner
// Turn on if you want to pass a pre-shuffled file. // Turn on if you want to pass a pre-shuffled file.
bool no_shuffle = false; bool no_shuffle = false;
// elmo lambda global_learning_rate = 1.0;
ELMO_LAMBDA = 0.33;
ELMO_LAMBDA2 = 0.33;
ELMO_LAMBDA_LIMIT = 32000;
// Discount rate. If this is set to a value other than 0,
// the slope will be added even at other than the PV termination.
// (At that time, apply this discount rate)
double discount_rate = 0;
// if (gamePly <rand(reduction_gameply)) continue; // if (gamePly <rand(reduction_gameply)) continue;
// An option to exclude the early stage from the learning target moderately like // An option to exclude the early stage from the learning target moderately like
// If set to 1, rand(1)==0, so nothing is excluded. // If set to 1, rand(1)==0, so nothing is excluded.
int reduction_gameply = 1; int reduction_gameply = 1;
// Optional item that does not let you learn KK/KKP/KPP/KPPP
array<bool, 4> freeze = {};
uint64_t nn_batch_size = 1000; uint64_t nn_batch_size = 1000;
double newbob_decay = 1.0; double newbob_decay = 1.0;
int newbob_num_trials = 2; int newbob_num_trials = 2;
@@ -1700,7 +1572,6 @@ namespace Learner
uint64_t eval_save_interval = LEARN_EVAL_SAVE_INTERVAL; uint64_t eval_save_interval = LEARN_EVAL_SAVE_INTERVAL;
uint64_t loss_output_interval = 0; uint64_t loss_output_interval = 0;
uint64_t mirror_percentage = 0;
string validation_set_file_name; string validation_set_file_name;
string seed; string seed;
@@ -1734,12 +1605,7 @@ namespace Learner
else if (option == "batchsize") is >> mini_batch_size; else if (option == "batchsize") is >> mini_batch_size;
// learning rate // learning rate
else if (option == "eta") is >> eta1; else if (option == "lr") is >> global_learning_rate;
else if (option == "eta1") is >> eta1; // alias
else if (option == "eta2") is >> eta2;
else if (option == "eta3") is >> eta3;
else if (option == "eta1_epoch") is >> eta1_epoch;
else if (option == "eta2_epoch") is >> eta2_epoch;
// Accept also the old option name. // Accept also the old option name.
else if (option == "use_draw_in_training" else if (option == "use_draw_in_training"
@@ -1758,22 +1624,9 @@ namespace Learner
else if (option == "winning_probability_coefficient") is >> winning_probability_coefficient; else if (option == "winning_probability_coefficient") is >> winning_probability_coefficient;
// Discount rate
else if (option == "discount_rate") is >> discount_rate;
// Using WDL with win rate model instead of sigmoid // Using WDL with win rate model instead of sigmoid
else if (option == "use_wdl") is >> use_wdl; else if (option == "use_wdl") is >> use_wdl;
// No learning of KK/KKP/KPP/KPPP.
else if (option == "freeze_kk") is >> freeze[0];
else if (option == "freeze_kkp") is >> freeze[1];
else if (option == "freeze_kpp") is >> freeze[2];
// LAMBDA
else if (option == "lambda") is >> ELMO_LAMBDA;
else if (option == "lambda2") is >> ELMO_LAMBDA2;
else if (option == "lambda_limit") is >> ELMO_LAMBDA_LIMIT;
else if (option == "reduction_gameply") is >> reduction_gameply; else if (option == "reduction_gameply") is >> reduction_gameply;
// shuffle related // shuffle related
@@ -1794,7 +1647,6 @@ namespace Learner
else if (option == "eval_save_interval") is >> eval_save_interval; else if (option == "eval_save_interval") is >> eval_save_interval;
else if (option == "loss_output_interval") is >> loss_output_interval; else if (option == "loss_output_interval") is >> loss_output_interval;
else if (option == "mirror_percentage") is >> mirror_percentage;
else if (option == "validation_set_file_name") is >> validation_set_file_name; else if (option == "validation_set_file_name") is >> validation_set_file_name;
// Rabbit convert related // Rabbit convert related
@@ -1810,7 +1662,6 @@ namespace Learner
else if (option == "src_score_max_value") is >> src_score_max_value; else if (option == "src_score_max_value") is >> src_score_max_value;
else if (option == "dest_score_min_value") is >> dest_score_min_value; else if (option == "dest_score_min_value") is >> dest_score_min_value;
else if (option == "dest_score_max_value") is >> dest_score_max_value; else if (option == "dest_score_max_value") is >> dest_score_max_value;
else if (option == "convert_teacher_signal_to_winning_probability") is >> convert_teacher_signal_to_winning_probability;
else if (option == "seed") is >> seed; else if (option == "seed") is >> seed;
// Otherwise, it's a filename. // Otherwise, it's a filename.
else else
@@ -1884,7 +1735,7 @@ namespace Learner
if (use_convert_plain) if (use_convert_plain)
{ {
Eval::init_NNUE(); Eval::NNUE::init();
cout << "convert_plain.." << endl; cout << "convert_plain.." << endl;
convert_plain(filenames, output_file_name); convert_plain(filenames, output_file_name);
return; return;
@@ -1892,7 +1743,7 @@ namespace Learner
if (use_convert_bin) if (use_convert_bin)
{ {
Eval::init_NNUE(); Eval::NNUE::init();
cout << "convert_bin.." << endl; cout << "convert_bin.." << endl;
convert_bin( convert_bin(
filenames, filenames,
@@ -1913,7 +1764,7 @@ namespace Learner
if (use_convert_bin_from_pgn_extract) if (use_convert_bin_from_pgn_extract)
{ {
Eval::init_NNUE(); Eval::NNUE::init();
cout << "convert_bin_from_pgn-extract.." << endl; cout << "convert_bin_from_pgn-extract.." << endl;
convert_bin_from_pgn_extract( convert_bin_from_pgn_extract(
filenames, filenames,
@@ -1946,8 +1797,7 @@ namespace Learner
cout << "nn_batch_size : " << nn_batch_size << endl; cout << "nn_batch_size : " << nn_batch_size << endl;
cout << "nn_options : " << nn_options << endl; cout << "nn_options : " << nn_options << endl;
cout << "learning rate : " << eta1 << " , " << eta2 << " , " << eta3 << endl; cout << "learning rate : " << global_learning_rate << endl;
cout << "eta_epoch : " << eta1_epoch << " , " << eta2_epoch << endl;
cout << "use_draw_games_in_training : " << use_draw_games_in_training << endl; cout << "use_draw_games_in_training : " << use_draw_games_in_training << endl;
cout << "use_draw_games_in_validation : " << use_draw_games_in_validation << endl; cout << "use_draw_games_in_validation : " << use_draw_games_in_validation << endl;
cout << "skip_duplicated_positions_in_training : " << skip_duplicated_positions_in_training << endl; cout << "skip_duplicated_positions_in_training : " << skip_duplicated_positions_in_training << endl;
@@ -1960,17 +1810,10 @@ namespace Learner
cout << "scheduling : default" << endl; cout << "scheduling : default" << endl;
} }
cout << "discount rate : " << discount_rate << endl;
// If reduction_gameply is set to 0, rand(0) will be divided by 0, so correct it to 1. // If reduction_gameply is set to 0, rand(0) will be divided by 0, so correct it to 1.
reduction_gameply = max(reduction_gameply, 1); reduction_gameply = max(reduction_gameply, 1);
cout << "reduction_gameply : " << reduction_gameply << endl; cout << "reduction_gameply : " << reduction_gameply << endl;
cout << "LAMBDA : " << ELMO_LAMBDA << endl;
cout << "LAMBDA2 : " << ELMO_LAMBDA2 << endl;
cout << "LAMBDA_LIMIT : " << ELMO_LAMBDA_LIMIT << endl;
cout << "mirror_percentage : " << mirror_percentage << endl;
cout << "eval_save_interval : " << eval_save_interval << " sfens" << endl; cout << "eval_save_interval : " << eval_save_interval << " sfens" << endl;
cout << "loss_output_interval: " << loss_output_interval << " sfens" << endl; cout << "loss_output_interval: " << loss_output_interval << " sfens" << endl;
@@ -1981,7 +1824,7 @@ namespace Learner
cout << "init.." << endl; cout << "init.." << endl;
// Read evaluation function parameters // Read evaluation function parameters
Eval::init_NNUE(); Eval::NNUE::init();
Threads.main()->ponder = false; Threads.main()->ponder = false;
@@ -2004,12 +1847,12 @@ namespace Learner
} }
cout << "init_training.." << endl; cout << "init_training.." << endl;
Eval::NNUE::InitializeTraining(eta1, eta1_epoch, eta2, eta2_epoch, eta3); Eval::NNUE::InitializeTraining(seed);
Eval::NNUE::SetBatchSize(nn_batch_size); Eval::NNUE::SetBatchSize(nn_batch_size);
Eval::NNUE::SetOptions(nn_options); Eval::NNUE::SetOptions(nn_options);
if (newbob_decay != 1.0 && !Options["SkipLoadingEval"]) { if (newbob_decay != 1.0 && !Options["SkipLoadingEval"]) {
// Save the current net to [EvalSaveDir]\original. // Save the current net to [EvalSaveDir]\original.
Eval::save_eval("original"); Eval::NNUE::save_eval("original");
// Set the folder above to best_nn_directory so that the trainer can // Set the folder above to best_nn_directory so that the trainer can
// resotre the network parameters from the original net file. // resotre the network parameters from the original net file.
@@ -2020,11 +1863,9 @@ namespace Learner
cout << "init done." << endl; cout << "init done." << endl;
// Reflect other option settings. // Reflect other option settings.
learn_think.discount_rate = discount_rate;
learn_think.eval_limit = eval_limit; learn_think.eval_limit = eval_limit;
learn_think.save_only_once = save_only_once; learn_think.save_only_once = save_only_once;
learn_think.sr.no_shuffle = no_shuffle; learn_think.sr.no_shuffle = no_shuffle;
learn_think.freeze = freeze;
learn_think.reduction_gameply = reduction_gameply; learn_think.reduction_gameply = reduction_gameply;
learn_think.newbob_scale = 1.0; learn_think.newbob_scale = 1.0;
@@ -2033,7 +1874,6 @@ namespace Learner
learn_think.eval_save_interval = eval_save_interval; learn_think.eval_save_interval = eval_save_interval;
learn_think.loss_output_interval = loss_output_interval; learn_think.loss_output_interval = loss_output_interval;
learn_think.mirror_percentage = mirror_percentage;
// Start a thread that loads the phase file in the background // Start a thread that loads the phase file in the background
// (If this is not started, mse cannot be calculated.) // (If this is not started, mse cannot be calculated.)
@@ -2069,6 +1909,8 @@ namespace Learner
// Start learning. // Start learning.
learn_think.go_think(); learn_think.go_think();
Eval::NNUE::FinalizeNet();
// Save once at the end. // Save once at the end.
learn_think.save(true); learn_think.save(true);
} }
+1 -5
View File
@@ -23,11 +23,7 @@ using LearnFloatType = float;
// configure // configure
// ====================== // ======================
// ---------------------- #define LOSS_FUNCTION "cross_entropy_eval"
// Learning with the method of elmo (WCSC27)
// ----------------------
#define LOSS_FUNCTION "ELMO_METHOD(WCSC27)"
// ---------------------- // ----------------------
// Definition of struct used in Learner // Definition of struct used in Learner
-18
View File
@@ -1,18 +0,0 @@
#include "learning_tools.h"
#include "misc.h"
using namespace Eval;
namespace EvalLearningTools
{
// --- static variables
double Weight::eta;
double Weight::eta1;
double Weight::eta2;
double Weight::eta3;
uint64_t Weight::eta1_epoch;
uint64_t Weight::eta2_epoch;
}
-99
View File
@@ -1,99 +0,0 @@
#ifndef __LEARN_WEIGHT_H__
#define __LEARN_WEIGHT_H__
// A set of machine learning tools related to the weight array used for machine learning of evaluation functions
#include "learn.h"
#include "misc.h" // PRNG , my_insertion_sort
#include <array>
#include <cmath> // std::sqrt()
namespace EvalLearningTools
{
// -------------------------------------------------
// Array for learning that stores gradients etc.
// -------------------------------------------------
#if defined(_MSC_VER)
#pragma pack(push,2)
#elif defined(__GNUC__)
#pragma pack(2)
#endif
struct Weight
{
// cumulative value of one mini-batch gradient
LearnFloatType g = LearnFloatType(0);
// Learning rate η(eta) such as AdaGrad.
// It is assumed that eta1,2,3,eta1_epoch,eta2_epoch have been set by the time updateFV() is called.
// The epoch of update_weights() gradually changes from eta1 to eta2 until eta1_epoch.
// After eta2_epoch, gradually change from eta2 to eta3.
static double eta;
static double eta1;
static double eta2;
static double eta3;
static uint64_t eta1_epoch;
static uint64_t eta2_epoch;
// Batch initialization of eta. If 0 is passed, the default value will be set.
static void init_eta(double new_eta1, double new_eta2, double new_eta3,
uint64_t new_eta1_epoch, uint64_t new_eta2_epoch)
{
Weight::eta1 = (new_eta1 != 0) ? new_eta1 : 30.0;
Weight::eta2 = (new_eta2 != 0) ? new_eta2 : 30.0;
Weight::eta3 = (new_eta3 != 0) ? new_eta3 : 30.0;
Weight::eta1_epoch = (new_eta1_epoch != 0) ? new_eta1_epoch : 0;
Weight::eta2_epoch = (new_eta2_epoch != 0) ? new_eta2_epoch : 0;
}
// Set eta according to epoch.
static void calc_eta(uint64_t epoch)
{
if (Weight::eta1_epoch == 0) // Exclude eta2
Weight::eta = Weight::eta1;
else if (epoch < Weight::eta1_epoch)
// apportion
Weight::eta = Weight::eta1 + (Weight::eta2 - Weight::eta1) * epoch / Weight::eta1_epoch;
else if (Weight::eta2_epoch == 0) // Exclude eta3
Weight::eta = Weight::eta2;
else if (epoch < Weight::eta2_epoch)
Weight::eta = Weight::eta2 + (Weight::eta3 - Weight::eta2) * (epoch - Weight::eta1_epoch) / (Weight::eta2_epoch - Weight::eta1_epoch);
else
Weight::eta = Weight::eta3;
}
template <typename T> void updateFV(T& v) { updateFV(v, 1.0); }
// grad setting
template <typename T> void set_grad(const T& g_) { g = g_; }
// Add grad
template <typename T> void add_grad(const T& g_) { g += g_; }
LearnFloatType get_grad() const { return g; }
};
#if defined(_MSC_VER)
#pragma pack(pop)
#elif defined(__GNUC__)
#pragma pack(0)
#endif
// Turned weight array
// In order to be able to handle it transparently, let's have the same member as Weight.
struct Weight2
{
Weight w[2];
//Evaluate your turn, eta 1/8.
template <typename T> void updateFV(std::array<T, 2>& v) { w[0].updateFV(v[0] , 1.0); w[1].updateFV(v[1],1.0/8.0); }
template <typename T> void set_grad(const std::array<T, 2>& g) { for (int i = 0; i<2; ++i) w[i].set_grad(g[i]); }
template <typename T> void add_grad(const std::array<T, 2>& g) { for (int i = 0; i<2; ++i) w[i].add_grad(g[i]); }
std::array<LearnFloatType, 2> get_grad() const { return std::array<LearnFloatType, 2>{w[0].get_grad(), w[1].get_grad()}; }
};
}
#endif
+1 -32
View File
@@ -9,39 +9,14 @@
void MultiThink::go_think() void MultiThink::go_think()
{ {
// Keep a copy to restore the Options settings later.
auto oldOptions = Options;
// When using the constant track, it takes a lot of time to perform on the fly & the part to access the file is
// Since it is not thread safe, it is guaranteed here that it is being completely read in memory.
Options["BookOnTheFly"] = std::string("false");
// Read evaluation function, etc. // Read evaluation function, etc.
// In the case of the learn command, the value of the evaluation function may be corrected after reading the evaluation function, so // In the case of the learn command, the value of the evaluation function may be corrected after reading the evaluation function, so
// Skip memory corruption check. // Skip memory corruption check.
Eval::init_NNUE(); Eval::NNUE::init();
// Call the derived class's init(). // Call the derived class's init().
init(); init();
// About Search::Limits
// Be careful because this member variable is global and affects other threads.
{
auto& limits = Search::Limits;
// Make the search equivalent to the "go infinite" command. (Because it is troublesome if time management is done)
limits.infinite = true;
// Since PV is an obstacle when displayed, erase it.
limits.silent = true;
// If you use this, it will be compared with the accumulated nodes of each thread. Therefore, do not use it.
limits.nodes = 0;
// depth is also processed by the one passed as an argument of Learner::search().
limits.depth = 0;
}
// The loop upper limit is set with set_loop_max(). // The loop upper limit is set with set_loop_max().
loop_count = 0; loop_count = 0;
done_count = 0; done_count = 0;
@@ -123,10 +98,4 @@ void MultiThink::go_think()
// The file writing thread etc. are still running only when all threads are finished // The file writing thread etc. are still running only when all threads are finished
// Since the work itself may not have completed, output only that all threads have finished. // Since the work itself may not have completed, output only that all threads have finished.
std::cout << "all threads are joined." << std::endl; std::cout << "all threads are joined." << std::endl;
// Restored because Options were rewritten.
// Restore the handler because the handler will not start unless you assign a value.
for (auto& s : oldOptions)
Options[s.first] = std::string(s.second);
} }
+3 -17
View File
@@ -259,7 +259,7 @@ namespace Learner {
return make_piece(c, pr); return make_piece(c, pr);
} }
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror) int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th)
{ {
SfenPacker packer; SfenPacker packer;
auto& stream = packer.stream; auto& stream = packer.stream;
@@ -280,16 +280,8 @@ namespace Learner {
pos.pieceList[B_KING][0] = SQUARE_NB; pos.pieceList[B_KING][0] = SQUARE_NB;
// First the position of the ball // First the position of the ball
if (mirror) for (auto c : Colors)
{ pos.board[stream.read_n_bit(6)] = make_piece(c, KING);
for (auto c : Colors)
pos.board[flip_file((Square)stream.read_n_bit(6))] = make_piece(c, KING);
}
else
{
for (auto c : Colors)
pos.board[stream.read_n_bit(6)] = make_piece(c, KING);
}
// Piece placement // Piece placement
for (Rank r = RANK_8; r >= RANK_1; --r) for (Rank r = RANK_8; r >= RANK_1; --r)
@@ -297,9 +289,6 @@ namespace Learner {
for (File f = FILE_A; f <= FILE_H; ++f) for (File f = FILE_A; f <= FILE_H; ++f)
{ {
auto sq = make_square(f, r); auto sq = make_square(f, r);
if (mirror) {
sq = flip_file(sq);
}
// it seems there are already balls // it seems there are already balls
Piece pc; Piece pc;
@@ -355,9 +344,6 @@ namespace Learner {
// En passant square. Ignore if no pawn capture is possible // En passant square. Ignore if no pawn capture is possible
if (stream.read_one_bit()) { if (stream.read_one_bit()) {
Square ep_square = static_cast<Square>(stream.read_n_bit(6)); Square ep_square = static_cast<Square>(stream.read_n_bit(6));
if (mirror) {
ep_square = flip_file(ep_square);
}
pos.st->epSquare = ep_square; pos.st->epSquare = ep_square;
if (!(pos.attackers_to(pos.st->epSquare) & pos.pieces(pos.sideToMove, PAWN)) if (!(pos.attackers_to(pos.st->epSquare) & pos.pieces(pos.sideToMove, PAWN))
+1 -1
View File
@@ -13,7 +13,7 @@ class Thread;
namespace Learner { namespace Learner {
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror); int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th);
PackedSfen sfen_pack(Position& pos); PackedSfen sfen_pack(Position& pos);
} }
+106 -12
View File
@@ -408,23 +408,11 @@ static void* aligned_large_pages_alloc_win(size_t allocSize) {
void* aligned_large_pages_alloc(size_t allocSize) { void* aligned_large_pages_alloc(size_t allocSize) {
static bool firstCall = true;
void* mem; void* mem;
// Try to allocate large pages // Try to allocate large pages
mem = aligned_large_pages_alloc_win(allocSize); mem = aligned_large_pages_alloc_win(allocSize);
// Suppress info strings on the first call. The first call occurs before 'uci'
// is received and in that case this output confuses some GUIs.
if (!firstCall)
{
if (mem)
sync_cout << "info string Hash table allocation: Windows large pages used." << sync_endl;
else
sync_cout << "info string Hash table allocation: Windows large pages not used." << sync_endl;
}
firstCall = false;
// Fall back to regular, page aligned, allocation if necessary // Fall back to regular, page aligned, allocation if necessary
if (!mem) if (!mem)
mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
@@ -641,3 +629,109 @@ void init(int argc, char* argv[]) {
} // namespace CommandLine } // namespace CommandLine
// Returns a string that represents the current time. (Used when learning evaluation functions)
std::string now_string()
{
// Using std::ctime(), localtime() gives a warning that MSVC is not secure.
// This shouldn't happen in the C++ standard, but...
#if defined(_MSC_VER)
// C4996 : 'ctime' : This function or variable may be unsafe.Consider using ctime_s instead.
#pragma warning(disable : 4996)
#endif
auto now = std::chrono::system_clock::now();
auto tp = std::chrono::system_clock::to_time_t(now);
auto result = string(std::ctime(&tp));
// remove line endings if they are included at the end
while (*result.rbegin() == '\n' || (*result.rbegin() == '\r'))
result.pop_back();
return result;
}
void sleep(int ms)
{
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
void* aligned_malloc(size_t size, size_t align)
{
void* p = _mm_malloc(size, align);
if (p == nullptr)
{
std::cout << "info string can't allocate memory. sise = " << size << std::endl;
exit(1);
}
return p;
}
std::uint64_t get_file_size(std::fstream& fs)
{
auto pos = fs.tellg();
fs.seekg(0, fstream::end);
const uint64_t eofPos = (uint64_t)fs.tellg();
fs.clear(); // Otherwise, the next seek may fail.
fs.seekg(0, fstream::beg);
const uint64_t begPos = (uint64_t)fs.tellg();
fs.seekg(pos);
return eofPos - begPos;
}
int read_file_to_memory(std::string filename, std::function<void* (uint64_t)> callback_func)
{
fstream fs(filename, ios::in | ios::binary);
if (fs.fail())
return 1;
const uint64_t file_size = get_file_size(fs);
//std::cout << "filename = " << filename << " , file_size = " << file_size << endl;
// I know the file size, so call callback_func to get a buffer for this,
// Get the pointer.
void* ptr = callback_func(file_size);
// If the buffer could not be secured, or if the file size is different from the expected file size,
// It is supposed to return nullptr. At this time, reading is interrupted and an error is returned.
if (ptr == nullptr)
return 2;
// read in pieces
const uint64_t block_size = 1024 * 1024 * 1024; // number of elements to read in one read (1GB)
for (uint64_t pos = 0; pos < file_size; pos += block_size)
{
// size to read this time
uint64_t read_size = (pos + block_size < file_size) ? block_size : (file_size - pos);
fs.read((char*)ptr + pos, read_size);
// Read error occurred in the middle of the file.
if (fs.fail())
return 2;
//cout << ".";
}
fs.close();
return 0;
}
int write_memory_to_file(std::string filename, void* ptr, uint64_t size)
{
fstream fs(filename, ios::out | ios::binary);
if (fs.fail())
return 1;
const uint64_t block_size = 1024 * 1024 * 1024; // number of elements to write in one write (1GB)
for (uint64_t pos = 0; pos < size; pos += block_size)
{
// Memory size to write this time
uint64_t write_size = (pos + block_size < size) ? block_size : (size - pos);
fs.write((char*)ptr + pos, write_size);
//cout << ".";
}
fs.close();
return 0;
}
@@ -1,7 +1,25 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Definition of input features and network structure used in NNUE evaluation function // Definition of input features and network structure used in NNUE evaluation function
#ifndef HALFKP_CR_EP_256X2_32_32_H #ifndef NNUE_HALFKP_CR_EP_256X2_32_32_H_INCLUDED
#define HALFKP_CR_EP_256X2_32_32_H #define NNUE_HALFKP_CR_EP_256X2_32_32_H_INCLUDED
#include "../features/feature_set.h" #include "../features/feature_set.h"
#include "../features/half_kp.h" #include "../features/half_kp.h"
@@ -12,31 +30,28 @@
#include "../layers/affine_transform.h" #include "../layers/affine_transform.h"
#include "../layers/clipped_relu.h" #include "../layers/clipped_relu.h"
namespace Eval { namespace Eval::NNUE {
namespace NNUE { // Input features used in evaluation function
using RawFeatures = Features::FeatureSet<
Features::HalfKP<Features::Side::kFriend>, Features::CastlingRight,
Features::EnPassant>;
// Input features used in evaluation function // Number of input feature dimensions after conversion
using RawFeatures = Features::FeatureSet< constexpr IndexType kTransformedFeatureDimensions = 256;
Features::HalfKP<Features::Side::kFriend>, Features::CastlingRight,
Features::EnPassant>;
// Number of input feature dimensions after conversion namespace Layers {
constexpr IndexType kTransformedFeatureDimensions = 256;
namespace Layers { // Define network structure
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
// define network structure } // namespace Layers
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
} // namespace Layers using Network = Layers::OutputLayer;
using Network = Layers::OutputLayer; } // namespace Eval::NNUE
} // namespace NNUE #endif // #ifndef NNUE_HALFKP_CR_EP_256X2_32_32_H_INCLUDED
} // namespace Eval
#endif // HALFKP_CR_EP_256X2_32_32_H
@@ -0,0 +1,37 @@
// Definition of input features and network structure used in NNUE evaluation function
#ifndef NNUE_HALFKP_CR_256X2_32_32_H_INCLUDED
#define NNUE_HALFKP_CR_256X2_32_32_H_INCLUDED
#include "../features/feature_set.h"
#include "../features/half_kp.h"
#include "../features/castling_right.h"
#include "../layers/input_slice.h"
#include "../layers/affine_transform.h"
#include "../layers/clipped_relu.h"
namespace Eval::NNUE {
// Input features used in evaluation function
using RawFeatures = Features::FeatureSet<
Features::HalfKP<Features::Side::kFriend>, Features::CastlingRight>;
// Number of input feature dimensions after conversion
constexpr IndexType kTransformedFeatureDimensions = 256;
namespace Layers {
// Define network structure
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
} // namespace Layers
using Network = Layers::OutputLayer;
} // namespace Eval::NNUE
#endif // #ifndef NNUE_HALFKP_CR_256X2_32_32_H_INCLUDED
+1
View File
@@ -1,4 +1,5 @@
// Definition of input features and network structure used in NNUE evaluation function // Definition of input features and network structure used in NNUE evaluation function
#ifndef K_P_256X2_32_32_H #ifndef K_P_256X2_32_32_H
#define K_P_256X2_32_32_H #define K_P_256X2_32_32_H
+29 -53
View File
@@ -18,7 +18,6 @@
// Code for calculating NNUE evaluation function // Code for calculating NNUE evaluation function
#include <fstream>
#include <iostream> #include <iostream>
#include <set> #include <set>
@@ -31,7 +30,7 @@
namespace Eval::NNUE { namespace Eval::NNUE {
uint32_t kpp_board_index[PIECE_NB][COLOR_NB] = { const uint32_t kpp_board_index[PIECE_NB][COLOR_NB] = {
// convention: W - us, B - them // convention: W - us, B - them
// viewed from other side, W and B are reversed // viewed from other side, W and B are reversed
{ PS_NONE, PS_NONE }, { PS_NONE, PS_NONE },
@@ -53,7 +52,7 @@ namespace Eval::NNUE {
}; };
// Input feature converter // Input feature converter
AlignedPtr<FeatureTransformer> feature_transformer; LargePagePtr<FeatureTransformer> feature_transformer;
// Evaluation function // Evaluation function
AlignedPtr<Network> network; AlignedPtr<Network> network;
@@ -80,14 +79,22 @@ namespace Eval::NNUE {
std::memset(pointer.get(), 0, sizeof(T)); std::memset(pointer.get(), 0, sizeof(T));
} }
template <typename T>
void Initialize(LargePagePtr<T>& pointer) {
static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T));
}
// Read evaluation function parameters // Read evaluation function parameters
template <typename T> template <typename T>
bool ReadParameters(std::istream& stream, const AlignedPtr<T>& pointer) { bool ReadParameters(std::istream& stream, T& reference) {
std::uint32_t header; std::uint32_t header;
header = read_little_endian<std::uint32_t>(stream); header = read_little_endian<std::uint32_t>(stream);
if (!stream || header != T::GetHashValue()) return false; if (!stream || header != T::GetHashValue()) return false;
return pointer->ReadParameters(stream); return reference.ReadParameters(stream);
} }
// write evaluation function parameters // write evaluation function parameters
@@ -98,6 +105,13 @@ namespace Eval::NNUE {
return pointer->WriteParameters(stream); return pointer->WriteParameters(stream);
} }
template <typename T>
bool WriteParameters(std::ostream& stream, const LargePagePtr<T>& pointer) {
constexpr std::uint32_t header = T::GetHashValue();
stream.write(reinterpret_cast<const char*>(&header), sizeof(header));
return pointer->WriteParameters(stream);
}
} // namespace Detail } // namespace Detail
// Initialize the evaluation function parameters // Initialize the evaluation function parameters
@@ -139,11 +153,10 @@ namespace Eval::NNUE {
std::string architecture; std::string architecture;
if (!ReadHeader(stream, &hash_value, &architecture)) return false; if (!ReadHeader(stream, &hash_value, &architecture)) return false;
if (hash_value != kHashValue) return false; if (hash_value != kHashValue) return false;
if (!Detail::ReadParameters(stream, feature_transformer)) return false; if (!Detail::ReadParameters(stream, *feature_transformer)) return false;
if (!Detail::ReadParameters(stream, network)) return false; if (!Detail::ReadParameters(stream, *network)) return false;
return stream && stream.peek() == std::ios::traits_type::eof(); return stream && stream.peek() == std::ios::traits_type::eof();
} }
// write evaluation function parameters // write evaluation function parameters
bool WriteParameters(std::ostream& stream) { bool WriteParameters(std::ostream& stream) {
if (!WriteHeader(stream, kHashValue, GetArchitectureString())) return false; if (!WriteHeader(stream, kHashValue, GetArchitectureString())) return false;
@@ -151,36 +164,20 @@ namespace Eval::NNUE {
if (!Detail::WriteParameters(stream, network)) return false; if (!Detail::WriteParameters(stream, network)) return false;
return !stream.fail(); return !stream.fail();
} }
// Evaluation function. Perform differential calculation.
// Proceed with the difference calculation if possible Value evaluate(const Position& pos) {
static void UpdateAccumulatorIfPossible(const Position& pos) {
feature_transformer->UpdateAccumulatorIfPossible(pos);
}
// Calculate the evaluation value
static Value ComputeScore(const Position& pos, bool refresh) {
auto& accumulator = pos.state()->accumulator;
if (!refresh && accumulator.computed_score) {
return accumulator.score;
}
alignas(kCacheLineSize) TransformedFeatureType alignas(kCacheLineSize) TransformedFeatureType
transformed_features[FeatureTransformer::kBufferSize]; transformed_features[FeatureTransformer::kBufferSize];
feature_transformer->Transform(pos, transformed_features, refresh); feature_transformer->Transform(pos, transformed_features);
alignas(kCacheLineSize) char buffer[Network::kBufferSize]; alignas(kCacheLineSize) char buffer[Network::kBufferSize];
const auto output = network->Propagate(transformed_features, buffer); const auto output = network->Propagate(transformed_features, buffer);
auto score = static_cast<Value>(output[0] / FV_SCALE); return static_cast<Value>(output[0] / FV_SCALE);
accumulator.score = score;
accumulator.computed_score = true;
return accumulator.score;
} }
// Load the evaluation function file // Load eval, from a file stream or a memory stream
bool load_eval_file(const std::string& evalFile) { bool load_eval(std::string name, std::istream& stream) {
Initialize(); Initialize();
@@ -189,29 +186,8 @@ namespace Eval::NNUE {
std::cout << "info string SkipLoadingEval set to true, Net not loaded!" << std::endl; std::cout << "info string SkipLoadingEval set to true, Net not loaded!" << std::endl;
return true; return true;
} }
fileName = name;
fileName = evalFile; return ReadParameters(stream);
std::ifstream stream(evalFile, std::ios::binary);
const bool result = ReadParameters(stream);
return result;
}
// Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos) {
return ComputeScore(pos, false);
}
// Evaluation function. Perform full calculation.
Value compute_eval(const Position& pos) {
return ComputeScore(pos, true);
}
// Proceed with the difference calculation if possible
void update_eval(const Position& pos) {
UpdateAccumulatorIfPossible(pos);
} }
} // namespace Eval::NNUE } // namespace Eval::NNUE
+29
View File
@@ -54,6 +54,35 @@ namespace Eval::NNUE {
template <typename T> template <typename T>
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>; using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
// Input feature converter
extern LargePagePtr<FeatureTransformer> feature_transformer;
// Evaluation function
extern AlignedPtr<Network> network;
// Evaluation function file name
extern std::string fileName;
// Saved evaluation function file name
extern std::string savedfileName;
// Get a string that represents the structure of the evaluation function
std::string GetArchitectureString();
// read the header
bool ReadHeader(std::istream& stream,
std::uint32_t* hash_value, std::string* architecture);
// write the header
bool WriteHeader(std::ostream& stream,
std::uint32_t hash_value, const std::string& architecture);
// read evaluation function parameters
bool ReadParameters(std::istream& stream);
// write evaluation function parameters
bool WriteParameters(std::ostream& stream);
} // namespace Eval::NNUE } // namespace Eval::NNUE
#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED #endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
+161 -188
View File
@@ -5,15 +5,12 @@
#include <filesystem> #include <filesystem>
#include "../learn/learn.h" #include "../learn/learn.h"
#include "../learn/learning_tools.h"
#include "../position.h" #include "../position.h"
#include "../uci.h" #include "../uci.h"
#include "../misc.h" #include "../misc.h"
#include "../thread_win32_osx.h" #include "../thread_win32_osx.h"
#include "../eval/evaluate_common.h"
#include "evaluate_nnue.h" #include "evaluate_nnue.h"
#include "evaluate_nnue_learner.h" #include "evaluate_nnue_learner.h"
#include "trainer/features/factorizer_feature_set.h" #include "trainer/features/factorizer_feature_set.h"
@@ -24,215 +21,191 @@
#include "trainer/trainer_clipped_relu.h" #include "trainer/trainer_clipped_relu.h"
#include "trainer/trainer_sum.h" #include "trainer/trainer_sum.h"
namespace Eval {
namespace NNUE {
namespace {
// learning data
std::vector<Example> examples;
// Mutex for exclusive control of examples
std::mutex examples_mutex;
// number of samples in mini-batch
uint64_t batch_size;
// random number generator
std::mt19937 rng;
// learner
std::shared_ptr<Trainer<Network>> trainer;
// Learning rate scale // Learning rate scale
double global_learning_rate_scale; double global_learning_rate;
// Get the learning rate scale namespace Eval::NNUE {
double GetGlobalLearningRateScale() {
return global_learning_rate_scale;
}
// Tell the learner options such as hyperparameters namespace {
void SendMessages(std::vector<Message> messages) {
for (auto& message : messages) {
trainer->SendMessage(&message);
assert(message.num_receivers > 0);
}
}
} // namespace // learning data
std::vector<Example> examples;
// Initialize learning // Mutex for exclusive control of examples
void InitializeTraining(double eta1, uint64_t eta1_epoch, std::mutex examples_mutex;
double eta2, uint64_t eta2_epoch, double eta3) {
std::cout << "Initializing NN training for "
<< GetArchitectureString() << std::endl;
assert(feature_transformer); // number of samples in mini-batch
assert(network); uint64_t batch_size;
trainer = Trainer<Network>::Create(network.get(), feature_transformer.get());
if (Options["SkipLoadingEval"]) { // random number generator
trainer->Initialize(rng); std::mt19937 rng;
}
global_learning_rate_scale = 1.0; // learner
EvalLearningTools::Weight::init_eta(eta1, eta2, eta3, eta1_epoch, eta2_epoch); std::shared_ptr<Trainer<Network>> trainer;
}
// set the number of samples in the mini-batch // Tell the learner options such as hyperparameters
void SetBatchSize(uint64_t size) { void SendMessages(std::vector<Message> messages) {
assert(size > 0); for (auto& message : messages) {
batch_size = size; trainer->SendMessage(&message);
} assert(message.num_receivers > 0);
// set the learning rate scale
void SetGlobalLearningRateScale(double scale) {
global_learning_rate_scale = scale;
}
// Set options such as hyperparameters
void SetOptions(const std::string& options) {
std::vector<Message> messages;
for (const auto& option : Split(options, ',')) {
const auto fields = Split(option, '=');
assert(fields.size() == 1 || fields.size() == 2);
if (fields.size() == 1) {
messages.emplace_back(fields[0]);
} else {
messages.emplace_back(fields[0], fields[1]);
}
}
SendMessages(std::move(messages));
}
// Reread the evaluation function parameters for learning from the file
void RestoreParameters(const std::string& dir_name) {
const std::string file_name = Path::Combine(dir_name, NNUE::savedfileName);
std::ifstream stream(file_name, std::ios::binary);
#ifndef NDEBUG
bool result =
#endif
ReadParameters(stream);
#ifndef NDEBUG
assert(result);
#endif
SendMessages({{"reset"}});
}
// Add 1 sample of learning data
void AddExample(Position& pos, Color rootColor,
const Learner::PackedSfenValue& psv, double weight) {
Example example;
if (rootColor == pos.side_to_move()) {
example.sign = 1;
} else {
example.sign = -1;
}
example.psv = psv;
example.weight = weight;
Features::IndexList active_indices[2];
for (const auto trigger : kRefreshTriggers) {
RawFeatures::AppendActiveIndices(pos, trigger, active_indices);
}
if (pos.side_to_move() != WHITE) {
active_indices[0].swap(active_indices[1]);
}
for (const auto color : Colors) {
std::vector<TrainingFeature> training_features;
for (const auto base_index : active_indices[color]) {
static_assert(Features::Factorizer<RawFeatures>::GetDimensions() <
(1 << TrainingFeature::kIndexBits), "");
Features::Factorizer<RawFeatures>::AppendTrainingFeatures(
base_index, &training_features);
}
std::sort(training_features.begin(), training_features.end());
auto& unique_features = example.training_features[color];
for (const auto& feature : training_features) {
if (!unique_features.empty() &&
feature.GetIndex() == unique_features.back().GetIndex()) {
unique_features.back() += feature;
} else {
unique_features.push_back(feature);
} }
} }
} // namespace
// Initialize learning
void InitializeTraining(const std::string& seed) {
std::cout << "Initializing NN training for "
<< GetArchitectureString() << std::endl;
assert(feature_transformer);
assert(network);
trainer = Trainer<Network>::Create(network.get(), feature_transformer.get());
rng.seed(PRNG(seed).rand<uint64_t>());
if (Options["SkipLoadingEval"]) {
trainer->Initialize(rng);
}
} }
std::lock_guard<std::mutex> lock(examples_mutex); // set the number of samples in the mini-batch
examples.push_back(std::move(example)); void SetBatchSize(uint64_t size) {
} assert(size > 0);
batch_size = size;
}
// Set options such as hyperparameters
void SetOptions(const std::string& options) {
std::vector<Message> messages;
for (const auto& option : Split(options, ',')) {
const auto fields = Split(option, '=');
assert(fields.size() == 1 || fields.size() == 2);
if (fields.size() == 1) {
messages.emplace_back(fields[0]);
} else {
messages.emplace_back(fields[0], fields[1]);
}
}
SendMessages(std::move(messages));
}
// update the evaluation function parameters // Reread the evaluation function parameters for learning from the file
void UpdateParameters(uint64_t epoch) { void RestoreParameters(const std::string& dir_name) {
assert(batch_size > 0); const std::string file_name = Path::Combine(dir_name, NNUE::savedfileName);
std::ifstream stream(file_name, std::ios::binary);
#ifndef NDEBUG
bool result =
#endif
ReadParameters(stream);
#ifndef NDEBUG
assert(result);
#endif
EvalLearningTools::Weight::calc_eta(epoch); SendMessages({{"reset"}});
const auto learning_rate = static_cast<LearnFloatType>( }
get_eta() / batch_size);
std::lock_guard<std::mutex> lock(examples_mutex); void FinalizeNet() {
std::shuffle(examples.begin(), examples.end(), rng); SendMessages({{"clear_unobserved_feature_weights"}});
while (examples.size() >= batch_size) { }
std::vector<Example> batch(examples.end() - batch_size, examples.end());
examples.resize(examples.size() - batch_size);
const auto network_output = trainer->Propagate(batch); // Add 1 sample of learning data
void AddExample(Position& pos, Color rootColor,
const Learner::PackedSfenValue& psv, double weight) {
Example example;
if (rootColor == pos.side_to_move()) {
example.sign = 1;
} else {
example.sign = -1;
}
example.psv = psv;
example.weight = weight;
std::vector<LearnFloatType> gradients(batch.size()); Features::IndexList active_indices[2];
for (std::size_t b = 0; b < batch.size(); ++b) { for (const auto trigger : kRefreshTriggers) {
const auto shallow = static_cast<Value>(Round<std::int32_t>( RawFeatures::AppendActiveIndices(pos, trigger, active_indices);
batch[b].sign * network_output[b] * kPonanzaConstant)); }
const auto& psv = batch[b].psv; if (pos.side_to_move() != WHITE) {
const double gradient = batch[b].sign * Learner::calc_grad(shallow, psv); active_indices[0].swap(active_indices[1]);
gradients[b] = static_cast<LearnFloatType>(gradient * batch[b].weight); }
for (const auto color : Colors) {
std::vector<TrainingFeature> training_features;
for (const auto base_index : active_indices[color]) {
static_assert(Features::Factorizer<RawFeatures>::GetDimensions() <
(1 << TrainingFeature::kIndexBits), "");
Features::Factorizer<RawFeatures>::AppendTrainingFeatures(
base_index, &training_features);
}
std::sort(training_features.begin(), training_features.end());
auto& unique_features = example.training_features[color];
for (const auto& feature : training_features) {
if (!unique_features.empty() &&
feature.GetIndex() == unique_features.back().GetIndex()) {
unique_features.back() += feature;
} else {
unique_features.push_back(feature);
}
}
} }
trainer->Backpropagate(gradients.data(), learning_rate); std::lock_guard<std::mutex> lock(examples_mutex);
} examples.push_back(std::move(example));
SendMessages({{"quantize_parameters"}});
}
// Check if there are any problems with learning
void CheckHealth() {
SendMessages({{"check_health"}});
}
} // namespace NNUE
// save merit function parameters to a file
void save_eval(std::string dir_name) {
auto eval_dir = Path::Combine(Options["EvalSaveDir"], dir_name);
std::cout << "save_eval() start. folder = " << eval_dir << std::endl;
// mkdir() will fail if this folder already exists, but
// Apart from that. If not, I just want you to make it.
// Also, assume that the folders up to EvalSaveDir have been dug.
std::filesystem::create_directories(eval_dir);
if (Options["SkipLoadingEval"] && NNUE::trainer) {
NNUE::SendMessages({{"clear_unobserved_feature_weights"}});
} }
const std::string file_name = Path::Combine(eval_dir, NNUE::savedfileName); // update the evaluation function parameters
std::ofstream stream(file_name, std::ios::binary); void UpdateParameters() {
assert(batch_size > 0);
const auto learning_rate = static_cast<LearnFloatType>(
global_learning_rate / batch_size);
std::lock_guard<std::mutex> lock(examples_mutex);
std::shuffle(examples.begin(), examples.end(), rng);
while (examples.size() >= batch_size) {
std::vector<Example> batch(examples.end() - batch_size, examples.end());
examples.resize(examples.size() - batch_size);
const auto network_output = trainer->Propagate(batch);
std::vector<LearnFloatType> gradients(batch.size());
for (std::size_t b = 0; b < batch.size(); ++b) {
const auto shallow = static_cast<Value>(Round<std::int32_t>(
batch[b].sign * network_output[b] * kPonanzaConstant));
const auto& psv = batch[b].psv;
const double gradient = batch[b].sign * Learner::calc_grad(shallow, psv);
gradients[b] = static_cast<LearnFloatType>(gradient * batch[b].weight);
}
trainer->Backpropagate(gradients.data(), learning_rate);
}
SendMessages({{"quantize_parameters"}});
}
// Check if there are any problems with learning
void CheckHealth() {
SendMessages({{"check_health"}});
}
// save merit function parameters to a file
void save_eval(std::string dir_name) {
auto eval_dir = Path::Combine(Options["EvalSaveDir"], dir_name);
std::cout << "save_eval() start. folder = " << eval_dir << std::endl;
// mkdir() will fail if this folder already exists, but
// Apart from that. If not, I just want you to make it.
// Also, assume that the folders up to EvalSaveDir have been dug.
std::filesystem::create_directories(eval_dir);
const std::string file_name = Path::Combine(eval_dir, NNUE::savedfileName);
std::ofstream stream(file_name, std::ios::binary);
#ifndef NDEBUG #ifndef NDEBUG
const bool result = bool result =
#endif #endif
NNUE::WriteParameters(stream); WriteParameters(stream);
#ifndef NDEBUG #ifndef NDEBUG
assert(result); assert(result);
#endif #endif
std::cout << "save_eval() finished. folder = " << eval_dir << std::endl; std::cout << "save_eval() finished. folder = " << eval_dir << std::endl;
} }
} // namespace Eval::NNUE
// get the current eta
double get_eta() {
return NNUE::GetGlobalLearningRateScale() * EvalLearningTools::Weight::eta;
}
} // namespace Eval
+18 -23
View File
@@ -5,38 +5,33 @@
#include "../learn/learn.h" #include "../learn/learn.h"
namespace Eval { namespace Eval::NNUE {
namespace NNUE { // Initialize learning
void InitializeTraining(const std::string& seed);
// Initialize learning // set the number of samples in the mini-batch
void InitializeTraining(double eta1, uint64_t eta1_epoch, void SetBatchSize(uint64_t size);
double eta2, uint64_t eta2_epoch, double eta3);
// set the number of samples in the mini-batch // Set options such as hyperparameters
void SetBatchSize(uint64_t size); void SetOptions(const std::string& options);
// set the learning rate scale // Reread the evaluation function parameters for learning from the file
void SetGlobalLearningRateScale(double scale); void RestoreParameters(const std::string& dir_name);
// Set options such as hyperparameters
void SetOptions(const std::string& options);
// Reread the evaluation function parameters for learning from the file
void RestoreParameters(const std::string& dir_name);
// Add 1 sample of learning data // Add 1 sample of learning data
void AddExample(Position& pos, Color rootColor, void AddExample(Position& pos, Color rootColor,
const Learner::PackedSfenValue& psv, double weight); const Learner::PackedSfenValue& psv, double weight);
// update the evaluation function parameters // update the evaluation function parameters
void UpdateParameters(uint64_t epoch); void UpdateParameters();
// Check if there are any problems with learning // Check if there are any problems with learning
void CheckHealth(); void CheckHealth();
} // namespace NNUE void FinalizeNet();
} // namespace Eval void save_eval(std::string suffix);
} // namespace Eval::NNUE
#endif #endif
+30 -59
View File
@@ -1,69 +1,40 @@
//Definition of input feature quantity K of NNUE evaluation function //Definition of input feature quantity CastlingRight of NNUE evaluation function
#include "castling_right.h" #include "castling_right.h"
#include "index_list.h" #include "index_list.h"
namespace Eval { namespace Eval::NNUE::Features {
namespace NNUE { // Get a list of indices with a value of 1 among the features
void CastlingRight::AppendActiveIndices(
const Position& pos, Color perspective, IndexList* active) {
// do nothing if array size is small to avoid compiler warning
if (RawFeatures::kMaxActiveDimensions < kMaxActiveDimensions) return;
namespace Features { int castling_rights = pos.state()->castlingRights;
int relative_castling_rights;
if (perspective == WHITE) {
relative_castling_rights = castling_rights;
}
else {
// Invert the perspective.
relative_castling_rights = ((castling_rights & 3) << 2)
& ((castling_rights >> 2) & 3);
}
// Get a list of indices with a value of 1 among the features for (Eval::NNUE::IndexType i = 0; i < kDimensions; ++i) {
void CastlingRight::AppendActiveIndices( if (relative_castling_rights & (1 << i)) {
const Position& pos, Color perspective, IndexList* active) { active->push_back(i);
// do nothing if array size is small to avoid compiler warning
if (RawFeatures::kMaxActiveDimensions < kMaxActiveDimensions) return;
int castling_rights = pos.state()->castlingRights;
int relative_castling_rights;
if (perspective == WHITE) {
relative_castling_rights = castling_rights;
}
else {
// Invert the perspective.
relative_castling_rights = ((castling_rights & 3) << 2)
& ((castling_rights >> 2) & 3);
}
for (Eval::NNUE::IndexType i = 0; i < kDimensions; ++i) {
if (relative_castling_rights & (1 << i)) {
active->push_back(i);
}
}
} }
}
}
// Get a list of indices whose values have changed from the previous one in the feature quantity // Get a list of indices whose values have changed from the previous one in the feature quantity
void CastlingRight::AppendChangedIndices( void CastlingRight::AppendChangedIndices(
const Position& pos, Color perspective, const Position& /* pos */, Color /* perspective */,
IndexList* removed, IndexList* /* added */) { IndexList* /* removed */, IndexList* /* added */) {
// Not implemented.
assert(false);
}
int previous_castling_rights = pos.state()->previous->castlingRights; } // namespace Eval::NNUE::Features
int current_castling_rights = pos.state()->castlingRights;
int relative_previous_castling_rights;
int relative_current_castling_rights;
if (perspective == WHITE) {
relative_previous_castling_rights = previous_castling_rights;
relative_current_castling_rights = current_castling_rights;
}
else {
// Invert the perspective.
relative_previous_castling_rights = ((previous_castling_rights & 3) << 2)
& ((previous_castling_rights >> 2) & 3);
relative_current_castling_rights = ((current_castling_rights & 3) << 2)
& ((current_castling_rights >> 2) & 3);
}
for (Eval::NNUE::IndexType i = 0; i < kDimensions; ++i) {
if ((relative_previous_castling_rights & (1 << i)) &&
(relative_current_castling_rights & (1 << i)) == 0) {
removed->push_back(i);
}
}
}
} // namespace Features
} // namespace NNUE
} // namespace Eval
+22 -31
View File
@@ -1,4 +1,4 @@
//Definition of input feature quantity K of NNUE evaluation function //Definition of input feature quantity CastlingRight of NNUE evaluation function
#ifndef _NNUE_FEATURES_CASTLING_RIGHT_H_ #ifndef _NNUE_FEATURES_CASTLING_RIGHT_H_
#define _NNUE_FEATURES_CASTLING_RIGHT_H_ #define _NNUE_FEATURES_CASTLING_RIGHT_H_
@@ -6,39 +6,30 @@
#include "../../evaluate.h" #include "../../evaluate.h"
#include "features_common.h" #include "features_common.h"
namespace Eval { namespace Eval::NNUE::Features {
namespace NNUE { class CastlingRight {
public:
// feature quantity name
static constexpr const char* kName = "CastlingRight";
// Hash value embedded in the evaluation function file
static constexpr std::uint32_t kHashValue = 0x913968AAu;
// number of feature dimensions
static constexpr IndexType kDimensions = 4;
// The maximum value of the number of indexes whose value is 1 at the same time among the feature values
static constexpr IndexType kMaxActiveDimensions = 4;
// Timing of full calculation instead of difference calculation
static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kAnyPieceMoved;
namespace Features { // Get a list of indices with a value of 1 among the features
static void AppendActiveIndices(const Position& pos, Color perspective,
IndexList* active);
// Feature K: Ball position // Get a list of indices whose values have changed from the previous one in the feature quantity
class CastlingRight { static void AppendChangedIndices(const Position& pos, Color perspective,
public: IndexList* removed, IndexList* added);
// feature quantity name };
static constexpr const char* kName = "CastlingRight";
// Hash value embedded in the evaluation function file
static constexpr std::uint32_t kHashValue = 0x913968AAu;
// number of feature dimensions
static constexpr IndexType kDimensions = 4;
// The maximum value of the number of indexes whose value is 1 at the same time among the feature values
static constexpr IndexType kMaxActiveDimensions = 4;
// Timing of full calculation instead of difference calculation
static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kNone;
// Get a list of indices with a value of 1 among the features } // namespace Eval::NNUE::Features
static void AppendActiveIndices(const Position& pos, Color perspective,
IndexList* active);
// Get a list of indices whose values ??have changed from the previous one in the feature quantity
static void AppendChangedIndices(const Position& pos, Color perspective,
IndexList* removed, IndexList* added);
};
} // namespace Features
} // namespace NNUE
} // namespace Eval
#endif #endif
+22 -35
View File
@@ -1,43 +1,30 @@
//Definition of input feature quantity K of NNUE evaluation function //Definition of input feature quantity EnPassant of NNUE evaluation function
#include "enpassant.h" #include "enpassant.h"
#include "index_list.h" #include "index_list.h"
namespace Eval { namespace Eval::NNUE::Features {
namespace NNUE { // Get a list of indices with a value of 1 among the features
void EnPassant::AppendActiveIndices(
const Position& pos, Color /* perspective */, IndexList* active) {
// do nothing if array size is small to avoid compiler warning
if (RawFeatures::kMaxActiveDimensions < kMaxActiveDimensions) return;
namespace Features { auto epSquare = pos.state()->epSquare;
if (epSquare == SQ_NONE) {
return;
}
auto file = file_of(epSquare);
active->push_back(file);
}
// Get a list of indices with a value of 1 among the features // Get a list of indices whose values have changed from the previous one in the feature quantity
void EnPassant::AppendActiveIndices( void EnPassant::AppendChangedIndices(
const Position& pos, Color perspective, IndexList* active) { const Position& /* pos */, Color /* perspective */,
// do nothing if array size is small to avoid compiler warning IndexList* /* removed */, IndexList* /* added */) {
if (RawFeatures::kMaxActiveDimensions < kMaxActiveDimensions) return; // Not implemented.
assert(false);
}
auto epSquare = pos.state()->epSquare; } // namespace Eval::NNUE::Features
if (epSquare == SQ_NONE) {
return;
}
if (perspective == BLACK) {
epSquare = rotate180(epSquare);
}
auto file = file_of(epSquare);
active->push_back(file);
}
// Get a list of indices whose values ??have changed from the previous one in the feature quantity
void EnPassant::AppendChangedIndices(
const Position& /* pos */, Color /* perspective */,
IndexList* /* removed */, IndexList* /* added */) {
// Not implemented.
assert(false);
}
} // namespace Features
} // namespace NNUE
} // namespace Eval
+22 -31
View File
@@ -1,4 +1,4 @@
//Definition of input feature quantity K of NNUE evaluation function //Definition of input feature quantity EnPassant of NNUE evaluation function
#ifndef _NNUE_FEATURES_ENPASSANT_H_ #ifndef _NNUE_FEATURES_ENPASSANT_H_
#define _NNUE_FEATURES_ENPASSANT_H_ #define _NNUE_FEATURES_ENPASSANT_H_
@@ -6,39 +6,30 @@
#include "../../evaluate.h" #include "../../evaluate.h"
#include "features_common.h" #include "features_common.h"
namespace Eval { namespace Eval::NNUE::Features {
namespace NNUE { class EnPassant {
public:
// feature quantity name
static constexpr const char* kName = "EnPassant";
// Hash value embedded in the evaluation function file
static constexpr std::uint32_t kHashValue = 0x02924F91u;
// number of feature dimensions
static constexpr IndexType kDimensions = 8;
// The maximum value of the number of indexes whose value is 1 at the same time among the feature values
static constexpr IndexType kMaxActiveDimensions = 1;
// Timing of full calculation instead of difference calculation
static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kAnyPieceMoved;
namespace Features { // Get a list of indices with a value of 1 among the features
static void AppendActiveIndices(const Position& pos, Color perspective,
IndexList* active);
// Feature K: Ball position // Get a list of indices whose values ??have changed from the previous one in the feature quantity
class EnPassant { static void AppendChangedIndices(const Position& pos, Color perspective,
public: IndexList* removed, IndexList* added);
// feature quantity name };
static constexpr const char* kName = "EnPassant";
// Hash value embedded in the evaluation function file
static constexpr std::uint32_t kHashValue = 0x02924F91u;
// number of feature dimensions
static constexpr IndexType kDimensions = 8;
// The maximum value of the number of indexes whose value is 1 at the same time among the feature values
static constexpr IndexType kMaxActiveDimensions = 1;
// Timing of full calculation instead of difference calculation
static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kAnyPieceMoved;
// Get a list of indices with a value of 1 among the features } // namespace Eval::NNUE::Features
static void AppendActiveIndices(const Position& pos, Color perspective,
IndexList* active);
// Get a list of indices whose values ??have changed from the previous one in the feature quantity
static void AppendChangedIndices(const Position& pos, Color perspective,
IndexList* removed, IndexList* added);
};
} // namespace Features
} // namespace NNUE
} // namespace Eval
#endif #endif
+11
View File
@@ -105,9 +105,20 @@ namespace Eval::NNUE::Features {
for (Color perspective : { WHITE, BLACK }) { for (Color perspective : { WHITE, BLACK }) {
reset[perspective] = false; reset[perspective] = false;
switch (trigger) { switch (trigger) {
case TriggerEvent::kNone:
break;
case TriggerEvent::kFriendKingMoved: case TriggerEvent::kFriendKingMoved:
reset[perspective] = dp.piece[0] == make_piece(perspective, KING); reset[perspective] = dp.piece[0] == make_piece(perspective, KING);
break; break;
case TriggerEvent::kEnemyKingMoved:
reset[perspective] = dp.piece[0] == make_piece(~perspective, KING);
break;
case TriggerEvent::kAnyKingMoved:
reset[perspective] = type_of(dp.piece[0]) == KING;
break;
case TriggerEvent::kAnyPieceMoved:
reset[perspective] = true;
break;
default: default:
assert(false); assert(false);
break; break;
+4 -4
View File
@@ -34,10 +34,10 @@ namespace Eval::NNUE::Features {
// Trigger to perform full calculations instead of difference only // Trigger to perform full calculations instead of difference only
enum class TriggerEvent { enum class TriggerEvent {
kNone, // Calculate the difference whenever possible kNone, // Calculate the difference whenever possible
kFriendKingMoved, // calculate all when own ball moves kFriendKingMoved, // calculate full evaluation when own king moves
kEnemyKingMoved, // do all calculations when enemy balls move kEnemyKingMoved, // calculate full evaluation when opponent king moves
kAnyKingMoved, // do all calculations if either ball moves kAnyKingMoved, // calculate full evaluation when any king moves
kAnyPieceMoved, // always do all calculations kAnyPieceMoved, // always calculate full evaluation
}; };
enum class Side { enum class Side {
+2 -2
View File
@@ -23,9 +23,9 @@
namespace Eval::NNUE::Features { namespace Eval::NNUE::Features {
// Orient a square according to perspective (rotates by 180 for black) // Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) { inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63)); return Square(int(s) ^ (bool(perspective) * SQ_A8));
} }
// Find the index of the feature quantity from the king position and PieceSquare // Find the index of the feature quantity from the king position and PieceSquare
+2 -2
View File
@@ -9,9 +9,9 @@ namespace NNUE {
namespace Features { namespace Features {
// Orient a square according to perspective (rotates by 180 for black) // Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) { inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63)); return Square(int(s) ^ (bool(perspective) * SQ_A8));
} }
// Find the index of the feature quantity from the ball position and PieceSquare // Find the index of the feature quantity from the ball position and PieceSquare
+6 -14
View File
@@ -9,9 +9,9 @@ namespace NNUE {
namespace Features { namespace Features {
// Orient a square according to perspective (rotates by 180 for black) // Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) { inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63)); return Square(int(s) ^ (bool(perspective) * SQ_A8));
} }
// Index of a feature for a given king position. // Index of a feature for a given king position.
@@ -32,19 +32,11 @@ void K::AppendChangedIndices(
const Position& pos, Color perspective, const Position& pos, Color perspective,
IndexList* removed, IndexList* added) { IndexList* removed, IndexList* added) {
const auto& dp = pos.state()->dirtyPiece; const auto& dp = pos.state()->dirtyPiece;
Color king_color; if (type_of(dp.piece[0]) == KING)
if (dp.piece[0] == Piece::W_KING) { {
king_color = WHITE; removed->push_back(MakeIndex(perspective, dp.from[0], color_of(dp.piece[0])));
added->push_back(MakeIndex(perspective, dp.to[0], color_of(dp.piece[0])));
} }
else if (dp.piece[0] == Piece::B_KING) {
king_color = BLACK;
}
else {
return;
}
removed->push_back(MakeIndex(perspective, dp.from[0], king_color));
added->push_back(MakeIndex(perspective, dp.to[0], king_color));
} }
} // namespace Features } // namespace Features
+2 -2
View File
@@ -9,9 +9,9 @@ namespace NNUE {
namespace Features { namespace Features {
// Orient a square according to perspective (rotates by 180 for black) // Orient a square according to perspective (flip rank for black)
inline Square orient(Color perspective, Square s) { inline Square orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63)); return Square(int(s) ^ (bool(perspective) * SQ_A8));
} }
// Find the index of the feature quantity from the king position and PieceSquare // Find the index of the feature quantity from the king position and PieceSquare
+1 -1
View File
@@ -22,7 +22,7 @@
#define NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED
// Defines the network structure // Defines the network structure
#include "architectures/halfkp_256x2-32-32.h" #include "architectures/halfkp-cr-ep_256x2-32-32.h"
namespace Eval::NNUE { namespace Eval::NNUE {
+1 -1
View File
@@ -69,7 +69,7 @@
namespace Eval::NNUE { namespace Eval::NNUE {
// Version of the evaluation file // Version of the evaluation file
constexpr std::uint32_t kVersion = 0x7AF32F16u; constexpr std::uint32_t kVersion = 0x7AF32F17u;
// Constant used in evaluation value calculation // Constant used in evaluation value calculation
constexpr int FV_SCALE = 16; constexpr int FV_SCALE = 16;
+146 -95
View File
@@ -1,4 +1,4 @@
/* /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
@@ -40,6 +40,7 @@ namespace Eval::NNUE {
#define vec_store(a,b) _mm512_storeA_si512(a,b) #define vec_store(a,b) _mm512_storeA_si512(a,b)
#define vec_add_16(a,b) _mm512_add_epi16(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b)
#define vec_sub_16(a,b) _mm512_sub_epi16(a,b) #define vec_sub_16(a,b) _mm512_sub_epi16(a,b)
#define vec_zero _mm512_setzero_si512()
static constexpr IndexType kNumRegs = 8; // only 8 are needed static constexpr IndexType kNumRegs = 8; // only 8 are needed
#elif USE_AVX2 #elif USE_AVX2
@@ -48,6 +49,7 @@ namespace Eval::NNUE {
#define vec_store(a,b) _mm256_storeA_si256(a,b) #define vec_store(a,b) _mm256_storeA_si256(a,b)
#define vec_add_16(a,b) _mm256_add_epi16(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b)
#define vec_sub_16(a,b) _mm256_sub_epi16(a,b) #define vec_sub_16(a,b) _mm256_sub_epi16(a,b)
#define vec_zero _mm256_setzero_si256()
static constexpr IndexType kNumRegs = 16; static constexpr IndexType kNumRegs = 16;
#elif USE_SSE2 #elif USE_SSE2
@@ -56,6 +58,7 @@ namespace Eval::NNUE {
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_epi16(a,b) #define vec_add_16(a,b) _mm_add_epi16(a,b)
#define vec_sub_16(a,b) _mm_sub_epi16(a,b) #define vec_sub_16(a,b) _mm_sub_epi16(a,b)
#define vec_zero _mm_setzero_si128()
static constexpr IndexType kNumRegs = Is64Bit ? 16 : 8; static constexpr IndexType kNumRegs = Is64Bit ? 16 : 8;
#elif USE_MMX #elif USE_MMX
@@ -64,6 +67,7 @@ namespace Eval::NNUE {
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_pi16(a,b) #define vec_add_16(a,b) _mm_add_pi16(a,b)
#define vec_sub_16(a,b) _mm_sub_pi16(a,b) #define vec_sub_16(a,b) _mm_sub_pi16(a,b)
#define vec_zero _mm_setzero_si64()
static constexpr IndexType kNumRegs = 8; static constexpr IndexType kNumRegs = 8;
#elif USE_NEON #elif USE_NEON
@@ -72,6 +76,7 @@ namespace Eval::NNUE {
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) vaddq_s16(a,b) #define vec_add_16(a,b) vaddq_s16(a,b)
#define vec_sub_16(a,b) vsubq_s16(a,b) #define vec_sub_16(a,b) vsubq_s16(a,b)
#define vec_zero {0}
static constexpr IndexType kNumRegs = 16; static constexpr IndexType kNumRegs = 16;
#else #else
@@ -193,6 +198,12 @@ namespace Eval::NNUE {
&reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 0]); &reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 0]);
__m256i sum1 = _mm256_loadA_si256( __m256i sum1 = _mm256_loadA_si256(
&reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 1]); &reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 1]);
for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) {
sum0 = _mm256_add_epi16(sum0, reinterpret_cast<const __m256i*>(
accumulation[perspectives[p]][i])[j * 2 + 0]);
sum1 = _mm256_add_epi16(sum1, reinterpret_cast<const __m256i*>(
accumulation[perspectives[p]][i])[j * 2 + 1]);
}
_mm256_storeA_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8( _mm256_storeA_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8(
_mm256_packs_epi16(sum0, sum1), kZero), kControl)); _mm256_packs_epi16(sum0, sum1), kZero), kControl));
} }
@@ -204,6 +215,12 @@ namespace Eval::NNUE {
accumulation[perspectives[p]][0])[j * 2 + 0]); accumulation[perspectives[p]][0])[j * 2 + 0]);
__m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>( __m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
accumulation[perspectives[p]][0])[j * 2 + 1]); accumulation[perspectives[p]][0])[j * 2 + 1]);
for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) {
sum0 = _mm_add_epi16(sum0, reinterpret_cast<const __m128i*>(
accumulation[perspectives[p]][i])[j * 2 + 0]);
sum1 = _mm_add_epi16(sum1, reinterpret_cast<const __m128i*>(
accumulation[perspectives[p]][i])[j * 2 + 1]);
}
const __m128i packedbytes = _mm_packs_epi16(sum0, sum1); const __m128i packedbytes = _mm_packs_epi16(sum0, sum1);
_mm_store_si128(&out[j], _mm_store_si128(&out[j],
@@ -224,6 +241,12 @@ namespace Eval::NNUE {
accumulation[perspectives[p]][0])[j * 2 + 0]); accumulation[perspectives[p]][0])[j * 2 + 0]);
__m64 sum1 = *(&reinterpret_cast<const __m64*>( __m64 sum1 = *(&reinterpret_cast<const __m64*>(
accumulation[perspectives[p]][0])[j * 2 + 1]); accumulation[perspectives[p]][0])[j * 2 + 1]);
for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) {
sum0 = _mm_add_pi16(sum0, reinterpret_cast<const __m64*>(
accumulation[perspectives[p]][i])[j * 2 + 0]);
sum1 = _mm_add_pi16(sum1, reinterpret_cast<const __m64*>(
accumulation[perspectives[p]][i])[j * 2 + 1]);
}
const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); const __m64 packedbytes = _mm_packs_pi16(sum0, sum1);
out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
} }
@@ -233,12 +256,19 @@ namespace Eval::NNUE {
for (IndexType j = 0; j < kNumChunks; ++j) { for (IndexType j = 0; j < kNumChunks; ++j) {
int16x8_t sum = reinterpret_cast<const int16x8_t*>( int16x8_t sum = reinterpret_cast<const int16x8_t*>(
accumulation[perspectives[p]][0])[j]; accumulation[perspectives[p]][0])[j];
for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) {
sum = vaddq_s16(sum, reinterpret_cast<const int16x8_t*>(
accumulation[perspectives[p]][i])[j]);
}
out[j] = vmax_s8(vqmovn_s16(sum), kZero); out[j] = vmax_s8(vqmovn_s16(sum), kZero);
} }
#else #else
for (IndexType j = 0; j < kHalfDimensions; ++j) { for (IndexType j = 0; j < kHalfDimensions; ++j) {
BiasType sum = accumulation[static_cast<int>(perspectives[p])][0][j]; BiasType sum = accumulation[static_cast<int>(perspectives[p])][0][j];
for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) {
sum += accumulation[static_cast<int>(perspectives[p])][i][j];
}
output[offset + j] = static_cast<OutputType>( output[offset + j] = static_cast<OutputType>(
std::max<int>(0, std::min<int>(127, sum))); std::max<int>(0, std::min<int>(127, sum)));
} }
@@ -255,44 +285,55 @@ namespace Eval::NNUE {
void RefreshAccumulator(const Position& pos) const { void RefreshAccumulator(const Position& pos) const {
auto& accumulator = pos.state()->accumulator; auto& accumulator = pos.state()->accumulator;
IndexType i = 0; for (IndexType i = 0; i < kRefreshTriggers.size(); ++i) {
Features::IndexList active_indices[2]; Features::IndexList active_indices[2];
RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i], RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
active_indices); active_indices);
for (Color perspective : { WHITE, BLACK }) { for (Color perspective : { WHITE, BLACK }) {
#ifdef TILING #ifdef TILING
for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) { for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) {
auto biasesTile = reinterpret_cast<const vec_t*>( auto accTile = reinterpret_cast<vec_t*>(
&biases_[j * kTileHeight]); &accumulator.accumulation[perspective][i][j * kTileHeight]);
auto accTile = reinterpret_cast<vec_t*>( vec_t acc[kNumRegs];
&accumulator.accumulation[perspective][i][j * kTileHeight]);
vec_t acc[kNumRegs];
for (unsigned k = 0; k < kNumRegs; ++k) if (i == 0) {
acc[k] = biasesTile[k]; auto biasesTile = reinterpret_cast<const vec_t*>(
&biases_[j * kTileHeight]);
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = biasesTile[k];
} else {
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = vec_zero;
}
for (const auto index : active_indices[perspective]) {
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
for (const auto index : active_indices[perspective]) { for (unsigned k = 0; k < kNumRegs; ++k)
const IndexType offset = kHalfDimensions * index + j * kTileHeight; acc[k] = vec_add_16(acc[k], column[k]);
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]); }
for (unsigned k = 0; k < kNumRegs; ++k) for (unsigned k = 0; k < kNumRegs; k++)
acc[k] = vec_add_16(acc[k], column[k]); vec_store(&accTile[k], acc[k]);
}
#else
if (i == 0) {
std::memcpy(accumulator.accumulation[perspective][i], biases_,
kHalfDimensions * sizeof(BiasType));
} else {
std::memset(accumulator.accumulation[perspective][i], 0,
kHalfDimensions * sizeof(BiasType));
} }
for (unsigned k = 0; k < kNumRegs; k++) for (const auto index : active_indices[perspective]) {
vec_store(&accTile[k], acc[k]); const IndexType offset = kHalfDimensions * index;
}
#else
std::memcpy(accumulator.accumulation[perspective][i], biases_,
kHalfDimensions * sizeof(BiasType));
for (const auto index : active_indices[perspective]) { for (IndexType j = 0; j < kHalfDimensions; ++j)
const IndexType offset = kHalfDimensions * index; accumulator.accumulation[perspective][i][j] += weights_[offset + j];
}
for (IndexType j = 0; j < kHalfDimensions; ++j) #endif
accumulator.accumulation[perspective][i][j] += weights_[offset + j];
} }
#endif
} }
#if defined(USE_MMX) #if defined(USE_MMX)
@@ -307,86 +348,96 @@ namespace Eval::NNUE {
const auto prev_accumulator = pos.state()->previous->accumulator; const auto prev_accumulator = pos.state()->previous->accumulator;
auto& accumulator = pos.state()->accumulator; auto& accumulator = pos.state()->accumulator;
IndexType i = 0; for (IndexType i = 0; i < kRefreshTriggers.size(); ++i) {
Features::IndexList removed_indices[2], added_indices[2]; Features::IndexList removed_indices[2], added_indices[2];
bool reset[2]; bool reset[2];
RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i], RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i],
removed_indices, added_indices, reset); removed_indices, added_indices, reset);
#ifdef TILING #ifdef TILING
for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) { for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) {
for (Color perspective : { WHITE, BLACK }) {
auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[perspective][i][j * kTileHeight]);
vec_t acc[kNumRegs];
if (reset[perspective]) {
if (i == 0) {
auto biasesTile = reinterpret_cast<const vec_t*>(
&biases_[j * kTileHeight]);
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = biasesTile[k];
} else {
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = vec_zero;
}
} else {
auto prevAccTile = reinterpret_cast<const vec_t*>(
&prev_accumulator.accumulation[perspective][i][j * kTileHeight]);
for (IndexType k = 0; k < kNumRegs; ++k)
acc[k] = vec_load(&prevAccTile[k]);
// Difference calculation for the deactivated features
for (const auto index : removed_indices[perspective]) {
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
for (IndexType k = 0; k < kNumRegs; ++k)
acc[k] = vec_sub_16(acc[k], column[k]);
}
}
{ // Difference calculation for the activated features
for (const auto index : added_indices[perspective]) {
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
for (IndexType k = 0; k < kNumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]);
}
}
for (IndexType k = 0; k < kNumRegs; ++k)
vec_store(&accTile[k], acc[k]);
}
}
#if defined(USE_MMX)
_mm_empty();
#endif
#else
for (Color perspective : { WHITE, BLACK }) { for (Color perspective : { WHITE, BLACK }) {
auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[perspective][i][j * kTileHeight]);
vec_t acc[kNumRegs];
if (reset[perspective]) { if (reset[perspective]) {
auto biasesTile = reinterpret_cast<const vec_t*>( if (i == 0) {
&biases_[j * kTileHeight]); std::memcpy(accumulator.accumulation[perspective][i], biases_,
for (unsigned k = 0; k < kNumRegs; ++k) kHalfDimensions * sizeof(BiasType));
acc[k] = biasesTile[k]; } else {
std::memset(accumulator.accumulation[perspective][i], 0,
kHalfDimensions * sizeof(BiasType));
}
} else { } else {
auto prevAccTile = reinterpret_cast<const vec_t*>( std::memcpy(accumulator.accumulation[perspective][i],
&prev_accumulator.accumulation[perspective][i][j * kTileHeight]); prev_accumulator.accumulation[perspective][i],
for (IndexType k = 0; k < kNumRegs; ++k) kHalfDimensions * sizeof(BiasType));
acc[k] = vec_load(&prevAccTile[k]);
// Difference calculation for the deactivated features // Difference calculation for the deactivated features
for (const auto index : removed_indices[perspective]) { for (const auto index : removed_indices[perspective]) {
const IndexType offset = kHalfDimensions * index + j * kTileHeight; const IndexType offset = kHalfDimensions * index;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
for (IndexType k = 0; k < kNumRegs; ++k) for (IndexType j = 0; j < kHalfDimensions; ++j)
acc[k] = vec_sub_16(acc[k], column[k]); accumulator.accumulation[perspective][i][j] -= weights_[offset + j];
} }
} }
{ // Difference calculation for the activated features { // Difference calculation for the activated features
for (const auto index : added_indices[perspective]) { for (const auto index : added_indices[perspective]) {
const IndexType offset = kHalfDimensions * index + j * kTileHeight; const IndexType offset = kHalfDimensions * index;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
for (IndexType k = 0; k < kNumRegs; ++k) for (IndexType j = 0; j < kHalfDimensions; ++j)
acc[k] = vec_add_16(acc[k], column[k]); accumulator.accumulation[perspective][i][j] += weights_[offset + j];
} }
} }
for (IndexType k = 0; k < kNumRegs; ++k)
vec_store(&accTile[k], acc[k]);
} }
#endif
} }
#if defined(USE_MMX)
_mm_empty();
#endif
#else
for (Color perspective : { WHITE, BLACK }) {
if (reset[perspective]) {
std::memcpy(accumulator.accumulation[perspective][i], biases_,
kHalfDimensions * sizeof(BiasType));
} else {
std::memcpy(accumulator.accumulation[perspective][i],
prev_accumulator.accumulation[perspective][i],
kHalfDimensions * sizeof(BiasType));
// Difference calculation for the deactivated features
for (const auto index : removed_indices[perspective]) {
const IndexType offset = kHalfDimensions * index;
for (IndexType j = 0; j < kHalfDimensions; ++j)
accumulator.accumulation[perspective][i][j] -= weights_[offset + j];
}
}
{ // Difference calculation for the activated features
for (const auto index : added_indices[perspective]) {
const IndexType offset = kHalfDimensions * index;
for (IndexType j = 0; j < kHalfDimensions; ++j)
accumulator.accumulation[perspective][i][j] += weights_[offset + j];
}
}
}
#endif
accumulator.computed_accumulation = true; accumulator.computed_accumulation = true;
} }
+1 -1
View File
@@ -194,7 +194,7 @@ class Trainer<Layers::AffineTransform<PreviousLayer, OutputDimensions>> {
weights_(), weights_(),
biases_diff_(), biases_diff_(),
weights_diff_(), weights_diff_(),
momentum_(0.0), momentum_(0.2),
learning_rate_scale_(1.0) { learning_rate_scale_(1.0) {
DequantizeParameters(); DequantizeParameters();
} }
@@ -232,7 +232,7 @@ class Trainer<FeatureTransformer> {
biases_(), biases_(),
weights_(), weights_(),
biases_diff_(), biases_diff_(),
momentum_(0.0), momentum_(0.2),
learning_rate_scale_(1.0) { learning_rate_scale_(1.0) {
min_pre_activation_ = std::numeric_limits<LearnFloatType>::max(); min_pre_activation_ = std::numeric_limits<LearnFloatType>::max();
max_pre_activation_ = std::numeric_limits<LearnFloatType>::lowest(); max_pre_activation_ = std::numeric_limits<LearnFloatType>::lowest();
+2 -2
View File
@@ -1351,9 +1351,9 @@ bool Position::pos_is_ok() const {
// Add a function that directly unpacks for speed. It's pretty tough. // Add a function that directly unpacks for speed. It's pretty tough.
// Write it by combining packer::unpack() and Position::set(). // Write it by combining packer::unpack() and Position::set().
// If there is a problem with the passed phase and there is an error, non-zero is returned. // If there is a problem with the passed phase and there is an error, non-zero is returned.
int Position::set_from_packed_sfen(const Learner::PackedSfen& sfen , StateInfo* si, Thread* th, bool mirror) int Position::set_from_packed_sfen(const Learner::PackedSfen& sfen , StateInfo* si, Thread* th)
{ {
return Learner::set_from_packed_sfen(*this, sfen, si, th, mirror); return Learner::set_from_packed_sfen(*this, sfen, si, th);
} }
// Give the board, hand piece, and turn, and return the sfen. // Give the board, hand piece, and turn, and return the sfen.
+2 -2
View File
@@ -177,7 +177,7 @@ public:
// --sfenization helper // --sfenization helper
friend int Learner::set_from_packed_sfen(Position& pos, const Learner::PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror); friend int Learner::set_from_packed_sfen(Position& pos, const Learner::PackedSfen& sfen, StateInfo* si, Thread* th);
// Get the packed sfen. Returns to the buffer specified in the argument. // Get the packed sfen. Returns to the buffer specified in the argument.
// Do not include gamePly in pack. // Do not include gamePly in pack.
@@ -187,7 +187,7 @@ public:
// Equivalent to pos.set(sfen_unpack(data),si,th);. // Equivalent to pos.set(sfen_unpack(data),si,th);.
// If there is a problem with the passed phase and there is an error, non-zero is returned. // If there is a problem with the passed phase and there is an error, non-zero is returned.
// PackedSfen does not include gamePly so it cannot be restored. If you want to set it, specify it with an argument. // PackedSfen does not include gamePly so it cannot be restored. If you want to set it, specify it with an argument.
int set_from_packed_sfen(const Learner::PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror = false); int set_from_packed_sfen(const Learner::PackedSfen& sfen, StateInfo* si, Thread* th);
void clear() { std::memset(this, 0, sizeof(Position)); } void clear() { std::memset(this, 0, sizeof(Position)); }
+304 -33
View File
@@ -40,20 +40,12 @@ namespace Search {
LimitsType Limits; LimitsType Limits;
} }
namespace Tablebases {
int Cardinality;
bool RootInTB;
bool UseRule50;
Depth ProbeDepth;
}
namespace TB = Tablebases;
using std::string; using std::string;
using Eval::evaluate; using Eval::evaluate;
using namespace Search; using namespace Search;
bool Search::prune_at_shallow_depth = true;
namespace { namespace {
// Different node types, used as a template parameter // Different node types, used as a template parameter
@@ -712,27 +704,27 @@ namespace {
} }
// Step 5. Tablebases probe // Step 5. Tablebases probe
if (!rootNode && TB::Cardinality) if (!rootNode && thisThread->Cardinality)
{ {
int piecesCount = pos.count<ALL_PIECES>(); int piecesCount = pos.count<ALL_PIECES>();
if ( piecesCount <= TB::Cardinality if ( piecesCount <= thisThread->Cardinality
&& (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && (piecesCount < thisThread->Cardinality || depth >= thisThread->ProbeDepth)
&& pos.rule50_count() == 0 && pos.rule50_count() == 0
&& !pos.can_castle(ANY_CASTLING)) && !pos.can_castle(ANY_CASTLING))
{ {
TB::ProbeState err; Tablebases::ProbeState err;
TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); Tablebases::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
// Force check of time on the next occasion // Force check of time on the next occasion
if (thisThread == Threads.main()) if (thisThread == Threads.main())
static_cast<MainThread*>(thisThread)->callsCnt = 0; static_cast<MainThread*>(thisThread)->callsCnt = 0;
if (err != TB::ProbeState::FAIL) if (err != Tablebases::ProbeState::FAIL)
{ {
thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); thisThread->tbHits.fetch_add(1, std::memory_order_relaxed);
int drawScore = TB::UseRule50 ? 1 : 0; int drawScore = thisThread->UseRule50 ? 1 : 0;
// use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score
value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1
@@ -992,7 +984,9 @@ moves_loop: // When in check, search starts from here
ss->moveCount = ++moveCount; ss->moveCount = ++moveCount;
if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000
&& !Limits.silent
)
sync_cout << "info depth " << depth sync_cout << "info depth " << depth
<< " currmove " << UCI::move(move, pos.is_chess960()) << " currmove " << UCI::move(move, pos.is_chess960())
<< " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
@@ -1009,6 +1003,7 @@ moves_loop: // When in check, search starts from here
// Step 13. Pruning at shallow depth (~200 Elo) // Step 13. Pruning at shallow depth (~200 Elo)
if ( !rootNode if ( !rootNode
&& (PvNode ? prune_at_shallow_depth : true)
&& pos.non_pawn_material(us) && pos.non_pawn_material(us)
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
{ {
@@ -1520,6 +1515,7 @@ moves_loop: // When in check, search starts from here
// Futility pruning // Futility pruning
if ( !ss->inCheck if ( !ss->inCheck
&& Search::prune_at_shallow_depth
&& !givesCheck && !givesCheck
&& futilityBase > -VALUE_KNOWN_WIN && futilityBase > -VALUE_KNOWN_WIN
&& !pos.advanced_pawn_push(move)) && !pos.advanced_pawn_push(move))
@@ -1547,6 +1543,7 @@ moves_loop: // When in check, search starts from here
// Do not search moves with negative SEE values // Do not search moves with negative SEE values
if ( !ss->inCheck if ( !ss->inCheck
&& Search::prune_at_shallow_depth
&& !(givesCheck && pos.is_discovery_check_on_king(~pos.side_to_move(), move)) && !(givesCheck && pos.is_discovery_check_on_king(~pos.side_to_move(), move))
&& !pos.see_ge(move)) && !pos.see_ge(move))
continue; continue;
@@ -1569,6 +1566,7 @@ moves_loop: // When in check, search starts from here
// CounterMove based pruning // CounterMove based pruning
if ( !captureOrPromotion if ( !captureOrPromotion
&& Search::prune_at_shallow_depth
&& moveCount && moveCount
&& (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold
&& (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold) && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold)
@@ -1839,7 +1837,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
size_t pvIdx = pos.this_thread()->pvIdx; size_t pvIdx = pos.this_thread()->pvIdx;
size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size());
uint64_t nodesSearched = Threads.nodes_searched(); uint64_t nodesSearched = Threads.nodes_searched();
uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); uint64_t tbHits = Threads.tb_hits() + (pos.this_thread()->rootInTB ? rootMoves.size() : 0);
for (size_t i = 0; i < multiPV; ++i) for (size_t i = 0; i < multiPV; ++i)
{ {
@@ -1854,7 +1852,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
if (v == -VALUE_INFINITE) if (v == -VALUE_INFINITE)
v = VALUE_ZERO; v = VALUE_ZERO;
bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; bool tb = pos.this_thread()->rootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY;
v = tb ? rootMoves[i].tbScore : v; v = tb ? rootMoves[i].tbScore : v;
if (ss.rdbuf()->in_avail()) // Not at first line if (ss.rdbuf()->in_avail()) // Not at first line
@@ -1921,34 +1919,34 @@ bool RootMove::extract_ponder_from_tt(Position& pos) {
void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
RootInTB = false; auto& rootInTB = pos.this_thread()->rootInTB;
UseRule50 = bool(Options["Syzygy50MoveRule"]); auto& cardinality = pos.this_thread()->Cardinality;
ProbeDepth = int(Options["SyzygyProbeDepth"]); auto& probeDepth = pos.this_thread()->ProbeDepth;
Cardinality = int(Options["SyzygyProbeLimit"]); rootInTB = false;
bool dtz_available = true; bool dtz_available = true;
// Tables with fewer pieces than SyzygyProbeLimit are searched with // Tables with fewer pieces than SyzygyProbeLimit are searched with
// ProbeDepth == DEPTH_ZERO // ProbeDepth == DEPTH_ZERO
if (Cardinality > MaxCardinality) if (cardinality > Tablebases::MaxCardinality)
{ {
Cardinality = MaxCardinality; cardinality = Tablebases::MaxCardinality;
ProbeDepth = 0; probeDepth = 0;
} }
if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) if (cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
{ {
// Rank moves using DTZ tables // Rank moves using DTZ tables
RootInTB = root_probe(pos, rootMoves); rootInTB = root_probe(pos, rootMoves);
if (!RootInTB) if (!rootInTB)
{ {
// DTZ tables are missing; try to rank moves using WDL tables // DTZ tables are missing; try to rank moves using WDL tables
dtz_available = false; dtz_available = false;
RootInTB = root_probe_wdl(pos, rootMoves); rootInTB = root_probe_wdl(pos, rootMoves);
} }
} }
if (RootInTB) if (rootInTB)
{ {
// Sort moves according to TB rank // Sort moves according to TB rank
std::stable_sort(rootMoves.begin(), rootMoves.end(), std::stable_sort(rootMoves.begin(), rootMoves.end(),
@@ -1956,7 +1954,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
// Probe during search only if DTZ is not available and we are winning // Probe during search only if DTZ is not available and we are winning
if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW)
Cardinality = 0; cardinality = 0;
} }
else else
{ {
@@ -1964,4 +1962,277 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
for (auto& m : rootMoves) for (auto& m : rootMoves)
m.tbRank = 0; m.tbRank = 0;
} }
}
// --- expose the functions such as fixed depth search used for learning to the outside
namespace Learner
{
// For learning, prepare a stub that can call search,qsearch() from one thread.
// From now on, it is better to have a Searcher and prepare a substitution table for each thread like Apery.
// It might have been good.
// Initialization for learning.
// Called from Learner::search(),Learner::qsearch().
void init_for_search(Position& pos, Stack* ss)
{
// RootNode requires ss->ply == 0.
// Because it clears to zero, ss->ply == 0, so it's okay...
std::memset(ss - 7, 0, 10 * sizeof(Stack));
// Regarding this_thread.
{
auto th = pos.this_thread();
th->completedDepth = 0;
th->selDepth = 0;
th->rootDepth = 0;
th->nmpMinPly = th->bestMoveChanges = 0;
th->ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2;
// Zero initialization of the number of search nodes
th->nodes = 0;
// Clear all history types. This initialization takes a little time, and the accuracy of the search is rather low, so the good and bad are not well understood.
// th->clear();
int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns
Color us = pos.side_to_move();
// In analysis mode, adjust contempt in accordance with user preference
if (Limits.infinite || Options["UCI_AnalyseMode"])
ct = Options["Analysis Contempt"] == "Off" ? 0
: Options["Analysis Contempt"] == "Both" ? ct
: Options["Analysis Contempt"] == "White" && us == BLACK ? -ct
: Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct
: ct;
// Evaluation score is from the white point of view
th->contempt = (us == WHITE ? make_score(ct, ct / 2)
: -make_score(ct, ct / 2));
for (int i = 7; i > 0; i--)
(ss - i)->continuationHistory = &th->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
// set rootMoves
auto& rootMoves = th->rootMoves;
rootMoves.clear();
for (auto m: MoveList<LEGAL>(pos))
rootMoves.push_back(Search::RootMove(m));
assert(!rootMoves.empty());
th->UseRule50 = bool(Options["Syzygy50MoveRule"]);
th->ProbeDepth = int(Options["SyzygyProbeDepth"]);
th->Cardinality = int(Options["SyzygyProbeLimit"]);
// Tables with fewer pieces than SyzygyProbeLimit are searched with
// ProbeDepth == DEPTH_ZERO
if (th->Cardinality > Tablebases::MaxCardinality)
{
th->Cardinality = Tablebases::MaxCardinality;
th->ProbeDepth = 0;
}
Tablebases::rank_root_moves(pos, rootMoves);
}
}
// A pair of reader and evaluation value. Returned by Learner::search(),Learner::qsearch().
typedef std::pair<Value, std::vector<Move> > ValueAndPV;
// Stationary search.
//
// Precondition) Search thread is set by pos.set_this_thread(Threads[thread_id]).
// Also, when Threads.stop arrives, the search is interrupted, so the PV at that time is not correct.
// After returning from search(), if Threads.stop == true, do not use the search result.
// Also, note that before calling, if you do not call it with Threads.stop == false, the search will be interrupted and it will return.
//
// If it is clogged, MOVE_RESIGN is returned in the PV array.
//
//Although it was possible to specify alpha and beta with arguments, this will show the result when searching in that window
// Because it writes to the substitution table, the value that can be pruned is written to that window when learning
// As it has a bad effect, I decided to stop allowing the window range to be specified.
ValueAndPV qsearch(Position& pos)
{
Stack stack[MAX_PLY+10], *ss = stack+7;
Move pv[MAX_PLY+1];
init_for_search(pos, ss);
ss->pv = pv; // For the time being, it must be a dummy and somewhere with a buffer.
if (pos.is_draw(0)) {
// Return draw value if draw.
return { VALUE_DRAW, {} };
}
// Is it stuck?
if (MoveList<LEGAL>(pos).size() == 0)
{
// Return the mated value if checkmated.
return { mated_in(/*ss->ply*/ 0 + 1), {} };
}
auto bestValue = ::qsearch<PV>(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, 0);
// Returns the PV obtained.
std::vector<Move> pvs;
for (Move* p = &ss->pv[0]; is_ok(*p); ++p)
pvs.push_back(*p);
return ValueAndPV(bestValue, pvs);
}
// Normal search. Depth depth (specified as an integer).
// 3 If you want a score for hand reading,
// auto v = search(pos,3);
// Do something like
// Evaluation value is obtained in v.first and PV is obtained in v.second.
// When multi pv is enabled, you can get the PV (reading line) array in pos.this_thread()->rootMoves[N].pv.
// Specify multi pv with the argument multiPV of this function. (The value of Options["MultiPV"] is ignored)
//
// Declaration win judgment is not done as root (because it is troublesome to handle), so it is not done here.
// Handle it by the caller.
//
// Precondition) Search thread is set by pos.set_this_thread(Threads[thread_id]).
// Also, when Threads.stop arrives, the search is interrupted, so the PV at that time is not correct.
// After returning from search(), if Threads.stop == true, do not use the search result.
// Also, note that before calling, if you do not call it with Threads.stop == false, the search will be interrupted and it will return.
ValueAndPV search(Position& pos, int depth_, size_t multiPV /* = 1 */, uint64_t nodesLimit /* = 0 */)
{
std::vector<Move> pvs;
Depth depth = depth_;
if (depth < 0)
return std::pair<Value, std::vector<Move>>(Eval::evaluate(pos), std::vector<Move>());
if (depth == 0)
return qsearch(pos);
Stack stack[MAX_PLY + 10], * ss = stack + 7;
Move pv[MAX_PLY + 1];
init_for_search(pos, ss);
ss->pv = pv; // For the time being, it must be a dummy and somewhere with a buffer.
// Initialize the variables related to this_thread
auto th = pos.this_thread();
auto& rootDepth = th->rootDepth;
auto& pvIdx = th->pvIdx;
auto& pvLast = th->pvLast;
auto& rootMoves = th->rootMoves;
auto& completedDepth = th->completedDepth;
auto& selDepth = th->selDepth;
// A function to search the top N of this stage as best move
//size_t multiPV = Options["MultiPV"];
// Do not exceed the number of moves in this situation
multiPV = std::min(multiPV, rootMoves.size());
// If you do not multiply the node limit by the value of MultiPV, you will not be thinking about the same node for one candidate hand when you fix the depth and have MultiPV.
nodesLimit *= multiPV;
Value alpha = -VALUE_INFINITE;
Value beta = VALUE_INFINITE;
Value delta = -VALUE_INFINITE;
Value bestValue = -VALUE_INFINITE;
while ((rootDepth += 1) <= depth
// exit this loop even if the node limit is exceeded
// The number of search nodes is passed in the argument of this function.
&& !(nodesLimit /* limited nodes */ && th->nodes.load(std::memory_order_relaxed) >= nodesLimit)
)
{
for (RootMove& rm : rootMoves)
rm.previousScore = rm.score;
size_t pvFirst = 0;
pvLast = 0;
// MultiPV loop. We perform a full root search for each PV line
for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx)
{
if (pvIdx == pvLast)
{
pvFirst = pvLast;
for (pvLast++; pvLast < rootMoves.size(); pvLast++)
if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank)
break;
}
// selDepth output with USI info for each depth and PV line
selDepth = 0;
// Switch to aspiration search for depth 5 and above.
if (rootDepth >= 4)
{
Value prev = rootMoves[pvIdx].previousScore;
delta = Value(17);
alpha = std::max(prev - delta,-VALUE_INFINITE);
beta = std::min(prev + delta, VALUE_INFINITE);
}
while (true)
{
Depth adjustedDepth = std::max(1, rootDepth);
bestValue = ::search<PV>(pos, ss, alpha, beta, adjustedDepth, false);
stable_sort(rootMoves.begin() + pvIdx, rootMoves.end());
//my_stable_sort(pos.this_thread()->thread_id(),&rootMoves[0] + pvIdx, rootMoves.size() - pvIdx);
// Expand aspiration window for fail low/high.
// However, if it is the value specified by the argument, it will be treated as fail low/high and break.
if (bestValue <= alpha)
{
beta = (alpha + beta) / 2;
alpha = std::max(bestValue - delta, -VALUE_INFINITE);
}
else if (bestValue >= beta)
{
beta = std::min(bestValue + delta, VALUE_INFINITE);
}
else
break;
delta += delta / 4 + 5;
assert(-VALUE_INFINITE <= alpha && beta <= VALUE_INFINITE);
// runaway check
//assert(th->nodes.load(std::memory_order_relaxed) <= 1000000 );
}
stable_sort(rootMoves.begin(), rootMoves.begin() + pvIdx + 1);
//my_stable_sort(pos.this_thread()->thread_id() , &rootMoves[0] , pvIdx + 1);
} // multi PV
completedDepth = rootDepth;
}
// Pass PV_is(ok) to eliminate this PV, there may be NULL_MOVE in the middle.
// MOVE_WIN has never been thrust. (For now)
for (Move move : rootMoves[0].pv)
{
if (!is_ok(move))
break;
pvs.push_back(move);
}
//sync_cout << rootDepth << sync_endl;
// Considering multiPV, the score of rootMoves[0] is returned as bestValue.
bestValue = rootMoves[0].score;
return ValueAndPV(bestValue, pvs);
}
} }
+7 -1
View File
@@ -24,6 +24,7 @@
#include "misc.h" #include "misc.h"
#include "movepick.h" #include "movepick.h"
#include "types.h" #include "types.h"
#include "uci.h"
class Position; class Position;
@@ -32,7 +33,7 @@ namespace Search {
/// Threshold used for countermoves based pruning /// Threshold used for countermoves based pruning
constexpr int CounterMovePruneThreshold = 0; constexpr int CounterMovePruneThreshold = 0;
extern bool prune_at_shallow_depth_on_pv_node; extern bool prune_at_shallow_depth;
/// Stack struct keeps track of the information we need to remember from nodes /// Stack struct keeps track of the information we need to remember from nodes
/// shallower and deeper in the tree during the search. Each search thread has /// shallower and deeper in the tree during the search. Each search thread has
@@ -111,6 +112,11 @@ void clear();
} // namespace Search } // namespace Search
namespace Tablebases {
extern int MaxCardinality;
}
namespace Learner { namespace Learner {
// A pair of reader and evaluation value. Returned by Learner::search(),Learner::qsearch(). // A pair of reader and evaluation value. Returned by Learner::search(),Learner::qsearch().
-2
View File
@@ -43,8 +43,6 @@ enum ProbeState {
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
}; };
extern int MaxCardinality;
void init(const std::string& paths); void init(const std::string& paths);
WDLScore probe_wdl(Position& pos, ProbeState* result); WDLScore probe_wdl(Position& pos, ProbeState* result);
int probe_dtz(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result);
+15 -3
View File
@@ -181,9 +181,6 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
rootMoves.emplace_back(m); rootMoves.emplace_back(m);
if (!rootMoves.empty())
Tablebases::rank_root_moves(pos, rootMoves);
// After ownership transfer 'states' becomes empty, so if we stop the search // After ownership transfer 'states' becomes empty, so if we stop the search
// and call 'go' again without setting a new position states.get() == NULL. // and call 'go' again without setting a new position states.get() == NULL.
assert(states.get() || setupStates.get()); assert(states.get() || setupStates.get());
@@ -203,6 +200,21 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
th->rootMoves = rootMoves; th->rootMoves = rootMoves;
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
th->rootState = setupStates->back(); th->rootState = setupStates->back();
th->UseRule50 = bool(Options["Syzygy50MoveRule"]);
th->ProbeDepth = int(Options["SyzygyProbeDepth"]);
th->Cardinality = int(Options["SyzygyProbeLimit"]);
// Tables with fewer pieces than SyzygyProbeLimit are searched with
// ProbeDepth == DEPTH_ZERO
if (th->Cardinality > Tablebases::MaxCardinality)
{
th->Cardinality = Tablebases::MaxCardinality;
th->ProbeDepth = 0;
}
if (!rootMoves.empty())
Tablebases::rank_root_moves(pos, rootMoves);
} }
main()->start_searching(); main()->start_searching();
+5
View File
@@ -73,6 +73,11 @@ public:
CapturePieceToHistory captureHistory; CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2]; ContinuationHistory continuationHistory[2][2];
Score contempt; Score contempt;
bool rootInTB;
int Cardinality;
bool UseRule50;
Depth ProbeDepth;
}; };
+3
View File
@@ -35,6 +35,9 @@ bool TranspositionTable::enable_transposition_table = true;
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
if (!TranspositionTable::enable_transposition_table) {
return;
}
// Preserve any existing move for the same position // Preserve any existing move for the same position
if (m || (uint16_t)k != key16) if (m || (uint16_t)k != key16)
move16 = (uint16_t)m; move16 = (uint16_t)m;
+8 -8
View File
@@ -47,7 +47,7 @@ const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
void test_cmd(Position& pos, istringstream& is) void test_cmd(Position& pos, istringstream& is)
{ {
// Initialize as it may be searched. // Initialize as it may be searched.
Eval::init_NNUE(); Eval::NNUE::init();
std::string param; std::string param;
is >> param; is >> param;
@@ -210,15 +210,15 @@ namespace {
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl; << "\nNodes/second : " << 1000 * nodes / elapsed << endl;
} }
// The win rate model returns the probability (per mille) of winning given an eval
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
int win_rate_model(Value v, int ply) {
// Return win rate in per mille (rounded to nearest)
return int(0.5 + UCI::win_rate_model_double(v, ply));
}
} // namespace } // namespace
// The win rate model returns the probability (per mille) of winning given an eval
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
int UCI::win_rate_model(Value v, int ply) {
// Return win rate in per mille (rounded to nearest)
return int(0.5 + win_rate_model_double(v, ply));
}
// The win rate model returns the probability (per mille) of winning given an eval // The win rate model returns the probability (per mille) of winning given an eval
// and a game-ply. The model fits rather accurately the LTC fishtest statistics. // and a game-ply. The model fits rather accurately the LTC fishtest statistics.
double UCI::win_rate_model_double(double v, int ply) { double UCI::win_rate_model_double(double v, int ply) {
+1
View File
@@ -72,6 +72,7 @@ std::string square(Square s);
std::string move(Move m, bool chess960); std::string move(Move m, bool chess960);
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
std::string wdl(Value v, int ply); std::string wdl(Value v, int ply);
int win_rate_model(Value v, int ply);
double win_rate_model_double(double v, int ply); double win_rate_model_double(double v, int ply);
Move to_move(const Position& pos, std::string& str); Move to_move(const Position& pos, std::string& str);
+6 -12
View File
@@ -41,15 +41,14 @@ void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
void on_logger(const Option& o) { start_logger(o); } void on_logger(const Option& o) { start_logger(o); }
void on_threads(const Option& o) { Threads.set(size_t(o)); } void on_threads(const Option& o) { Threads.set(size_t(o)); }
void on_tb_path(const Option& o) { Tablebases::init(o); } void on_tb_path(const Option& o) { Tablebases::init(o); }
void on_use_NNUE(const Option& ) { Eval::init_NNUE(); } void on_use_NNUE(const Option& ) { Eval::NNUE::init(); }
void on_eval_file(const Option& ) { Eval::init_NNUE(); } void on_eval_file(const Option& ) { Eval::NNUE::init(); }
void on_prune_at_shallow_depth_on_pv_node(const Option& o) { void on_prune_at_shallow_depth(const Option& o) {
Search::prune_at_shallow_depth_on_pv_node = o; Search::prune_at_shallow_depth = o;
} }
void on_enable_transposition_table(const Option& o) { void on_enable_transposition_table(const Option& o) {
TranspositionTable::enable_transposition_table = o; TranspositionTable::enable_transposition_table = o;
} }
void on_eval_file(const Option& ) { Eval::NNUE::init(); }
/// Our case insensitive less() function as required by UCI protocol /// Our case insensitive less() function as required by UCI protocol
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
@@ -87,26 +86,21 @@ void init(OptionsMap& o) {
o["Syzygy50MoveRule"] << Option(true); o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7); o["SyzygyProbeLimit"] << Option(7, 0, 7);
o["Use NNUE"] << Option("true var true var false var pure", "true", on_use_NNUE); o["Use NNUE"] << Option("true var true var false var pure", "true", on_use_NNUE);
// The default must follow the format nn-[SHA256 first 12 digits].nnue o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file);
// for the build process (profile-build and fishtest) to work.
o["EvalFile"] << Option("nn-82215d0fd0df.nnue", on_eval_file);
// When the evaluation function is loaded at the ucinewgame timing, it is necessary to convert the new evaluation function. // When the evaluation function is loaded at the ucinewgame timing, it is necessary to convert the new evaluation function.
// I want to hit the test eval convert command, but there is no new evaluation function // I want to hit the test eval convert command, but there is no new evaluation function
// It ends abnormally before executing this command. // It ends abnormally before executing this command.
// Therefore, with this hidden option, you can suppress the loading of the evaluation function when ucinewgame, // Therefore, with this hidden option, you can suppress the loading of the evaluation function when ucinewgame,
// Hit the test eval convert command. // Hit the test eval convert command.
o["SkipLoadingEval"] << Option(false); o["SkipLoadingEval"] << Option(false);
// how many moves to use a fixed move
// o["BookMoves"] << Option(16, 0, 10000);
// When learning the evaluation function, you can change the folder to save the evaluation function. // When learning the evaluation function, you can change the folder to save the evaluation function.
// Evalsave by default. This folder shall be prepared in advance. // Evalsave by default. This folder shall be prepared in advance.
// Automatically create a folder under this folder like "0/", "1/", ... and save the evaluation function file there. // Automatically create a folder under this folder like "0/", "1/", ... and save the evaluation function file there.
o["EvalSaveDir"] << Option("evalsave"); o["EvalSaveDir"] << Option("evalsave");
// Prune at shallow depth on PV nodes. False is recommended when using fixed depth search. // Prune at shallow depth on PV nodes. False is recommended when using fixed depth search.
o["PruneAtShallowDepthOnPvNode"] << Option(true, on_prune_at_shallow_depth_on_pv_node); o["PruneAtShallowDepth"] << Option(true, on_prune_at_shallow_depth);
// Enable transposition table. // Enable transposition table.
o["EnableTranspositionTable"] << Option(true, on_enable_transposition_table); o["EnableTranspositionTable"] << Option(true, on_enable_transposition_table);
o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file);
} }