mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Merge commit '8e5392d79a36aba5b997cf6fb590937e3e624e80' into cluster
This commit is contained in:
@@ -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
@@ -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 cluster.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 \
|
||||
cluster.h
|
||||
|
||||
|
||||
+5
-1
@@ -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
|
||||
@@ -509,4 +513,4 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
|
||||
return setup;
|
||||
}
|
||||
|
||||
} // namespace Stockfish
|
||||
} // namespace Stockfish
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
@@ -35,6 +35,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
@@ -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];
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -224,7 +224,7 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks;
|
||||
if (Distributed::is_root())
|
||||
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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -47,6 +47,7 @@
|
||||
#include "thread.h"
|
||||
#include "timeman.h"
|
||||
#include "tt.h"
|
||||
#include "types.h"
|
||||
#include "uci.h"
|
||||
#include "ucioption.h"
|
||||
|
||||
@@ -575,15 +576,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
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user