mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-23 13:17:12 +00:00
+1








8e5392d79a
This commit introduces Full Threat Input features, which are a subset of Piece(Square)-Piece(Square) pairs. In any given position, the active features consist of pairs where the second piece’s square lies in the attack set of the first piece. This is an extremely simplified explanation that leaves out many details. The already-used HalfKAv2_hm feature set completes the input features. Minor quantization changes have also been made. The net nn-49c1193b131c.nnue was trained by vondele using the following setup: https://github.com/vondele/nettest/blob/7de71238e9b295e3f88ed7c9c5936af632c9b981/threats.yaml A graphical version of an earlier scheme (with less refinement) that illustrates the core concepts can be found attached. [NewInputs.pdf](https://github.com/user-attachments/files/23478441/NewInputs.pdf) Further information, as well as a brief description of the history of development, can be found attached. [Stockfish threat inputs PR summary.pdf](https://github.com/user-attachments/files/23478634/Stockfish.threat.inputs.PR.summary.pdf) This has been a huge effort spanning over half a year, with the original [discussion thread](https://discord.com/channels/435943710472011776/1336647760388034610) reaching over 11k messages. Thanks to everyone who has contributed. Monty PRs: https://github.com/official-monty/Monty/pull/87 (Initial threat input PR) https://github.com/official-monty/Monty/pull/114 (Fixed threat indexing to take into account colour correctly) https://github.com/official-monty/Monty/pull/116 (i8 quantisation of weights whilst keeping calculations in i16) Yukari commit: https://github.com/yukarichess/yukari/commit/2d482c64a79cec03cf4987d5289334b9cdc737bc (Threat inputs merged) Plentychess PRs: https://github.com/Yoshie2000/PlentyChess/pull/400 (Threat inputs merged) https://github.com/Yoshie2000/PlentyChess/pull/411 (Threat input weights quantised to i8) Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 63424 W: 16956 L: 16591 D: 29877 Ptnml(0-2): 276, 7522, 15797, 7795, 322 https://tests.stockfishchess.org/tests/view/69105b3dec1d00d2c195c569 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 27876 W: 7417 L: 7110 D: 13349 Ptnml(0-2): 23, 3033, 7530, 3318, 34 https://tests.stockfishchess.org/tests/view/6910d817ec1d00d2c195c66e Passed VVLTC (Hash accidentally set to 1/2 normal value for both sides): LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 12458 W: 3353 L: 3102 D: 6003 Ptnml(0-2): 0, 1106, 3767, 1355, 1 https://tests.stockfishchess.org/tests/view/69115a26ec1d00d2c195c7cd This version has also passed non-regression LTC against the originally passed version: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 51144 W: 13086 L: 12903 D: 25155 Ptnml(0-2): 22, 5167, 15018, 5336, 29 https://tests.stockfishchess.org/tests/view/69138a317ca87818523314bf LTC elo estimate on ARM: 1 patch : 13.9 1.9 38296.5 73728 52 2 master : 0.0 ---- 35431.5 73728 48 closes https://github.com/official-stockfish/Stockfish/pull/6411 bench: 2626086 Co-authored-by: Shawn Xu <xu107288696@gmail.com> Co-authored-by: Timothy Herchen <timothy.herchen@gmail.com> Co-authored-by: Viren6 <94880762+Viren6@users.noreply.github.com> Co-authored-by: Yoshie2000 <patrick.leonhardt@gmx.net> Co-authored-by: Joost Vandevondele <Joost.VandeVondele@gmail.com> Co-authored-by: rn5f107s2 <clemens.lerchl@gmail.com> Co-authored-by: cj5716 <125858804+cj5716@users.noreply.github.com> Co-authored-by: AliceRoselia <63040919+AliceRoselia@users.noreply.github.com> Co-authored-by: Linmiao Xu <linmiao.xu@gmail.com> Co-authored-by: Disservin <disservin.social@gmail.com>
895 lines
37 KiB
C++
895 lines
37 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
|
|
|
Stockfish is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Stockfish is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "nnue_accumulator.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <type_traits>
|
|
|
|
#include "../bitboard.h"
|
|
#include "../misc.h"
|
|
#include "../position.h"
|
|
#include "../types.h"
|
|
#include "features/half_ka_v2_hm.h"
|
|
#include "nnue_architecture.h"
|
|
#include "nnue_common.h"
|
|
#include "nnue_feature_transformer.h" // IWYU pragma: keep
|
|
#include "simd.h"
|
|
|
|
namespace Stockfish::Eval::NNUE {
|
|
|
|
using namespace SIMD;
|
|
|
|
namespace {
|
|
|
|
template<Color Perspective, IndexType TransformedFeatureDimensions>
|
|
void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
|
|
const Square ksq,
|
|
AccumulatorState<PSQFeatureSet>& middle_state,
|
|
AccumulatorState<PSQFeatureSet>& target_state,
|
|
const AccumulatorState<PSQFeatureSet>& computed);
|
|
|
|
template<Color Perspective, IndexType TransformedFeatureDimensions>
|
|
void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
|
|
const Square ksq,
|
|
AccumulatorState<ThreatFeatureSet>& middle_state,
|
|
AccumulatorState<ThreatFeatureSet>& target_state,
|
|
const AccumulatorState<ThreatFeatureSet>& computed,
|
|
const DirtyPiece& dp2);
|
|
|
|
template<Color Perspective,
|
|
bool Forward,
|
|
typename FeatureSet,
|
|
IndexType TransformedFeatureDimensions>
|
|
void update_accumulator_incremental(
|
|
const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
|
|
const Square ksq,
|
|
AccumulatorState<FeatureSet>& target_state,
|
|
const AccumulatorState<FeatureSet>& computed);
|
|
|
|
template<Color Perspective, IndexType Dimensions>
|
|
void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& featureTransformer,
|
|
const Position& pos,
|
|
AccumulatorState<PSQFeatureSet>& accumulatorState,
|
|
AccumulatorCaches::Cache<Dimensions>& cache);
|
|
|
|
template<Color Perspective, IndexType Dimensions>
|
|
void update_threats_accumulator_full(const FeatureTransformer<Dimensions>& featureTransformer,
|
|
const Position& pos,
|
|
AccumulatorState<ThreatFeatureSet>& accumulatorState);
|
|
}
|
|
|
|
template<typename T>
|
|
const AccumulatorState<T>& AccumulatorStack::latest() const noexcept {
|
|
return accumulators<T>()[size - 1];
|
|
}
|
|
|
|
// Explicit template instantiations
|
|
template const AccumulatorState<PSQFeatureSet>& AccumulatorStack::latest() const noexcept;
|
|
template const AccumulatorState<ThreatFeatureSet>& AccumulatorStack::latest() const noexcept;
|
|
|
|
template<typename T>
|
|
AccumulatorState<T>& AccumulatorStack::mut_latest() noexcept {
|
|
return mut_accumulators<T>()[size - 1];
|
|
}
|
|
|
|
template<typename T>
|
|
const std::array<AccumulatorState<T>, AccumulatorStack::MaxSize>&
|
|
AccumulatorStack::accumulators() const noexcept {
|
|
static_assert(std::is_same_v<T, PSQFeatureSet> || std::is_same_v<T, ThreatFeatureSet>,
|
|
"Invalid Feature Set Type");
|
|
|
|
if constexpr (std::is_same_v<T, PSQFeatureSet>)
|
|
return psq_accumulators;
|
|
|
|
if constexpr (std::is_same_v<T, ThreatFeatureSet>)
|
|
return threat_accumulators;
|
|
}
|
|
|
|
template<typename T>
|
|
std::array<AccumulatorState<T>, AccumulatorStack::MaxSize>&
|
|
AccumulatorStack::mut_accumulators() noexcept {
|
|
static_assert(std::is_same_v<T, PSQFeatureSet> || std::is_same_v<T, ThreatFeatureSet>,
|
|
"Invalid Feature Set Type");
|
|
|
|
if constexpr (std::is_same_v<T, PSQFeatureSet>)
|
|
return psq_accumulators;
|
|
|
|
if constexpr (std::is_same_v<T, ThreatFeatureSet>)
|
|
return threat_accumulators;
|
|
}
|
|
|
|
void AccumulatorStack::reset() noexcept {
|
|
psq_accumulators[0].reset({});
|
|
threat_accumulators[0].reset({});
|
|
size = 1;
|
|
}
|
|
|
|
void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept {
|
|
assert(size < MaxSize);
|
|
psq_accumulators[size].reset(dirtyBoardData.dp);
|
|
threat_accumulators[size].reset(dirtyBoardData.dts);
|
|
size++;
|
|
}
|
|
|
|
void AccumulatorStack::pop() noexcept {
|
|
assert(size > 1);
|
|
size--;
|
|
}
|
|
|
|
template<IndexType Dimensions>
|
|
void AccumulatorStack::evaluate(const Position& pos,
|
|
const FeatureTransformer<Dimensions>& featureTransformer,
|
|
AccumulatorCaches::Cache<Dimensions>& cache) noexcept {
|
|
constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig);
|
|
|
|
evaluate_side<WHITE, PSQFeatureSet>(pos, featureTransformer, cache);
|
|
|
|
if (UseThreats)
|
|
evaluate_side<WHITE, ThreatFeatureSet>(pos, featureTransformer, cache);
|
|
|
|
evaluate_side<BLACK, PSQFeatureSet>(pos, featureTransformer, cache);
|
|
|
|
if (UseThreats)
|
|
evaluate_side<BLACK, ThreatFeatureSet>(pos, featureTransformer, cache);
|
|
}
|
|
|
|
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
|
|
void AccumulatorStack::evaluate_side(const Position& pos,
|
|
const FeatureTransformer<Dimensions>& featureTransformer,
|
|
AccumulatorCaches::Cache<Dimensions>& cache) noexcept {
|
|
|
|
const auto last_usable_accum =
|
|
find_last_usable_accumulator<Perspective, FeatureSet, Dimensions>();
|
|
|
|
if ((accumulators<FeatureSet>()[last_usable_accum].template acc<Dimensions>())
|
|
.computed[Perspective])
|
|
forward_update_incremental<Perspective, FeatureSet>(pos, featureTransformer,
|
|
last_usable_accum);
|
|
|
|
else
|
|
{
|
|
if constexpr (std::is_same_v<FeatureSet, PSQFeatureSet>)
|
|
update_accumulator_refresh_cache<Perspective>(featureTransformer, pos,
|
|
mut_latest<PSQFeatureSet>(), cache);
|
|
else
|
|
update_threats_accumulator_full<Perspective>(featureTransformer, pos,
|
|
mut_latest<ThreatFeatureSet>());
|
|
|
|
backward_update_incremental<Perspective, FeatureSet>(pos, featureTransformer,
|
|
last_usable_accum);
|
|
}
|
|
}
|
|
|
|
// Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator
|
|
// state just before a change that requires full refresh.
|
|
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
|
|
std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept {
|
|
|
|
for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--)
|
|
{
|
|
if ((accumulators<FeatureSet>()[curr_idx].template acc<Dimensions>()).computed[Perspective])
|
|
return curr_idx;
|
|
|
|
if (FeatureSet::requires_refresh(accumulators<FeatureSet>()[curr_idx].diff, Perspective))
|
|
return curr_idx;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
|
|
void AccumulatorStack::forward_update_incremental(
|
|
const Position& pos,
|
|
const FeatureTransformer<Dimensions>& featureTransformer,
|
|
const std::size_t begin) noexcept {
|
|
|
|
assert(begin < accumulators<FeatureSet>().size());
|
|
assert((accumulators<FeatureSet>()[begin].template acc<Dimensions>()).computed[Perspective]);
|
|
|
|
const Square ksq = pos.square<KING>(Perspective);
|
|
|
|
for (std::size_t next = begin + 1; next < size; next++)
|
|
{
|
|
if (next + 1 < size)
|
|
{
|
|
DirtyPiece& dp1 = mut_accumulators<PSQFeatureSet>()[next].diff;
|
|
DirtyPiece& dp2 = mut_accumulators<PSQFeatureSet>()[next + 1].diff;
|
|
|
|
auto& accumulators = mut_accumulators<FeatureSet>();
|
|
|
|
if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)
|
|
{
|
|
if (dp2.remove_sq != SQ_NONE
|
|
&& (accumulators[next].diff.threateningSqs & square_bb(dp2.remove_sq)))
|
|
{
|
|
double_inc_update<Perspective>(featureTransformer, ksq, accumulators[next],
|
|
accumulators[next + 1], accumulators[next - 1],
|
|
dp2);
|
|
next++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if constexpr (std::is_same_v<FeatureSet, PSQFeatureSet>)
|
|
{
|
|
if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq)
|
|
{
|
|
const Square captureSq = dp1.to;
|
|
dp1.to = dp2.remove_sq = SQ_NONE;
|
|
double_inc_update<Perspective>(featureTransformer, ksq, accumulators[next],
|
|
accumulators[next + 1], accumulators[next - 1]);
|
|
dp1.to = dp2.remove_sq = captureSq;
|
|
next++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
update_accumulator_incremental<Perspective, true>(featureTransformer, ksq,
|
|
mut_accumulators<FeatureSet>()[next],
|
|
accumulators<FeatureSet>()[next - 1]);
|
|
}
|
|
|
|
assert((latest<PSQFeatureSet>().acc<Dimensions>()).computed[Perspective]);
|
|
}
|
|
|
|
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
|
|
void AccumulatorStack::backward_update_incremental(
|
|
const Position& pos,
|
|
const FeatureTransformer<Dimensions>& featureTransformer,
|
|
const std::size_t end) noexcept {
|
|
|
|
assert(end < accumulators<FeatureSet>().size());
|
|
assert(end < size);
|
|
assert((latest<FeatureSet>().template acc<Dimensions>()).computed[Perspective]);
|
|
|
|
const Square ksq = pos.square<KING>(Perspective);
|
|
|
|
for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--)
|
|
update_accumulator_incremental<Perspective, false>(featureTransformer, ksq,
|
|
mut_accumulators<FeatureSet>()[next],
|
|
accumulators<FeatureSet>()[next + 1]);
|
|
|
|
assert((accumulators<FeatureSet>()[end].template acc<Dimensions>()).computed[Perspective]);
|
|
}
|
|
|
|
// Explicit template instantiations
|
|
template void AccumulatorStack::evaluate<TransformedFeatureDimensionsBig>(
|
|
const Position& pos,
|
|
const FeatureTransformer<TransformedFeatureDimensionsBig>& featureTransformer,
|
|
AccumulatorCaches::Cache<TransformedFeatureDimensionsBig>& cache) noexcept;
|
|
template void AccumulatorStack::evaluate<TransformedFeatureDimensionsSmall>(
|
|
const Position& pos,
|
|
const FeatureTransformer<TransformedFeatureDimensionsSmall>& featureTransformer,
|
|
AccumulatorCaches::Cache<TransformedFeatureDimensionsSmall>& cache) noexcept;
|
|
|
|
|
|
namespace {
|
|
|
|
template<typename VectorWrapper,
|
|
IndexType Width,
|
|
UpdateOperation... ops,
|
|
typename ElementType,
|
|
typename... Ts,
|
|
std::enable_if_t<is_all_same_v<ElementType, Ts...>, bool> = true>
|
|
void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const... rows) {
|
|
constexpr IndexType size = Width * sizeof(ElementType) / sizeof(typename VectorWrapper::type);
|
|
|
|
auto* vecIn = reinterpret_cast<const typename VectorWrapper::type*>(in);
|
|
auto* vecOut = reinterpret_cast<typename VectorWrapper::type*>(out);
|
|
|
|
for (IndexType i = 0; i < size; ++i)
|
|
vecOut[i] = fused<VectorWrapper, ops...>(
|
|
vecIn[i], reinterpret_cast<const typename VectorWrapper::type*>(rows)[i]...);
|
|
}
|
|
|
|
template<typename FeatureSet, Color Perspective, IndexType Dimensions>
|
|
struct AccumulatorUpdateContext {
|
|
const FeatureTransformer<Dimensions>& featureTransformer;
|
|
const AccumulatorState<FeatureSet>& from;
|
|
AccumulatorState<FeatureSet>& to;
|
|
|
|
AccumulatorUpdateContext(const FeatureTransformer<Dimensions>& ft,
|
|
const AccumulatorState<FeatureSet>& accF,
|
|
AccumulatorState<FeatureSet>& accT) noexcept :
|
|
featureTransformer{ft},
|
|
from{accF},
|
|
to{accT} {}
|
|
|
|
template<UpdateOperation... ops,
|
|
typename... Ts,
|
|
std::enable_if_t<is_all_same_v<IndexType, Ts...>, bool> = true>
|
|
void apply(const Ts... indices) {
|
|
auto to_weight_vector = [&](const IndexType index) {
|
|
return &featureTransformer.weights[index * Dimensions];
|
|
};
|
|
|
|
auto to_psqt_weight_vector = [&](const IndexType index) {
|
|
return &featureTransformer.psqtWeights[index * PSQTBuckets];
|
|
};
|
|
|
|
fused_row_reduce<Vec16Wrapper, Dimensions, ops...>(
|
|
(from.template acc<Dimensions>()).accumulation[Perspective],
|
|
(to.template acc<Dimensions>()).accumulation[Perspective], to_weight_vector(indices)...);
|
|
|
|
fused_row_reduce<Vec32Wrapper, PSQTBuckets, ops...>(
|
|
(from.template acc<Dimensions>()).psqtAccumulation[Perspective],
|
|
(to.template acc<Dimensions>()).psqtAccumulation[Perspective],
|
|
to_psqt_weight_vector(indices)...);
|
|
}
|
|
|
|
void apply(typename FeatureSet::IndexList added, typename FeatureSet::IndexList removed) {
|
|
const auto fromAcc = from.template acc<Dimensions>().accumulation[Perspective];
|
|
const auto toAcc = to.template acc<Dimensions>().accumulation[Perspective];
|
|
|
|
const auto fromPsqtAcc = from.template acc<Dimensions>().psqtAccumulation[Perspective];
|
|
const auto toPsqtAcc = to.template acc<Dimensions>().psqtAccumulation[Perspective];
|
|
|
|
#ifdef VECTOR
|
|
using Tiling = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;
|
|
vec_t acc[Tiling::NumRegs];
|
|
psqt_vec_t psqt[Tiling::NumPsqtRegs];
|
|
|
|
for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j)
|
|
{
|
|
auto* fromTile = reinterpret_cast<const vec_t*>(&fromAcc[j * Tiling::TileHeight]);
|
|
auto* toTile = reinterpret_cast<vec_t*>(&toAcc[j * Tiling::TileHeight]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = fromTile[k];
|
|
|
|
for (IndexType i = 0; i < removed.size(); ++i)
|
|
{
|
|
IndexType index = removed[i];
|
|
const IndexType offset = Dimensions * index + j * Tiling::TileHeight;
|
|
auto* column =
|
|
reinterpret_cast<const vec_i8_t*>(&featureTransformer.threatWeights[offset]);
|
|
|
|
#ifdef USE_NEON
|
|
for (IndexType k = 0; k < Tiling::NumRegs; k += 2)
|
|
{
|
|
acc[k] = vec_sub_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2])));
|
|
acc[k + 1] = vec_sub_16(acc[k + 1], vmovl_high_s8(column[k / 2]));
|
|
}
|
|
#else
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = vec_sub_16(acc[k], vec_convert_8_16(column[k]));
|
|
#endif
|
|
}
|
|
|
|
for (IndexType i = 0; i < added.size(); ++i)
|
|
{
|
|
IndexType index = added[i];
|
|
const IndexType offset = Dimensions * index + j * Tiling::TileHeight;
|
|
auto* column =
|
|
reinterpret_cast<const vec_i8_t*>(&featureTransformer.threatWeights[offset]);
|
|
|
|
#ifdef USE_NEON
|
|
for (IndexType k = 0; k < Tiling::NumRegs; k += 2)
|
|
{
|
|
acc[k] = vec_add_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2])));
|
|
acc[k + 1] = vec_add_16(acc[k + 1], vmovl_high_s8(column[k / 2]));
|
|
}
|
|
#else
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = vec_add_16(acc[k], vec_convert_8_16(column[k]));
|
|
#endif
|
|
}
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; k++)
|
|
vec_store(&toTile[k], acc[k]);
|
|
}
|
|
|
|
for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j)
|
|
{
|
|
auto* fromTilePsqt =
|
|
reinterpret_cast<const psqt_vec_t*>(&fromPsqtAcc[j * Tiling::PsqtTileHeight]);
|
|
auto* toTilePsqt =
|
|
reinterpret_cast<psqt_vec_t*>(&toPsqtAcc[j * Tiling::PsqtTileHeight]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = fromTilePsqt[k];
|
|
|
|
for (IndexType i = 0; i < removed.size(); ++i)
|
|
{
|
|
IndexType index = removed[i];
|
|
const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;
|
|
auto* columnPsqt = reinterpret_cast<const psqt_vec_t*>(
|
|
&featureTransformer.threatPsqtWeights[offset]);
|
|
|
|
for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);
|
|
}
|
|
|
|
for (IndexType i = 0; i < added.size(); ++i)
|
|
{
|
|
IndexType index = added[i];
|
|
const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;
|
|
auto* columnPsqt = reinterpret_cast<const psqt_vec_t*>(
|
|
&featureTransformer.threatPsqtWeights[offset]);
|
|
|
|
for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
|
|
}
|
|
|
|
for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
vec_store_psqt(&toTilePsqt[k], psqt[k]);
|
|
}
|
|
|
|
#else
|
|
|
|
std::copy_n(fromAcc, Dimensions, toAcc);
|
|
std::copy_n(fromPsqtAcc, PSQTBuckets, toPsqtAcc);
|
|
|
|
for (const auto index : removed)
|
|
{
|
|
const IndexType offset = Dimensions * index;
|
|
|
|
for (IndexType j = 0; j < Dimensions; ++j)
|
|
toAcc[j] -= featureTransformer.threatWeights[offset + j];
|
|
|
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
|
toPsqtAcc[k] -= featureTransformer.threatPsqtWeights[index * PSQTBuckets + k];
|
|
}
|
|
|
|
for (const auto index : added)
|
|
{
|
|
const IndexType offset = Dimensions * index;
|
|
|
|
for (IndexType j = 0; j < Dimensions; ++j)
|
|
toAcc[j] += featureTransformer.threatWeights[offset + j];
|
|
|
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
|
toPsqtAcc[k] += featureTransformer.threatPsqtWeights[index * PSQTBuckets + k];
|
|
}
|
|
|
|
#endif
|
|
}
|
|
};
|
|
|
|
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
|
|
auto make_accumulator_update_context(const FeatureTransformer<Dimensions>& featureTransformer,
|
|
const AccumulatorState<FeatureSet>& accumulatorFrom,
|
|
AccumulatorState<FeatureSet>& accumulatorTo) noexcept {
|
|
return AccumulatorUpdateContext<FeatureSet, Perspective, Dimensions>{
|
|
featureTransformer, accumulatorFrom, accumulatorTo};
|
|
}
|
|
|
|
template<Color Perspective, IndexType TransformedFeatureDimensions>
|
|
void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
|
|
const Square ksq,
|
|
AccumulatorState<PSQFeatureSet>& middle_state,
|
|
AccumulatorState<PSQFeatureSet>& target_state,
|
|
const AccumulatorState<PSQFeatureSet>& computed) {
|
|
|
|
assert(computed.acc<TransformedFeatureDimensions>().computed[Perspective]);
|
|
assert(!middle_state.acc<TransformedFeatureDimensions>().computed[Perspective]);
|
|
assert(!target_state.acc<TransformedFeatureDimensions>().computed[Perspective]);
|
|
|
|
PSQFeatureSet::IndexList removed, added;
|
|
PSQFeatureSet::append_changed_indices<Perspective>(ksq, middle_state.diff, removed, added);
|
|
// you can't capture a piece that was just involved in castling since the rook ends up
|
|
// in a square that the king passed
|
|
assert(added.size() < 2);
|
|
PSQFeatureSet::append_changed_indices<Perspective>(ksq, target_state.diff, removed, added);
|
|
|
|
assert(added.size() == 1);
|
|
assert(removed.size() == 2 || removed.size() == 3);
|
|
|
|
// Workaround compiler warning for uninitialized variables, replicated on
|
|
// profile builds on windows with gcc 14.2.0.
|
|
// TODO remove once unneeded
|
|
sf_assume(added.size() == 1);
|
|
sf_assume(removed.size() == 2 || removed.size() == 3);
|
|
|
|
auto updateContext =
|
|
make_accumulator_update_context<Perspective>(featureTransformer, computed, target_state);
|
|
|
|
if (removed.size() == 2)
|
|
{
|
|
updateContext.template apply<Add, Sub, Sub>(added[0], removed[0], removed[1]);
|
|
}
|
|
else
|
|
{
|
|
updateContext.template apply<Add, Sub, Sub, Sub>(added[0], removed[0], removed[1],
|
|
removed[2]);
|
|
}
|
|
|
|
target_state.acc<TransformedFeatureDimensions>().computed[Perspective] = true;
|
|
}
|
|
|
|
template<Color Perspective, IndexType TransformedFeatureDimensions>
|
|
void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
|
|
const Square ksq,
|
|
AccumulatorState<ThreatFeatureSet>& middle_state,
|
|
AccumulatorState<ThreatFeatureSet>& target_state,
|
|
const AccumulatorState<ThreatFeatureSet>& computed,
|
|
const DirtyPiece& dp2) {
|
|
|
|
assert(computed.acc<TransformedFeatureDimensions>().computed[Perspective]);
|
|
assert(!middle_state.acc<TransformedFeatureDimensions>().computed[Perspective]);
|
|
assert(!target_state.acc<TransformedFeatureDimensions>().computed[Perspective]);
|
|
|
|
ThreatFeatureSet::FusedUpdateData fusedData;
|
|
|
|
fusedData.dp2removed = dp2.remove_sq;
|
|
|
|
ThreatFeatureSet::IndexList removed, added;
|
|
ThreatFeatureSet::append_changed_indices<Perspective>(ksq, middle_state.diff, removed, added,
|
|
&fusedData, true);
|
|
ThreatFeatureSet::append_changed_indices<Perspective>(ksq, target_state.diff, removed, added,
|
|
&fusedData, false);
|
|
|
|
auto updateContext =
|
|
make_accumulator_update_context<Perspective>(featureTransformer, computed, target_state);
|
|
|
|
updateContext.apply(added, removed);
|
|
|
|
target_state.acc<TransformedFeatureDimensions>().computed[Perspective] = true;
|
|
}
|
|
|
|
template<Color Perspective,
|
|
bool Forward,
|
|
typename FeatureSet,
|
|
IndexType TransformedFeatureDimensions>
|
|
void update_accumulator_incremental(
|
|
const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
|
|
const Square ksq,
|
|
AccumulatorState<FeatureSet>& target_state,
|
|
const AccumulatorState<FeatureSet>& computed) {
|
|
|
|
assert((computed.template acc<TransformedFeatureDimensions>()).computed[Perspective]);
|
|
assert(!(target_state.template acc<TransformedFeatureDimensions>()).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
|
|
// feature set's update cost calculation to be correct and never allow
|
|
// updates with more added/removed features than MaxActiveDimensions.
|
|
// In this case, the maximum size of both feature addition and removal
|
|
// is 2, since we are incrementally updating one move at a time.
|
|
typename FeatureSet::IndexList removed, added;
|
|
if constexpr (Forward)
|
|
FeatureSet::template append_changed_indices<Perspective>(ksq, target_state.diff, removed,
|
|
added);
|
|
else
|
|
FeatureSet::template append_changed_indices<Perspective>(ksq, computed.diff, added,
|
|
removed);
|
|
|
|
auto updateContext =
|
|
make_accumulator_update_context<Perspective>(featureTransformer, computed, target_state);
|
|
|
|
if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)
|
|
updateContext.apply(added, removed);
|
|
else
|
|
{
|
|
assert(added.size() == 1 || added.size() == 2);
|
|
assert(removed.size() == 1 || removed.size() == 2);
|
|
assert((Forward && added.size() <= removed.size())
|
|
|| (!Forward && added.size() >= removed.size()));
|
|
|
|
// Workaround compiler warning for uninitialized variables, replicated
|
|
// on profile builds on windows with gcc 14.2.0.
|
|
// TODO remove once unneeded
|
|
sf_assume(added.size() == 1 || added.size() == 2);
|
|
sf_assume(removed.size() == 1 || removed.size() == 2);
|
|
|
|
if ((Forward && removed.size() == 1) || (!Forward && added.size() == 1))
|
|
{
|
|
assert(added.size() == 1 && removed.size() == 1);
|
|
updateContext.template apply<Add, Sub>(added[0], removed[0]);
|
|
}
|
|
else if (Forward && added.size() == 1)
|
|
{
|
|
assert(removed.size() == 2);
|
|
updateContext.template apply<Add, Sub, Sub>(added[0], removed[0], removed[1]);
|
|
}
|
|
else if (!Forward && removed.size() == 1)
|
|
{
|
|
assert(added.size() == 2);
|
|
updateContext.template apply<Add, Add, Sub>(added[0], added[1], removed[0]);
|
|
}
|
|
else
|
|
{
|
|
assert(added.size() == 2 && removed.size() == 2);
|
|
updateContext.template apply<Add, Add, Sub, Sub>(added[0], added[1], removed[0],
|
|
removed[1]);
|
|
}
|
|
}
|
|
|
|
(target_state.template acc<TransformedFeatureDimensions>()).computed[Perspective] = true;
|
|
}
|
|
|
|
Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) {
|
|
#if defined(USE_AVX512) || defined(USE_AVX2)
|
|
static_assert(sizeof(Piece) == 1);
|
|
Bitboard same_bb = 0;
|
|
for (int i = 0; i < 64; i += 32)
|
|
{
|
|
const __m256i old_v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(old + i));
|
|
const __m256i new_v = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(new_ + i));
|
|
const __m256i cmp_equal = _mm256_cmpeq_epi8(old_v, new_v);
|
|
const std::uint32_t equal_mask = _mm256_movemask_epi8(cmp_equal);
|
|
same_bb |= static_cast<Bitboard>(equal_mask) << i;
|
|
}
|
|
return ~same_bb;
|
|
#else
|
|
Bitboard changed = 0;
|
|
for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq)
|
|
{
|
|
changed |= static_cast<Bitboard>(old[sq] != new_[sq]) << sq;
|
|
}
|
|
return changed;
|
|
#endif
|
|
}
|
|
|
|
template<Color Perspective, IndexType Dimensions>
|
|
void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& featureTransformer,
|
|
const Position& pos,
|
|
AccumulatorState<PSQFeatureSet>& accumulatorState,
|
|
AccumulatorCaches::Cache<Dimensions>& cache) {
|
|
|
|
using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;
|
|
|
|
const Square ksq = pos.square<KING>(Perspective);
|
|
auto& entry = cache[ksq][Perspective];
|
|
PSQFeatureSet::IndexList removed, added;
|
|
|
|
const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array().data());
|
|
Bitboard removed_bb = changed_bb & entry.pieceBB;
|
|
Bitboard added_bb = changed_bb & pos.pieces();
|
|
|
|
while (removed_bb)
|
|
{
|
|
Square sq = pop_lsb(removed_bb);
|
|
removed.push_back(PSQFeatureSet::make_index<Perspective>(sq, entry.pieces[sq], ksq));
|
|
}
|
|
while (added_bb)
|
|
{
|
|
Square sq = pop_lsb(added_bb);
|
|
added.push_back(PSQFeatureSet::make_index<Perspective>(sq, pos.piece_on(sq), ksq));
|
|
}
|
|
|
|
entry.pieceBB = pos.pieces();
|
|
std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces);
|
|
|
|
auto& accumulator = accumulatorState.acc<Dimensions>();
|
|
accumulator.computed[Perspective] = true;
|
|
|
|
#ifdef VECTOR
|
|
vec_t acc[Tiling::NumRegs];
|
|
psqt_vec_t psqt[Tiling::NumPsqtRegs];
|
|
|
|
for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j)
|
|
{
|
|
auto* accTile =
|
|
reinterpret_cast<vec_t*>(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]);
|
|
auto* entryTile = reinterpret_cast<vec_t*>(&entry.accumulation[j * Tiling::TileHeight]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = entryTile[k];
|
|
|
|
IndexType i = 0;
|
|
for (; i < std::min(removed.size(), added.size()); ++i)
|
|
{
|
|
IndexType indexR = removed[i];
|
|
const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight;
|
|
auto* columnR = reinterpret_cast<const vec_t*>(&featureTransformer.weights[offsetR]);
|
|
IndexType indexA = added[i];
|
|
const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight;
|
|
auto* columnA = reinterpret_cast<const vec_t*>(&featureTransformer.weights[offsetA]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = fused<Vec16Wrapper, Add, Sub>(acc[k], columnA[k], columnR[k]);
|
|
}
|
|
for (; i < removed.size(); ++i)
|
|
{
|
|
IndexType index = removed[i];
|
|
const IndexType offset = Dimensions * index + j * Tiling::TileHeight;
|
|
auto* column = reinterpret_cast<const vec_t*>(&featureTransformer.weights[offset]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = vec_sub_16(acc[k], column[k]);
|
|
}
|
|
for (; i < added.size(); ++i)
|
|
{
|
|
IndexType index = added[i];
|
|
const IndexType offset = Dimensions * index + j * Tiling::TileHeight;
|
|
auto* column = reinterpret_cast<const vec_t*>(&featureTransformer.weights[offset]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = vec_add_16(acc[k], column[k]);
|
|
}
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; k++)
|
|
vec_store(&entryTile[k], acc[k]);
|
|
for (IndexType k = 0; k < Tiling::NumRegs; k++)
|
|
vec_store(&accTile[k], acc[k]);
|
|
}
|
|
|
|
for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j)
|
|
{
|
|
auto* accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
|
&accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]);
|
|
auto* entryTilePsqt =
|
|
reinterpret_cast<psqt_vec_t*>(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = entryTilePsqt[k];
|
|
|
|
for (IndexType i = 0; i < removed.size(); ++i)
|
|
{
|
|
IndexType index = removed[i];
|
|
const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;
|
|
auto* columnPsqt =
|
|
reinterpret_cast<const psqt_vec_t*>(&featureTransformer.psqtWeights[offset]);
|
|
|
|
for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]);
|
|
}
|
|
for (IndexType i = 0; i < added.size(); ++i)
|
|
{
|
|
IndexType index = added[i];
|
|
const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;
|
|
auto* columnPsqt =
|
|
reinterpret_cast<const psqt_vec_t*>(&featureTransformer.psqtWeights[offset]);
|
|
|
|
for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
|
|
}
|
|
|
|
for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
vec_store_psqt(&entryTilePsqt[k], psqt[k]);
|
|
for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
vec_store_psqt(&accTilePsqt[k], psqt[k]);
|
|
}
|
|
|
|
#else
|
|
|
|
for (const auto index : removed)
|
|
{
|
|
const IndexType offset = Dimensions * index;
|
|
for (IndexType j = 0; j < Dimensions; ++j)
|
|
entry.accumulation[j] -= featureTransformer.weights[offset + j];
|
|
|
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
|
entry.psqtAccumulation[k] -= featureTransformer.psqtWeights[index * PSQTBuckets + k];
|
|
}
|
|
for (const auto index : added)
|
|
{
|
|
const IndexType offset = Dimensions * index;
|
|
for (IndexType j = 0; j < Dimensions; ++j)
|
|
entry.accumulation[j] += featureTransformer.weights[offset + j];
|
|
|
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
|
entry.psqtAccumulation[k] += featureTransformer.psqtWeights[index * PSQTBuckets + k];
|
|
}
|
|
|
|
// The accumulator of the refresh entry has been updated.
|
|
// Now copy its content to the actual accumulator we were refreshing.
|
|
|
|
std::memcpy(accumulator.accumulation[Perspective], entry.accumulation.data(),
|
|
sizeof(BiasType) * Dimensions);
|
|
|
|
std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation.data(),
|
|
sizeof(int32_t) * PSQTBuckets);
|
|
#endif
|
|
}
|
|
|
|
template<Color Perspective, IndexType Dimensions>
|
|
void update_threats_accumulator_full(const FeatureTransformer<Dimensions>& featureTransformer,
|
|
const Position& pos,
|
|
AccumulatorState<ThreatFeatureSet>& accumulatorState) {
|
|
using Tiling [[maybe_unused]] = SIMDTiling<Dimensions, Dimensions, PSQTBuckets>;
|
|
|
|
ThreatFeatureSet::IndexList active;
|
|
ThreatFeatureSet::append_active_indices<Perspective>(pos, active);
|
|
|
|
auto& accumulator = accumulatorState.acc<Dimensions>();
|
|
accumulator.computed[Perspective] = true;
|
|
|
|
#ifdef VECTOR
|
|
vec_t acc[Tiling::NumRegs];
|
|
psqt_vec_t psqt[Tiling::NumPsqtRegs];
|
|
|
|
for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j)
|
|
{
|
|
auto* accTile =
|
|
reinterpret_cast<vec_t*>(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = vec_zero();
|
|
|
|
IndexType i = 0;
|
|
|
|
for (; i < active.size(); ++i)
|
|
{
|
|
IndexType index = active[i];
|
|
const IndexType offset = Dimensions * index + j * Tiling::TileHeight;
|
|
auto* column =
|
|
reinterpret_cast<const vec_i8_t*>(&featureTransformer.threatWeights[offset]);
|
|
|
|
#ifdef USE_NEON
|
|
for (IndexType k = 0; k < Tiling::NumRegs; k += 2)
|
|
{
|
|
acc[k] = vec_add_16(acc[k], vmovl_s8(vget_low_s8(column[k / 2])));
|
|
acc[k + 1] = vec_add_16(acc[k + 1], vmovl_high_s8(column[k / 2]));
|
|
}
|
|
#else
|
|
for (IndexType k = 0; k < Tiling::NumRegs; ++k)
|
|
acc[k] = vec_add_16(acc[k], vec_convert_8_16(column[k]));
|
|
#endif
|
|
}
|
|
|
|
for (IndexType k = 0; k < Tiling::NumRegs; k++)
|
|
vec_store(&accTile[k], acc[k]);
|
|
}
|
|
|
|
for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j)
|
|
{
|
|
auto* accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
|
&accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]);
|
|
|
|
for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = vec_zero_psqt();
|
|
|
|
for (IndexType i = 0; i < active.size(); ++i)
|
|
{
|
|
IndexType index = active[i];
|
|
const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight;
|
|
auto* columnPsqt =
|
|
reinterpret_cast<const psqt_vec_t*>(&featureTransformer.threatPsqtWeights[offset]);
|
|
|
|
for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
|
|
}
|
|
|
|
for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k)
|
|
vec_store_psqt(&accTilePsqt[k], psqt[k]);
|
|
}
|
|
|
|
#else
|
|
|
|
for (IndexType j = 0; j < Dimensions; ++j)
|
|
accumulator.accumulation[Perspective][j] = 0;
|
|
|
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
|
accumulator.psqtAccumulation[Perspective][k] = 0;
|
|
|
|
for (const auto index : active)
|
|
{
|
|
const IndexType offset = Dimensions * index;
|
|
|
|
for (IndexType j = 0; j < Dimensions; ++j)
|
|
accumulator.accumulation[Perspective][j] +=
|
|
featureTransformer.threatWeights[offset + j];
|
|
|
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
|
accumulator.psqtAccumulation[Perspective][k] +=
|
|
featureTransformer.threatPsqtWeights[index * PSQTBuckets + k];
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
}
|