Skip to content

Commit

Permalink
Redefine Nodes Count (#39)
Browse files Browse the repository at this point in the history
Previously, a node was one complete playout from the root position. Now, we redefine nodes count to the number of times `perform_one_iteration()` is called, i.e. for every position we consider. The previous definition has been renamed to "iters" as a more unambiguous term.

The benefit of this is a much more consistent NPS value, as opposed to the previous version where NPS would greatly fluctuate between different patches.

This patch also includes all search threads in the total nodes count calculation and output, not just the main thread.

Bench depth was also decreased from 7 to 6 to reduce the total time a bench takes.

Passed non-regression STC:
LLR: 3.05 (-2.94,2.94) <-3.50,0.50>
Total: 77896 W: 18486 L: 18528 D: 40882
Ptnml(0-2): 1173, 8945, 18710, 8991, 1129
https://montychess.org/tests/view/66ba5f9d3ae9310e136de28e

Passed non-regression STC SMP:
LLR: 2.97 (-2.94,2.94) <-3.50,0.50>
Total: 30404 W: 7246 L: 7172 D: 15986
Ptnml(0-2): 434, 3388, 7475, 3480, 425
https://montychess.org/tests/view/66ba6ff93ae9310e136de30e

Bench: 2093550
  • Loading branch information
XInTheDark authored and Viren6 committed Aug 14, 2024
1 parent acf9c34 commit 90ccd51
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 43 deletions.
2 changes: 1 addition & 1 deletion src/chess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ impl Default for ChessState {

impl ChessState {
pub const STARTPOS: &'static str = STARTPOS;
pub const BENCH_DEPTH: usize = 7;
pub const BENCH_DEPTH: usize = 6;

pub fn bbs(&self) -> [u64; 8] {
self.board.bbs()
Expand Down
93 changes: 54 additions & 39 deletions src/mcts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
};

use std::{
sync::atomic::{AtomicBool, Ordering},
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
thread,
time::Instant,
};
Expand All @@ -24,6 +24,14 @@ pub struct Limits {
pub max_nodes: usize,
}

#[derive(Default)]
pub struct SearchStats {
pub total_nodes: AtomicUsize,
pub total_iters: AtomicUsize,
pub main_iters: AtomicUsize,
pub avg_depth: AtomicUsize,
}

pub struct Searcher<'a> {
root_position: ChessState,
tree: &'a Tree,
Expand Down Expand Up @@ -57,24 +65,20 @@ impl<'a> Searcher<'a> {
&self,
limits: &Limits,
timer: &Instant,
nodes: &mut usize,
depth: &mut usize,
cumulative_depth: &mut usize,
search_stats: &SearchStats,
best_move: &mut Move,
best_move_changes: &mut i32,
previous_score: &mut f32,
#[cfg(not(feature = "uci-minimal"))] uci_output: bool,
) {
if self.playout_until_full_internal(nodes, cumulative_depth, |n, cd| {
if self.playout_until_full_internal(search_stats, true, || {
self.check_limits(
limits,
timer,
n,
search_stats,
best_move,
best_move_changes,
previous_score,
depth,
cd,
#[cfg(not(feature = "uci-minimal"))]
uci_output,
)
Expand All @@ -83,18 +87,18 @@ impl<'a> Searcher<'a> {
}
}

fn playout_until_full_worker(&self, nodes: &mut usize, cumulative_depth: &mut usize) {
let _ = self.playout_until_full_internal(nodes, cumulative_depth, |_, _| false);
fn playout_until_full_worker(&self, search_stats: &SearchStats) {
let _ = self.playout_until_full_internal(search_stats, false, || false);
}

fn playout_until_full_internal<F>(
&self,
nodes: &mut usize,
cumulative_depth: &mut usize,
search_stats: &SearchStats,
main_thread: bool,
mut stop: F,
) -> bool
where
F: FnMut(usize, usize) -> bool,
F: FnMut() -> bool,
{
loop {
let mut pos = self.root_position.clone();
Expand All @@ -111,8 +115,13 @@ impl<'a> Searcher<'a> {
return false;
}

*cumulative_depth += this_depth - 1;
*nodes += 1;
search_stats.total_iters.fetch_add(1, Ordering::Relaxed);
search_stats
.total_nodes
.fetch_add(this_depth, Ordering::Relaxed);
if main_thread {
search_stats.main_iters.fetch_add(1, Ordering::Relaxed);
}

// proven checkmate
if self.tree[self.tree.root_node()].is_terminal() {
Expand All @@ -124,7 +133,7 @@ impl<'a> Searcher<'a> {
return true;
}

if stop(*nodes, *cumulative_depth) {
if stop() {
return true;
}
}
Expand All @@ -135,19 +144,19 @@ impl<'a> Searcher<'a> {
&self,
limits: &Limits,
timer: &Instant,
nodes: usize,
search_stats: &SearchStats,
best_move: &mut Move,
best_move_changes: &mut i32,
previous_score: &mut f32,
depth: &mut usize,
cumulative_depth: usize,
#[cfg(not(feature = "uci-minimal"))] uci_output: bool,
) -> bool {
if nodes >= limits.max_nodes {
let iters = search_stats.main_iters.load(Ordering::Relaxed);

if search_stats.total_iters.load(Ordering::Relaxed) >= limits.max_nodes {
return true;
}

if nodes % 128 == 0 {
if iters % 128 == 0 {
if let Some(time) = limits.max_time {
if timer.elapsed().as_millis() >= time {
return true;
Expand All @@ -161,23 +170,23 @@ impl<'a> Searcher<'a> {
}
}

if nodes % 4096 == 0 {
if iters % 4096 == 0 {
// Time management
if let Some(time) = limits.opt_time {
let (should_stop, score) = SearchHelpers::soft_time_cutoff(
self,
timer,
*previous_score,
*best_move_changes,
nodes,
iters,
time,
);

if should_stop {
return true;
}

if nodes % 16384 == 0 {
if iters % 16384 == 0 {
*best_move_changes = 0;
}

Expand All @@ -190,16 +199,22 @@ impl<'a> Searcher<'a> {
}

// define "depth" as the average depth of selection
let avg_depth = cumulative_depth / nodes;
if avg_depth > *depth {
*depth = avg_depth;
if *depth >= limits.max_depth {
let total_depth = search_stats.total_nodes.load(Ordering::Relaxed)
- search_stats.total_iters.load(Ordering::Relaxed);
let new_depth = total_depth / search_stats.total_iters.load(Ordering::Relaxed);
if new_depth > search_stats.avg_depth.load(Ordering::Relaxed) {
search_stats.avg_depth.store(new_depth, Ordering::Relaxed);
if new_depth >= limits.max_depth {
return true;
}

#[cfg(not(feature = "uci-minimal"))]
if uci_output {
self.search_report(*depth, timer, nodes);
self.search_report(
new_depth,
timer,
search_stats.total_nodes.load(Ordering::Relaxed),
);
}
}

Expand All @@ -211,7 +226,7 @@ impl<'a> Searcher<'a> {
threads: usize,
limits: Limits,
uci_output: bool,
total_nodes: &mut usize,
update_nodes: &mut usize,
) -> (Move, f32) {
let timer = Instant::now();

Expand All @@ -225,9 +240,7 @@ impl<'a> Searcher<'a> {
self.tree[node].expand::<true>(&self.root_position, self.params, self.policy);
}

let mut nodes = 0;
let mut depth = 0;
let mut cumulative_depth = 0;
let search_stats = SearchStats::default();

let mut best_move = Move::NULL;
let mut best_move_changes = 0;
Expand All @@ -240,9 +253,7 @@ impl<'a> Searcher<'a> {
self.playout_until_full_main(
&limits,
&timer,
&mut nodes,
&mut depth,
&mut cumulative_depth,
&search_stats,
&mut best_move,
&mut best_move_changes,
&mut previous_score,
Expand All @@ -252,7 +263,7 @@ impl<'a> Searcher<'a> {
});

for _ in 0..threads - 1 {
s.spawn(|| self.playout_until_full_worker(&mut 0, &mut 0));
s.spawn(|| self.playout_until_full_worker(&search_stats));
}
});

Expand All @@ -261,10 +272,14 @@ impl<'a> Searcher<'a> {
}
}

*total_nodes += nodes;
*update_nodes += search_stats.total_nodes.load(Ordering::Relaxed);

if uci_output {
self.search_report(depth.max(1), &timer, nodes);
self.search_report(
search_stats.avg_depth.load(Ordering::Relaxed).max(1),
&timer,
search_stats.total_nodes.load(Ordering::Relaxed),
);
}

let best_action = self.get_best_action();
Expand Down
26 changes: 23 additions & 3 deletions src/uci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ impl Uci {
let mut tree = Tree::new_mb(64, 1);
let mut report_moves = false;
let mut threads = 1;
let mut move_overhead = 40;

let mut stored_message: Option<String> = None;

Expand Down Expand Up @@ -58,6 +59,7 @@ impl Uci {
&mut report_moves,
&mut tree,
&mut threads,
&mut move_overhead,
),
"position" => position(commands, &mut pos),
"go" => {
Expand All @@ -75,11 +77,21 @@ impl Uci {
policy,
value,
threads,
move_overhead,
&mut stored_message,
);

prev = Some(pos.clone());
}
"bench" => {
let depth = if let Some(d) = commands.get(1) {
d.parse().unwrap_or(ChessState::BENCH_DEPTH)
} else {
ChessState::BENCH_DEPTH
};

Uci::bench(depth, policy, value, &params);
}
"perft" => run_perft(&commands, &pos),
"quit" => std::process::exit(0),
"eval" => {
Expand Down Expand Up @@ -169,6 +181,7 @@ fn preamble() {
println!("id author Jamie Whiting");
println!("option name Hash type spin default 64 min 1 max 8192");
println!("option name Threads type spin default 1 min 1 max 512");
println!("option name MoveOverhead type spin default 40 min 0 max 5000");
println!("option name report_moves type button");
Uci::options();

Expand All @@ -184,6 +197,7 @@ fn setoption(
report_moves: &mut bool,
tree: &mut Tree,
threads: &mut usize,
move_overhead: &mut usize,
) {
if let ["setoption", "name", "report_moves"] = commands {
*report_moves = !*report_moves;
Expand All @@ -200,6 +214,11 @@ fn setoption(
return;
}

if *x == "MoveOverhead" {
*move_overhead = y.parse().unwrap();
return;
}

(*x, y.parse::<i32>().unwrap_or(0))
} else {
return;
Expand Down Expand Up @@ -259,6 +278,7 @@ fn go(
policy: &PolicyNetwork,
value: &ValueNetwork,
threads: usize,
move_overhead: usize,
stored_message: &mut Option<String>,
) {
let mut max_nodes = i32::MAX as usize;
Expand Down Expand Up @@ -313,12 +333,12 @@ fn go(
max_time = Some(max_time.unwrap_or(u128::MAX).min(max));
}

// 20ms move overhead
// apply move overhead
if let Some(t) = opt_time.as_mut() {
*t = t.saturating_sub(20);
*t = t.saturating_sub(move_overhead as u128);
}
if let Some(t) = max_time.as_mut() {
*t = t.saturating_sub(20);
*t = t.saturating_sub(move_overhead as u128);
}

let abort = AtomicBool::new(false);
Expand Down

0 comments on commit 90ccd51

Please sign in to comment.