Skip to content

Commit

Permalink
New NNUE architecture and net
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Sopel97 authored and vondele committed Aug 15, 2021
1 parent dabaf22 commit d61d385
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 46 deletions.
2 changes: 1 addition & 1 deletion src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ endif
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2.cpp
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp

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

Expand Down
2 changes: 1 addition & 1 deletion src/evaluate.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace Eval {
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-46832cfbead3.nnue"
#define EvalFileDefaultName "nn-e8321e467bf6.nnue"

namespace NNUE {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,32 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

//Definition of input features HalfKAv2 of NNUE evaluation function
//Definition of input features HalfKAv2_hm of NNUE evaluation function

#include "half_ka_v2.h"
#include "half_ka_v2_hm.h"

#include "../../position.h"

namespace Stockfish::Eval::NNUE::Features {

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

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

// Get a list of indices for active features
void HalfKAv2::append_active_indices(
void HalfKAv2_hm::append_active_indices(
const Position& pos,
Color perspective,
ValueListInserter<IndexType> active
) {
Square ksq = orient(perspective, pos.square<KING>(perspective));
Square ksq = pos.square<KING>(perspective);
Bitboard bb = pos.pieces();
while (bb)
{
Expand All @@ -52,33 +53,32 @@ namespace Stockfish::Eval::NNUE::Features {

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

void HalfKAv2::append_changed_indices(
void HalfKAv2_hm::append_changed_indices(
Square ksq,
StateInfo* st,
Color perspective,
ValueListInserter<IndexType> removed,
ValueListInserter<IndexType> added
) {
const auto& dp = st->dirtyPiece;
Square oriented_ksq = orient(perspective, ksq);
for (int i = 0; i < dp.dirty_num; ++i) {
Piece pc = dp.piece[i];
if (dp.from[i] != SQ_NONE)
removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq));
removed.push_back(make_index(perspective, dp.from[i], pc, ksq));
if (dp.to[i] != SQ_NONE)
added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq));
added.push_back(make_index(perspective, dp.to[i], pc, ksq));
}
}

int HalfKAv2::update_cost(StateInfo* st) {
int HalfKAv2_hm::update_cost(StateInfo* st) {
return st->dirtyPiece.dirty_num;
}

int HalfKAv2::refresh_cost(const Position& pos) {
int HalfKAv2_hm::refresh_cost(const Position& pos) {
return pos.count<ALL_PIECES>();
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

//Definition of input features HalfKP of NNUE evaluation function

#ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
#define NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED

#include "../nnue_common.h"

Expand All @@ -32,9 +32,9 @@ namespace Stockfish {

namespace Stockfish::Eval::NNUE::Features {

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

// unique number for each piece type on each square
enum {
Expand Down Expand Up @@ -63,21 +63,32 @@ namespace Stockfish::Eval::NNUE::Features {
};

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

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

public:
// Feature name
static constexpr const char* Name = "HalfKAv2(Friend)";
static constexpr const char* Name = "HalfKAv2_hm(Friend)";

// Hash value embedded in the evaluation file
static constexpr std::uint32_t HashValue = 0x5f234cb8u;
static constexpr std::uint32_t HashValue = 0x7f234cb8u;

// Number of feature dimensions
static constexpr IndexType Dimensions =
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB);
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;

static constexpr int KingBuckets[64] = {
-1, -1, -1, -1, 31, 30, 29, 28,
-1, -1, -1, -1, 27, 26, 25, 24,
-1, -1, -1, -1, 23, 22, 21, 20,
-1, -1, -1, -1, 19, 18, 17, 16,
-1, -1, -1, -1, 15, 14, 13, 12,
-1, -1, -1, -1, 11, 10, 9, 8,
-1, -1, -1, -1, 7, 6, 5, 4,
-1, -1, -1, -1, 3, 2, 1, 0
};

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

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

#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
Loading

0 comments on commit d61d385

Please sign in to comment.