diff --git a/AUTHORS b/AUTHORS index db552a3a5..fac1e7d8d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -131,6 +131,7 @@ Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery Justin Blanchard (UncombedCoconut) +Kazuki Yamashita (KazApps) Kelly Wilson Ken Takusagawa Kenneth Lee (kennethlee33) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 38cafaec7..136b4031e 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -483,7 +483,6 @@ BenchmarkSetup setup_benchmark(std::istream& is) { float totalTime = 0; for (const auto& game : BenchmarkPositions) { - setup.commands.emplace_back("ucinewgame"); int ply = 1; for (int i = 0; i < static_cast(game.size()); ++i) { diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 23bc70d0e..d20843e85 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,29 +59,29 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) - : networks.big.evaluate(pos, accumulators, &caches.big); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small) + : networks.big.evaluate(pos, accumulators, caches.big); Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (std::abs(nnue) < 236)) + if (smallNet && (std::abs(nnue) < 277)) { - std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); nnue = (125 * psqt + 131 * positional) / 128; smallNet = false; } // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); - optimism += optimism * nnueComplexity / 468; - nnue -= nnue * nnueComplexity / 18000; + optimism += optimism * nnueComplexity / 476; + nnue -= nnue * nnueComplexity / 18236; - int material = 535 * pos.count() + pos.non_pawn_material(); - int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; + int material = 534 * pos.count() + pos.non_pawn_material(); + int v = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871; // Damp down the evaluation linearly when shuffling - v -= v * pos.rule50_count() / 212; + v -= v * pos.rule50_count() / 199; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); @@ -107,7 +107,7 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches->big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches->big); Value v = psqt + positional; v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; diff --git a/src/evaluate.h b/src/evaluate.h index c8dc64ace..853aeb5b2 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -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-49c1193b131c.nnue" +#define EvalFileDefaultNameBig "nn-2962dca31855.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { diff --git a/src/history.h b/src/history.h index 940e98991..a605ae417 100644 --- a/src/history.h +++ b/src/history.h @@ -33,32 +33,28 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 +constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits::max() + 1; constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, +static_assert((UINT_16_HISTORY_SIZE & (UINT_16_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); inline int pawn_history_index(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } -inline int pawn_correction_history_index(const Position& pos) { - return pos.pawn_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t pawn_correction_history_index(const Position& pos) { return pos.pawn_key(); } -inline int minor_piece_index(const Position& pos) { - return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t minor_piece_index(const Position& pos) { return pos.minor_piece_key(); } template -inline int non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); +inline uint16_t non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c); } // StatsEntry is the container of various numerical statistics. We use a class @@ -102,12 +98,11 @@ using Stats = MultiArray, Sizes...>; // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, // see https://www.chessprogramming.org/Butterfly_Boards -using ButterflyHistory = Stats; +using ButterflyHistory = Stats; -// LowPlyHistory is adressed by play and move's from and to squares, used +// LowPlyHistory is addressed by play and move's from and to squares, used // to improve move ordering near the root -using LowPlyHistory = - Stats; +using LowPlyHistory = Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; @@ -139,7 +134,7 @@ namespace Detail { template struct CorrHistTypedef { - using type = Stats; + using type = Stats; }; template<> @@ -155,7 +150,7 @@ struct CorrHistTypedef { template<> struct CorrHistTypedef { using type = - Stats; + Stats; }; } diff --git a/src/memory.h b/src/memory.h index b9be6f170..dad07df1d 100644 --- a/src/memory.h +++ b/src/memory.h @@ -41,6 +41,13 @@ #endif #include + // Some Windows headers (RPC/old headers) define short macros such + // as 'small' expanding to 'char', which breaks identifiers in the code. + // Undefine those macros immediately after including . + #ifdef small + #undef small + #endif + #include extern "C" { diff --git a/src/misc.cpp b/src/misc.cpp index 3bdde000f..d21497280 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -259,12 +259,12 @@ std::string compiler_info() { #if defined(USE_SSE2) compiler += " SSE2"; #endif - compiler += (HasPopCnt ? " POPCNT" : ""); #if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; #elif defined(USE_NEON) compiler += " NEON"; #endif + compiler += (HasPopCnt ? " POPCNT" : ""); #if !defined(NDEBUG) compiler += " DEBUG"; diff --git a/src/misc.h b/src/misc.h index fce6f17df..db5f701e9 100644 --- a/src/misc.h +++ b/src/misc.h @@ -142,6 +142,13 @@ class ValueList { const T* end() const { return values_ + size_; } const T& operator[](int index) const { return values_[index]; } + T* make_space(size_t count) { + T* result = &values_[size_]; + size_ += count; + assert(size_ <= MaxSize); + return result; + } + private: T values_[MaxSize]; std::size_t size_ = 0; @@ -412,6 +419,15 @@ void move_to_front(std::vector& vec, Predicate pred) { } } +#if defined(__GNUC__) + #define sf_always_inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define sf_always_inline __forceinline +#else + // do nothign for other compilers + #define sf_always_inline +#endif + #if defined(__GNUC__) && !defined(__clang__) #if __GNUC__ >= 13 #define sf_assume(cond) __attribute__((assume(cond))) diff --git a/src/movepick.cpp b/src/movepick.cpp index 2eec3556b..7de11fa1f 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -153,12 +153,12 @@ ExtMove* MovePicker::score(MoveList& ml) { if constexpr (Type == CAPTURES) m.value = (*captureHistory)[pc][to][type_of(capturedPiece)] - + 7 * int(PieceValue[capturedPiece]) + 1024 * bool(pos.check_squares(pt) & to); + + 7 * int(PieceValue[capturedPiece]); else if constexpr (Type == QUIETS) { // histories - m.value = 2 * (*mainHistory)[us][m.from_to()]; + m.value = 2 * (*mainHistory)[us][m.raw()]; m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -171,13 +171,12 @@ ExtMove* MovePicker::score(MoveList& ml) { // penalty for moving to a square threatened by a lesser piece // or bonus for escaping an attack by a lesser piece. - static constexpr int bonus[KING + 1] = {0, 0, 144, 144, 256, 517, 10000}; - int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); - m.value += bonus[pt] * v; + int v = threatByLesser[pt] & to ? -19 : 20 * bool(threatByLesser[pt] & from); + m.value += PieceValue[pt] * v; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); + m.value += 8 * (*lowPlyHistory)[ply][m.raw()] / (1 + ply); } else // Type == EVASIONS @@ -186,9 +185,9 @@ ExtMove* MovePicker::score(MoveList& ml) { m.value = PieceValue[capturedPiece] + (1 << 28); else { - m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; + m.value = (*mainHistory)[us][m.raw()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += (*lowPlyHistory)[ply][m.from_to()]; + m.value += (*lowPlyHistory)[ply][m.raw()]; } } } diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 6fc6b00ec..645bb7090 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -32,7 +32,12 @@ namespace Stockfish::Eval::NNUE::Features { // Lookup array for indexing threats -IndexType offsets[PIECE_NB][SQUARE_NB + 2]; +IndexType offsets[PIECE_NB][SQUARE_NB]; + +struct HelperOffsets { + int cumulativePieceOffset, cumulativeOffset; +}; +std::array helper_offsets; // Information on a particular pair of pieces and whether they should be excluded struct PiecePairData { @@ -69,9 +74,9 @@ static void init_index_luts() { int map = FullThreats::map[attackerType - 1][attackedType - 1]; bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN); - IndexType feature = offsets[attacker][65] + IndexType feature = helper_offsets[attacker].cumulativeOffset + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) - * offsets[attacker][64]; + * helper_offsets[attacker].cumulativePieceOffset; bool excluded = map < 0; index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); @@ -116,8 +121,7 @@ void init_threat_offsets() { } } - offsets[pieceIdx][64] = cumulativePieceOffset; - offsets[pieceIdx][65] = cumulativeOffset; + helper_offsets[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; } @@ -126,50 +130,40 @@ void init_threat_offsets() { } // Index of a feature for a given king position and another piece on some square -template -IndexType -FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - from = (Square) (int(from) ^ OrientTBL[Perspective][ksq]); - to = (Square) (int(to) ^ OrientTBL[Perspective][ksq]); +inline sf_always_inline IndexType FullThreats::make_index( + Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { + const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective); + unsigned from_oriented = uint8_t(from) ^ orientation; + unsigned to_oriented = uint8_t(to) ^ orientation; - if (Perspective == BLACK) - { - attacker = ~attacker; - attacked = ~attacked; - } + std::int8_t swap = 8 * perspective; + unsigned attacker_oriented = attacker ^ swap; + unsigned attacked_oriented = attacked ^ swap; - auto piecePairData = index_lut1[attacker][attacked]; + const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented]; - // Some threats imply the existence of the corresponding ones in the opposite - // direction. We filter them here to ensure only one such threat is active. - - // In the below addition, the 2nd lsb gets set iff either the pair is always excluded, - // or the pair is semi-excluded and from < to. By using an unsigned compare, the following - // sequence can use an add-with-carry instruction. - bool less_than = static_cast(from) < static_cast(to); + const bool less_than = from_oriented < to_oriented; if ((piecePairData.excluded_pair_info() + less_than) & 2) - return Dimensions; + return FullThreats::Dimensions; - IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != Dimensions); + const IndexType index = piecePairData.feature_index_base() + + offsets[attacker_oriented][from_oriented] + + index_lut2[attacker_oriented][from_oriented][to_oriented]; + sf_assume(index < Dimensions); return index; } // Get a list of indices for active features in ascending order -template -void FullThreats::append_active_indices(const Position& pos, IndexList& active) { - static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(Perspective); +void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) { + Square ksq = pos.square(perspective); Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { for (PieceType pt = PAWN; pt <= KING; ++pt) { - Color c = order[Perspective][color]; + Color c = Color(perspective ^ color); Piece attacker = make_piece(c, pt); Bitboard bb = pos.pieces(c, pt); @@ -187,7 +181,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -198,7 +192,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -216,7 +210,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); IndexType index = - make_index(attacker, from, to, attacked, ksq); + make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -227,23 +221,17 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) } } -// Explicit template instantiations -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); - // Get a list of indices for recently changed features -template -void FullThreats::append_changed_indices(Square ksq, + +void FullThreats::append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, FusedUpdateData* fusedData, bool first) { - for (const auto dirty : diff.list) + + for (const auto& dirty : diff.list) { auto attacker = dirty.pc(); auto attacked = dirty.threatened_pc(); @@ -282,30 +270,16 @@ void FullThreats::append_changed_indices(Square ksq, } } - IndexType index = make_index(attacker, from, to, attacked, ksq); + auto& insert = add ? added : removed; + const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); - if (index != Dimensions) - (add ? added : removed).push_back(index); + if (index < Dimensions) + insert.push_back(index); } } -// Explicit template instantiations -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); - bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { - return perspective == diff.us - && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; + return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index 458b04dd1..177e9fabe 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -32,7 +32,6 @@ namespace Stockfish::Eval::NNUE::Features { static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0, 0, 6, 12, 10, 10, 12, 8, 0}; -extern IndexType offsets[PIECE_NB][SQUARE_NB + 2]; void init_threat_offsets(); class FullThreats { @@ -48,23 +47,15 @@ class FullThreats { // clang-format off // Orient a square according to perspective (rotates by 180 for black) - static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { - { SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + static constexpr std::int8_t OrientTBL[SQUARE_NB] = { + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, - SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1 }, - { SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8 } }; static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = { @@ -89,16 +80,15 @@ class FullThreats { using IndexList = ValueList; using DiffType = DirtyThreats; - template - static IndexType make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); + static IndexType + make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - template - static void append_changed_indices(Square ksq, + static void append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index f652ba0b8..56779ddce 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -28,58 +28,40 @@ namespace Stockfish::Eval::NNUE::Features { // Index of a feature for a given king position and another piece on some square -template -IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - const IndexType flip = 56 * Perspective; - return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[Perspective][pc] + +IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) { + const IndexType flip = 56 * perspective; + return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[perspective][pc] + KingBuckets[int(ksq) ^ flip]; } // Get a list of indices for active features -template -void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { - Square ksq = pos.square(Perspective); + +void HalfKAv2_hm::append_active_indices(Color perspective, const Position& pos, IndexList& active) { + Square ksq = pos.square(perspective); Bitboard bb = pos.pieces(); while (bb) { Square s = pop_lsb(bb); - active.push_back(make_index(s, pos.piece_on(s), ksq)); + active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); } } -// Explicit template instantiations -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); - // Get a list of indices for recently changed features -template -void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added) { - removed.push_back(make_index(diff.from, diff.pc, ksq)); + +void HalfKAv2_hm::append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) { + removed.push_back(make_index(perspective, diff.from, diff.pc, ksq)); if (diff.to != SQ_NONE) - added.push_back(make_index(diff.to, diff.pc, ksq)); + added.push_back(make_index(perspective, diff.to, diff.pc, ksq)); if (diff.remove_sq != SQ_NONE) - removed.push_back(make_index(diff.remove_sq, diff.remove_pc, ksq)); + removed.push_back(make_index(perspective, diff.remove_sq, diff.remove_pc, ksq)); if (diff.add_sq != SQ_NONE) - added.push_back(make_index(diff.add_sq, diff.add_pc, ksq)); + added.push_back(make_index(perspective, diff.add_sq, diff.add_pc, ksq)); } -// Explicit template instantiations -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); - bool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) { return diff.pc == make_piece(perspective, KING); } diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index e695b273a..c58a3246b 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -107,17 +107,16 @@ class HalfKAv2_hm { using DiffType = DirtyPiece; // Index of a feature for a given king position and another piece on some square - template - static IndexType make_index(Square s, Piece pc, Square ksq); + + static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - template - static void - append_changed_indices(Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); + static void append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index 9e2aa4290..815c81074 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -173,7 +173,7 @@ template NetworkOutput Network::evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; @@ -236,7 +236,7 @@ template NnueEvalTrace Network::trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; diff --git a/src/nnue/network.h b/src/nnue/network.h index c701ac6f5..ba8f469f7 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -76,13 +76,13 @@ class Network { NetworkOutput evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; private: void load_user_net(const std::string&, const std::string&); diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 0444b6e40..358a4b278 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -18,9 +18,9 @@ #include "nnue_accumulator.h" -#include #include #include +#include #include #include "../bitboard.h" @@ -39,39 +39,41 @@ using namespace SIMD; namespace { -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed); -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2); -template +template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed); -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState); } @@ -122,11 +124,13 @@ void AccumulatorStack::reset() noexcept { size = 1; } -void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept { +std::pair AccumulatorStack::push() noexcept { assert(size < MaxSize); - psq_accumulators[size].reset(dirtyBoardData.dp); - threat_accumulators[size].reset(dirtyBoardData.dts); + auto& dp = psq_accumulators[size].reset(); + auto& dts = threat_accumulators[size].reset(); + new (&dts) DirtyThreats; size++; + return {dp, dts}; } void AccumulatorStack::pop() noexcept { @@ -140,71 +144,73 @@ void AccumulatorStack::evaluate(const Position& pos, AccumulatorCaches::Cache& cache) noexcept { constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); } -template -void AccumulatorStack::evaluate_side(const Position& pos, +template +void AccumulatorStack::evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept { const auto last_usable_accum = - find_last_usable_accumulator(); + find_last_usable_accumulator(perspective); if ((accumulators()[last_usable_accum].template acc()) - .computed[Perspective]) - forward_update_incremental(pos, featureTransformer, - last_usable_accum); + .computed[perspective]) + forward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); else { if constexpr (std::is_same_v) - update_accumulator_refresh_cache(featureTransformer, pos, - mut_latest(), cache); + update_accumulator_refresh_cache(perspective, featureTransformer, pos, + mut_latest(), cache); else - update_threats_accumulator_full(featureTransformer, pos, - mut_latest()); + update_threats_accumulator_full(perspective, featureTransformer, pos, + mut_latest()); - backward_update_incremental(pos, featureTransformer, - last_usable_accum); + backward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); } } // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator // state just before a change that requires full refresh. -template -std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { +template +std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) const noexcept { for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) { - if ((accumulators()[curr_idx].template acc()).computed[Perspective]) + if ((accumulators()[curr_idx].template acc()).computed[perspective]) return curr_idx; - if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, Perspective)) + if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, perspective)) return curr_idx; } return 0; } -template +template void AccumulatorStack::forward_update_incremental( + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { assert(begin < accumulators().size()); - assert((accumulators()[begin].template acc()).computed[Perspective]); + assert((accumulators()[begin].template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::size_t next = begin + 1; next < size; next++) { @@ -220,9 +226,8 @@ void AccumulatorStack::forward_update_incremental( if (dp2.remove_sq != SQ_NONE && (accumulators[next].diff.threateningSqs & square_bb(dp2.remove_sq))) { - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1], - dp2); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1], dp2); next++; continue; } @@ -234,8 +239,8 @@ void AccumulatorStack::forward_update_incremental( { const Square captureSq = dp1.to; dp1.to = dp2.remove_sq = SQ_NONE; - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1]); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); dp1.to = dp2.remove_sq = captureSq; next++; continue; @@ -243,32 +248,34 @@ void AccumulatorStack::forward_update_incremental( } } - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next - 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next - 1]); } - assert((latest().acc()).computed[Perspective]); + assert((latest().acc()).computed[perspective]); } -template +template void AccumulatorStack::backward_update_incremental( + Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept { assert(end < accumulators().size()); assert(end < size); - assert((latest().template acc()).computed[Perspective]); + assert((latest().template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--) - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next + 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next + 1]); - assert((accumulators()[end].template acc()).computed[Perspective]); + assert((accumulators()[end].template acc()).computed[perspective]); } // Explicit template instantiations @@ -301,15 +308,18 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const.. vecIn[i], reinterpret_cast(rows)[i]...); } -template +template struct AccumulatorUpdateContext { + Color perspective; const FeatureTransformer& featureTransformer; const AccumulatorState& from; AccumulatorState& to; - AccumulatorUpdateContext(const FeatureTransformer& ft, + AccumulatorUpdateContext(Color persp, + const FeatureTransformer& ft, const AccumulatorState& accF, AccumulatorState& accT) noexcept : + perspective{persp}, featureTransformer{ft}, from{accF}, to{accT} {} @@ -327,21 +337,23 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.template acc()).accumulation[Perspective], - (to.template acc()).accumulation[Perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[perspective].data(), + (to.template acc()).accumulation[perspective].data(), + to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[Perspective], - (to.template acc()).psqtAccumulation[Perspective], + (from.template acc()).psqtAccumulation[perspective].data(), + (to.template acc()).psqtAccumulation[perspective].data(), to_psqt_weight_vector(indices)...); } - void apply(typename FeatureSet::IndexList added, typename FeatureSet::IndexList removed) { - const auto fromAcc = from.template acc().accumulation[Perspective]; - const auto toAcc = to.template acc().accumulation[Perspective]; + void apply(const typename FeatureSet::IndexList& added, + const typename FeatureSet::IndexList& removed) { + const auto& fromAcc = from.template acc().accumulation[perspective]; + auto& toAcc = to.template acc().accumulation[perspective]; - const auto fromPsqtAcc = from.template acc().psqtAccumulation[Perspective]; - const auto toPsqtAcc = to.template acc().psqtAccumulation[Perspective]; + const auto& fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; + auto& toPsqtAcc = to.template acc().psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; @@ -436,8 +448,8 @@ struct AccumulatorUpdateContext { #else - std::copy_n(fromAcc, Dimensions, toAcc); - std::copy_n(fromPsqtAcc, PSQTBuckets, toPsqtAcc); + toAcc = fromAcc; + toPsqtAcc = fromPsqtAcc; for (const auto index : removed) { @@ -465,31 +477,33 @@ struct AccumulatorUpdateContext { } }; -template -auto make_accumulator_update_context(const FeatureTransformer& featureTransformer, +template +auto make_accumulator_update_context(Color perspective, + const FeatureTransformer& featureTransformer, const AccumulatorState& accumulatorFrom, AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - featureTransformer, accumulatorFrom, accumulatorTo}; + return AccumulatorUpdateContext{perspective, featureTransformer, + accumulatorFrom, accumulatorTo}; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed) { - assert(computed.acc().computed[Perspective]); - assert(!middle_state.acc().computed[Perspective]); - assert(!target_state.acc().computed[Perspective]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); PSQFeatureSet::IndexList removed, added; - PSQFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added); // you can't capture a piece that was just involved in castling since the rook ends up // in a square that the king passed assert(added.size() < 2); - PSQFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); assert(added.size() == 1); assert(removed.size() == 2 || removed.size() == 3); @@ -501,7 +515,7 @@ void double_inc_update(const FeatureTransformer& f sf_assume(removed.size() == 2 || removed.size() == 3); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if (removed.size() == 2) { @@ -513,51 +527,50 @@ void double_inc_update(const FeatureTransformer& f removed[2]); } - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2) { - assert(computed.acc().computed[Perspective]); - assert(!middle_state.acc().computed[Perspective]); - assert(!target_state.acc().computed[Perspective]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); ThreatFeatureSet::FusedUpdateData fusedData; fusedData.dp2removed = dp2.remove_sq; ThreatFeatureSet::IndexList removed, added; - ThreatFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added, - &fusedData, true); - ThreatFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added, - &fusedData, false); + ThreatFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added, + &fusedData, true); + ThreatFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added, + &fusedData, false); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); updateContext.apply(added, removed); - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template +template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed) { - assert((computed.template acc()).computed[Perspective]); - assert(!(target_state.template acc()).computed[Perspective]); + assert((computed.template acc()).computed[perspective]); + assert(!(target_state.template acc()).computed[perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -567,14 +580,12 @@ void update_accumulator_incremental( // is 2, since we are incrementally updating one move at a time. typename FeatureSet::IndexList removed, added; if constexpr (Forward) - FeatureSet::template append_changed_indices(ksq, target_state.diff, removed, - added); + FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); else - FeatureSet::template append_changed_indices(ksq, computed.diff, added, - removed); + FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if constexpr (std::is_same_v) updateContext.apply(added, removed); @@ -614,64 +625,67 @@ void update_accumulator_incremental( } } - (target_state.template acc()).computed[Perspective] = true; + (target_state.template acc()).computed[perspective] = true; } -Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) { +Bitboard get_changed_pieces(const std::array& oldPieces, + const std::array& newPieces) { #if defined(USE_AVX512) || defined(USE_AVX2) static_assert(sizeof(Piece) == 1); - Bitboard same_bb = 0; + Bitboard sameBB = 0; + for (int i = 0; i < 64; i += 32) { - const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(old + i)); - const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(new_ + i)); - const __m256i cmp_equal = _mm256_cmpeq_epi8(old_v, new_v); - const std::uint32_t equal_mask = _mm256_movemask_epi8(cmp_equal); - same_bb |= static_cast(equal_mask) << i; + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(&oldPieces[i])); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(&newPieces[i])); + const __m256i cmpEqual = _mm256_cmpeq_epi8(old_v, new_v); + const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual); + sameBB |= static_cast(equalMask) << i; } - return ~same_bb; + return ~sameBB; #else Bitboard changed = 0; + for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq) - { - changed |= static_cast(old[sq] != new_[sq]) << sq; - } + changed |= static_cast(oldPieces[sq] != newPieces[sq]) << sq; + return changed; #endif } -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache) { using Tiling [[maybe_unused]] = SIMDTiling; - const Square ksq = pos.square(Perspective); - auto& entry = cache[ksq][Perspective]; + const Square ksq = pos.square(perspective); + auto& entry = cache[ksq][perspective]; PSQFeatureSet::IndexList removed, added; - const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array().data()); - Bitboard removed_bb = changed_bb & entry.pieceBB; - Bitboard added_bb = changed_bb & pos.pieces(); + const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array()); + Bitboard removedBB = changedBB & entry.pieceBB; + Bitboard addedBB = changedBB & pos.pieces(); - while (removed_bb) + while (removedBB) { - Square sq = pop_lsb(removed_bb); - removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); + Square sq = pop_lsb(removedBB); + removed.push_back(PSQFeatureSet::make_index(perspective, sq, entry.pieces[sq], ksq)); } - while (added_bb) + while (addedBB) { - Square sq = pop_lsb(added_bb); - added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); + Square sq = pop_lsb(addedBB); + added.push_back(PSQFeatureSet::make_index(perspective, sq, pos.piece_on(sq), ksq)); } entry.pieceBB = pos.pieces(); - std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); + entry.pieces = pos.piece_array(); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -680,7 +694,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) @@ -727,7 +741,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); auto* entryTilePsqt = reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); @@ -784,26 +798,23 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation.data(), - sizeof(BiasType) * Dimensions); - - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation.data(), - sizeof(int32_t) * PSQTBuckets); + accumulator.accumulation[perspective] = entry.accumulation; + accumulator.psqtAccumulation[perspective] = entry.psqtAccumulation; #endif } -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState) { using Tiling [[maybe_unused]] = SIMDTiling; ThreatFeatureSet::IndexList active; - ThreatFeatureSet::append_active_indices(pos, active); + ThreatFeatureSet::append_active_indices(perspective, pos, active); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -812,7 +823,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_zero(); @@ -845,7 +856,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); @@ -868,21 +879,21 @@ void update_threats_accumulator_full(const FeatureTransformer& featu #else for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] = 0; + accumulator.accumulation[perspective][j] = 0; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; + accumulator.psqtAccumulation[perspective][k] = 0; for (const auto index : active) { const IndexType offset = Dimensions * index; for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] += + accumulator.accumulation[perspective][j] += featureTransformer.threatWeights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += + accumulator.psqtAccumulation[perspective][k] += featureTransformer.threatPsqtWeights[index * PSQTBuckets + k]; } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 4c907c7e3..69ab49632 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "../types.h" #include "nnue_architecture.h" @@ -45,9 +46,9 @@ class FeatureTransformer; // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[COLOR_NB][Size]; - std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - std::array computed = {}; + std::array, COLOR_NB> accumulation; + std::array, COLOR_NB> psqtAccumulation; + std::array computed = {}; }; @@ -70,16 +71,15 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Entry { std::array accumulation; std::array psqtAccumulation; - Piece pieces[SQUARE_NB]; + std::array pieces; Bitboard pieceBB; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top void clear(const std::array& biases) { - accumulation = biases; - std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, - sizeof(Entry) - offsetof(Entry, psqtAccumulation)); + std::memset(reinterpret_cast(this) + offsetof(Entry, psqtAccumulation), + 0, sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; @@ -141,6 +141,12 @@ struct AccumulatorState { accumulatorBig.computed.fill(false); accumulatorSmall.computed.fill(false); } + + typename FeatureSet::DiffType& reset() noexcept { + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); + return diff; + } }; class AccumulatorStack { @@ -150,9 +156,10 @@ class AccumulatorStack { template [[nodiscard]] const AccumulatorState& latest() const noexcept; - void reset() noexcept; - void push(const DirtyBoardData& dirtyBoardData) noexcept; - void pop() noexcept; + void reset() noexcept; + void push(const DirtyBoardData& dirtyBoardData) noexcept; + std::pair push() noexcept; + void pop() noexcept; template void evaluate(const Position& pos, @@ -169,21 +176,24 @@ class AccumulatorStack { template [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; - template - void evaluate_side(const Position& pos, + template + void evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; - template - [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + template + [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept; - template - void forward_update_incremental(const Position& pos, + template + void forward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept; - template - void backward_update_incremental(const Position& pos, + template + void backward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 5ad2d3371..1f328fff4 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -264,12 +264,12 @@ class FeatureTransformer { // Convert input features std::int32_t transform(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache, + AccumulatorCaches::Cache& cache, OutputType* output, int bucket) const { using namespace SIMD; - accumulatorStack.evaluate(pos, *this, *cache); + accumulatorStack.evaluate(pos, *this, cache); const auto& accumulatorState = accumulatorStack.latest(); const auto& threatAccumulatorState = accumulatorStack.latest(); diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 957e3453f..220140e5e 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -124,7 +124,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -140,7 +140,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat pos.remove_piece(sq); accumulators->reset(); - std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; @@ -157,7 +157,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << '\n'; accumulators->reset(); - auto t = networks.big.trace_evaluate(pos, *accumulators, &caches.big); + auto t = networks.big.trace_evaluate(pos, *accumulators, caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl diff --git a/src/numa.h b/src/numa.h index 261b6005d..76d265af2 100644 --- a/src/numa.h +++ b/src/numa.h @@ -1346,7 +1346,7 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase { std::size_t get_discriminator(NumaIndex idx) const { const NumaConfig& cfg = get_numa_config(); const NumaConfig& cfg_sys = NumaConfig::from_system(false); - // as a descriminator, locate the hardware/system numadomain this cpuindex belongs to + // as a discriminator, locate the hardware/system numadomain this cpuindex belongs to CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx); diff --git a/src/position.cpp b/src/position.cpp index a515876b6..049c72682 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -201,7 +201,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { Square sq = SQ_A8; std::istringstream ss(fenStr); - std::memset(this, 0, sizeof(Position)); + std::memset(reinterpret_cast(this), 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); st = si; @@ -336,8 +336,8 @@ void Position::set_check_info() const { // The function is only used when a new position is set up void Position::set_state() const { - st->key = st->materialKey = 0; - st->minorPieceKey = 0; + st->key = 0; + st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -375,10 +375,15 @@ void Position::set_state() const { st->key ^= Zobrist::side; st->key ^= Zobrist::castling[st->castlingRights]; + st->materialKey = compute_material_key(); +} +Key Position::compute_material_key() const { + Key k = 0; for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][8 + cnt]; + k ^= Zobrist::psq[pc][8 + cnt]; + return k; } @@ -688,10 +693,12 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position // will be prefetched -DirtyBoardData Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +void Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -720,12 +727,10 @@ DirtyBoardData Position::do_move(Move m, bool checkEP = false; - DirtyPiece dp; - dp.pc = pc; - dp.from = from; - dp.to = to; - dp.add_sq = SQ_NONE; - DirtyThreats dts; + dp.pc = pc; + dp.from = from; + dp.to = to; + dp.add_sq = SQ_NONE; dts.us = us; dts.prevKsq = square(us); dts.threatenedSqs = dts.threateningSqs = 0; @@ -970,8 +975,6 @@ DirtyBoardData Position::do_move(Move m, assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE)); assert(dp.from != SQ_NONE); assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING)); - - return {dp, dts}; } @@ -1049,14 +1052,67 @@ inline void add_dirty_threat( dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); } -template -void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { - // Add newly threatened pieces - Bitboard occupied = pieces(); +#ifdef USE_AVX512ICL +// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats +// present at the given bitboard. +template +void write_multiple_dirties(const Position& p, + Bitboard mask, + DirtyThreat dt_template, + DirtyThreats* dts) { + static_assert(sizeof(DirtyThreat) == 4); - Bitboard rAttacks = attacks_bb(s, occupied); - Bitboard bAttacks = attacks_bb(s, occupied); - Bitboard qAttacks = rAttacks | bAttacks; + const __m512i board = _mm512_loadu_si512(p.piece_array().data()); + const __m512i AllSquares = _mm512_set_epi8( + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, + 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + const int dt_count = popcount(mask); + assert(dt_count <= 16); + + const __m512i template_v = _mm512_set1_epi32(dt_template.raw()); + auto* write = dts->list.make_space(dt_count); + + // Extract the list of squares and upconvert to 32 bits. There are never more than 16 + // incoming threats so this is sufficient. + __m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares); + threat_squares = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares)); + + __m512i threat_pieces = + _mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board); + + // Shift the piece and square into place + threat_squares = _mm512_slli_epi32(threat_squares, SqShift); + threat_pieces = _mm512_slli_epi32(threat_pieces, PcShift); + + const __m512i dirties = + _mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */); + _mm512_storeu_si512(reinterpret_cast<__m512i*>(write), dirties); +} +#endif + +template +void Position::update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining) { + const Bitboard occupied = pieces(); + const Bitboard rookQueens = pieces(ROOK, QUEEN); + const Bitboard bishopQueens = pieces(BISHOP, QUEEN); + const Bitboard knights = pieces(KNIGHT); + const Bitboard kings = pieces(KING); + const Bitboard whitePawns = pieces(WHITE, PAWN); + const Bitboard blackPawns = pieces(BLACK, PAWN); + + const Bitboard rAttacks = attacks_bb(s, occupied); + const Bitboard bAttacks = attacks_bb(s, occupied); + + Bitboard qAttacks = Bitboard(0); + if constexpr (ComputeRay) + qAttacks = rAttacks | bAttacks; + else if (type_of(pc) == QUEEN) + qAttacks = rAttacks | bAttacks; Bitboard threatened; @@ -1080,58 +1136,88 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) } threatened &= occupied; + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); - while (threatened) +#ifdef USE_AVX512ICL + if (threatened) { - Square threatened_sq = pop_lsb(threatened); - Piece threatened_pc = piece_on(threatened_sq); - - assert(threatened_sq != s); - assert(threatened_pc); - - add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); - } - - Bitboard sliders = (pieces(ROOK, QUEEN) & rAttacks) | (pieces(BISHOP, QUEEN) & bAttacks); - - Bitboard incoming_threats = (attacks_bb(s, occupied) & pieces(KNIGHT)) - | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) - | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) - | (attacks_bb(s, occupied) & pieces(KING)); - - while (sliders) - { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); - - Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; - threatened = ray & qAttacks & occupied; - - assert(!more_than_one(threatened)); - if (ComputeRay && threatened) + if constexpr (PutPiece) { - Square threatened_sq = lsb(threatened); - - Piece threatened_pc = piece_on(threatened_sq); - add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + dts->threatenedSqs |= threatened; + dts->threateningSqs |= square_bb(s); } - add_dirty_threat(dts, slider, pc, slider_sq, s); + DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece}; + write_multiple_dirties( + *this, threatened, dt_template, dts); } - // Add threats of sliders that were already threatening s, - // sliders are already handled in the loop above + Bitboard all_attackers = sliders | incoming_threats; + if (!all_attackers) + return; // Square s is threatened iff there's at least one attacker + dts->threatenedSqs |= square_bb(s); + dts->threateningSqs |= all_attackers; + + DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece}; + write_multiple_dirties(*this, all_attackers, + dt_template, dts); +#else + while (threatened) + { + Square threatenedSq = pop_lsb(threatened); + Piece threatenedPc = piece_on(threatenedSq); + + assert(threatenedSq != s); + assert(threatenedPc); + + add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); + } +#endif + + if constexpr (ComputeRay) + { + while (sliders) + { + Square sliderSq = pop_lsb(sliders); + Piece slider = piece_on(sliderSq); + + const Bitboard ray = RayPassBB[sliderSq][s] & ~BetweenBB[sliderSq][s]; + const Bitboard discovered = ray & qAttacks & occupied; + + assert(!more_than_one(discovered)); + if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining) + { + const Square threatenedSq = lsb(discovered); + const Piece threatenedPc = piece_on(threatenedSq); + add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); + } + +#ifndef USE_AVX512ICL // for ICL, direct threats were processed earlier (all_attackers) + add_dirty_threat(dts, slider, pc, sliderSq, s); +#endif + } + } + else + { + incoming_threats |= sliders; + } + +#ifndef USE_AVX512ICL while (incoming_threats) { - Square src_sq = pop_lsb(incoming_threats); - Piece src_pc = piece_on(src_sq); + Square srcSq = pop_lsb(incoming_threats); + Piece srcPc = piece_on(srcSq); - assert(src_sq != s); - assert(src_pc != NO_PIECE); + assert(srcSq != s); + assert(srcPc != NO_PIECE); - add_dirty_threat(dts, src_pc, pc, src_sq, s); + add_dirty_threat(dts, srcPc, pc, srcSq, s); } +#endif } // Helper used to do/undo a castling move. This is a bit @@ -1432,6 +1518,9 @@ void Position::flip() { } +bool Position::material_key_is_ok() const { return compute_material_key() == st->materialKey; } + + // Performs some consistency checks for the position object // and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. @@ -1481,6 +1570,8 @@ bool Position::pos_is_ok() const { assert(0 && "pos_is_ok: Castling"); } + assert(material_key_is_ok() && "pos_is_ok: materialKey"); + return true; } diff --git a/src/position.h b/src/position.h index 9afdb17f9..7a029ce18 100644 --- a/src/position.h +++ b/src/position.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "bitboard.h" @@ -133,11 +134,16 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - DirtyBoardData do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); - void undo_move(Move m); - void do_null_move(StateInfo& newSt, const TranspositionTable& tt); - void undo_null_move(); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + void do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -163,6 +169,7 @@ class Position { // Position consistency check, for debugging bool pos_is_ok() const; + bool material_key_is_ok() const; void flip(); StateInfo* state() const; @@ -174,12 +181,16 @@ class Position { private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); + Key compute_material_key() const; void set_state() const; void set_check_info() const; // Other helpers template - void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts); + void update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining = -1ULL); void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); template void do_castling(Color us, @@ -196,14 +207,16 @@ class Position { std::array byTypeBB; std::array byColorBB; - int pieceCount[PIECE_NB]; - int castlingRightsMask[SQUARE_NB]; - Square castlingRookSquare[CASTLING_RIGHT_NB]; - Bitboard castlingPath[CASTLING_RIGHT_NB]; - StateInfo* st; - int gamePly; - Color sideToMove; - bool chess960; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; + DirtyPiece scratch_dp; + DirtyThreats scratch_dts; }; std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -362,7 +375,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts Bitboard fromTo = from | to; if (dts) - update_piece_threats(pc, from, dts); + update_piece_threats(pc, from, dts, fromTo); byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; @@ -371,7 +384,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts board[to] = pc; if (dts) - update_piece_threats(pc, to, dts); + update_piece_threats(pc, to, dts, fromTo); } inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { @@ -389,7 +402,8 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { } inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { - do_move(m, newSt, gives_check(m), tt); + new (&scratch_dts) DirtyThreats; + do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt); } inline StateInfo* Position::state() const { return st; } diff --git a/src/search.cpp b/src/search.cpp index c17e30daa..5c2301009 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -89,7 +89,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 8; - return 9536 * pcv + 8494 * micv + 10132 * (wnpcv + bnpcv) + 7156 * cntcv; + return 10347 * pcv + 8821 * micv + 11168 * (wnpcv + bnpcv) + 7841 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -105,10 +105,10 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - constexpr int nonPawnWeight = 165; + constexpr int nonPawnWeight = 178; workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 156 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] @@ -118,8 +118,8 @@ void update_correction_history(const Position& pos, { const Square to = m.to_sq(); const Piece pc = pos.piece_on(m.to_sq()); - (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 137 / 128; - (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 64 / 128; + (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 127 / 128; + (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 59 / 128; } } @@ -142,6 +142,16 @@ void update_all_stats(const Position& pos, Move TTMove, int moveCount); +bool isShuffling(Move move, Stack* const ss, const Position& pos) { + if (type_of(pos.moved_piece(move)) == PAWN || pos.capture_stage(move) + || pos.rule50_count() < 10) + return false; + if (pos.state()->pliesFromNull <= 6 || ss->ply < 20) + return false; + return move.from_sq() == (ss - 2)->currentMove.to_sq() + && (ss - 2)->currentMove.from_sq() == (ss - 4)->currentMove.to_sq(); +} + } // namespace Search::Worker::Worker(SharedState& sharedState, @@ -380,7 +390,7 @@ void Search::Worker::iterative_deepening() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 137 * avg / (std::abs(avg) + 91); + optimism[us] = 142 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -515,20 +525,22 @@ void Search::Worker::iterative_deepening() { uint64_t nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); - double fallingEval = - (11.325 + 2.115 * (mainThread->bestPreviousAverageScore - bestValue) - + 0.987 * (mainThread->iterValue[iterIdx] - bestValue)) - / 100.0; - fallingEval = std::clamp(fallingEval, 0.5688, 1.5698); + double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.93 * (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; + fallingEval = std::clamp(fallingEval, 0.57, 1.70); // If the bestMove is stable over several iterations, reduce time accordingly - double k = 0.5189; - double center = lastBestMoveDepth + 11.57; - timeReduction = 0.723 + 0.79 / (1.104 + std::exp(-k * (completedDepth - center))); - double reduction = - (1.455 + mainThread->previousTimeReduction) / (2.2375 * timeReduction); - double bestMoveInstability = 1.04 + 1.8956 * totBestMoveChanges / threads.size(); - double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 92425 ? 0.666 : 1.0; + double k = 0.51; + double center = lastBestMoveDepth + 12.15; + + timeReduction = 0.66 + 0.85 / (0.98 + std::exp(-k * (completedDepth - center))); + + double reduction = (1.43 + mainThread->previousTimeReduction) / (2.28 * timeReduction); + + double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size(); + + double highBestMoveEffort = nodesEffort >= 93340 ? 0.76 : 1.0; double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * highBestMoveEffort; @@ -550,7 +562,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.503; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.50; } mainThread->iterValue[iterIdx] = bestValue; @@ -577,22 +589,28 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac void Search::Worker::do_move( Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { bool capture = pos.capture_stage(move); - nodes.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); - DirtyBoardData dirtyBoardData = pos.do_move(move, st, givesCheck, &tt); - accumulatorStack.push(dirtyBoardData); + auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); + pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); if (ss != nullptr) { ss->currentMove = move; ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][dirtyBoardData.dp.pc][move.to_sq()]; + &continuationHistory[ss->inCheck][capture][dirtyPiece.pc][move.to_sq()]; ss->continuationCorrectionHistory = - &continuationCorrectionHistory[dirtyBoardData.dp.pc][move.to_sq()]; + &continuationCorrectionHistory[dirtyPiece.pc][move.to_sq()]; } } -void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } +void Search::Worker::do_null_move(Position& pos, StateInfo& st, Stack* const ss) { + pos.do_null_move(st, tt); + ss->currentMove = Move::null(); + ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; + ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; +} void Search::Worker::undo_move(Position& pos, const Move move) { pos.undo_move(move); @@ -624,7 +642,7 @@ void Search::Worker::clear() { h.fill(-529); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2809 / 128.0 * std::log(i)); + reductions[i] = int(2747 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -744,11 +762,11 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(130 * depth - 71, 1043)); + std::min(132 * depth - 72, 985)); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2142); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060); } // Partial workaround for the graph history interaction problem @@ -766,6 +784,7 @@ Value Search::Worker::search( // Check that the ttValue after the tt move would also trigger a cutoff if (!is_valid(ttDataNext.value)) return ttData.value; + if ((ttData.value >= beta) == (-ttDataNext.value >= beta)) return ttData.value; } @@ -792,7 +811,8 @@ Value Search::Worker::search( if (err != TB::ProbeState::FAIL) { - tbHits.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); int drawScore = tbConfig.useRule50 ? 1 : 0; @@ -866,41 +886,45 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << evalDiff * 9; + int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -209, 167) + 59; + mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13; } // Set up the improving flag, which is true if current static evaluation is // bigger than the previous static evaluation at our turn (if we were in // check at our previous move we go back until we weren't in check) and is // false otherwise. The improving flag is used in various pruning heuristics. + // Similarly, opponentWorsening is true if our static evaluation is better + // for us than at the last ply. improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; + // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) + + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 514 - 294 * depth * depth) + if (!PvNode && eval < alpha - 485 - 281 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 91 - 21 * !ss->ttHit; + Value futilityMult = 76 - 23 * !ss->ttHit; - return futilityMult * d // - - 2094 * improving * futilityMult / 1024 // - - 1324 * opponentWorsening * futilityMult / 4096 // - + std::abs(correctionValue) / 158105; + return futilityMult * d // + - 2474 * improving * futilityMult / 1024 // + - 331 * opponentWorsening * futilityMult / 1024 // + + std::abs(correctionValue) / 174665; }; if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta @@ -909,19 +933,14 @@ Value Search::Worker::search( } // Step 9. Null move search with verification search - if (cutNode && ss->staticEval >= beta - 18 * depth + 390 && !excludedMove + if (cutNode && ss->staticEval >= beta - 18 * depth + 350 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 6 + depth / 3 + improving; - - ss->currentMove = Move::null(); - ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; - ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; - - do_null_move(pos, st); + Depth R = 7 + depth / 3; + do_null_move(pos, st, ss); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); @@ -952,14 +971,14 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. - // (*Scaler) Especially if they make IIR less aggressive. + // (*Scaler) Making IIR more aggressive scales poorly. if (!allNode && depth >= 6 && !ttData.move && priorReduction <= 3) depth--; // Step 11. ProbCut // 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 + 224 - 64 * improving; + probCutBeta = beta + 235 - 63 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -969,7 +988,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 306, 0, depth); + Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 315, 0, depth); while ((move = mp.next_move()) != Move::none()) { @@ -1067,12 +1086,11 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); // Increase reduction for ttPv nodes (*Scaler) - // Smaller or even negative value is better for short time controls - // Bigger value is better for long time controls + // Larger values scale well if (ss->ttPv) r += 946; - // Step 14. Pruning at shallow depth. + // Step 14. Pruning at shallow depths. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { @@ -1091,8 +1109,8 @@ moves_loop: // When in check, search starts here // Futility pruning for captures if (!givesCheck && lmrDepth < 7) { - Value futilityValue = ss->staticEval + 231 + 211 * lmrDepth - + PieceValue[capturedPiece] + 130 * captHist / 1024; + Value futilityValue = ss->staticEval + 232 + 217 * lmrDepth + + PieceValue[capturedPiece] + 131 * captHist / 1024; if (futilityValue <= alpha) continue; @@ -1100,7 +1118,7 @@ moves_loop: // When in check, search starts here // SEE based pruning for captures and checks // Avoid pruning sacrifices of our last piece for stalemate - int margin = std::max(157 * depth + captHist / 29, 0); + int margin = std::max(166 * depth + captHist / 29, 0); if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece]) && !pos.see_ge(move, -margin)) continue; @@ -1112,21 +1130,21 @@ moves_loop: // When in check, search starts here + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4312 * depth) + if (history < -4083 * depth) continue; - history += 76 * mainHistory[us][move.from_to()] / 32; + history += 69 * mainHistory[us][move.raw()] / 32; - // (*Scaler): Generally, a lower divisor scales well - lmrDepth += history / 3220; + // (*Scaler): Generally, lower divisors scales well + lmrDepth += history / 3208; - Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth - + 90 * (ss->staticEval > alpha); + Value futilityValue = ss->staticEval + 42 + 161 * !bestMove + 127 * lmrDepth + + 85 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning - // scales well with respect to time and threads - if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) + // scales well + if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) @@ -1137,7 +1155,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) continue; } } @@ -1152,12 +1170,11 @@ moves_loop: // When in check, search starts here // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. - if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3) + && ttData.depth >= depth - 3 && !isShuffling(move, ss, pos)) { - Value singularBeta = ttData.value - (56 + 81 * (ss->ttPv && !PvNode)) * depth / 60; + Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1166,11 +1183,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 229958; - int doubleMargin = -4 + 198 * PvNode - 212 * !ttCapture - corrValAdj - - 921 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 45; - int tripleMargin = 76 + 308 * PvNode - 250 * !ttCapture + 92 * ss->ttPv - corrValAdj - - (ss->ply * 2 > rootDepth * 3) * 52; + int corrValAdj = std::abs(correctionValue) / 230673; + int doubleMargin = -4 + 199 * PvNode - 201 * !ttCapture - corrValAdj + - 897 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 42; + int tripleMargin = 73 + 302 * PvNode - 248 * !ttCapture + 90 * ss->ttPv - corrValAdj + - (ss->ply * 2 > rootDepth * 3) * 50; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1216,41 +1233,39 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2618 + PvNode * 991 + (ttData.value > alpha) * 903 - + (ttData.depth >= depth) * (978 + cutNode * 1051); + r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922 + + (ttData.depth >= depth) * (934 + cutNode * 1011); - // These reduction adjustments have no proven non-linear scaling - - r += 843; // Base reduction offset to compensate for other tweaks - r -= moveCount * 66; - r -= std::abs(correctionValue) / 30450; + r += 714; // Base reduction offset to compensate for other tweaks + r -= moveCount * 73; + r -= std::abs(correctionValue) / 30370; // Increase reduction for cut nodes if (cutNode) - r += 3094 + 1056 * !ttData.move; + r += 3372 + 997 * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1415; + r += 1119; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1051 + allNode * 814; + r += 991 + allNode * 923; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2018; + r -= 2151; if (capture) - ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + ss->statScore = 868 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else - ss->statScore = 2 * mainHistory[us][move.from_to()] + ss->statScore = 2 * mainHistory[us][move.raw()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 794 / 8192; + r -= ss->statScore * 850 / 8192; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1267,8 +1282,7 @@ moves_loop: // When in check, search starts here ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high - // (*Scaler) Usually doing more shallower searches - // doesn't scale well to longer TCs + // (*Scaler) Shallower searches here don't scale well if (value > alpha) { // Adjust full-depth search based on LMR results - if the result was @@ -1291,11 +1305,11 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1118; + r += 1140; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3212) - (r > 4784 && newDepth > 2), !cutNode); + newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1396,7 +1410,7 @@ moves_loop: // When in check, search starts here if (value >= beta) { - // (*Scaler) Especially if they make cutoffCnt increment more often. + // (*Scaler) Infrequent and small updates scale well ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; @@ -1449,25 +1463,25 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -228; - bonusScale -= (ss - 1)->statScore / 104; - bonusScale += std::min(63 * depth, 508); + int bonusScale = -215; + bonusScale -= (ss - 1)->statScore / 100; + bonusScale += std::min(56 * depth, 489); bonusScale += 184 * ((ss - 1)->moveCount > 8); - bonusScale += 143 * (!ss->inCheck && bestValue <= ss->staticEval - 92); - bonusScale += 149 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 70); + bonusScale += 147 * (!ss->inCheck && bestValue <= ss->staticEval - 107); + bonusScale += 156 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 65); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(144 * depth - 92, 1365) * bonusScale; + const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 400 / 32768); + scaledBonus * 406 / 32768); - mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 220 / 32768; + mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1164 / 32768; + << scaledBonus * 1160 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1475,7 +1489,7 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 964; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012; } if (PvNode) @@ -1500,11 +1514,10 @@ moves_loop: // When in check, search starts here // Adjust correction history if the best move is not a capture // and the error direction matches whether we are above/below bounds. if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) - && (bestValue < ss->staticEval) == !bestMove) + && (bestValue > ss->staticEval) == bool(bestMove)) { - auto bonus = - std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), - -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (bestMove ? 10 : 8), + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); update_correction_history(pos, ss, *this, bonus); } @@ -1594,8 +1607,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(pos); + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); @@ -1607,8 +1622,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) else { unadjustedStaticEval = evaluate(pos); - - ss->staticEval = bestValue = + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); } @@ -1617,6 +1631,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { if (!is_decisive(bestValue)) bestValue = (bestValue + beta) / 2; + if (!ss->ttHit) Distributed::save(tt, threads, this, ttWriter, posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, @@ -1629,11 +1644,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 352; + futilityBase = ss->staticEval + 351; } - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, - (ss - 2)->continuationHistory}; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; @@ -1691,7 +1705,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -78)) + if (!pos.see_ge(move, -80)) continue; } @@ -1735,7 +1749,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!is_decisive(bestValue) && bestValue > beta) bestValue = (bestValue + beta) / 2; - Color us = pos.side_to_move(); if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us) && type_of(pos.captured_piece()) >= ROOK) @@ -1764,7 +1777,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 757 / rootDelta + !i * reductionScale * 218 / 512 + 1200; + return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182; } // elapsed() returns the time elapsed since the search started. If the @@ -1860,35 +1873,35 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(121 * depth - 77, 1633) + 375 * (bestMove == ttMove); - int malus = std::min(825 * depth - 196, 2159) - 16 * moveCount; + int bonus = std::min(116 * depth - 81, 1515) + 347 * (bestMove == ttMove); + int malus = std::min(848 * depth - 207, 2446) - 17 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 881 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 910 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1083 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1085 / 1024); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1482 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1395 / 1024; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 614 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 602 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1397 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1448 / 1024; } } @@ -1896,14 +1909,15 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - static constexpr std::array conthist_bonuses = { - {{1, 1157}, {2, 648}, {3, 288}, {4, 576}, {5, 140}, {6, 441}}}; + static std::array conthist_bonuses = { + {{1, 1133}, {2, 683}, {3, 312}, {4, 582}, {5, 149}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; + if (((ss - i)->currentMove).is_ok()) (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 88 * (i < 2); } @@ -1915,16 +1929,16 @@ 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; // Untuned to prevent duplicate effort + workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 761 / 1024; + workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 805 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 896 / 1024); int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << bonus * (bonus > 0 ? 850 : 550) / 1024; + << bonus * (bonus > 0 ? 905 : 505) / 1024; } } @@ -1960,7 +1974,6 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { return best; } - // Used to print debug info and, more importantly, to detect // when we are out of available time and thus stop the search. void SearchManager::check_time(Search::Worker& worker) { @@ -2038,8 +2051,9 @@ void syzygy_extend_pv(const OptionsMap& options, for (const auto& m : MoveList(pos)) legalMoves.emplace_back(m); - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves); - RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, false, time_abort); + RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); if (legalMoves[0].tbRank != rm.tbRank) break; @@ -2098,7 +2112,8 @@ void syzygy_extend_pv(const OptionsMap& options, [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); // The winning side tries to minimize DTZ, the losing side maximizes it - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, true, time_abort); // If DTZ is not available we might not find a mate, so we bail out if (!config.rootInTB || config.cardinality > 0) diff --git a/src/search.h b/src/search.h index 848cbbc89..43826093d 100644 --- a/src/search.h +++ b/src/search.h @@ -333,7 +333,7 @@ class Worker { void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss); void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); - void do_null_move(Position& pos, StateInfo& st); + void do_null_move(Position& pos, StateInfo& st, Stack* const ss); void undo_move(Position& pos, const Move move); void undo_null_move(Position& pos); diff --git a/src/shm.h b/src/shm.h index ae5429676..b870afc24 100644 --- a/src/shm.h +++ b/src/shm.h @@ -455,7 +455,8 @@ class SharedMemoryBackend { public: SharedMemoryBackend() = default; - SharedMemoryBackend(const std::string& shm_name, const T& value) {} + SharedMemoryBackend([[maybe_unused]] const std::string& shm_name, + [[maybe_unused]] const T& value) {} void* get() const { return nullptr; } diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 2cd53db65..f6c4e1215 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1216,6 +1216,8 @@ template void* mapped(TBTable& e, const Position& pos) { static std::mutex mutex; + // Because TB is the only usage of materialKey, check it here in debug mode + assert(pos.material_key_is_ok()); // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). @@ -1594,10 +1596,11 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, - Search::RootMoves& rootMoves, - bool rule50, - bool rankDTZ) { +bool Tablebases::root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort) { ProbeState result = OK; StateInfo st; @@ -1642,7 +1645,7 @@ bool Tablebases::root_probe(Position& pos, pos.undo_move(m.pv[0]); - if (result == FAIL) + if (time_abort() || result == FAIL) return false; // Better moves are ranked higher. Certain wins are ranked equally. @@ -1707,10 +1710,11 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo return true; } -Config Tablebases::rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ) { +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ, + const std::function& time_abort) { Config config; if (rootMoves.empty()) @@ -1733,10 +1737,11 @@ Config Tablebases::rank_root_moves(const OptionsMap& options, if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { - // Rank moves using DTZ tables - config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ); + // Rank moves using DTZ tables, bail out if time_abort flags zeitnot + config.rootInTB = + root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort); - if (!config.rootInTB) + if (!config.rootInTB && !time_abort()) { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index c34338fe3..4a6c3b763 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,6 +19,7 @@ #ifndef TBPROBE_H #define TBPROBE_H +#include #include #include @@ -66,12 +67,18 @@ extern int MaxCardinality; void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ); +bool root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); -Config rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ = false); +Config rank_root_moves( + const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ = false, + const std::function& time_abort = []() { return false; }); } // namespace Stockfish::Tablebases diff --git a/src/types.h b/src/types.h index 46aa16a03..c6613f2f2 100644 --- a/src/types.h +++ b/src/types.h @@ -293,26 +293,31 @@ struct DirtyPiece { // Keep track of what threats change on the board (used by NNUE) struct DirtyThreat { + static constexpr int PcSqOffset = 0; + static constexpr int ThreatenedSqOffset = 8; + static constexpr int ThreatenedPcOffset = 16; + static constexpr int PcOffset = 20; + DirtyThreat() { /* don't initialize data */ } + DirtyThreat(uint32_t raw) : + data(raw) {} DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 28) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset) + | (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset); } - Piece pc() const { return static_cast(data >> 20 & 0xf); } - Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } - Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } - Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { - uint32_t b = data >> 28; - sf_assume(b == 0 || b == 1); - return b; - } + Piece pc() const { return static_cast(data >> 20 & 0xf); } + Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } + Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } + Square pc_sq() const { return static_cast(data & 0xff); } + bool add() const { return data >> 31; } + uint32_t raw() const { return data; } private: uint32_t data; }; -using DirtyThreatList = ValueList; +using DirtyThreatList = ValueList; // A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. // Moving a piece also can reveal at most 8 discovered attacks. @@ -448,8 +453,6 @@ class Move { return Square(data & 0x3F); } - constexpr int from_to() const { return data & 0xFFF; } - constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } diff --git a/src/uci.cpp b/src/uci.cpp index a34e266c7..0cdda0c4f 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -345,16 +345,9 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); - TimePoint elapsed = now(); - // Run with silenced network verification engine.go(limits); engine.wait_for_search_finished(); - - totalTime += now() - elapsed; - - nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); @@ -402,6 +395,7 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); + nodesSearched = 0; TimePoint elapsed = now(); // Run with silenced network verification @@ -413,7 +407,6 @@ void UCIEngine::benchmark(std::istream& args) { updateHashfullReadings(); nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); diff --git a/tests/instrumented.py b/tests/instrumented.py index db5ec8e08..80831ce3f 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -67,7 +67,6 @@ def Stockfish(*args, **kwargs): class TestCLI(metaclass=OrderedClassMembers): - def beforeAll(self): pass @@ -141,7 +140,7 @@ class TestCLI(metaclass=OrderedClassMembers): def test_bench_128_threads_3_bench_tmp_epd_depth(self): self.stockfish = Stockfish( - f"bench 128 {get_threads()} 3 {os.path.join(PATH,'bench_tmp.epd')} depth".split( + f"bench 128 {get_threads()} 3 {os.path.join(PATH, 'bench_tmp.epd')} depth".split( " " ), True, @@ -167,7 +166,7 @@ class TestCLI(metaclass=OrderedClassMembers): def test_export_net_verify_nnue(self): current_path = os.path.abspath(os.getcwd()) self.stockfish = Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) assert self.stockfish.process.returncode == 0 @@ -254,7 +253,7 @@ class TestInteractive(metaclass=OrderedClassMembers): self.stockfish.send_command("go depth 5") def callback(output): - regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp -?\d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth") and not re.match(regex, output): assert False if output.startswith("bestmove"): @@ -274,7 +273,7 @@ class TestInteractive(metaclass=OrderedClassMembers): def callback(output): nonlocal depth - regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp -?\d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth"): if not re.match(regex, output): @@ -390,7 +389,7 @@ class TestInteractive(metaclass=OrderedClassMembers): def test_verify_nnue_network(self): current_path = os.path.abspath(os.getcwd()) Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) self.stockfish.send_command("setoption name EvalFile value verify.nnue") @@ -469,7 +468,7 @@ class TestSyzygy(metaclass=OrderedClassMembers): self.stockfish.send_command("go depth 5") def check_output(output): - if "score cp -20000" in output or "score mate" in output: + if "score cp -20000" in output or "score mate -" in output: return True self.stockfish.check_output(check_output) @@ -508,7 +507,7 @@ if __name__ == "__main__": framework = MiniTestFramework() - # Each test suite will be ran inside a temporary directory + # Each test suite will be run inside a temporary directory framework.run([TestCLI, TestInteractive, TestSyzygy]) EPD.delete_bench_epd()