From d6f3c2cea672e2a82a099417aa232947ffe21b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= Date: Tue, 6 Feb 2018 16:35:31 +0100 Subject: [PATCH 1/3] Use per-thread dynamic contempt TL;DR: this patch has the following effect: * for Threads=1: **non-functional** * for Threads>1: * with MultiPV=1: **no regression, little to no ELO gain** * with MultiPV>1: **clear improvement over master** First, I tried testing at standard MultiPV=1 play with [0,5] bounds. This yielded 2 yellow and 1 red test: 5+0.05, Threads=5: LLR: -2.96 (-2.94,2.94) [0.00,5.00] Total: 82689 W: 16439 L: 16190 D: 50060 http://tests.stockfishchess.org/tests/view/5aa93a5a0ebc5902952892e6 5+0.05, Threads=8: LLR: -2.96 (-2.94,2.94) [0.00,5.00] Total: 27164 W: 4974 L: 4983 D: 17207 http://tests.stockfishchess.org/tests/view/5ab2639b0ebc5902a6fbefd5 5+0.5, Threads=16: LLR: -2.97 (-2.94,2.94) [0.00,5.00] Total: 41396 W: 7127 L: 7082 D: 27187 http://tests.stockfishchess.org/tests/view/5ab124220ebc59029516cb62 Then, I tested with Skill Level=17 (implicitly MutliPV=4), showing a clear improvement: 5+0.05, Threads=5: LLR: 2.96 (-2.94,2.94) [0.00,5.00] Total: 3498 W: 1316 L: 1135 D: 1047 http://tests.stockfishchess.org/tests/view/5ab4b6580ebc5902932aeca2 Next, I tested the patch with MultiPV=1 again, this time checking for non-regression ([-3, 1]): 5+0.5, Threads=5: LLR: 2.96 (-2.94,2.94) [-3.00,1.00] Total: 65575 W: 12786 L: 12745 D: 40044 http://tests.stockfishchess.org/tests/view/5ab4e8500ebc5902932aecb3 Finally, I ran some tests with fixed number of games, checking if reverting dynamic contempt gains more elo with Skill Level=17 (i.e. MultiPV) than applying the "prevScore" fix and this patch. These tests showed, that this patch gains 15 ELO when playing with Skill Level=17: 5+0.05, Threads=3, "revert dynamic contempt" vs. "WITHOUT this patch": ELO: -11.43 +-4.1 (95%) LOS: 0.0% Total: 20000 W: 7085 L: 7743 D: 5172 http://tests.stockfishchess.org/tests/view/5ab636450ebc590295d88536 5+0.05, Threads=3, "revert dynamic contempt" vs. "WITH this patch": ELO: -26.42 +-4.1 (95%) LOS: 0.0% Total: 20000 W: 6661 L: 8179 D: 5160 http://tests.stockfishchess.org/tests/view/5ab62e680ebc590295d88524 === FAQ === **Why should this be commited?** I believe that the gain for multi-thread MultiPV search is a sufficient justification for this otherwise neutral change. I also believe this implementation of dynamic contempt is more logical, although this may be just my opinion. **Why is per-thread contempt better at MultiPV?** A likely explanation for the gain in MultiPV mode is that during search each thread independently switches between rootMoves and via the shared contempt score skews each other's evaluation. **Why were the tests done with Skill Level=17?** This was originally suggested by @Hanamuke and the idea is that with Skill Level Stockfish sometimes plays also moves it thinks are slightly sub-optimal and thus the quality of all moves offered by the MultiPV search is checked by the test. **Why are the ELO differences so huge?** This is most likely because of the nature of Skill Level mode -- since it slower and weaker than normal mode, bugs in evaluation have much greater effect. --- src/evaluate.cpp | 7 ++----- src/evaluate.h | 2 -- src/search.cpp | 8 ++++---- src/thread.cpp | 2 ++ src/thread.h | 1 + 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 5f8db7414eb..1e6d427f8a9 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -28,8 +28,7 @@ #include "evaluate.h" #include "material.h" #include "pawns.h" - -std::atomic Eval::Contempt; +#include "thread.h" namespace Trace { @@ -844,7 +843,7 @@ namespace { // Initialize score by reading the incrementally updated scores included in // the position object (material + piece square tables) and the material // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance() + Eval::Contempt; + Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contemptScore; // Probe the pawn hash table pe = Pawns::probe(pos); @@ -915,8 +914,6 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - Eval::Contempt = SCORE_ZERO; // Reset any dynamic contempt - Value v = Evaluation(pos).value(); v = pos.side_to_move() == WHITE ? v : -v; // Trace scores are from white's point of view diff --git a/src/evaluate.h b/src/evaluate.h index c3b0b2b9e2b..ccc6d5fa7b4 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -32,8 +32,6 @@ namespace Eval { constexpr Value Tempo = Value(20); // Must be visible to search -extern std::atomic Contempt; - std::string trace(const Position& pos); Value evaluate(const Position& pos); diff --git a/src/search.cpp b/src/search.cpp index 7b7737ea9fd..a0d8d29766f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -309,8 +309,8 @@ void Thread::search() { multiPV = std::min(multiPV, rootMoves.size()); int ct = Options["Contempt"] * PawnValueEg / 100; // From centipawns - Eval::Contempt = (us == WHITE ? make_score(ct, ct / 2) - : -make_score(ct, ct / 2)); + contemptScore = (us == WHITE ? make_score(ct, ct / 2) + : -make_score(ct, ct / 2)); // Iterative deepening loop until requested to stop or the target depth is reached while ( (rootDepth += ONE_PLY) < DEPTH_MAX @@ -353,8 +353,8 @@ void Thread::search() { // Adjust contempt based on root move's previousScore (dynamic contempt) ct += int(std::round(48 * atan(float(previousScore) / 128))); - Eval::Contempt = (us == WHITE ? make_score(ct, ct / 2) - : -make_score(ct, ct / 2)); + contemptScore = (us == WHITE ? make_score(ct, ct / 2) + : -make_score(ct, ct / 2)); } // Start with a small aspiration window and, in the case of a fail diff --git a/src/thread.cpp b/src/thread.cpp index ec62c3ff760..6dfdef88447 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -35,6 +35,8 @@ ThreadPool Threads; // Global object Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { + contemptScore = SCORE_ZERO; + wait_for_search_finished(); } diff --git a/src/thread.h b/src/thread.h index 1397449733d..137cd3e0824 100644 --- a/src/thread.h +++ b/src/thread.h @@ -71,6 +71,7 @@ class Thread { ButterflyHistory mainHistory; CapturePieceToHistory captureHistory; ContinuationHistory contHistory; + Score contemptScore; }; From 59acdebe5070e462cbbf0bff4b01e2efc97a728c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= Date: Tue, 27 Mar 2018 20:46:30 +0200 Subject: [PATCH 2/3] Reintroduce the missing contempt reset Even though contempt is reset to zero on thread creation, there might be some leftover contempt set after previous search. --- src/evaluate.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 1e6d427f8a9..c6d1e9bdac6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -914,6 +914,8 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); + pos.this_thread()->contemptScore = SCORE_ZERO; // Reset any dynamic contempt + Value v = Evaluation(pos).value(); v = pos.side_to_move() == WHITE ? v : -v; // Trace scores are from white's point of view From 877ef0035e0e1307041f0eabdf2763d635248792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Mosn=C3=A1=C4=8Dek?= Date: Tue, 27 Mar 2018 20:57:11 +0200 Subject: [PATCH 3/3] Remove initialization in thread.cpp It turns out this is not actually needed. --- src/thread.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/thread.cpp b/src/thread.cpp index 6dfdef88447..ec62c3ff760 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -35,8 +35,6 @@ ThreadPool Threads; // Global object Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { - contemptScore = SCORE_ZERO; - wait_for_search_finished(); }