Update NNUE architecture to SFNNv10 with Threat Inputs and net nn-49c1193b131c.nnue

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>
This commit is contained in:
sscg13
2025-11-12 10:49:39 +01:00
committed by Joost VandeVondele
co-authored by Shawn Xu Timothy Herchen Viren6 Yoshie2000 Joost Vandevondele rn5f107s2 cj5716 AliceRoselia Linmiao Xu Disservin
parent 69a01b88f3
commit 8e5392d79a
23 changed files with 1468 additions and 279 deletions
+1
View File
@@ -200,6 +200,7 @@ Panthee
Pascal Romaret
Pasquale Pigazzini (ppigazzini)
Patrick Jansen (mibere)
Patrick Leonhardt (Yoshie2000)
Peter Schneider (pschneider1968)
Peter Zsifkovits (CoffeeOne)
PikaCat
+7 -6
View File
@@ -55,15 +55,16 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench
SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
misc.cpp movegen.cpp movepick.cpp position.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \
nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/network.cpp \
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \
engine.cpp score.cpp memory.cpp
HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \
nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h \
nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h position.h \
search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \
nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \
nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \
nnue/nnue_architecture.h nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h \
position.h search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h
OBJS = $(notdir $(SRCS:.cpp=.o))
+4
View File
@@ -65,6 +65,10 @@ const std::vector<std::string> Defaults = {
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
// Positions with high numbers of changed threats
"k7/2n1n3/1nbNbn2/2NbRBn1/1nbRQR2/2NBRBN1/3N1N2/7K w - - 0 1",
"K7/8/8/BNQNQNB1/N5N1/R1Q1q2r/n5n1/bnqnqnbk w - - 0 1",
// 5-man positions
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate
"8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate
+3
View File
@@ -31,6 +31,7 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
alignas(64) Magic Magics[SQUARE_NB][2];
@@ -105,6 +106,8 @@ void Bitboards::init() {
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
BetweenBB[s1][s2] =
(attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
RayPassBB[s1][s2] =
attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2);
}
BetweenBB[s1][s2] |= s2;
}
+15
View File
@@ -61,6 +61,7 @@ extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
@@ -252,6 +253,20 @@ inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
}
}
inline Bitboard attacks_bb(Piece pc, Square s) {
if (type_of(pc) == PAWN)
return PseudoAttacks[color_of(pc)][s];
return PseudoAttacks[type_of(pc)][s];
}
inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) {
if (type_of(pc) == PAWN)
return PseudoAttacks[color_of(pc)][s];
return attacks_bb(type_of(pc), s, occupied);
}
// Counts the number of non-zero bits in a bitboard.
inline int popcount(Bitboard b) {
+1 -1
View File
@@ -33,7 +33,7 @@ namespace Eval {
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro or the location where this macro is defined, as it is used
// in the Makefile/Fishtest.
#define EvalFileDefaultNameBig "nn-1c0000000000.nnue"
#define EvalFileDefaultNameBig "nn-49c1193b131c.nnue"
#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue"
namespace NNUE {
+2 -1
View File
@@ -21,9 +21,9 @@
#include "bitboard.h"
#include "misc.h"
#include "nnue/features/full_threats.h"
#include "position.h"
#include "tune.h"
#include "types.h"
#include "uci.h"
using namespace Stockfish;
@@ -33,6 +33,7 @@ int main(int argc, char* argv[]) {
Bitboards::init();
Position::init();
Eval::NNUE::Features::init_threat_offsets();
auto uci = std::make_unique<UCIEngine>(argc, argv);
+7 -4
View File
@@ -134,10 +134,13 @@ class ValueList {
public:
std::size_t size() const { return size_; }
void push_back(const T& value) { values_[size_++] = value; }
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
const T& operator[](int index) const { return values_[index]; }
void push_back(const T& value) {
assert(size_ < MaxSize);
values_[size_++] = value;
}
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
const T& operator[](int index) const { return values_[index]; }
private:
T values_[MaxSize];
+311
View File
@@ -0,0 +1,311 @@
/*
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/>.
*/
//Definition of input features FullThreats of NNUE evaluation function
#include "full_threats.h"
#include <array>
#include <initializer_list>
#include "../../bitboard.h"
#include "../../misc.h"
#include "../../position.h"
#include "../../types.h"
#include "../nnue_common.h"
namespace Stockfish::Eval::NNUE::Features {
// Lookup array for indexing threats
IndexType offsets[PIECE_NB][SQUARE_NB + 2];
// Information on a particular pair of pieces and whether they should be excluded
struct PiecePairData {
// Layout: bits 8..31 are the index contribution of this piece pair, bits 0 and 1 are exclusion info
uint32_t data;
PiecePairData() {}
PiecePairData(bool excluded_pair, bool semi_excluded_pair, IndexType feature_index_base) {
data =
excluded_pair << 1 | (semi_excluded_pair && !excluded_pair) | feature_index_base << 8;
}
// lsb: excluded if from < to; 2nd lsb: always excluded
uint8_t excluded_pair_info() const { return (uint8_t) data; }
IndexType feature_index_base() const { return data >> 8; }
};
constexpr std::array<Piece, 12> AllPieces = {
W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
};
// The final index is calculated from summing data found in these two LUTs, as well
// as offsets[attacker][from]
PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked]
uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to]
static void init_index_luts() {
for (Piece attacker : AllPieces)
{
for (Piece attacked : AllPieces)
{
bool enemy = (attacker ^ attacked) == 8;
PieceType attackerType = type_of(attacker);
PieceType attackedType = type_of(attacked);
int map = FullThreats::map[attackerType - 1][attackedType - 1];
bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN);
IndexType feature = offsets[attacker][65]
+ (color_of(attacked) * (numValidTargets[attacker] / 2) + map)
* offsets[attacker][64];
bool excluded = map < 0;
index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature);
}
}
for (Piece attacker : AllPieces)
{
for (int from = 0; from < SQUARE_NB; ++from)
{
for (int to = 0; to < SQUARE_NB; ++to)
{
Bitboard attacks = attacks_bb(attacker, Square(from));
index_lut2[attacker][from][to] = popcount((square_bb(Square(to)) - 1) & attacks);
}
}
}
}
void init_threat_offsets() {
int cumulativeOffset = 0;
for (Piece piece : AllPieces)
{
int pieceIdx = piece;
int cumulativePieceOffset = 0;
for (Square from = SQ_A1; from <= SQ_H8; ++from)
{
offsets[pieceIdx][from] = cumulativePieceOffset;
if (type_of(piece) != PAWN)
{
Bitboard attacks = attacks_bb(piece, from, 0ULL);
cumulativePieceOffset += popcount(attacks);
}
else if (from >= SQ_A2 && from <= SQ_H7)
{
Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb<WHITE>(square_bb(from))
: pawn_attacks_bb<BLACK>(square_bb(from));
cumulativePieceOffset += popcount(attacks);
}
}
offsets[pieceIdx][64] = cumulativePieceOffset;
offsets[pieceIdx][65] = cumulativeOffset;
cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset;
}
init_index_luts();
}
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
IndexType
FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) {
from = (Square) (int(from) ^ OrientTBL[Perspective][ksq]);
to = (Square) (int(to) ^ OrientTBL[Perspective][ksq]);
if (Perspective == BLACK)
{
attacker = ~attacker;
attacked = ~attacked;
}
auto piecePairData = index_lut1[attacker][attacked];
// Some threats imply the existence of the corresponding ones in the opposite
// direction. We filter them here to ensure only one such threat is active.
// In the below addition, the 2nd lsb gets set iff either the pair is always excluded,
// or the pair is semi-excluded and from < to. By using an unsigned compare, the following
// sequence can use an add-with-carry instruction.
bool less_than = static_cast<uint8_t>(from) < static_cast<uint8_t>(to);
if ((piecePairData.excluded_pair_info() + less_than) & 2)
return Dimensions;
IndexType index =
piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to];
sf_assume(index != Dimensions);
return index;
}
// Get a list of indices for active features in ascending order
template<Color Perspective>
void FullThreats::append_active_indices(const Position& pos, IndexList& active) {
static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}};
Square ksq = pos.square<KING>(Perspective);
Bitboard occupied = pos.pieces();
for (Color color : {WHITE, BLACK})
{
for (PieceType pt = PAWN; pt <= KING; ++pt)
{
Color c = order[Perspective][color];
Piece attacker = make_piece(c, pt);
Bitboard bb = pos.pieces(c, pt);
if (pt == PAWN)
{
auto right = (c == WHITE) ? NORTH_EAST : SOUTH_WEST;
auto left = (c == WHITE) ? NORTH_WEST : SOUTH_EAST;
auto attacks_left =
((c == WHITE) ? shift<NORTH_EAST>(bb) : shift<SOUTH_WEST>(bb)) & occupied;
auto attacks_right =
((c == WHITE) ? shift<NORTH_WEST>(bb) : shift<SOUTH_EAST>(bb)) & occupied;
while (attacks_left)
{
Square to = pop_lsb(attacks_left);
Square from = to - right;
Piece attacked = pos.piece_on(to);
IndexType index = make_index<Perspective>(attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
while (attacks_right)
{
Square to = pop_lsb(attacks_right);
Square from = to - left;
Piece attacked = pos.piece_on(to);
IndexType index = make_index<Perspective>(attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
}
else
{
while (bb)
{
Square from = pop_lsb(bb);
Bitboard attacks = (attacks_bb(pt, from, occupied)) & occupied;
while (attacks)
{
Square to = pop_lsb(attacks);
Piece attacked = pos.piece_on(to);
IndexType index =
make_index<Perspective>(attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
}
}
}
}
}
// Explicit template instantiations
template void FullThreats::append_active_indices<WHITE>(const Position& pos, IndexList& active);
template void FullThreats::append_active_indices<BLACK>(const Position& pos, IndexList& active);
template IndexType
FullThreats::make_index<WHITE>(Piece attkr, Square from, Square to, Piece attkd, Square ksq);
template IndexType
FullThreats::make_index<BLACK>(Piece attkr, Square from, Square to, Piece attkd, Square ksq);
// Get a list of indices for recently changed features
template<Color Perspective>
void FullThreats::append_changed_indices(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fusedData,
bool first) {
for (const auto dirty : diff.list)
{
auto attacker = dirty.pc();
auto attacked = dirty.threatened_pc();
auto from = dirty.pc_sq();
auto to = dirty.threatened_sq();
auto add = dirty.add();
if (fusedData)
{
if (from == fusedData->dp2removed)
{
if (add)
{
if (first)
{
fusedData->dp2removedOriginBoard |= square_bb(to);
continue;
}
}
else if (fusedData->dp2removedOriginBoard & square_bb(to))
continue;
}
if (to != SQ_NONE && to == fusedData->dp2removed)
{
if (add)
{
if (first)
{
fusedData->dp2removedTargetBoard |= square_bb(from);
continue;
}
}
else if (fusedData->dp2removedTargetBoard & square_bb(from))
continue;
}
}
IndexType index = make_index<Perspective>(attacker, from, to, attacked, ksq);
if (index != Dimensions)
(add ? added : removed).push_back(index);
}
}
// Explicit template instantiations
template void FullThreats::append_changed_indices<WHITE>(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fd,
bool first);
template void FullThreats::append_changed_indices<BLACK>(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fd,
bool first);
bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) {
return perspective == diff.us
&& OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq];
}
} // namespace Stockfish::Eval::NNUE::Features
+115
View File
@@ -0,0 +1,115 @@
/*
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/>.
*/
//Definition of input features Simplified_Threats of NNUE evaluation function
#ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED
#define NNUE_FEATURES_FULL_THREATS_INCLUDED
#include <cstdint>
#include "../../misc.h"
#include "../../types.h"
#include "../nnue_common.h"
namespace Stockfish {
class Position;
}
namespace Stockfish::Eval::NNUE::Features {
static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0,
0, 6, 12, 10, 10, 12, 8, 0};
extern IndexType offsets[PIECE_NB][SQUARE_NB + 2];
void init_threat_offsets();
class FullThreats {
public:
// Feature name
static constexpr const char* Name = "Full_Threats(Friend)";
// Hash value embedded in the evaluation file
static constexpr std::uint32_t HashValue = 0x8f234cb8u;
// Number of feature dimensions
static constexpr IndexType Dimensions = 79856;
// clang-format off
// Orient a square according to perspective (rotates by 180 for black)
static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = {
{ SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1 },
{ SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8,
SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8,
SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8,
SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8,
SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8,
SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8,
SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8,
SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8 }
};
static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = {
{0, 1, -1, 2, -1, -1},
{0, 1, 2, 3, 4, 5},
{0, 1, 2, 3, -1, 4},
{0, 1, 2, 3, -1, 4},
{0, 1, 2, 3, 4, 5},
{0, 1, 2, 3, -1, -1}
};
// clang-format on
struct FusedUpdateData {
Bitboard dp2removedOriginBoard = 0;
Bitboard dp2removedTargetBoard = 0;
Square dp2removed;
};
// Maximum number of simultaneously active features.
static constexpr IndexType MaxActiveDimensions = 128;
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
using DiffType = DirtyThreats;
template<Color Perspective>
static IndexType make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq);
// Get a list of indices for active features
template<Color Perspective>
static void append_active_indices(const Position& pos, IndexList& active);
// Get a list of indices for recently changed features
template<Color Perspective>
static void append_changed_indices(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fd = nullptr,
bool first = false);
// Returns whether the change stored in this DirtyPiece means
// that a full accumulator refresh is required.
static bool requires_refresh(const DiffType& diff, Color perspective);
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED
+21 -21
View File
@@ -55,33 +55,33 @@ 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>
void HalfKAv2_hm::append_changed_indices(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added) {
removed.push_back(make_index<Perspective>(dp.from, dp.pc, ksq));
if (dp.to != SQ_NONE)
added.push_back(make_index<Perspective>(dp.to, dp.pc, ksq));
void HalfKAv2_hm::append_changed_indices(Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added) {
removed.push_back(make_index<Perspective>(diff.from, diff.pc, ksq));
if (diff.to != SQ_NONE)
added.push_back(make_index<Perspective>(diff.to, diff.pc, ksq));
if (dp.remove_sq != SQ_NONE)
removed.push_back(make_index<Perspective>(dp.remove_sq, dp.remove_pc, ksq));
if (diff.remove_sq != SQ_NONE)
removed.push_back(make_index<Perspective>(diff.remove_sq, diff.remove_pc, ksq));
if (dp.add_sq != SQ_NONE)
added.push_back(make_index<Perspective>(dp.add_sq, dp.add_pc, ksq));
if (diff.add_sq != SQ_NONE)
added.push_back(make_index<Perspective>(diff.add_sq, diff.add_pc, ksq));
}
// Explicit template instantiations
template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added);
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added);
template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq,
const DiffType& dp,
IndexList& removed,
IndexList& added);
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq,
const DiffType& dp,
IndexList& removed,
IndexList& added);
bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) {
return dirtyPiece.pc == make_piece(perspective, KING);
bool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) {
return diff.pc == make_piece(perspective, KING);
}
} // namespace Stockfish::Eval::NNUE::Features
+3 -2
View File
@@ -104,6 +104,7 @@ class HalfKAv2_hm {
// Maximum number of simultaneously active features.
static constexpr IndexType MaxActiveDimensions = 32;
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
using DiffType = DirtyPiece;
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
@@ -116,11 +117,11 @@ class HalfKAv2_hm {
// Get a list of indices for recently changed features
template<Color Perspective>
static void
append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
append_changed_indices(Square ksq, const DiffType& diff, IndexList& removed, IndexList& added);
// Returns whether the change stored in this DirtyPiece means
// that a full accumulator refresh is required.
static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective);
static bool requires_refresh(const DiffType& diff, Color perspective);
};
} // namespace Stockfish::Eval::NNUE::Features
+1 -1
View File
@@ -222,7 +222,7 @@ void Network<Arch, Transformer>::verify(std::string
{
size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks;
f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024))
+ "MiB, (" + std::to_string(featureTransformer.InputDimensions) + ", "
+ "MiB, (" + std::to_string(featureTransformer.TotalInputDimensions) + ", "
+ std::to_string(network[0].TransformedFeatureDimensions) + ", "
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
+ ", 1))");
+470 -119
View File
@@ -27,7 +27,9 @@
#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"
@@ -40,43 +42,90 @@ namespace {
template<Color Perspective, IndexType TransformedFeatureDimensions>
void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& featureTransformer,
const Square ksq,
AccumulatorState& middle_state,
AccumulatorState& target_state,
const AccumulatorState& computed);
AccumulatorState<PSQFeatureSet>& middle_state,
AccumulatorState<PSQFeatureSet>& target_state,
const AccumulatorState<PSQFeatureSet>& computed);
template<Color Perspective, bool Forward, IndexType TransformedFeatureDimensions>
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& target_state,
const AccumulatorState& computed);
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& accumulatorState,
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);
}
void AccumulatorState::reset(const DirtyPiece& dp) noexcept {
dirtyPiece = dp;
accumulatorBig.computed.fill(false);
accumulatorSmall.computed.fill(false);
template<typename T>
const AccumulatorState<T>& AccumulatorStack::latest() const noexcept {
return accumulators<T>()[size - 1];
}
const AccumulatorState& AccumulatorStack::latest() const noexcept { return accumulators[size - 1]; }
// Explicit template instantiations
template const AccumulatorState<PSQFeatureSet>& AccumulatorStack::latest() const noexcept;
template const AccumulatorState<ThreatFeatureSet>& AccumulatorStack::latest() const noexcept;
AccumulatorState& AccumulatorStack::mut_latest() noexcept { return accumulators[size - 1]; }
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 {
accumulators[0].reset({});
psq_accumulators[0].reset({});
threat_accumulators[0].reset({});
size = 1;
}
void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept {
assert(size < accumulators.size());
accumulators[size].reset(dirtyPiece);
void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept {
assert(size < MaxSize);
psq_accumulators[size].reset(dirtyBoardData.dp);
threat_accumulators[size].reset(dirtyBoardData.dts);
size++;
}
@@ -89,53 +138,71 @@ 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>(pos, featureTransformer, cache);
evaluate_side<BLACK>(pos, featureTransformer, cache);
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, IndexType Dimensions>
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, Dimensions>();
const auto last_usable_accum =
find_last_usable_accumulator<Perspective, FeatureSet, Dimensions>();
if ((accumulators[last_usable_accum].template acc<Dimensions>()).computed[Perspective])
forward_update_incremental<Perspective>(pos, featureTransformer, last_usable_accum);
if ((accumulators<FeatureSet>()[last_usable_accum].template acc<Dimensions>())
.computed[Perspective])
forward_update_incremental<Perspective, FeatureSet>(pos, featureTransformer,
last_usable_accum);
else
{
update_accumulator_refresh_cache<Perspective>(featureTransformer, pos, mut_latest(), cache);
backward_update_incremental<Perspective>(pos, featureTransformer, last_usable_accum);
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, IndexType Dimensions>
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[curr_idx].template acc<Dimensions>()).computed[Perspective])
if ((accumulators<FeatureSet>()[curr_idx].template acc<Dimensions>()).computed[Perspective])
return curr_idx;
if (FeatureSet::requires_refresh(accumulators[curr_idx].dirtyPiece, Perspective))
if (FeatureSet::requires_refresh(accumulators<FeatureSet>()[curr_idx].diff, Perspective))
return curr_idx;
}
return 0;
}
template<Color Perspective, IndexType Dimensions>
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.size());
assert((accumulators[begin].acc<Dimensions>()).computed[Perspective]);
assert(begin < accumulators<FeatureSet>().size());
assert((accumulators<FeatureSet>()[begin].template acc<Dimensions>()).computed[Perspective]);
const Square ksq = pos.square<KING>(Perspective);
@@ -143,45 +210,65 @@ void AccumulatorStack::forward_update_incremental(
{
if (next + 1 < size)
{
DirtyPiece& dp1 = accumulators[next].dirtyPiece;
DirtyPiece& dp2 = accumulators[next + 1].dirtyPiece;
DirtyPiece& dp1 = mut_accumulators<PSQFeatureSet>()[next].diff;
DirtyPiece& dp2 = mut_accumulators<PSQFeatureSet>()[next + 1].diff;
if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq)
auto& accumulators = mut_accumulators<FeatureSet>();
if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)
{
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;
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;
}
}
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, accumulators[next], accumulators[next - 1]);
update_accumulator_incremental<Perspective, true>(featureTransformer, ksq,
mut_accumulators<FeatureSet>()[next],
accumulators<FeatureSet>()[next - 1]);
}
assert((latest().acc<Dimensions>()).computed[Perspective]);
assert((latest<PSQFeatureSet>().acc<Dimensions>()).computed[Perspective]);
}
template<Color Perspective, IndexType Dimensions>
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.size());
assert(end < accumulators<FeatureSet>().size());
assert(end < size);
assert((latest().acc<Dimensions>()).computed[Perspective]);
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, accumulators[next], accumulators[next + 1]);
update_accumulator_incremental<Perspective, false>(featureTransformer, ksq,
mut_accumulators<FeatureSet>()[next],
accumulators<FeatureSet>()[next + 1]);
assert((accumulators[end].acc<Dimensions>()).computed[Perspective]);
assert((accumulators<FeatureSet>()[end].template acc<Dimensions>()).computed[Perspective]);
}
// Explicit template instantiations
@@ -214,15 +301,15 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const..
vecIn[i], reinterpret_cast<const typename VectorWrapper::type*>(rows)[i]...);
}
template<Color Perspective, IndexType Dimensions>
template<typename FeatureSet, Color Perspective, IndexType Dimensions>
struct AccumulatorUpdateContext {
const FeatureTransformer<Dimensions>& featureTransformer;
const AccumulatorState& from;
AccumulatorState& to;
const AccumulatorState<FeatureSet>& from;
AccumulatorState<FeatureSet>& to;
AccumulatorUpdateContext(const FeatureTransformer<Dimensions>& ft,
const AccumulatorState& accF,
AccumulatorState& accT) noexcept :
const AccumulatorState<FeatureSet>& accF,
AccumulatorState<FeatureSet>& accT) noexcept :
featureTransformer{ft},
from{accF},
to{accT} {}
@@ -240,40 +327,169 @@ struct AccumulatorUpdateContext {
};
fused_row_reduce<Vec16Wrapper, Dimensions, ops...>(
(from.acc<Dimensions>()).accumulation[Perspective],
(to.acc<Dimensions>()).accumulation[Perspective], to_weight_vector(indices)...);
(from.template acc<Dimensions>()).accumulation[Perspective],
(to.template acc<Dimensions>()).accumulation[Perspective], to_weight_vector(indices)...);
fused_row_reduce<Vec32Wrapper, PSQTBuckets, ops...>(
(from.acc<Dimensions>()).psqtAccumulation[Perspective],
(to.acc<Dimensions>()).psqtAccumulation[Perspective], to_psqt_weight_vector(indices)...);
(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, IndexType Dimensions>
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
auto make_accumulator_update_context(const FeatureTransformer<Dimensions>& featureTransformer,
const AccumulatorState& accumulatorFrom,
AccumulatorState& accumulatorTo) noexcept {
return AccumulatorUpdateContext<Perspective, Dimensions>{featureTransformer, accumulatorFrom,
accumulatorTo};
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& middle_state,
AccumulatorState& target_state,
const AccumulatorState& computed) {
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]);
FeatureSet::IndexList removed, added;
FeatureSet::append_changed_indices<Perspective>(ksq, middle_state.dirtyPiece, removed, added);
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);
FeatureSet::append_changed_indices<Perspective>(ksq, target_state.dirtyPiece, removed, added);
PSQFeatureSet::append_changed_indices<Perspective>(ksq, target_state.diff, removed, added);
assert(added.size() == 1);
assert(removed.size() == 2 || removed.size() == 3);
@@ -300,15 +516,48 @@ void double_inc_update(const FeatureTransformer<TransformedFeatureDimensions>& f
target_state.acc<TransformedFeatureDimensions>().computed[Perspective] = true;
}
template<Color Perspective, bool Forward, IndexType TransformedFeatureDimensions>
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& target_state,
const AccumulatorState& computed) {
AccumulatorState<FeatureSet>& target_state,
const AccumulatorState<FeatureSet>& computed) {
assert((computed.acc<TransformedFeatureDimensions>()).computed[Perspective]);
assert(!(target_state.acc<TransformedFeatureDimensions>()).computed[Perspective]);
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
@@ -316,50 +565,56 @@ void update_accumulator_incremental(
// 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.
FeatureSet::IndexList removed, added;
typename FeatureSet::IndexList removed, added;
if constexpr (Forward)
FeatureSet::append_changed_indices<Perspective>(ksq, target_state.dirtyPiece, removed,
added);
FeatureSet::template append_changed_indices<Perspective>(ksq, target_state.diff, removed,
added);
else
FeatureSet::append_changed_indices<Perspective>(ksq, computed.dirtyPiece, added, removed);
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);
FeatureSet::template append_changed_indices<Perspective>(ksq, computed.diff, added,
removed);
auto updateContext =
make_accumulator_update_context<Perspective>(featureTransformer, computed, target_state);
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]);
}
if constexpr (std::is_same_v<FeatureSet, ThreatFeatureSet>)
updateContext.apply(added, removed);
else
{
assert(added.size() == 2 && removed.size() == 2);
updateContext.template apply<Add, Add, Sub, Sub>(added[0], added[1], removed[0],
removed[1]);
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.acc<TransformedFeatureDimensions>()).computed[Perspective] = true;
(target_state.template acc<TransformedFeatureDimensions>()).computed[Perspective] = true;
}
Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) {
@@ -388,32 +643,32 @@ Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_
template<Color Perspective, IndexType Dimensions>
void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& featureTransformer,
const Position& pos,
AccumulatorState& accumulatorState,
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];
FeatureSet::IndexList removed, added;
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());
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(FeatureSet::make_index<Perspective>(sq, entry.pieces[sq], ksq));
removed.push_back(PSQFeatureSet::make_index<Perspective>(sq, entry.pieces[sq], ksq));
}
while (added_bb)
{
Square sq = pop_lsb(added_bb);
added.push_back(FeatureSet::make_index<Perspective>(sq, pos.piece_on(sq), ksq));
added.push_back(PSQFeatureSet::make_index<Perspective>(sq, pos.piece_on(sq), ksq));
}
entry.pieceBB = pos.pieces();
std::copy_n(pos.piece_array(), SQUARE_NB, entry.pieces);
std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces);
auto& accumulator = accumulatorState.acc<Dimensions>();
accumulator.computed[Perspective] = true;
@@ -530,14 +785,110 @@ void update_accumulator_refresh_cache(const FeatureTransformer<Dimensions>& feat
// 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,
std::memcpy(accumulator.accumulation[Perspective], entry.accumulation.data(),
sizeof(BiasType) * Dimensions);
std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation,
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
}
}
}
+33 -18
View File
@@ -68,16 +68,16 @@ struct AccumulatorCaches {
struct alignas(CacheLineSize) Cache {
struct alignas(CacheLineSize) Entry {
BiasType accumulation[Size];
PSQTWeightType psqtAccumulation[PSQTBuckets];
Piece pieces[SQUARE_NB];
Bitboard pieceBB;
std::array<BiasType, Size> accumulation;
std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation;
Piece pieces[SQUARE_NB];
Bitboard pieceBB;
// To initialize a refresh entry, we set all its bitboards empty,
// so we put the biases in the accumulation, without any weights on top
void clear(const BiasType* biases) {
void clear(const std::array<BiasType, Size>& biases) {
std::memcpy(accumulation, biases, sizeof(accumulation));
accumulation = biases;
std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0,
sizeof(Entry) - offsetof(Entry, psqtAccumulation));
}
@@ -106,10 +106,11 @@ struct AccumulatorCaches {
};
template<typename FeatureSet>
struct AccumulatorState {
Accumulator<TransformedFeatureDimensionsBig> accumulatorBig;
Accumulator<TransformedFeatureDimensionsSmall> accumulatorSmall;
DirtyPiece dirtyPiece;
typename FeatureSet::DiffType diff;
template<IndexType Size>
auto& acc() noexcept {
@@ -135,16 +136,22 @@ struct AccumulatorState {
return accumulatorSmall;
}
void reset(const DirtyPiece& dp) noexcept;
void reset(const typename FeatureSet::DiffType& dp) noexcept {
diff = dp;
accumulatorBig.computed.fill(false);
accumulatorSmall.computed.fill(false);
}
};
class AccumulatorStack {
public:
[[nodiscard]] const AccumulatorState& latest() const noexcept;
static constexpr std::size_t MaxSize = MAX_PLY + 1;
template<typename T>
[[nodiscard]] const AccumulatorState<T>& latest() const noexcept;
void reset() noexcept;
void push(const DirtyPiece& dirtyPiece) noexcept;
void push(const DirtyBoardData& dirtyBoardData) noexcept;
void pop() noexcept;
template<IndexType Dimensions>
@@ -153,28 +160,36 @@ class AccumulatorStack {
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
private:
[[nodiscard]] AccumulatorState& mut_latest() noexcept;
template<typename T>
[[nodiscard]] AccumulatorState<T>& mut_latest() noexcept;
template<Color Perspective, IndexType Dimensions>
template<typename T>
[[nodiscard]] const std::array<AccumulatorState<T>, MaxSize>& accumulators() const noexcept;
template<typename T>
[[nodiscard]] std::array<AccumulatorState<T>, MaxSize>& mut_accumulators() noexcept;
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
void evaluate_side(const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer,
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
template<Color Perspective, IndexType Dimensions>
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
[[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept;
template<Color Perspective, IndexType Dimensions>
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
void forward_update_incremental(const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer,
const std::size_t begin) noexcept;
template<Color Perspective, IndexType Dimensions>
template<Color Perspective, typename FeatureSet, IndexType Dimensions>
void backward_update_incremental(const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer,
const std::size_t end) noexcept;
std::array<AccumulatorState, MAX_PLY + 1> accumulators;
std::size_t size = 1;
std::array<AccumulatorState<PSQFeatureSet>, MaxSize> psq_accumulators;
std::array<AccumulatorState<ThreatFeatureSet>, MaxSize> threat_accumulators;
std::size_t size = 1;
};
} // namespace Stockfish::Eval::NNUE
+4 -2
View File
@@ -26,6 +26,7 @@
#include <iosfwd>
#include "features/half_ka_v2_hm.h"
#include "features/full_threats.h"
#include "layers/affine_transform.h"
#include "layers/affine_transform_sparse_input.h"
#include "layers/clipped_relu.h"
@@ -35,10 +36,11 @@
namespace Stockfish::Eval::NNUE {
// Input features used in evaluation function
using FeatureSet = Features::HalfKAv2_hm;
using ThreatFeatureSet = Features::FullThreats;
using PSQFeatureSet = Features::HalfKAv2_hm;
// Number of input feature dimensions after conversion
constexpr IndexType TransformedFeatureDimensionsBig = 3072;
constexpr IndexType TransformedFeatureDimensionsBig = 1024;
constexpr int L2Big = 15;
constexpr int L3Big = 32;
+12 -11
View File
@@ -48,10 +48,11 @@
namespace Stockfish::Eval::NNUE {
using BiasType = std::int16_t;
using WeightType = std::int16_t;
using PSQTWeightType = std::int32_t;
using IndexType = std::uint32_t;
using BiasType = std::int16_t;
using ThreatWeightType = std::int8_t;
using WeightType = std::int16_t;
using PSQTWeightType = std::int32_t;
using IndexType = std::uint32_t;
// Version of the evaluation file
constexpr std::uint32_t Version = 0x7AF32F20u;
@@ -172,8 +173,8 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std
// Read N signed integers from the stream s, putting them in the array out.
// The stream is assumed to be compressed using the signed LEB128 format.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename IntType>
inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
template<typename IntType, std::size_t Count>
inline void read_leb_128(std::istream& stream, std::array<IntType, Count>& out) {
// Check the presence of our LEB128 magic string
char leb128MagicString[Leb128MagicStringSize];
@@ -188,7 +189,7 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count)
auto bytes_left = read_little_endian<std::uint32_t>(stream);
std::uint32_t buf_pos = BUF_SIZE;
for (std::size_t i = 0; i < count; ++i)
for (std::size_t i = 0; i < Count; ++i)
{
IntType result = 0;
size_t shift = 0;
@@ -223,8 +224,8 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count)
// This takes N integers from array values, compresses them with
// the LEB128 algorithm and writes the result on the stream s.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename IntType>
inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
template<typename IntType, std::size_t Count>
inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>& values) {
// Write our LEB128 magic string
stream.write(Leb128MagicString, Leb128MagicStringSize);
@@ -232,7 +233,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
std::uint32_t byte_count = 0;
for (std::size_t i = 0; i < count; ++i)
for (std::size_t i = 0; i < Count; ++i)
{
IntType value = values[i];
std::uint8_t byte;
@@ -264,7 +265,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size
flush();
};
for (std::size_t i = 0; i < count; ++i)
for (std::size_t i = 0; i < Count; ++i)
{
IntType value = values[i];
while (true)
+176 -35
View File
@@ -25,6 +25,7 @@
#include <cstdint>
#include <cstring>
#include <iosfwd>
#include <iterator>
#include "../position.h"
#include "../types.h"
@@ -48,7 +49,7 @@ invert_permutation(const std::array<std::size_t, Len>& order) {
// Divide a byte region of size TotalSize to chunks of size
// BlockSize, and permute the blocks by a given order
template<std::size_t BlockSize, typename T, std::size_t N, std::size_t OrderSize>
void permute(T (&data)[N], const std::array<std::size_t, OrderSize>& order) {
void permute(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& order) {
constexpr std::size_t TotalSize = N * sizeof(T);
static_assert(TotalSize % (BlockSize * OrderSize) == 0,
@@ -58,7 +59,7 @@ void permute(T (&data)[N], const std::array<std::size_t, OrderSize>& order) {
std::array<std::byte, ProcessChunkSize> buffer{};
std::byte* const bytes = reinterpret_cast<std::byte*>(data);
std::byte* const bytes = reinterpret_cast<std::byte*>(data.data());
for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize)
{
@@ -79,7 +80,8 @@ void permute(T (&data)[N], const std::array<std::size_t, OrderSize>& order) {
// Input feature converter
template<IndexType TransformedFeatureDimensions>
class FeatureTransformer {
static constexpr bool UseThreats =
(TransformedFeatureDimensions == TransformedFeatureDimensionsBig);
// Number of output dimensions for one side
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
@@ -88,7 +90,10 @@ class FeatureTransformer {
using OutputType = TransformedFeatureType;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = FeatureSet::Dimensions;
static constexpr IndexType InputDimensions = PSQFeatureSet::Dimensions;
static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions;
static constexpr IndexType TotalInputDimensions =
InputDimensions + (UseThreats ? ThreatInputDimensions : 0);
static constexpr IndexType OutputDimensions = HalfDimensions;
// Size of forward propagation buffer
@@ -119,17 +124,24 @@ class FeatureTransformer {
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() {
return FeatureSet::HashValue ^ (OutputDimensions * 2);
return (UseThreats ? ThreatFeatureSet::HashValue : PSQFeatureSet::HashValue)
^ (OutputDimensions * 2);
}
void permute_weights() {
permute<16>(biases, PackusEpi16Order);
permute<16>(weights, PackusEpi16Order);
if (UseThreats)
permute<8>(threatWeights, PackusEpi16Order);
}
void unpermute_weights() {
permute<16>(biases, InversePackusEpi16Order);
permute<16>(weights, InversePackusEpi16Order);
if (UseThreats)
permute<8>(threatWeights, InversePackusEpi16Order);
}
inline void scale_weights(bool read) {
@@ -145,14 +157,51 @@ class FeatureTransformer {
}
// Read network parameters
// TODO: This is ugly. Currently LEB128 on the entire L1 necessitates
// reading the weights into a combined array, and then splitting.
bool read_parameters(std::istream& stream) {
read_leb_128<BiasType>(stream, biases);
read_leb_128<BiasType>(stream, biases, HalfDimensions);
read_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
read_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
if (UseThreats)
{
auto combinedWeights =
std::make_unique<std::array<WeightType, HalfDimensions * TotalInputDimensions>>();
auto combinedPsqtWeights =
std::make_unique<std::array<PSQTWeightType, TotalInputDimensions * PSQTBuckets>>();
read_leb_128<WeightType>(stream, *combinedWeights);
std::copy(combinedWeights->begin(),
combinedWeights->begin() + ThreatInputDimensions * HalfDimensions,
std::begin(threatWeights));
std::copy(combinedWeights->begin() + ThreatInputDimensions * HalfDimensions,
combinedWeights->begin()
+ (ThreatInputDimensions + InputDimensions) * HalfDimensions,
std::begin(weights));
read_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights);
std::copy(combinedPsqtWeights->begin(),
combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets,
std::begin(threatPsqtWeights));
std::copy(combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets,
combinedPsqtWeights->begin()
+ (ThreatInputDimensions + InputDimensions) * PSQTBuckets,
std::begin(psqtWeights));
}
else
{
read_leb_128<WeightType>(stream, weights);
read_leb_128<PSQTWeightType>(stream, psqtWeights);
}
permute_weights();
scale_weights(true);
if (!UseThreats)
scale_weights(true);
return !stream.fail();
}
@@ -161,11 +210,44 @@ class FeatureTransformer {
std::unique_ptr<FeatureTransformer> copy = std::make_unique<FeatureTransformer>(*this);
copy->unpermute_weights();
copy->scale_weights(false);
write_leb_128<BiasType>(stream, copy->biases, HalfDimensions);
write_leb_128<WeightType>(stream, copy->weights, HalfDimensions * InputDimensions);
write_leb_128<PSQTWeightType>(stream, copy->psqtWeights, PSQTBuckets * InputDimensions);
if (!UseThreats)
copy->scale_weights(false);
write_leb_128<BiasType>(stream, copy->biases);
if (UseThreats)
{
auto combinedWeights =
std::make_unique<std::array<WeightType, HalfDimensions * TotalInputDimensions>>();
auto combinedPsqtWeights =
std::make_unique<std::array<PSQTWeightType, TotalInputDimensions * PSQTBuckets>>();
std::copy(std::begin(copy->threatWeights),
std::begin(copy->threatWeights) + ThreatInputDimensions * HalfDimensions,
combinedWeights->begin());
std::copy(std::begin(copy->weights),
std::begin(copy->weights) + InputDimensions * HalfDimensions,
combinedWeights->begin() + ThreatInputDimensions * HalfDimensions);
write_leb_128<WeightType>(stream, *combinedWeights);
std::copy(std::begin(copy->threatPsqtWeights),
std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets,
combinedPsqtWeights->begin());
std::copy(std::begin(copy->psqtWeights),
std::begin(copy->psqtWeights) + InputDimensions * PSQTBuckets,
combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets);
write_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights);
}
else
{
write_leb_128<WeightType>(stream, copy->weights);
write_leb_128<PSQTWeightType>(stream, copy->psqtWeights);
}
return !stream.fail();
}
@@ -187,17 +269,29 @@ class FeatureTransformer {
int bucket) const {
using namespace SIMD;
accumulatorStack.evaluate(pos, *this, *cache);
const auto& accumulatorState = accumulatorStack.latest();
const auto& accumulatorState = accumulatorStack.latest<PSQFeatureSet>();
const auto& threatAccumulatorState = accumulatorStack.latest<ThreatFeatureSet>();
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
const auto& psqtAccumulation = (accumulatorState.acc<HalfDimensions>()).psqtAccumulation;
const auto psqt =
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket])
/ 2;
auto psqt =
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);
if (UseThreats)
{
const auto& threatPsqtAccumulation =
(threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation;
psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket]
- threatPsqtAccumulation[perspectives[1]][bucket])
/ 2;
}
else
psqt /= 2;
const auto& accumulation = (accumulatorState.acc<HalfDimensions>()).accumulation;
const auto& threatAccumulation =
(threatAccumulatorState.acc<HalfDimensions>()).accumulation;
for (IndexType p = 0; p < 2; ++p)
{
@@ -210,7 +304,7 @@ class FeatureTransformer {
constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;
const vec_t Zero = vec_zero();
const vec_t One = vec_set_16(127 * 2);
const vec_t One = vec_set_16(UseThreats ? 255 : 127 * 2);
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
const vec_t* in1 =
@@ -276,20 +370,48 @@ class FeatureTransformer {
#else
6;
#endif
for (IndexType j = 0; j < NumOutputChunks; ++j)
if (UseThreats)
{
const vec_t sum0a =
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift);
const vec_t sum0b =
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift);
const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One);
const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One);
const vec_t* tin0 =
reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0]));
const vec_t* tin1 = reinterpret_cast<const vec_t*>(
&(threatAccumulation[perspectives[p]][HalfDimensions / 2]));
for (IndexType j = 0; j < NumOutputChunks; ++j)
{
const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]);
const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]);
const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]);
const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]);
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
const vec_t sum0a =
vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift);
const vec_t sum0b =
vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift);
const vec_t sum1a = vec_min_16(acc1a, One);
const vec_t sum1b = vec_min_16(acc1b, One);
out[j] = vec_packus_16(pa, pb);
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
out[j] = vec_packus_16(pa, pb);
}
}
else
{
for (IndexType j = 0; j < NumOutputChunks; ++j)
{
const vec_t sum0a =
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift);
const vec_t sum0b =
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift);
const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One);
const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One);
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
out[j] = vec_packus_16(pa, pb);
}
}
#else
@@ -299,8 +421,21 @@ class FeatureTransformer {
BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
BiasType sum1 =
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
sum0 = std::clamp<BiasType>(sum0, 0, 127 * 2);
sum1 = std::clamp<BiasType>(sum1, 0, 127 * 2);
if (UseThreats)
{
BiasType sum0t = threatAccumulation[static_cast<int>(perspectives[p])][j + 0];
BiasType sum1t =
threatAccumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
sum0 = std::clamp<BiasType>(sum0 + sum0t, 0, 255);
sum1 = std::clamp<BiasType>(sum1 + sum1t, 0, 255);
}
else
{
sum0 = std::clamp<BiasType>(sum0, 0, 127 * 2);
sum1 = std::clamp<BiasType>(sum1, 0, 127 * 2);
}
output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 512);
}
@@ -310,9 +445,15 @@ class FeatureTransformer {
return psqt;
} // end of function transform()
alignas(CacheLineSize) BiasType biases[HalfDimensions];
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
alignas(CacheLineSize) std::array<BiasType, HalfDimensions> biases;
alignas(CacheLineSize) std::array<WeightType, HalfDimensions * InputDimensions> weights;
alignas(CacheLineSize)
std::array<ThreatWeightType,
UseThreats ? HalfDimensions * ThreatInputDimensions : 0> threatWeights;
alignas(CacheLineSize) std::array<PSQTWeightType, InputDimensions * PSQTBuckets> psqtWeights;
alignas(CacheLineSize)
std::array<PSQTWeightType,
UseThreats ? ThreatInputDimensions * PSQTBuckets : 0> threatPsqtWeights;
};
} // namespace Stockfish::Eval::NNUE
+30 -2
View File
@@ -47,11 +47,13 @@ namespace Stockfish::Eval::NNUE::SIMD {
#ifdef USE_AVX512
using vec_t = __m512i;
using vec_i8_t = __m256i;
using vec128_t = __m128i;
using psqt_vec_t = __m256i;
using vec_uint_t = __m512i;
#define vec_load(a) _mm512_load_si512(a)
#define vec_store(a, b) _mm512_store_si512(a, b)
#define vec_convert_8_16(a) _mm512_cvtepi8_epi16(a)
#define vec_add_16(a, b) _mm512_add_epi16(a, b)
#define vec_sub_16(a, b) _mm512_sub_epi16(a, b)
#define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b)
@@ -82,11 +84,13 @@ using vec_uint_t = __m512i;
#elif USE_AVX2
using vec_t = __m256i;
using vec_i8_t = __m128i;
using vec128_t = __m128i;
using psqt_vec_t = __m256i;
using vec_uint_t = __m256i;
#define vec_load(a) _mm256_load_si256(a)
#define vec_store(a, b) _mm256_store_si256(a, b)
#define vec_convert_8_16(a) _mm256_cvtepi8_epi16(a)
#define vec_add_16(a, b) _mm256_add_epi16(a, b)
#define vec_sub_16(a, b) _mm256_sub_epi16(a, b)
#define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b)
@@ -119,11 +123,12 @@ using vec_uint_t = __m256i;
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
#define vec128_add(a, b) _mm_add_epi16(a, b)
#define NumRegistersSIMD 16
#define NumRegistersSIMD 12
#define MaxChunkSize 32
#elif USE_SSE2
using vec_t = __m128i;
using vec_i8_t = std::uint64_t; // for the correct size -- will be loaded into an xmm reg
using vec128_t = __m128i;
using psqt_vec_t = __m128i;
using vec_uint_t = __m128i;
@@ -149,17 +154,35 @@ using vec_uint_t = __m128i;
_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
#endif
#ifdef __i386__
inline __m128i _mm_cvtsi64_si128(int64_t val) {
return _mm_loadl_epi64(reinterpret_cast<const __m128i*>(&val));
}
#endif
#ifdef USE_SSE41
#define vec_convert_8_16(a) _mm_cvtepi8_epi16(_mm_cvtsi64_si128(static_cast<int64_t>(a)))
#else
// Credit: Yoshie2000
inline __m128i vec_convert_8_16(uint64_t x) {
__m128i v8 = _mm_cvtsi64_si128(static_cast<int64_t>(x));
__m128i sign = _mm_cmpgt_epi8(_mm_setzero_si128(), v8);
return _mm_unpacklo_epi8(v8, sign);
}
#endif
#define vec128_zero _mm_setzero_si128()
#define vec128_set_16(a) _mm_set1_epi16(a)
#define vec128_load(a) _mm_load_si128(a)
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
#define vec128_add(a, b) _mm_add_epi16(a, b)
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
#define NumRegistersSIMD (Is64Bit ? 12 : 6)
#define MaxChunkSize 16
#elif USE_NEON
using vec_t = int16x8_t;
using vec_i8_t = int8x16_t;
using psqt_vec_t = int32x4_t;
using vec128_t = uint16x8_t;
using vec_uint_t = uint32x4_t;
@@ -191,6 +214,11 @@ static constexpr std::uint32_t Mask[4] = {1, 2, 4, 8};
#define NumRegistersSIMD 16
#define MaxChunkSize 16
#ifndef __aarch64__
// Single instruction doesn't exist on 32-bit ARM
inline int8x16_t vmovl_high_s8(int8x16_t val) { return vmovl_s8(vget_high_s8(val)); }
#endif
#else
#undef VECTOR
+137 -20
View File
@@ -48,6 +48,7 @@ Key psq[PIECE_NB][SQUARE_NB];
Key enpassant[FILE_NB];
Key castling[CASTLING_RIGHT_NB];
Key side, noPawns;
}
namespace {
@@ -687,10 +688,10 @@ bool Position::gives_check(Move m) const {
// moves should be filtered out before this function is called.
// If a pointer to the TT table is passed, the entry for the new position
// will be prefetched
DirtyPiece Position::do_move(Move m,
StateInfo& newSt,
bool givesCheck,
const TranspositionTable* tt = nullptr) {
DirtyBoardData Position::do_move(Move m,
StateInfo& newSt,
bool givesCheck,
const TranspositionTable* tt = nullptr) {
assert(m.is_ok());
assert(&newSt != st);
@@ -724,6 +725,10 @@ DirtyPiece Position::do_move(Move m,
dp.from = from;
dp.to = to;
dp.add_sq = SQ_NONE;
DirtyThreats dts;
dts.us = us;
dts.prevKsq = square<KING>(us);
dts.threatenedSqs = dts.threateningSqs = 0;
assert(color_of(pc) == us);
assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us));
@@ -735,7 +740,7 @@ DirtyPiece Position::do_move(Move m,
assert(captured == make_piece(us, ROOK));
Square rfrom, rto;
do_castling<true>(us, from, to, rfrom, rto, &dp);
do_castling<true>(us, from, to, rfrom, rto, &dts, &dp);
k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
@@ -758,6 +763,9 @@ DirtyPiece Position::do_move(Move m,
assert(relative_rank(us, to) == RANK_6);
assert(piece_on(to) == NO_PIECE);
assert(piece_on(capsq) == make_piece(them, PAWN));
// Update board and piece lists in ep case, normal captures are updated later
remove_piece(capsq, &dts);
}
st->pawnKey ^= Zobrist::psq[captured][capsq];
@@ -774,11 +782,9 @@ DirtyPiece Position::do_move(Move m,
dp.remove_pc = captured;
dp.remove_sq = capsq;
// Update board and piece lists
remove_piece(capsq);
k ^= Zobrist::psq[captured][capsq];
st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]];
st->materialKey ^=
Zobrist::psq[captured][8 + pieceCount[captured] - (m.type_of() != EN_PASSANT)];
// Reset rule 50 counter
st->rule50 = 0;
@@ -806,7 +812,15 @@ DirtyPiece Position::do_move(Move m,
// Move the piece. The tricky Chess960 castling is handled earlier
if (m.type_of() != CASTLING)
move_piece(from, to);
{
if (captured && m.type_of() != EN_PASSANT)
{
remove_piece(from, &dts);
swap_piece(to, pc, &dts);
}
else
move_piece(from, to, &dts);
}
// If the moving piece is a pawn do some special extra work
if (type_of(pc) == PAWN)
@@ -823,8 +837,7 @@ DirtyPiece Position::do_move(Move m,
assert(relative_rank(us, to) == RANK_8);
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
remove_piece(to);
put_piece(promotion, to);
swap_piece(to, promotion, &dts);
dp.add_pc = promotion;
dp.add_sq = to;
@@ -949,13 +962,16 @@ DirtyPiece Position::do_move(Move m,
}
}
dts.ksq = square<KING>(us);
assert(pos_is_ok());
assert(dp.pc != NO_PIECE);
assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE));
assert(dp.from != SQ_NONE);
assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING));
return dp;
return {dp, dts};
}
@@ -1021,12 +1037,113 @@ void Position::undo_move(Move m) {
assert(pos_is_ok());
}
template<bool PutPiece>
inline void add_dirty_threat(
DirtyThreats* const dts, Piece pc, Piece threatened, Square s, Square threatenedSq) {
if (PutPiece)
{
dts->threatenedSqs |= square_bb(threatenedSq);
dts->threateningSqs |= square_bb(s);
}
dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece});
}
template<bool PutPiece, bool ComputeRay>
void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) {
// Add newly threatened pieces
Bitboard occupied = pieces();
Bitboard rAttacks = attacks_bb<ROOK>(s, occupied);
Bitboard bAttacks = attacks_bb<BISHOP>(s, occupied);
Bitboard qAttacks = rAttacks | bAttacks;
Bitboard threatened;
switch (type_of(pc))
{
case PAWN :
threatened = PseudoAttacks[color_of(pc)][s];
break;
case BISHOP :
threatened = bAttacks;
break;
case ROOK :
threatened = rAttacks;
break;
case QUEEN :
threatened = qAttacks;
break;
default :
threatened = PseudoAttacks[type_of(pc)][s];
}
threatened &= occupied;
while (threatened)
{
Square threatened_sq = pop_lsb(threatened);
Piece threatened_pc = piece_on(threatened_sq);
assert(threatened_sq != s);
assert(threatened_pc);
add_dirty_threat<PutPiece>(dts, pc, threatened_pc, s, threatened_sq);
}
Bitboard sliders = (pieces(ROOK, QUEEN) & rAttacks) | (pieces(BISHOP, QUEEN) & bAttacks);
Bitboard incoming_threats = (attacks_bb<KNIGHT>(s, occupied) & pieces(KNIGHT))
| (attacks_bb<PAWN>(s, WHITE) & pieces(BLACK, PAWN))
| (attacks_bb<PAWN>(s, BLACK) & pieces(WHITE, PAWN))
| (attacks_bb<KING>(s, occupied) & pieces(KING));
while (sliders)
{
Square slider_sq = pop_lsb(sliders);
Piece slider = piece_on(slider_sq);
Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s];
threatened = ray & qAttacks & occupied;
assert(!more_than_one(threatened));
if (ComputeRay && threatened)
{
Square threatened_sq = lsb(threatened);
Piece threatened_pc = piece_on(threatened_sq);
add_dirty_threat<!PutPiece>(dts, slider, threatened_pc, slider_sq, threatened_sq);
}
add_dirty_threat<PutPiece>(dts, slider, pc, slider_sq, s);
}
// Add threats of sliders that were already threatening s,
// sliders are already handled in the loop above
while (incoming_threats)
{
Square src_sq = pop_lsb(incoming_threats);
Piece src_pc = piece_on(src_sq);
assert(src_sq != s);
assert(src_pc != NO_PIECE);
add_dirty_threat<PutPiece>(dts, src_pc, pc, src_sq, s);
}
}
// Helper used to do/undo a castling move. This is a bit
// tricky in Chess960 where from/to squares can overlap.
template<bool Do>
void Position::do_castling(
Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) {
void Position::do_castling(Color us,
Square from,
Square& to,
Square& rfrom,
Square& rto,
DirtyThreats* const dts,
DirtyPiece* const dp) {
bool kingSide = to > from;
rfrom = to; // Castling is encoded as "king captures friendly rook"
@@ -1044,12 +1161,12 @@ void Position::do_castling(
}
// Remove both pieces first since squares could overlap in Chess960
remove_piece(Do ? from : to);
remove_piece(Do ? rfrom : rto);
remove_piece(Do ? from : to, dts);
remove_piece(Do ? rfrom : rto, dts);
board[Do ? from : to] = board[Do ? rfrom : rto] =
NO_PIECE; // remove_piece does not do this for us
put_piece(make_piece(us, KING), Do ? to : from);
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
put_piece(make_piece(us, KING), Do ? to : from, dts);
put_piece(make_piece(us, ROOK), Do ? rto : rfrom, dts);
}
@@ -1349,7 +1466,7 @@ bool Position::pos_is_ok() const {
for (Piece pc : Pieces)
if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
|| pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
|| pieceCount[pc] != std::count(board.begin(), board.end(), pc))
assert(0 && "pos_is_ok: Pieces");
for (Color c : {WHITE, BLACK})
+60 -29
View File
@@ -19,6 +19,7 @@
#ifndef POSITION_H_INCLUDED
#define POSITION_H_INCLUDED
#include <array>
#include <cassert>
#include <deque>
#include <iosfwd>
@@ -91,11 +92,11 @@ class Position {
Bitboard pieces(PieceTypes... pts) const;
Bitboard pieces(Color c) const;
template<typename... PieceTypes>
Bitboard pieces(Color c, PieceTypes... pts) const;
Piece piece_on(Square s) const;
const Piece* piece_array() const;
Square ep_square() const;
bool empty(Square s) const;
Bitboard pieces(Color c, PieceTypes... pts) const;
Piece piece_on(Square s) const;
const std::array<Piece, SQUARE_NB>& piece_array() const;
Square ep_square() const;
bool empty(Square s) const;
template<PieceType Pt>
int count(Color c) const;
template<PieceType Pt>
@@ -132,11 +133,11 @@ class Position {
Piece captured_piece() const;
// Doing and undoing moves
void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt);
DirtyPiece do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt);
void undo_move(Move m);
void do_null_move(StateInfo& newSt, const TranspositionTable& tt);
void undo_null_move();
void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt);
DirtyBoardData do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt);
void undo_move(Move m);
void do_null_move(StateInfo& newSt, const TranspositionTable& tt);
void undo_null_move();
// Static Exchange Evaluation
bool see_ge(Move m, int threshold = 0) const;
@@ -166,8 +167,9 @@ class Position {
StateInfo* state() const;
void put_piece(Piece pc, Square s);
void remove_piece(Square s);
void put_piece(Piece pc, Square s, DirtyThreats* const dts = nullptr);
void remove_piece(Square s, DirtyThreats* const dts = nullptr);
void swap_piece(Square s, Piece pc, DirtyThreats* const dts = nullptr);
private:
// Initialization helpers (used while setting up a position)
@@ -176,20 +178,24 @@ class Position {
void set_check_info() const;
// Other helpers
void move_piece(Square from, Square to);
template<bool PutPiece, bool ComputeRay = true>
void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts);
void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr);
template<bool Do>
void do_castling(Color us,
Square from,
Square& to,
Square& rfrom,
Square& rto,
DirtyPiece* const dp = nullptr);
void do_castling(Color us,
Square from,
Square& to,
Square& rfrom,
Square& rto,
DirtyThreats* const dts = nullptr,
DirtyPiece* const dp = nullptr);
Key adjust_key50(Key k) const;
// Data members
Piece board[SQUARE_NB];
Bitboard byTypeBB[PIECE_TYPE_NB];
Bitboard byColorBB[COLOR_NB];
std::array<Piece, SQUARE_NB> board;
std::array<Bitboard, PIECE_TYPE_NB> byTypeBB;
std::array<Bitboard, COLOR_NB> byColorBB;
int pieceCount[PIECE_NB];
int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB];
@@ -209,7 +215,7 @@ inline Piece Position::piece_on(Square s) const {
return board[s];
}
inline const Piece* Position::piece_array() const { return board; }
inline const std::array<Piece, SQUARE_NB>& Position::piece_array() const { return board; }
inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
@@ -326,18 +332,23 @@ inline bool Position::capture_stage(Move m) const {
inline Piece Position::captured_piece() const { return st->capturedPiece; }
inline void Position::put_piece(Piece pc, Square s) {
inline void Position::put_piece(Piece pc, Square s, DirtyThreats* const dts) {
board[s] = pc;
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
byColorBB[color_of(pc)] |= s;
pieceCount[pc]++;
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
if (dts)
update_piece_threats<true>(pc, s, dts);
}
inline void Position::remove_piece(Square s) {
inline void Position::remove_piece(Square s, DirtyThreats* const dts) {
Piece pc = board[s];
if (dts)
update_piece_threats<false>(pc, s, dts);
byTypeBB[ALL_PIECES] ^= s;
byTypeBB[type_of(pc)] ^= s;
byColorBB[color_of(pc)] ^= s;
@@ -346,15 +357,35 @@ inline void Position::remove_piece(Square s) {
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
}
inline void Position::move_piece(Square from, Square to) {
inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts) {
Piece pc = board[from];
Bitboard fromTo = from | to;
if (dts)
update_piece_threats<false>(pc, from, dts);
byTypeBB[ALL_PIECES] ^= fromTo;
byTypeBB[type_of(pc)] ^= fromTo;
byColorBB[color_of(pc)] ^= fromTo;
board[from] = NO_PIECE;
board[to] = pc;
if (dts)
update_piece_threats<true>(pc, to, dts);
}
inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
Piece old = board[s];
remove_piece(s);
if (dts)
update_piece_threats<false, false>(old, s, dts);
put_piece(pc, s);
if (dts)
update_piece_threats<true, false>(pc, s, dts);
}
inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) {
+11 -6
View File
@@ -46,6 +46,7 @@
#include "thread.h"
#include "timeman.h"
#include "tt.h"
#include "types.h"
#include "uci.h"
#include "ucioption.h"
@@ -527,15 +528,19 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac
void Search::Worker::do_move(
Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) {
bool capture = pos.capture_stage(move);
DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt);
bool capture = pos.capture_stage(move);
nodes.fetch_add(1, std::memory_order_relaxed);
accumulatorStack.push(dp);
DirtyBoardData dirtyBoardData = pos.do_move(move, st, givesCheck, &tt);
accumulatorStack.push(dirtyBoardData);
if (ss != nullptr)
{
ss->currentMove = move;
ss->continuationHistory = &continuationHistory[ss->inCheck][capture][dp.pc][move.to_sq()];
ss->continuationCorrectionHistory = &continuationCorrectionHistory[dp.pc][move.to_sq()];
ss->currentMove = move;
ss->continuationHistory =
&continuationHistory[ss->inCheck][capture][dirtyBoardData.dp.pc][move.to_sq()];
ss->continuationCorrectionHistory =
&continuationCorrectionHistory[dirtyBoardData.dp.pc][move.to_sq()];
}
}
+43
View File
@@ -40,6 +40,7 @@
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include "misc.h"
#if defined(_MSC_VER)
// Disable some silly and noisy warnings from MSVC compiler
@@ -290,6 +291,48 @@ struct DirtyPiece {
Piece remove_pc, add_pc;
};
// Keep track of what threats change on the board (used by NNUE)
struct DirtyThreat {
DirtyThreat() { /* don't initialize data */ }
DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) {
data = (add << 28) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq);
}
Piece pc() const { return static_cast<Piece>(data >> 20 & 0xf); }
Piece threatened_pc() const { return static_cast<Piece>(data >> 16 & 0xf); }
Square threatened_sq() const { return static_cast<Square>(data >> 8 & 0xff); }
Square pc_sq() const { return static_cast<Square>(data & 0xff); }
bool add() const {
uint32_t b = data >> 28;
sf_assume(b == 0 || b == 1);
return b;
}
private:
uint32_t data;
};
using DirtyThreatList = ValueList<DirtyThreat, 80>;
// A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks.
// Moving a piece also can reveal at most 8 discovered attacks.
// This implies that a non-castling move can change at most (8 + 16) * 3 + 8 = 80 features.
// By similar logic, a castling move can change at most (5 + 1 + 3 + 9) * 2 = 36 features.
// Thus, 80 should work as an upper bound.
struct DirtyThreats {
DirtyThreatList list;
Color us;
Square prevKsq, ksq;
Bitboard threatenedSqs, threateningSqs;
};
struct DirtyBoardData {
DirtyPiece dp;
DirtyThreats dts;
};
#define ENABLE_INCR_OPERATORS_ON(T) \
constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \
constexpr T& operator--(T& d) { return d = T(int(d) - 1); }