/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2026 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 . */ // Class for difference calculation of NNUE evaluation function #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED #include #include #include #include #include #include "../types.h" #include "nnue_architecture.h" #include "nnue_common.h" namespace Stockfish { class Position; } namespace Stockfish::Eval::NNUE { template struct alignas(CacheLineSize) Accumulator; template class FeatureTransformer; // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { std::array, COLOR_NB> accumulation; std::array, COLOR_NB> psqtAccumulation; std::array computed = {}; }; // AccumulatorCaches struct provides per-thread accumulator caches, where each // cache contains multiple entries for each of the possible king squares. // When the accumulator needs to be refreshed, the cached entry is used to more // efficiently update the accumulator, instead of rebuilding it from scratch. // This idea, was first described by Luecx (author of Koivisto) and // is commonly referred to as "Finny Tables". struct AccumulatorCaches { template AccumulatorCaches(const Networks& networks) { clear(networks); } template struct alignas(CacheLineSize) Cache { struct alignas(CacheLineSize) Entry { std::array accumulation; std::array psqtAccumulation; std::array pieces; Bitboard pieceBB; // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top void clear(const std::array& biases) { accumulation = biases; std::memset(reinterpret_cast(this) + offsetof(Entry, psqtAccumulation), 0, sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; template void clear(const Network& network) { for (auto& entries1D : entries) for (auto& entry : entries1D) entry.clear(network.featureTransformer.biases); } std::array& operator[](Square sq) { return entries[sq]; } std::array, SQUARE_NB> entries; }; template void clear(const Networks& networks) { big.clear(networks.big); small.clear(networks.small); } Cache big; Cache small; }; template struct AccumulatorState { Accumulator accumulatorBig; Accumulator accumulatorSmall; typename FeatureSet::DiffType diff; template auto& acc() noexcept { static_assert(Size == TransformedFeatureDimensionsBig || Size == TransformedFeatureDimensionsSmall, "Invalid size for accumulator"); if constexpr (Size == TransformedFeatureDimensionsBig) return accumulatorBig; else if constexpr (Size == TransformedFeatureDimensionsSmall) return accumulatorSmall; } template const auto& acc() const noexcept { static_assert(Size == TransformedFeatureDimensionsBig || Size == TransformedFeatureDimensionsSmall, "Invalid size for accumulator"); if constexpr (Size == TransformedFeatureDimensionsBig) return accumulatorBig; else if constexpr (Size == TransformedFeatureDimensionsSmall) return accumulatorSmall; } void reset(const typename FeatureSet::DiffType& dp) noexcept { diff = dp; accumulatorBig.computed.fill(false); accumulatorSmall.computed.fill(false); } typename FeatureSet::DiffType& reset() noexcept { accumulatorBig.computed.fill(false); accumulatorSmall.computed.fill(false); return diff; } }; class AccumulatorStack { public: static constexpr std::size_t MaxSize = MAX_PLY + 1; template [[nodiscard]] const AccumulatorState& latest() const noexcept; void reset() noexcept; std::pair push() noexcept; void pop() noexcept; template void evaluate(const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; private: template [[nodiscard]] AccumulatorState& mut_latest() noexcept; template [[nodiscard]] const std::array, MaxSize>& accumulators() const noexcept; template [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; template void evaluate_side(Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; template [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept; template void forward_update_incremental(Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept; template void backward_update_incremental(Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; std::array, MaxSize> psq_accumulators; std::array, MaxSize> threat_accumulators; std::size_t size = 1; }; } // namespace Stockfish::Eval::NNUE #endif // NNUE_ACCUMULATOR_H_INCLUDED