diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 70e9beeb1..5bcfb0849 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -29,9 +29,10 @@ namespace Stockfish::Eval::NNUE::Features { // Index of a feature for a given king position and another piece on some square template -inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] - + KingBuckets[Perspective][ksq]); +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] + + KingBuckets[int(ksq) ^ flip]; } // Get a list of indices for active features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index ba122adc8..b72bbbce4 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -75,45 +75,29 @@ class HalfKAv2_hm { #define B(v) (v * PS_NB) // clang-format off - static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { - { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), + static constexpr IndexType KingBuckets[SQUARE_NB] = { + B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), - B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) }, - { B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0), - B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), - B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), - B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), - B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), - B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), - B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), - B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } + B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0), }; // clang-format on #undef B // clang-format off // Orient a square according to perspective (rotates by 180 for black) - static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { - { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + static constexpr IndexType OrientTBL[SQUARE_NB] = { 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_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, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } + 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 , }; // clang-format on diff --git a/src/nnue/network.h b/src/nnue/network.h index c9358823b..6855831d5 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -106,8 +106,6 @@ class Network { template friend struct AccumulatorCaches::Cache; - - friend class AccumulatorStack; }; // Definitions of the network types diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index d13105aa4..b1743329f 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" @@ -75,7 +75,7 @@ void AccumulatorStack::reset() noexcept { } void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { - assert(size + 1 < accumulators.size()); + assert(size < accumulators.size()); accumulators[size].reset(dirtyPiece); size++; } @@ -362,6 +362,29 @@ void update_accumulator_incremental( (target_state.acc()).computed[Perspective] = true; } +Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) { +#if defined(USE_AVX512) || defined(USE_AVX2) + static_assert(sizeof(Piece) == 1); + Bitboard same_bb = 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; + } + return ~same_bb; +#else + Bitboard changed = 0; + for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq) + { + changed |= static_cast(old[sq] != new_[sq]) << sq; + } + return changed; +#endif +} + template void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, const Position& pos, @@ -374,28 +397,23 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat auto& entry = cache[ksq][Perspective]; FeatureSet::IndexList removed, added; - for (Color c : {WHITE, BLACK}) - { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; + const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array()); + Bitboard removed_bb = changed_bb & entry.pieceBB; + Bitboard added_bb = changed_bb & pos.pieces(); - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - } + while (removed_bb) + { + Square sq = pop_lsb(removed_bb); + removed.push_back(FeatureSet::make_index(sq, entry.pieces[sq], ksq)); } + while (added_bb) + { + Square sq = pop_lsb(added_bb); + added.push_back(FeatureSet::make_index(sq, pos.piece_on(sq), ksq)); + } + + entry.pieceBB = pos.pieces(); + std::copy_n(pos.piece_array(), SQUARE_NB, entry.pieces); auto& accumulator = accumulatorState.acc(); accumulator.computed[Perspective] = true; @@ -518,12 +536,6 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, sizeof(int32_t) * PSQTBuckets); #endif - - for (Color c : {WHITE, BLACK}) - entry.byColorBB[c] = pos.pieces(c); - - for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[pt] = pos.pieces(pt); } } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 10aadc917..7ff95b6ef 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -25,7 +25,6 @@ #include #include #include -#include #include "../types.h" #include "nnue_architecture.h" @@ -48,7 +47,7 @@ template struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[COLOR_NB][Size]; std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - std::array computed; + std::array computed = {}; }; @@ -71,8 +70,8 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Entry { BiasType accumulation[Size]; PSQTWeightType psqtAccumulation[PSQTBuckets]; - Bitboard byColorBB[COLOR_NB]; - Bitboard byTypeBB[PIECE_TYPE_NB]; + Piece pieces[SQUARE_NB]; + 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 @@ -142,10 +141,6 @@ struct AccumulatorState { class AccumulatorStack { public: - AccumulatorStack() : - accumulators(MAX_PLY + 1), - size{1} {} - [[nodiscard]] const AccumulatorState& latest() const noexcept; void reset() noexcept; @@ -178,8 +173,8 @@ class AccumulatorStack { const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; - std::vector accumulators; - std::size_t size; + std::array accumulators; + std::size_t size = 1; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/position.cpp b/src/position.cpp index d0cad3e7f..1551eb9df 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -858,6 +858,10 @@ DirtyPiece Position::do_move(Move m, st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } + // If en passant is impossible, then k will not change and we can prefetch earlier + if (tt && !checkEP) + prefetch(tt->first_entry(adjust_key50(k))); + // Set capture piece st->capturedPiece = captured; diff --git a/src/position.h b/src/position.h index dde496fe0..579121bf3 100644 --- a/src/position.h +++ b/src/position.h @@ -91,10 +91,11 @@ class Position { Bitboard pieces(PieceTypes... pts) const; Bitboard pieces(Color c) const; template - Bitboard pieces(Color c, PieceTypes... pts) const; - Piece piece_on(Square s) const; - Square ep_square() const; - bool empty(Square s) const; + Bitboard pieces(Color c, PieceTypes... pts) const; + Piece piece_on(Square s) const; + const Piece* piece_array() const; + Square ep_square() const; + bool empty(Square s) const; template int count(Color c) const; template @@ -208,6 +209,8 @@ inline Piece Position::piece_on(Square s) const { return board[s]; } +inline const Piece* Position::piece_array() const { return board; } + inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; } inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); } diff --git a/src/search.cpp b/src/search.cpp index 150bf5f67..fd2cb6367 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -138,7 +138,8 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move TTMove); + Move TTMove, + int moveCount); } // namespace @@ -526,9 +527,10 @@ void Search::Worker::iterative_deepening() { 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 totalTime = - mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability; + double totalTime = mainThread->tm.optimum() * fallingEval * reduction + * bestMoveInstability * highBestMoveEffort; // Cap used time in case of a single legal move for a better viewer experience if (rootMoves.size() == 1) @@ -536,10 +538,6 @@ void Search::Worker::iterative_deepening() { auto elapsedTime = elapsed(); - if (completedDepth >= 10 && nodesEffort >= 92425 && elapsedTime > totalTime * 0.666 - && !mainThread->ponder) - threads.stop = true; - // Stop the search if we have exceeded the totalTime or maximum if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum()))) { @@ -733,10 +731,7 @@ Value Search::Worker::search( if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) - && (cutNode == (ttData.value >= beta) || depth > 5) - // avoid a TT cutoff if the rule50 count is high and the TT move is zeroing - && (depth > 8 || ttData.move == Move::none() || pos.rule50_count() < 80 - || (!ttCapture && type_of(pos.moved_piece(ttData.move)) != PAWN))) + && (cutNode == (ttData.value >= beta) || depth > 5)) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) @@ -866,12 +861,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -2023, 1563) + 583; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 944 / 1024; + int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; + mainHistory[~us][((ss - 1)->currentMove).from_to()] << 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] - << bonus * 1438 / 1024; + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; } // Set up the improving flag, which is true if current static evaluation is @@ -901,7 +895,6 @@ Value Search::Worker::search( return futilityMult * d // - 2094 * improving * futilityMult / 1024 // - 1324 * opponentWorsening * futilityMult / 4096 // - + (ss - 1)->statScore / 331 // + std::abs(correctionValue) / 158105; }; @@ -917,7 +910,7 @@ Value Search::Worker::search( assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 6 + depth / 3; + Depth R = 6 + depth / 3 + improving; ss->currentMove = Move::null(); ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; @@ -1308,7 +1301,10 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && ttData.depth > 1 && rootDepth > 8) + // decisive score handling improves mate finding and retrograde analysis. + if (move == ttData.move + && ((is_valid(ttData.value) && is_decisive(ttData.value) && ttData.depth > 0) + || (ttData.depth > 1 && rootDepth > 8))) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); @@ -1440,7 +1436,7 @@ moves_loop: // When in check, search starts here else if (bestMove) { update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - ttData.move); + ttData.move, moveCount); if (!PvNode) ttMoveHistory << (bestMove == ttData.move ? 809 : -865); } @@ -1496,16 +1492,15 @@ moves_loop: // When in check, search starts here moveCount != 0 ? depth : std::min(MAX_PLY - 1, depth + 6), bestMove, unadjustedStaticEval, tt.generation()); - // Adjust correction history + // 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 && bestValue < beta) // negative correction & no fail high - || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low + && (bestValue < ss->staticEval) == !bestMove) { auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - update_correction_history(pos, ss, *this, - (1088 - 180 * (bestValue > ss->staticEval)) * bonus / 1024); + update_correction_history(pos, ss, *this, bonus); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1853,41 +1848,42 @@ void update_all_stats(const Position& pos, SearchedList& quietsSearched, SearchedList& capturesSearched, Depth depth, - Move ttMove) { + Move ttMove, + int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(151 * depth - 91, 1730) + 302 * (bestMove == ttMove); - int malus = std::min(951 * depth - 156, 2468) - 30 * quietsSearched.size(); + int bonus = std::min(121 * depth - 77, 1633) + 375 * (bestMove == ttMove); + int malus = std::min(825 * depth - 196, 2159) - 16 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 957 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 881 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1083 / 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; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1482 / 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 * 503 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 614 / 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 * 1157 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1397 / 1024; } } @@ -1923,7 +1919,7 @@ void update_quiet_histories( int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << (bonus * (bonus > 0 ? 800 : 500) / 1024) + 70; + << bonus * (bonus > 0 ? 850 : 550) / 1024; } }