Merge commit 'bc9f08731f10896a306bcc34e30e5606087af79a' into cluster

This is the last commit before there are more conflicts.
This commit is contained in:
Steinar H. Gunderson
2025-12-25 20:13:41 +01:00
8 changed files with 95 additions and 102 deletions
+4 -3
View File
@@ -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<Color Perspective>
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
+6 -22
View File
@@ -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
-2
View File
@@ -106,8 +106,6 @@ class Network {
template<IndexType Size>
friend struct AccumulatorCaches::Cache;
friend class AccumulatorStack;
};
// Definitions of the network types
+40 -28
View File
@@ -18,9 +18,9 @@
#include "nnue_accumulator.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <initializer_list>
#include <type_traits>
#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<TransformedFeatureDimensions>()).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<const __m256i*>(old + i));
const __m256i new_v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(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<Bitboard>(equal_mask) << i;
}
return ~same_bb;
#else
Bitboard changed = 0;
for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq)
{
changed |= static_cast<Bitboard>(old[sq] != new_[sq]) << sq;
}
return changed;
#endif
}
template<Color Perspective, IndexType Dimensions>
void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& featureTransformer,
const Position& pos,
@@ -374,28 +397,23 @@ void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& 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<Perspective>(sq, piece, ksq));
}
while (toAdd)
{
Square sq = pop_lsb(toAdd);
added.push_back(FeatureSet::make_index<Perspective>(sq, piece, ksq));
}
}
while (removed_bb)
{
Square sq = pop_lsb(removed_bb);
removed.push_back(FeatureSet::make_index<Perspective>(sq, entry.pieces[sq], ksq));
}
while (added_bb)
{
Square sq = pop_lsb(added_bb);
added.push_back(FeatureSet::make_index<Perspective>(sq, pos.piece_on(sq), ksq));
}
entry.pieceBB = pos.pieces();
std::copy_n(pos.piece_array(), SQUARE_NB, entry.pieces);
auto& accumulator = accumulatorState.acc<Dimensions>();
accumulator.computed[Perspective] = true;
@@ -518,12 +536,6 @@ void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& 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);
}
}
+5 -10
View File
@@ -25,7 +25,6 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include "../types.h"
#include "nnue_architecture.h"
@@ -48,7 +47,7 @@ template<IndexType Size>
struct alignas(CacheLineSize) Accumulator {
std::int16_t accumulation[COLOR_NB][Size];
std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets];
std::array<bool, COLOR_NB> computed;
std::array<bool, COLOR_NB> 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<Dimensions>& featureTransformer,
const std::size_t end) noexcept;
std::vector<AccumulatorState> accumulators;
std::size_t size;
std::array<AccumulatorState, MAX_PLY + 1> accumulators;
std::size_t size = 1;
};
} // namespace Stockfish::Eval::NNUE
+4
View File
@@ -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;
+7 -4
View File
@@ -91,10 +91,11 @@ class Position {
Bitboard pieces(PieceTypes... pts) const;
Bitboard pieces(Color c) const;
template<typename... PieceTypes>
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<PieceType Pt>
int count(Color c) const;
template<PieceType Pt>
@@ -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()); }
+29 -33
View File
@@ -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<PV>(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;
}
}