Merge commit '3502c8ae426506453ca64e87e48d962b327c2356' into cluster

This commit is contained in:
Steinar H. Gunderson
2025-12-24 15:12:37 +01:00
15 changed files with 411 additions and 126 deletions
+10 -2
View File
@@ -53,6 +53,7 @@ Engine::Engine(std::string path) :
NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG),
NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) {
pos.set(StartFEN, false, &states->back());
capSq = SQ_NONE;
}
std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
@@ -61,9 +62,10 @@ std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960
return Benchmark::perft(fen, depth, isChess960);
}
void Engine::go(const Search::LimitsType& limits) {
void Engine::go(Search::LimitsType& limits) {
assert(limits.perft == 0);
verify_networks();
limits.capSq = capSq;
threads.start_thinking(options, pos, states, limits);
}
@@ -102,6 +104,7 @@ void Engine::set_position(const std::string& fen, const std::vector<std::string>
states = StateListPtr(new std::deque<StateInfo>(1));
pos.set(fen, options["UCI_Chess960"], &states->back());
capSq = SQ_NONE;
for (const auto& move : moves)
{
auto m = UCIEngine::to_move(pos, move);
@@ -111,6 +114,11 @@ void Engine::set_position(const std::string& fen, const std::vector<std::string>
states->emplace_back();
pos.do_move(m, states->back());
capSq = SQ_NONE;
DirtyPiece& dp = states->back().dirtyPiece;
if (dp.dirty_num > 1 && dp.to[1] == SQ_NONE)
capSq = m.to_sq();
}
}
@@ -172,4 +180,4 @@ std::string Engine::visualize() const {
return ss.str();
}
}
}
+7 -4
View File
@@ -20,24 +20,26 @@
#define ENGINE_H_INCLUDED
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <cstdint>
#include "nnue/network.h"
#include "position.h"
#include "search.h"
#include "syzygy/tbprobe.h" // for Stockfish::Depth
#include "thread.h"
#include "tt.h"
#include "ucioption.h"
#include "syzygy/tbprobe.h" // for Stockfish::Depth
namespace Stockfish {
enum Square : int;
class Engine {
public:
using InfoShort = Search::InfoShort;
@@ -50,7 +52,7 @@ class Engine {
std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960);
// non blocking call to start searching
void go(const Search::LimitsType&);
void go(Search::LimitsType&);
// non blocking call to stop searching
void stop();
@@ -92,6 +94,7 @@ class Engine {
Position pos;
StateListPtr states;
Square capSq;
OptionsMap options;
ThreadPool threads;
@@ -104,4 +107,4 @@ class Engine {
} // namespace Stockfish
#endif // #ifndef ENGINE_H_INCLUDED
#endif // #ifndef ENGINE_H_INCLUDED
+20 -13
View File
@@ -25,12 +25,14 @@
#include <iomanip>
#include <iostream>
#include <sstream>
#include <memory>
#include "nnue/network.h"
#include "nnue/nnue_misc.h"
#include "position.h"
#include "types.h"
#include "uci.h"
#include "nnue/nnue_accumulator.h"
namespace Stockfish {
@@ -45,7 +47,10 @@ int Eval::simple_eval(const Position& pos, Color c) {
// Evaluate is the evaluator for the outer world. It returns a static evaluation
// of the position from the point of view of the side to move.
Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, int optimism) {
Value Eval::evaluate(const Eval::NNUE::Networks& networks,
const Position& pos,
Eval::NNUE::AccumulatorCaches& caches,
int optimism) {
assert(!pos.checkers());
@@ -55,17 +60,17 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos,
int nnueComplexity;
int v;
Value nnue = smallNet ? networks.small.evaluate(pos, true, &nnueComplexity, psqtOnly)
: networks.big.evaluate(pos, true, &nnueComplexity, false);
Value nnue = smallNet ? networks.small.evaluate(pos, nullptr, true, &nnueComplexity, psqtOnly)
: networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false);
const auto adjustEval = [&](int optDiv, int nnueDiv, int pawnCountConstant, int pawnCountMul,
int npmConstant, int evalDiv, int shufflingConstant,
int shufflingDiv) {
const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant,
int pawnCountMul, int npmConstant, int evalDiv,
int shufflingConstant, int shufflingDiv) {
// Blend optimism and eval with nnue complexity and material imbalance
optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv;
nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv;
int npm = pos.non_pawn_material() / 64;
int npm = pos.non_pawn_material() / npmDiv;
v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count<PAWN>())
+ optimism * (npmConstant + npm))
/ evalDiv;
@@ -76,11 +81,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos,
};
if (!smallNet)
adjustEval(513, 32395, 919, 11, 145, 1036, 178, 204);
adjustEval(524, 32395, 66, 942, 11, 139, 1058, 178, 204);
else if (psqtOnly)
adjustEval(517, 32857, 908, 7, 155, 1019, 224, 238);
adjustEval(517, 32857, 65, 908, 7, 155, 1006, 224, 238);
else
adjustEval(499, 32793, 903, 9, 147, 1067, 208, 211);
adjustEval(515, 32793, 63, 944, 9, 140, 1067, 206, 206);
// 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);
@@ -94,20 +99,22 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos,
// Trace scores are from white's point of view
std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) {
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);
if (pos.checkers())
return "Final evaluation: none (in check)";
std::stringstream ss;
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
ss << '\n' << NNUE::trace(pos, networks) << '\n';
ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n';
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
Value v = networks.big.evaluate(pos, false);
Value v = networks.big.evaluate(pos, &caches->big, false);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n";
v = evaluate(networks, pos, VALUE_ZERO);
v = evaluate(networks, pos, *caches, VALUE_ZERO);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
ss << " [with scaled NNUE, ...]";
+5 -3
View File
@@ -40,14 +40,16 @@ constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389;
namespace NNUE {
struct Networks;
struct AccumulatorCaches;
}
std::string trace(Position& pos, const Eval::NNUE::Networks& networks);
int simple_eval(const Position& pos, Color c);
Value evaluate(const NNUE::Networks& networks, const Position& pos, int optimism);
Value evaluate(const NNUE::Networks& networks,
const Position& pos,
Eval::NNUE::AccumulatorCaches& caches,
int optimism);
} // namespace Eval
} // namespace Stockfish
+5 -5
View File
@@ -190,8 +190,8 @@ void MovePicker::score() {
m.value += bool(pos.check_squares(pt) & to) * 16384;
// bonus for escaping from capture
m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51000
: pt == ROOK && !(to & threatenedByMinor) ? 24950
m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700
: pt == ROOK && !(to & threatenedByMinor) ? 25600
: !(to & threatenedByPawn) ? 14450
: 0)
: 0;
@@ -200,7 +200,7 @@ void MovePicker::score() {
m.value -= !(threatenedPieces & from)
? (pt == QUEEN ? bool(to & threatenedByRook) * 48150
+ bool(to & threatenedByMinor) * 10650
: pt == ROOK ? bool(to & threatenedByMinor) * 24500
: pt == ROOK ? bool(to & threatenedByMinor) * 24335
: pt != PAWN ? bool(to & threatenedByPawn) * 14950
: 0)
: 0;
@@ -241,7 +241,7 @@ Move MovePicker::select(Pred filter) {
// moves left, picking the move with the highest score from a list of generated moves.
Move MovePicker::next_move(bool skipQuiets) {
auto quiet_threshold = [](Depth d) { return -3550 * d; };
auto quiet_threshold = [](Depth d) { return -3560 * d; };
top:
switch (stage)
@@ -310,7 +310,7 @@ top:
return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2];
}))
{
if ((cur - 1)->value > -8000 || (cur - 1)->value <= quiet_threshold(depth))
if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth))
return *(cur - 1);
// Remaining quiets are bad
+3 -1
View File
@@ -23,7 +23,7 @@
#include "../../bitboard.h"
#include "../../position.h"
#include "../../types.h"
#include "../nnue_common.h"
#include "../nnue_accumulator.h"
namespace Stockfish::Eval::NNUE::Features {
@@ -49,6 +49,8 @@ void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active)
// Explicit template instantiations
template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
template IndexType HalfKAv2_hm::make_index<WHITE>(Square s, Piece pc, Square ksq);
template IndexType HalfKAv2_hm::make_index<BLACK>(Square s, Piece pc, Square ksq);
// Get a list of indices for recently changed features
template<Color Perspective>
+4 -4
View File
@@ -63,10 +63,6 @@ class HalfKAv2_hm {
{PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
static IndexType make_index(Square s, Piece pc, Square ksq);
public:
// Feature name
static constexpr const char* Name = "HalfKAv2_hm(Friend)";
@@ -126,6 +122,10 @@ class HalfKAv2_hm {
static constexpr IndexType MaxActiveDimensions = 32;
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
static IndexType make_index(Square s, Piece pc, Square ksq);
// Get a list of indices for active features
template<Color Perspective>
static void append_active_indices(const Position& pos, IndexList& active);
+25 -20
View File
@@ -187,10 +187,11 @@ bool Network<Arch, Transformer>::save(const std::optional<std::string>& filename
template<typename Arch, typename Transformer>
Value Network<Arch, Transformer>::evaluate(const Position& pos,
bool adjusted,
int* complexity,
bool psqtOnly) const {
Value Network<Arch, Transformer>::evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache,
bool adjusted,
int* complexity,
bool psqtOnly) const {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
@@ -198,20 +199,21 @@ Value Network<Arch, Transformer>::evaluate(const Position& pos,
constexpr int delta = 24;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned
[FeatureTransformer<Arch::TransformedFeatureDimensions, nullptr>::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
TransformedFeatureType
transformedFeaturesUnaligned[FeatureTransformer<FTDimensions, nullptr>::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment) TransformedFeatureType transformedFeatures
[FeatureTransformer<Arch::TransformedFeatureDimensions, nullptr>::BufferSize];
alignas(alignment) TransformedFeatureType
transformedFeatures[FeatureTransformer<FTDimensions, nullptr>::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket, psqtOnly);
const auto psqt =
featureTransformer->transform(pos, cache, transformedFeatures, bucket, psqtOnly);
const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0;
if (complexity)
@@ -257,26 +259,29 @@ void Network<Arch, Transformer>::verify(std::string evalfilePath) const {
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::hint_common_access(const Position& pos, bool psqtOnl) const {
featureTransformer->hint_common_access(pos, psqtOnl);
void Network<Arch, Transformer>::hint_common_access(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache,
bool psqtOnl) const {
featureTransformer->hint_common_access(pos, cache, psqtOnl);
}
template<typename Arch, typename Transformer>
NnueEvalTrace Network<Arch, Transformer>::trace_evaluate(const Position& pos) const {
NnueEvalTrace
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache) const {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned
[FeatureTransformer<Arch::TransformedFeatureDimensions, nullptr>::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
TransformedFeatureType
transformedFeaturesUnaligned[FeatureTransformer<FTDimensions, nullptr>::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment) TransformedFeatureType transformedFeatures
[FeatureTransformer<Arch::TransformedFeatureDimensions, nullptr>::BufferSize];
alignas(alignment) TransformedFeatureType
transformedFeatures[FeatureTransformer<FTDimensions, nullptr>::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
@@ -286,7 +291,7 @@ NnueEvalTrace Network<Arch, Transformer>::trace_evaluate(const Position& pos) co
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
{
const auto materialist =
featureTransformer->transform(pos, transformedFeatures, bucket, false);
featureTransformer->transform(pos, cache, transformedFeatures, bucket, false);
const auto positional = network[bucket]->propagate(transformedFeatures);
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
+16 -8
View File
@@ -31,10 +31,10 @@
#include "nnue_architecture.h"
#include "nnue_feature_transformer.h"
#include "nnue_misc.h"
#include "nnue_accumulator.h"
namespace Stockfish::Eval::NNUE {
enum class EmbeddedNNUEType {
BIG,
SMALL,
@@ -43,6 +43,8 @@ enum class EmbeddedNNUEType {
template<typename Arch, typename Transformer>
class Network {
static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions;
public:
Network(EvalFile file, EmbeddedNNUEType type) :
evalFile(file),
@@ -51,17 +53,20 @@ class Network {
void load(const std::string& rootDirectory, std::string evalfilePath);
bool save(const std::optional<std::string>& filename) const;
Value evaluate(const Position& pos,
bool adjusted = false,
int* complexity = nullptr,
bool psqtOnly = false) const;
Value evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache,
bool adjusted = false,
int* complexity = nullptr,
bool psqtOnly = false) const;
void hint_common_access(const Position& pos, bool psqtOnl) const;
void hint_common_access(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache,
bool psqtOnl) const;
void verify(std::string evalfilePath) const;
NnueEvalTrace trace_evaluate(const Position& pos) const;
NnueEvalTrace trace_evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache) const;
private:
void load_user_net(const std::string&, const std::string&);
@@ -89,6 +94,9 @@ class Network {
// Hash value of evaluation function structure
static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value();
template<IndexType Size>
friend struct AccumulatorCaches::Cache;
};
// Definitions of the network types
+71 -4
View File
@@ -28,13 +28,80 @@
namespace Stockfish::Eval::NNUE {
using BiasType = std::int16_t;
using PSQTWeightType = std::int32_t;
using IndexType = std::uint32_t;
// Class that holds the result of affine transformation of input features
template<IndexType Size>
struct alignas(CacheLineSize) Accumulator {
std::int16_t accumulation[2][Size];
std::int32_t psqtAccumulation[2][PSQTBuckets];
bool computed[2];
bool computedPSQT[2];
std::int16_t accumulation[COLOR_NB][Size];
std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets];
bool computed[COLOR_NB];
bool computedPSQT[COLOR_NB];
};
// AccumulatorCaches struct provides per-thread accumulator caches, where each
// cache contains multiple entries for each of the possible king squares.
// When the accumulator needs to be refreshed, the cached entry is used to more
// efficiently update the accumulator, instead of rebuilding it from scratch.
// This idea, was first described by Luecx (author of Koivisto) and
// is commonly referred to as "Finny Tables".
struct AccumulatorCaches {
template<typename Networks>
AccumulatorCaches(const Networks& networks) {
clear(networks);
}
template<IndexType Size>
struct alignas(CacheLineSize) Cache {
struct alignas(CacheLineSize) Entry {
BiasType accumulation[COLOR_NB][Size];
PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets];
Bitboard byColorBB[COLOR_NB][COLOR_NB];
Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB];
// 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 BiasType* biases) {
std::memset(byColorBB, 0, sizeof(byColorBB));
std::memset(byTypeBB, 0, sizeof(byTypeBB));
std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType));
std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType));
std::memset(psqtAccumulation, 0, sizeof(psqtAccumulation));
}
};
template<typename Network>
void clear(const Network& network) {
for (auto& entry : entries)
entry.clear(network.featureTransformer->biases);
}
void clear(const BiasType* biases) {
for (auto& entry : entries)
entry.clear(biases);
}
Entry& operator[](Square sq) { return entries[sq]; }
std::array<Entry, SQUARE_NB> entries;
};
template<typename Networks>
void clear(const Networks& networks) {
big.clear(networks.big);
}
// When adding a new cache for a network, i.e. the smallnet
// the appropriate condition must be added to FeatureTransformer::update_accumulator_refresh.
Cache<TransformedFeatureDimensionsBig> big;
};
} // namespace Stockfish::Eval::NNUE
+179 -13
View File
@@ -193,10 +193,10 @@ template<IndexType TransformedFeatureDimensions,
Accumulator<TransformedFeatureDimensions> StateInfo::* accPtr>
class FeatureTransformer {
private:
// Number of output dimensions for one side
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
private:
#ifdef VECTOR
static constexpr int NumRegs =
BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
@@ -304,10 +304,13 @@ class FeatureTransformer {
}
// Convert input features
std::int32_t
transform(const Position& pos, OutputType* output, int bucket, bool psqtOnly) const {
update_accumulator<WHITE>(pos, psqtOnly);
update_accumulator<BLACK>(pos, psqtOnly);
std::int32_t transform(const Position& pos,
AccumulatorCaches::Cache<HalfDimensions>* cache,
OutputType* output,
int bucket,
bool psqtOnly) const {
update_accumulator<WHITE>(pos, cache, psqtOnly);
update_accumulator<BLACK>(pos, cache, psqtOnly);
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation;
@@ -369,9 +372,11 @@ class FeatureTransformer {
return psqt;
} // end of function transform()
void hint_common_access(const Position& pos, bool psqtOnly) const {
hint_common_access_for_perspective<WHITE>(pos, psqtOnly);
hint_common_access_for_perspective<BLACK>(pos, psqtOnly);
void hint_common_access(const Position& pos,
AccumulatorCaches::Cache<HalfDimensions>* cache,
bool psqtOnly) const {
hint_common_access_for_perspective<WHITE>(pos, cache, psqtOnly);
hint_common_access_for_perspective<BLACK>(pos, cache, psqtOnly);
}
private:
@@ -648,7 +653,161 @@ class FeatureTransformer {
}
template<Color Perspective>
void update_accumulator_refresh(const Position& pos, bool psqtOnly) const {
void update_accumulator_refresh_cache(const Position& pos,
AccumulatorCaches::Cache<HalfDimensions>* cache) const {
assert(cache != nullptr);
Square ksq = pos.square<KING>(Perspective);
auto& entry = (*cache)[ksq];
auto& accumulator = pos.state()->*accPtr;
accumulator.computed[Perspective] = true;
accumulator.computedPSQT[Perspective] = true;
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[Perspective][c] & entry.byTypeBB[Perspective][pt];
const Bitboard newBB = pos.pieces(c, pt);
Bitboard toRemove = oldBB & ~newBB;
Bitboard toAdd = newBB & ~oldBB;
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));
}
}
}
#ifdef VECTOR
vec_t acc[NumRegs];
psqt_vec_t psqt[NumPsqtRegs];
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
{
auto entryTile =
reinterpret_cast<vec_t*>(&entry.accumulation[Perspective][j * TileHeight]);
for (IndexType k = 0; k < NumRegs; ++k)
acc[k] = entryTile[k];
for (int i = 0; i < int(added.size()); ++i)
{
IndexType index = added[i];
const IndexType offset = HalfDimensions * index + j * TileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
for (unsigned k = 0; k < NumRegs; ++k)
acc[k] = vec_add_16(acc[k], column[k]);
}
for (int i = 0; i < int(removed.size()); ++i)
{
IndexType index = removed[i];
const IndexType offset = HalfDimensions * index + j * TileHeight;
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
for (unsigned k = 0; k < NumRegs; ++k)
acc[k] = vec_sub_16(acc[k], column[k]);
}
for (IndexType k = 0; k < NumRegs; k++)
vec_store(&entryTile[k], acc[k]);
}
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
{
auto entryTilePsqt = reinterpret_cast<psqt_vec_t*>(
&entry.psqtAccumulation[Perspective][j * PsqtTileHeight]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = entryTilePsqt[k];
for (int i = 0; i < int(added.size()); ++i)
{
IndexType index = added[i];
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
}
for (int i = 0; i < int(removed.size()); ++i)
{
IndexType index = removed[i];
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);
}
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
vec_store_psqt(&entryTilePsqt[k], psqt[k]);
}
#else
for (const auto index : added)
{
const IndexType offset = HalfDimensions * index;
for (IndexType j = 0; j < HalfDimensions; ++j)
entry.accumulation[Perspective][j] += weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
}
for (const auto index : removed)
{
const IndexType offset = HalfDimensions * index;
for (IndexType j = 0; j < HalfDimensions; ++j)
entry.accumulation[Perspective][j] -= weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k];
}
#endif
// The accumulator of the refresh entry has been updated.
// Now copy its content to the actual accumulator we were refreshing
std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective],
sizeof(int32_t) * PSQTBuckets);
std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective],
sizeof(BiasType) * HalfDimensions);
for (Color c : {WHITE, BLACK})
entry.byColorBB[Perspective][c] = pos.pieces(c);
for (PieceType pt = PAWN; pt <= KING; ++pt)
entry.byTypeBB[Perspective][pt] = pos.pieces(pt);
}
template<Color Perspective>
void
update_accumulator_refresh(const Position& pos,
[[maybe_unused]] AccumulatorCaches::Cache<HalfDimensions>* cache,
bool psqtOnly) const {
// When we are refreshing the accumulator of the big net,
// redirect to the version of refresh that uses the refresh table.
// Using the cache for the small net is not beneficial.
if constexpr (HalfDimensions == Eval::NNUE::TransformedFeatureDimensionsBig)
{
update_accumulator_refresh_cache<Perspective>(pos, cache);
return;
}
#ifdef VECTOR
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
// is defined in the VECTOR code below, once in each branch
@@ -762,7 +921,9 @@ class FeatureTransformer {
}
template<Color Perspective>
void hint_common_access_for_perspective(const Position& pos, bool psqtOnly) const {
void hint_common_access_for_perspective(const Position& pos,
AccumulatorCaches::Cache<HalfDimensions>* cache,
bool psqtOnly) const {
// Works like update_accumulator, but performs less work.
// Updates ONLY the accumulator for pos.
@@ -785,11 +946,13 @@ class FeatureTransformer {
psqtOnly);
}
else
update_accumulator_refresh<Perspective>(pos, psqtOnly);
update_accumulator_refresh<Perspective>(pos, cache, psqtOnly);
}
template<Color Perspective>
void update_accumulator(const Position& pos, bool psqtOnly) const {
void update_accumulator(const Position& pos,
AccumulatorCaches::Cache<HalfDimensions>* cache,
bool psqtOnly) const {
auto [oldest_st, next] = try_find_computed_accumulator<Perspective>(pos, psqtOnly);
@@ -811,9 +974,12 @@ class FeatureTransformer {
psqtOnly);
}
else
update_accumulator_refresh<Perspective>(pos, psqtOnly);
update_accumulator_refresh<Perspective>(pos, cache, psqtOnly);
}
template<IndexType Size>
friend struct AccumulatorCaches::Cache;
alignas(CacheLineSize) BiasType biases[HalfDimensions];
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
+10 -7
View File
@@ -42,13 +42,15 @@ namespace Stockfish::Eval::NNUE {
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
void hint_common_parent_position(const Position& pos, const Networks& networks) {
void hint_common_parent_position(const Position& pos,
const Networks& networks,
AccumulatorCaches& caches) {
int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move()));
if (simpleEvalAbs > Eval::SmallNetThreshold)
networks.small.hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold);
networks.small.hint_common_access(pos, nullptr, simpleEvalAbs > Eval::PsqtOnlyThreshold);
else
networks.big.hint_common_access(pos, false);
networks.big.hint_common_access(pos, &caches.big, false);
}
namespace {
@@ -104,7 +106,8 @@ void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& p
// Returns a string with the value of each piece on a board,
// and a table for (PSQT, Layers) values bucket by bucket.
std::string trace(Position& pos, const Eval::NNUE::Networks& networks) {
std::string
trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) {
std::stringstream ss;
@@ -130,7 +133,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) {
// 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.
Value base = networks.big.evaluate(pos);
Value base = networks.big.evaluate(pos, &caches.big);
base = pos.side_to_move() == WHITE ? base : -base;
for (File f = FILE_A; f <= FILE_H; ++f)
@@ -149,7 +152,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) {
st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] =
false;
Value eval = networks.big.evaluate(pos);
Value eval = networks.big.evaluate(pos, &caches.big);
eval = pos.side_to_move() == WHITE ? eval : -eval;
v = base - eval;
@@ -167,7 +170,7 @@ std::string trace(Position& pos, const Eval::NNUE::Networks& networks) {
ss << board[row] << '\n';
ss << '\n';
auto t = networks.big.trace_evaluate(pos);
auto t = networks.big.trace_evaluate(pos, &caches.big);
ss << " NNUE network contributions "
<< (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl
+5 -4
View File
@@ -50,12 +50,13 @@ struct NnueEvalTrace {
std::size_t correctBucket;
};
struct Networks;
struct AccumulatorCaches;
std::string trace(Position& pos, const Networks& networks);
void hint_common_parent_position(const Position& pos, const Networks& networks);
std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches);
void hint_common_parent_position(const Position& pos,
const Networks& networks,
AccumulatorCaches& caches);
} // namespace Stockfish::Eval::NNUE
} // namespace Stockfish
+45 -36
View File
@@ -34,6 +34,8 @@
#include "misc.h"
#include "movegen.h"
#include "movepick.h"
#include "nnue/network.h"
#include "nnue/nnue_accumulator.h"
#include "nnue/nnue_common.h"
#include "nnue/nnue_misc.h"
#include "position.h"
@@ -53,14 +55,14 @@ using namespace Search;
namespace {
static constexpr double EvalLevel[10] = {1.043, 1.017, 0.952, 1.009, 0.971,
1.002, 0.992, 0.947, 1.046, 1.001};
static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913,
0.942, 0.933, 0.890, 0.984, 0.941};
// Futility margin
Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) {
Value futilityMult = 118 - 44 * noTtCutNode;
Value futilityMult = 118 - 45 * noTtCutNode;
Value improvingDeduction = 52 * improving * futilityMult / 32;
Value worseningDeduction = (310 + 48 * improving) * oppWorsening * futilityMult / 1024;
Value worseningDeduction = (316 + 48 * improving) * oppWorsening * futilityMult / 1024;
return futilityMult * d - improvingDeduction - worseningDeduction;
}
@@ -77,10 +79,10 @@ Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) {
}
// History and stats update bonus, based on depth
int stat_bonus(Depth d) { return std::clamp(211 * d - 315, 0, 1291); }
int stat_bonus(Depth d) { return std::clamp(214 * d - 318, 16, 1304); }
// History and stats update malus, based on depth
int stat_malus(Depth d) { return (d < 4 ? 572 * d - 285 : 1372); }
int stat_malus(Depth d) { return (d < 4 ? 572 * d - 284 : 1355); }
// Add a small random component to draw evaluations to avoid 3-fold blindness
Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); }
@@ -139,11 +141,16 @@ Search::Worker::Worker(SharedState& sharedState,
options(sharedState.options),
threads(sharedState.threads),
tt(sharedState.tt),
networks(sharedState.networks) {
networks(sharedState.networks),
refreshTable(networks) {
clear();
}
void Search::Worker::start_searching() {
// Initialize accumulator refresh entries
refreshTable.clear(networks);
// Non-main threads go directly to iterative_deepening()
if (!is_mainthread())
{
@@ -345,12 +352,12 @@ void Search::Worker::iterative_deepening() {
// Reset aspiration window starting size
Value avg = rootMoves[pvIdx].averageScore;
delta = 11 + avg * avg / 11254;
delta = 10 + avg * avg / 11480;
alpha = std::max(avg - delta, -VALUE_INFINITE);
beta = std::min(avg + delta, VALUE_INFINITE);
// Adjust optimism based on root move's averageScore (~4 Elo)
optimism[us] = 125 * avg / (std::abs(avg) + 91);
optimism[us] = 122 * avg / (std::abs(avg) + 92);
optimism[~us] = -optimism[us];
// Start with a small aspiration window and, in the case of a fail
@@ -487,9 +494,10 @@ void Search::Worker::iterative_deepening() {
double reduction = (1.48 + mainThread->previousTimeReduction) / (2.17 * timeReduction);
double bestMoveInstability = 1 + 1.88 * totBestMoveChanges / threads.size();
int el = std::clamp((bestValue + 750) / 150, 0, 9);
double recapture = limits.capSq == rootMoves[0].pv[0].to_sq() ? 0.955 : 1.005;
double totalTime = mainThread->tm.optimum() * fallingEval * reduction
* bestMoveInstability * EvalLevel[el];
* bestMoveInstability * EvalLevel[el] * recapture;
// Cap used time in case of a single legal move for a better viewer experience
if (rootMoves.size() == 1)
@@ -612,7 +620,7 @@ Value Search::Worker::search(
if (threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply)
|| ss->ply >= MAX_PLY)
return (ss->ply >= MAX_PLY && !ss->inCheck)
? evaluate(networks, pos, thisThread->optimism[us])
? evaluate(networks, pos, refreshTable, thisThread->optimism[us])
: value_draw(thisThread->nodes);
// Step 3. Mate distance pruning. Even if we mate at the next move our score
@@ -746,7 +754,7 @@ Value Search::Worker::search(
{
// Providing the hint that this node's accumulator will be used often
// brings significant Elo gain (~13 Elo).
Eval::NNUE::hint_common_parent_position(pos, networks);
Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable);
unadjustedStaticEval = eval = ss->staticEval;
}
else if (ss->ttHit)
@@ -754,9 +762,9 @@ Value Search::Worker::search(
// Never assume anything about values stored in TT
unadjustedStaticEval = tte->eval();
if (unadjustedStaticEval == VALUE_NONE)
unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]);
unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]);
else if (PvNode)
Eval::NNUE::hint_common_parent_position(pos, networks);
Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable);
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
@@ -766,7 +774,7 @@ Value Search::Worker::search(
}
else
{
unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]);
unadjustedStaticEval = evaluate(networks, pos, refreshTable, thisThread->optimism[us]);
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
// Static evaluation is saved as it was before adjustment by correction history
@@ -800,7 +808,7 @@ Value Search::Worker::search(
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
// return a fail low.
// Adjust razor margin according to cutoffCnt. (~1 Elo)
if (eval < alpha - 471 - (276 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth)
if (eval < alpha - 471 - (275 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha)
@@ -811,14 +819,14 @@ Value Search::Worker::search(
// The depth condition is important for mate finding.
if (!ss->ttPv && depth < 12
&& eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening)
- (ss - 1)->statScore / 284
- (ss - 1)->statScore / 286
>= beta
&& eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture))
return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval;
// Step 9. Null move search with verification search (~35 Elo)
if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001
&& eval >= beta && ss->staticEval >= beta - 21 * depth + 315 && !excludedMove
&& eval >= beta && ss->staticEval >= beta - 21 * depth + 312 && !excludedMove
&& pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly
&& beta > VALUE_TB_LOSS_IN_MAX_PLY)
{
@@ -924,13 +932,13 @@ Value Search::Worker::search(
}
}
Eval::NNUE::hint_common_parent_position(pos, networks);
Eval::NNUE::hint_common_parent_position(pos, networks, refreshTable);
}
moves_loop: // When in check, search starts here
// Step 12. A small Probcut idea, when we are in check (~4 Elo)
probCutBeta = beta + 436;
probCutBeta = beta + 452;
if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 4 && ttValue >= probCutBeta
&& std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
@@ -1013,7 +1021,7 @@ moves_loop: // When in check, search starts here
{
Piece capturedPiece = pos.piece_on(move.to_sq());
Value futilityValue =
ss->staticEval + 288 + 277 * lmrDepth + PieceValue[capturedPiece]
ss->staticEval + 285 + 277 * lmrDepth + PieceValue[capturedPiece]
+ thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]
/ 7;
if (futilityValue <= alpha)
@@ -1021,7 +1029,7 @@ moves_loop: // When in check, search starts here
}
// SEE based pruning for captures and checks (~11 Elo)
if (!pos.see_ge(move, -199 * depth))
if (!pos.see_ge(move, -203 * depth))
continue;
}
else
@@ -1041,10 +1049,10 @@ moves_loop: // When in check, search starts here
lmrDepth += history / 5285;
Value futilityValue =
ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 58) + 131 * lmrDepth;
ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 57) + 131 * lmrDepth;
// Futility pruning: parent node (~13 Elo)
if (!ss->inCheck && lmrDepth < 15 && futilityValue <= alpha)
if (!ss->inCheck && lmrDepth < 14 && futilityValue <= alpha)
{
if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY
&& futilityValue < VALUE_TB_WIN_IN_MAX_PLY)
@@ -1055,7 +1063,7 @@ moves_loop: // When in check, search starts here
lmrDepth = std::max(lmrDepth, 0);
// Prune moves with negative SEE (~4 Elo)
if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth))
if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth))
continue;
}
}
@@ -1075,11 +1083,11 @@ moves_loop: // When in check, search starts here
// so changing them requires tests at these types of time controls.
// Recursive singular search is avoided.
if (!rootNode && move == ttMove && !excludedMove
&& depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv
&& depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv
&& std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
Value singularBeta = ttValue - (64 + 59 * (ss->ttPv && !PvNode)) * depth / 64;
Value singularBeta = ttValue - (65 + 59 * (ss->ttPv && !PvNode)) * depth / 63;
Depth singularDepth = newDepth / 2;
ss->excludedMove = move;
@@ -1183,10 +1191,10 @@ moves_loop: // When in check, search starts here
ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()]
+ (*contHist[0])[movedPiece][move.to_sq()]
+ (*contHist[1])[movedPiece][move.to_sq()]
+ (*contHist[3])[movedPiece][move.to_sq()] - 5007;
+ (*contHist[3])[movedPiece][move.to_sq()] - 5024;
// Decrease/increase reduction for moves with a good/bad history (~8 Elo)
r -= ss->statScore / 12901;
r -= ss->statScore / 13182;
// Step 17. Late moves reduction / extension (LMR, ~117 Elo)
if (depth >= 2 && moveCount > 1 + rootNode)
@@ -1323,7 +1331,7 @@ moves_loop: // When in check, search starts here
else
{
// Reduce other moves if we have found at least one score improvement (~2 Elo)
if (depth > 2 && depth < 12 && beta < 13132 && value > -13295)
if (depth > 2 && depth < 12 && beta < 13546 && value > -13478)
depth -= 2;
assert(depth > 0);
@@ -1368,7 +1376,7 @@ moves_loop: // When in check, search starts here
{
int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761)
+ ((ss - 1)->moveCount > 11)
+ (!ss->inCheck && bestValue <= ss->staticEval - 144);
+ (!ss->inCheck && bestValue <= ss->staticEval - 142);
update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
stat_bonus(depth) * bonus);
thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()]
@@ -1463,7 +1471,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
// Step 2. Check for an immediate draw or maximum ply reached
if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY)
return (ss->ply >= MAX_PLY && !ss->inCheck)
? evaluate(networks, pos, thisThread->optimism[us])
? evaluate(networks, pos, refreshTable, thisThread->optimism[us])
: VALUE_DRAW;
assert(0 <= ss->ply && ss->ply < MAX_PLY);
@@ -1495,7 +1503,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
// Never assume anything about values stored in TT
unadjustedStaticEval = tte->eval();
if (unadjustedStaticEval == VALUE_NONE)
unadjustedStaticEval = evaluate(networks, pos, thisThread->optimism[us]);
unadjustedStaticEval =
evaluate(networks, pos, refreshTable, thisThread->optimism[us]);
ss->staticEval = bestValue =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
@@ -1508,7 +1517,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
{
// In case of null move search, use previous static eval with a different sign
unadjustedStaticEval = (ss - 1)->currentMove != Move::null()
? evaluate(networks, pos, thisThread->optimism[us])
? evaluate(networks, pos, refreshTable, thisThread->optimism[us])
: -(ss - 1)->staticEval;
ss->staticEval = bestValue =
to_corrected_static_eval(unadjustedStaticEval, *thisThread, pos);
@@ -1528,7 +1537,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta,
if (bestValue > alpha)
alpha = bestValue;
futilityBase = ss->staticEval + 246;
futilityBase = ss->staticEval + 250;
}
const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
@@ -1676,7 +1685,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) {
int reductionScale = reductions[d] * reductions[mn];
return (reductionScale + 1123 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025);
return (reductionScale + 1150 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025);
}
TimePoint Search::Worker::elapsed() const {
+6 -2
View File
@@ -39,6 +39,7 @@
#include "syzygy/tbprobe.h"
#include "timeman.h"
#include "types.h"
#include "nnue/nnue_accumulator.h"
namespace Stockfish {
@@ -110,8 +111,7 @@ struct RootMove {
using RootMoves = std::vector<RootMove>;
// LimitsType struct stores information sent by GUI about available time to
// search the current move, maximum depth/time, or if we are in analysis mode.
// LimitsType struct stores information sent by the caller about the analysis required.
struct LimitsType {
// Init explicitly due to broken value-initialization of non POD in MSVC
@@ -129,6 +129,7 @@ struct LimitsType {
int movestogo, depth, mate, perft, infinite;
uint64_t nodes;
bool ponderMode;
Square capSq;
};
@@ -338,6 +339,9 @@ class Worker {
TranspositionTable& tt;
const Eval::NNUE::Networks& networks;
// Used by NNUE
Eval::NNUE::AccumulatorCaches refreshTable;
friend class Stockfish::ThreadPool;
friend class SearchManager;
};