Merge commit '2ce47573b4d3664dca4cbc4354c8c600540d16ad' into cluster

This is the last commit before there are more conflicts.
This commit is contained in:
Steinar H. Gunderson
2025-12-25 17:22:27 +01:00
13 changed files with 242 additions and 159 deletions
+2
View File
@@ -143,6 +143,7 @@ Maciej Żenczykowski (zenczykowski)
Malcolm Campbell (xoto10)
Mark Tenzer (31m059)
marotear
Mathias Parnaudeau (mparnaudeau)
Matt Ginsberg (mattginsberg)
Matthew Lai (matthewlai)
Matthew Sullivan (Matt14916)
@@ -176,6 +177,7 @@ Ofek Shochat (OfekShochat, ghostway)
Ondrej Mosnáček (WOnder93)
Ondřej Mišina (AndrovT)
Oskar Werkelin Ahlin
Ömer Faruk Tutkun (OmerFarukTutkun)
Pablo Vazquez
Panthee
Pascal Romaret
+18
View File
@@ -54,6 +54,20 @@ set_arch_x86_64() {
fi
}
set_arch_ppc_64() {
if $(grep -q -w "altivec" /proc/cpuinfo); then
power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo)
if [ "0$power" -gt 7 ]; then
# VSX started with POWER8
true_arch='ppc-64-vsx'
else
true_arch='ppc-64-altivec'
fi
else
true_arch='ppc-64'
fi
}
# Check the system type
uname_s=$(uname -s)
uname_m=$(uname -m)
@@ -87,6 +101,10 @@ case $uname_s in
file_os='ubuntu'
true_arch='x86-32'
;;
'ppc64'*)
file_os='ubuntu'
set_arch_ppc_64
;;
'aarch64')
file_os='android'
true_arch='armv8'
+40 -2
View File
@@ -99,6 +99,8 @@ VPATH = syzygy:nnue:nnue/features
# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
# altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension
# vsx = yes/no --- -mvsx --- Use POWER VSX SIMD extension
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
# lsx = yes/no --- -mlsx --- Use Loongson SIMD eXtension
@@ -128,7 +130,7 @@ endif
ifeq ($(ARCH), $(filter $(ARCH), \
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \
x86-64-avx2 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 e2k \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \
loongarch64 loongarch64-lsx loongarch64-lasx))
SUPPORTED_ARCH=true
@@ -153,6 +155,8 @@ avxvnni = no
avx512 = no
vnni256 = no
vnni512 = no
altivec = no
vsx = no
neon = no
mpi = no
dotprod = no
@@ -363,6 +367,20 @@ ifeq ($(ARCH),ppc-64)
prefetch = yes
endif
ifeq ($(ARCH),ppc-64-altivec)
arch = ppc64
popcnt = yes
prefetch = yes
altivec = yes
endif
ifeq ($(ARCH),ppc-64-vsx)
arch = ppc64
popcnt = yes
prefetch = yes
vsx = yes
endif
ifeq ($(findstring e2k,$(ARCH)),e2k)
arch = e2k
mmx = yes
@@ -653,7 +671,7 @@ else
endif
ifeq ($(popcnt),yes)
ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64))
CXXFLAGS += -DUSE_POPCNT
else
CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
@@ -723,6 +741,20 @@ ifeq ($(mmx),yes)
endif
endif
ifeq ($(altivec),yes)
CXXFLAGS += -maltivec
ifeq ($(COMP),gcc)
CXXFLAGS += -mabi=altivec
endif
endif
ifeq ($(vsx),yes)
CXXFLAGS += -mvsx
ifeq ($(COMP),gcc)
CXXFLAGS += -DNO_WARN_X86_INTRINSICS -DUSE_SSE2
endif
endif
ifeq ($(neon),yes)
CXXFLAGS += -DUSE_NEON=$(arm_version)
ifeq ($(KERNEL),Linux)
@@ -864,6 +896,8 @@ help:
@echo "x86-32-sse2 > x86 32-bit with sse2 support"
@echo "x86-32 > x86 32-bit generic (with mmx compile support)"
@echo "ppc-64 > PPC 64-bit"
@echo "ppc-64-altivec > PPC 64-bit with altivec support"
@echo "ppc-64-vsx > PPC 64-bit with vsx support"
@echo "ppc-32 > PPC 32-bit"
@echo "armv7 > ARMv7 32-bit"
@echo "armv7-neon > ARMv7 32-bit with popcnt and neon"
@@ -999,6 +1033,8 @@ config-sanity: net
@echo "avx512: '$(avx512)'"
@echo "vnni256: '$(vnni256)'"
@echo "vnni512: '$(vnni512)'"
@echo "altivec: '$(altivec)'"
@echo "vsx: '$(vsx)'"
@echo "neon: '$(neon)'"
@echo "mpi: '$(mpi)'"
@echo "dotprod: '$(dotprod)'"
@@ -1033,6 +1069,8 @@ config-sanity: net
@test "$(avx512)" = "yes" || test "$(avx512)" = "no"
@test "$(vnni256)" = "yes" || test "$(vnni256)" = "no"
@test "$(vnni512)" = "yes" || test "$(vnni512)" = "no"
@test "$(altivec)" = "yes" || test "$(altivec)" = "no"
@test "$(vsx)" = "yes" || test "$(vsx)" = "no"
@test "$(neon)" = "yes" || test "$(neon)" = "no"
@test "$(lsx)" = "yes" || test "$(lsx)" = "no"
@test "$(lasx)" = "yes" || test "$(lasx)" = "no"
+23 -17
View File
@@ -34,15 +34,14 @@ Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
Magic RookMagics[SQUARE_NB];
Magic BishopMagics[SQUARE_NB];
alignas(64) Magic Magics[SQUARE_NB][2];
namespace {
Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]);
// Returns the bitboard of target square for the given step
// from the given square. If the step is off the board, returns empty bitboard.
@@ -82,8 +81,8 @@ void Bitboards::init() {
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
init_magics(ROOK, RookTable, RookMagics);
init_magics(BISHOP, BishopTable, BishopMagics);
init_magics(ROOK, RookTable, Magics);
init_magics(BISHOP, BishopTable, Magics);
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
@@ -142,39 +141,47 @@ Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
// bitboards are used to look up attacks of sliding pieces. As a reference see
// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use
// the so called "fancy" approach.
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
#ifndef USE_PEXT
// Optimal PRNG seeds to pick the correct magics in the shortest time
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
Bitboard occupancy[4096], reference[4096], edges, b;
int epoch[4096] = {}, cnt = 0, size = 0;
Bitboard occupancy[4096];
int epoch[4096] = {}, cnt = 0;
#endif
Bitboard reference[4096];
int size = 0;
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
// Board edges are not considered in the relevant occupancies
edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
// Given a square 's', the mask is the bitboard of sliding attacks from
// 's' computed on an empty board. The index must be big enough to contain
// all the attacks for each possible subset of the mask and so is 2 power
// the number of 1s of the mask. Hence we deduce the size of the shift to
// apply to the 64 or 32 bits word to get the index.
Magic& m = magics[s];
Magic& m = magics[s][pt - BISHOP];
m.mask = sliding_attack(pt, s, 0) & ~edges;
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
#ifndef USE_PEXT
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
#endif
// Set the offset for the attacks table of the square. We have individual
// table sizes for each square with "Fancy Magic Bitboards".
m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size;
m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;
size = 0;
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
// store the corresponding sliding attack bitboard in reference[].
b = size = 0;
Bitboard b = 0;
do
{
#ifndef USE_PEXT
occupancy[size] = b;
#endif
reference[size] = sliding_attack(pt, s, b);
if (HasPext)
@@ -184,9 +191,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
b = (b - m.mask) & m.mask;
} while (b);
if (HasPext)
continue;
#ifndef USE_PEXT
PRNG rng(seeds[Is64Bit][rank_of(s)]);
// Find a magic for square 's' picking up an (almost) random number
@@ -215,6 +220,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
break;
}
}
#endif
}
}
}
+12 -9
View File
@@ -67,27 +67,31 @@ extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
// Magic holds all magic bitboards relevant data for a single square
struct Magic {
Bitboard mask;
Bitboard magic;
Bitboard* attacks;
unsigned shift;
#ifndef USE_PEXT
Bitboard magic;
unsigned shift;
#endif
// Compute the attack's index using the 'magic bitboards' approach
unsigned index(Bitboard occupied) const {
if (HasPext)
return unsigned(pext(occupied, mask));
#ifdef USE_PEXT
return unsigned(pext(occupied, mask));
#else
if (Is64Bit)
return unsigned(((occupied & mask) * magic) >> shift);
unsigned lo = unsigned(occupied) & unsigned(mask);
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
#endif
}
Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; }
};
extern Magic RookMagics[SQUARE_NB];
extern Magic BishopMagics[SQUARE_NB];
extern Magic Magics[SQUARE_NB][2];
constexpr Bitboard square_bb(Square s) {
assert(is_ok(s));
@@ -229,9 +233,8 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) {
switch (Pt)
{
case BISHOP :
return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
case ROOK :
return RookMagics[s].attacks[RookMagics[s].index(occupied)];
return Magics[s][Pt - BISHOP].attacks_bb(occupied);
case QUEEN :
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
default :
+4 -9
View File
@@ -59,9 +59,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
assert(!pos.checkers());
bool smallNet = use_smallnet(pos);
int v;
bool smallNet = use_smallnet(pos);
auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small)
: networks.big.evaluate(pos, &caches.big);
@@ -77,14 +75,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
// Blend optimism and eval with nnue complexity
int nnueComplexity = std::abs(psqt - positional);
optimism += optimism * nnueComplexity / (smallNet ? 433 : 453);
nnue -= nnue * nnueComplexity / (smallNet ? 18815 : 17864);
optimism += optimism * nnueComplexity / 468;
nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879);
int material = (smallNet ? 553 : 532) * pos.count<PAWN>() + pos.non_pawn_material();
v = (nnue * (73921 + material) + optimism * (8112 + material)) / (smallNet ? 68104 : 74715);
// Evaluation grain (to get more alpha-beta cuts) with randomization (for robustness)
v = (v / 16) * 16 - 1 + (pos.key() & 0x2);
int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777;
// Damp down the evaluation linearly when shuffling
v -= v * pos.rule50_count() / 212;
+1 -1
View File
@@ -33,7 +33,7 @@ namespace Eval {
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro or the location where this macro is defined, as it is used
// in the Makefile/Fishtest.
#define EvalFileDefaultNameBig "nn-1111cefa1111.nnue"
#define EvalFileDefaultNameBig "nn-1cedc0ffeeee.nnue"
#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue"
namespace NNUE {
+6 -6
View File
@@ -82,20 +82,20 @@ MovePicker::MovePicker(const Position& p,
Move ttm,
Depth d,
const ButterflyHistory* mh,
const ButterflyHistory* rh,
const LowPlyHistory* lph,
const CapturePieceToHistory* cph,
const PieceToHistory** ch,
const PawnHistory* ph,
bool rn) :
int pl) :
pos(p),
mainHistory(mh),
rootHistory(rh),
lowPlyHistory(lph),
captureHistory(cph),
continuationHistory(ch),
pawnHistory(ph),
ttMove(ttm),
depth(d),
rootNode(rn) {
ply(pl) {
if (pos.checkers())
stage = EVASION_TT + !(ttm && pos.pseudo_legal(ttm));
@@ -179,8 +179,8 @@ void MovePicker::score() {
: pt == ROOK && bool(to & threatenedByMinor) ? 24335
: 0);
if (rootNode)
m.value += 4 * (*rootHistory)[pos.side_to_move()][m.from_to()];
if (ply < LOW_PLY_HISTORY_SIZE)
m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + 2 * ply);
}
else // Type == EVASIONS
+29 -25
View File
@@ -34,18 +34,15 @@
namespace Stockfish {
constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2
constexpr int PAWN_CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2
constexpr int MATERIAL_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2
constexpr int MAJOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2
constexpr int MINOR_PIECE_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2
constexpr int NON_PAWN_CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2
constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
constexpr int LOW_PLY_HISTORY_SIZE = 4;
static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0,
"PAWN_HISTORY_SIZE has to be a power of 2");
static_assert((PAWN_CORRECTION_HISTORY_SIZE & (PAWN_CORRECTION_HISTORY_SIZE - 1)) == 0,
static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0,
"CORRECTION_HISTORY_SIZE has to be a power of 2");
enum PawnHistoryType {
@@ -55,24 +52,24 @@ enum PawnHistoryType {
template<PawnHistoryType T = Normal>
inline int pawn_structure_index(const Position& pos) {
return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : PAWN_CORRECTION_HISTORY_SIZE) - 1);
return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1);
}
inline int material_index(const Position& pos) {
return pos.material_key() & (MATERIAL_CORRECTION_HISTORY_SIZE - 1);
return pos.material_key() & (CORRECTION_HISTORY_SIZE - 1);
}
inline int major_piece_index(const Position& pos) {
return pos.major_piece_key() & (MAJOR_PIECE_CORRECTION_HISTORY_SIZE - 1);
return pos.major_piece_key() & (CORRECTION_HISTORY_SIZE - 1);
}
inline int minor_piece_index(const Position& pos) {
return pos.minor_piece_key() & (MINOR_PIECE_CORRECTION_HISTORY_SIZE - 1);
return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1);
}
template<Color c>
inline int non_pawn_index(const Position& pos) {
return pos.non_pawn_key(c) & (NON_PAWN_CORRECTION_HISTORY_SIZE - 1);
return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1);
}
// StatsEntry stores the stat table value. It is usually a number but could
@@ -139,12 +136,19 @@ enum StatsType {
// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo)
using ButterflyHistory = Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>;
// LowPlyHistory is adressed by play and move's from and to squares, used
// to improve move ordering near the root
using LowPlyHistory = Stats<int16_t, 7183, LOW_PLY_HISTORY_SIZE, int(SQUARE_NB) * int(SQUARE_NB)>;
// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
using CapturePieceToHistory = Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>;
// PieceToCorrectionHistory is addressed by a move's [piece][to]
using PieceToCorrectionHistory = Stats<int16_t, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>;
// ContinuationHistory is the combined history of a given pair of moves, usually
// the current one given a previous one. The nested history table is based on
// PieceToHistory instead of ButterflyBoards.
@@ -161,23 +165,23 @@ using PawnHistory = Stats<int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>
// PawnCorrectionHistory is addressed by color and pawn structure
using PawnCorrectionHistory =
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, PAWN_CORRECTION_HISTORY_SIZE>;
// MaterialCorrectionHistory is addressed by color and material configuration
using MaterialCorrectionHistory =
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, MATERIAL_CORRECTION_HISTORY_SIZE>;
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
// MajorPieceCorrectionHistory is addressed by color and king/major piece (Queen, Rook) positions
using MajorPieceCorrectionHistory =
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, MAJOR_PIECE_CORRECTION_HISTORY_SIZE>;
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
// MinorPieceCorrectionHistory is addressed by color and king/minor piece (Knight, Bishop) positions
using MinorPieceCorrectionHistory =
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, MINOR_PIECE_CORRECTION_HISTORY_SIZE>;
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
// NonPawnCorrectionHistory is addressed by color and non-pawn material positions
using NonPawnCorrectionHistory =
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, NON_PAWN_CORRECTION_HISTORY_SIZE>;
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
// ContinuationCorrectionHistory is the combined correction history of a given pair of moves
using ContinuationCorrectionHistory =
Stats<PieceToCorrectionHistory, NOT_USED, PIECE_NB, SQUARE_NB>;
// The MovePicker class is used to pick one pseudo-legal move at a time from the
// current position. The most important method is next_move(), which emits one
@@ -199,11 +203,11 @@ class MovePicker {
Move,
Depth,
const ButterflyHistory*,
const ButterflyHistory*,
const LowPlyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
const PawnHistory*,
bool);
int);
MovePicker(const Position&, Move, int, const CapturePieceToHistory*);
Move next_move(bool skipQuiets = false);
@@ -217,7 +221,7 @@ class MovePicker {
const Position& pos;
const ButterflyHistory* mainHistory;
const ButterflyHistory* rootHistory;
const LowPlyHistory* lowPlyHistory;
const CapturePieceToHistory* captureHistory;
const PieceToHistory** continuationHistory;
const PawnHistory* pawnHistory;
@@ -226,7 +230,7 @@ class MovePicker {
int stage;
int threshold;
Depth depth;
bool rootNode;
int ply;
ExtMove moves[MAX_MOVES];
};
+1 -1
View File
@@ -759,7 +759,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
st->nonPawnMaterial[them] -= PieceValue[captured];
st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq];
if (type_of(pc) == QUEEN || type_of(pc) == ROOK)
if (type_of(captured) == QUEEN || type_of(captured) == ROOK)
st->majorPieceKey ^= Zobrist::psq[captured][capsq];
else
+73 -62
View File
@@ -80,17 +80,21 @@ constexpr int futility_move_count(bool improving, Depth depth) {
// Add correctionHistory value to raw staticEval and guarantee evaluation
// does not hit the tablebase range.
Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) {
Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos, Stack* ss) {
const Color us = pos.side_to_move();
const auto m = (ss - 1)->currentMove;
const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)];
const auto mcv = w.materialCorrectionHistory[us][material_index(pos)];
const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)];
const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)];
const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)];
const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)];
const auto cv =
(99916 * pcv + 55067 * mcv + 55530 * macv + 95324 * micv + 105056 * (wnpcv + bnpcv))
/ 2097152;
int cntcv = 1;
if (m.is_ok())
cntcv = int((*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()]);
const auto cv =
(5932 * pcv + 3269 * macv + 5660 * micv + 6666 * (wnpcv + bnpcv) + 5555 * cntcv) / 131072;
v += cv;
return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
}
@@ -107,21 +111,16 @@ Value value_to_tt(Value v, int ply);
Value value_from_tt(Value v, int ply, int r50c);
void update_pv(Move* pv, Move move, const Move* childPv);
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
void update_quiet_histories(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move move,
int bonus,
bool rootNode);
void update_all_stats(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move bestMove,
Square prevSq,
ValueList<Move, 32>& quietsSearched,
ValueList<Move, 32>& capturesSearched,
Depth depth,
bool rootNode);
void update_quiet_histories(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus);
void update_all_stats(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move bestMove,
Square prevSq,
ValueList<Move, 32>& quietsSearched,
ValueList<Move, 32>& capturesSearched,
Depth depth);
} // namespace
@@ -288,7 +287,8 @@ void Search::Worker::iterative_deepening() {
{
(ss - i)->continuationHistory =
&this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
(ss - i)->staticEval = VALUE_NONE;
(ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0];
(ss - i)->staticEval = VALUE_NONE;
}
for (int i = 0; i <= MAX_PLY + 2; ++i)
@@ -316,7 +316,7 @@ void Search::Worker::iterative_deepening() {
int searchAgainCounter = 0;
rootHistory.fill(0);
lowPlyHistory.fill(0);
// Iterative deepening loop until requested to stop or the target depth is reached
while (++rootDepth < MAX_PLY && !threads.stop
@@ -352,8 +352,8 @@ void Search::Worker::iterative_deepening() {
selDepth = 0;
// Reset aspiration window starting size
delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13797;
Value avg = rootMoves[pvIdx].averageScore;
delta = 5 + avg * avg / 11797;
alpha = std::max(avg - delta, -VALUE_INFINITE);
beta = std::min(avg + delta, VALUE_INFINITE);
@@ -548,16 +548,19 @@ void Search::Worker::iterative_deepening() {
// Reset histories, usually before a new game
void Search::Worker::clear() {
mainHistory.fill(0);
rootHistory.fill(0);
lowPlyHistory.fill(0);
captureHistory.fill(-753);
pawnHistory.fill(-1152);
pawnCorrectionHistory.fill(0);
materialCorrectionHistory.fill(0);
majorPieceCorrectionHistory.fill(0);
minorPieceCorrectionHistory.fill(0);
nonPawnCorrectionHistory[WHITE].fill(0);
nonPawnCorrectionHistory[BLACK].fill(0);
for (auto& to : continuationCorrectionHistory)
for (auto& h : to)
h->fill(0);
for (bool inCheck : {false, true})
for (StatsType c : {NoCaptures, Captures})
for (auto& to : continuationHistory[inCheck][c])
@@ -687,7 +690,7 @@ Value Search::Worker::search(
{
// Bonus for a quiet ttMove that fails high (~2 Elo)
if (!ttCapture)
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth), rootNode);
update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth));
// Extra penalty for early quiet moves of
// the previous ply (~1 Elo on STC, ~2 Elo on LTC)
@@ -782,7 +785,8 @@ Value Search::Worker::search(
else if (PvNode)
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
ss->staticEval = eval =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
// ttValue can be used as a better position evaluation (~7 Elo)
if (ttData.value != VALUE_NONE
@@ -793,7 +797,8 @@ Value Search::Worker::search(
{
unadjustedStaticEval =
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
ss->staticEval = eval =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
// Static evaluation is saved as it was before adjustment by correction history
Distributed::save(tt, threads, thisThread, ttWriter, posKey, VALUE_NONE, ss->ttPv,
@@ -849,8 +854,9 @@ Value Search::Worker::search(
// Null move dynamic reduction based on depth and eval
Depth R = std::min(int(eval - beta) / 209, 6) + depth / 3 + 5;
ss->currentMove = Move::null();
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
ss->currentMove = Move::null();
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0];
pos.do_null_move(st, tt);
@@ -891,12 +897,12 @@ Value Search::Worker::search(
// For cutNodes, if depth is high enough, decrease depth by 2 if there is no ttMove,
// or by 1 if there is a ttMove with an upper bound.
if (cutNode && depth >= 7 && (!ttData.move || ttData.bound == BOUND_UPPER))
depth -= 1 + !ttData.move;
depth -= 2;
// Step 11. ProbCut (~10 Elo)
// If we have a good enough capture (or queen promotion) and a reduced search
// returns a value much above beta, we can (almost) safely prune the previous move.
probCutBeta = beta + 189 - 53 * improving;
probCutBeta = beta + 189 - 53 * improving - 30 * opponentWorsening;
if (!PvNode && depth > 3
&& std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
// If value from transposition table is lower than probCutBeta, don't attempt
@@ -932,6 +938,8 @@ Value Search::Worker::search(
ss->currentMove = move;
ss->continuationHistory =
&this->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][move.to_sq()];
ss->continuationCorrectionHistory =
&this->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()];
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
pos.do_move(move, st);
@@ -980,8 +988,8 @@ moves_loop: // When in check, search starts here
(ss - 6)->continuationHistory};
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->rootHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, rootNode);
MovePicker mp(pos, ttData.move, depth, &thisThread->mainHistory, &thisThread->lowPlyHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply);
value = bestValue;
@@ -1105,7 +1113,7 @@ moves_loop: // When in check, search starts here
// (alpha, beta), then that move is singular and should be extended. To
// verify this we do a reduced search on the position excluding the ttMove
// and if the result is lower than ttValue minus a margin, then we will
// extend the ttMove. Recursive singular search is avoided.
// extend the ttMove. Recursive singular search is avoided.
// Note: the depth margin and singularBeta margin are known for having
// non-linear scaling. Their values are optimized to time controls of
@@ -1181,7 +1189,8 @@ moves_loop: // When in check, search starts here
ss->currentMove = move;
ss->continuationHistory =
&thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()];
ss->continuationCorrectionHistory =
&thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()];
uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0;
// Step 16. Make the move
@@ -1209,7 +1218,7 @@ moves_loop: // When in check, search starts here
// Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo)
if (ttCapture && !capture)
r++;
r += 1 + (depth < 8);
// Increase reduction if next ply has a lot of fail high (~5 Elo)
if ((ss + 1)->cutoffCnt > 3)
@@ -1252,8 +1261,7 @@ moves_loop: // When in check, search starts here
value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);
// Post LMR continuation history updates (~1 Elo)
int bonus = value >= beta ? stat_bonus(newDepth) : -stat_malus(newDepth);
int bonus = 2 * (value >= beta) * stat_bonus(newDepth);
update_continuation_histories(ss, movedPiece, move.to_sq(), bonus);
}
}
@@ -1305,6 +1313,10 @@ moves_loop: // When in check, search starts here
rm.averageScore =
rm.averageScore != -VALUE_INFINITE ? (value + rm.averageScore) / 2 : value;
rm.meanSquaredScore = rm.meanSquaredScore != -VALUE_INFINITE * VALUE_INFINITE
? (value * std::abs(value) + rm.meanSquaredScore) / 2
: value * std::abs(value);
// PV move or new best move?
if (moveCount == 1 || value > alpha)
{
@@ -1407,8 +1419,7 @@ moves_loop: // When in check, search starts here
// If there is a move that produces search value greater than alpha,
// we update the stats of searched moves.
else if (bestMove)
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth,
rootNode);
update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth);
// Bonus for prior countermove that caused the fail low
else if (!priorCapture && prevSq != SQ_NONE)
@@ -1460,17 +1471,21 @@ moves_loop: // When in check, search starts here
&& !(bestValue >= beta && bestValue <= ss->staticEval)
&& !(!bestMove && bestValue >= ss->staticEval))
{
const auto m = (ss - 1)->currentMove;
auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8,
-CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4);
thisThread->pawnCorrectionHistory[us][pawn_structure_index<Correction>(pos)]
<< bonus * 101 / 128;
thisThread->materialCorrectionHistory[us][material_index(pos)] << bonus * 99 / 128;
thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 157 / 128;
thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 153 / 128;
thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index<WHITE>(pos)]
<< bonus * 123 / 128;
thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index<BLACK>(pos)]
<< bonus * 165 / 128;
if (m.is_ok())
(*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus;
}
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
@@ -1566,7 +1581,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
unadjustedStaticEval =
evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us]);
ss->staticEval = bestValue =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
// ttValue can be used as a better position evaluation (~13 Elo)
if (std::abs(ttData.value) < VALUE_TB_WIN_IN_MAX_PLY
@@ -1581,14 +1596,14 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
? evaluate(networks[numaAccessToken], pos, refreshTable, thisThread->optimism[us])
: -(ss - 1)->staticEval;
ss->staticEval = bestValue =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos, ss);
}
// Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta)
{
if (std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY)
bestValue = (3 * bestValue + beta) / 4;
bestValue = (bestValue + beta) / 2;
if (!ss->ttHit)
Distributed::save(tt, threads, thisThread, ttWriter, posKey,
value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
@@ -1612,9 +1627,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
// Initialize a MovePicker object for the current position, and prepare to search
// the moves. We presently use two stages of move generator in quiescence search:
// captures, or evasions only when in check.
MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->rootHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory,
nodeType == Root);
MovePicker mp(pos, ttData.move, DEPTH_QS, &thisThread->mainHistory, &thisThread->lowPlyHistory,
&thisThread->captureHistory, contHist, &thisThread->pawnHistory, ss->ply);
// Step 5. Loop through all pseudo-legal moves until no moves remain or a beta
// cutoff occurs.
@@ -1650,11 +1664,11 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
continue;
}
// if static exchange evaluation is low enough
// If static exchange evaluation is low enough
// we can prune this move. (~2 Elo)
if (!pos.see_ge(move, alpha - futilityBase))
{
bestValue = (futilityBase > alpha) ? alpha : std::max(bestValue, futilityBase);
bestValue = std::min(alpha, futilityBase);
continue;
}
}
@@ -1681,6 +1695,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta)
ss->continuationHistory =
&thisThread
->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][move.to_sq()];
ss->continuationCorrectionHistory =
&thisThread->continuationCorrectionHistory[pos.moved_piece(move)][move.to_sq()];
// Step 7. Make and search the move
thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
@@ -1823,8 +1839,7 @@ void update_all_stats(const Position& pos,
Square prevSq,
ValueList<Move, 32>& quietsSearched,
ValueList<Move, 32>& capturesSearched,
Depth depth,
bool rootNode) {
Depth depth) {
CapturePieceToHistory& captureHistory = workerThread.captureHistory;
Piece moved_piece = pos.moved_piece(bestMove);
@@ -1835,11 +1850,11 @@ void update_all_stats(const Position& pos,
if (!pos.capture_stage(bestMove))
{
update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus, rootNode);
update_quiet_histories(pos, ss, workerThread, bestMove, quietMoveBonus);
// Decrease stats for all non-best quiet moves
for (Move move : quietsSearched)
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus, rootNode);
update_quiet_histories(pos, ss, workerThread, move, -quietMoveMalus);
}
else
{
@@ -1881,17 +1896,13 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
// Updates move sorting heuristics
void update_quiet_histories(const Position& pos,
Stack* ss,
Search::Worker& workerThread,
Move move,
int bonus,
bool rootNode) {
void update_quiet_histories(
const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) {
Color us = pos.side_to_move();
workerThread.mainHistory[us][move.from_to()] << bonus;
if (rootNode)
workerThread.rootHistory[us][move.from_to()] << bonus;
if (ss->ply < LOW_PLY_HISTORY_SIZE)
workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus;
update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus);
+29 -27
View File
@@ -63,18 +63,19 @@ namespace Search {
// shallower and deeper in the tree during the search. Each search thread has
// its own array of Stack objects, indexed by the current ply.
struct Stack {
Move* pv;
PieceToHistory* continuationHistory;
int ply;
Move currentMove;
Move excludedMove;
Value staticEval;
int statScore;
int moveCount;
bool inCheck;
bool ttPv;
bool ttHit;
int cutoffCnt;
Move* pv;
PieceToHistory* continuationHistory;
PieceToCorrectionHistory* continuationCorrectionHistory;
int ply;
Move currentMove;
Move excludedMove;
Value staticEval;
int statScore;
int moveCount;
bool inCheck;
bool ttPv;
bool ttHit;
int cutoffCnt;
};
@@ -92,15 +93,16 @@ struct RootMove {
return m.score != score ? m.score < score : m.previousScore < previousScore;
}
uint64_t effort = 0;
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
Value uciScore = -VALUE_INFINITE;
bool scoreLowerbound = false;
bool scoreUpperbound = false;
int selDepth = 0;
int tbRank = 0;
uint64_t effort = 0;
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
Value uciScore = -VALUE_INFINITE;
bool scoreLowerbound = false;
bool scoreUpperbound = false;
int selDepth = 0;
int tbRank = 0;
Value tbScore;
std::vector<Move> pv;
};
@@ -290,17 +292,17 @@ class Worker {
// Public because they need to be updatable by the stats
ButterflyHistory mainHistory;
ButterflyHistory rootHistory;
LowPlyHistory lowPlyHistory;
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
PawnHistory pawnHistory;
PawnCorrectionHistory pawnCorrectionHistory;
MaterialCorrectionHistory materialCorrectionHistory;
MajorPieceCorrectionHistory majorPieceCorrectionHistory;
MinorPieceCorrectionHistory minorPieceCorrectionHistory;
NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB];
PawnCorrectionHistory pawnCorrectionHistory;
MajorPieceCorrectionHistory majorPieceCorrectionHistory;
MinorPieceCorrectionHistory minorPieceCorrectionHistory;
NonPawnCorrectionHistory nonPawnCorrectionHistory[COLOR_NB];
ContinuationCorrectionHistory continuationCorrectionHistory;
#ifdef USE_MPI
struct {
+4
View File
@@ -437,6 +437,8 @@ void UCIEngine::benchmark(std::istream& args) {
if (threadBinding.empty())
threadBinding = "none";
// clang-format off
std::cerr << "==========================="
<< "\nVersion : "
<< engine_version_info()
@@ -459,6 +461,8 @@ void UCIEngine::benchmark(std::istream& args) {
<< "\nTotal search time [s] : " << totalTime / 1000.0
<< "\nNodes/second : " << 1000 * nodes / totalTime << std::endl;
// clang-format on
init_search_update_listeners();
}