Merge branch 'master' into tools

This commit is contained in:
Tomasz Sobczyk
2021-05-24 11:32:58 +02:00
26 changed files with 432 additions and 426 deletions
+3 -1
View File
@@ -1,4 +1,4 @@
# List of authors for Stockfish, as of March 31, 2021 # List of authors for Stockfish, as of May 17, 2021
# Founders of the Stockfish project and fishtest infrastructure # Founders of the Stockfish project and fishtest infrastructure
Tord Romstad (romstad) Tord Romstad (romstad)
@@ -52,6 +52,7 @@ Dieter Dobbelaere (ddobbelaere)
DiscanX DiscanX
Dominik Schlösser (domschl) Dominik Schlösser (domschl)
double-beep double-beep
Douglas Matos Gomes (dsmsgms)
Eduardo Cáceres (eduherminio) Eduardo Cáceres (eduherminio)
Eelco de Groot (KingDefender) Eelco de Groot (KingDefender)
Elvin Liu (solarlight2) Elvin Liu (solarlight2)
@@ -174,6 +175,7 @@ Stefan Geschwentner (locutus2)
Stefano Cardanobile (Stefano80) Stefano Cardanobile (Stefano80)
Steinar Gunderson (sesse) Steinar Gunderson (sesse)
Stéphane Nicolet (snicolet) Stéphane Nicolet (snicolet)
Prokop Randáček (ProkopRandacek)
Thanar2 Thanar2
thaspel thaspel
theo77186 theo77186
+5 -5
View File
@@ -21,13 +21,13 @@ intrinsics available on most CPUs (sse2, avx2, neon, or similar).
This distribution of Stockfish consists of the following files: This distribution of Stockfish consists of the following files:
* Readme.md, the file you are currently reading. * [Readme.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), the file you are currently reading.
* Copying.txt, a text file containing the GNU General Public License version 3. * [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), a text file containing the GNU General Public License version 3.
* AUTHORS, a text file with the list of authors for the project * [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS), a text file with the list of authors for the project
* src, a subdirectory containing the full source code, including a Makefile * [src](https://github.com/official-stockfish/Stockfish/tree/master/src), a subdirectory containing the full source code, including a Makefile
that can be used to compile Stockfish on Unix-like systems. that can be used to compile Stockfish on Unix-like systems.
* a file with the .nnue extension, storing the neural network for the NNUE * a file with the .nnue extension, storing the neural network for the NNUE
@@ -365,4 +365,4 @@ you are distributing. If you make any changes to the source code,
these changes must also be made available under the GPL. these changes must also be made available under the GPL.
For full details, read the copy of the GPL v3 found in the file named For full details, read the copy of the GPL v3 found in the file named
*Copying.txt*. [*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt).
+3 -5
View File
@@ -50,7 +50,7 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/evaluate_nnue.cpp \ nnue/evaluate_nnue.cpp \
nnue/features/half_kp.cpp \ nnue/features/half_ka_v2.cpp \
tools/sfen_packer.cpp \ tools/sfen_packer.cpp \
tools/training_data_generator.cpp \ tools/training_data_generator.cpp \
tools/training_data_generator_nonpv.cpp \ tools/training_data_generator_nonpv.cpp \
@@ -106,8 +106,7 @@ endif
ifeq ($(ARCH), $(filter $(ARCH), \ ifeq ($(ARCH), $(filter $(ARCH), \
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \ x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \
x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 \ x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \
e2k \
armv7 armv7-neon armv8 apple-silicon general-64 general-32)) armv7 armv7-neon armv8 apple-silicon general-64 general-32))
SUPPORTED_ARCH=true SUPPORTED_ARCH=true
else else
@@ -853,8 +852,7 @@ config-sanity: net
@test "$(optimize)" = "yes" || test "$(optimize)" = "no" @test "$(optimize)" = "yes" || test "$(optimize)" = "no"
@test "$(SUPPORTED_ARCH)" = "true" @test "$(SUPPORTED_ARCH)" = "true"
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
test "$(arch)" = "e2k" || \
test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64"
@test "$(bits)" = "32" || test "$(bits)" = "64" @test "$(bits)" = "32" || test "$(bits)" = "64"
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
+30 -29
View File
@@ -63,16 +63,9 @@ namespace Eval {
namespace NNUE { namespace NNUE {
string eval_file_loaded = "None"; string eval_file_loaded = "None";
UseNNUEMode useNNUE; UseNNUEMode useNNUE;
}
/// NNUE::init() tries to load a NNUE network at startup time, or when the engine static UseNNUEMode NNUE::nnue_mode_from_option(const UCI::Option& mode)
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
/// The name of the NNUE network is always retrieved from the EvalFile option.
/// 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") if (mode == "false")
return UseNNUEMode::False; return UseNNUEMode::False;
@@ -84,7 +77,15 @@ namespace Eval {
return UseNNUEMode::False; return UseNNUEMode::False;
} }
void init() { /// NNUE::init() 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.
void NNUE::init() {
useNNUE = nnue_mode_from_option(Options["Use NNUE"]); useNNUE = nnue_mode_from_option(Options["Use NNUE"]);
if (useNNUE == UseNNUEMode::False) if (useNNUE == UseNNUEMode::False)
@@ -127,12 +128,16 @@ namespace Eval {
} }
} }
void export_net(const std::optional<std::string>& filename) { /// NNUE::export_net() exports the currently loaded network to a file
void NNUE::export_net(const std::optional<std::string>& filename) {
std::string actualFilename; std::string actualFilename;
if (filename.has_value()) {
if (filename.has_value())
actualFilename = filename.value(); actualFilename = filename.value();
} else { else
if (eval_file_loaded != EvalFileDefaultName) { {
if (eval_file_loaded != EvalFileDefaultName)
{
sync_cout << "Failed to export a net. A non-embedded net can only be saved if the filename is specified." << sync_endl; sync_cout << "Failed to export a net. A non-embedded net can only be saved if the filename is specified." << sync_endl;
return; return;
} }
@@ -140,15 +145,15 @@ namespace Eval {
} }
ofstream stream(actualFilename, std::ios_base::binary); ofstream stream(actualFilename, std::ios_base::binary);
if (save_eval(stream)) {
if (save_eval(stream))
sync_cout << "Network saved successfully to " << actualFilename << "." << sync_endl; sync_cout << "Network saved successfully to " << actualFilename << "." << sync_endl;
} else { else
sync_cout << "Failed to export a net." << sync_endl; sync_cout << "Failed to export a net." << sync_endl;
} }
}
/// NNUE::verify() verifies that the last net used was loaded successfully /// NNUE::verify() verifies that the last net used was loaded successfully
void verify() { void NNUE::verify() {
string eval_file = string(Options["EvalFile"]); string eval_file = string(Options["EvalFile"]);
@@ -178,7 +183,6 @@ namespace Eval {
sync_cout << "info string classical evaluation enabled" << sync_endl; sync_cout << "info string classical evaluation enabled" << sync_endl;
} }
} }
}
namespace Trace { namespace Trace {
@@ -941,7 +945,7 @@ namespace {
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
int sf = me->scale_factor(pos, strongSide); int sf = me->scale_factor(pos, strongSide);
// If scale factor is not already specific, scale down via general heuristics // If scale factor is not already specific, scale up/down via general heuristics
if (sf == SCALE_FACTOR_NORMAL) if (sf == SCALE_FACTOR_NORMAL)
{ {
if (pos.opposite_bishops()) if (pos.opposite_bishops())
@@ -1068,7 +1072,7 @@ make_v:
v = (v / 16) * 16; v = (v / 16) * 16;
// Side to move point of view // Side to move point of view
v = (pos.side_to_move() == WHITE ? v : -v) + Tempo; v = (pos.side_to_move() == WHITE ? v : -v);
return v; return v;
} }
@@ -1136,12 +1140,10 @@ Value Eval::evaluate(const Position& pos) {
// Scale and shift NNUE for compatibility with search and classical evaluation // Scale and shift NNUE for compatibility with search and classical evaluation
auto adjusted_NNUE = [&]() auto adjusted_NNUE = [&]()
{ {
int material = pos.non_pawn_material() + 4 * PawnValueMg * pos.count<PAWN>();
int scale = 580
+ material / 32
- 4 * pos.rule50_count();
Value nnue = NNUE::evaluate(pos) * scale / 1024 + Time.tempoNNUE; int scale = 903 + 28 * pos.count<PAWN>() + 28 * pos.non_pawn_material() / 1024;
Value nnue = NNUE::evaluate(pos, true) * scale / 1024;
if (pos.is_chess960()) if (pos.is_chess960())
nnue += fix_FRC(pos); nnue += fix_FRC(pos);
@@ -1154,7 +1156,7 @@ Value Eval::evaluate(const Position& pos) {
Value psq = Value(abs(eg_value(pos.psq_score()))); Value psq = Value(abs(eg_value(pos.psq_score())));
int r50 = 16 + pos.rule50_count(); int r50 = 16 + pos.rule50_count();
bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50; bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50;
bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB)); bool classical = largePsq;
// Use classical evaluation for really low piece endgames. // Use classical evaluation for really low piece endgames.
// One critical case is the draw for bishop + A/H file pawn vs naked king. // One critical case is the draw for bishop + A/H file pawn vs naked king.
@@ -1171,8 +1173,7 @@ Value Eval::evaluate(const Position& pos) {
&& !lowPieceEndgame && !lowPieceEndgame
&& ( abs(v) * 16 < NNUEThreshold2 * r50 && ( abs(v) * 16 < NNUEThreshold2 * r50
|| ( pos.opposite_bishops() || ( pos.opposite_bishops()
&& abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50 && abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50)))
&& !(pos.this_thread()->nodes & 0xB))))
v = adjusted_NNUE(); v = adjusted_NNUE();
} }
+2 -2
View File
@@ -36,7 +36,7 @@ namespace Eval {
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the // for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile. // name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-62ef826d1a6d.nnue" #define EvalFileDefaultName "nn-7756374aaed3.nnue"
namespace NNUE { namespace NNUE {
enum struct UseNNUEMode enum struct UseNNUEMode
@@ -49,7 +49,7 @@ namespace Eval {
extern UseNNUEMode useNNUE; extern UseNNUEMode useNNUE;
extern std::string eval_file_loaded; extern std::string eval_file_loaded;
Value evaluate(const Position& pos); Value evaluate(const Position& pos, bool adjusted = false);
bool load_eval(std::string name, std::istream& stream); bool load_eval(std::string name, std::istream& stream);
bool save_eval(std::ostream& stream); bool save_eval(std::ostream& stream);
void init(); void init();
+4 -5
View File
@@ -192,9 +192,9 @@ namespace {
const Square ksq = pos.square<KING>(Us); const Square ksq = pos.square<KING>(Us);
Bitboard target; Bitboard target;
if (Type == EVASIONS && more_than_one(pos.checkers())) // Skip generating non-king moves when in double check
goto kingMoves; // Double check, only a king move can save the day if (Type != EVASIONS || !more_than_one(pos.checkers()))
{
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
: Type == NON_EVASIONS ? ~pos.pieces( Us) : Type == NON_EVASIONS ? ~pos.pieces( Us)
: Type == CAPTURES ? pos.pieces(~Us) : Type == CAPTURES ? pos.pieces(~Us)
@@ -205,8 +205,7 @@ namespace {
moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target); moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target); moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target); moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
}
kingMoves:
if (!Checks || pos.blockers_for_king(~Us) & ksq) if (!Checks || pos.blockers_for_king(~Us) & ksq)
{ {
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target); Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
+29 -8
View File
@@ -35,7 +35,7 @@ namespace Stockfish::Eval::NNUE {
LargePagePtr<FeatureTransformer> featureTransformer; LargePagePtr<FeatureTransformer> featureTransformer;
// Evaluation function // Evaluation function
AlignedPtr<Network> network; AlignedPtr<Network> network[LayerStacks];
// Evaluation function file name // Evaluation function file name
std::string fileName; std::string fileName;
@@ -83,7 +83,8 @@ namespace Stockfish::Eval::NNUE {
void initialize() { void initialize() {
Detail::initialize(featureTransformer); Detail::initialize(featureTransformer);
Detail::initialize(network); for (std::size_t i = 0; i < LayerStacks; ++i)
Detail::initialize(network[i]);
} }
// Read network header // Read network header
@@ -117,7 +118,8 @@ namespace Stockfish::Eval::NNUE {
if (!read_header(stream, &hashValue, &netDescription)) return false; if (!read_header(stream, &hashValue, &netDescription)) return false;
if (hashValue != HashValue) return false; if (hashValue != HashValue) return false;
if (!Detail::read_parameters(stream, *featureTransformer)) return false; if (!Detail::read_parameters(stream, *featureTransformer)) return false;
if (!Detail::read_parameters(stream, *network)) return false; for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::read_parameters(stream, *(network[i]))) return false;
return stream && stream.peek() == std::ios::traits_type::eof(); return stream && stream.peek() == std::ios::traits_type::eof();
} }
@@ -126,12 +128,13 @@ namespace Stockfish::Eval::NNUE {
if (!write_header(stream, HashValue, netDescription)) return false; if (!write_header(stream, HashValue, netDescription)) return false;
if (!Detail::write_parameters(stream, *featureTransformer)) return false; if (!Detail::write_parameters(stream, *featureTransformer)) return false;
if (!Detail::write_parameters(stream, *network)) return false; for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::write_parameters(stream, *(network[i]))) return false;
return (bool)stream; return (bool)stream;
} }
// Evaluation function. Perform differential calculation. // Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos) { Value evaluate(const Position& pos, bool adjusted) {
// We manually align the arrays on the stack because with gcc < 9.3 // We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly. // overaligning stack variables with alignas() doesn't work correctly.
@@ -154,10 +157,28 @@ namespace Stockfish::Eval::NNUE {
ASSERT_ALIGNED(transformedFeatures, alignment); ASSERT_ALIGNED(transformedFeatures, alignment);
ASSERT_ALIGNED(buffer, alignment); ASSERT_ALIGNED(buffer, alignment);
featureTransformer->transform(pos, transformedFeatures); const std::size_t bucket = (pos.count<ALL_PIECES>() - 1) / 4;
const auto output = network->propagate(transformedFeatures, buffer); const auto [psqt, lazy] = featureTransformer->transform(pos, transformedFeatures, bucket);
return static_cast<Value>(output[0] / OutputScale); if (lazy)
return static_cast<Value>(psqt / OutputScale);
else
{
const auto output = network[bucket]->propagate(transformedFeatures, buffer);
int materialist = psqt;
int positional = output[0];
int delta_npm = abs(pos.non_pawn_material(WHITE) - pos.non_pawn_material(BLACK));
int entertainment = (adjusted && delta_npm <= BishopValueMg - KnightValueMg ? 7 : 0);
int A = 128 - entertainment;
int B = 128 + entertainment;
int sum = (A * materialist + B * positional) / 128;
return static_cast<Value>( sum / OutputScale );
}
} }
// Load eval, from a file stream or a memory stream // Load eval, from a file stream or a memory stream
@@ -16,32 +16,32 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
//Definition of input features HalfKP of NNUE evaluation function //Definition of input features HalfKAv2 of NNUE evaluation function
#include "half_kp.h" #include "half_ka_v2.h"
#include "../../position.h" #include "../../position.h"
namespace Stockfish::Eval::NNUE::Features { namespace Stockfish::Eval::NNUE::Features {
// Orient a square according to perspective (rotates by 180 for black) // Orient a square according to perspective (rotates by 180 for black)
inline Square HalfKP::orient(Color perspective, Square s) { inline Square HalfKAv2::orient(Color perspective, Square s) {
return Square(int(s) ^ (bool(perspective) * 63)); return Square(int(s) ^ (bool(perspective) * 56));
} }
// Index of a feature for a given king position and another piece on some square // Index of a feature for a given king position and another piece on some square
inline IndexType HalfKP::make_index(Color perspective, Square s, Piece pc, Square ksq) { inline IndexType HalfKAv2::make_index(Color perspective, Square s, Piece pc, Square ksq) {
return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq); return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
} }
// Get a list of indices for active features // Get a list of indices for active features
void HalfKP::append_active_indices( void HalfKAv2::append_active_indices(
const Position& pos, const Position& pos,
Color perspective, Color perspective,
ValueListInserter<IndexType> active ValueListInserter<IndexType> active
) { ) {
Square ksq = orient(perspective, pos.square<KING>(perspective)); Square ksq = orient(perspective, pos.square<KING>(perspective));
Bitboard bb = pos.pieces() & ~pos.pieces(KING); Bitboard bb = pos.pieces();
while (bb) while (bb)
{ {
Square s = pop_lsb(bb); Square s = pop_lsb(bb);
@@ -52,7 +52,7 @@ namespace Stockfish::Eval::NNUE::Features {
// append_changed_indices() : get a list of indices for recently changed features // append_changed_indices() : get a list of indices for recently changed features
void HalfKP::append_changed_indices( void HalfKAv2::append_changed_indices(
Square ksq, Square ksq,
StateInfo* st, StateInfo* st,
Color perspective, Color perspective,
@@ -63,7 +63,6 @@ namespace Stockfish::Eval::NNUE::Features {
Square oriented_ksq = orient(perspective, ksq); Square oriented_ksq = orient(perspective, ksq);
for (int i = 0; i < dp.dirty_num; ++i) { for (int i = 0; i < dp.dirty_num; ++i) {
Piece pc = dp.piece[i]; Piece pc = dp.piece[i];
if (type_of(pc) == KING) continue;
if (dp.from[i] != SQ_NONE) if (dp.from[i] != SQ_NONE)
removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq)); removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq));
if (dp.to[i] != SQ_NONE) if (dp.to[i] != SQ_NONE)
@@ -71,15 +70,15 @@ namespace Stockfish::Eval::NNUE::Features {
} }
} }
int HalfKP::update_cost(StateInfo* st) { int HalfKAv2::update_cost(StateInfo* st) {
return st->dirtyPiece.dirty_num; return st->dirtyPiece.dirty_num;
} }
int HalfKP::refresh_cost(const Position& pos) { int HalfKAv2::refresh_cost(const Position& pos) {
return pos.count<ALL_PIECES>() - 2; return pos.count<ALL_PIECES>();
} }
bool HalfKP::requires_refresh(StateInfo* st, Color perspective) { bool HalfKAv2::requires_refresh(StateInfo* st, Color perspective) {
return st->dirtyPiece.piece[0] == make_piece(perspective, KING); return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
} }
@@ -18,8 +18,8 @@
//Definition of input features HalfKP of NNUE evaluation function //Definition of input features HalfKP of NNUE evaluation function
#ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
#define NNUE_FEATURES_HALF_KP_H_INCLUDED #define NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
#include "../nnue_common.h" #include "../nnue_common.h"
@@ -32,33 +32,34 @@ namespace Stockfish {
namespace Stockfish::Eval::NNUE::Features { namespace Stockfish::Eval::NNUE::Features {
// Feature HalfKP: Combination of the position of own king // Feature HalfKAv2: Combination of the position of own king
// and the position of pieces other than kings // and the position of pieces
class HalfKP { class HalfKAv2 {
// unique number for each piece type on each square // unique number for each piece type on each square
enum { enum {
PS_NONE = 0, PS_NONE = 0,
PS_W_PAWN = 1, PS_W_PAWN = 0,
PS_B_PAWN = 1 * SQUARE_NB + 1, PS_B_PAWN = 1 * SQUARE_NB,
PS_W_KNIGHT = 2 * SQUARE_NB + 1, PS_W_KNIGHT = 2 * SQUARE_NB,
PS_B_KNIGHT = 3 * SQUARE_NB + 1, PS_B_KNIGHT = 3 * SQUARE_NB,
PS_W_BISHOP = 4 * SQUARE_NB + 1, PS_W_BISHOP = 4 * SQUARE_NB,
PS_B_BISHOP = 5 * SQUARE_NB + 1, PS_B_BISHOP = 5 * SQUARE_NB,
PS_W_ROOK = 6 * SQUARE_NB + 1, PS_W_ROOK = 6 * SQUARE_NB,
PS_B_ROOK = 7 * SQUARE_NB + 1, PS_B_ROOK = 7 * SQUARE_NB,
PS_W_QUEEN = 8 * SQUARE_NB + 1, PS_W_QUEEN = 8 * SQUARE_NB,
PS_B_QUEEN = 9 * SQUARE_NB + 1, PS_B_QUEEN = 9 * SQUARE_NB,
PS_NB = 10 * SQUARE_NB + 1 PS_KING = 10 * SQUARE_NB,
PS_NB = 11 * SQUARE_NB
}; };
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
// convention: W - us, B - them // convention: W - us, B - them
// viewed from other side, W and B are reversed // viewed from other side, W and B are reversed
{ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE, { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE }, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE },
{ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE, { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE } PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE }
}; };
// Orient a square according to perspective (rotates by 180 for black) // Orient a square according to perspective (rotates by 180 for black)
@@ -69,17 +70,17 @@ namespace Stockfish::Eval::NNUE::Features {
public: public:
// Feature name // Feature name
static constexpr const char* Name = "HalfKP(Friend)"; static constexpr const char* Name = "HalfKAv2(Friend)";
// Hash value embedded in the evaluation file // Hash value embedded in the evaluation file
static constexpr std::uint32_t HashValue = 0x5D69D5B8u; static constexpr std::uint32_t HashValue = 0x5f234cb8u;
// Number of feature dimensions // Number of feature dimensions
static constexpr IndexType Dimensions = static constexpr IndexType Dimensions =
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB); static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB);
// Maximum number of simultaneously active features. 30 because kins are not included. // Maximum number of simultaneously active features.
static constexpr IndexType MaxActiveDimensions = 30; static constexpr IndexType MaxActiveDimensions = 32;
// Get a list of indices for active features // Get a list of indices for active features
static void append_active_indices( static void append_active_indices(
@@ -107,4 +108,4 @@ namespace Stockfish::Eval::NNUE::Features {
} // namespace Stockfish::Eval::NNUE::Features } // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED #endif // #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
+26 -90
View File
@@ -69,62 +69,15 @@ namespace Stockfish::Eval::NNUE::Layers {
if (!previousLayer.read_parameters(stream)) return false; if (!previousLayer.read_parameters(stream)) return false;
for (std::size_t i = 0; i < OutputDimensions; ++i) for (std::size_t i = 0; i < OutputDimensions; ++i)
biases[i] = read_little_endian<BiasType>(stream); biases[i] = read_little_endian<BiasType>(stream);
#if !defined (USE_SSSE3)
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
#if !defined (USE_SSSE3)
weights[i] = read_little_endian<WeightType>(stream); weights[i] = read_little_endian<WeightType>(stream);
#else #else
std::unique_ptr<uint32_t[]> indexMap = std::make_unique<uint32_t[]>(OutputDimensions * PaddedInputDimensions); weights[
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) {
const uint32_t scrambledIdx =
(i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 +
i / PaddedInputDimensions * 4 + i / PaddedInputDimensions * 4 +
i % 4; i % 4
weights[scrambledIdx] = read_little_endian<WeightType>(stream); ] = read_little_endian<WeightType>(stream);
indexMap[scrambledIdx] = i;
}
// Determine if eights of weight and input products can be summed using 16bits
// without saturation. We assume worst case combinations of 0 and 127 for all inputs.
if (OutputDimensions > 1 && !stream.fail())
{
canSaturate16.count = 0;
#if !defined(USE_VNNI)
for (IndexType i = 0; i < PaddedInputDimensions; i += 16)
for (IndexType j = 0; j < OutputDimensions; ++j)
for (int x = 0; x < 2; ++x)
{
WeightType* w = &weights[i * OutputDimensions + j * 4 + x * 2];
int sum[2] = {0, 0};
for (int k = 0; k < 8; ++k)
{
IndexType idx = k / 2 * OutputDimensions * 4 + k % 2;
sum[w[idx] < 0] += w[idx];
}
for (int sign : { -1, 1 })
while (sign * sum[sign == -1] > 258)
{
int maxK = 0, maxW = 0;
for (int k = 0; k < 8; ++k)
{
IndexType idx = k / 2 * OutputDimensions * 4 + k % 2;
if (maxW < sign * w[idx])
maxK = k, maxW = sign * w[idx];
}
IndexType idx = maxK / 2 * OutputDimensions * 4 + maxK % 2;
sum[sign == -1] -= w[idx];
const uint32_t scrambledIdx = idx + i * OutputDimensions + j * 4 + x * 2;
canSaturate16.add(j, i + maxK / 2 * 4 + maxK % 2 + x * 2, w[idx], indexMap[scrambledIdx]);
w[idx] = 0;
}
}
// Non functional optimization for faster more linear access
std::sort(canSaturate16.ids, canSaturate16.ids + canSaturate16.count,
[](const typename CanSaturate::Entry& e1, const typename CanSaturate::Entry& e2)
{ return e1.in == e2.in ? e1.out < e2.out : e1.in < e2.in; });
#endif
}
#endif #endif
return !stream.fail(); return !stream.fail();
@@ -148,8 +101,6 @@ namespace Stockfish::Eval::NNUE::Layers {
i % 4 i % 4
]; ];
} }
for (int i = 0; i < canSaturate16.count; ++i)
unscrambledWeights[canSaturate16.ids[i].wIdx] = canSaturate16.ids[i].w;
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
write_little_endian<WeightType>(stream, unscrambledWeights[i]); write_little_endian<WeightType>(stream, unscrambledWeights[i]);
@@ -194,11 +145,11 @@ namespace Stockfish::Eval::NNUE::Layers {
__m512i product1 = _mm512_maddubs_epi16(a1, b1); __m512i product1 = _mm512_maddubs_epi16(a1, b1);
__m512i product2 = _mm512_maddubs_epi16(a2, b2); __m512i product2 = _mm512_maddubs_epi16(a2, b2);
__m512i product3 = _mm512_maddubs_epi16(a3, b3); __m512i product3 = _mm512_maddubs_epi16(a3, b3);
product0 = _mm512_add_epi16(product0, product1); product0 = _mm512_adds_epi16(product0, product1);
product2 = _mm512_add_epi16(product2, product3);
product0 = _mm512_add_epi16(product0, product2);
product0 = _mm512_madd_epi16(product0, Ones512); product0 = _mm512_madd_epi16(product0, Ones512);
acc = _mm512_add_epi32(acc, product0); product2 = _mm512_adds_epi16(product2, product3);
product2 = _mm512_madd_epi16(product2, Ones512);
acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product2));
#endif #endif
}; };
@@ -236,11 +187,11 @@ namespace Stockfish::Eval::NNUE::Layers {
__m256i product1 = _mm256_maddubs_epi16(a1, b1); __m256i product1 = _mm256_maddubs_epi16(a1, b1);
__m256i product2 = _mm256_maddubs_epi16(a2, b2); __m256i product2 = _mm256_maddubs_epi16(a2, b2);
__m256i product3 = _mm256_maddubs_epi16(a3, b3); __m256i product3 = _mm256_maddubs_epi16(a3, b3);
product0 = _mm256_add_epi16(product0, product1); product0 = _mm256_adds_epi16(product0, product1);
product2 = _mm256_add_epi16(product2, product3);
product0 = _mm256_add_epi16(product0, product2);
product0 = _mm256_madd_epi16(product0, Ones256); product0 = _mm256_madd_epi16(product0, Ones256);
acc = _mm256_add_epi32(acc, product0); product2 = _mm256_adds_epi16(product2, product3);
product2 = _mm256_madd_epi16(product2, Ones256);
acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product2));
#endif #endif
}; };
@@ -267,11 +218,11 @@ namespace Stockfish::Eval::NNUE::Layers {
__m128i product1 = _mm_maddubs_epi16(a1, b1); __m128i product1 = _mm_maddubs_epi16(a1, b1);
__m128i product2 = _mm_maddubs_epi16(a2, b2); __m128i product2 = _mm_maddubs_epi16(a2, b2);
__m128i product3 = _mm_maddubs_epi16(a3, b3); __m128i product3 = _mm_maddubs_epi16(a3, b3);
product0 = _mm_add_epi16(product0, product1); product0 = _mm_adds_epi16(product0, product1);
product2 = _mm_add_epi16(product2, product3);
product0 = _mm_add_epi16(product0, product2);
product0 = _mm_madd_epi16(product0, Ones128); product0 = _mm_madd_epi16(product0, Ones128);
acc = _mm_add_epi32(acc, product0); product2 = _mm_adds_epi16(product2, product3);
product2 = _mm_madd_epi16(product2, Ones128);
acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product2));
}; };
#endif #endif
@@ -300,6 +251,8 @@ namespace Stockfish::Eval::NNUE::Layers {
#endif #endif
#if defined (USE_SSSE3) #if defined (USE_SSSE3)
// Different layout, we process 4 inputs at a time, always.
static_assert(InputDimensions % 4 == 0);
const auto output = reinterpret_cast<OutputType*>(buffer); const auto output = reinterpret_cast<OutputType*>(buffer);
const auto inputVector = reinterpret_cast<const vec_t*>(input); const auto inputVector = reinterpret_cast<const vec_t*>(input);
@@ -310,7 +263,7 @@ namespace Stockfish::Eval::NNUE::Layers {
// because then it is also an input dimension. // because then it is also an input dimension.
if constexpr (OutputDimensions % OutputSimdWidth == 0) if constexpr (OutputDimensions % OutputSimdWidth == 0)
{ {
constexpr IndexType NumChunks = PaddedInputDimensions / 4; constexpr IndexType NumChunks = InputDimensions / 4;
const auto input32 = reinterpret_cast<const std::int32_t*>(input); const auto input32 = reinterpret_cast<const std::int32_t*>(input);
vec_t* outptr = reinterpret_cast<vec_t*>(output); vec_t* outptr = reinterpret_cast<vec_t*>(output);
@@ -329,8 +282,6 @@ namespace Stockfish::Eval::NNUE::Layers {
for (int j = 0; j * OutputSimdWidth < OutputDimensions; ++j) for (int j = 0; j * OutputSimdWidth < OutputDimensions; ++j)
vec_add_dpbusd_32x4(outptr[j], in0, col0[j], in1, col1[j], in2, col2[j], in3, col3[j]); vec_add_dpbusd_32x4(outptr[j], in0, col0[j], in1, col1[j], in2, col2[j], in3, col3[j]);
} }
for (int i = 0; i < canSaturate16.count; ++i)
output[canSaturate16.ids[i].out] += input[canSaturate16.ids[i].in] * canSaturate16.ids[i].w;
} }
else if constexpr (OutputDimensions == 1) else if constexpr (OutputDimensions == 1)
{ {
@@ -377,17 +328,21 @@ namespace Stockfish::Eval::NNUE::Layers {
auto output = reinterpret_cast<OutputType*>(buffer); auto output = reinterpret_cast<OutputType*>(buffer);
#if defined(USE_SSE2) #if defined(USE_SSE2)
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; // At least a multiple of 16, with SSE2.
static_assert(InputDimensions % SimdWidth == 0);
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m128i Zeros = _mm_setzero_si128(); const __m128i Zeros = _mm_setzero_si128();
const auto inputVector = reinterpret_cast<const __m128i*>(input); const auto inputVector = reinterpret_cast<const __m128i*>(input);
#elif defined(USE_MMX) #elif defined(USE_MMX)
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; static_assert(InputDimensions % SimdWidth == 0);
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m64 Zeros = _mm_setzero_si64(); const __m64 Zeros = _mm_setzero_si64();
const auto inputVector = reinterpret_cast<const __m64*>(input); const auto inputVector = reinterpret_cast<const __m64*>(input);
#elif defined(USE_NEON) #elif defined(USE_NEON)
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; static_assert(InputDimensions % SimdWidth == 0);
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const auto inputVector = reinterpret_cast<const int8x8_t*>(input); const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
#endif #endif
@@ -473,25 +428,6 @@ namespace Stockfish::Eval::NNUE::Layers {
alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
#if defined (USE_SSSE3)
struct CanSaturate {
int count;
struct Entry {
uint32_t wIdx;
uint16_t out;
uint16_t in;
int8_t w;
} ids[PaddedInputDimensions * OutputDimensions * 3 / 4];
void add(int i, int j, int8_t w, uint32_t wIdx) {
ids[count].wIdx = wIdx;
ids[count].out = i;
ids[count].in = j;
ids[count].w = w;
++count;
}
} canSaturate16;
#endif
}; };
} // namespace Stockfish::Eval::NNUE::Layers } // namespace Stockfish::Eval::NNUE::Layers
+21 -1
View File
@@ -72,6 +72,7 @@ namespace Stockfish::Eval::NNUE::Layers {
const auto output = reinterpret_cast<OutputType*>(buffer); const auto output = reinterpret_cast<OutputType*>(buffer);
#if defined(USE_AVX2) #if defined(USE_AVX2)
if constexpr (InputDimensions % SimdWidth == 0) {
constexpr IndexType NumChunks = InputDimensions / SimdWidth; constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m256i Zero = _mm256_setzero_si256(); const __m256i Zero = _mm256_setzero_si256();
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
@@ -87,7 +88,26 @@ namespace Stockfish::Eval::NNUE::Layers {
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
_mm256_packs_epi16(words0, words1), Zero), Offsets)); _mm256_packs_epi16(words0, words1), Zero), Offsets));
} }
constexpr IndexType Start = NumChunks * SimdWidth; } else {
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const __m128i Zero = _mm_setzero_si128();
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
}
}
constexpr IndexType Start =
InputDimensions % SimdWidth == 0
? InputDimensions / SimdWidth * SimdWidth
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
#elif defined(USE_SSE2) #elif defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / SimdWidth; constexpr IndexType NumChunks = InputDimensions / SimdWidth;
+1 -1
View File
@@ -53,7 +53,7 @@ class InputSlice {
return true; return true;
} }
// Read network parameters // Write network parameters
bool write_parameters(std::ostream& /*stream*/) const { bool write_parameters(std::ostream& /*stream*/) const {
return true; return true;
} }
+2 -2
View File
@@ -30,8 +30,8 @@ namespace Stockfish::Eval::NNUE {
// Class that holds the result of affine transformation of input features // Class that holds the result of affine transformation of input features
struct alignas(CacheLineSize) Accumulator { struct alignas(CacheLineSize) Accumulator {
std::int16_t std::int16_t accumulation[2][TransformedFeatureDimensions];
accumulation[2][TransformedFeatureDimensions]; std::int32_t psqtAccumulation[2][PSQTBuckets];
AccumulatorState state[2]; AccumulatorState state[2];
}; };
+6 -4
View File
@@ -23,7 +23,7 @@
#include "nnue_common.h" #include "nnue_common.h"
#include "features/half_kp.h" #include "features/half_ka_v2.h"
#include "layers/input_slice.h" #include "layers/input_slice.h"
#include "layers/affine_transform.h" #include "layers/affine_transform.h"
@@ -32,16 +32,18 @@
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
// Input features used in evaluation function // Input features used in evaluation function
using FeatureSet = Features::HalfKP; using FeatureSet = Features::HalfKAv2;
// Number of input feature dimensions after conversion // Number of input feature dimensions after conversion
constexpr IndexType TransformedFeatureDimensions = 256; constexpr IndexType TransformedFeatureDimensions = 512;
constexpr IndexType PSQTBuckets = 8;
constexpr IndexType LayerStacks = 8;
namespace Layers { namespace Layers {
// Define network structure // Define network structure
using InputLayer = InputSlice<TransformedFeatureDimensions * 2>; using InputLayer = InputSlice<TransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>; using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 16>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>; using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>; using OutputLayer = AffineTransform<HiddenLayer2, 1>;
+1 -1
View File
@@ -48,7 +48,7 @@
namespace Stockfish::Eval::NNUE { namespace Stockfish::Eval::NNUE {
// Version of the evaluation file // Version of the evaluation file
constexpr std::uint32_t Version = 0x7AF32F16u; constexpr std::uint32_t Version = 0x7AF32F20u;
// Constant used in evaluation value calculation // Constant used in evaluation value calculation
constexpr int OutputScale = 16; constexpr int OutputScale = 16;
+131 -2
View File
@@ -36,45 +36,82 @@ namespace Stockfish::Eval::NNUE {
// vector registers. // vector registers.
#define VECTOR #define VECTOR
static_assert(PSQTBuckets == 8, "Assumed by the current choice of constants.");
#ifdef USE_AVX512 #ifdef USE_AVX512
typedef __m512i vec_t; typedef __m512i vec_t;
typedef __m256i psqt_vec_t;
#define vec_load(a) _mm512_load_si512(a) #define vec_load(a) _mm512_load_si512(a)
#define vec_store(a,b) _mm512_store_si512(a,b) #define vec_store(a,b) _mm512_store_si512(a,b)
#define vec_add_16(a,b) _mm512_add_epi16(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b)
#define vec_sub_16(a,b) _mm512_sub_epi16(a,b) #define vec_sub_16(a,b) _mm512_sub_epi16(a,b)
#define vec_load_psqt(a) _mm256_load_si256(a)
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
#define vec_zero_psqt() _mm256_setzero_si256()
static constexpr IndexType NumRegs = 8; // only 8 are needed static constexpr IndexType NumRegs = 8; // only 8 are needed
static constexpr IndexType NumPsqtRegs = 1;
#elif USE_AVX2 #elif USE_AVX2
typedef __m256i vec_t; typedef __m256i vec_t;
typedef __m256i psqt_vec_t;
#define vec_load(a) _mm256_load_si256(a) #define vec_load(a) _mm256_load_si256(a)
#define vec_store(a,b) _mm256_store_si256(a,b) #define vec_store(a,b) _mm256_store_si256(a,b)
#define vec_add_16(a,b) _mm256_add_epi16(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b)
#define vec_sub_16(a,b) _mm256_sub_epi16(a,b) #define vec_sub_16(a,b) _mm256_sub_epi16(a,b)
#define vec_load_psqt(a) _mm256_load_si256(a)
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
#define vec_zero_psqt() _mm256_setzero_si256()
static constexpr IndexType NumRegs = 16; static constexpr IndexType NumRegs = 16;
static constexpr IndexType NumPsqtRegs = 1;
#elif USE_SSE2 #elif USE_SSE2
typedef __m128i vec_t; typedef __m128i vec_t;
typedef __m128i psqt_vec_t;
#define vec_load(a) (*(a)) #define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_epi16(a,b) #define vec_add_16(a,b) _mm_add_epi16(a,b)
#define vec_sub_16(a,b) _mm_sub_epi16(a,b) #define vec_sub_16(a,b) _mm_sub_epi16(a,b)
#define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) _mm_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b)
#define vec_zero_psqt() _mm_setzero_si128()
static constexpr IndexType NumRegs = Is64Bit ? 16 : 8; static constexpr IndexType NumRegs = Is64Bit ? 16 : 8;
static constexpr IndexType NumPsqtRegs = 2;
#elif USE_MMX #elif USE_MMX
typedef __m64 vec_t; typedef __m64 vec_t;
typedef __m64 psqt_vec_t;
#define vec_load(a) (*(a)) #define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_pi16(a,b) #define vec_add_16(a,b) _mm_add_pi16(a,b)
#define vec_sub_16(a,b) _mm_sub_pi16(a,b) #define vec_sub_16(a,b) _mm_sub_pi16(a,b)
#define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) _mm_add_pi32(a,b)
#define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b)
#define vec_zero_psqt() _mm_setzero_si64()
static constexpr IndexType NumRegs = 8; static constexpr IndexType NumRegs = 8;
static constexpr IndexType NumPsqtRegs = 4;
#elif USE_NEON #elif USE_NEON
typedef int16x8_t vec_t; typedef int16x8_t vec_t;
typedef int32x4_t psqt_vec_t;
#define vec_load(a) (*(a)) #define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b) #define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) vaddq_s16(a,b) #define vec_add_16(a,b) vaddq_s16(a,b)
#define vec_sub_16(a,b) vsubq_s16(a,b) #define vec_sub_16(a,b) vsubq_s16(a,b)
#define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) vaddq_s32(a,b)
#define vec_sub_psqt_32(a,b) vsubq_s32(a,b)
#define vec_zero_psqt() psqt_vec_t{0}
static constexpr IndexType NumRegs = 16; static constexpr IndexType NumRegs = 16;
static constexpr IndexType NumPsqtRegs = 2;
#else #else
#undef VECTOR #undef VECTOR
@@ -88,9 +125,13 @@ namespace Stockfish::Eval::NNUE {
// Number of output dimensions for one side // Number of output dimensions for one side
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
static constexpr int LazyThreshold = 1400;
#ifdef VECTOR #ifdef VECTOR
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
#endif #endif
public: public:
@@ -116,6 +157,8 @@ namespace Stockfish::Eval::NNUE {
biases[i] = read_little_endian<BiasType>(stream); biases[i] = read_little_endian<BiasType>(stream);
for (std::size_t i = 0; i < HalfDimensions * InputDimensions; ++i) for (std::size_t i = 0; i < HalfDimensions * InputDimensions; ++i)
weights[i] = read_little_endian<WeightType>(stream); weights[i] = read_little_endian<WeightType>(stream);
for (std::size_t i = 0; i < PSQTBuckets * InputDimensions; ++i)
psqtWeights[i] = read_little_endian<PSQTWeightType>(stream);
return !stream.fail(); return !stream.fail();
} }
@@ -129,11 +172,21 @@ namespace Stockfish::Eval::NNUE {
} }
// Convert input features // Convert input features
void transform(const Position& pos, OutputType* output) const { std::pair<std::int32_t, bool> transform(const Position& pos, OutputType* output, int bucket) const {
update_accumulator(pos, WHITE); update_accumulator(pos, WHITE);
update_accumulator(pos, BLACK); update_accumulator(pos, BLACK);
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
const auto& accumulation = pos.state()->accumulator.accumulation; const auto& accumulation = pos.state()->accumulator.accumulation;
const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation;
const auto psqt = (
psqtAccumulation[static_cast<int>(perspectives[0])][bucket]
- psqtAccumulation[static_cast<int>(perspectives[1])][bucket]
) / 2;
if (abs(psqt) > LazyThreshold * OutputScale)
return { psqt, true };
#if defined(USE_AVX512) #if defined(USE_AVX512)
constexpr IndexType NumChunks = HalfDimensions / (SimdWidth * 2); constexpr IndexType NumChunks = HalfDimensions / (SimdWidth * 2);
@@ -164,7 +217,6 @@ namespace Stockfish::Eval::NNUE {
const int8x8_t Zero = {0}; const int8x8_t Zero = {0};
#endif #endif
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
for (IndexType p = 0; p < 2; ++p) { for (IndexType p = 0; p < 2; ++p) {
const IndexType offset = HalfDimensions * p; const IndexType offset = HalfDimensions * p;
@@ -241,6 +293,8 @@ namespace Stockfish::Eval::NNUE {
#if defined(USE_MMX) #if defined(USE_MMX)
_mm_empty(); _mm_empty();
#endif #endif
return { psqt, false };
} }
private: private:
@@ -256,6 +310,7 @@ namespace Stockfish::Eval::NNUE {
// Gcc-10.2 unnecessarily spills AVX2 registers if this array // Gcc-10.2 unnecessarily spills AVX2 registers if this array
// is defined in the VECTOR code below, once in each branch // is defined in the VECTOR code below, once in each branch
vec_t acc[NumRegs]; vec_t acc[NumRegs];
psqt_vec_t psqt[NumPsqtRegs];
#endif #endif
// Look for a usable accumulator of an earlier position. We keep track // Look for a usable accumulator of an earlier position. We keep track
@@ -334,12 +389,52 @@ namespace Stockfish::Eval::NNUE {
} }
} }
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
{
// Load accumulator
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
&st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_load_psqt(&accTilePsqt[k]);
for (IndexType i = 0; states_to_update[i]; ++i)
{
// Difference calculation for the deactivated features
for (const auto index : removed[i])
{
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);
}
// Difference calculation for the activated features
for (const auto index : added[i])
{
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
}
// Store accumulator
accTilePsqt = reinterpret_cast<psqt_vec_t*>(
&states_to_update[i]->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
vec_store_psqt(&accTilePsqt[k], psqt[k]);
}
}
#else #else
for (IndexType i = 0; states_to_update[i]; ++i) for (IndexType i = 0; states_to_update[i]; ++i)
{ {
std::memcpy(states_to_update[i]->accumulator.accumulation[perspective], std::memcpy(states_to_update[i]->accumulator.accumulation[perspective],
st->accumulator.accumulation[perspective], st->accumulator.accumulation[perspective],
HalfDimensions * sizeof(BiasType)); HalfDimensions * sizeof(BiasType));
for (std::size_t k = 0; k < PSQTBuckets; ++k)
states_to_update[i]->accumulator.psqtAccumulation[perspective][k] = st->accumulator.psqtAccumulation[perspective][k];
st = states_to_update[i]; st = states_to_update[i];
// Difference calculation for the deactivated features // Difference calculation for the deactivated features
@@ -349,6 +444,9 @@ namespace Stockfish::Eval::NNUE {
for (IndexType j = 0; j < HalfDimensions; ++j) for (IndexType j = 0; j < HalfDimensions; ++j)
st->accumulator.accumulation[perspective][j] -= weights[offset + j]; st->accumulator.accumulation[perspective][j] -= weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
st->accumulator.psqtAccumulation[perspective][k] -= psqtWeights[index * PSQTBuckets + k];
} }
// Difference calculation for the activated features // Difference calculation for the activated features
@@ -358,6 +456,9 @@ namespace Stockfish::Eval::NNUE {
for (IndexType j = 0; j < HalfDimensions; ++j) for (IndexType j = 0; j < HalfDimensions; ++j)
st->accumulator.accumulation[perspective][j] += weights[offset + j]; st->accumulator.accumulation[perspective][j] += weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
st->accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k];
} }
} }
#endif #endif
@@ -393,16 +494,42 @@ namespace Stockfish::Eval::NNUE {
vec_store(&accTile[k], acc[k]); vec_store(&accTile[k], acc[k]);
} }
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
{
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_zero_psqt();
for (const auto index : active)
{
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
}
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
&accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
vec_store_psqt(&accTilePsqt[k], psqt[k]);
}
#else #else
std::memcpy(accumulator.accumulation[perspective], biases, std::memcpy(accumulator.accumulation[perspective], biases,
HalfDimensions * sizeof(BiasType)); HalfDimensions * sizeof(BiasType));
for (std::size_t k = 0; k < PSQTBuckets; ++k)
accumulator.psqtAccumulation[perspective][k] = 0;
for (const auto index : active) for (const auto index : active)
{ {
const IndexType offset = HalfDimensions * index; const IndexType offset = HalfDimensions * index;
for (IndexType j = 0; j < HalfDimensions; ++j) for (IndexType j = 0; j < HalfDimensions; ++j)
accumulator.accumulation[perspective][j] += weights[offset + j]; accumulator.accumulation[perspective][j] += weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k];
} }
#endif #endif
} }
@@ -414,9 +541,11 @@ namespace Stockfish::Eval::NNUE {
using BiasType = std::int16_t; using BiasType = std::int16_t;
using WeightType = std::int16_t; using WeightType = std::int16_t;
using PSQTWeightType = std::int32_t;
alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) BiasType biases[HalfDimensions];
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
}; };
} // namespace Stockfish::Eval::NNUE } // namespace Stockfish::Eval::NNUE
+6 -3
View File
@@ -993,7 +993,7 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
} }
/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips /// Position::do_null_move() is used to do a "null move": it flips
/// the side to move without executing any move on the board. /// the side to move without executing any move on the board.
void Position::do_null_move(StateInfo& newSt) { void Position::do_null_move(StateInfo& newSt) {
@@ -1033,6 +1033,9 @@ void Position::do_null_move(StateInfo& newSt) {
assert(pos_is_ok()); assert(pos_is_ok());
} }
/// Position::undo_null_move() must be used to undo a "null move"
void Position::undo_null_move() { void Position::undo_null_move() {
assert(!checkers()); assert(!checkers());
@@ -1098,8 +1101,8 @@ bool Position::see_ge(Move m, Value threshold) const {
if (!(stmAttackers = attackers & pieces(stm))) if (!(stmAttackers = attackers & pieces(stm)))
break; break;
// Don't allow pinned pieces to attack (except the king) as long as // Don't allow pinned pieces to attack as long as there are
// there are pinners on their original square. // pinners on their original square.
if (pinners(~stm) & occupied) if (pinners(~stm) & occupied)
stmAttackers &= ~blockers_for_king(stm); stmAttackers &= ~blockers_for_king(stm);
+3 -3
View File
@@ -54,11 +54,11 @@ struct StateInfo {
// Not copied when making a move (will be recomputed anyhow) // Not copied when making a move (will be recomputed anyhow)
Key key; Key key;
Bitboard checkersBB; Bitboard checkersBB;
Piece capturedPiece;
StateInfo* previous; StateInfo* previous;
Bitboard blockersForKing[COLOR_NB]; Bitboard blockersForKing[COLOR_NB];
Bitboard pinners[COLOR_NB]; Bitboard pinners[COLOR_NB];
Bitboard checkSquares[PIECE_TYPE_NB]; Bitboard checkSquares[PIECE_TYPE_NB];
Piece capturedPiece;
int repetition; int repetition;
// Used by NNUE // Used by NNUE
@@ -219,11 +219,11 @@ private:
int castlingRightsMask[SQUARE_NB]; int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB];
Bitboard castlingPath[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB];
Thread* thisThread;
StateInfo* st;
int gamePly; int gamePly;
Color sideToMove; Color sideToMove;
Score psq; Score psq;
Thread* thisThread;
StateInfo* st;
bool chess960; bool chess960;
}; };
+23 -51
View File
@@ -60,7 +60,7 @@ namespace {
// Futility margin // Futility margin
Value futility_margin(Depth d, bool improving) { Value futility_margin(Depth d, bool improving) {
return Value(234 * (d - improving)); return Value(214 * (d - improving));
} }
// Reductions lookup table, initialized at startup // Reductions lookup table, initialized at startup
@@ -68,7 +68,7 @@ namespace {
Depth reduction(bool i, Depth d, int mn) { Depth reduction(bool i, Depth d, int mn) {
int r = Reductions[d] * Reductions[mn]; int r = Reductions[d] * Reductions[mn];
return (r + 503) / 1024 + (!i && r > 915); return (r + 534) / 1024 + (!i && r > 904);
} }
constexpr int futility_move_count(bool improving, Depth depth) { constexpr int futility_move_count(bool improving, Depth depth) {
@@ -77,7 +77,7 @@ namespace {
// History and stats update bonus, based on depth // History and stats update bonus, based on depth
int stat_bonus(Depth d) { int stat_bonus(Depth d) {
return d > 14 ? 66 : 6 * d * d + 231 * d - 206; return d > 14 ? 73 : 6 * d * d + 229 * d - 215;
} }
// Add a small random component to draw evaluations to avoid 3-fold blindness // Add a small random component to draw evaluations to avoid 3-fold blindness
@@ -374,7 +374,7 @@ void Thread::search() {
// Start with a small aspiration window and, in the case of a fail // Start with a small aspiration window and, in the case of a fail
// high/low, re-search with a bigger window until we don't fail // high/low, re-search with a bigger window until we don't fail
// high/low anymore. // high/low anymore.
failedHighCnt = 0; int failedHighCnt = 0;
while (true) while (true)
{ {
Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter); Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter);
@@ -764,7 +764,7 @@ namespace {
if ((ss-1)->currentMove != MOVE_NULL) if ((ss-1)->currentMove != MOVE_NULL)
ss->staticEval = eval = evaluate(pos); ss->staticEval = eval = evaluate(pos);
else else
ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo; ss->staticEval = eval = -(ss-1)->staticEval;
// Save static evaluation into transposition table // Save static evaluation into transposition table
tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
@@ -773,7 +773,7 @@ namespace {
// Use static evaluation difference to improve quiet move ordering // Use static evaluation difference to improve quiet move ordering
if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture)
{ {
int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval - 2 * Tempo), -1000, 1000); int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval), -1000, 1000);
thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus;
} }
@@ -795,10 +795,10 @@ namespace {
// Step 8. Null move search with verification search (~40 Elo) // Step 8. Null move search with verification search (~40 Elo)
if ( !PvNode if ( !PvNode
&& (ss-1)->currentMove != MOVE_NULL && (ss-1)->currentMove != MOVE_NULL
&& (ss-1)->statScore < 24185 && (ss-1)->statScore < 23767
&& eval >= beta && eval >= beta
&& eval >= ss->staticEval && eval >= ss->staticEval
&& ss->staticEval >= beta - 24 * depth - 34 * improving + 162 * ss->ttPv + 159 && ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159
&& !excludedMove && !excludedMove
&& pos.non_pawn_material(us) && pos.non_pawn_material(us)
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
@@ -806,7 +806,7 @@ namespace {
assert(eval - beta >= 0); assert(eval - beta >= 0);
// Null move dynamic reduction based on depth and value // Null move dynamic reduction based on depth and value
Depth R = (1062 + 68 * depth) / 256 + std::min(int(eval - beta) / 190, 3); Depth R = (1090 + 81 * depth) / 256 + std::min(int(eval - beta) / 205, 3);
ss->currentMove = MOVE_NULL; ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
@@ -844,7 +844,7 @@ namespace {
probCutBeta = beta + 209 - 44 * improving; probCutBeta = beta + 209 - 44 * improving;
// Step 9. ProbCut (~10 Elo) // Step 9. ProbCut (~4 Elo)
// If we have a good enough capture and a reduced search returns a value // If we have a good enough capture and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move. // much above beta, we can (almost) safely prune the previous move.
if ( !PvNode if ( !PvNode
@@ -859,16 +859,6 @@ namespace {
&& ttValue != VALUE_NONE && ttValue != VALUE_NONE
&& ttValue < probCutBeta)) && ttValue < probCutBeta))
{ {
// if ttMove is a capture and value from transposition table is good enough produce probCut
// cutoff without digging into actual probCut search
if ( ss->ttHit
&& tte->depth() >= depth - 3
&& ttValue != VALUE_NONE
&& ttValue >= probCutBeta
&& ttMove
&& pos.capture_or_promotion(ttMove))
return probCutBeta;
assert(probCutBeta < VALUE_INFINITE); assert(probCutBeta < VALUE_INFINITE);
MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory); MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
@@ -929,7 +919,7 @@ moves_loop: // When in check, search starts from here
ttCapture = ttMove && pos.capture_or_promotion(ttMove); ttCapture = ttMove && pos.capture_or_promotion(ttMove);
// Step 11. A small Probcut idea, when we are in check // Step 11. A small Probcut idea, when we are in check
probCutBeta = beta + 400; probCutBeta = beta + 409;
if ( ss->inCheck if ( ss->inCheck
&& !PvNode && !PvNode
&& depth >= 4 && depth >= 4
@@ -1034,8 +1024,8 @@ moves_loop: // When in check, search starts from here
} }
else else
{ {
// Countermoves based pruning (~20 Elo) // Continuation history based pruning (~20 Elo)
if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1) if ( lmrDepth < 5
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
&& (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
continue; continue;
@@ -1083,7 +1073,7 @@ moves_loop: // When in check, search starts from here
{ {
extension = 1; extension = 1;
singularQuietLMR = !ttCapture; singularQuietLMR = !ttCapture;
if (!PvNode && value < singularBeta - 140) if (!PvNode && value < singularBeta - 93)
extension = 2; extension = 2;
} }
@@ -1131,21 +1121,18 @@ moves_loop: // When in check, search starts from here
if ( depth >= 3 if ( depth >= 3
&& moveCount > 1 + 2 * rootNode && moveCount > 1 + 2 * rootNode
&& ( !captureOrPromotion && ( !captureOrPromotion
|| moveCountPruning
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
|| cutNode || cutNode
|| (!PvNode && !formerPv && captureHistory[movedPiece][to_sq(move)][type_of(pos.captured_piece())] < 3678) || (!PvNode && !formerPv))
|| thisThread->ttHitAverage < 432 * TtHitAverageResolution * TtHitAverageWindow / 1024)
&& (!PvNode || ss->ply > 1 || thisThread->id() % 4 != 3)) && (!PvNode || ss->ply > 1 || thisThread->id() % 4 != 3))
{ {
Depth r = reduction(improving, depth, moveCount); Depth r = reduction(improving, depth, moveCount);
// Decrease reduction if the ttHit running average is large // Decrease reduction if the ttHit running average is large (~0 Elo)
if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024) if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024)
r--; r--;
// Decrease reduction if position is or has been on the PV // Decrease reduction if position is or has been on the PV
// and node is not likely to fail low. (~10 Elo) // and node is not likely to fail low. (~3 Elo)
if ( ss->ttPv if ( ss->ttPv
&& !likelyFailLow) && !likelyFailLow)
r -= 2; r -= 2;
@@ -1170,10 +1157,7 @@ moves_loop: // When in check, search starts from here
if (ttCapture) if (ttCapture)
r++; r++;
// Increase reduction at root if failing high // Increase reduction for cut nodes (~3 Elo)
r += rootNode ? thisThread->failedHighCnt * thisThread->failedHighCnt * moveCount / 512 : 0;
// Increase reduction for cut nodes (~10 Elo)
if (cutNode) if (cutNode)
r += 2; r += 2;
@@ -1181,23 +1165,11 @@ moves_loop: // When in check, search starts from here
+ (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[0])[movedPiece][to_sq(move)]
+ (*contHist[1])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)]
+ (*contHist[3])[movedPiece][to_sq(move)] + (*contHist[3])[movedPiece][to_sq(move)]
- 4741; - 4923;
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
if (ss->statScore >= -89 && (ss-1)->statScore < -116)
r--;
else if ((ss-1)->statScore >= -112 && ss->statScore < -100)
r++;
// Decrease/increase reduction for moves with a good/bad history (~30 Elo) // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
// If we are not in check use statScore, but if we are in check we use if (!ss->inCheck)
// the sum of main history and first continuation history with an offset. r -= ss->statScore / 14721;
if (ss->inCheck)
r -= (thisThread->mainHistory[us][from_to(move)]
+ (*contHist[0])[movedPiece][to_sq(move)] - 3833) / 16384;
else
r -= ss->statScore / 14790;
} }
// In general we want to cap the LMR depth search at newDepth. But if // In general we want to cap the LMR depth search at newDepth. But if
@@ -1460,7 +1432,7 @@ moves_loop: // When in check, search starts from here
// and addition of two tempos // and addition of two tempos
ss->staticEval = bestValue = ss->staticEval = bestValue =
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos) (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
: -(ss-1)->staticEval + 2 * Tempo; : -(ss-1)->staticEval;
// Stand pat. Return immediately if static value is at least beta // Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta) if (bestValue >= beta)
@@ -1548,7 +1520,7 @@ moves_loop: // When in check, search starts from here
[pos.moved_piece(move)] [pos.moved_piece(move)]
[to_sq(move)]; [to_sq(move)];
// CounterMove based pruning // Continuation history based pruning
if ( !captureOrPromotion if ( !captureOrPromotion
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY && bestValue > VALUE_TB_LOSS_IN_MAX_PLY
&& (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold
+1 -15
View File
@@ -74,16 +74,7 @@ public:
void idle_loop(); void idle_loop();
void start_searching(); void start_searching();
void wait_for_search_finished(); void wait_for_search_finished();
int id() const { return idx; } size_t id() const { return idx; }
void wait_for_worker_finished();
size_t thread_idx() const { return idx; }
template <typename FuncT>
void set_eval_callback(FuncT&& f) { on_eval_callback = std::forward<FuncT>(f); }
void clear_eval_callback() { on_eval_callback = nullptr; }
void on_eval() { if (on_eval_callback) on_eval_callback(rootPos); }
Pawns::Table pawnsTable; Pawns::Table pawnsTable;
Material::Table materialTable; Material::Table materialTable;
@@ -103,11 +94,6 @@ public:
CapturePieceToHistory captureHistory; CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2]; ContinuationHistory continuationHistory[2][2];
Score contempt; Score contempt;
int failedHighCnt;
bool rootInTB;
int Cardinality;
bool UseRule50;
Depth ProbeDepth;
}; };
-8
View File
@@ -94,14 +94,6 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
optimumTime = TimePoint(optScale * timeLeft); optimumTime = TimePoint(optScale * timeLeft);
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)); maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime));
if (Stockfish::Search::Limits.use_time_management())
{
int strength = std::log( std::max(1, int(optimumTime * Threads.size() / 10))) * 60;
tempoNNUE = std::clamp( (strength + 264) / 24, 18, 30);
}
else
tempoNNUE = 28; // default for no time given
if (Options["Ponder"]) if (Options["Ponder"])
optimumTime += optimumTime / 4; optimumTime += optimumTime / 4;
} }
-1
View File
@@ -37,7 +37,6 @@ public:
TimePoint(Threads.nodes_searched()) : now() - startTime; } TimePoint(Threads.nodes_searched()) : now() - startTime; }
int64_t availableNodes; // When in 'nodes as time' mode int64_t availableNodes; // When in 'nodes as time' mode
int tempoNNUE;
private: private:
TimePoint startTime; TimePoint startTime;
-19
View File
@@ -30,7 +30,6 @@ namespace Stockfish {
bool Tune::update_on_last; bool Tune::update_on_last;
const UCI::Option* LastOption = nullptr; const UCI::Option* LastOption = nullptr;
BoolConditions Conditions;
static std::map<std::string, int> TuneResults; static std::map<std::string, int> TuneResults;
string Tune::next(string& names, bool pop) { string Tune::next(string& names, bool pop) {
@@ -110,24 +109,6 @@ template<> void Tune::Entry<Score>::read_option() {
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {} template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); } template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
// Set binary conditions according to a probability that depends
// on the corresponding parameter value.
void BoolConditions::set() {
static PRNG rng(now());
static bool startup = true; // To workaround fishtest bench
for (size_t i = 0; i < binary.size(); i++)
binary[i] = !startup && (values[i] + int(rng.rand<unsigned>() % variance) > threshold);
startup = false;
for (size_t i = 0; i < binary.size(); i++)
sync_cout << binary[i] << sync_endl;
}
} // namespace Stockfish } // namespace Stockfish
-34
View File
@@ -46,27 +46,6 @@ struct SetRange {
#define SetDefaultRange SetRange(default_range) #define SetDefaultRange SetRange(default_range)
/// BoolConditions struct is used to tune boolean conditions in the
/// code by toggling them on/off according to a probability that
/// depends on the value of a tuned integer parameter: for high
/// values of the parameter condition is always disabled, for low
/// values is always enabled, otherwise it is enabled with a given
/// probability that depnends on the parameter under tuning.
struct BoolConditions {
void init(size_t size) { values.resize(size, defaultValue), binary.resize(size, 0); }
void set();
std::vector<int> binary, values;
int defaultValue = 465, variance = 40, threshold = 500;
SetRange range = SetRange(0, 1000);
};
extern BoolConditions Conditions;
inline void set_conditions() { Conditions.set(); }
/// Tune class implements the 'magic' code that makes the setup of a fishtest /// Tune class implements the 'magic' code that makes the setup of a fishtest
/// tuning session as easy as it can be. Mainly you have just to remove const /// tuning session as easy as it can be. Mainly you have just to remove const
/// qualifiers from the variables you want to tune and flag them for tuning, so /// qualifiers from the variables you want to tune and flag them for tuning, so
@@ -159,14 +138,6 @@ class Tune {
return add(value, (next(names), std::move(names)), args...); return add(value, (next(names), std::move(names)), args...);
} }
// Template specialization for BoolConditions
template<typename... Args>
int add(const SetRange& range, std::string&& names, BoolConditions& cond, Args&&... args) {
for (size_t size = cond.values.size(), i = 0; i < size; i++)
add(cond.range, next(names, i == size - 1) + "_" + std::to_string(i), cond.values[i]);
return add(range, std::move(names), args...);
}
std::vector<std::unique_ptr<EntryBase>> list; std::vector<std::unique_ptr<EntryBase>> list;
public: public:
@@ -187,11 +158,6 @@ public:
#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
// Some macro to tune toggling of boolean conditions
#define CONDITION(x) (Conditions.binary[__COUNTER__] || (x))
#define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \
TUNE(Conditions, set_conditions)
} // namespace Stockfish } // namespace Stockfish
#endif // #ifndef TUNE_H_INCLUDED #endif // #ifndef TUNE_H_INCLUDED
-1
View File
@@ -193,7 +193,6 @@ enum Value : int {
BishopValueMg = 825, BishopValueEg = 915, BishopValueMg = 825, BishopValueEg = 915,
RookValueMg = 1276, RookValueEg = 1380, RookValueMg = 1276, RookValueEg = 1380,
QueenValueMg = 2538, QueenValueEg = 2682, QueenValueMg = 2538, QueenValueEg = 2682,
Tempo = 28,
MidgameLimit = 15258, EndgameLimit = 3915 MidgameLimit = 15258, EndgameLimit = 3915
}; };