Skip to content

Commit

Permalink
feature-reset-cache (#161)
Browse files Browse the repository at this point in the history
ELO   | 10.67 +- 6.51 (95%)
SPRT  | 8.0+0.08s Threads=1 Hash=32MB
LLR   | 2.95 (-2.94, 2.94) [0.00, 5.00]
GAMES | N: 5344 W: 1390 L: 1226 D: 2728
ELO   | 5.79 +- 4.29 (95%)
SPRT  | 40.0+0.40s Threads=1 Hash=64MB
LLR   | 2.97 (-2.94, 2.94) [0.00, 5.00]
GAMES | N: 11648 W: 2801 L: 2607 D: 6240
bench: 3853493
  • Loading branch information
connormcmonigle authored Jun 20, 2023
1 parent 1e27113 commit 7d3da06
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 70 deletions.
55 changes: 30 additions & 25 deletions include/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -826,38 +826,42 @@ struct board {
});
}

template <color c, typename T>
void feature_partial_reset_(const move& mv, T& sided_set) const {
template <color c, typename T0, typename T1>
void half_feature_partial_reset_(const move& mv, T0& feature_reset_cache, T1& sided_set) const {
namespace h_ka = feature::half_ka;

const square our_king = mv.to();
const square their_king = man_.them<c>().king().item();
auto* entry = feature_reset_cache.template us<c>().look_up(our_king);

sided_set.template us<c>().clear();
sided_set.template us<c>().insert(h_ka::index<c, c>(our_king, piece_type::king, mv.to()));
over_types([&](const piece_type& pt) {
square_set& them_entry_plane = entry->config.template them<c>().get_plane(pt);
square_set& us_entry_plane = entry->config.template us<c>().get_plane(pt);

sided_set.template them<c>().copy_parent();
sided_set.template them<c>().erase(h_ka::index<opponent<c>, c>(their_king, piece_type::king, mv.from()));
sided_set.template them<c>().insert(h_ka::index<opponent<c>, c>(their_king, piece_type::king, mv.to()));
if (mv.is_capture()) { sided_set.template them<c>().erase(h_ka::index<opponent<c>, opponent<c>>(their_king, mv.captured(), mv.to())); }
const square_set them_board_plane = man_.them<c>().get_plane(pt).excluding(mv.to());
const square_set us_board_plane = [&] {
if (pt == piece_type::king) { return man_.us<c>().get_plane(pt).excluding(mv.from()).insert(mv.to()); }
return man_.us<c>().get_plane(pt).excluding(mv.from());
}();

over_types([&](const piece_type& pt) {
for (const auto sq : man_.us<c>().get_plane(pt).excluding(mv.from())) {
sided_set.template us<c>().insert(h_ka::index<c, c>(our_king, pt, sq));
}
for (const auto sq : (us_entry_plane & ~us_board_plane)) { entry->erase(h_ka::index<c, c>(our_king, pt, sq)); }
for (const auto sq : (them_entry_plane & ~them_board_plane)) { entry->erase(h_ka::index<c, opponent<c>>(our_king, pt, sq)); }

for (const auto sq : man_.them<c>().get_plane(pt).excluding(mv.to())) {
sided_set.template us<c>().insert(h_ka::index<c, opponent<c>>(our_king, pt, sq));
}
for (const auto sq : (us_board_plane & ~us_entry_plane)) { entry->insert(h_ka::index<c, c>(our_king, pt, sq)); }
for (const auto sq : (them_board_plane & ~them_entry_plane)) { entry->insert(h_ka::index<c, opponent<c>>(our_king, pt, sq)); }

us_entry_plane = us_board_plane;
them_entry_plane = them_board_plane;
});

entry->copy_state_to(sided_set.template us<c>());
}

template <color pov, color p, typename T>
void half_feature_move_delta_(const move& mv, T& sided_set) const {
namespace h_ka = feature::half_ka;
const square our_king = man_.us<pov>().king().item();
const size_t erase_idx_0 = h_ka::index<pov, p>(our_king, mv.piece(), mv.from());

const size_t insert_idx = [&] {
const piece_type on_to = mv.is_promotion<p>() ? mv.promotion() : mv.piece();
return h_ka::index<pov, p>(our_king, on_to, mv.to());
Expand All @@ -868,7 +872,7 @@ struct board {
sided_set.template us<pov>().copy_parent_insert_erase_erase(insert_idx, erase_idx_0, erase_idx_1);
return;
}

if (mv.is_enpassant()) {
const size_t erase_idx_1 = h_ka::index<pov, opponent<p>>(our_king, piece_type::pawn, mv.enpassant_sq());
sided_set.template us<pov>().copy_parent_insert_erase_erase(insert_idx, erase_idx_0, erase_idx_1);
Expand All @@ -878,8 +882,8 @@ struct board {
sided_set.template us<pov>().copy_parent_insert_erase(insert_idx, erase_idx_0);
}

template <color c, typename T>
void feature_move_delta_(const move& mv, T& sided_set) const {
template <color c, typename T0, typename T1>
void feature_move_delta_(const move& mv, T0& feature_reset_cache, T1& sided_set) const {
namespace h_ka = feature::half_ka;

if (mv.is_castle_oo<c>() || mv.is_castle_ooo<c>()) {
Expand All @@ -888,20 +892,21 @@ struct board {
}

if (mv.is_king_move()) {
feature_partial_reset_<c>(mv, sided_set);
half_feature_partial_reset_<c>(mv, feature_reset_cache, sided_set);
half_feature_move_delta_<opponent<c>, c>(mv, sided_set);
return;
}

half_feature_move_delta_<c, c>(mv, sided_set);
half_feature_move_delta_<opponent<c>, c>(mv, sided_set);
}

template <typename T>
void feature_move_delta(const move& mv, T& sided_set) const {
template <typename T0, typename T1>
void feature_move_delta(const move& mv, T0& feature_reset_cache, T1& sided_set) const {
if (turn()) {
feature_move_delta_<color::white>(mv, sided_set);
feature_move_delta_<color::white>(mv, feature_reset_cache, sided_set);
} else {
feature_move_delta_<color::black>(mv, sided_set);
feature_move_delta_<color::black>(mv, feature_reset_cache, sided_set);
}
}

Expand Down
75 changes: 75 additions & 0 deletions include/nnue_eval_node.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Seer is a UCI chess engine by Connor McMonigle
Copyright (C) 2021 Connor McMonigle
Seer 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.
Seer 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/>.
*/

#pragma once

#include <board.h>
#include <chess_types.h>
#include <feature_util.h>
#include <nnue_feature_reset_cache.h>
#include <nnue_model.h>
#include <nnue_util.h>
#include <search_constants.h>
#include <weights_streamer.h>

#include <cmath>
#include <cstdint>
#include <iostream>
#include <string>

namespace nnue {

struct eval_node {
struct context {
sided_feature_reset_cache* reset_cache_{nullptr};
eval_node* parent_node_{nullptr};
const chess::board* parent_board_{nullptr};
const chess::move move_{chess::move::null()};
};

bool dirty_;

union {
context context_;
eval eval_;
} data_;

bool dirty() const { return dirty_; }

const eval& evaluator() {
if (!dirty_) { return data_.eval_; }
dirty_ = false;
const context ctxt = data_.context_;
data_.eval_ = ctxt.parent_node_->evaluator().next_child();
ctxt.parent_board_->feature_move_delta(ctxt.move_, *ctxt.reset_cache_, data_.eval_);
return data_.eval_;
}

eval_node dirty_child(sided_feature_reset_cache* reset_cache, const chess::board* bd, const chess::move& mv) {
return eval_node::dirty_node(context{reset_cache, this, bd, mv});
}

static eval_node dirty_node(const context& context) { return eval_node{true, {context}}; }

static eval_node clean_node(const eval& eval) {
eval_node result{};
result.dirty_ = false;
result.data_.eval_ = eval;
return result;
}
};

} // namespace nnue
104 changes: 104 additions & 0 deletions include/nnue_feature_reset_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
Seer is a UCI chess engine by Connor McMonigle
Copyright (C) 2021 Connor McMonigle
Seer 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.
Seer 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/>.
*/

#pragma once

#include <board.h>
#include <nnue_model.h>
#include <nnue_util.h>
#include <search_constants.h>
#include <zobrist_util.h>

#include <array>
#include <optional>

namespace nnue {

struct piece_configuration {
chess::square_set pawn_{};
chess::square_set knight_{};
chess::square_set bishop_{};
chess::square_set rook_{};
chess::square_set queen_{};
chess::square_set king_{};

chess::square_set& get_plane(const chess::piece_type& pt) { return chess::get_member(pt, *this); }
const chess::square_set& get_plane(const chess::piece_type& pt) const { return chess::get_member(pt, *this); }
};

struct sided_piece_configuration : chess::sided<sided_piece_configuration, piece_configuration> {
piece_configuration white;
piece_configuration black;

sided_piece_configuration() : white{}, black{} {}
};

struct feature_reset_cache_entry {
static constexpr size_t dim = weights::base_dim;

using parameter_type = weights::quantized_parameter_type;
using weights_type = big_affine<parameter_type, feature::half_ka::numel, dim>;

const weights_type* weights_;
aligned_slice<parameter_type, dim> slice_;
sided_piece_configuration config;

void insert(const size_t& idx) { weights_->insert_idx(idx, slice_); }
void erase(const size_t& idx) { weights_->erase_idx(idx, slice_); }
void copy_state_to(feature_transformer<parameter_type>& dst) const { dst.slice_.copy_from(slice_); }

void reinitialize(const weights_type* weights, const aligned_slice<parameter_type, dim>& slice) {
weights_ = weights;
slice_ = slice;
config = sided_piece_configuration{};

slice_.copy_from(weights_->b);
}

feature_reset_cache_entry() : slice_{nullptr}, config{} {}
};

struct feature_reset_cache {
using entry_type = feature_reset_cache_entry;
static constexpr size_t num_squares = 64;

stack_scratchpad<entry_type::parameter_type, num_squares * entry_type::dim> scratchpad_{};
feature_reset_cache_entry entries_[num_squares]{};

feature_reset_cache_entry* look_up(const chess::square& sq) { return entries_ + sq.index(); }

void reinitialize(const weights* weights) {
for (size_t i(0); i < num_squares; ++i) {
const auto slice = scratchpad_.get_nth_slice<entry_type::dim>(i);
entries_[i].reinitialize(&weights->quantized_shared, slice);
}
}
};

struct sided_feature_reset_cache : chess::sided<sided_feature_reset_cache, feature_reset_cache> {
feature_reset_cache white;
feature_reset_cache black;

void reinitialize(const weights* weights) {
white.reinitialize(weights);
black.reinitialize(weights);
}

sided_feature_reset_cache() : white{}, black{} {}
};

} // namespace nnue
37 changes: 0 additions & 37 deletions include/nnue_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,41 +166,4 @@ struct eval : chess::sided<eval, feature_transformer<weights::quantized_paramete
black{&src->quantized_shared, parent_base_.slice<base_dim, base_dim>(), base_.slice<base_dim, base_dim>()} {}
};

struct eval_node {
struct context {
eval_node* parent_node_{nullptr};
const chess::board* parent_board_{nullptr};
const chess::move move_{chess::move::null()};
};

bool dirty_;

union {
context context_;
eval eval_;
} data_;

bool dirty() const { return dirty_; }

const eval& evaluator() {
if (!dirty_) { return data_.eval_; }
dirty_ = false;
const context ctxt = data_.context_;
data_.eval_ = ctxt.parent_node_->evaluator().next_child();
ctxt.parent_board_->feature_move_delta(ctxt.move_, data_.eval_);
return data_.eval_;
}

eval_node dirty_child(const chess::board* bd, const chess::move& mv) { return eval_node::dirty_node(context{this, bd, mv}); }

static eval_node dirty_node(const context& context) { return eval_node{true, {context}}; }

static eval_node clean_node(const eval& eval) {
eval_node result{};
result.dirty_ = false;
result.data_.eval_ = eval;
return result;
}
};

} // namespace nnue
9 changes: 9 additions & 0 deletions include/nnue_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ struct aligned_slice {
aligned_slice(T* data) : data{data} {}
};

template <typename T, size_t dim>
std::ostream& operator<<(std::ostream& ostr, const aligned_slice<T, dim>& vec) {
static_assert(dim != 0, "can't stream empty slice.");
ostr << "aligned_slice<T, " << dim << ">([";
for (size_t i = 0; i < (dim - 1); ++i) { ostr << vec.data[i] << ", "; }
ostr << vec.data[dim - 1] << "])";
return ostr;
}

template <typename T, size_t scratchpad_size>
struct stack_scratchpad {
alignas(simd::alignment) T data[scratchpad_size];
Expand Down
Loading

0 comments on commit 7d3da06

Please sign in to comment.