mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-23 05:07:14 +00:00
Merge commit '3502c8ae426506453ca64e87e48d962b327c2356' into cluster
This commit is contained in:
+10
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user