Skip to content

Commit d61d385

Browse files
Sopel97vondele
authored andcommitted
New NNUE architecture and net
Introduces a new NNUE network architecture and associated network parameters The summary of the changes: * Position for each perspective mirrored such that the king is on e..h files. Cuts the feature transformer size in half, while preserving enough knowledge to be good. See https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY/edit#heading=h.b40q4rb1w7on. * The number of neurons after the feature transformer increased two-fold, to 1024x2. This is possibly mostly due to the now very optimized feature transformer update code. * The number of neurons after the second layer is reduced from 16 to 8, to reduce the speed impact. This, perhaps surprisingly, doesn't harm the strength much. See https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY/edit#heading=h.6qkocr97fezq The AffineTransform code did not work out-of-the box with the smaller number of neurons after the second layer, so some temporary changes have been made to add a special case for InputDimensions == 8. Also additional 0 padding is added to the output for some archs that cannot process inputs by <=8 (SSE2, NEON). VNNI uses an implementation that can keep all outputs in the registers while reducing the number of loads by 3 for each 16 inputs, thanks to the reduced number of output neurons. However GCC is particularily bad at optimization here (and perhaps why the current way the affine transform is done even passed sprt) (see https://docs.google.com/document/d/1gTlrr02qSNKiXNZ_SuO4-RjK4MXBiFlLE6jvNqqMkAY/edit# for details) and more work will be done on this in the following days. I expect the current VNNI implementation to be improved and extended to other architectures. The network was trained with a slightly modified version of the pytorch trainer (https://github.com/glinscott/nnue-pytorch); the changes are in official-stockfish/nnue-pytorch#143 The training utilized 2 datasets. dataset A - https://drive.google.com/file/d/1VlhnHL8f-20AXhGkILujnNXHwy9T-MQw/view?usp=sharing dataset B - as described in ba01f4b The training process was as following: train on dataset A for 350 epochs, take the best net in terms of elo at 20k nodes per move (it's fine to take anything from later stages of training). convert the .ckpt to .pt --resume-from-model from the .pt file, train on dataset B for <600 epochs, take the best net. Lambda=0.8, applied before the loss function. The first training command: python3 train.py \ ../nnue-pytorch-training/data/large_gensfen_multipvdiff_100_d9.binpack \ ../nnue-pytorch-training/data/large_gensfen_multipvdiff_100_d9.binpack \ --gpus "$3," \ --threads 1 \ --num-workers 1 \ --batch-size 16384 \ --progress_bar_refresh_rate 20 \ --smart-fen-skipping \ --random-fen-skipping 3 \ --features=HalfKAv2_hm^ \ --lambda=1.0 \ --max_epochs=600 \ --default_root_dir ../nnue-pytorch-training/experiment_$1/run_$2 The second training command: python3 serialize.py \ --features=HalfKAv2_hm^ \ ../nnue-pytorch-training/experiment_131/run_6/default/version_0/checkpoints/epoch-499.ckpt \ ../nnue-pytorch-training/experiment_$1/base/base.pt python3 train.py \ ../nnue-pytorch-training/data/michael_commit_b94a65.binpack \ ../nnue-pytorch-training/data/michael_commit_b94a65.binpack \ --gpus "$3," \ --threads 1 \ --num-workers 1 \ --batch-size 16384 \ --progress_bar_refresh_rate 20 \ --smart-fen-skipping \ --random-fen-skipping 3 \ --features=HalfKAv2_hm^ \ --lambda=0.8 \ --max_epochs=600 \ --resume-from-model ../nnue-pytorch-training/experiment_$1/base/base.pt \ --default_root_dir ../nnue-pytorch-training/experiment_$1/run_$2 STC: https://tests.stockfishchess.org/tests/view/611120b32a8a49ac5be798c4 LLR: 2.97 (-2.94,2.94) <-0.50,2.50> Total: 22480 W: 2434 L: 2251 D: 17795 Ptnml(0-2): 101, 1736, 7410, 1865, 128 LTC: https://tests.stockfishchess.org/tests/view/611152b32a8a49ac5be798ea LLR: 2.93 (-2.94,2.94) <0.50,3.50> Total: 9776 W: 442 L: 333 D: 9001 Ptnml(0-2): 5, 295, 4180, 402, 6 closes #3646 bench: 5189338
1 parent dabaf22 commit d61d385

File tree

6 files changed

+247
-46
lines changed

6 files changed

+247
-46
lines changed

src/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ endif
4141
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
4242
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
4343
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
44-
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2.cpp
44+
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp
4545

4646
OBJS = $(notdir $(SRCS:.cpp=.o))
4747

src/evaluate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ namespace Eval {
3939
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
4040
// for the build process (profile-build and fishtest) to work. Do not change the
4141
// name of the macro, as it is used in the Makefile.
42-
#define EvalFileDefaultName "nn-46832cfbead3.nnue"
42+
#define EvalFileDefaultName "nn-e8321e467bf6.nnue"
4343

4444
namespace NNUE {
4545

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,32 @@
1616
along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
*/
1818

19-
//Definition of input features HalfKAv2 of NNUE evaluation function
19+
//Definition of input features HalfKAv2_hm of NNUE evaluation function
2020

21-
#include "half_ka_v2.h"
21+
#include "half_ka_v2_hm.h"
2222

2323
#include "../../position.h"
2424

2525
namespace Stockfish::Eval::NNUE::Features {
2626

2727
// Orient a square according to perspective (rotates by 180 for black)
28-
inline Square HalfKAv2::orient(Color perspective, Square s) {
29-
return Square(int(s) ^ (bool(perspective) * 56));
28+
inline Square HalfKAv2_hm::orient(Color perspective, Square s, Square ksq) {
29+
return Square(int(s) ^ (bool(perspective) * SQ_A8) ^ ((file_of(ksq) < FILE_E) * SQ_H1));
3030
}
3131

3232
// Index of a feature for a given king position and another piece on some square
33-
inline IndexType HalfKAv2::make_index(Color perspective, Square s, Piece pc, Square ksq) {
34-
return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq);
33+
inline IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {
34+
Square o_ksq = orient(perspective, ksq, ksq);
35+
return IndexType(orient(perspective, s, ksq) + PieceSquareIndex[perspective][pc] + PS_NB * KingBuckets[o_ksq]);
3536
}
3637

3738
// Get a list of indices for active features
38-
void HalfKAv2::append_active_indices(
39+
void HalfKAv2_hm::append_active_indices(
3940
const Position& pos,
4041
Color perspective,
4142
ValueListInserter<IndexType> active
4243
) {
43-
Square ksq = orient(perspective, pos.square<KING>(perspective));
44+
Square ksq = pos.square<KING>(perspective);
4445
Bitboard bb = pos.pieces();
4546
while (bb)
4647
{
@@ -52,33 +53,32 @@ namespace Stockfish::Eval::NNUE::Features {
5253

5354
// append_changed_indices() : get a list of indices for recently changed features
5455

55-
void HalfKAv2::append_changed_indices(
56+
void HalfKAv2_hm::append_changed_indices(
5657
Square ksq,
5758
StateInfo* st,
5859
Color perspective,
5960
ValueListInserter<IndexType> removed,
6061
ValueListInserter<IndexType> added
6162
) {
6263
const auto& dp = st->dirtyPiece;
63-
Square oriented_ksq = orient(perspective, ksq);
6464
for (int i = 0; i < dp.dirty_num; ++i) {
6565
Piece pc = dp.piece[i];
6666
if (dp.from[i] != SQ_NONE)
67-
removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq));
67+
removed.push_back(make_index(perspective, dp.from[i], pc, ksq));
6868
if (dp.to[i] != SQ_NONE)
69-
added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq));
69+
added.push_back(make_index(perspective, dp.to[i], pc, ksq));
7070
}
7171
}
7272

73-
int HalfKAv2::update_cost(StateInfo* st) {
73+
int HalfKAv2_hm::update_cost(StateInfo* st) {
7474
return st->dirtyPiece.dirty_num;
7575
}
7676

77-
int HalfKAv2::refresh_cost(const Position& pos) {
77+
int HalfKAv2_hm::refresh_cost(const Position& pos) {
7878
return pos.count<ALL_PIECES>();
7979
}
8080

81-
bool HalfKAv2::requires_refresh(StateInfo* st, Color perspective) {
81+
bool HalfKAv2_hm::requires_refresh(StateInfo* st, Color perspective) {
8282
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
8383
}
8484

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
//Definition of input features HalfKP of NNUE evaluation function
2020

21-
#ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
22-
#define NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
21+
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
22+
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
2323

2424
#include "../nnue_common.h"
2525

@@ -32,9 +32,9 @@ namespace Stockfish {
3232

3333
namespace Stockfish::Eval::NNUE::Features {
3434

35-
// Feature HalfKAv2: Combination of the position of own king
36-
// and the position of pieces
37-
class HalfKAv2 {
35+
// Feature HalfKAv2_hm: Combination of the position of own king
36+
// and the position of pieces. Position mirrored such that king always on e..h files.
37+
class HalfKAv2_hm {
3838

3939
// unique number for each piece type on each square
4040
enum {
@@ -63,21 +63,32 @@ namespace Stockfish::Eval::NNUE::Features {
6363
};
6464

6565
// Orient a square according to perspective (rotates by 180 for black)
66-
static Square orient(Color perspective, Square s);
66+
static Square orient(Color perspective, Square s, Square ksq);
6767

6868
// Index of a feature for a given king position and another piece on some square
6969
static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
7070

7171
public:
7272
// Feature name
73-
static constexpr const char* Name = "HalfKAv2(Friend)";
73+
static constexpr const char* Name = "HalfKAv2_hm(Friend)";
7474

7575
// Hash value embedded in the evaluation file
76-
static constexpr std::uint32_t HashValue = 0x5f234cb8u;
76+
static constexpr std::uint32_t HashValue = 0x7f234cb8u;
7777

7878
// Number of feature dimensions
7979
static constexpr IndexType Dimensions =
80-
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB);
80+
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
81+
82+
static constexpr int KingBuckets[64] = {
83+
-1, -1, -1, -1, 31, 30, 29, 28,
84+
-1, -1, -1, -1, 27, 26, 25, 24,
85+
-1, -1, -1, -1, 23, 22, 21, 20,
86+
-1, -1, -1, -1, 19, 18, 17, 16,
87+
-1, -1, -1, -1, 15, 14, 13, 12,
88+
-1, -1, -1, -1, 11, 10, 9, 8,
89+
-1, -1, -1, -1, 7, 6, 5, 4,
90+
-1, -1, -1, -1, 3, 2, 1, 0
91+
};
8192

8293
// Maximum number of simultaneously active features.
8394
static constexpr IndexType MaxActiveDimensions = 32;
@@ -108,4 +119,4 @@ namespace Stockfish::Eval::NNUE::Features {
108119

109120
} // namespace Stockfish::Eval::NNUE::Features
110121

111-
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
122+
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED

0 commit comments

Comments
 (0)