From d21424c8d3af0f63e6317ebd0a727114442248e0 Mon Sep 17 00:00:00 2001 From: noobpwnftw Date: Tue, 8 Sep 2020 09:35:53 +0800 Subject: [PATCH] test --- README.md | 5 +- src/Makefile | 3 +- src/evaluate.cpp | 52 ++++---- src/learn/gensfen.cpp | 170 +++---------------------- src/learn/gensfen2019.cpp | 1 - src/learn/learner.cpp | 25 ---- src/nnue/features/enpassant.cpp | 2 +- src/nnue/features/half_kp.cpp | 4 +- src/nnue/features/half_relative_kp.cpp | 4 +- src/nnue/features/k.cpp | 4 +- src/nnue/features/p.cpp | 4 +- src/nnue/nnue_common.h | 2 +- src/search.cpp | 17 +-- src/tt.cpp | 4 +- src/ucioption.cpp | 2 +- 15 files changed, 61 insertions(+), 238 deletions(-) delete mode 100644 src/learn/gensfen2019.cpp diff --git a/README.md b/README.md index 6d28a998..0dcce0a6 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,10 @@ setoption name Threads value x setoption name Hash value y setoption name SyzygyPath value path isready -gensfen depth a loop b use_draw_in_training_data_generation 1 eval_limit 32000 use_raw_nnue_eval 0 +gensfen depth a loop b use_draw_in_training_data_generation 1 eval_limit 32000 ``` Specify how many threads and how much memory you would like to use with the x and y values. The option SyzygyPath is not necessary, but if you would like to use it, you must first have Syzygy endgame tablebases on your computer, which you can find [here](http://oics.olympuschess.com/tracker/index.php). You will need to have a torrent client to download these tablebases, as that is probably the fastest way to obtain them. The path is the path to the folder containing those tablebases. It does not have to be surrounded in quotes. -use_raw_nnue_eval controls if the training data generator or trainer uses raw NNUE eval values. Don't forget to set use_raw_nnue_eval 0 when initial training data are generated. Otherwise, the gensfen command will crash. - This will save a file named "generated_kifu.bin" in the same folder as the binary. Once generation is done, rename the file to something like "1billiondepth12.bin" to remember the depth and quantity of the positions and move it to a folder named "trainingdata" in the same directory as the binaries. #### Generation Parameters - Depth is the searched depth per move, or how far the engine looks forward. This value is an integer. @@ -34,6 +32,7 @@ Use the "learn" binary. Create an empty folder named "evalsave" in the same dire ``` uci setoption name SkipLoadingEval value true +setoption name Training value true setoption name Use NNUE value true setoption name Threads value x isready diff --git a/src/Makefile b/src/Makefile index 9db13e44..4f8801ee 100644 --- a/src/Makefile +++ b/src/Makefile @@ -56,7 +56,6 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp nnue/features/enpassant.cpp \ nnue/nnue_test_command.cpp \ extra/sfen_packer.cpp \ - learn/gensfen2019.cpp \ learn/learner.cpp \ learn/gensfen.cpp \ learn/convert.cpp \ @@ -908,7 +907,7 @@ learn: config-sanity EXTRALDFLAGS=' $(BLASLDFLAGS) -fopenmp ' \ all -profile-learn: net config-sanity objclean profileclean +profile-learn: config-sanity objclean profileclean @echo "" @echo "Step 1/4. Building instrumented executable ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) \ diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 8edc9bb8..9dd83e1f 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -32,13 +32,6 @@ #include "thread.h" #include "uci.h" -#ifdef EVAL_LEARN -namespace Learner -{ - extern bool use_raw_nnue_eval; -} -#endif - namespace Eval { bool useNNUE; @@ -947,27 +940,32 @@ make_v: /// evaluation of the position from the point of view of the side to move. Value Eval::evaluate(const Position& pos) { -#ifdef EVAL_LEARN - if (Learner::use_raw_nnue_eval) { - return NNUE::evaluate(pos); + if (Options["Training"]) { + Value v = NNUE::evaluate(pos); + // Damp down the evaluation linearly when shuffling + v = v * (100 - pos.rule50_count()) / 100; + + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return v; + } else { + bool classical = !Eval::useNNUE + || abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count()); + Value v = classical ? Evaluation(pos).value() + : NNUE::evaluate(pos) * 5 / 4 + Tempo; + + if (classical && Eval::useNNUE && abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count())) + v = NNUE::evaluate(pos) * 5 / 4 + Tempo; + + // Damp down the evaluation linearly when shuffling + v = v * (100 - pos.rule50_count()) / 100; + + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return v; } -#endif - - bool classical = !Eval::useNNUE - || abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count()); - Value v = classical ? Evaluation(pos).value() - : NNUE::evaluate(pos) * 5 / 4 + Tempo; - - if (classical && Eval::useNNUE && abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count())) - v = NNUE::evaluate(pos) * 5 / 4 + Tempo; - - // Damp down the evaluation linearly when shuffling - v = v * (100 - pos.rule50_count()) / 100; - - // Guarantee evaluation does not hit the tablebase range - v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); - - return v; } /// trace() is like evaluate(), but instead of returning a value, it returns diff --git a/src/learn/gensfen.cpp b/src/learn/gensfen.cpp index 6c8c455e..8526bc40 100644 --- a/src/learn/gensfen.cpp +++ b/src/learn/gensfen.cpp @@ -11,10 +11,6 @@ #include "../uci.h" #include "../syzygy/tbprobe.h" -#if defined(USE_BOOK) -#include "../extra/book/book.h" -#endif - #include #include #include @@ -54,11 +50,7 @@ namespace Learner static bool detect_draw_by_consecutive_low_score = false; static bool detect_draw_by_insufficient_mating_material = false; - // Use raw NNUE eval value in the Eval::evaluate(). - // If hybrid eval is enabled, training data - // generation and training don't work well. - // https://discordapp.com/channels/435943710472011776/733545871911813221/748524079761326192 - static bool use_raw_nnue_eval = true; + static std::vector bookStart; // Helper class for exporting Sfen struct SfenWriter @@ -313,13 +305,6 @@ namespace Learner int ply, int& random_move_c); - Value evaluate_leaf( - Position& pos, - std::vector>& states, - int ply, - int depth, - vector& pv); - // Min and max depths for search during gensfen int search_depth_min; int search_depth_max; @@ -674,69 +659,6 @@ namespace Learner return random_move_flag; } - Value MultiThinkGenSfen::evaluate_leaf( - Position& pos, - std::vector>& states, - int ply, - int depth, - vector& pv) - { - auto rootColor = pos.side_to_move(); - - for (auto m : pv) - { -#if 1 - // There should be no illegal move. This is as a debugging precaution. - if (!pos.pseudo_legal(m) || !pos.legal(m)) - { - cout << "Error! : " << pos.fen() << m << endl; - } -#endif - 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 defined(EVAL_NNUE) - if (depth < 8) - { - Eval::NNUE::update_eval(pos); - } -#endif // defined(EVAL_NNUE) - } - - // Reach leaf - Value v; - if (pos.checkers()) - { - // Sometime a king is checked. An example is a case that a checkmate is - // found in the search. If Eval::evaluate() is called whne a king is - // checked, classic eval crashes by an assertion. To avoid crashes, return - // VALUE_NONE and let the caller assign a value to the position. - v = VALUE_NONE; - } - else - { - v = Eval::evaluate(pos); - - // evaluate() returns the evaluation value on the turn side, so - // If it's a turn different from root_color, you must invert v and return it. - if (rootColor != pos.side_to_move()) - { - v = -v; - } - } - - // Rewind the pv moves. - for (auto it = pv.rbegin(); it != pv.rend(); ++it) - { - pos.undo_move(*it); - } - - return v; - } - // thread_id = 0..Threads.size()-1 void MultiThinkGenSfen::thread_worker(size_t thread_id) { @@ -760,12 +682,7 @@ namespace Learner auto th = Threads[thread_id]; auto& pos = th->rootPos; - pos.set(StartFEN, false, &si, th); - -#if defined(USE_BOOK) - // Refer to the members of BookMoveSelector defined in the search section. - auto& book = ::book; -#endif + pos.set(bookStart[prng.rand(bookStart.size())], false, &si, th); // Vector for holding the sfens in the current simulated game. PSVector a_psv; @@ -800,35 +717,6 @@ namespace Learner flush_psv(result.value()); break; } -#if defined(USE_BOOK) - if ((next_move = book.probe(pos)) != MOVE_NONE) - { - // Hit the constant track. - // The move was stored in next_move. - - // Do not use the fixed phase for learning. - sfens.clear(); - - if (random_move_minply != -1) - { - // Random move is performed with a certain - // probability even in the constant phase. - goto RANDOM_MOVE; - } - else - { - // When -1 is specified as random_move_minply, - // it points according to the standard until - // it goes out of the standard. - // Prepare an innumerable number of situations - // that have left the constant as - // ConsiderationBookMoveCount true using a huge constant - // Used for purposes such as performing - // a random move 5 times from there. - goto DO_MOVE; - } - } -#endif { auto [search_value, search_pv] = search(pos, depth, 1, nodes); @@ -916,18 +804,7 @@ namespace Learner // Get the value of evaluate() as seen from the // 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.score = search_value; psv.gamePly = ply; @@ -948,9 +825,6 @@ namespace Learner // Update the next move according to best search result. next_move = search_pv[0]; } - - RANDOM_MOVE:; - auto random_move = choose_random_move(pos, random_move_flag, ply, actual_random_move_count); if (random_move.has_value()) { @@ -962,13 +836,7 @@ namespace Learner { break; } - - // Clear the sfens that were written before the random move. - // (???) why? - a_psv.clear(); } - - DO_MOVE:; pos.do_move(next_move, states[ply]); // Call node evaluate() for each difference calculation. @@ -1095,18 +963,10 @@ namespace Learner is >> detect_draw_by_consecutive_low_score; else if (token == "detect_draw_by_insufficient_mating_material") is >> detect_draw_by_insufficient_mating_material; - else if (token == "use_raw_nnue_eval") - is >> use_raw_nnue_eval; else cout << "Error! : Illegal token " << token << endl; } -#if defined(USE_GLOBAL_OPTIONS) - // Save it for later restore. - auto oldGlobalOptions = GlobalOptions; - GlobalOptions.use_eval_hash = use_eval_hash; -#endif - // If search depth2 is not set, leave it the same as search depth. if (search_depth_max == INT_MIN) search_depth_max = search_depth_min; @@ -1130,15 +990,26 @@ namespace Learner output_file_name = output_file_name + "_" + to_hex(r.rand()) + to_hex(r.rand()); } + 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(); + } + } std::cout << "gensfen : " << endl << " search_depth_min = " << search_depth_min << " to " << search_depth_max << endl << " nodes = " << nodes << endl << " loop_max = " << loop_max << endl << " eval_limit = " << eval_limit << endl - << " thread_num (set by USI setoption) = " << thread_num << endl -#if defined(USE_BOOK) - << " book_moves (set by USI setoption) = " << Options["BookMoves"] << endl -#endif + << " thread_num = " << thread_num << endl + << " bookStart = " << bookStart.size() << endl << " random_move_minply = " << random_move_minply << endl << " random_move_maxply = " << random_move_maxply << endl << " random_move_count = " << random_move_count << endl @@ -1188,11 +1059,6 @@ namespace Learner std::cout << "gensfen finished." << endl; -#if defined(USE_GLOBAL_OPTIONS) - // Restore Global Options. - GlobalOptions = oldGlobalOptions; -#endif - } } #endif diff --git a/src/learn/gensfen2019.cpp b/src/learn/gensfen2019.cpp deleted file mode 100644 index 01293b9c..00000000 --- a/src/learn/gensfen2019.cpp +++ /dev/null @@ -1 +0,0 @@ -// just a place holder diff --git a/src/learn/learner.cpp b/src/learn/learner.cpp index 7021fd7f..a8724892 100644 --- a/src/learn/learner.cpp +++ b/src/learn/learner.cpp @@ -98,12 +98,6 @@ namespace Learner // data directly. In those cases, we set false to this variable. static bool convert_teacher_signal_to_winning_probability = true; - // Use raw NNUE eval value in the Eval::evaluate(). If hybrid eval is enabled, training data - // generation and training don't work well. - // https://discordapp.com/channels/435943710472011776/733545871911813221/748524079761326192 - // This CANNOT be static since it's used elsewhere. - bool use_raw_nnue_eval = true; - // Using WDL with win rate model instead of sigmoid static bool use_wdl = false; @@ -1616,15 +1610,6 @@ namespace Learner uint64_t eta1_epoch = 0; // eta2 is not applied by default uint64_t eta2_epoch = 0; // eta3 is not applied by default -#if defined(USE_GLOBAL_OPTIONS) - // Save it for later restore. - auto oldGlobalOptions = GlobalOptions; - // If you hit the eval hash, you can not calculate rmse etc. so turn it off. - GlobalOptions.use_eval_hash = false; - // If you hit the replacement table, pruning may occur at the previous evaluation value, so turn it off. - GlobalOptions.use_hash_probe = false; -#endif - // --- Function that only shuffles the teacher aspect // normal shuffle @@ -1796,7 +1781,6 @@ namespace Learner 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 == "convert_teacher_signal_to_winning_probability") is >> convert_teacher_signal_to_winning_probability; - else if (option == "use_raw_nnue_eval") is >> use_raw_nnue_eval; // Otherwise, it's a filename. else @@ -2076,18 +2060,9 @@ namespace Learner // Save once at the end. learn_think.save(true); -#if defined(USE_GLOBAL_OPTIONS) - // Restore Global Options. - GlobalOptions = oldGlobalOptions; -#endif } } // namespace Learner -#if defined(GENSFEN2019) -#include "gensfen2019.cpp" -#endif - - #endif // EVAL_LEARN diff --git a/src/nnue/features/enpassant.cpp b/src/nnue/features/enpassant.cpp index ea70529a..ed877322 100644 --- a/src/nnue/features/enpassant.cpp +++ b/src/nnue/features/enpassant.cpp @@ -23,7 +23,7 @@ namespace Eval { } if (perspective == BLACK) { - epSquare = rotate180(epSquare); + epSquare = flip_rank(epSquare); } auto file = file_of(epSquare); diff --git a/src/nnue/features/half_kp.cpp b/src/nnue/features/half_kp.cpp index 88e384a3..ff20a00a 100644 --- a/src/nnue/features/half_kp.cpp +++ b/src/nnue/features/half_kp.cpp @@ -23,9 +23,9 @@ 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) { - 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 diff --git a/src/nnue/features/half_relative_kp.cpp b/src/nnue/features/half_relative_kp.cpp index 015ecb73..efe85035 100644 --- a/src/nnue/features/half_relative_kp.cpp +++ b/src/nnue/features/half_relative_kp.cpp @@ -11,9 +11,9 @@ namespace NNUE { 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) { - 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 diff --git a/src/nnue/features/k.cpp b/src/nnue/features/k.cpp index 314b1338..1bb28c53 100644 --- a/src/nnue/features/k.cpp +++ b/src/nnue/features/k.cpp @@ -11,9 +11,9 @@ namespace NNUE { 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) { - return Square(int(s) ^ (bool(perspective) * 63)); + return Square(int(s) ^ (bool(perspective) * SQ_A8)); } // Index of a feature for a given king position. diff --git a/src/nnue/features/p.cpp b/src/nnue/features/p.cpp index b4a6faf9..7e008fdc 100644 --- a/src/nnue/features/p.cpp +++ b/src/nnue/features/p.cpp @@ -11,9 +11,9 @@ namespace NNUE { 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) { - 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 diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index d7ffa21a..cc54378b 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -69,7 +69,7 @@ namespace Eval::NNUE { // Version of the evaluation file - constexpr std::uint32_t kVersion = 0x7AF32F16u; + constexpr std::uint32_t kVersion = 0x7AF32F17u; // Constant used in evaluation value calculation constexpr int FV_SCALE = 16; diff --git a/src/search.cpp b/src/search.cpp index 8f258ae4..c01247bd 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -68,8 +68,6 @@ namespace { return Value(223 * (d - improving)); } - bool training; - // Reductions lookup table, initialized at startup int Reductions[MAX_MOVES]; // [depth or moveNumber] @@ -195,8 +193,6 @@ void Search::init() { for (int i = 1; i < MAX_MOVES; ++i) Reductions[i] = int((22.0 + std::log(Threads.size())) * std::log(i)); - - training = Options["Training"]; } @@ -1011,7 +1007,7 @@ moves_loop: // When in check, search starts from here // Step 12. Pruning at shallow depth (~200 Elo) if ( !rootNode - && !(training && PvNode) + && !(Options["Training"] && PvNode) && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { @@ -2070,17 +2066,6 @@ namespace Learner rootMoves.push_back(Search::RootMove(m)); assert(!rootMoves.empty()); - - //#if defined(USE_GLOBAL_OPTIONS) - // Since the generation of the substitution table for each search thread should be managed, - // Increase the generation of the substitution table for this thread because it is a new search. - //TT.new_search(th->thread_id()); - - // ª If you call new_search here, it may be a loss because you can't use the previous search result. - // Do not do this here, but caller should do TT.new_search(th->thread_id()) for each station ... - - // ¨Because we want to avoid reaching the same final diagram, use the substitution table commonly for all threads when generating teachers. - //#endif } } diff --git a/src/tt.cpp b/src/tt.cpp index 60a3a5f1..5e1f53d2 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -115,7 +115,9 @@ void TranspositionTable::clear() { /// TTEntry t2 if its replace value is greater than that of t2. TTEntry* TranspositionTable::probe(const Key key, bool& found) const { - + if (Options["Training"]) { + return found = false, first_entry(0); + } TTEntry* const tte = first_entry(key); const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 0007b559..1517326e 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -82,7 +82,7 @@ void init(OptionsMap& o) { o["Use NNUE"] << Option(true, on_use_NNUE); // The default must follow the format nn-[SHA256 first 12 digits].nnue // for the build process (profile-build and fishtest) to work. - o["EvalFile"] << Option("nn-82215d0fd0df.nnue", on_eval_file); + o["EvalFile"] << Option("nn.bin", on_eval_file); #ifdef EVAL_NNUE // 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