mirror of
https://github.com/opelly27/Stockfish.git
synced 2026-05-20 12:07:43 +00:00
Merge remote-tracking branch 'remotes/nodchip/master' into trainer
This commit is contained in:
+34
-51
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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,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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Eval::NNUE::Features {
|
||||
}
|
||||
|
||||
private:
|
||||
T values_[MaxSize];
|
||||
T values_[MaxSize] = {};
|
||||
std::size_t size_ = 0;
|
||||
};
|
||||
|
||||
|
||||
+4
-12
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -73,6 +73,11 @@ public:
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
Score contempt;
|
||||
bool rootInTB;
|
||||
int Cardinality;
|
||||
bool UseRule50;
|
||||
Depth ProbeDepth;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
+10
-4
@@ -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
|
||||
|
||||
@@ -84,6 +84,8 @@ public:
|
||||
return &table[mul_hi64(key, clusterCount)].entry[0];
|
||||
}
|
||||
|
||||
static bool enable_transposition_table;
|
||||
|
||||
private:
|
||||
friend struct TTEntry;
|
||||
|
||||
|
||||
+36
-55
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user