diff --git a/AUTHORS b/AUTHORS index abae401c1..36b2b6f79 100644 --- a/AUTHORS +++ b/AUTHORS @@ -46,6 +46,7 @@ Bryan Cross (crossbr) candirufish Chess13234 Chris Cain (ceebo) +Ciekce clefrks Clemens L. (rn5f107s2) Cody Ho (aesrentai) diff --git a/src/engine.cpp b/src/engine.cpp index 72a37ce9b..e8da24aa9 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -141,14 +141,18 @@ void Engine::verify_networks() const { } void Engine::load_networks() { - networks.big.load(binaryDirectory, options["EvalFile"]); - networks.small.load(binaryDirectory, options["EvalFileSmall"]); + load_big_network(options["EvalFile"]); + load_small_network(options["EvalFileSmall"]); } -void Engine::load_big_network(const std::string& file) { networks.big.load(binaryDirectory, file); } +void Engine::load_big_network(const std::string& file) { + networks.big.load(binaryDirectory, file); + threads.clear(); +} void Engine::load_small_network(const std::string& file) { networks.small.load(binaryDirectory, file); + threads.clear(); } void Engine::save_network(const std::pair, std::string> files[2]) { diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 6e101e783..5be7e7a1b 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -56,36 +56,32 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, int simpleEval = simple_eval(pos, pos.side_to_move()); bool smallNet = std::abs(simpleEval) > SmallNetThreshold; - bool psqtOnly = std::abs(simpleEval) > PsqtOnlyThreshold; int nnueComplexity; int v; - Value nnue = smallNet ? networks.small.evaluate(pos, nullptr, true, &nnueComplexity, psqtOnly) - : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity, false); + Value nnue = smallNet ? networks.small.evaluate(pos, &caches.small, true, &nnueComplexity) + : networks.big.evaluate(pos, &caches.big, true, &nnueComplexity); - const auto adjustEval = [&](int optDiv, int nnueDiv, int npmDiv, int pawnCountConstant, - int pawnCountMul, int npmConstant, int evalDiv, - int shufflingConstant, int shufflingDiv) { + const auto adjustEval = [&](int nnueDiv, int pawnCountConstant, int pawnCountMul, + int npmConstant, int evalDiv, int shufflingConstant) { // Blend optimism and eval with nnue complexity and material imbalance - optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / optDiv; + optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 584; nnue -= nnue * (nnueComplexity * 5 / 3) / nnueDiv; - int npm = pos.non_pawn_material() / npmDiv; + int npm = pos.non_pawn_material() / 64; v = (nnue * (npm + pawnCountConstant + pawnCountMul * pos.count()) + optimism * (npmConstant + npm)) / evalDiv; // Damp down the evaluation linearly when shuffling int shuffling = pos.rule50_count(); - v = v * (shufflingConstant - shuffling) / shufflingDiv; + v = v * (shufflingConstant - shuffling) / 207; }; if (!smallNet) - adjustEval(524, 32395, 66, 942, 11, 139, 1058, 178, 204); - else if (psqtOnly) - adjustEval(517, 32857, 65, 908, 7, 155, 1006, 224, 238); + adjustEval(32395, 942, 11, 139, 1058, 178); else - adjustEval(515, 32793, 63, 944, 9, 140, 1067, 206, 206); + adjustEval(32793, 944, 9, 140, 1067, 206); // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); @@ -99,11 +95,11 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Trace scores are from white's point of view std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { - auto caches = std::make_unique(networks); - if (pos.checkers()) return "Final evaluation: none (in check)"; + auto caches = std::make_unique(networks); + std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; diff --git a/src/evaluate.h b/src/evaluate.h index 38615ff7d..2d244ff67 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -29,7 +29,7 @@ class Position; namespace Eval { -constexpr inline int SmallNetThreshold = 1274, PsqtOnlyThreshold = 2389; +constexpr inline int SmallNetThreshold = 1274; // 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 diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index f50fd5262..0796beec5 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -190,8 +190,7 @@ template Value Network::evaluate(const Position& pos, AccumulatorCaches::Cache* cache, bool adjusted, - int* complexity, - bool psqtOnly) const { + int* complexity) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -211,13 +210,12 @@ Value Network::evaluate(const Position& ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = - featureTransformer->transform(pos, cache, transformedFeatures, bucket, psqtOnly); - const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0; + const int bucket = (pos.count() - 1) / 4; + const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); + const auto positional = network[bucket]->propagate(transformedFeatures); if (complexity) - *complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0; + *complexity = std::abs(psqt - positional) / OutputScale; // Give more value to positional evaluation when adjusted flag is set if (adjusted) @@ -263,10 +261,9 @@ void Network::verify(std::string evalfilePath) const { template -void Network::hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnl) const { - featureTransformer->hint_common_access(pos, cache, psqtOnl); +void Network::hint_common_access( + const Position& pos, AccumulatorCaches::Cache* cache) const { + featureTransformer->hint_common_access(pos, cache); } template @@ -295,7 +292,7 @@ Network::trace_evaluate(const Position& for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, cache, transformedFeatures, bucket, false); + featureTransformer->transform(pos, cache, transformedFeatures, bucket); const auto positional = network[bucket]->propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); diff --git a/src/nnue/network.h b/src/nnue/network.h index df59732d9..23f566630 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -56,13 +56,11 @@ class Network { Value evaluate(const Position& pos, AccumulatorCaches::Cache* cache, bool adjusted = false, - int* complexity = nullptr, - bool psqtOnly = false) const; + int* complexity = nullptr) const; void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnl) const; + AccumulatorCaches::Cache* cache) const; void verify(std::string evalfilePath) const; NnueEvalTrace trace_evaluate(const Position& pos, diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index f65385688..b8dcf1e48 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -38,7 +38,6 @@ struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[COLOR_NB][Size]; std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; bool computed[COLOR_NB]; - bool computedPSQT[COLOR_NB]; }; @@ -59,29 +58,26 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Cache { struct alignas(CacheLineSize) Entry { - BiasType accumulation[COLOR_NB][Size]; - PSQTWeightType psqtAccumulation[COLOR_NB][PSQTBuckets]; - Bitboard byColorBB[COLOR_NB][COLOR_NB]; - Bitboard byTypeBB[COLOR_NB][PIECE_TYPE_NB]; + BiasType accumulation[Size]; + PSQTWeightType psqtAccumulation[PSQTBuckets]; + Bitboard byColorBB[COLOR_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; // 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 BiasType* biases) { - std::memset(byColorBB, 0, sizeof(byColorBB)); - std::memset(byTypeBB, 0, sizeof(byTypeBB)); - - std::memcpy(accumulation[WHITE], biases, Size * sizeof(BiasType)); - std::memcpy(accumulation[BLACK], biases, Size * sizeof(BiasType)); - - std::memset(psqtAccumulation, 0, sizeof(psqtAccumulation)); + std::memcpy(accumulation, biases, sizeof(accumulation)); + std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, + sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; template void clear(const Network& network) { - for (auto& entry : entries) - entry.clear(network.featureTransformer->biases); + for (auto& entries1D : entries) + for (auto& entry : entries1D) + entry.clear(network.featureTransformer->biases); } void clear(const BiasType* biases) { @@ -89,19 +85,19 @@ struct AccumulatorCaches { entry.clear(biases); } - Entry& operator[](Square sq) { return entries[sq]; } + std::array& operator[](Square sq) { return entries[sq]; } - std::array entries; + std::array, SQUARE_NB> entries; }; template void clear(const Networks& networks) { big.clear(networks.big); + small.clear(networks.small); } - // When adding a new cache for a network, i.e. the smallnet - // the appropriate condition must be added to FeatureTransformer::update_accumulator_refresh. - Cache big; + Cache big; + Cache small; }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index edff9193e..2b3248162 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -307,10 +307,9 @@ class FeatureTransformer { std::int32_t transform(const Position& pos, AccumulatorCaches::Cache* cache, OutputType* output, - int bucket, - bool psqtOnly) const { - update_accumulator(pos, cache, psqtOnly); - update_accumulator(pos, cache, psqtOnly); + int bucket) const { + update_accumulator(pos, cache); + update_accumulator(pos, cache); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; @@ -318,9 +317,6 @@ class FeatureTransformer { (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; - if (psqtOnly) - return psqt; - const auto& accumulation = (pos.state()->*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) @@ -373,23 +369,20 @@ class FeatureTransformer { } // end of function transform() void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - hint_common_access_for_perspective(pos, cache, psqtOnly); - hint_common_access_for_perspective(pos, cache, psqtOnly); + AccumulatorCaches::Cache* cache) const { + hint_common_access_for_perspective(pos, cache); + hint_common_access_for_perspective(pos, cache); } private: template [[nodiscard]] std::pair - try_find_computed_accumulator(const Position& pos, bool psqtOnly) const { + try_find_computed_accumulator(const Position& pos) const { // 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 = FeatureSet::refresh_cost(pos); - while (st->previous - && (!(st->*accPtr).computedPSQT[Perspective] - || (!psqtOnly && !(st->*accPtr).computed[Perspective]))) + while (st->previous && !(st->*accPtr).computed[Perspective]) { // This governs when a full feature refresh is needed and how many // updates are better than just one full refresh. @@ -402,19 +395,24 @@ class FeatureTransformer { return {st, next}; } - // NOTE: The parameter states_to_update is an array of position states, ending with nullptr. + // NOTE: The parameter states_to_update is an array of position states. // All states must be sequential, that is states_to_update[i] must either be reachable - // by repeatedly applying ->previous from states_to_update[i+1] or - // states_to_update[i] == nullptr. + // by repeatedly applying ->previous from states_to_update[i+1]. // computed_st must be reachable by repeatedly applying ->previous on - // states_to_update[0], if not nullptr. + // states_to_update[0]. template void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, - StateInfo* states_to_update[N], - bool psqtOnly) const { + StateInfo* states_to_update[N]) const { static_assert(N > 0); - assert(states_to_update[N - 1] == nullptr); + assert([&]() { + for (size_t i = 0; i < N; ++i) + { + if (states_to_update[i] == nullptr) + return false; + } + return true; + }()); #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array @@ -423,11 +421,7 @@ class FeatureTransformer { psqt_vec_t psqt[NumPsqtRegs]; #endif - if (states_to_update[0] == nullptr) - return; - // Update incrementally going back through states_to_update. - // Gather all features to be updated. const Square ksq = pos.square(Perspective); @@ -435,28 +429,17 @@ class FeatureTransformer { // That might depend on the feature set and generally relies on the // feature set's update cost calculation to be correct and never allow // updates with more added/removed features than MaxActiveDimensions. - FeatureSet::IndexList removed[N - 1], added[N - 1]; + FeatureSet::IndexList removed[N], added[N]; + for (int i = N - 1; i >= 0; --i) { - int i = - N - - 2; // Last potential state to update. Skip last element because it must be nullptr. - while (states_to_update[i] == nullptr) - --i; + (states_to_update[i]->*accPtr).computed[Perspective] = true; - StateInfo* st2 = states_to_update[i]; + const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - for (; i >= 0; --i) - { - (states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly; - (states_to_update[i]->*accPtr).computedPSQT[Perspective] = true; - - const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1]; - - for (; st2 != end_state; st2 = st2->previous) - FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, - removed[i], added[i]); - } + for (StateInfo* st2 = states_to_update[i]; st2 != end_state; st2 = st2->previous) + FeatureSet::append_changed_indices(ksq, st2->dirtyPiece, removed[i], + added[i]); } StateInfo* st = computed_st; @@ -464,39 +447,35 @@ class FeatureTransformer { // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. #ifdef VECTOR - if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2) - && added[0].size() == 1) + if (N == 1 && (removed[0].size() == 1 || removed[0].size() == 2) && added[0].size() == 1) { assert(states_to_update[0]); - if (!psqtOnly) + auto accIn = + reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); + auto accOut = reinterpret_cast( + &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + + const IndexType offsetR0 = HalfDimensions * removed[0][0]; + auto columnR0 = reinterpret_cast(&weights[offsetR0]); + const IndexType offsetA = HalfDimensions * added[0][0]; + auto columnA = reinterpret_cast(&weights[offsetA]); + + if (removed[0].size() == 1) { - auto accIn = - reinterpret_cast(&(st->*accPtr).accumulation[Perspective][0]); - auto accOut = reinterpret_cast( - &(states_to_update[0]->*accPtr).accumulation[Perspective][0]); + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); + } + else + { + const IndexType offsetR1 = HalfDimensions * removed[0][1]; + auto columnR1 = reinterpret_cast(&weights[offsetR1]); - const IndexType offsetR0 = HalfDimensions * removed[0][0]; - auto columnR0 = reinterpret_cast(&weights[offsetR0]); - const IndexType offsetA = HalfDimensions * added[0][0]; - auto columnA = reinterpret_cast(&weights[offsetA]); - - if (removed[0].size() == 1) - { - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]); - } - else - { - const IndexType offsetR1 = HalfDimensions * removed[0][1]; - auto columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); - ++k) - accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), - vec_add_16(columnR0[k], columnR1[k])); - } + for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); + ++k) + accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]), + vec_add_16(columnR0[k], columnR1[k])); } auto accPsqtIn = @@ -530,43 +509,41 @@ class FeatureTransformer { } else { - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTileIn = reinterpret_cast( + &(st->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTileIn[k]); + + for (IndexType i = 0; i < N; ++i) { - // Load accumulator - auto accTileIn = reinterpret_cast( - &(st->*accPtr).accumulation[Perspective][j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_load(&accTileIn[k]); - - for (IndexType i = 0; states_to_update[i]; ++i) + // Difference calculation for the deactivated features + for (const auto index : removed[i]) { - // Difference calculation for the deactivated features - for (const auto index : removed[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - - // Difference calculation for the activated features - for (const auto index : added[i]) - { - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - // Store accumulator - auto accTileOut = - reinterpret_cast(&(states_to_update[i]->*accPtr) - .accumulation[Perspective][j * TileHeight]); + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); for (IndexType k = 0; k < NumRegs; ++k) - vec_store(&accTileOut[k], acc[k]); + acc[k] = vec_sub_16(acc[k], column[k]); } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + auto accTileOut = reinterpret_cast( + &(states_to_update[i]->*accPtr).accumulation[Perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTileOut[k], acc[k]); } + } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { @@ -576,7 +553,7 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_load_psqt(&accTilePsqtIn[k]); - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { // Difference calculation for the deactivated features for (const auto index : removed[i]) @@ -606,12 +583,10 @@ class FeatureTransformer { } } #else - for (IndexType i = 0; states_to_update[i]; ++i) + for (IndexType i = 0; i < N; ++i) { - if (!psqtOnly) - std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], - (st->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); + std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective], + (st->*accPtr).accumulation[Perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) (states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] = @@ -622,12 +597,9 @@ class FeatureTransformer { // Difference calculation for the deactivated features for (const auto index : removed[i]) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] -= @@ -637,12 +609,9 @@ class FeatureTransformer { // Difference calculation for the activated features for (const auto index : added[i]) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + (st->*accPtr).accumulation[Perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) (st->*accPtr).psqtAccumulation[Perspective][k] += @@ -657,22 +626,16 @@ class FeatureTransformer { AccumulatorCaches::Cache* cache) const { assert(cache != nullptr); - Square ksq = pos.square(Perspective); - - auto& entry = (*cache)[ksq]; - - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; - accumulator.computedPSQT[Perspective] = true; - + Square ksq = pos.square(Perspective); + auto& entry = (*cache)[ksq][Perspective]; FeatureSet::IndexList removed, added; + for (Color c : {WHITE, BLACK}) { for (PieceType pt = PAWN; pt <= KING; ++pt) { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = - entry.byColorBB[Perspective][c] & entry.byTypeBB[Perspective][pt]; + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; const Bitboard newBB = pos.pieces(c, pt); Bitboard toRemove = oldBB & ~newBB; Bitboard toAdd = newBB & ~oldBB; @@ -690,27 +653,33 @@ class FeatureTransformer { } } + auto& accumulator = pos.state()->*accPtr; + accumulator.computed[Perspective] = true; + #ifdef VECTOR vec_t acc[NumRegs]; psqt_vec_t psqt[NumPsqtRegs]; for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { - auto entryTile = - reinterpret_cast(&entry.accumulation[Perspective][j * TileHeight]); + auto entryTile = reinterpret_cast(&entry.accumulation[j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = entryTile[k]; - for (int i = 0; i < int(added.size()); ++i) + int i = 0; + for (; i < int(std::min(removed.size(), added.size())); ++i) { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); + IndexType indexR = removed[i]; + const IndexType offsetR = HalfDimensions * indexR + j * TileHeight; + auto columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = HalfDimensions * indexA + j * TileHeight; + auto columnA = reinterpret_cast(&weights[offsetA]); for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), columnA[k]); } - for (int i = 0; i < int(removed.size()); ++i) + for (; i < int(removed.size()); ++i) { IndexType index = removed[i]; const IndexType offset = HalfDimensions * index + j * TileHeight; @@ -719,6 +688,15 @@ class FeatureTransformer { for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } + for (; i < int(added.size()); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } for (IndexType k = 0; k < NumRegs; k++) vec_store(&entryTile[k], acc[k]); @@ -726,20 +704,11 @@ class FeatureTransformer { for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { - auto entryTilePsqt = reinterpret_cast( - &entry.psqtAccumulation[Perspective][j * PsqtTileHeight]); + auto entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = entryTilePsqt[k]; - for (int i = 0; i < int(added.size()); ++i) - { - IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; - auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } for (int i = 0; i < int(removed.size()); ++i) { IndexType index = removed[i]; @@ -749,140 +718,9 @@ class FeatureTransformer { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&entryTilePsqt[k], psqt[k]); - } - -#else - - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k]; - } - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[Perspective][j] -= weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k]; - } - -#endif - - // The accumulator of the refresh entry has been updated. - // Now copy its content to the actual accumulator we were refreshing - - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation[Perspective], - sizeof(int32_t) * PSQTBuckets); - - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation[Perspective], - sizeof(BiasType) * HalfDimensions); - - for (Color c : {WHITE, BLACK}) - entry.byColorBB[Perspective][c] = pos.pieces(c); - - for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[Perspective][pt] = pos.pieces(pt); - } - - template - void - update_accumulator_refresh(const Position& pos, - [[maybe_unused]] AccumulatorCaches::Cache* cache, - bool psqtOnly) const { - - // When we are refreshing the accumulator of the big net, - // redirect to the version of refresh that uses the refresh table. - // Using the cache for the small net is not beneficial. - if constexpr (HalfDimensions == Eval::NNUE::TransformedFeatureDimensionsBig) - { - update_accumulator_refresh_cache(pos, cache); - return; - } - -#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[NumRegs]; - psqt_vec_t psqt[NumPsqtRegs]; -#endif - - // Refresh the accumulator - // Could be extracted to a separate function because it's done in 2 places, - // but it's unclear if compilers would correctly handle register allocation. - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = !psqtOnly; - accumulator.computedPSQT[Perspective] = true; - FeatureSet::IndexList active; - FeatureSet::append_active_indices(pos, active); - -#ifdef VECTOR - if (!psqtOnly) - for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + for (int i = 0; i < int(added.size()); ++i) { - auto biasesTile = reinterpret_cast(&biases[j * TileHeight]); - for (IndexType k = 0; k < NumRegs; ++k) - acc[k] = biasesTile[k]; - - int i = 0; - for (; i < int(active.size()) - 1; i += 2) - { - IndexType index0 = active[i]; - IndexType index1 = active[i + 1]; - const IndexType offset0 = HalfDimensions * index0 + j * TileHeight; - const IndexType offset1 = HalfDimensions * index1 + j * TileHeight; - auto column0 = reinterpret_cast(&weights[offset0]); - auto column1 = reinterpret_cast(&weights[offset1]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_add_16(column0[k], column1[k])); - } - for (; i < int(active.size()); ++i) - { - IndexType index = active[i]; - const IndexType offset = HalfDimensions * index + j * TileHeight; - auto column = reinterpret_cast(&weights[offset]); - - for (unsigned k = 0; k < NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - - auto accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * TileHeight]); - for (unsigned k = 0; k < NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) - { - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = vec_zero_psqt(); - - int i = 0; - for (; i < int(active.size()) - 1; i += 2) - { - IndexType index0 = active[i]; - IndexType index1 = active[i + 1]; - const IndexType offset0 = PSQTBuckets * index0 + j * PsqtTileHeight; - const IndexType offset1 = PSQTBuckets * index1 + j * PsqtTileHeight; - auto columnPsqt0 = reinterpret_cast(&psqtWeights[offset0]); - auto columnPsqt1 = reinterpret_cast(&psqtWeights[offset1]); - - for (std::size_t k = 0; k < NumPsqtRegs; ++k) - psqt[k] = - vec_add_psqt_32(psqt[k], vec_add_psqt_32(columnPsqt0[k], columnPsqt1[k])); - } - for (; i < int(active.size()); ++i) - { - IndexType index = active[i]; + IndexType index = added[i]; const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); @@ -890,40 +728,52 @@ class FeatureTransformer { psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } - auto accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); + vec_store_psqt(&entryTilePsqt[k], psqt[k]); } #else - if (!psqtOnly) - std::memcpy(accumulator.accumulation[Perspective], biases, - HalfDimensions * sizeof(BiasType)); - for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; - - for (const auto index : active) + for (const auto index : removed) { - if (!psqtOnly) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - accumulator.accumulation[Perspective][j] += weights[offset + j]; - } + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += - psqtWeights[index * PSQTBuckets + k]; + entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; } + for (const auto index : added) + { + const IndexType offset = HalfDimensions * index; + for (IndexType j = 0; j < HalfDimensions; ++j) + entry.accumulation[j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; + } + #endif + + // The accumulator of the refresh entry has been updated. + // Now copy its content to the actual accumulator we were refreshing + + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + sizeof(BiasType) * HalfDimensions); + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, + sizeof(int32_t) * PSQTBuckets); + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[pt] = pos.pieces(pt); } template void hint_common_access_for_perspective(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { // Works like update_accumulator, but performs less work. // Updates ONLY the accumulator for pos. @@ -931,33 +781,28 @@ class FeatureTransformer { // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. // Fast early exit. - if ((pos.state()->*accPtr).computed[Perspective] - || (psqtOnly && (pos.state()->*accPtr).computedPSQT[Perspective])) + if ((pos.state()->*accPtr).computed[Perspective]) return; - auto [oldest_st, _] = try_find_computed_accumulator(pos, psqtOnly); + auto [oldest_st, _] = try_find_computed_accumulator(pos); - if ((oldest_st->*accPtr).computed[Perspective] - || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) + if ((oldest_st->*accPtr).computed[Perspective]) { // Only update current position accumulator to minimize work. - StateInfo* states_to_update[2] = {pos.state(), nullptr}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + StateInfo* states_to_update[1] = {pos.state()}; + update_accumulator_incremental(pos, oldest_st, states_to_update); } else - update_accumulator_refresh(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache); } template void update_accumulator(const Position& pos, - AccumulatorCaches::Cache* cache, - bool psqtOnly) const { + AccumulatorCaches::Cache* cache) const { - auto [oldest_st, next] = try_find_computed_accumulator(pos, psqtOnly); + auto [oldest_st, next] = try_find_computed_accumulator(pos); - if ((oldest_st->*accPtr).computed[Perspective] - || (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective])) + if ((oldest_st->*accPtr).computed[Perspective]) { if (next == nullptr) return; @@ -967,14 +812,21 @@ class FeatureTransformer { // 1. for the current position // 2. the next accumulator after the computed one // The heuristic may change in the future. - StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(), - nullptr}; + if (next == pos.state()) + { + StateInfo* states_to_update[1] = {next}; - update_accumulator_incremental(pos, oldest_st, states_to_update, - psqtOnly); + update_accumulator_incremental(pos, oldest_st, states_to_update); + } + else + { + StateInfo* states_to_update[2] = {next, pos.state()}; + + update_accumulator_incremental(pos, oldest_st, states_to_update); + } } else - update_accumulator_refresh(pos, cache, psqtOnly); + update_accumulator_refresh_cache(pos, cache); } template diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 51838fefa..bf73a58bf 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -48,9 +48,9 @@ void hint_common_parent_position(const Position& pos, int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move())); if (simpleEvalAbs > Eval::SmallNetThreshold) - networks.small.hint_common_access(pos, nullptr, simpleEvalAbs > Eval::PsqtOnlyThreshold); + networks.small.hint_common_access(pos, &caches.small); else - networks.big.hint_common_access(pos, &caches.big, false); + networks.big.hint_common_access(pos, &caches.big); } namespace { @@ -148,18 +148,14 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat auto st = pos.state(); pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; Value eval = networks.big.evaluate(pos, &caches.big); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); diff --git a/src/position.cpp b/src/position.cpp index 78e62bda3..b46ba0299 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -680,11 +680,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { ++st->pliesFromNull; // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = - st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = - false; + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; auto& dp = st->dirtyPiece; dp.dirty_num = 1; @@ -968,13 +965,10 @@ void Position::do_null_move(StateInfo& newSt, TranspositionTable& tt) { newSt.previous = st; st = &newSt; - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = - st->accumulatorSmall.computedPSQT[WHITE] = st->accumulatorSmall.computedPSQT[BLACK] = - false; + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = + st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; if (st->epSquare != SQ_NONE) { diff --git a/src/search.cpp b/src/search.cpp index 7299462f6..b2d844fb0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -60,9 +60,9 @@ static constexpr double EvalLevel[10] = {0.981, 0.956, 0.895, 0.949, 0.913, // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 118 - 45 * noTtCutNode; - Value improvingDeduction = 52 * improving * futilityMult / 32; - Value worseningDeduction = (316 + 48 * improving) * oppWorsening * futilityMult / 1024; + Value futilityMult = 126 - 46 * noTtCutNode; + Value improvingDeduction = 58 * improving * futilityMult / 32; + Value worseningDeduction = (323 + 52 * improving) * oppWorsening * futilityMult / 1024; return futilityMult * d - improvingDeduction - worseningDeduction; } @@ -74,15 +74,15 @@ constexpr int futility_move_count(bool improving, Depth depth) { // Add correctionHistory value to raw staticEval and guarantee evaluation does not hit the tablebase range Value to_corrected_static_eval(Value v, const Worker& w, const Position& pos) { auto cv = w.correctionHistory[pos.side_to_move()][pawn_structure_index(pos)]; - v += cv * std::abs(cv) / 9260; + v += cv * std::abs(cv) / 7350; return std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::clamp(214 * d - 318, 16, 1304); } +int stat_bonus(Depth d) { return std::clamp(208 * d - 297, 16, 1406); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return (d < 4 ? 572 * d - 284 : 1355); } +int stat_malus(Depth d) { return (d < 4 ? 520 * d - 312 : 1479); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } @@ -115,8 +115,11 @@ Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply, int r50c); void update_pv(Move* pv, Move move, const Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); -void update_quiet_stats( +void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move); +void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus); void update_all_stats(const Position& pos, Stack* ss, Search::Worker& workerThread, @@ -148,9 +151,6 @@ Search::Worker::Worker(SharedState& sharedState, void Search::Worker::start_searching() { - // Initialize accumulator refresh entries - refreshTable.clear(networks); - // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -352,12 +352,12 @@ void Search::Worker::iterative_deepening() { // Reset aspiration window starting size Value avg = rootMoves[pvIdx].averageScore; - delta = 10 + avg * avg / 11480; + delta = 10 + avg * avg / 9530; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore (~4 Elo) - optimism[us] = 122 * avg / (std::abs(avg) + 92); + optimism[us] = 119 * avg / (std::abs(avg) + 88); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -550,10 +550,12 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h->fill(-65); + h->fill(-60); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int((20.14 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + reductions[i] = int((18.93 + std::log(size_t(options["Threads"])) / 2) * std::log(i)); + + refreshTable.clear(networks); } @@ -642,7 +644,6 @@ Value Search::Worker::search( bestMove = Move::none(); (ss + 2)->killers[0] = (ss + 2)->killers[1] = Move::none(); (ss + 2)->cutoffCnt = 0; - ss->multipleExtensions = (ss - 1)->multipleExtensions; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; ss->statScore = 0; @@ -785,7 +786,7 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering (~9 Elo) if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int bonus = std::clamp(-14 * int((ss - 1)->staticEval + ss->staticEval), -1644, 1384); + int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1796, 1526); bonus = bonus > 0 ? 2 * bonus : bonus / 2; thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) @@ -808,7 +809,7 @@ Value Search::Worker::search( // If eval is really low check with qsearch if it can exceed alpha, if it can't, // return a fail low. // Adjust razor margin according to cutoffCnt. (~1 Elo) - if (eval < alpha - 471 - (275 - 148 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) + if (eval < alpha - 433 - (302 - 141 * ((ss + 1)->cutoffCnt > 3)) * depth * depth) { value = qsearch(pos, ss, alpha - 1, alpha); if (value < alpha) @@ -817,23 +818,23 @@ Value Search::Worker::search( // Step 8. Futility pruning: child node (~40 Elo) // The depth condition is important for mate finding. - if (!ss->ttPv && depth < 12 + if (!ss->ttPv && depth < 11 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 286 + - (ss - 1)->statScore / 254 >= beta && eval >= beta && eval < VALUE_TB_WIN_IN_MAX_PLY && (!ttMove || ttCapture)) return beta > VALUE_TB_LOSS_IN_MAX_PLY ? (eval + beta) / 2 : eval; // Step 9. Null move search with verification search (~35 Elo) - if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 18001 - && eval >= beta && ss->staticEval >= beta - 21 * depth + 312 && !excludedMove + if (!PvNode && (ss - 1)->currentMove != Move::null() && (ss - 1)->statScore < 16993 + && eval >= beta && ss->staticEval >= beta - 19 * depth + 326 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && beta > VALUE_TB_LOSS_IN_MAX_PLY) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 152, 6) + depth / 3 + 4; + Depth R = std::min(int(eval - beta) / 134, 6) + depth / 3 + 4; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -875,13 +876,13 @@ Value Search::Worker::search( return qsearch(pos, ss, alpha, beta); // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough. - if (cutNode && depth >= 8 && !ttMove) - depth -= 2; + if (cutNode && depth >= 8 && (!ttMove || tte->bound() == BOUND_UPPER)) + depth -= 1 + !ttMove; // Step 11. ProbCut (~10 Elo) // If we have a good enough capture (or queen promotion) and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 169 - 63 * improving; + probCutBeta = beta + 159 - 66 * improving; if ( !PvNode && depth > 3 && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY @@ -938,7 +939,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea, when we are in check (~4 Elo) - probCutBeta = beta + 452; + probCutBeta = beta + 420; if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 4 && ttValue >= probCutBeta && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY) @@ -1016,20 +1017,22 @@ moves_loop: // When in check, search starts here if (capture || givesCheck) { + Piece capturedPiece = pos.piece_on(move.to_sq()); + int captHist = + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; + // Futility pruning for captures (~2 Elo) if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Piece capturedPiece = pos.piece_on(move.to_sq()); - Value futilityValue = - ss->staticEval + 285 + 277 * lmrDepth + PieceValue[capturedPiece] - + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)] - / 7; + Value futilityValue = ss->staticEval + 295 + 280 * lmrDepth + + PieceValue[capturedPiece] + captHist / 7; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks (~11 Elo) - if (!pos.see_ge(move, -203 * depth)) + int seeHist = std::clamp(captHist / 32, -197 * depth, 196 * depth); + if (!pos.see_ge(move, -186 * depth - seeHist)) continue; } else @@ -1037,22 +1040,22 @@ moves_loop: // When in check, search starts here int history = (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] + + (*contHist[3])[movedPiece][move.to_sq()] / 2 + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning (~2 Elo) - if (lmrDepth < 6 && history < -4173 * depth) + if (lmrDepth < 6 && history < -4081 * depth) continue; history += 2 * thisThread->mainHistory[us][move.from_to()]; - lmrDepth += history / 5285; + lmrDepth += history / 4768; Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 54 ? 128 : 57) + 131 * lmrDepth; + ss->staticEval + (bestValue < ss->staticEval - 52 ? 134 : 54) + 142 * lmrDepth; // Futility pruning: parent node (~13 Elo) - if (!ss->inCheck && lmrDepth < 14 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) { if (bestValue <= futilityValue && std::abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY && futilityValue < VALUE_TB_WIN_IN_MAX_PLY) @@ -1063,7 +1066,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE (~4 Elo) - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -28 * lmrDepth * lmrDepth)) continue; } } @@ -1083,11 +1086,11 @@ moves_loop: // When in check, search starts here // so changing them requires tests at these types of time controls. // Recursive singular search is avoided. if (!rootNode && move == ttMove && !excludedMove - && depth >= 4 - (thisThread->completedDepth > 33) + ss->ttPv + && depth >= 4 - (thisThread->completedDepth > 32) + ss->ttPv && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3) { - Value singularBeta = ttValue - (65 + 59 * (ss->ttPv && !PvNode)) * depth / 63; + Value singularBeta = ttValue - (65 + 52 * (ss->ttPv && !PvNode)) * depth / 63; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1097,17 +1100,16 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - extension = 1; + int doubleMargin = 251 * PvNode - 241 * !ttCapture; + int tripleMargin = + 135 + 234 * PvNode - 248 * !ttCapture + 124 * (ss->ttPv || !ttCapture); + int quadMargin = 447 + 354 * PvNode - 300 * !ttCapture + 206 * ss->ttPv; - // We make sure to limit the extensions in some way to avoid a search explosion - if (!PvNode && ss->multipleExtensions <= 16) - { - extension = 2 + (value < singularBeta - 11 && !ttCapture); - depth += depth < 14; - } - if (PvNode && !ttCapture && ss->multipleExtensions <= 5 - && value < singularBeta - 38) - extension = 2; + extension = 1 + (value < singularBeta - doubleMargin) + + (value < singularBeta - tripleMargin) + + (value < singularBeta - quadMargin); + + depth += ((!PvNode) && (depth < 14)); } // Multi-cut pruning @@ -1116,7 +1118,12 @@ moves_loop: // When in check, search starts here // we assume this expected cut-node is not singular (multiple moves fail high), // and we can prune the whole subtree by returning a softbound. else if (singularBeta >= beta) + { + if (!ttCapture) + update_quiet_histories(pos, ss, *this, ttMove, -stat_malus(depth)); + return singularBeta; + } // Negative extensions // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search, @@ -1141,13 +1148,12 @@ moves_loop: // When in check, search starts here else if (PvNode && move == ttMove && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] - > 3807) + > 4016) extension = 1; } // Add extension to new depth newDepth += extension; - ss->multipleExtensions = (ss - 1)->multipleExtensions + (extension >= 2); // Speculative prefetch as early as possible prefetch(tt.first_entry(pos.key_after(move))); @@ -1167,6 +1173,9 @@ moves_loop: // When in check, search starts here if (ss->ttPv) r -= 1 + (ttValue > alpha) + (tte->depth() >= depth); + else if (cutNode && move != ttMove && move != ss->killers[0]) + r++; + // Increase reduction for cut nodes (~4 Elo) if (cutNode) r += 2 - (tte->depth() >= depth && ss->ttPv); @@ -1191,10 +1200,10 @@ moves_loop: // When in check, search starts here ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - + (*contHist[3])[movedPiece][move.to_sq()] - 5024; + + (*contHist[3])[movedPiece][move.to_sq()] - 5078; // Decrease/increase reduction for moves with a good/bad history (~8 Elo) - r -= ss->statScore / 13182; + r -= ss->statScore / (17662 - std::min(depth, 16) * 105); // Step 17. Late moves reduction / extension (LMR, ~117 Elo) if (depth >= 2 && moveCount > 1 + rootNode) @@ -1213,7 +1222,7 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result // was good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 42 + 2 * newDepth); // (~1 Elo) + const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); // (~1 Elo) const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo) newDepth += doDeeperSearch - doShallowerSearch; @@ -1331,7 +1340,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement (~2 Elo) - if (depth > 2 && depth < 12 && beta < 13546 && value > -13478) + if (depth > 2 && depth < 13 && beta < 15868 && value > -14630) depth -= 2; assert(depth > 0); @@ -1374,9 +1383,9 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14761) - + ((ss - 1)->moveCount > 11) - + (!ss->inCheck && bestValue <= ss->staticEval - 142); + int bonus = (depth > 5) + (PvNode || cutNode) + ((ss - 1)->statScore < -14455) + + ((ss - 1)->moveCount > 10) + (!ss->inCheck && bestValue <= ss->staticEval - 130) + + (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 77); update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] @@ -1537,7 +1546,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 250; + futilityBase = ss->staticEval + 270; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1612,12 +1621,16 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, break; // Continuation history based pruning (~3 Elo) - if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] < 0 - && (*contHist[1])[pos.moved_piece(move)][move.to_sq()] < 0) + if (!capture + && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] + [move.to_sq()] + <= 4000) continue; // Do not search moves with bad enough SEE values (~5 Elo) - if (!pos.see_ge(move, -79)) + if (!pos.see_ge(move, -69)) continue; } @@ -1685,7 +1698,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) { int reductionScale = reductions[d] * reductions[mn]; - return (reductionScale + 1150 - delta * 832 / rootDelta) / 1024 + (!i && reductionScale > 1025); + return (reductionScale + 1318 - delta * 760 / rootDelta) / 1024 + (!i && reductionScale > 1066); } TimePoint Search::Worker::elapsed() const { @@ -1769,7 +1782,6 @@ void update_all_stats(const Position& pos, int captureCount, Depth depth) { - Color us = pos.side_to_move(); CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; @@ -1779,26 +1791,14 @@ void update_all_stats(const Position& pos, if (!pos.capture_stage(bestMove)) { - int bestMoveBonus = bestValue > beta + 185 ? quietMoveBonus // larger bonus + int bestMoveBonus = bestValue > beta + 165 ? quietMoveBonus // larger bonus : stat_bonus(depth); // smaller bonus - // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, workerThread, bestMove, bestMoveBonus); - int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][moved_piece][bestMove.to_sq()] << quietMoveBonus; - // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) - { - workerThread - .pawnHistory[pIndex][pos.moved_piece(quietsSearched[i])][quietsSearched[i].to_sq()] - << -quietMoveMalus; - - workerThread.mainHistory[us][quietsSearched[i].from_to()] << -quietMoveMalus; - update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), - quietsSearched[i].to_sq(), -quietMoveMalus); - } + update_quiet_histories(pos, ss, workerThread, quietsSearched[i], -quietMoveMalus); } else { @@ -1839,10 +1839,8 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { } } - // Updates move sorting heuristics -void update_quiet_stats( - const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { +void update_refutations(const Position& pos, Stack* ss, Search::Worker& workerThread, Move move) { // Update killers if (ss->killers[0] != move) @@ -1851,10 +1849,6 @@ void update_quiet_stats( ss->killers[0] = move; } - Color us = pos.side_to_move(); - workerThread.mainHistory[us][move.from_to()] << bonus; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); - // Update countermove history if (((ss - 1)->currentMove).is_ok()) { @@ -1862,6 +1856,27 @@ void update_quiet_stats( workerThread.counterMoves[pos.piece_on(prevSq)][prevSq] = move; } } + +void update_quiet_histories( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { + + Color us = pos.side_to_move(); + workerThread.mainHistory[us][move.from_to()] << bonus; + + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus); + + int pIndex = pawn_structure_index(pos); + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus; +} + +// Updates move sorting heuristics +void update_quiet_stats( + const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { + + update_refutations(pos, ss, workerThread, move); + update_quiet_histories(pos, ss, workerThread, move, bonus); +} + } // When playing with strength handicap, choose the best move among a set of RootMoves diff --git a/src/search.h b/src/search.h index af040c287..fef90ffcc 100644 --- a/src/search.h +++ b/src/search.h @@ -76,7 +76,6 @@ struct Stack { bool inCheck; bool ttPv; bool ttHit; - int multipleExtensions; int cutoffCnt; }; diff --git a/src/thread.cpp b/src/thread.cpp index 78c1623eb..18ce50b9a 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -166,6 +166,9 @@ void ThreadPool::clear() { for (Thread* th : threads) th->worker->clear(); + if (threads.size() == 0) + return; + main_manager()->callsCnt = 0; main_manager()->bestPreviousScore = VALUE_INFINITE; main_manager()->bestPreviousAverageScore = VALUE_INFINITE;