mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
Replace hint_common_parent_position() by backwards accumulator updates
Only calls to `evaluate()` now trigger NNUE accumulator updates. To make sure that we are likely to find parent positions from which to update the accumulators we perform a backwards NNUE update whenever we compute the accumulator from scratch for some position. passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 39680 W: 10474 L: 10164 D: 19042 Ptnml(0-2): 171, 4068, 11042, 4398, 161 https://tests.stockfishchess.org/tests/view/67a27f26eb183d11c65945be passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 337308 W: 86408 L: 85550 D: 165350 Ptnml(0-2): 276, 30551, 106126, 31441, 260 https://tests.stockfishchess.org/tests/view/67a287efeb183d11c65945ee then simplified: STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 28608 W: 7641 L: 7413 D: 13554 Ptnml(0-2): 132, 3036, 7744, 3256, 136 https://tests.stockfishchess.org/tests/view/67a4703719f522d3866d3345 LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 200226 W: 51026 L: 50990 D: 98210 Ptnml(0-2): 170, 18468, 62799, 18508, 168 https://tests.stockfishchess.org/tests/view/67a4f255229c1a170cc08964 The version in this PR is a bit different from the simplified version, but it's compile-time changes only. closes https://github.com/official-stockfish/Stockfish/pull/5870 No functional change
This commit is contained in:
committed by
Disservin
parent
fa6c30af81
commit
e9997afb1c
@@ -278,12 +278,6 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::hint_common_access(
|
||||
const Position& pos, AccumulatorCaches::Cache<FTDimensions>* cache) const {
|
||||
featureTransformer->hint_common_access(pos, cache);
|
||||
}
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
NnueEvalTrace
|
||||
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
|
||||
|
||||
@@ -67,9 +67,6 @@ class Network {
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
|
||||
|
||||
void hint_common_access(const Position& pos,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
|
||||
void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const;
|
||||
NnueEvalTrace trace_evaluate(const Position& pos,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
|
||||
@@ -41,6 +41,11 @@ using BiasType = std::int16_t;
|
||||
using WeightType = std::int16_t;
|
||||
using PSQTWeightType = std::int32_t;
|
||||
|
||||
enum IncUpdateDirection {
|
||||
FORWARD,
|
||||
BACKWARDS
|
||||
};
|
||||
|
||||
// If vector instructions are enabled, we update and refresh the
|
||||
// accumulator tile by tile such that each tile fits in the CPU's
|
||||
// vector registers.
|
||||
@@ -249,6 +254,7 @@ class FeatureTransformer {
|
||||
|
||||
// Number of output dimensions for one side
|
||||
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
||||
static constexpr bool Big = TransformedFeatureDimensions == TransformedFeatureDimensionsBig;
|
||||
|
||||
private:
|
||||
using Tiling = SIMDTiling<TransformedFeatureDimensions, HalfDimensions>;
|
||||
@@ -468,38 +474,20 @@ class FeatureTransformer {
|
||||
return psqt;
|
||||
} // end of function transform()
|
||||
|
||||
void hint_common_access(const Position& pos,
|
||||
AccumulatorCaches::Cache<HalfDimensions>* cache) const {
|
||||
update_accumulator<WHITE>(pos, cache);
|
||||
update_accumulator<BLACK>(pos, cache);
|
||||
}
|
||||
|
||||
private:
|
||||
template<Color Perspective>
|
||||
StateInfo* try_find_computed_accumulator(const Position& pos) const {
|
||||
// Look for a usable accumulator of an earlier position. We keep track
|
||||
// of the estimated gain in terms of features to be added/subtracted.
|
||||
StateInfo* st = pos.state();
|
||||
int gain = FeatureSet::refresh_cost(pos);
|
||||
while (st->previous && !(st->*accPtr).computed[Perspective])
|
||||
{
|
||||
// This governs when a full feature refresh is needed and how many
|
||||
// updates are better than just one full refresh.
|
||||
if (FeatureSet::requires_refresh(st, Perspective)
|
||||
|| (gain -= FeatureSet::update_cost(st) + 1) < 0)
|
||||
break;
|
||||
st = st->previous;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
// Given a computed accumulator, computes the accumulator of the next position.
|
||||
template<Color Perspective>
|
||||
void update_accumulator_incremental(const Position& pos, StateInfo* computed) const {
|
||||
// Given a computed accumulator, computes the accumulator of another position.
|
||||
template<Color Perspective, IncUpdateDirection Direction = FORWARD>
|
||||
void update_accumulator_incremental(const Square ksq,
|
||||
StateInfo* target_state,
|
||||
const StateInfo* computed) const {
|
||||
[[maybe_unused]] constexpr bool Forward = Direction == FORWARD;
|
||||
[[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS;
|
||||
assert((computed->*accPtr).computed[Perspective]);
|
||||
assert(computed->next != nullptr);
|
||||
|
||||
const Square ksq = pos.square<KING>(Perspective);
|
||||
StateInfo* next = Forward ? computed->next : computed->previous;
|
||||
|
||||
assert(next != nullptr);
|
||||
assert(!(next->*accPtr).computed[Perspective]);
|
||||
|
||||
// The size must be enough to contain the largest possible update.
|
||||
// That might depend on the feature set and generally relies on the
|
||||
@@ -508,11 +496,11 @@ class FeatureTransformer {
|
||||
// In this case, the maximum size of both feature addition and removal
|
||||
// is 2, since we are incrementally updating one move at a time.
|
||||
FeatureSet::IndexList removed, added;
|
||||
FeatureSet::append_changed_indices<Perspective>(ksq, computed->next->dirtyPiece, removed,
|
||||
added);
|
||||
|
||||
StateInfo* next = computed->next;
|
||||
assert(!(next->*accPtr).computed[Perspective]);
|
||||
if constexpr (Forward)
|
||||
FeatureSet::append_changed_indices<Perspective>(ksq, next->dirtyPiece, removed, added);
|
||||
else
|
||||
FeatureSet::append_changed_indices<Perspective>(ksq, computed->dirtyPiece, added,
|
||||
removed);
|
||||
|
||||
if (removed.size() == 0 && added.size() == 0)
|
||||
{
|
||||
@@ -527,7 +515,10 @@ class FeatureTransformer {
|
||||
{
|
||||
assert(added.size() == 1 || added.size() == 2);
|
||||
assert(removed.size() == 1 || removed.size() == 2);
|
||||
assert(added.size() <= removed.size());
|
||||
if (Forward)
|
||||
assert(added.size() <= removed.size());
|
||||
else
|
||||
assert(removed.size() <= added.size());
|
||||
|
||||
#ifdef VECTOR
|
||||
auto* accIn =
|
||||
@@ -539,13 +530,15 @@ class FeatureTransformer {
|
||||
const IndexType offsetR0 = HalfDimensions * removed[0];
|
||||
auto* columnR0 = reinterpret_cast<const vec_t*>(&weights[offsetR0]);
|
||||
|
||||
if (removed.size() == 1)
|
||||
if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1))
|
||||
{
|
||||
assert(added.size() == 1 && removed.size() == 1);
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i)
|
||||
accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]);
|
||||
}
|
||||
else if (added.size() == 1)
|
||||
else if (Forward && added.size() == 1)
|
||||
{
|
||||
assert(removed.size() == 2);
|
||||
const IndexType offsetR1 = HalfDimensions * removed[1];
|
||||
auto* columnR1 = reinterpret_cast<const vec_t*>(&weights[offsetR1]);
|
||||
|
||||
@@ -553,8 +546,19 @@ class FeatureTransformer {
|
||||
accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]),
|
||||
vec_add_16(columnR0[i], columnR1[i]));
|
||||
}
|
||||
else if (Backwards && removed.size() == 1)
|
||||
{
|
||||
assert(added.size() == 2);
|
||||
const IndexType offsetA1 = HalfDimensions * added[1];
|
||||
auto* columnA1 = reinterpret_cast<const vec_t*>(&weights[offsetA1]);
|
||||
|
||||
for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i)
|
||||
accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]),
|
||||
vec_sub_16(columnA1[i], columnR0[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(added.size() == 2 && removed.size() == 2);
|
||||
const IndexType offsetA1 = HalfDimensions * added[1];
|
||||
auto* columnA1 = reinterpret_cast<const vec_t*>(&weights[offsetA1]);
|
||||
const IndexType offsetR1 = HalfDimensions * removed[1];
|
||||
@@ -576,14 +580,15 @@ class FeatureTransformer {
|
||||
const IndexType offsetPsqtR0 = PSQTBuckets * removed[0];
|
||||
auto* columnPsqtR0 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR0]);
|
||||
|
||||
if (removed.size() == 1)
|
||||
if ((Forward && removed.size() == 1)
|
||||
|| (Backwards && added.size() == 1)) // added.size() == removed.size() == 1
|
||||
{
|
||||
for (std::size_t i = 0;
|
||||
i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i)
|
||||
accPsqtOut[i] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]),
|
||||
columnPsqtA0[i]);
|
||||
}
|
||||
else if (added.size() == 1)
|
||||
else if (Forward && added.size() == 1)
|
||||
{
|
||||
const IndexType offsetPsqtR1 = PSQTBuckets * removed[1];
|
||||
auto* columnPsqtR1 =
|
||||
@@ -595,6 +600,18 @@ class FeatureTransformer {
|
||||
vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]),
|
||||
vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]));
|
||||
}
|
||||
else if (Backwards && removed.size() == 1)
|
||||
{
|
||||
const IndexType offsetPsqtA1 = PSQTBuckets * added[1];
|
||||
auto* columnPsqtA1 =
|
||||
reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtA1]);
|
||||
|
||||
for (std::size_t i = 0;
|
||||
i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i)
|
||||
accPsqtOut[i] =
|
||||
vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]),
|
||||
vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i]));
|
||||
}
|
||||
else
|
||||
{
|
||||
const IndexType offsetPsqtA1 = PSQTBuckets * added[1];
|
||||
@@ -647,8 +664,8 @@ class FeatureTransformer {
|
||||
|
||||
(next->*accPtr).computed[Perspective] = true;
|
||||
|
||||
if (next != pos.state())
|
||||
update_accumulator_incremental<Perspective>(pos, next);
|
||||
if (next != target_state)
|
||||
update_accumulator_incremental<Perspective, Direction>(ksq, target_state, next);
|
||||
}
|
||||
|
||||
|
||||
@@ -815,16 +832,40 @@ class FeatureTransformer {
|
||||
template<Color Perspective>
|
||||
void update_accumulator(const Position& pos,
|
||||
AccumulatorCaches::Cache<HalfDimensions>* cache) const {
|
||||
if ((pos.state()->*accPtr).computed[Perspective])
|
||||
return;
|
||||
StateInfo* oldest = try_find_computed_accumulator<Perspective>(pos);
|
||||
StateInfo* st = pos.state();
|
||||
if ((st->*accPtr).computed[Perspective])
|
||||
return; // nothing to do
|
||||
|
||||
if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state())
|
||||
// Start from the oldest computed accumulator, update all the
|
||||
// accumulators up to the current position.
|
||||
update_accumulator_incremental<Perspective>(pos, oldest);
|
||||
else
|
||||
update_accumulator_refresh_cache<Perspective>(pos, cache);
|
||||
[[maybe_unused]] // only used when !Big
|
||||
int gain = FeatureSet::refresh_cost(pos);
|
||||
// Look for a usable already computed accumulator of an earlier position.
|
||||
// When computing the small accumulator, we keep track of the estimated gain in
|
||||
// terms of features to be added/subtracted.
|
||||
// When computing the big accumulator, we expect to be able to reuse any
|
||||
// accumulators, so we always try to do an incremental update.
|
||||
do
|
||||
{
|
||||
if (FeatureSet::requires_refresh(st, Perspective)
|
||||
|| (!Big && (gain -= FeatureSet::update_cost(st) < 0)) || !st->previous
|
||||
|| st->previous->next != st)
|
||||
{
|
||||
// compute accumulator from scratch for this position
|
||||
update_accumulator_refresh_cache<Perspective>(pos, cache);
|
||||
if (Big && st != pos.state())
|
||||
// when computing a big accumulator from scratch we can use it to
|
||||
// efficiently compute the accumulator backwards, until we get to a king
|
||||
// move. We expect that we will need these accumulators later anyway, so
|
||||
// computing them now will save some work.
|
||||
update_accumulator_incremental<Perspective, BACKWARDS>(
|
||||
pos.square<KING>(Perspective), st, pos.state());
|
||||
return;
|
||||
}
|
||||
st = st->previous;
|
||||
} while (!(st->*accPtr).computed[Perspective]);
|
||||
|
||||
// Start from the oldest computed accumulator, update all the
|
||||
// accumulators up to the current position.
|
||||
update_accumulator_incremental<Perspective>(pos.square<KING>(Perspective), pos.state(), st);
|
||||
}
|
||||
|
||||
template<IndexType Size>
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include "../evaluate.h"
|
||||
#include "../position.h"
|
||||
#include "../types.h"
|
||||
#include "../uci.h"
|
||||
@@ -43,15 +42,6 @@ namespace Stockfish::Eval::NNUE {
|
||||
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
|
||||
|
||||
|
||||
void hint_common_parent_position(const Position& pos,
|
||||
const Networks& networks,
|
||||
AccumulatorCaches& caches) {
|
||||
if (Eval::use_smallnet(pos))
|
||||
networks.small.hint_common_access(pos, &caches.small);
|
||||
else
|
||||
networks.big.hint_common_access(pos, &caches.big);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Converts a Value into (centi)pawns and writes it in a buffer.
|
||||
// The buffer must have capacity for at least 5 chars.
|
||||
|
||||
@@ -54,9 +54,6 @@ struct Networks;
|
||||
struct AccumulatorCaches;
|
||||
|
||||
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
|
||||
|
||||
+1
-1
@@ -63,9 +63,9 @@ struct StateInfo {
|
||||
int repetition;
|
||||
|
||||
// Used by NNUE
|
||||
DirtyPiece dirtyPiece;
|
||||
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsBig> accumulatorBig;
|
||||
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsSmall> accumulatorSmall;
|
||||
DirtyPiece dirtyPiece;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
#include "nnue/network.h"
|
||||
#include "nnue/nnue_accumulator.h"
|
||||
#include "nnue/nnue_common.h"
|
||||
#include "nnue/nnue_misc.h"
|
||||
#include "position.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
#include "thread.h"
|
||||
@@ -759,7 +758,6 @@ Value Search::Worker::search(
|
||||
else if (excludedMove)
|
||||
{
|
||||
// Providing the hint that this node's accumulator will be used often
|
||||
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
|
||||
unadjustedStaticEval = eval = ss->staticEval;
|
||||
}
|
||||
else if (ss->ttHit)
|
||||
@@ -768,8 +766,6 @@ Value Search::Worker::search(
|
||||
unadjustedStaticEval = ttData.eval;
|
||||
if (!is_valid(unadjustedStaticEval))
|
||||
unadjustedStaticEval = evaluate(pos);
|
||||
else if (PvNode)
|
||||
Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable);
|
||||
|
||||
ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user