Merge remote-tracking branch 'remotes/nodchip/master' into trainer

This commit is contained in:
noobpwnftw
2020-09-19 15:41:30 +08:00
57 changed files with 9136 additions and 1469 deletions
+34 -51
View File
@@ -39,8 +39,9 @@ PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
### Built-in benchmark for pgo-builds
PGO_TRAINING_DATA_FILE = pgo_training_data.bin
PGOBENCH = ./$(EXE) bench
PGOGENSFEN = ./$(EXE) gensfen depth 3 loop 1000
PGOGENSFEN = ./$(EXE) gensfen depth 6 loop 10000 output_file_name $(PGO_TRAINING_DATA_FILE)
### Source and object files
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
@@ -55,8 +56,8 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp
nnue/features/castling_right.cpp \
nnue/features/enpassant.cpp \
nnue/nnue_test_command.cpp \
extra/sfen_packer.cpp \
learn/learner.cpp \
learn/sfen_packer.cpp \
learn/learn.cpp \
learn/gensfen.cpp \
learn/convert.cpp \
learn/learning_tools.cpp \
@@ -116,6 +117,7 @@ else
SUPPORTED_ARCH=false
endif
blas = no
optimize = yes
debug = no
sanitize = no
@@ -135,20 +137,6 @@ vnni512 = no
neon = no
STRIP = strip
### BLAS libraries
ifeq ($(KERNEL),Linux)
BLASCXXFLAGS =
BLASLDFLAGS = -lopenblas
else
BLASCXXFLAGS = -I/mingw64/include/OpenBLAS
ifeq ($(debug),yes)
BLASLDFLAGS = -lopenblas -Wl,-static
else
BLASLDFLAGS = -lopenblas -Wl,-s -static
endif
endif
### 2.2 Architecture specific
ifeq ($(findstring x86,$(ARCH)),x86)
@@ -325,9 +313,9 @@ endif
### ==========================================================================
### 3.1 Selecting compiler (default = gcc)
CXXFLAGS += -g -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) $(LEARNCXXFLAGS)
DEPENDFLAGS += -std=c++17
LDFLAGS += $(EXTRALDFLAGS) $(LEARNLDFLAGS)
CXXFLAGS += -g -Wall -Wcast-qual -fno-exceptions -std=c++17 -fopenmp -I. $(EXTRACXXFLAGS)
LDFLAGS += -fopenmp $(EXTRALDFLAGS)
DEPENDFLAGS += -std=c++17 -I.
ifeq ($(COMP),)
COMP=gcc
@@ -479,14 +467,33 @@ ifneq ($(comp),mingw)
endif
endif
### 3.2.1 Debugging
### 3.2.1. BLAS libraries
ifeq ($(blas), yes)
LDFLAGS += -lopenblas
ifeq ($(KERNEL),Linux)
LDFLAGS +=
else
CXXFLAGS += -I/mingw64/include/OpenBLAS
ifeq ($(debug),yes)
LDFLAGS += -Wl,-static
else
LDFLAGS += -Wl,-s -static
endif
endif
CXXFLAGS += -DUSE_BLAS
endif
### 3.2.2 Debugging
ifeq ($(debug),no)
CXXFLAGS += -DNDEBUG
else
CXXFLAGS += -g
endif
### 3.2.2 Debugging with undefined behavior sanitizers
### 3.2.3 Debugging with undefined behavior sanitizers
ifneq ($(sanitize),no)
CXXFLAGS += -g3 -fsanitize=$(sanitize)
LDFLAGS += -fsanitize=$(sanitize)
@@ -740,16 +747,17 @@ endif
config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
clang-profile-use clang-profile-make
build: config-sanity net
build: config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
profile-build: net config-sanity objclean profileclean
profile-build: config-sanity objclean profileclean
@echo ""
@echo "Step 1/4. Building instrumented executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
@echo ""
@echo "Step 2/4. Running benchmark for pgo-build ..."
$(PGOBENCH) > /dev/null
$(PGOGENSFEN) > /dev/null
@echo ""
@echo "Step 3/4. Building optimized executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
@@ -803,6 +811,7 @@ profileclean:
@rm -rf profdir
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s ./learn/*.gcda ./extra/*.gcda ./eval/*.gcda
@rm -f stockfish.profdata *.profraw
@rm -f $(PGO_TRAINING_DATA_FILE)
default:
help
@@ -907,33 +916,7 @@ icc-profile-use:
EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
all
learn: config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
EXTRACXXFLAGS=' -DEVAL_LEARN -DNNUE_EMBEDDING_OFF -DUSE_BLAS $(BLASCXXFLAGS) -fopenmp ' \
EXTRALDFLAGS=' $(BLASLDFLAGS) -fopenmp ' \
all
profile-learn: config-sanity objclean profileclean
@echo ""
@echo "Step 1/4. Building instrumented executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) \
LEARNCXXFLAGS=' -DEVAL_LEARN -DNNUE_EMBEDDING_OFF -DUSE_BLAS $(BLASCXXFLAGS) -fopenmp ' \
LEARNLDFLAGS=' $(BLASLDFLAGS) -fopenmp '
@echo ""
@echo "Step 2/4. Running benchmark for pgo-build ..."
$(PGOGENSFEN)
@echo ""
@echo "Step 3/4. Building optimized executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) \
LEARNCXXFLAGS=' -DEVAL_LEARN -DNNUE_EMBEDDING_OFF -DUSE_BLAS $(BLASCXXFLAGS) -fopenmp ' \
LEARNLDFLAGS=' $(BLASLDFLAGS) -fopenmp '
@echo ""
@echo "Step 4/4. Deleting profile data ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
rm generated_kifu.bin
.depend:
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@
-include .depend
-4
View File
@@ -1,8 +1,6 @@
#ifndef _EVALUATE_COMMON_H_
#define _EVALUATE_COMMON_H_
#if defined(EVAL_LEARN)
// A common header-like function for modern evaluation functions.
#include <string>
@@ -20,6 +18,4 @@ namespace Eval
double get_eta();
}
#endif // defined(EVAL_LEARN)
#endif // _EVALUATE_COMMON_H_
+47 -77
View File
@@ -36,42 +36,30 @@
#include "uci.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 Eval::NNUE;
namespace Eval {
bool useNNUE;
UseNNUEMode useNNUE;
string eval_file_loaded = "None";
/// init_NNUE() tries to load a nnue network at startup time, or when the engine
/// 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.
/// We search the given network in three locations: internally (the default
/// network may be embedded in the binary), in the active working directory and
/// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
/// variable to have the engine search in a special directory in their distro.
static UseNNUEMode nnue_mode_from_option(const UCI::Option& mode)
{
if (mode == "false")
return UseNNUEMode::False;
else if (mode == "true")
return UseNNUEMode::True;
else if (mode == "pure")
return UseNNUEMode::Pure;
return UseNNUEMode::False;
}
void init_NNUE() {
useNNUE = Options["Use NNUE"];
if (!useNNUE)
useNNUE = nnue_mode_from_option(Options["Use NNUE"]);
if (useNNUE == UseNNUEMode::False)
return;
string eval_file = string(Options["EvalFile"]);
@@ -79,35 +67,17 @@ namespace Eval {
#if defined(DEFAULT_NNUE_DIRECTORY)
#define stringify2(x) #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
vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
vector<string> dirs = { "" , CommandLine::binaryDirectory };
#endif
for (string directory : dirs)
if (eval_file_loaded != eval_file)
{
if (directory != "<internal>")
{
ifstream stream(directory + eval_file, ios::binary);
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;
}
ifstream stream(directory + eval_file, ios::binary);
if (load_eval(eval_file, stream))
eval_file_loaded = eval_file;
}
}
@@ -116,7 +86,7 @@ namespace Eval {
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::init(defaults);
@@ -136,7 +106,7 @@ namespace Eval {
exit(EXIT_FAILURE);
}
if (useNNUE)
if (useNNUE != UseNNUEMode::False)
sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
else
sync_cout << "info string classical evaluation enabled" << sync_endl;
@@ -1014,32 +984,32 @@ make_v:
/// evaluation of the position from the point of view of the side to move.
Value Eval::evaluate(const Position& pos) {
if (Options["Training"]) {
return NNUE::evaluate(pos);
} else {
// Use classical eval if there is a large imbalance
// If there is a moderate imbalance, use classical eval with probability (1/8),
// as derived from the node counter.
bool useClassical = abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count());
bool classical = !Eval::useNNUE
|| useClassical
|| (abs(eg_value(pos.psq_score())) > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB));
Value v = classical ? Evaluation<NO_TRACE>(pos).value() : NNUE::evaluate(pos);
if ( useClassical
&& Eval::useNNUE
&& abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count()))
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;
if (useNNUE == UseNNUEMode::Pure) {
return NNUE::evaluate(pos);
}
// Use classical eval if there is a large imbalance
// If there is a moderate imbalance, use classical eval with probability (1/8),
// as derived from the node counter.
bool useClassical = abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count());
bool classical = (useNNUE == UseNNUEMode::False)
|| useClassical
|| (abs(eg_value(pos.psq_score())) > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB));
Value v = classical ? Evaluation<NO_TRACE>(pos).value()
: NNUE::evaluate(pos);
if ( useClassical
&& useNNUE != UseNNUEMode::False
&& abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count()))
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;
}
/// trace() is like evaluate(), but instead of returning a value, it returns
@@ -1087,7 +1057,7 @@ std::string Eval::trace(const Position& pos) {
ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n";
if (Eval::useNNUE)
if (useNNUE != UseNNUEMode::False)
{
v = NNUE::evaluate(pos);
v = pos.side_to_move() == WHITE ? v : -v;
+7 -1
View File
@@ -26,11 +26,17 @@
class Position;
namespace Eval {
enum struct UseNNUEMode
{
False,
True,
Pure
};
std::string trace(const Position& pos);
Value evaluate(const Position& pos);
extern bool useNNUE;
extern UseNNUEMode useNNUE;
extern std::string eval_file_loaded;
void init_NNUE();
void verify_NNUE();
File diff suppressed because it is too large Load Diff
-432
View File
@@ -1,432 +0,0 @@
#if defined (EVAL_LEARN)
#include "../misc.h"
#include "../position.h"
#include <sstream>
#include <fstream>
#include <cstring> // std::memset()
using namespace std;
// -----------------------------------
// stage compression/decompression
// -----------------------------------
// Class that handles bitstream
// useful when doing aspect encoding
struct BitStream
{
// Set the memory to store the data in advance.
// Assume that memory is cleared to 0.
void set_data(uint8_t* data_) { data = data_; reset(); }
// Get the pointer passed in set_data().
uint8_t* get_data() const { return data; }
// Get the cursor.
int get_cursor() const { return bit_cursor; }
// reset the cursor
void reset() { bit_cursor = 0; }
// Write 1bit to the stream.
// If b is non-zero, write out 1. If 0, write 0.
void write_one_bit(int b)
{
if (b)
data[bit_cursor / 8] |= 1 << (bit_cursor & 7);
++bit_cursor;
}
// Get 1 bit from the stream.
int read_one_bit()
{
int b = (data[bit_cursor / 8] >> (bit_cursor & 7)) & 1;
++bit_cursor;
return b;
}
// write n bits of data
// Data shall be written out from the lower order of d.
void write_n_bit(int d, int n)
{
for (int i = 0; i <n; ++i)
write_one_bit(d & (1 << i));
}
// read n bits of data
// Reverse conversion of write_n_bit().
int read_n_bit(int n)
{
int result = 0;
for (int i = 0; i < n; ++i)
result |= read_one_bit() ? (1 << i) : 0;
return result;
}
private:
// Next bit position to read/write.
int bit_cursor;
// data entity
uint8_t* data;
};
// Huffman coding
// * is simplified from mini encoding to make conversion easier.
//
// 1 box on the board (other than NO_PIECE) = 2 to 6 bits (+ 1-bit flag + 1-bit forward and backward)
// 1 piece of hand piece = 1-5bit (+ 1-bit flag + 1bit ahead and behind)
//
// empty xxxxx0 + 0 (none)
// step xxxx01 + 2 xxxx0 + 2
// incense xx0011 + 2 xx001 + 2
// Katsura xx1011 + 2 xx101 + 2
// silver xx0111 + 2 xx011 + 2
// Gold x01111 + 1 x0111 + 1 // Gold is valid and has no flags.
// corner 011111 + 2 01111 + 2
// Fly 111111 + 2 11111 + 2
//
// Assuming all pieces are on the board,
// Sky 81-40 pieces = 41 boxes = 41bit
// Walk 4bit*18 pieces = 72bit
// Incense 6bit*4 pieces = 24bit
// Katsura 6bit*4 pieces = 24bit
// Silver 6bit*4 pieces = 24bit
// Gold 6bit* 4 pieces = 24bit
// corner 8bit* 2 pieces = 16bit
// Fly 8bit* 2 pieces = 16bit
// -------
// 241bit + 1bit (turn) + 7bit × 2 (King's position after) = 256bit
//
// When the piece on the board moves to the hand piece, the piece on the board becomes empty, so the box on the board can be expressed with 1 bit,
// Since the hand piece can be expressed by 1 bit less than the piece on the board, the total number of bits does not change in the end.
// Therefore, in this expression, any aspect can be expressed by this bit number.
// It is a hand piece and no flag is required, but if you include this, the bit number of the piece on the board will be -1
// Since the total number of bits can be fixed, we will include this as well.
// Huffman Encoding
//
// Empty xxxxxxx0
// Pawn xxxxx001 + 1 bit (Side to move)
// Knight xxxxx011 + 1 bit (Side to move)
// Bishop xxxxx101 + 1 bit (Side to move)
// Rook xxxxx111 + 1 bit (Side to move)
struct HuffmanedPiece
{
int code; // how it will be coded
int bits; // How many bits do you have
};
HuffmanedPiece huffman_table[] =
{
{0b0000,1}, // NO_PIECE
{0b0001,4}, // PAWN
{0b0011,4}, // KNIGHT
{0b0101,4}, // BISHOP
{0b0111,4}, // ROOK
{0b1001,4}, // QUEEN
};
// Class for compressing/decompressing sfen
// sfen can be packed to 256bit (32bytes) by Huffman coding.
// This is proven by mini. The above is Huffman coding.
//
// Internal format = 1-bit turn + 7-bit king position *2 + piece on board (Huffman coding) + hand piece (Huffman coding)
// Side to move (White = 0, Black = 1) (1bit)
// White King Position (6 bits)
// Black King Position (6 bits)
// Huffman Encoding of the board
// Castling availability (1 bit x 4)
// En passant square (1 or 1 + 6 bits)
// Rule 50 (6 bits)
// Game play (8 bits)
//
// TODO(someone): Rename SFEN to FEN.
//
struct SfenPacker
{
// Pack sfen and store in data[32].
void pack(const Position& pos)
{
// cout << pos;
memset(data, 0, 32 /* 256bit */);
stream.set_data(data);
// turn
// Side to move.
stream.write_one_bit((int)(pos.side_to_move()));
// 7-bit positions for leading and trailing balls
// White king and black king, 6 bits for each.
for(auto c: Colors)
stream.write_n_bit(pos.king_square(c), 6);
// Write the pieces on the board other than the kings.
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
{
Piece pc = pos.piece_on(make_square(f, r));
if (type_of(pc) == KING)
continue;
write_board_piece_to_stream(pc);
}
}
// TODO(someone): Support chess960.
stream.write_one_bit(pos.can_castle(WHITE_OO));
stream.write_one_bit(pos.can_castle(WHITE_OOO));
stream.write_one_bit(pos.can_castle(BLACK_OO));
stream.write_one_bit(pos.can_castle(BLACK_OOO));
if (pos.ep_square() == SQ_NONE) {
stream.write_one_bit(0);
}
else {
stream.write_one_bit(1);
stream.write_n_bit(static_cast<int>(pos.ep_square()), 6);
}
stream.write_n_bit(pos.state()->rule50, 6);
stream.write_n_bit(1 + (pos.game_ply()-(pos.side_to_move() == BLACK)) / 2, 8);
assert(stream.get_cursor() <= 256);
}
// sfen packed by pack() (256bit = 32bytes)
// Or sfen to decode with unpack()
uint8_t *data; // uint8_t[32];
//private:
// Position::set_from_packed_sfen(uint8_t data[32]) I want to use these functions, so the line is bad, but I want to keep it public.
BitStream stream;
// Output the board pieces to stream.
void write_board_piece_to_stream(Piece pc)
{
// piece type
PieceType pr = type_of(pc);
auto c = huffman_table[pr];
stream.write_n_bit(c.code, c.bits);
if (pc == NO_PIECE)
return;
// first and second flag
stream.write_one_bit(color_of(pc));
}
// Read one board piece from stream
Piece read_board_piece_from_stream()
{
PieceType pr = NO_PIECE_TYPE;
int code = 0, bits = 0;
while (true)
{
code |= stream.read_one_bit() << bits;
++bits;
assert(bits <= 6);
for (pr = NO_PIECE_TYPE; pr <KING; ++pr)
if (huffman_table[pr].code == code
&& huffman_table[pr].bits == bits)
goto Found;
}
Found:;
if (pr == NO_PIECE_TYPE)
return NO_PIECE;
// first and second flag
Color c = (Color)stream.read_one_bit();
return make_piece(c, pr);
}
};
// -----------------------------------
// Add to Position class
// -----------------------------------
// Add a function that directly unpacks for speed. It's pretty tough.
// 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.
int Position::set_from_packed_sfen(const PackedSfen& sfen , StateInfo * si, Thread* th, bool mirror)
{
SfenPacker packer;
auto& stream = packer.stream;
// TODO: separate streams for writing and reading. Here we actually have to
// const_cast which is not safe in the long run.
stream.set_data(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(&sfen)));
std::memset(this, 0, sizeof(Position));
std::memset(si, 0, sizeof(StateInfo));
std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
st = si;
// Active color
sideToMove = (Color)stream.read_one_bit();
pieceList[W_KING][0] = SQUARE_NB;
pieceList[B_KING][0] = SQUARE_NB;
// First the position of the ball
if (mirror)
{
for (auto c : Colors)
board[flip_file((Square)stream.read_n_bit(6))] = make_piece(c, KING);
}
else
{
for (auto c : Colors)
board[stream.read_n_bit(6)] = make_piece(c, KING);
}
// Piece placement
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
{
auto sq = make_square(f, r);
if (mirror) {
sq = flip_file(sq);
}
// it seems there are already balls
Piece pc;
if (type_of(board[sq]) != KING)
{
assert(board[sq] == NO_PIECE);
pc = packer.read_board_piece_from_stream();
}
else
{
pc = board[sq];
board[sq] = NO_PIECE; // put_piece() will catch ASSERT unless you remove it all.
}
// There may be no pieces, so skip in that case.
if (pc == NO_PIECE)
continue;
put_piece(Piece(pc), sq);
//cout << sq << ' ' << board[sq] << ' ' << stream.get_cursor() << endl;
if (stream.get_cursor()> 256)
return 1;
//assert(stream.get_cursor() <= 256);
}
}
// Castling availability.
// TODO(someone): Support chess960.
st->castlingRights = 0;
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(WHITE, SQ_H1); piece_on(rsq) != W_ROOK; --rsq) {}
set_castling_right(WHITE, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(WHITE, SQ_A1); piece_on(rsq) != W_ROOK; ++rsq) {}
set_castling_right(WHITE, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(BLACK, SQ_H1); piece_on(rsq) != B_ROOK; --rsq) {}
set_castling_right(BLACK, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(BLACK, SQ_A1); piece_on(rsq) != B_ROOK; ++rsq) {}
set_castling_right(BLACK, rsq);
}
// En passant square. Ignore if no pawn capture is possible
if (stream.read_one_bit()) {
Square ep_square = static_cast<Square>(stream.read_n_bit(6));
if (mirror) {
ep_square = flip_file(ep_square);
}
st->epSquare = ep_square;
if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))
|| !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))))
st->epSquare = SQ_NONE;
}
else {
st->epSquare = SQ_NONE;
}
// Halfmove clock
st->rule50 = static_cast<Square>(stream.read_n_bit(6));
// Fullmove number
gamePly = static_cast<Square>(stream.read_n_bit(8));
// Convert from fullmove starting from 1 to gamePly starting from 0,
// handle also common incorrect FEN with fullmove = 0.
gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
assert(stream.get_cursor() <= 256);
chess960 = false;
thisThread = th;
set_state(st);
//std::cout << *this << std::endl;
assert(pos_is_ok());
return 0;
}
// Give the board, hand piece, and turn, and return the sfen.
//std::string Position::sfen_from_rawdata(Piece board[81], Hand hands[2], Color turn, int gamePly_)
//{
// // Copy it to an internal structure and call sfen() if the conversion process depends only on it
// // Maybe it will be converted normally...
// Position pos;
//
// memcpy(pos.board, board, sizeof(Piece) * 81);
// memcpy(pos.hand, hands, sizeof(Hand) * 2);
// pos.sideToMove = turn;
// pos.gamePly = gamePly_;
//
// return pos.sfen();
//
// // Implementation of ↑ is beautiful, but slow.
// // This is a bottleneck when learning a large amount of game records, so write a function to unpack directly.
//}
// Get the packed sfen. Returns to the buffer specified in the argument.
void Position::sfen_pack(PackedSfen& sfen)
{
SfenPacker sp;
sp.data = (uint8_t*)&sfen;
sp.pack(*this);
}
//// Unpack the packed sfen. Returns an sfen string.
//std::string Position::sfen_unpack(const PackedSfen& sfen)
//{
// SfenPacker sp;
// sp.data = (uint8_t*)&sfen;
// return sp.unpack();
//}
#endif // USE_SFEN_PACKER
+117 -12
View File
@@ -1,16 +1,19 @@
#if defined(EVAL_LEARN)
#include "convert.h"
#include "multi_think.h"
#include "uci.h"
#include "misc.h"
#include "thread.h"
#include "position.h"
#include "tt.h"
// evaluate header for learning
#include "../eval/evaluate_common.h"
#include "eval/evaluate_common.h"
#include "learn.h"
#include "multi_think.h"
#include "../uci.h"
#include "../syzygy/tbprobe.h"
#include "../misc.h"
#include "../thread.h"
#include "../position.h"
#include "../tt.h"
#include "extra/nnue_data_binpack_format.h"
#include "syzygy/tbprobe.h"
#include <sstream>
#include <fstream>
@@ -119,7 +122,7 @@ namespace Learner
else if (token == "score") {
double 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].
score = (score - src_score_min_value) / (src_score_max_value - src_score_min_value);
// Scale to [dest_score_min_value, dest_score_max_value].
@@ -497,5 +500,107 @@ namespace Learner
ofs.close();
std::cout << "all done" << std::endl;
}
static inline const std::string plain_extension = ".plain";
static inline const std::string bin_extension = ".bin";
static inline const std::string binpack_extension = ".binpack";
static bool file_exists(const std::string& name)
{
std::ifstream f(name);
return f.good();
}
static bool ends_with(const std::string& lhs, const std::string& end)
{
if (end.size() > lhs.size()) return false;
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
}
static bool is_convert_of_type(
const std::string& input_path,
const std::string& output_path,
const std::string& expected_input_extension,
const std::string& expected_output_extension)
{
return ends_with(input_path, expected_input_extension)
&& ends_with(output_path, expected_output_extension);
}
using ConvertFunctionType = void(std::string inputPath, std::string outputPath, std::ios_base::openmode om);
static ConvertFunctionType* get_convert_function(const std::string& input_path, const std::string& output_path)
{
if (is_convert_of_type(input_path, output_path, plain_extension, bin_extension))
return binpack::convertPlainToBin;
if (is_convert_of_type(input_path, output_path, plain_extension, binpack_extension))
return binpack::convertPlainToBinpack;
if (is_convert_of_type(input_path, output_path, bin_extension, plain_extension))
return binpack::convertBinToPlain;
if (is_convert_of_type(input_path, output_path, bin_extension, binpack_extension))
return binpack::convertBinToBinpack;
if (is_convert_of_type(input_path, output_path, binpack_extension, plain_extension))
return binpack::convertBinpackToPlain;
if (is_convert_of_type(input_path, output_path, binpack_extension, bin_extension))
return binpack::convertBinpackToBin;
return nullptr;
}
static void convert(const std::string& input_path, const std::string& output_path, std::ios_base::openmode om)
{
if(!file_exists(input_path))
{
std::cerr << "Input file does not exist.\n";
return;
}
auto func = get_convert_function(input_path, output_path);
if (func != nullptr)
{
func(input_path, output_path, om);
}
else
{
std::cerr << "Conversion between files of these types is not supported.\n";
}
}
static void convert(const std::vector<std::string>& args)
{
if (args.size() < 2 || args.size() > 3)
{
std::cerr << "Invalid arguments.\n";
std::cerr << "Usage: convert from_path to_path [append]\n";
return;
}
const bool append = (args.size() == 3) && (args[2] == "append");
const std::ios_base::openmode openmode =
append
? std::ios_base::app
: std::ios_base::trunc;
convert(args[0], args[1], openmode);
}
void convert(istringstream& is)
{
std::vector<std::string> args;
while (true)
{
std::string token = "";
is >> token;
if (token == "")
break;
args.push_back(token);
}
convert(args);
}
}
#endif
+35
View File
@@ -0,0 +1,35 @@
#ifndef _CONVERT_H_
#define _CONVERT_H_
#include <vector>
#include <string>
#include <sstream>
namespace Learner {
void convert_bin_from_pgn_extract(
const std::vector<std::string>& filenames,
const std::string& output_file_name,
const bool pgn_eval_side_to_move,
const bool convert_no_eval_fens_as_score_zero);
void convert_bin(
const std::vector<std::string>& filenames,
const std::string& output_file_name,
const int ply_minimum,
const int ply_maximum,
const int interpolate_eval,
const int src_score_min_value,
const int src_score_max_value,
const int dest_score_min_value,
const int dest_score_max_value,
const bool check_invalid_fen,
const bool check_illegal_move);
void convert_plain(
const std::vector<std::string>& filenames,
const std::string& output_file_name);
void convert(std::istringstream& is);
}
#endif
+180 -40
View File
@@ -1,17 +1,23 @@
#if defined(EVAL_LEARN)
#include "gensfen.h"
#include "../eval/evaluate_common.h"
#include "../misc.h"
#include "../nnue/evaluate_nnue_learner.h"
#include "../position.h"
#include "../syzygy/tbprobe.h"
#include "../thread.h"
#include "../tt.h"
#include "../uci.h"
#include "learn.h"
#include "packed_sfen.h"
#include "multi_think.h"
#include "../syzygy/tbprobe.h"
#include "misc.h"
#include "position.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
#include "eval/evaluate_common.h"
#include "extra/nnue_data_binpack_format.h"
#include "nnue/evaluate_nnue_learner.h"
#include "syzygy/tbprobe.h"
#include <chrono>
#include <climits>
#include <cmath>
@@ -33,11 +39,107 @@ using namespace std;
namespace Learner
{
enum struct SfenOutputType
{
Bin,
Binpack
};
static bool write_out_draw_game_in_training_data_generation = false;
static bool detect_draw_by_consecutive_low_score = false;
static bool detect_draw_by_insufficient_mating_material = false;
static std::vector<std::string> bookStart;
static SfenOutputType sfen_output_type = SfenOutputType::Bin;
static bool ends_with(const std::string& lhs, const std::string& end)
{
if (end.size() > lhs.size()) return false;
return std::equal(end.rbegin(), end.rend(), lhs.rbegin());
}
static std::string filename_with_extension(const std::string& filename, const std::string& ext)
{
if (ends_with(filename, ext))
{
return filename;
}
else
{
return filename + "." + ext;
}
}
struct BasicSfenOutputStream
{
virtual void write(const PSVector& sfens) = 0;
virtual ~BasicSfenOutputStream() {}
};
struct BinSfenOutputStream : BasicSfenOutputStream
{
static constexpr auto openmode = ios::out | ios::binary | ios::app;
static inline const std::string extension = "bin";
BinSfenOutputStream(std::string filename) :
m_stream(filename_with_extension(filename, extension), openmode)
{
}
void write(const PSVector& sfens) override
{
m_stream.write(reinterpret_cast<const char*>(sfens.data()), sizeof(PackedSfenValue) * sfens.size());
}
~BinSfenOutputStream() override {}
private:
fstream m_stream;
};
struct BinpackSfenOutputStream : BasicSfenOutputStream
{
static constexpr auto openmode = ios::out | ios::binary | ios::app;
static inline const std::string extension = "binpack";
BinpackSfenOutputStream(std::string filename) :
m_stream(filename_with_extension(filename, extension), openmode)
{
}
void write(const PSVector& sfens) override
{
static_assert(sizeof(binpack::nodchip::PackedSfenValue) == sizeof(PackedSfenValue));
for(auto& sfen : sfens)
{
// The library uses a type that's different but layout-compatibile.
binpack::nodchip::PackedSfenValue e;
std::memcpy(&e, &sfen, sizeof(binpack::nodchip::PackedSfenValue));
m_stream.addTrainingDataEntry(binpack::packedSfenValueToTrainingDataEntry(e));
}
}
~BinpackSfenOutputStream() override {}
private:
binpack::CompressedTrainingDataEntryWriter m_stream;
};
static std::unique_ptr<BasicSfenOutputStream> create_new_sfen_output(const std::string& filename)
{
switch(sfen_output_type)
{
case SfenOutputType::Bin:
return std::make_unique<BinSfenOutputStream>(filename);
case SfenOutputType::Binpack:
return std::make_unique<BinpackSfenOutputStream>(filename);
}
assert(false);
return nullptr;
}
// Helper class for exporting Sfen
struct SfenWriter
@@ -55,7 +157,7 @@ namespace Learner
sfen_buffers_pool.reserve((size_t)thread_num * 10);
sfen_buffers.resize(thread_num);
output_file_stream.open(filename_, ios::out | ios::binary | ios::app);
output_file_stream = create_new_sfen_output(filename_);
filename = filename_;
finished = false;
@@ -65,7 +167,7 @@ namespace Learner
{
finished = true;
file_worker_thread.join();
output_file_stream.close();
output_file_stream.reset();
#if defined(_DEBUG)
{
@@ -134,9 +236,6 @@ namespace Learner
{
// Also output the current time to console.
sync_cout << endl << sfen_write_count << " sfens , at " << now_string() << sync_endl;
// This is enough for flush().
output_file_stream.flush();
};
while (!finished || sfen_buffers_pool.size())
@@ -160,7 +259,7 @@ namespace Learner
{
for (auto& buf : buffers)
{
output_file_stream.write(reinterpret_cast<const char*>(buf->data()), sizeof(PackedSfenValue) * buf->size());
output_file_stream->write(*buf);
sfen_write_count += buf->size();
@@ -171,8 +270,6 @@ namespace Learner
{
sfen_write_count_current_file = 0;
output_file_stream.close();
// Sequential number attached to the file
int n = (int)(sfen_write_count / save_every);
@@ -180,7 +277,7 @@ namespace Learner
// Add ios::app in consideration of overwriting.
// (Depending on the operation, it may not be necessary.)
string new_filename = filename + "_" + std::to_string(n);
output_file_stream.open(new_filename, ios::out | ios::binary | ios::app);
output_file_stream = create_new_sfen_output(new_filename);
cout << endl << "output sfen file = " << new_filename << endl;
}
@@ -214,7 +311,7 @@ namespace Learner
private:
fstream output_file_stream;
std::unique_ptr<BasicSfenOutputStream> output_file_stream;
// A new net is saved after every save_every sfens are processed.
uint64_t save_every = std::numeric_limits<uint64_t>::max();
@@ -260,7 +357,8 @@ namespace Learner
// It must be 2**N because it will be used as the mask to calculate hash_index.
static_assert((GENSFEN_HASH_SIZE& (GENSFEN_HASH_SIZE - 1)) == 0);
MultiThinkGenSfen(int search_depth_min_, int search_depth_max_, SfenWriter& sw_) :
MultiThinkGenSfen(int search_depth_min_, int search_depth_max_, SfenWriter& sw_, const std::string& seed) :
MultiThink(seed),
search_depth_min(search_depth_min_),
search_depth_max(search_depth_max_),
sfen_writer(sw_)
@@ -759,20 +857,6 @@ namespace Learner
break;
}
if (pos.count<ALL_PIECES>() <= 6) {
Tablebases::ProbeState probe_state;
Tablebases::WDLScore wdl = Tablebases::probe_wdl(pos, &probe_state);
assert(wdl != Tablebases::WDLScore::WDLScoreNone);
if (wdl == Tablebases::WDLScore::WDLWin) {
flush_psv(1);
} else if (wdl == Tablebases::WDLScore::WDLLoss) {
flush_psv(-1);
} else {
flush_psv(0);
}
break;
}
{
auto [search_value, search_pv] = search(pos, depth, 1, nodes);
@@ -819,6 +903,25 @@ namespace Learner
goto SKIP_SAVE;
}
// Look into the position hashtable to see if the same
// position was seen before.
// This is a good heuristic to exlude already seen
// positions without many false positives.
{
auto key = pos.key();
auto hash_index = (size_t)(key & (GENSFEN_HASH_SIZE - 1));
auto old_key = hash[hash_index];
if (key == old_key)
{
goto SKIP_SAVE;
}
else
{
// Replace with the current key.
hash[hash_index] = key;
}
}
// Pack the current position into a packed sfen and save it into the buffer.
{
a_psv.emplace_back(PackedSfenValue());
@@ -916,7 +1019,7 @@ namespace Learner
int write_maxply = 400;
// File name to write
string output_file_name = "generated_kifu.bin";
string output_file_name = "generated_kifu";
string token;
@@ -927,6 +1030,9 @@ namespace Learner
// Add a random number to the end of the file name.
bool random_file_name = false;
std::string sfen_format;
std::string seed;
while (true)
{
token = "";
@@ -980,10 +1086,26 @@ 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 == "sfen_format")
is >> sfen_format;
else if (token == "seed")
is >> seed;
else
cout << "Error! : Illegal token " << token << endl;
}
if (!sfen_format.empty())
{
if (sfen_format == "bin")
sfen_output_type = SfenOutputType::Bin;
else if (sfen_format == "binpack")
sfen_output_type = SfenOutputType::Binpack;
else
{
cout << "Unknown sfen format `" << sfen_format << "`. Using bin\n";
}
}
// 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;
@@ -994,7 +1116,7 @@ namespace Learner
{
// Give a random number to output_file_name at this point.
// Do not use std::random_device(). Because it always the same integers on MinGW.
PRNG r(std::chrono::system_clock::now().time_since_epoch().count());
PRNG r(seed);
// Just in case, reassign the random numbers.
for (int i = 0; i < 10; ++i)
r.rand(1);
@@ -1018,6 +1140,8 @@ namespace Learner
bookStart.push_back(line);
}
myfile.close();
} else {
bookStart.push_back(StartFEN);
}
}
std::cout << "gensfen : " << endl
@@ -1048,12 +1172,30 @@ namespace Learner
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"].
{
SfenWriter sfen_writer(output_file_name, thread_num);
sfen_writer.set_save_interval(save_every);
MultiThinkGenSfen multi_think(search_depth_min, search_depth_max, sfen_writer);
MultiThinkGenSfen multi_think(search_depth_min, search_depth_max, sfen_writer, seed);
multi_think.nodes = nodes;
multi_think.set_loop_max(loop_max);
multi_think.eval_limit = eval_limit;
@@ -1074,7 +1216,5 @@ namespace Learner
}
std::cout << "gensfen finished." << endl;
}
}
#endif
+14
View File
@@ -0,0 +1,14 @@
#ifndef _GENSFEN_H_
#define _GENSFEN_H_
#include "position.h"
#include <sstream>
namespace Learner {
// Automatic generation of teacher position
void gen_sfen(Position& pos, std::istringstream& is);
}
#endif
+1 -1
View File
@@ -7,7 +7,7 @@
// Floating point operation by 16bit type
// Assume that the float type code generated by the compiler is in IEEE 754 format and use it.
#include "../types.h"
#include "types.h"
namespace HalfFloat
{
File diff suppressed because it is too large Load Diff
+32 -95
View File
@@ -1,10 +1,6 @@
#ifndef _LEARN_H_
#define _LEARN_H_
#if defined(EVAL_LEARN)
#include <vector>
// ----------------------
// Floating point for learning
// ----------------------
@@ -14,7 +10,7 @@
// Even if it is a double type, there is almost no difference in the way of convergence, so fix it to float.
// when using float
typedef float LearnFloatType;
using LearnFloatType = float;
// when using double
//typedef double LearnFloatType;
@@ -36,107 +32,48 @@ typedef float LearnFloatType;
// ----------------------
// Definition of struct used in Learner
// ----------------------
#include "../position.h"
#include "packed_sfen.h"
#include "position.h"
#include <sstream>
#include <vector>
namespace Learner
{
// ----------------------
// Settings for learning
// ----------------------
// ----------------------
// Settings for learning
// ----------------------
// mini-batch size.
// Calculate the gradient by combining this number of phases.
// If you make it smaller, the number of update_weights() will increase and the convergence will be faster. The gradient is incorrect.
// If you increase it, the number of update_weights() decreases, so the convergence will be slow. The slope will come out accurately.
// I don't think you need to change this value in most cases.
// mini-batch size.
// Calculate the gradient by combining this number of phases.
// If you make it smaller, the number of update_weights() will increase and the convergence will be faster. The gradient is incorrect.
// If you increase it, the number of update_weights() decreases, so the convergence will be slow. The slope will come out accurately.
// I don't think you need to change this value in most cases.
constexpr std::size_t LEARN_MINI_BATCH_SIZE = 1000 * 1000 * 1;
constexpr std::size_t LEARN_MINI_BATCH_SIZE = 1000 * 1000 * 1;
// The number of phases to read from the file at one time. After reading this much, shuffle.
// It is better to have a certain size, but this number x 40 bytes x 3 times as much memory is consumed. 400MB*3 is consumed in the 10M phase.
// Must be a multiple of THREAD_BUFFER_SIZE(=10000).
// The number of phases to read from the file at one time. After reading this much, shuffle.
// It is better to have a certain size, but this number x 40 bytes x 3 times as much memory is consumed. 400MB*3 is consumed in the 10M phase.
// Must be a multiple of THREAD_BUFFER_SIZE(=10000).
constexpr std::size_t LEARN_SFEN_READ_SIZE = 1000 * 1000 * 10;
constexpr std::size_t LEARN_SFEN_READ_SIZE = 1000 * 1000 * 10;
// Saving interval of evaluation function at learning. Save each time you learn this number of phases.
// Needless to say, the longer the saving interval, the shorter the learning time.
// Folder name is incremented for each save like 0/, 1/, 2/...
// By default, once every 1 billion phases.
constexpr std::size_t LEARN_EVAL_SAVE_INTERVAL = 1000000000ULL;
// Saving interval of evaluation function at learning. Save each time you learn this number of phases.
// Needless to say, the longer the saving interval, the shorter the learning time.
// Folder name is incremented for each save like 0/, 1/, 2/...
// By default, once every 1 billion phases.
constexpr std::size_t LEARN_EVAL_SAVE_INTERVAL = 1000000000ULL;
// Reduce the output of rmse during learning to 1 for this number of times.
// rmse calculation is done in one thread, so it takes some time, so reducing the output is effective.
constexpr std::size_t LEARN_RMSE_OUTPUT_INTERVAL = 1;
// Reduce the output of rmse during learning to 1 for this number of times.
// rmse calculation is done in one thread, so it takes some time, so reducing the output is effective.
constexpr std::size_t LEARN_RMSE_OUTPUT_INTERVAL = 1;
//Structure in which PackedSfen and evaluation value are integrated
// If you write different contents for each option, it will be a problem when reusing the teacher game
// For the time being, write all the following members regardless of the options.
struct PackedSfenValue
{
// phase
PackedSfen sfen;
double calc_grad(Value shallow, const PackedSfenValue& psv);
// Evaluation value returned from Learner::search()
int16_t score;
// PV first move
// Used when finding the match rate with the teacher
uint16_t move;
// Trouble of the phase from the initial phase.
uint16_t gamePly;
// 1 if the player on this side ultimately wins the game. -1 if you are losing.
// 0 if a draw is reached.
// The draw is in the teacher position generation command gensfen,
// Only write if LEARN_GENSFEN_DRAW_RESULT is enabled.
int8_t game_result;
// When exchanging the file that wrote the teacher aspect with other people
//Because this structure size is not fixed, pad it so that it is 40 bytes in any environment.
uint8_t padding;
// 32 + 2 + 2 + 2 + 1 + 1 = 40bytes
};
// Type that returns the reading line and the evaluation value at that time
// Used in Learner::search(), Learner::qsearch().
typedef std::pair<Value, std::vector<Move> > ValueAndPV;
// Phase array: PSVector stands for packed sfen vector.
typedef std::vector<PackedSfenValue> PSVector;
// So far, only Yaneura King 2018 Otafuku has this stub
// This stub is required if EVAL_LEARN is defined.
extern Learner::ValueAndPV search(Position& pos, int depth , size_t multiPV = 1 , uint64_t NodesLimit = 0);
extern Learner::ValueAndPV qsearch(Position& pos);
double calc_grad(Value shallow, const PackedSfenValue& psv);
void convert_bin_from_pgn_extract(
const std::vector<std::string>& filenames,
const std::string& output_file_name,
const bool pgn_eval_side_to_move,
const bool convert_no_eval_fens_as_score_zero);
void convert_bin(
const std::vector<std::string>& filenames,
const std::string& output_file_name,
const int ply_minimum,
const int ply_maximum,
const int interpolate_eval,
const int src_score_min_value,
const int src_score_max_value,
const int dest_score_min_value,
const int dest_score_max_value,
const bool check_invalid_fen,
const bool check_illegal_move);
void convert_plain(
const std::vector<std::string>& filenames,
const std::string& output_file_name);
// Learning from the generated game record
void learn(Position& pos, std::istringstream& is);
}
#endif
#endif // ifndef _LEARN_H_
+1 -5
View File
@@ -1,8 +1,6 @@
#include "learning_tools.h"
#if defined (EVAL_LEARN)
#include "../misc.h"
#include "misc.h"
using namespace Eval;
@@ -18,5 +16,3 @@ namespace EvalLearningTools
uint64_t Weight::eta1_epoch;
uint64_t Weight::eta2_epoch;
}
#endif
+1 -4
View File
@@ -5,9 +5,7 @@
#include "learn.h"
#if defined (EVAL_LEARN)
#include "../misc.h" // PRNG , my_insertion_sort
#include "misc.h" // PRNG , my_insertion_sort
#include <array>
#include <cmath> // std::sqrt()
@@ -98,5 +96,4 @@ namespace EvalLearningTools
};
}
#endif // defined (EVAL_LEARN)
#endif
+10 -19
View File
@@ -1,10 +1,9 @@
#include "../types.h"
#include "multi_think.h"
#if defined(EVAL_LEARN)
#include "multi_think.h"
#include "../tt.h"
#include "../uci.h"
#include "tt.h"
#include "uci.h"
#include "types.h"
#include "search.h"
#include <thread>
@@ -27,14 +26,13 @@ void MultiThink::go_think()
auto thread_num = (size_t)Options["Threads"];
// Secure end flag of worker thread
thread_finished.resize(thread_num);
threads_finished=0;
// start worker thread
for (size_t i = 0; i < thread_num; ++i)
{
thread_finished[i] = 0;
threads.push_back(std::thread([i, this]
{
{
// exhaust all processor threads.
WinProcGroup::bindThisThread(i);
@@ -42,7 +40,7 @@ void MultiThink::go_think()
this->thread_worker(i);
// Set the end flag because the thread has ended
this->thread_finished[i] = 1;
this->threads_finished++;
}));
}
@@ -56,11 +54,7 @@ void MultiThink::go_think()
// function to determine if all threads have finished
auto threads_done = [&]()
{
// returns false if no one is finished
for (auto& f : thread_finished)
if (!f)
return false;
return true;
return threads_finished == thread_num;
};
// Call back if the callback function is set.
@@ -105,6 +99,3 @@ void MultiThink::go_think()
// Since the work itself may not have completed, output only that all threads have finished.
std::cout << "all threads are joined." << std::endl;
}
#endif // defined(EVAL_LEARN)
+14 -17
View File
@@ -1,17 +1,18 @@
#ifndef _MULTI_THINK_
#define _MULTI_THINK_
#if defined(EVAL_LEARN)
#include "learn.h"
#include <functional>
#include <mutex>
#include "../misc.h"
#include "../learn/learn.h"
#include "../thread_win32_osx.h"
#include "misc.h"
#include "thread_win32_osx.h"
#include <atomic>
#include <limits>
#include <functional>
#include <mutex>
#include <string>
#include <cstdint>
// Learning from a game record, when making yourself think and generating a fixed track, etc.
// Helper class used when multiple threads want to call Search::think() individually.
@@ -20,10 +21,11 @@ struct MultiThink
{
static constexpr std::uint64_t LOOP_COUNT_FINISHED = std::numeric_limits<std::uint64_t>::max();
MultiThink() : prng(std::chrono::system_clock::now().time_since_epoch().count())
{
loop_count = 0;
}
MultiThink() : prng{}, loop_count(0) { }
MultiThink(std::uint64_t seed) : prng(seed), loop_count(0) { }
MultiThink(const std::string& seed) : prng(seed), loop_count(0) { }
// Call this function from the master thread, each thread will think,
// Return control when the thought ending condition is satisfied.
@@ -94,10 +96,7 @@ private:
std::mutex loop_mutex;
// Thread end flag.
// vector<bool> may not be reflected properly when trying to rewrite from multiple threads...
typedef uint8_t Flag;
std::vector<Flag> thread_finished;
std::atomic<uint64_t> threads_finished;
};
// Mechanism to process task during idle time.
@@ -150,6 +149,4 @@ protected:
std::mutex task_mutex;
};
#endif // defined(EVAL_LEARN) && defined(YANEURAOU_2018_OTAFUKU_ENGINE)
#endif
+46
View File
@@ -0,0 +1,46 @@
#ifndef _PACKED_SFEN_H_
#define _PACKED_SFEN_H_
#include <vector>
#include <cstdint>
namespace Learner {
// packed sfen
struct PackedSfen { std::uint8_t data[32]; };
// Structure in which PackedSfen and evaluation value are integrated
// If you write different contents for each option, it will be a problem when reusing the teacher game
// For the time being, write all the following members regardless of the options.
struct PackedSfenValue
{
// phase
PackedSfen sfen;
// Evaluation value returned from Learner::search()
std::int16_t score;
// PV first move
// Used when finding the match rate with the teacher
std::uint16_t move;
// Trouble of the phase from the initial phase.
std::uint16_t gamePly;
// 1 if the player on this side ultimately wins the game. -1 if you are losing.
// 0 if a draw is reached.
// The draw is in the teacher position generation command gensfen,
// Only write if LEARN_GENSFEN_DRAW_RESULT is enabled.
std::int8_t game_result;
// When exchanging the file that wrote the teacher aspect with other people
//Because this structure size is not fixed, pad it so that it is 40 bytes in any environment.
std::uint8_t padding;
// 32 + 2 + 2 + 2 + 1 + 1 = 40bytes
};
// Phase array: PSVector stands for packed sfen vector.
using PSVector = std::vector<PackedSfenValue>;
}
#endif
+402
View File
@@ -0,0 +1,402 @@
#include "sfen_packer.h"
#include "packed_sfen.h"
#include "misc.h"
#include "position.h"
#include <sstream>
#include <fstream>
#include <cstring> // std::memset()
using namespace std;
namespace Learner {
// Class that handles bitstream
// useful when doing aspect encoding
struct BitStream
{
// Set the memory to store the data in advance.
// Assume that memory is cleared to 0.
void set_data(std::uint8_t* data_) { data = data_; reset(); }
// Get the pointer passed in set_data().
uint8_t* get_data() const { return data; }
// Get the cursor.
int get_cursor() const { return bit_cursor; }
// reset the cursor
void reset() { bit_cursor = 0; }
// Write 1bit to the stream.
// If b is non-zero, write out 1. If 0, write 0.
void write_one_bit(int b)
{
if (b)
data[bit_cursor / 8] |= 1 << (bit_cursor & 7);
++bit_cursor;
}
// Get 1 bit from the stream.
int read_one_bit()
{
int b = (data[bit_cursor / 8] >> (bit_cursor & 7)) & 1;
++bit_cursor;
return b;
}
// write n bits of data
// Data shall be written out from the lower order of d.
void write_n_bit(int d, int n)
{
for (int i = 0; i <n; ++i)
write_one_bit(d & (1 << i));
}
// read n bits of data
// Reverse conversion of write_n_bit().
int read_n_bit(int n)
{
int result = 0;
for (int i = 0; i < n; ++i)
result |= read_one_bit() ? (1 << i) : 0;
return result;
}
private:
// Next bit position to read/write.
int bit_cursor;
// data entity
std::uint8_t* data;
};
// Class for compressing/decompressing sfen
// sfen can be packed to 256bit (32bytes) by Huffman coding.
// This is proven by mini. The above is Huffman coding.
//
// Internal format = 1-bit turn + 7-bit king position *2 + piece on board (Huffman coding) + hand piece (Huffman coding)
// Side to move (White = 0, Black = 1) (1bit)
// White King Position (6 bits)
// Black King Position (6 bits)
// Huffman Encoding of the board
// Castling availability (1 bit x 4)
// En passant square (1 or 1 + 6 bits)
// Rule 50 (6 bits)
// Game play (8 bits)
//
// TODO(someone): Rename SFEN to FEN.
//
struct SfenPacker
{
void pack(const Position& pos);
// sfen packed by pack() (256bit = 32bytes)
// Or sfen to decode with unpack()
uint8_t *data; // uint8_t[32];
BitStream stream;
// Output the board pieces to stream.
void write_board_piece_to_stream(Piece pc);
// Read one board piece from stream
Piece read_board_piece_from_stream();
};
// Huffman coding
// * is simplified from mini encoding to make conversion easier.
//
// 1 box on the board (other than NO_PIECE) = 2 to 6 bits (+ 1-bit flag + 1-bit forward and backward)
// 1 piece of hand piece = 1-5bit (+ 1-bit flag + 1bit ahead and behind)
//
// empty xxxxx0 + 0 (none)
// step xxxx01 + 2 xxxx0 + 2
// incense xx0011 + 2 xx001 + 2
// Katsura xx1011 + 2 xx101 + 2
// silver xx0111 + 2 xx011 + 2
// Gold x01111 + 1 x0111 + 1 // Gold is valid and has no flags.
// corner 011111 + 2 01111 + 2
// Fly 111111 + 2 11111 + 2
//
// Assuming all pieces are on the board,
// Sky 81-40 pieces = 41 boxes = 41bit
// Walk 4bit*18 pieces = 72bit
// Incense 6bit*4 pieces = 24bit
// Katsura 6bit*4 pieces = 24bit
// Silver 6bit*4 pieces = 24bit
// Gold 6bit* 4 pieces = 24bit
// corner 8bit* 2 pieces = 16bit
// Fly 8bit* 2 pieces = 16bit
// -------
// 241bit + 1bit (turn) + 7bit × 2 (King's position after) = 256bit
//
// When the piece on the board moves to the hand piece, the piece on the board becomes empty, so the box on the board can be expressed with 1 bit,
// Since the hand piece can be expressed by 1 bit less than the piece on the board, the total number of bits does not change in the end.
// Therefore, in this expression, any aspect can be expressed by this bit number.
// It is a hand piece and no flag is required, but if you include this, the bit number of the piece on the board will be -1
// Since the total number of bits can be fixed, we will include this as well.
// Huffman Encoding
//
// Empty xxxxxxx0
// Pawn xxxxx001 + 1 bit (Side to move)
// Knight xxxxx011 + 1 bit (Side to move)
// Bishop xxxxx101 + 1 bit (Side to move)
// Rook xxxxx111 + 1 bit (Side to move)
struct HuffmanedPiece
{
int code; // how it will be coded
int bits; // How many bits do you have
};
constexpr HuffmanedPiece huffman_table[] =
{
{0b0000,1}, // NO_PIECE
{0b0001,4}, // PAWN
{0b0011,4}, // KNIGHT
{0b0101,4}, // BISHOP
{0b0111,4}, // ROOK
{0b1001,4}, // QUEEN
};
// Pack sfen and store in data[32].
void SfenPacker::pack(const Position& pos)
{
// cout << pos;
memset(data, 0, 32 /* 256bit */);
stream.set_data(data);
// turn
// Side to move.
stream.write_one_bit((int)(pos.side_to_move()));
// 7-bit positions for leading and trailing balls
// White king and black king, 6 bits for each.
for(auto c: Colors)
stream.write_n_bit(pos.king_square(c), 6);
// Write the pieces on the board other than the kings.
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
{
Piece pc = pos.piece_on(make_square(f, r));
if (type_of(pc) == KING)
continue;
write_board_piece_to_stream(pc);
}
}
// TODO(someone): Support chess960.
stream.write_one_bit(pos.can_castle(WHITE_OO));
stream.write_one_bit(pos.can_castle(WHITE_OOO));
stream.write_one_bit(pos.can_castle(BLACK_OO));
stream.write_one_bit(pos.can_castle(BLACK_OOO));
if (pos.ep_square() == SQ_NONE) {
stream.write_one_bit(0);
}
else {
stream.write_one_bit(1);
stream.write_n_bit(static_cast<int>(pos.ep_square()), 6);
}
stream.write_n_bit(pos.state()->rule50, 6);
stream.write_n_bit(1 + (pos.game_ply()-(pos.side_to_move() == BLACK)) / 2, 8);
assert(stream.get_cursor() <= 256);
}
// Output the board pieces to stream.
void SfenPacker::write_board_piece_to_stream(Piece pc)
{
// piece type
PieceType pr = type_of(pc);
auto c = huffman_table[pr];
stream.write_n_bit(c.code, c.bits);
if (pc == NO_PIECE)
return;
// first and second flag
stream.write_one_bit(color_of(pc));
}
// Read one board piece from stream
Piece SfenPacker::read_board_piece_from_stream()
{
PieceType pr = NO_PIECE_TYPE;
int code = 0, bits = 0;
while (true)
{
code |= stream.read_one_bit() << bits;
++bits;
assert(bits <= 6);
for (pr = NO_PIECE_TYPE; pr <KING; ++pr)
if (huffman_table[pr].code == code
&& huffman_table[pr].bits == bits)
goto Found;
}
Found:;
if (pr == NO_PIECE_TYPE)
return NO_PIECE;
// first and second flag
Color c = (Color)stream.read_one_bit();
return make_piece(c, pr);
}
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror)
{
SfenPacker packer;
auto& stream = packer.stream;
// TODO: separate streams for writing and reading. Here we actually have to
// const_cast which is not safe in the long run.
stream.set_data(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(&sfen)));
pos.clear();
std::memset(si, 0, sizeof(StateInfo));
std::fill_n(&pos.pieceList[0][0], sizeof(pos.pieceList) / sizeof(Square), SQ_NONE);
pos.st = si;
// Active color
pos.sideToMove = (Color)stream.read_one_bit();
pos.pieceList[W_KING][0] = SQUARE_NB;
pos.pieceList[B_KING][0] = SQUARE_NB;
// First the position of the ball
if (mirror)
{
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
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
{
auto sq = make_square(f, r);
if (mirror) {
sq = flip_file(sq);
}
// it seems there are already balls
Piece pc;
if (type_of(pos.board[sq]) != KING)
{
assert(pos.board[sq] == NO_PIECE);
pc = packer.read_board_piece_from_stream();
}
else
{
pc = pos.board[sq];
// put_piece() will catch ASSERT unless you remove it all.
pos.board[sq] = NO_PIECE;
}
// There may be no pieces, so skip in that case.
if (pc == NO_PIECE)
continue;
pos.put_piece(Piece(pc), sq);
if (stream.get_cursor()> 256)
return 1;
//assert(stream.get_cursor() <= 256);
}
}
// Castling availability.
// TODO(someone): Support chess960.
pos.st->castlingRights = 0;
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(WHITE, SQ_H1); pos.piece_on(rsq) != W_ROOK; --rsq) {}
pos.set_castling_right(WHITE, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(WHITE, SQ_A1); pos.piece_on(rsq) != W_ROOK; ++rsq) {}
pos.set_castling_right(WHITE, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(BLACK, SQ_H1); pos.piece_on(rsq) != B_ROOK; --rsq) {}
pos.set_castling_right(BLACK, rsq);
}
if (stream.read_one_bit()) {
Square rsq;
for (rsq = relative_square(BLACK, SQ_A1); pos.piece_on(rsq) != B_ROOK; ++rsq) {}
pos.set_castling_right(BLACK, rsq);
}
// En passant square. Ignore if no pawn capture is possible
if (stream.read_one_bit()) {
Square ep_square = static_cast<Square>(stream.read_n_bit(6));
if (mirror) {
ep_square = flip_file(ep_square);
}
pos.st->epSquare = ep_square;
if (!(pos.attackers_to(pos.st->epSquare) & pos.pieces(pos.sideToMove, PAWN))
|| !(pos.pieces(~pos.sideToMove, PAWN) & (pos.st->epSquare + pawn_push(~pos.sideToMove))))
pos.st->epSquare = SQ_NONE;
}
else {
pos.st->epSquare = SQ_NONE;
}
// Halfmove clock
pos.st->rule50 = static_cast<Square>(stream.read_n_bit(6));
// Fullmove number
pos.gamePly = static_cast<Square>(stream.read_n_bit(8));
// Convert from fullmove starting from 1 to gamePly starting from 0,
// handle also common incorrect FEN with fullmove = 0.
pos.gamePly = std::max(2 * (pos.gamePly - 1), 0) + (pos.sideToMove == BLACK);
assert(stream.get_cursor() <= 256);
pos.chess960 = false;
pos.thisThread = th;
pos.set_state(pos.st);
assert(pos.pos_is_ok());
return 0;
}
PackedSfen sfen_pack(Position& pos)
{
PackedSfen sfen;
SfenPacker sp;
sp.data = (uint8_t*)&sfen;
sp.pack(pos);
return sfen;
}
}
+20
View File
@@ -0,0 +1,20 @@
#ifndef _SFEN_PACKER_H_
#define _SFEN_PACKER_H_
#include "types.h"
#include "learn/packed_sfen.h"
#include <cstdint>
class Position;
struct StateInfo;
class Thread;
namespace Learner {
int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror);
PackedSfen sfen_pack(Position& pos);
}
#endif
+41
View File
@@ -19,6 +19,7 @@
#ifndef MISC_H_INCLUDED
#define MISC_H_INCLUDED
#include <algorithm>
#include <cassert>
#include <chrono>
#include <functional>
@@ -28,6 +29,7 @@
#include <vector>
#include <utility>
#include <cmath>
#include <cctype>
#include "types.h"
@@ -83,6 +85,19 @@ std::ostream& operator<<(std::ostream&, SyncCout);
/// For further analysis see
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
static uint64_t string_hash(const std::string& str)
{
uint64_t h = 525201411107845655ull;
for (auto c : str) {
h ^= static_cast<uint64_t>(c);
h *= 0x5bd1e9955bd1e995ull;
h ^= h >> 47;
}
return h;
}
class PRNG {
uint64_t s;
@@ -94,7 +109,9 @@ class PRNG {
}
public:
PRNG() { set_seed_from_time(); }
PRNG(uint64_t seed) : s(seed) { assert(seed); }
PRNG(const std::string& seed) { set_seed(seed); }
template<typename T> T rand() { return T(rand64()); }
@@ -107,6 +124,28 @@ public:
// Return the random seed used internally.
uint64_t get_seed() const { return s; }
void set_seed(uint64_t seed) { s = seed; }
void set_seed_from_time()
{
set_seed(std::chrono::system_clock::now().time_since_epoch().count());
}
void set_seed(const std::string& str)
{
if (str.empty())
{
set_seed_from_time();
}
else if (std::all_of(str.begin(), str.end(), [](char c) { return std::isdigit(c);} )) {
set_seed(std::stoull(str));
}
else
{
set_seed(string_hash(str));
}
}
};
// Display a random seed. (For debugging)
@@ -166,7 +205,9 @@ int write_memory_to_file(std::string filename, void* ptr, uint64_t size);
// async version of PRNG
struct AsyncPRNG
{
AsyncPRNG() : prng() { }
AsyncPRNG(uint64_t seed) : prng(seed) { assert(seed); }
AsyncPRNG(const std::string& seed) : prng(seed) { }
// [ASYNC] Extract one random number.
template<typename T> T rand() {
std::unique_lock<std::mutex> lk(mutex);
@@ -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
#ifndef HALFKP_CR_EP_256X2_32_32_H
#define HALFKP_CR_EP_256X2_32_32_H
#ifndef NNUE_HALFKP_CR_EP_256X2_32_32_H_INCLUDED
#define NNUE_HALFKP_CR_EP_256X2_32_32_H_INCLUDED
#include "../features/feature_set.h"
#include "../features/half_kp.h"
@@ -12,31 +30,28 @@
#include "../layers/affine_transform.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
using RawFeatures = Features::FeatureSet<
Features::HalfKP<Features::Side::kFriend>, Features::CastlingRight,
Features::EnPassant>;
// Number of input feature dimensions after conversion
constexpr IndexType kTransformedFeatureDimensions = 256;
// Number of input feature dimensions after conversion
constexpr IndexType kTransformedFeatureDimensions = 256;
namespace Layers {
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
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
} // namespace Layers
} // namespace Layers
using Network = Layers::OutputLayer;
using Network = Layers::OutputLayer;
} // namespace Eval::NNUE
} // namespace NNUE
} // namespace Eval
#endif // HALFKP_CR_EP_256X2_32_32_H
#endif // #ifndef NNUE_HALFKP_CR_EP_256X2_32_32_H_INCLUDED
@@ -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
#ifndef K_P_256X2_32_32_H
#define K_P_256X2_32_32_H
-4
View File
@@ -1,7 +1,5 @@
// Code for learning NNUE evaluation function
#if defined(EVAL_LEARN)
#include <random>
#include <fstream>
#include <filesystem>
@@ -238,5 +236,3 @@ double get_eta() {
}
} // namespace Eval
#endif // defined(EVAL_LEARN)
-4
View File
@@ -3,8 +3,6 @@
#ifndef _EVALUATE_NNUE_LEARNER_H_
#define _EVALUATE_NNUE_LEARNER_H_
#if defined(EVAL_LEARN)
#include "../learn/learn.h"
namespace Eval {
@@ -43,6 +41,4 @@ void FinalizeNet();
} // namespace Eval
#endif // defined(EVAL_LEARN)
#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 "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
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;
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 & (i << 1)) {
active->push_back(i);
}
}
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
void CastlingRight::AppendChangedIndices(
const Position& pos, Color perspective,
IndexList* removed, IndexList* /* added */) {
// Get a list of indices whose values have changed from the previous one in the feature quantity
void CastlingRight::AppendChangedIndices(
const Position& /* pos */, Color /* perspective */,
IndexList* /* removed */, IndexList* /* added */) {
// Not implemented.
assert(false);
}
int previous_castling_rights = pos.state()->previous->castlingRights;
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 & (i << 1)) &&
(relative_current_castling_rights & (i << 1)) == 0) {
removed->push_back(i);
}
}
}
} // namespace Features
} // namespace NNUE
} // namespace Eval
} // namespace Eval::NNUE::Features
+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_
#define _NNUE_FEATURES_CASTLING_RIGHT_H_
@@ -6,39 +6,30 @@
#include "../../evaluate.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
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::kNone;
// 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);
};
// Get a list of indices with a value of 1 among the 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
} // namespace Eval::NNUE::Features
#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 "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
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;
// 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);
}
auto epSquare = pos.state()->epSquare;
if (epSquare == SQ_NONE) {
return;
}
if (perspective == BLACK) {
epSquare = flip_rank(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
} // namespace Eval::NNUE::Features
+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_
#define _NNUE_FEATURES_ENPASSANT_H_
@@ -6,39 +6,30 @@
#include "../../evaluate.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
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;
// 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);
};
// Get a list of indices with a value of 1 among the 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
} // namespace Eval::NNUE::Features
#endif
+11
View File
@@ -105,9 +105,20 @@ namespace Eval::NNUE::Features {
for (Color perspective : { WHITE, BLACK }) {
reset[perspective] = false;
switch (trigger) {
case TriggerEvent::kNone:
break;
case TriggerEvent::kFriendKingMoved:
reset[perspective] = dp.piece[0] == make_piece(perspective, KING);
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:
assert(false);
break;
+4 -4
View File
@@ -34,10 +34,10 @@ namespace Eval::NNUE::Features {
// Trigger to perform full calculations instead of difference only
enum class TriggerEvent {
kNone, // Calculate the difference whenever possible
kFriendKingMoved, // calculate all when own ball moves
kEnemyKingMoved, // do all calculations when enemy balls move
kAnyKingMoved, // do all calculations if either ball moves
kAnyPieceMoved, // always do all calculations
kFriendKingMoved, // calculate full evaluation when own king moves
kEnemyKingMoved, // calculate full evaluation when opponent king moves
kAnyKingMoved, // calculate full evaluation when any king moves
kAnyPieceMoved, // always calculate full evaluation
};
enum class Side {
+1 -1
View File
@@ -50,7 +50,7 @@ namespace Eval::NNUE::Features {
}
private:
T values_[MaxSize];
T values_[MaxSize] = {};
std::size_t size_ = 0;
};
+4 -12
View File
@@ -32,19 +32,11 @@ void K::AppendChangedIndices(
const Position& pos, Color perspective,
IndexList* removed, IndexList* added) {
const auto& dp = pos.state()->dirtyPiece;
Color king_color;
if (dp.piece[0] == Piece::W_KING) {
king_color = WHITE;
if (type_of(dp.piece[0]) == KING)
{
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
+1 -1
View File
@@ -22,7 +22,7 @@
#define NNUE_ARCHITECTURE_H_INCLUDED
// Defines the network structure
#include "architectures/halfkp_256x2-32-32.h"
#include "architectures/halfkp-cr-ep_256x2-32-32.h"
namespace Eval::NNUE {
+139 -95
View File
@@ -1,4 +1,4 @@
/*
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
@@ -193,6 +193,12 @@ namespace Eval::NNUE {
&reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 0]);
__m256i sum1 = _mm256_loadA_si256(
&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_packs_epi16(sum0, sum1), kZero), kControl));
}
@@ -204,6 +210,12 @@ namespace Eval::NNUE {
accumulation[perspectives[p]][0])[j * 2 + 0]);
__m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
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);
_mm_store_si128(&out[j],
@@ -224,6 +236,12 @@ namespace Eval::NNUE {
accumulation[perspectives[p]][0])[j * 2 + 0]);
__m64 sum1 = *(&reinterpret_cast<const __m64*>(
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);
out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
}
@@ -233,12 +251,19 @@ namespace Eval::NNUE {
for (IndexType j = 0; j < kNumChunks; ++j) {
int16x8_t sum = reinterpret_cast<const int16x8_t*>(
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);
}
#else
for (IndexType j = 0; j < kHalfDimensions; ++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>(
std::max<int>(0, std::min<int>(127, sum)));
}
@@ -255,44 +280,54 @@ namespace Eval::NNUE {
void RefreshAccumulator(const Position& pos) const {
auto& accumulator = pos.state()->accumulator;
IndexType i = 0;
Features::IndexList active_indices[2];
RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
active_indices);
for (Color perspective : { WHITE, BLACK }) {
#ifdef TILING
for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) {
auto biasesTile = reinterpret_cast<const vec_t*>(
&biases_[j * kTileHeight]);
auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[perspective][i][j * kTileHeight]);
vec_t acc[kNumRegs];
for (IndexType i = 0; i < kRefreshTriggers.size(); ++i) {
Features::IndexList active_indices[2];
RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
active_indices);
for (Color perspective : { WHITE, BLACK }) {
#ifdef TILING
for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) {
auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[perspective][i][j * kTileHeight]);
vec_t acc[kNumRegs];
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = biasesTile[k];
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 {
std::memset(acc, 0, kNumRegs * sizeof(vec_t));
}
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]) {
const IndexType offset = kHalfDimensions * index + j * kTileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]);
}
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]);
for (unsigned k = 0; k < kNumRegs; 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++)
vec_store(&accTile[k], acc[k]);
}
#else
std::memcpy(accumulator.accumulation[perspective][i], biases_,
kHalfDimensions * sizeof(BiasType));
for (const auto index : active_indices[perspective]) {
const IndexType offset = kHalfDimensions * index;
for (const auto index : active_indices[perspective]) {
const IndexType offset = kHalfDimensions * index;
for (IndexType j = 0; j < kHalfDimensions; ++j)
accumulator.accumulation[perspective][i][j] += weights_[offset + j];
for (IndexType j = 0; j < kHalfDimensions; ++j)
accumulator.accumulation[perspective][i][j] += weights_[offset + j];
}
#endif
}
#endif
}
#if defined(USE_MMX)
@@ -307,86 +342,95 @@ namespace Eval::NNUE {
const auto prev_accumulator = pos.state()->previous->accumulator;
auto& accumulator = pos.state()->accumulator;
IndexType i = 0;
Features::IndexList removed_indices[2], added_indices[2];
bool reset[2];
RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i],
removed_indices, added_indices, reset);
for (IndexType i = 0; i < kRefreshTriggers.size(); ++i) {
Features::IndexList removed_indices[2], added_indices[2];
bool reset[2];
RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i],
removed_indices, added_indices, reset);
#ifdef TILING
for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) {
#ifdef TILING
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 {
std::memset(acc, 0, kNumRegs * sizeof(vec_t));
}
} 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 }) {
auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[perspective][i][j * kTileHeight]);
vec_t acc[kNumRegs];
if (reset[perspective]) {
auto biasesTile = reinterpret_cast<const vec_t*>(
&biases_[j * kTileHeight]);
for (unsigned k = 0; k < kNumRegs; ++k)
acc[k] = biasesTile[k];
if (i == 0) {
std::memcpy(accumulator.accumulation[perspective][i], biases_,
kHalfDimensions * sizeof(BiasType));
} else {
std::memset(accumulator.accumulation[perspective][i], 0,
kHalfDimensions * sizeof(BiasType));
}
} 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]);
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 + j * kTileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
const IndexType offset = kHalfDimensions * index;
for (IndexType k = 0; k < kNumRegs; ++k)
acc[k] = vec_sub_16(acc[k], column[k]);
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 + j * kTileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
const IndexType offset = kHalfDimensions * index;
for (IndexType k = 0; k < kNumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]);
for (IndexType j = 0; j < kHalfDimensions; ++j)
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;
}
-4
View File
@@ -3,8 +3,6 @@
#ifndef _NNUE_TRAINER_H_
#define _NNUE_TRAINER_H_
#if defined(EVAL_LEARN)
#include "../nnue_common.h"
#include "../features/index_list.h"
@@ -120,6 +118,4 @@ std::shared_ptr<T> MakeAlignedSharedPtr(ArgumentTypes&&... arguments) {
} // namespace Eval
#endif // defined(EVAL_LEARN)
#endif
+1 -5
View File
@@ -3,8 +3,6 @@
#ifndef _NNUE_TRAINER_AFFINE_TRANSFORM_H_
#define _NNUE_TRAINER_AFFINE_TRANSFORM_H_
#if defined(EVAL_LEARN)
#include "../../learn/learn.h"
#include "../layers/affine_transform.h"
#include "trainer.h"
@@ -196,7 +194,7 @@ class Trainer<Layers::AffineTransform<PreviousLayer, OutputDimensions>> {
weights_(),
biases_diff_(),
weights_diff_(),
momentum_(0.1),
momentum_(0.0),
learning_rate_scale_(1.0) {
DequantizeParameters();
}
@@ -296,6 +294,4 @@ class Trainer<Layers::AffineTransform<PreviousLayer, OutputDimensions>> {
} // namespace Eval
#endif // defined(EVAL_LEARN)
#endif
-4
View File
@@ -3,8 +3,6 @@
#ifndef _NNUE_TRAINER_CLIPPED_RELU_H_
#define _NNUE_TRAINER_CLIPPED_RELU_H_
#if defined(EVAL_LEARN)
#include "../../learn/learn.h"
#include "../layers/clipped_relu.h"
#include "trainer.h"
@@ -137,6 +135,4 @@ class Trainer<Layers::ClippedReLU<PreviousLayer>> {
} // namespace Eval
#endif // defined(EVAL_LEARN)
#endif
@@ -3,8 +3,6 @@
#ifndef _NNUE_TRAINER_FEATURE_TRANSFORMER_H_
#define _NNUE_TRAINER_FEATURE_TRANSFORMER_H_
#if defined(EVAL_LEARN)
#include "../../learn/learn.h"
#include "../nnue_feature_transformer.h"
#include "trainer.h"
@@ -234,7 +232,7 @@ class Trainer<FeatureTransformer> {
biases_(),
weights_(),
biases_diff_(),
momentum_(0.1),
momentum_(0.0),
learning_rate_scale_(1.0) {
min_pre_activation_ = std::numeric_limits<LearnFloatType>::max();
max_pre_activation_ = std::numeric_limits<LearnFloatType>::lowest();
@@ -372,6 +370,4 @@ class Trainer<FeatureTransformer> {
} // namespace Eval
#endif // defined(EVAL_LEARN)
#endif
-4
View File
@@ -3,8 +3,6 @@
#ifndef _NNUE_TRAINER_INPUT_SLICE_H_
#define _NNUE_TRAINER_INPUT_SLICE_H_
#if defined(EVAL_LEARN)
#include "../../learn/learn.h"
#include "../layers/input_slice.h"
#include "trainer.h"
@@ -246,6 +244,4 @@ class Trainer<Layers::InputSlice<OutputDimensions, Offset>> {
} // namespace Eval
#endif // defined(EVAL_LEARN)
#endif
-4
View File
@@ -3,8 +3,6 @@
#ifndef _NNUE_TRAINER_SUM_H_
#define _NNUE_TRAINER_SUM_H_
#if defined(EVAL_LEARN)
#include "../../learn/learn.h"
#include "../layers/sum.h"
#include "trainer.h"
@@ -185,6 +183,4 @@ class Trainer<Layers::Sum<PreviousLayer>> {
} // namespace Eval
#endif // defined(EVAL_LEARN)
#endif
+40 -5
View File
@@ -32,6 +32,9 @@
#include "uci.h"
#include "syzygy/tbprobe.h"
#include "learn/packed_sfen.h"
#include "learn/sfen_packer.h"
using std::string;
namespace Zobrist {
@@ -754,7 +757,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
else
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
if (Eval::useNNUE)
if (Eval::useNNUE != Eval::UseNNUEMode::False)
{
dp.dirty_num = 2; // 1 piece moved, 1 piece captured
dp.piece[1] = captured;
@@ -798,7 +801,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
// Move the piece. The tricky Chess960 castling is handled earlier
if (type_of(m) != CASTLING)
{
if (Eval::useNNUE)
if (Eval::useNNUE != Eval::UseNNUEMode::False)
{
dp.piece[0] = pc;
dp.from[0] = from;
@@ -829,7 +832,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
remove_piece(to);
put_piece(promotion, to);
if (Eval::useNNUE)
if (Eval::useNNUE != Eval::UseNNUEMode::False)
{
// Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
dp.to[0] = SQ_NONE;
@@ -967,7 +970,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
if (Do && Eval::useNNUE)
if (Do && Eval::useNNUE != Eval::UseNNUEMode::False)
{
auto& dp = st->dirtyPiece;
dp.piece[0] = make_piece(us, KING);
@@ -996,7 +999,7 @@ void Position::do_null_move(StateInfo& newSt) {
assert(!checkers());
assert(&newSt != st);
if (Eval::useNNUE)
if (Eval::useNNUE != Eval::UseNNUEMode::False)
{
std::memcpy(&newSt, st, sizeof(StateInfo));
}
@@ -1344,3 +1347,35 @@ bool Position::pos_is_ok() const {
return true;
}
// Add a function that directly unpacks for speed. It's pretty tough.
// 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.
int Position::set_from_packed_sfen(const Learner::PackedSfen& sfen , StateInfo* si, Thread* th, bool mirror)
{
return Learner::set_from_packed_sfen(*this, sfen, si, th, mirror);
}
// Give the board, hand piece, and turn, and return the sfen.
//std::string Position::sfen_from_rawdata(Piece board[81], Hand hands[2], Color turn, int gamePly_)
//{
// // Copy it to an internal structure and call sfen() if the conversion process depends only on it
// // Maybe it will be converted normally...
// Position pos;
//
// memcpy(pos.board, board, sizeof(Piece) * 81);
// memcpy(pos.hand, hands, sizeof(Hand) * 2);
// pos.sideToMove = turn;
// pos.gamePly = gamePly_;
//
// return pos.sfen();
//
// // Implementation of ↑ is beautiful, but slow.
// // This is a bottleneck when learning a large amount of game records, so write a function to unpack directly.
//}
// Get the packed sfen. Returns to the buffer specified in the argument.
void Position::sfen_pack(Learner::PackedSfen& sfen)
{
sfen = Learner::sfen_pack(*this);
}
+9 -8
View File
@@ -30,6 +30,9 @@
#include "nnue/nnue_accumulator.h"
#include "learn/packed_sfen.h"
#include "learn/sfen_packer.h"
/// StateInfo struct stores information needed to restore a Position object to
/// its previous state when we retract a move. Whenever a move is made on the
@@ -75,9 +78,6 @@ typedef std::unique_ptr<std::deque<StateInfo>> StateListPtr;
/// traversing the search tree.
class Thread;
// packed sfen
struct PackedSfen { uint8_t data[32]; };
class Position {
public:
static void init();
@@ -175,26 +175,27 @@ public:
// Used by NNUE
StateInfo* state() const;
#if defined(EVAL_LEARN)
// --sfenization helper
friend int Learner::set_from_packed_sfen(Position& pos, const Learner::PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror);
// Get the packed sfen. Returns to the buffer specified in the argument.
// Do not include gamePly in pack.
void sfen_pack(PackedSfen& sfen);
void sfen_pack(Learner::PackedSfen& sfen);
// It is slow to go through sfen, so I made a function to set packed sfen directly.
// 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.
// 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 PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror = false);
int set_from_packed_sfen(const Learner::PackedSfen& sfen, StateInfo* si, Thread* th, bool mirror = false);
void clear() { std::memset(this, 0, sizeof(Position)); }
// Give the board, hand piece, and turn, and return the sfen.
//static std::string sfen_from_rawdata(Piece board[81], Hand hands[2], Color turn, int gamePly);
// Returns the position of the ball on the c side.
Square king_square(Color c) const { return pieceList[make_piece(c, KING)][0]; }
#endif // EVAL_LEARN
bool RootInTB;
private:
// Initialization helpers (used while setting up a position)
+57 -94
View File
@@ -40,19 +40,12 @@ namespace Search {
LimitsType Limits;
}
namespace Tablebases {
int Cardinality;
bool UseRule50;
Depth ProbeDepth;
}
namespace TB = Tablebases;
using std::string;
using Eval::evaluate;
using namespace Search;
bool Search::prune_at_shallow_depth = true;
namespace {
// Different node types, used as a template parameter
@@ -714,27 +707,27 @@ namespace {
}
// Step 5. Tablebases probe
if (!rootNode && TB::Cardinality)
if (!rootNode && thisThread->Cardinality)
{
int piecesCount = pos.count<ALL_PIECES>();
if ( piecesCount <= TB::Cardinality
&& (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth)
if ( piecesCount <= thisThread->Cardinality
&& (piecesCount < thisThread->Cardinality || depth >= thisThread->ProbeDepth)
&& pos.rule50_count() == 0
&& !pos.can_castle(ANY_CASTLING))
{
TB::ProbeState err;
TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
Tablebases::ProbeState err;
Tablebases::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
// Force check of time on the next occasion
if (thisThread == Threads.main())
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);
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
value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1
@@ -995,9 +988,7 @@ moves_loop: // When in check, search starts from here
ss->moveCount = ++moveCount;
if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000
#if defined(EVAL_LEARN)
&& !Limits.silent
#endif
)
sync_cout << "info depth " << depth
<< " currmove " << UCI::move(move, pos.is_chess960())
@@ -1015,6 +1006,7 @@ moves_loop: // When in check, search starts from here
// Step 13. Pruning at shallow depth (~200 Elo)
if ( !rootNode
&& (PvNode ? prune_at_shallow_depth : true)
&& pos.non_pawn_material(us)
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
{
@@ -1526,6 +1518,7 @@ moves_loop: // When in check, search starts from here
// Futility pruning
if ( !ss->inCheck
&& Search::prune_at_shallow_depth
&& !givesCheck
&& futilityBase > -VALUE_KNOWN_WIN
&& !pos.advanced_pawn_push(move))
@@ -1553,6 +1546,7 @@ moves_loop: // When in check, search starts from here
// Do not search moves with negative SEE values
if ( !ss->inCheck
&& Search::prune_at_shallow_depth
&& !(givesCheck && pos.is_discovery_check_on_king(~pos.side_to_move(), move))
&& !pos.see_ge(move))
continue;
@@ -1574,6 +1568,7 @@ moves_loop: // When in check, search starts from here
[to_sq(move)];
if ( !captureOrPromotion
&& Search::prune_at_shallow_depth
&& moveCount
&& (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold
&& (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold)
@@ -1844,7 +1839,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
size_t pvIdx = pos.this_thread()->pvIdx;
size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size());
uint64_t nodesSearched = Threads.nodes_searched();
uint64_t tbHits = Threads.tb_hits() + (pos.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)
{
@@ -1859,7 +1854,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
if (v == -VALUE_INFINITE)
v = VALUE_ZERO;
bool tb = pos.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;
if (ss.rdbuf()->in_avail()) // Not at first line
@@ -1926,34 +1921,34 @@ bool RootMove::extract_ponder_from_tt(Position& pos) {
void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
pos.RootInTB = false;
UseRule50 = bool(Options["Syzygy50MoveRule"]);
ProbeDepth = int(Options["SyzygyProbeDepth"]);
Cardinality = int(Options["SyzygyProbeLimit"]);
auto& rootInTB = pos.this_thread()->rootInTB;
auto& cardinality = pos.this_thread()->Cardinality;
auto& probeDepth = pos.this_thread()->ProbeDepth;
rootInTB = false;
bool dtz_available = true;
// Tables with fewer pieces than SyzygyProbeLimit are searched with
// ProbeDepth == DEPTH_ZERO
if (Cardinality > MaxCardinality)
if (cardinality > Tablebases::MaxCardinality)
{
Cardinality = MaxCardinality;
ProbeDepth = 0;
cardinality = Tablebases::MaxCardinality;
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
pos.RootInTB = root_probe(pos, rootMoves);
rootInTB = root_probe(pos, rootMoves);
if (!pos.RootInTB)
if (!rootInTB)
{
// DTZ tables are missing; try to rank moves using WDL tables
dtz_available = false;
pos.RootInTB = root_probe_wdl(pos, rootMoves);
rootInTB = root_probe_wdl(pos, rootMoves);
}
}
if (pos.RootInTB)
if (rootInTB)
{
// Sort moves according to TB rank
std::stable_sort(rootMoves.begin(), rootMoves.end(),
@@ -1961,7 +1956,7 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
// Probe during search only if DTZ is not available and we are winning
if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW)
Cardinality = 0;
cardinality = 0;
}
else
{
@@ -1969,11 +1964,11 @@ void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
for (auto& m : rootMoves)
m.tbRank = 0;
}
}
// --- expose the functions such as fixed depth search used for learning to the outside
#if defined (EVAL_LEARN)
namespace Learner
{
@@ -1991,39 +1986,6 @@ namespace Learner
std::memset(ss - 7, 0, 10 * sizeof(Stack));
// 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;
// Set a large value to prevent the draw value from being returned due to the number of moves near the draw.
//limits.max_game_ply = 1 << 16;
// If you do not include the ball entry rule, it will be a draw and it will be difficult to settle.
//limits.enteringKingRule = EnteringKingRule::EKR_27_POINT;
}
// Set DrawValue
{
// Because it is not prepared for each thread
// May be overwritten by another thread. There is no help for it.
// If that happens, I think it should be 0.
//drawValueTable[REPETITION_DRAW][BLACK] = VALUE_ZERO;
//drawValueTable[REPETITION_DRAW][WHITE] = VALUE_ZERO;
}
// Regarding this_thread.
{
@@ -2035,7 +1997,7 @@ namespace Learner
th->nmpMinPly = th->bestMoveChanges = 0;
th->ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2;
// Zero initialization of the number of search nodes
// 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.
@@ -2059,7 +2021,7 @@ namespace Learner
for (int i = 7; i > 0; i--)
(ss - i)->continuationHistory = &th->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
// set rootMoves
// set rootMoves
auto& rootMoves = th->rootMoves;
rootMoves.clear();
@@ -2067,7 +2029,20 @@ namespace Learner
rootMoves.push_back(Search::RootMove(m));
assert(!rootMoves.empty());
TB::rank_root_moves(pos, rootMoves);
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);
}
}
@@ -2088,8 +2063,8 @@ namespace Learner
// 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];
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.
@@ -2108,7 +2083,7 @@ namespace Learner
auto bestValue = ::qsearch<PV>(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, 0);
// Returns the PV obtained.
// Returns the PV obtained.
std::vector<Move> pvs;
for (Move* p = &ss->pv[0]; is_ok(*p); ++p)
pvs.push_back(*p);
@@ -2174,7 +2149,7 @@ namespace Learner
Value bestValue = -VALUE_INFINITE;
while ((rootDepth += 1) <= depth
// exit this loop even if the node limit is exceeded
// 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)
)
@@ -2196,46 +2171,36 @@ namespace Learner
break;
}
// selDepth output with USI info for each depth and PV line
// selDepth output with USI info for each depth and PV line
selDepth = 0;
// Switch to aspiration search for depth 5 and above.
if (rootDepth >= 5 * 1)
if (rootDepth >= 4)
{
delta = Value(20);
Value p = rootMoves[pvIdx].previousScore;
alpha = std::max(p - delta, -VALUE_INFINITE);
beta = std::min(p + delta, VALUE_INFINITE);
Value prev = rootMoves[pvIdx].previousScore;
delta = Value(17);
alpha = std::max(prev - delta,-VALUE_INFINITE);
beta = std::min(prev + delta, VALUE_INFINITE);
}
// aspiration search
int failedHighCnt = 0;
while (true)
{
Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt * 1);
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.
// 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);
failedHighCnt = 0;
//if (mainThread)
// mainThread->stopOnPonderhit = false;
}
else if (bestValue >= beta)
{
beta = std::min(bestValue + delta, VALUE_INFINITE);
++failedHighCnt;
}
else
break;
@@ -2256,7 +2221,6 @@ namespace Learner
}
// Pass PV_is(ok) to eliminate this PV, there may be NULL_MOVE in the middle.
// ?¡L PV should not be NULL_MOVE because it is PV
// MOVE_WIN has never been thrust. (For now)
for (Move move : rootMoves[0].pv)
{
@@ -2274,4 +2238,3 @@ namespace Learner
}
}
#endif
+16 -4
View File
@@ -24,6 +24,7 @@
#include "misc.h"
#include "movepick.h"
#include "types.h"
#include "uci.h"
class Position;
@@ -32,6 +33,7 @@ namespace Search {
/// Threshold used for countermoves based pruning
constexpr int CounterMovePruneThreshold = 0;
extern bool prune_at_shallow_depth;
/// 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
@@ -88,9 +90,7 @@ struct LimitsType {
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
movestogo = depth = mate = perft = infinite = 0;
nodes = 0;
#if defined (EVAL_LEARN)
silent = false;
#endif
}
bool use_time_management() const {
@@ -101,11 +101,9 @@ struct LimitsType {
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
int64_t nodes;
#if defined (EVAL_LEARN)
// Silent mode that does not output to the screen (for continuous self-play in process)
// Do not output PV at this time.
bool silent;
#endif
};
extern LimitsType Limits;
@@ -115,4 +113,18 @@ void clear();
} // namespace Search
namespace Tablebases {
extern int MaxCardinality;
}
namespace Learner {
// A pair of reader and evaluation value. Returned by Learner::search(),Learner::qsearch().
using ValueAndPV = std::pair<Value, std::vector<Move>>;
ValueAndPV qsearch(Position& pos);
ValueAndPV search(Position& pos, int depth_, size_t multiPV = 1, uint64_t nodesLimit = 0);
}
#endif // #ifndef SEARCH_H_INCLUDED
-2
View File
@@ -43,8 +43,6 @@ enum ProbeState {
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
};
extern int MaxCardinality;
void init(const std::string& paths);
WDLScore probe_wdl(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))
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
// and call 'go' again without setting a new position states.get() == NULL.
assert(states.get() || setupStates.get());
@@ -203,6 +200,21 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
th->rootMoves = rootMoves;
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
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();
+5
View File
@@ -73,6 +73,11 @@ public:
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
Score contempt;
bool rootInTB;
int Cardinality;
bool UseRule50;
Depth ProbeDepth;
};
+10 -4
View File
@@ -28,13 +28,16 @@
TranspositionTable TT; // Our global transposition table
bool TranspositionTable::enable_transposition_table = true;
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
/// overwriting an old position. Update is not atomic and can be racy.
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
if (Options["Training"])
return;
if (!TranspositionTable::enable_transposition_table) {
return;
}
// Preserve any existing move for the same position
if (m || (uint16_t)k != key16)
move16 = (uint16_t)m;
@@ -117,8 +120,11 @@ 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);
if (!enable_transposition_table) {
found = false;
return 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
+2
View File
@@ -84,6 +84,8 @@ public:
return &table[mul_hi64(key, clusterCount)].entry[0];
}
static bool enable_transposition_table;
private:
friend struct TTEntry;
+36 -55
View File
@@ -33,6 +33,10 @@
#include "tt.h"
#include "uci.h"
#include "learn/gensfen.h"
#include "learn/learn.h"
#include "learn/convert.h"
using namespace std;
extern vector<string> setup_bench(const Position&, istream&);
@@ -40,25 +44,6 @@ extern vector<string> setup_bench(const Position&, istream&);
// FEN string of the initial position, normal chess
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// Command to automatically generate a game record
#if defined (EVAL_LEARN)
namespace Learner
{
// Automatic generation of teacher position
void gen_sfen(Position& pos, istringstream& is);
// Learning from the generated game record
void learn(Position& pos, istringstream& is);
// A pair of reader and evaluation value. Returned by Learner::search(),Learner::qsearch().
typedef std::pair<Value, std::vector<Move> > ValueAndPV;
ValueAndPV qsearch(Position& pos);
ValueAndPV search(Position& pos, int depth_, size_t multiPV = 1, uint64_t nodesLimit = 0);
}
#endif
void test_cmd(Position& pos, istringstream& is)
{
// Initialize as it may be searched.
@@ -70,7 +55,7 @@ void test_cmd(Position& pos, istringstream& is)
if (param == "nnue") Eval::NNUE::TestCommand(pos, is);
}
namespace UCI {
namespace {
// position() is called when engine receives the "position" UCI command.
// The function sets up the position described in the given FEN string ("fen")
@@ -225,42 +210,41 @@ namespace UCI {
<< "\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 + win_rate_model_double(v, ply));
}
// 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.
double win_rate_model_double(double v, int ply) {
// The model captures only up to 240 plies, so limit input (and rescale)
double m = std::min(240, ply) / 64.0;
// Coefficients of a 3rd order polynomial fit based on fishtest data
// for two parameters needed to transform eval to the argument of a
// logistic function.
double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679};
double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751};
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
// Transform eval to centipawns with limited range
double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0);
// Return win rate in per mille
return 1000.0 / (1 + std::exp((a - x) / b));
}
} // 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
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
double UCI::win_rate_model_double(double v, int ply) {
// The model captures only up to 240 plies, so limit input (and rescale)
double m = std::min(240, ply) / 64.0;
// Coefficients of a 3rd order polynomial fit based on fishtest data
// for two parameters needed to transform eval to the argument of a
// logistic function.
double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679};
double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751};
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
// Transform eval to centipawns with limited range
double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0);
// Return win rate in per mille
return 1000.0 / (1 + std::exp((a - x) / b));
}
// --------------------
// Call qsearch(),search() directly for testing
// --------------------
#if defined(EVAL_LEARN)
void qsearch_cmd(Position& pos)
{
cout << "qsearch : ";
@@ -292,8 +276,6 @@ void search_cmd(Position& pos, istringstream& is)
cout << endl;
}
#endif
/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
/// GUI dies unexpectedly. When called with some command line arguments, e.g. to
@@ -349,16 +331,15 @@ void UCI::loop(int argc, char* argv[]) {
else if (token == "d") sync_cout << pos << sync_endl;
else if (token == "eval") trace_eval(pos);
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
#if defined (EVAL_LEARN)
else if (token == "gensfen") Learner::gen_sfen(pos, is);
else if (token == "learn") Learner::learn(pos, is);
else if (token == "convert") Learner::convert(is);
// Command to call qsearch(),search() directly for testing
else if (token == "qsearch") qsearch_cmd(pos);
else if (token == "search") search_cmd(pos, is);
#endif
// test command
else if (token == "test") test_cmd(pos, is);
else
+1
View File
@@ -72,6 +72,7 @@ std::string square(Square s);
std::string move(Move m, bool chess960);
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
std::string wdl(Value v, int ply);
int win_rate_model(Value v, int ply);
double win_rate_model_double(double v, int ply);
Move to_move(const Position& pos, std::string& str);
+12 -5
View File
@@ -43,6 +43,12 @@ void on_threads(const Option& o) { Threads.set(size_t(o)); }
void on_tb_path(const Option& o) { Tablebases::init(o); }
void on_use_NNUE(const Option& ) { Eval::init_NNUE(); }
void on_eval_file(const Option& ) { Eval::init_NNUE(); }
void on_prune_at_shallow_depth(const Option& o) {
Search::prune_at_shallow_depth = o;
}
void on_enable_transposition_table(const Option& o) {
TranspositionTable::enable_transposition_table = o;
}
/// Our case insensitive less() function as required by UCI protocol
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
@@ -70,7 +76,6 @@ void init(OptionsMap& o) {
o["Move Overhead"] << Option(10, 0, 5000);
o["Slow Mover"] << Option(100, 10, 1000);
o["nodestime"] << Option(0, 0, 10000);
o["Training"] << Option(false);
o["UCI_Chess960"] << Option(false);
o["UCI_AnalyseMode"] << Option(false);
o["UCI_LimitStrength"] << Option(false);
@@ -80,7 +85,7 @@ void init(OptionsMap& o) {
o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7);
o["Use NNUE"] << Option(true, on_use_NNUE);
o["Use NNUE"] << Option("true var true var false var pure", "true", on_use_NNUE);
o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file);
// 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
@@ -88,12 +93,14 @@ void init(OptionsMap& o) {
// Therefore, with this hidden option, you can suppress the loading of the evaluation function when ucinewgame,
// Hit the test eval convert command.
o["SkipLoadingEval"] << Option(false);
#if defined(EVAL_LEARN)
// 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.
// Automatically create a folder under this folder like "0/", "1/", ... and save the evaluation function file there.
o["EvalSaveDir"] << Option("evalsave");
#endif
// Prune at shallow depth on PV nodes. False is recommended when using fixed depth search.
o["PruneAtShallowDepth"] << Option(true, on_prune_at_shallow_depth);
// Enable transposition table.
o["EnableTranspositionTable"] << Option(true, on_enable_transposition_table);
}
@@ -147,7 +154,7 @@ Option::operator double() const {
}
Option::operator std::string() const {
assert(type == "string");
assert(type == "check" || type == "spin" || type == "combo" || type == "button" || type == "string");
return currentValue;
}