From 19f712cdbbc973fa568510177d043a48cf15438e Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Sun, 18 Apr 2021 20:33:17 +0200 Subject: [PATCH] Post-merge fixes. --- src/evaluate.cpp | 121 ++++++++- src/evaluate.h | 18 ++ src/misc.cpp | 4 +- src/nnue/evaluate_nnue.cpp | 152 +----------- src/nnue/evaluate_nnue.h | 14 +- src/nnue/features/feature_set.h | 300 +++------------------- src/nnue/features/features_common.h | 9 +- src/nnue/features/half_kp.cpp | 1 + src/nnue/layers/affine_transform.h | 46 +--- src/nnue/nnue_accumulator.h | 13 +- src/nnue/nnue_feature_transformer.h | 373 ++++++++++------------------ src/position.cpp | 6 +- src/position.h | 4 +- src/search.cpp | 6 +- src/tools/convert.cpp | 2 +- src/tools/convert.h | 2 +- src/tools/gensfen.cpp | 4 +- src/tools/gensfen.h | 2 +- src/tools/gensfen_nonpv.cpp | 4 +- src/tools/gensfen_nonpv.h | 2 +- src/tools/opening_book.cpp | 2 +- src/tools/opening_book.h | 2 +- src/tools/packed_sfen.h | 2 +- src/tools/sfen_packer.cpp | 6 +- src/tools/sfen_packer.h | 10 +- src/tools/sfen_reader.h | 2 +- src/tools/sfen_stream.h | 2 +- src/tools/sfen_writer.h | 16 +- src/tools/stats.cpp | 4 +- src/tools/stats.h | 2 +- src/tools/transform.cpp | 2 +- src/tools/transform.h | 2 +- src/uci.cpp | 5 +- 33 files changed, 372 insertions(+), 768 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index e4558730..3d9a48b7 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -38,8 +38,127 @@ #include "uci.h" #include "incbin/incbin.h" +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). +// This macro invocation will declare the following three variables +// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data +// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end +// const unsigned int gEmbeddedNNUESize; // the size of the embedded file +// Note that this does not work in Microsoft Visual Studio. +#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) + INCBIN(EmbeddedNNUE, EvalFileDefaultName); +#else + const unsigned char gEmbeddedNNUEData[1] = {0x0}; + const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; + const unsigned int gEmbeddedNNUESize = 1; +#endif + using namespace std; +namespace Stockfish { + +namespace Eval { + + namespace NNUE { + string eval_file_loaded = "None"; + UseNNUEMode useNNUE; + + /// NNUE::init() tries to load a NNUE network at startup time, or when the engine + /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" + /// The name of the NNUE network is always retrieved from the EvalFile option. + /// We search the given network in three locations: internally (the default + /// network may be embedded in the binary), in the active working directory and + /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY + /// variable to have the engine search in a special directory in their distro. + + static UseNNUEMode nnue_mode_from_option(const UCI::Option& mode) + { + if (mode == "false") + return UseNNUEMode::False; + else if (mode == "true") + return UseNNUEMode::True; + else if (mode == "pure") + return UseNNUEMode::Pure; + + return UseNNUEMode::False; + } + + void init() { + + useNNUE = nnue_mode_from_option(Options["Use NNUE"]); + if (useNNUE == UseNNUEMode::False) + return; + + string eval_file = string(Options["EvalFile"]); + + #if defined(DEFAULT_NNUE_DIRECTORY) + #define stringify2(x) #x + #define stringify(x) stringify2(x) + vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; + #else + vector dirs = { "" , "" , CommandLine::binaryDirectory }; + #endif + + for (string directory : dirs) + if (eval_file_loaded != eval_file) + { + if (directory != "") + { + ifstream stream(directory + eval_file, ios::binary); + if (load_eval(eval_file, stream)) + eval_file_loaded = eval_file; + } + + if (directory == "" && eval_file == EvalFileDefaultName) + { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer : public basic_streambuf { + public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } + }; + + MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), + size_t(gEmbeddedNNUESize)); + + istream stream(&buffer); + if (load_eval(eval_file, stream)) + eval_file_loaded = eval_file; + } + } + } + + /// NNUE::verify() verifies that the last net used was loaded successfully + void verify() { + + string eval_file = string(Options["EvalFile"]); + + if (useNNUE != UseNNUEMode::False && eval_file_loaded != eval_file) + { + UCI::OptionsMap defaults; + UCI::init(defaults); + + string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; + string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; + string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; + string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]); + string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + + exit(EXIT_FAILURE); + } + + if (useNNUE != UseNNUEMode::False) + sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; + else + sync_cout << "info string classical evaluation enabled" << sync_endl; + } + } +} + namespace Trace { enum Tracing { NO_TRACE, TRACE }; @@ -1021,7 +1140,7 @@ Value Eval::evaluate(const Position& pos) { bool lowPieceEndgame = pos.non_pawn_material() == BishopValueMg || (pos.non_pawn_material() < 2 * RookValueMg && pos.count() < 2); - v = classical || lowPieceEndgame ? Evaluation(pos).value() + v = classical || lowPieceEndgame ? Evaluation(pos).value() : adjusted_NNUE(); // If the classical eval is small and imbalance large, use NNUE nevertheless. diff --git a/src/evaluate.h b/src/evaluate.h index f3766f45..af3453b4 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -28,6 +28,7 @@ namespace Stockfish { class Position; namespace Eval { + std::string trace(const Position& pos); Value evaluate(const Position& pos); @@ -36,6 +37,23 @@ namespace Eval { // name of the macro, as it is used in the Makefile. #define EvalFileDefaultName "nn-62ef826d1a6d.nnue" + namespace NNUE { + enum struct UseNNUEMode + { + False, + True, + Pure + }; + + extern UseNNUEMode useNNUE; + extern std::string eval_file_loaded; + + Value evaluate(const Position& pos); + bool load_eval(std::string name, std::istream& stream); + void init(); + void verify(); + } + } // namespace Eval } // namespace Stockfish diff --git a/src/misc.cpp b/src/misc.cpp index e981136b..e47e2649 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -61,10 +61,10 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); using namespace std; -SynchronizedRegionLogger sync_region_cout(std::cout); - namespace Stockfish { +SynchronizedRegionLogger sync_region_cout(std::cout); + namespace { /// Version number. If Version is left empty, then compile date in the format diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp index bbc6ebc2..7b2a1ae8 100644 --- a/src/nnue/evaluate_nnue.cpp +++ b/src/nnue/evaluate_nnue.cpp @@ -1,33 +1,21 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // Code for calculating NNUE evaluation function -#include "evaluate_nnue.h" - -#include "position.h" -#include "misc.h" -#include "uci.h" -#include "types.h" - #include -#include -#include #include #include "../evaluate.h" @@ -49,36 +37,18 @@ namespace Stockfish::Eval::NNUE { // Evaluation function file name std::string fileName; - // Saved evaluation function file name - std::string savedfileName = "nn.bin"; - - // Get a string that represents the structure of the evaluation function - std::string get_architecture_string() { - return "Features=" + FeatureTransformer::get_structure_string() + - ",Network=" + Network::get_structure_string(); - } - - std::string get_layers_info() { - return - FeatureTransformer::get_layers_info() - + '\n' + Network::get_layers_info(); - } - - UseNNUEMode useNNUE; - std::string eval_file_loaded = "None"; - namespace Detail { // Initialize the evaluation function parameters template - void initialize(AlignedPtr& pointer) { + void Initialize(AlignedPtr& pointer) { pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); } template - void initialize(LargePagePtr& pointer) { + void Initialize(LargePagePtr& pointer) { static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); @@ -95,35 +65,17 @@ namespace Stockfish::Eval::NNUE { return reference.ReadParameters(stream); } - // write evaluation function parameters - template - bool WriteParameters(std::ostream& stream, const AlignedPtr& pointer) { - constexpr std::uint32_t header = T::GetHashValue(); - - stream.write(reinterpret_cast(&header), sizeof(header)); - - return pointer->WriteParameters(stream); - } - - template - bool WriteParameters(std::ostream& stream, const LargePagePtr& pointer) { - constexpr std::uint32_t header = T::GetHashValue(); - - stream.write(reinterpret_cast(&header), sizeof(header)); - - return pointer->WriteParameters(stream); - } } // namespace Detail // Initialize the evaluation function parameters - void initialize() { + void Initialize() { - Detail::initialize(feature_transformer); - Detail::initialize(network); + Detail::Initialize(feature_transformer); + Detail::Initialize(network); } // Read network header - bool read_header(std::istream& stream, std::uint32_t* hash_value, std::string* architecture) + bool ReadHeader(std::istream& stream, std::uint32_t* hash_value, std::string* architecture) { std::uint32_t version, size; @@ -136,48 +88,18 @@ namespace Stockfish::Eval::NNUE { return !stream.fail(); } - // write the header - bool write_header(std::ostream& stream, - std::uint32_t hash_value, const std::string& architecture) { - - stream.write(reinterpret_cast(&kVersion), sizeof(kVersion)); - stream.write(reinterpret_cast(&hash_value), sizeof(hash_value)); - - const std::uint32_t size = static_cast(architecture.size()); - - stream.write(reinterpret_cast(&size), sizeof(size)); - stream.write(architecture.data(), size); - - return !stream.fail(); - } - // Read network parameters bool ReadParameters(std::istream& stream) { std::uint32_t hash_value; std::string architecture; - if (!read_header(stream, &hash_value, &architecture)) return false; + if (!ReadHeader(stream, &hash_value, &architecture)) return false; if (hash_value != kHashValue) return false; if (!Detail::ReadParameters(stream, *feature_transformer)) return false; if (!Detail::ReadParameters(stream, *network)) return false; return stream && stream.peek() == std::ios::traits_type::eof(); } - // write evaluation function parameters - bool WriteParameters(std::ostream& stream) { - - if (!write_header(stream, kHashValue, get_architecture_string())) - return false; - - if (!Detail::WriteParameters(stream, feature_transformer)) - return false; - - if (!Detail::WriteParameters(stream, network)) - return false; - - return !stream.fail(); -} - // Evaluation function. Perform differential calculation. Value evaluate(const Position& pos) { @@ -211,65 +133,9 @@ namespace Stockfish::Eval::NNUE { // Load eval, from a file stream or a memory stream bool load_eval(std::string name, std::istream& stream) { - initialize(); + Initialize(); fileName = name; return ReadParameters(stream); -} - -static UseNNUEMode nnue_mode_from_option(const UCI::Option& mode) -{ - if (mode == "false") - return UseNNUEMode::False; - else if (mode == "true") - return UseNNUEMode::True; - else if (mode == "pure") - return UseNNUEMode::Pure; - - return UseNNUEMode::False; -} - -void init() { - - useNNUE = nnue_mode_from_option(Options["Use NNUE"]); - - if (Options["SkipLoadingEval"]) - { - eval_file_loaded.clear(); - return; } - if (useNNUE == UseNNUEMode::False) - { - // Keep the eval file loaded. Useful for mixed bench. - return; - } - - std::string eval_file = std::string(Options["EvalFile"]); - -#if defined(DEFAULT_NNUE_DIRECTORY) -#define stringify2(x) #x -#define stringify(x) stringify2(x) - std::vector dirs = { "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; -#else - std::vector dirs = { "" , CommandLine::binaryDirectory }; -#endif - - for (std::string directory : dirs) - { - if (eval_file_loaded != eval_file) - { - std::ifstream stream(directory + eval_file, std::ios::binary); - if (load_eval(eval_file, stream)) - { - sync_cout << "info string Loaded eval file " << directory + eval_file << sync_endl; - eval_file_loaded = eval_file; - } - else - { - sync_cout << "info string ERROR: failed to load eval file " << directory + eval_file << sync_endl; - eval_file_loaded.clear(); - } - } - } - -} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish::Eval::NNUE \ No newline at end of file diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h index b6070fae..010a89f7 100644 --- a/src/nnue/evaluate_nnue.h +++ b/src/nnue/evaluate_nnue.h @@ -1,17 +1,14 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ @@ -23,19 +20,10 @@ #include "nnue_feature_transformer.h" -#include "misc.h" - #include namespace Stockfish::Eval::NNUE { - enum struct UseNNUEMode - { - False, - True, - Pure - }; - // Hash value of evaluation function structure constexpr std::uint32_t kHashValue = FeatureTransformer::GetHashValue() ^ Network::GetHashValue(); @@ -65,4 +53,4 @@ namespace Stockfish::Eval::NNUE { } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED +#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED \ No newline at end of file diff --git a/src/nnue/features/feature_set.h b/src/nnue/features/feature_set.h index c293661b..fb25bce5 100644 --- a/src/nnue/features/feature_set.h +++ b/src/nnue/features/feature_set.h @@ -1,17 +1,14 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ @@ -26,277 +23,44 @@ namespace Stockfish::Eval::NNUE::Features { - // Class template that represents a list of values - template - struct CompileTimeList; + // Class template that represents a list of values + template + struct CompileTimeList; - template - struct CompileTimeList { - static constexpr bool contains(T value) { - return value == First || CompileTimeList::contains(value); - } + template + struct CompileTimeList { + static constexpr bool Contains(T value) { + return value == First || CompileTimeList::Contains(value); + } + static constexpr std::array + kValues = {{First, Remaining...}}; + }; - static constexpr std::array - kValues = {{First, Remaining...}}; - }; + // Base class of feature set + template + class FeatureSetBase { - template - constexpr std::array - CompileTimeList::kValues; + }; - template - struct CompileTimeList { - static constexpr bool contains(T /*value*/) { - return false; - } - static constexpr std::array kValues = { {} }; - }; + // Class template that represents the feature set + template + class FeatureSet : public FeatureSetBase> { - // Class template that adds to the beginning of the list - template - struct AppendToList; + public: + // Hash value embedded in the evaluation file + static constexpr std::uint32_t kHashValue = FeatureType::kHashValue; + // Number of feature dimensions + static constexpr IndexType kDimensions = FeatureType::kDimensions; + // Maximum number of simultaneously active features + static constexpr IndexType kMaxActiveDimensions = + FeatureType::kMaxActiveDimensions; + // Trigger for full calculation instead of difference calculation + using SortedTriggerSet = + CompileTimeList; + static constexpr auto kRefreshTriggers = SortedTriggerSet::kValues; - template - struct AppendToList, AnotherValue> { - using Result = CompileTimeList; - }; - - // Class template for adding to a sorted, unique list - template - struct InsertToSet; - - template - struct InsertToSet, AnotherValue> { - using Result = - std::conditional_t< - CompileTimeList::contains(AnotherValue), - CompileTimeList, - std::conditional_t< - (AnotherValue < First), - CompileTimeList, - typename AppendToList, AnotherValue>::Result, - First - >::Result - > - >; - }; - - template - struct InsertToSet, Value> { - using Result = CompileTimeList; - }; - - // Base class of feature set - template - class FeatureSetBase { - - public: - // Get a list of indices for active features - template - static void append_active_indices( - const Position& pos, TriggerEvent trigger, IndexListType active[2]) { - - for (Color perspective : { WHITE, BLACK }) { - Derived::collect_active_indices( - pos, trigger, perspective, &active[perspective]); - } - } - - // Get a list of indices for recently changed features - template - static void append_changed_indices( - const PositionType& pos, - TriggerEvent trigger, - IndexListType removed[2], - IndexListType added[2], - bool reset[2]) { - - const auto& dp = pos.state()->dirtyPiece; - - for (Color perspective : { WHITE, BLACK }) { - switch (trigger) { - case TriggerEvent::kNone: - break; - case TriggerEvent::kFriendKingMoved: - if (dp.dirty_num == 0) continue; - reset[perspective] = dp.piece[0] == make_piece(perspective, KING); - break; - case TriggerEvent::kEnemyKingMoved: - if (dp.dirty_num == 0) continue; - reset[perspective] = dp.piece[0] == make_piece(~perspective, KING); - break; - case TriggerEvent::kAnyKingMoved: - if (dp.dirty_num == 0) continue; - reset[perspective] = type_of(dp.piece[0]) == KING; - break; - case TriggerEvent::kAnyPieceMoved: - reset[perspective] = true; - break; - default: - assert(false); - break; - } - - if (reset[perspective]) { - Derived::collect_active_indices( - pos, trigger, perspective, &added[perspective]); - } else { - Derived::collect_changed_indices( - pos, trigger, perspective, - &removed[perspective], &added[perspective]); - } - } - } - }; - - // Class template that represents the feature set - // do internal processing in reverse order of template arguments in order to linearize the amount of calculation at runtime - template - class FeatureSet : - public FeatureSetBase< - FeatureSet - > { - - private: - using Head = FirstFeatureType; - using Tail = FeatureSet; - - public: - // Hash value embedded in the evaluation function file - static constexpr std::uint32_t kHashValue = - Head::kHashValue ^ (Tail::kHashValue << 1) ^ (Tail::kHashValue >> 31); - - // number of feature dimensions - static constexpr IndexType kDimensions = - Head::kDimensions + Tail::kDimensions; - - // The maximum value of the number of indexes whose value is 1 at the same time among the feature values - static constexpr IndexType kMaxActiveDimensions = - Head::kMaxActiveDimensions + Tail::kMaxActiveDimensions; - - // List of timings to perform all calculations instead of difference calculation - using SortedTriggerSet = typename InsertToSet::Result; - - static constexpr auto kRefreshTriggers = SortedTriggerSet::kValues; - - // Get the feature quantity name - static std::string get_name() { - return std::string(Head::kName) + "+" + Tail::get_name(); - } - - private: - // Get a list of indices with a value of 1 among the features - template - static void collect_active_indices( - const Position& pos, - const TriggerEvent trigger, - const Color perspective, - IndexListType* const active) { - - Tail::collect_active_indices(pos, trigger, perspective, active); - if (Head::kRefreshTrigger == trigger) { - const auto start = active->size(); - Head::append_active_indices(pos, perspective, active); - - for (auto i = start; i < active->size(); ++i) { - (*active)[i] += Tail::kDimensions; - } - } - } - - // Get a list of indices whose values have changed from the previous one in the feature quantity - template - static void collect_changed_indices( - const Position& pos, - const TriggerEvent trigger, - const Color perspective, - IndexListType* const removed, - IndexListType* const added) { - - Tail::collect_changed_indices(pos, trigger, perspective, removed, added); - if (Head::kRefreshTrigger == trigger) { - const auto start_removed = removed->size(); - const auto start_added = added->size(); - Head::append_changed_indices(pos, perspective, removed, added); - - for (auto i = start_removed; i < removed->size(); ++i) { - (*removed)[i] += Tail::kDimensions; - } - - for (auto i = start_added; i < added->size(); ++i) { - (*added)[i] += Tail::kDimensions; - } - } - } - - // Make the base class and the class template that recursively uses itself a friend - friend class FeatureSetBase; - - template - friend class FeatureSet; - }; - - // Class template that represents the feature set - template - class FeatureSet : public FeatureSetBase> { - - public: - // Hash value embedded in the evaluation file - static constexpr std::uint32_t kHashValue = FeatureType::kHashValue; - - // Number of feature dimensions - static constexpr IndexType kDimensions = FeatureType::kDimensions; - - // Maximum number of simultaneously active features - static constexpr IndexType kMaxActiveDimensions = - FeatureType::kMaxActiveDimensions; - - // Trigger for full calculation instead of difference calculation - using SortedTriggerSet = - CompileTimeList; - - static constexpr auto kRefreshTriggers = SortedTriggerSet::kValues; - - // Get the feature quantity name - static std::string get_name() { - return FeatureType::kName; - } - - private: - // Get a list of indices for active features - static void collect_active_indices( - const Position& pos, - const TriggerEvent trigger, - const Color perspective, - IndexList* const active) { - - if (FeatureType::kRefreshTrigger == trigger) { - FeatureType::append_active_indices(pos, perspective, active); - } - } - - // Get a list of indices for recently changed features - static void collect_changed_indices( - const Position& pos, - const TriggerEvent trigger, - const Color perspective, - IndexList* const removed, - IndexList* const added) { - - if (FeatureType::kRefreshTrigger == trigger) { - FeatureType::append_changed_indices(pos, perspective, removed, added); - } - } - - // Make the base class and the class template that recursively uses itself a friend - friend class FeatureSetBase; - - template - friend class FeatureSet; - }; + }; } // namespace Stockfish::Eval::NNUE::Features -#endif // #ifndef NNUE_FEATURE_SET_H_INCLUDED +#endif // #ifndef NNUE_FEATURE_SET_H_INCLUDED \ No newline at end of file diff --git a/src/nnue/features/features_common.h b/src/nnue/features/features_common.h index 0e2c0a84..118ec953 100644 --- a/src/nnue/features/features_common.h +++ b/src/nnue/features/features_common.h @@ -33,16 +33,11 @@ namespace Stockfish::Eval::NNUE::Features { // Trigger to perform full calculations instead of difference only enum class TriggerEvent { - kNone, // Calculate the difference whenever possible - kFriendKingMoved, // calculate full evaluation when own king moves - kEnemyKingMoved, // calculate full evaluation when opponent king moves - kAnyKingMoved, // calculate full evaluation when any king moves - kAnyPieceMoved, // always calculate full evaluation + kFriendKingMoved // calculate full evaluation when own king moves }; enum class Side { - kFriend, // side to move - kEnemy, // opponent + kFriend // side to move }; } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_kp.cpp b/src/nnue/features/half_kp.cpp index 612b42bd..05eb1a9a 100644 --- a/src/nnue/features/half_kp.cpp +++ b/src/nnue/features/half_kp.cpp @@ -80,6 +80,7 @@ namespace Stockfish::Eval::NNUE::Features { if (dp.to[i] != SQ_NONE) added->push_back(make_index(perspective, dp.to[i], pc, ksq)); } + } template class HalfKP; diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index b6fb5928..3e54ab7f 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -1,17 +1,14 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ @@ -55,8 +52,6 @@ namespace Stockfish::Eval::NNUE::Layers { static constexpr std::size_t kBufferSize = PreviousLayer::kBufferSize + kSelfBufferSize; - static constexpr int kLayerIndex = PreviousLayer::kLayerIndex + 1; - // Hash value embedded in the evaluation file static constexpr std::uint32_t GetHashValue() { std::uint32_t hash_value = 0xCC03DAE4u; @@ -66,27 +61,6 @@ namespace Stockfish::Eval::NNUE::Layers { return hash_value; } - static std::string get_name() { - return "AffineTransform[" + - std::to_string(kOutputDimensions) + "<-" + - std::to_string(kInputDimensions) + "]"; - } - - // A string that represents the structure from the input layer to this layer - static std::string get_structure_string() { - return get_name() + "(" + - PreviousLayer::get_structure_string() + ")"; - } - - static std::string get_layers_info() { - std::string info = PreviousLayer::get_layers_info(); - info += "\n - "; - info += std::to_string(kLayerIndex); - info += " - "; - info += get_name(); - return info; - } - // Read network parameters bool ReadParameters(std::istream& stream) { if (!previous_layer_.ReadParameters(stream)) return false; @@ -148,21 +122,6 @@ namespace Stockfish::Eval::NNUE::Layers { return !stream.fail(); } - // write parameters - bool WriteParameters(std::ostream& stream) const { - if (!previous_layer_.WriteParameters(stream)) - return false; - - stream.write(reinterpret_cast(biases_), - kOutputDimensions * sizeof(BiasType)); - - stream.write(reinterpret_cast(weights_), - kOutputDimensions * kPaddedInputDimensions * - sizeof(WeightType)); - - return !stream.fail(); - } - // Forward propagation const OutputType* Propagate( const TransformedFeatureType* transformed_features, char* buffer) const { @@ -474,9 +433,6 @@ namespace Stockfish::Eval::NNUE::Layers { using BiasType = OutputType; using WeightType = std::int8_t; - // Make the learning class a friend - friend class Trainer; - PreviousLayer previous_layer_; alignas(kCacheLineSize) BiasType biases_[kOutputDimensions]; @@ -502,4 +458,4 @@ namespace Stockfish::Eval::NNUE::Layers { } // namespace Stockfish::Eval::NNUE::Layers -#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED \ No newline at end of file diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index e84e26f1..5da4ecb5 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -1,17 +1,14 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ @@ -25,12 +22,16 @@ namespace Stockfish::Eval::NNUE { + // The accumulator of a StateInfo without parent is set to the INIT state + enum AccumulatorState { EMPTY, COMPUTED, INIT }; + // Class that holds the result of affine transformation of input features struct alignas(kCacheLineSize) Accumulator { - std::int16_t accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions]; - bool computed_accumulation; + std::int16_t + accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions]; + AccumulatorState state[2]; }; } // namespace Stockfish::Eval::NNUE -#endif // NNUE_ACCUMULATOR_H_INCLUDED +#endif // NNUE_ACCUMULATOR_H_INCLUDED \ No newline at end of file diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 3082c1df..b5c8f40e 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -1,17 +1,14 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ @@ -25,8 +22,7 @@ #include "nnue_architecture.h" #include "features/index_list.h" -#include -#include +#include // std::memset() namespace Stockfish::Eval::NNUE { @@ -41,7 +37,6 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) _mm512_store_si512(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b) #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) - #define vec_zero _mm512_setzero_si512() static constexpr IndexType kNumRegs = 8; // only 8 are needed #elif USE_AVX2 @@ -50,7 +45,6 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) _mm256_store_si256(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b) #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) - #define vec_zero _mm256_setzero_si256() static constexpr IndexType kNumRegs = 16; #elif USE_SSE2 @@ -59,7 +53,6 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_epi16(a,b) #define vec_sub_16(a,b) _mm_sub_epi16(a,b) - #define vec_zero _mm_setzero_si128() static constexpr IndexType kNumRegs = Is64Bit ? 16 : 8; #elif USE_MMX @@ -68,7 +61,6 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_pi16(a,b) #define vec_sub_16(a,b) _mm_sub_pi16(a,b) - #define vec_zero _mm_setzero_si64() static constexpr IndexType kNumRegs = 8; #elif USE_NEON @@ -77,7 +69,6 @@ namespace Stockfish::Eval::NNUE { #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) vaddq_s16(a,b) #define vec_sub_16(a,b) vsubq_s16(a,b) - #define vec_zero {0} static constexpr IndexType kNumRegs = 16; #else @@ -109,33 +100,12 @@ namespace Stockfish::Eval::NNUE { static constexpr std::size_t kBufferSize = kOutputDimensions * sizeof(OutputType); - static constexpr int kLayerIndex = 0; - // Hash value embedded in the evaluation file static constexpr std::uint32_t GetHashValue() { return RawFeatures::kHashValue ^ kOutputDimensions; } - static std::string get_name() { - return RawFeatures::get_name() + "[" + - std::to_string(kInputDimensions) + "->" + - std::to_string(kHalfDimensions) + "x2]"; - } - - // a string representing the structure - static std::string get_structure_string() { - return get_name(); - } - - static std::string get_layers_info() { - std::string info = " - "; - info += std::to_string(kLayerIndex); - info += " - "; - info += get_name(); - return info; - } - // Read network parameters bool ReadParameters(std::istream& stream) { @@ -146,38 +116,11 @@ namespace Stockfish::Eval::NNUE { return !stream.fail(); } - // write parameters - bool WriteParameters(std::ostream& stream) const { - stream.write(reinterpret_cast(biases_), - kHalfDimensions * sizeof(BiasType)); - - stream.write(reinterpret_cast(weights_), - kHalfDimensions * kInputDimensions * sizeof(WeightType)); - - return !stream.fail(); - } - - // Proceed with the difference calculation if possible - bool update_accumulator_if_possible(const Position& pos) const { - - const auto now = pos.state(); - if (now->accumulator.computed_accumulation) - return true; - - const auto prev = now->previous; - if (prev && prev->accumulator.computed_accumulation) { - update_accumulator(pos); - return true; - } - - return false; - } - // Convert input features void Transform(const Position& pos, OutputType* output) const { - if (!update_accumulator_if_possible(pos)) - refresh_accumulator(pos); + UpdateAccumulator(pos, WHITE); + UpdateAccumulator(pos, BLACK); const auto& accumulation = pos.state()->accumulator.accumulation; @@ -221,13 +164,6 @@ namespace Stockfish::Eval::NNUE { &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 0]); __m512i sum1 = _mm512_load_si512( &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 1]); - for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) { - sum0 = _mm512_add_epi16(sum0, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 0]); - sum1 = _mm512_add_epi16(sum1, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 1]); - } - _mm512_store_si512(&out[j], _mm512_permutexvar_epi64(kControl, _mm512_max_epi8(_mm512_packs_epi16(sum0, sum1), kZero))); } @@ -239,13 +175,6 @@ namespace Stockfish::Eval::NNUE { &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 0]); __m256i sum1 = _mm256_load_si256( &reinterpret_cast(accumulation[perspectives[p]][0])[j * 2 + 1]); - for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) { - sum0 = _mm256_add_epi16(sum0, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 0]); - sum1 = _mm256_add_epi16(sum1, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 1]); - } - _mm256_store_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8( _mm256_packs_epi16(sum0, sum1), kZero), kControl)); } @@ -257,13 +186,6 @@ namespace Stockfish::Eval::NNUE { accumulation[perspectives[p]][0])[j * 2 + 0]); __m128i sum1 = _mm_load_si128(&reinterpret_cast( accumulation[perspectives[p]][0])[j * 2 + 1]); - for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) { - sum0 = _mm_add_epi16(sum0, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 0]); - sum1 = _mm_add_epi16(sum1, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 1]); - } - const __m128i packedbytes = _mm_packs_epi16(sum0, sum1); _mm_store_si128(&out[j], @@ -284,13 +206,6 @@ namespace Stockfish::Eval::NNUE { accumulation[perspectives[p]][0])[j * 2 + 0]); __m64 sum1 = *(&reinterpret_cast( accumulation[perspectives[p]][0])[j * 2 + 1]); - for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) { - sum0 = _mm_add_pi16(sum0, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 0]); - sum1 = _mm_add_pi16(sum1, reinterpret_cast( - accumulation[perspectives[p]][i])[j * 2 + 1]); - } - const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); } @@ -300,22 +215,12 @@ namespace Stockfish::Eval::NNUE { for (IndexType j = 0; j < kNumChunks; ++j) { int16x8_t sum = reinterpret_cast( accumulation[perspectives[p]][0])[j]; - - for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) { - sum = vaddq_s16(sum, reinterpret_cast( - accumulation[perspectives[p]][i])[j]); - } - out[j] = vmax_s8(vqmovn_s16(sum), kZero); } #else for (IndexType j = 0; j < kHalfDimensions; ++j) { BiasType sum = accumulation[static_cast(perspectives[p])][0][j]; - for (IndexType i = 1; i < kRefreshTriggers.size(); ++i) { - sum += accumulation[static_cast(perspectives[p])][i][j]; - } - output[offset + j] = static_cast( std::max(0, std::min(127, sum))); } @@ -328,183 +233,177 @@ namespace Stockfish::Eval::NNUE { } private: - // Calculate cumulative value without using difference calculation - void refresh_accumulator(const Position& pos) const { + void UpdateAccumulator(const Position& pos, const Color c) const { #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array // is defined in the VECTOR code below, once in each branch vec_t acc[kNumRegs]; #endif - auto& accumulator = pos.state()->accumulator; - for (IndexType i = 0; i < kRefreshTriggers.size(); ++i) { - Features::IndexList active_indices[2]; - RawFeatures::append_active_indices(pos, kRefreshTriggers[i], - active_indices); - for (Color perspective : { WHITE, BLACK }) { -#ifdef VECTOR - for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) { - auto accTile = reinterpret_cast( - &accumulator.accumulation[perspective][i][j * kTileHeight]); - if (i == 0) { - auto biasesTile = reinterpret_cast( - &biases_[j * kTileHeight]); - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = biasesTile[k]; - } else { - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = vec_zero; - } + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + StateInfo *st = pos.state(), *next = nullptr; + int gain = pos.count() - 2; + while (st->accumulator.state[c] == EMPTY) + { + auto& dp = st->dirtyPiece; + // The first condition tests whether an incremental update is + // possible at all: if this side's king has moved, it is not possible. + static_assert(std::is_same_v>, + "Current code assumes that only kFriendlyKingMoved refresh trigger is being used."); + if ( dp.piece[0] == make_piece(c, KING) + || (gain -= dp.dirty_num + 1) < 0) + break; + next = st; + st = st->previous; + } - for (const auto index : active_indices[perspective]) { - const IndexType offset = kHalfDimensions * index + j * kTileHeight; - auto column = reinterpret_cast(&weights_[offset]); + if (st->accumulator.state[c] == COMPUTED) + { + if (next == nullptr) + return; - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } + // Update incrementally in two steps. First, we update the "next" + // accumulator. Then, we update the current accumulator (pos.state()). - for (IndexType k = 0; k < kNumRegs; k++) - vec_store(&accTile[k], acc[k]); - } -#else - if (i == 0) { - std::memcpy(accumulator.accumulation[perspective][i], biases_, - kHalfDimensions * sizeof(BiasType)); - } else { - std::memset(accumulator.accumulation[perspective][i], 0, - kHalfDimensions * sizeof(BiasType)); - } + // Gather all features to be updated. This code assumes HalfKP features + // only and doesn't support refresh triggers. + static_assert(std::is_same_v>, + RawFeatures>); + Features::IndexList removed[2], added[2]; + Features::HalfKP::AppendChangedIndices(pos, + next->dirtyPiece, c, &removed[0], &added[0]); + for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) + Features::HalfKP::AppendChangedIndices(pos, + st2->dirtyPiece, c, &removed[1], &added[1]); - for (const auto index : active_indices[perspective]) { - const IndexType offset = kHalfDimensions * index; - - for (IndexType j = 0; j < kHalfDimensions; ++j) - accumulator.accumulation[perspective][i][j] += weights_[offset + j]; - } -#endif - } - - } - -#if defined(USE_MMX) - _mm_empty(); -#endif - - accumulator.computed_accumulation = true; - } - - // Calculate cumulative value using difference calculation - void update_accumulator(const Position& pos) const { + // Mark the accumulators as computed. + next->accumulator.state[c] = COMPUTED; + pos.state()->accumulator.state[c] = COMPUTED; + // Now update the accumulators listed in info[], where the last element is a sentinel. + StateInfo *info[3] = + { next, next == pos.state() ? nullptr : pos.state(), nullptr }; #ifdef VECTOR - // Gcc-10.2 unnecessarily spills AVX2 registers if this array - // is defined in the VECTOR code below, once in each branch - vec_t acc[kNumRegs]; - #endif - const auto& prev_accumulator = pos.state()->previous->accumulator; - auto& accumulator = pos.state()->accumulator; - for (IndexType i = 0; i < kRefreshTriggers.size(); ++i) { - Features::IndexList removed_indices[2], added_indices[2]; - bool reset[2] = { false, false }; - RawFeatures::append_changed_indices(pos, kRefreshTriggers[i], - removed_indices, added_indices, reset); - -#ifdef VECTOR - for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) { - for (Color perspective : { WHITE, BLACK }) { + for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) + { + // Load accumulator auto accTile = reinterpret_cast( - &accumulator.accumulation[perspective][i][j * kTileHeight]); - - if (reset[perspective]) { - if (i == 0) { - auto biasesTile = reinterpret_cast( - &biases_[j * kTileHeight]); - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = biasesTile[k]; - } else { - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = vec_zero; - } - } else { - auto prevAccTile = reinterpret_cast( - &prev_accumulator.accumulation[perspective][i][j * kTileHeight]); - - for (IndexType k = 0; k < kNumRegs; ++k) - acc[k] = vec_load(&prevAccTile[k]); + &st->accumulator.accumulation[c][0][j * kTileHeight]); + for (IndexType k = 0; k < kNumRegs; ++k) + acc[k] = vec_load(&accTile[k]); + for (IndexType i = 0; info[i]; ++i) + { // Difference calculation for the deactivated features - for (const auto index : removed_indices[perspective]) { + for (const auto index : removed[i]) + { const IndexType offset = kHalfDimensions * index + j * kTileHeight; auto column = reinterpret_cast(&weights_[offset]); - for (IndexType k = 0; k < kNumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } - } - { // Difference calculation for the activated features - for (const auto index : added_indices[perspective]) { + // Difference calculation for the activated features + for (const auto index : added[i]) + { const IndexType offset = kHalfDimensions * index + j * kTileHeight; auto column = reinterpret_cast(&weights_[offset]); - for (IndexType k = 0; k < kNumRegs; ++k) acc[k] = vec_add_16(acc[k], column[k]); } + + // Store accumulator + accTile = reinterpret_cast( + &info[i]->accumulator.accumulation[c][0][j * kTileHeight]); + for (IndexType k = 0; k < kNumRegs; ++k) + vec_store(&accTile[k], acc[k]); + } + } + + #else + for (IndexType i = 0; info[i]; ++i) + { + std::memcpy(info[i]->accumulator.accumulation[c][0], + st->accumulator.accumulation[c][0], + kHalfDimensions * sizeof(BiasType)); + st = info[i]; + + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = kHalfDimensions * index; + + for (IndexType j = 0; j < kHalfDimensions; ++j) + st->accumulator.accumulation[c][0][j] -= weights_[offset + j]; } + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = kHalfDimensions * index; + + for (IndexType j = 0; j < kHalfDimensions; ++j) + st->accumulator.accumulation[c][0][j] += weights_[offset + j]; + } + } + #endif + } + else + { + // Refresh the accumulator + auto& accumulator = pos.state()->accumulator; + accumulator.state[c] = COMPUTED; + Features::IndexList active; + Features::HalfKP::AppendActiveIndices(pos, c, &active); + + #ifdef VECTOR + for (IndexType j = 0; j < kHalfDimensions / kTileHeight; ++j) + { + auto biasesTile = reinterpret_cast( + &biases_[j * kTileHeight]); for (IndexType k = 0; k < kNumRegs; ++k) + acc[k] = biasesTile[k]; + + for (const auto index : active) + { + const IndexType offset = kHalfDimensions * index + j * kTileHeight; + auto column = reinterpret_cast(&weights_[offset]); + + for (unsigned k = 0; k < kNumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = reinterpret_cast( + &accumulator.accumulation[c][0][j * kTileHeight]); + for (unsigned k = 0; k < kNumRegs; k++) vec_store(&accTile[k], acc[k]); } + + #else + std::memcpy(accumulator.accumulation[c][0], biases_, + kHalfDimensions * sizeof(BiasType)); + + for (const auto index : active) + { + const IndexType offset = kHalfDimensions * index; + + for (IndexType j = 0; j < kHalfDimensions; ++j) + accumulator.accumulation[c][0][j] += weights_[offset + j]; + } + #endif } -#if defined(USE_MMX) + + #if defined(USE_MMX) _mm_empty(); -#endif - -#else - for (Color perspective : { WHITE, BLACK }) { - - if (reset[perspective]) { - if (i == 0) { - std::memcpy(accumulator.accumulation[perspective][i], biases_, - kHalfDimensions * sizeof(BiasType)); - } else { - std::memset(accumulator.accumulation[perspective][i], 0, - kHalfDimensions * sizeof(BiasType)); - } - } else { - std::memcpy(accumulator.accumulation[perspective][i], - prev_accumulator.accumulation[perspective][i], - kHalfDimensions * sizeof(BiasType)); - // Difference calculation for the deactivated features - for (const auto index : removed_indices[perspective]) { - const IndexType offset = kHalfDimensions * index; - - for (IndexType j = 0; j < kHalfDimensions; ++j) - accumulator.accumulation[perspective][i][j] -= weights_[offset + j]; - } - } - { // Difference calculation for the activated features - for (const auto index : added_indices[perspective]) { - const IndexType offset = kHalfDimensions * index; - - for (IndexType j = 0; j < kHalfDimensions; ++j) - accumulator.accumulation[perspective][i][j] += weights_[offset + j]; - } - } - } -#endif - } - accumulator.computed_accumulation = true; + #endif } using BiasType = std::int16_t; using WeightType = std::int16_t; - // Make the learning class a friend - friend class Trainer; - alignas(kCacheLineSize) BiasType biases_[kHalfDimensions]; alignas(kCacheLineSize) WeightType weights_[kHalfDimensions * kInputDimensions]; @@ -512,4 +411,4 @@ namespace Stockfish::Eval::NNUE { } // namespace Stockfish::Eval::NNUE -#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED \ No newline at end of file diff --git a/src/position.cpp b/src/position.cpp index 57bc7c7b..c515f253 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -707,7 +707,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulator.computed_accumulation = false; + st->accumulator.state[WHITE] = Eval::NNUE::EMPTY; + st->accumulator.state[BLACK] = Eval::NNUE::EMPTY; auto& dp = st->dirtyPiece; dp.dirty_num = 1; @@ -1006,7 +1007,8 @@ void Position::do_null_move(StateInfo& newSt) { st = &newSt; // Used by NNUE - st->accumulator.computed_accumulation = false; + st->accumulator.state[WHITE] = Eval::NNUE::EMPTY; + st->accumulator.state[BLACK] = Eval::NNUE::EMPTY; auto& dp = st->dirtyPiece; dp.dirty_num = 0; diff --git a/src/position.h b/src/position.h index 27a0a596..e816c541 100644 --- a/src/position.h +++ b/src/position.h @@ -197,7 +197,7 @@ public: //static std::string sfen_from_rawdata(Piece board[81], Hand hands[2], Color turn, int gamePly); // Returns the position of the ball on the c side. - Square king_square(Color c) const { return pieceList[make_piece(c, KING)][0]; } + Square king_square(Color c) const { return lsb(pieces(c, KING)); } private: // Initialization helpers (used while setting up a position) @@ -445,6 +445,8 @@ inline StateInfo* Position::state() const { return st; } +static const char* const StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + } // namespace Stockfish #endif // #ifndef POSITION_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 6f2cfc7b..a179c1d1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -222,7 +222,7 @@ void MainThread::search() { Time.init(Limits, us, rootPos.game_ply()); TT.new_search(); - Eval::NNUE::verify_eval_file_loaded(); + Eval::NNUE::verify(); if (rootMoves.empty()) { @@ -2143,7 +2143,7 @@ namespace Search return { mated_in(/*ss->ply*/ 0 + 1), {} }; } - auto bestValue = ::qsearch(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, 0); + auto bestValue = Stockfish::qsearch(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, 0); // Returns the PV obtained. std::vector pvs; @@ -2249,7 +2249,7 @@ namespace Search while (true) { Depth adjustedDepth = std::max(1, rootDepth); - bestValue = ::search(pos, ss, alpha, beta, adjustedDepth, false); + bestValue = Stockfish::search(pos, ss, alpha, beta, adjustedDepth, false); stable_sort(rootMoves.begin() + pvIdx, rootMoves.end()); //my_stable_sort(pos.this_thread()->thread_id(),&rootMoves[0] + pvIdx, rootMoves.size() - pvIdx); diff --git a/src/tools/convert.cpp b/src/tools/convert.cpp index 8d8af4bb..0be7070c 100644 --- a/src/tools/convert.cpp +++ b/src/tools/convert.cpp @@ -28,7 +28,7 @@ using namespace std; -namespace Tools +namespace Stockfish::Tools { bool fen_is_ok(Position& pos, std::string input_fen) { std::string pos_fen = pos.fen(); diff --git a/src/tools/convert.h b/src/tools/convert.h index 9d628540..0bd9ee23 100644 --- a/src/tools/convert.h +++ b/src/tools/convert.h @@ -5,7 +5,7 @@ #include #include -namespace Tools { +namespace Stockfish::Tools { void convert(std::istringstream& is); void convert_bin_from_pgn_extract(std::istringstream& is); diff --git a/src/tools/gensfen.cpp b/src/tools/gensfen.cpp index 7021648a..1e721a3e 100644 --- a/src/tools/gensfen.cpp +++ b/src/tools/gensfen.cpp @@ -34,7 +34,7 @@ using namespace std; -namespace Tools +namespace Stockfish::Tools { // Class to generate sfen with multiple threads struct Gensfen @@ -962,7 +962,7 @@ namespace Tools << " - draw by insuff. mat. = " << params.detect_draw_by_insufficient_mating_material << endl; // Show if the training data generator uses NNUE. - Eval::NNUE::verify_eval_file_loaded(); + Eval::NNUE::verify(); Threads.main()->ponder = false; diff --git a/src/tools/gensfen.h b/src/tools/gensfen.h index 13eb0880..a8505474 100644 --- a/src/tools/gensfen.h +++ b/src/tools/gensfen.h @@ -5,7 +5,7 @@ #include -namespace Tools { +namespace Stockfish::Tools { // Automatic generation of teacher position void gensfen(std::istringstream& is); diff --git a/src/tools/gensfen_nonpv.cpp b/src/tools/gensfen_nonpv.cpp index 7edf9a33..9775f80e 100644 --- a/src/tools/gensfen_nonpv.cpp +++ b/src/tools/gensfen_nonpv.cpp @@ -34,7 +34,7 @@ using namespace std; -namespace Tools +namespace Stockfish::Tools { // Class to generate sfen with multiple threads struct GensfenNonPv @@ -476,7 +476,7 @@ namespace Tools << " - count = " << count << endl; // Show if the training data generator uses NNUE. - Eval::NNUE::verify_eval_file_loaded(); + Eval::NNUE::verify(); Threads.main()->ponder = false; diff --git a/src/tools/gensfen_nonpv.h b/src/tools/gensfen_nonpv.h index 31229d5e..842dd70b 100644 --- a/src/tools/gensfen_nonpv.h +++ b/src/tools/gensfen_nonpv.h @@ -3,7 +3,7 @@ #include -namespace Tools { +namespace Stockfish::Tools { // Automatic generation of teacher position void gensfen_nonpv(std::istringstream& is); diff --git a/src/tools/opening_book.cpp b/src/tools/opening_book.cpp index 3d3842ef..63ff7ed1 100644 --- a/src/tools/opening_book.cpp +++ b/src/tools/opening_book.cpp @@ -2,7 +2,7 @@ #include -namespace Tools { +namespace Stockfish::Tools { EpdOpeningBook::EpdOpeningBook(const std::string& file, PRNG& prng) : OpeningBook(file) diff --git a/src/tools/opening_book.h b/src/tools/opening_book.h index 562be0f9..8323ed10 100644 --- a/src/tools/opening_book.h +++ b/src/tools/opening_book.h @@ -13,7 +13,7 @@ #include #include -namespace Tools { +namespace Stockfish::Tools { struct OpeningBook { diff --git a/src/tools/packed_sfen.h b/src/tools/packed_sfen.h index 8080200f..969405ea 100644 --- a/src/tools/packed_sfen.h +++ b/src/tools/packed_sfen.h @@ -4,7 +4,7 @@ #include #include -namespace Tools { +namespace Stockfish::Tools { // packed sfen struct PackedSfen { std::uint8_t data[32]; }; diff --git a/src/tools/sfen_packer.cpp b/src/tools/sfen_packer.cpp index a51fd193..a8e1fec2 100644 --- a/src/tools/sfen_packer.cpp +++ b/src/tools/sfen_packer.cpp @@ -11,7 +11,7 @@ using namespace std; -namespace Tools { +namespace Stockfish::Tools { // Class that handles bitstream // useful when doing aspect encoding @@ -260,15 +260,11 @@ namespace Tools { pos.clear(); std::memset(si, 0, sizeof(StateInfo)); - std::fill_n(&pos.pieceList[0][0], sizeof(pos.pieceList) / sizeof(Square), SQ_NONE); pos.st = si; // Active color pos.sideToMove = (Color)stream.read_one_bit(); - pos.pieceList[W_KING][0] = SQUARE_NB; - pos.pieceList[B_KING][0] = SQUARE_NB; - // First the position of the ball for (auto c : Colors) pos.board[stream.read_n_bit(6)] = make_piece(c, KING); diff --git a/src/tools/sfen_packer.h b/src/tools/sfen_packer.h index c99d7985..42093043 100644 --- a/src/tools/sfen_packer.h +++ b/src/tools/sfen_packer.h @@ -7,11 +7,13 @@ #include -class Position; -struct StateInfo; -class Thread; +namespace Stockfish { + class Position; + struct StateInfo; + class Thread; +} -namespace Tools { +namespace Stockfish::Tools { int set_from_packed_sfen(Position& pos, const PackedSfen& sfen, StateInfo* si, Thread* th); PackedSfen sfen_pack(Position& pos); diff --git a/src/tools/sfen_reader.h b/src/tools/sfen_reader.h index 064efe53..28a6104d 100644 --- a/src/tools/sfen_reader.h +++ b/src/tools/sfen_reader.h @@ -17,7 +17,7 @@ #include #include -namespace Tools{ +namespace Stockfish::Tools{ enum struct SfenReaderMode { diff --git a/src/tools/sfen_stream.h b/src/tools/sfen_stream.h index bb731457..6122e091 100644 --- a/src/tools/sfen_stream.h +++ b/src/tools/sfen_stream.h @@ -10,7 +10,7 @@ #include #include -namespace Tools { +namespace Stockfish::Tools { enum struct SfenOutputType { diff --git a/src/tools/sfen_writer.h b/src/tools/sfen_writer.h index 37c36491..10dc98d6 100644 --- a/src/tools/sfen_writer.h +++ b/src/tools/sfen_writer.h @@ -17,9 +17,7 @@ #include #include -using namespace std; - -namespace Tools { +namespace Stockfish::Tools { // Helper class for exporting Sfen struct SfenWriter @@ -28,13 +26,13 @@ namespace Tools { static constexpr size_t SFEN_WRITE_SIZE = 5000; // File name to write and number of threads to create - SfenWriter(string filename_, int thread_num, uint64_t save_count, SfenOutputType sfen_output_type) + SfenWriter(std::string filename_, int thread_num, uint64_t save_count, SfenOutputType sfen_output_type) { sfen_buffers_pool.reserve((size_t)thread_num * 10); sfen_buffers.resize(thread_num); auto out = sync_region_cout.new_region(); - out << "INFO (sfen_writer): Creating new data file at " << filename_ << endl; + out << "INFO (sfen_writer): Creating new data file at " << filename_ << std::endl; sfen_format = sfen_output_type; output_file_stream = create_new_sfen_output(filename_, sfen_format); @@ -121,7 +119,7 @@ namespace Tools { { while (!finished || sfen_buffers_pool.size()) { - vector> buffers; + std::vector> buffers; { std::unique_lock lk(mutex); @@ -157,11 +155,11 @@ namespace Tools { // Rename the file and open it again. // Add ios::app in consideration of overwriting. // (Depending on the operation, it may not be necessary.) - string new_filename = filename + "_" + std::to_string(n); + std::string new_filename = filename + "_" + std::to_string(n); output_file_stream = create_new_sfen_output(new_filename, sfen_format); auto out = sync_region_cout.new_region(); - out << "INFO (sfen_writer): Creating new data file at " << new_filename << endl; + out << "INFO (sfen_writer): Creating new data file at " << new_filename << std::endl; } } } @@ -182,7 +180,7 @@ namespace Tools { std::thread file_worker_thread; // Flag that all threads have finished - atomic finished; + std::atomic finished; SfenOutputType sfen_format; diff --git a/src/tools/stats.cpp b/src/tools/stats.cpp index c40411b9..c154fc10 100644 --- a/src/tools/stats.cpp +++ b/src/tools/stats.cpp @@ -25,7 +25,7 @@ #include #include -namespace Tools::Stats +namespace Stockfish::Tools::Stats { struct StatisticGathererBase { @@ -398,7 +398,7 @@ namespace Tools::Stats m_castling += 1; else if (type_of(move) == PROMOTION) m_promotion += 1; - else if (type_of(move) == ENPASSANT) + else if (type_of(move) == EN_PASSANT) m_enpassant += 1; else if (type_of(move) == NORMAL) m_normal += 1; diff --git a/src/tools/stats.h b/src/tools/stats.h index c4a13d19..3276bab6 100644 --- a/src/tools/stats.h +++ b/src/tools/stats.h @@ -3,7 +3,7 @@ #include -namespace Tools::Stats { +namespace Stockfish::Tools::Stats { void gather_statistics(std::istringstream& is); diff --git a/src/tools/transform.cpp b/src/tools/transform.cpp index b3d1f94b..0b7f5a27 100644 --- a/src/tools/transform.cpp +++ b/src/tools/transform.cpp @@ -21,7 +21,7 @@ #include #include -namespace Tools +namespace Stockfish::Tools { using CommandFunc = void(*)(std::istringstream&); diff --git a/src/tools/transform.h b/src/tools/transform.h index f202b55c..dcaf9a1d 100644 --- a/src/tools/transform.h +++ b/src/tools/transform.h @@ -3,7 +3,7 @@ #include -namespace Tools { +namespace Stockfish::Tools { void transform(std::istringstream& is); diff --git a/src/uci.cpp b/src/uci.cpp index 725056bd..fdce17aa 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -45,9 +45,6 @@ namespace Stockfish { extern vector setup_bench(const Position&, istream&); -// FEN string of the initial position, normal chess -const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - namespace { // position() is called when engine receives the "position" UCI command. @@ -93,7 +90,7 @@ namespace { Position p; p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); - Eval::NNUE::verify_eval_file_loaded(); + Eval::NNUE::verify(); sync_cout << "\n" << Eval::trace(p) << sync_endl; }