Skip to content

Commit d8f9f78

Browse files
authored
Add more to the uci eval command output (#889)
Previously only the NNUE evaluation was printed. With this change, `eval` also prints: - NNUE derived piece values - NNUE evaluation per bucket (0–7), with the active bucket marked Piece values are computed as: 1. Full NNUE forward pass on the current position. 2. Remove the piece and create a copy of the board without that piece. 3. Run another full forward pass on the modified board. 4. Contribution = baseline - without. Buckets are computed as: 1. For each bucket (0-7) run the forward pass using the buckets weights. 2. Add an arrow to show which bucket is used. Bench: 2864276
1 parent 4c9c841 commit d8f9f78

2 files changed

Lines changed: 92 additions & 7 deletions

File tree

src/nnue.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const INPUT_BUCKETS_LAYOUT: [u8; 64] = [
7777
];
7878

7979
#[rustfmt::skip]
80-
const OUTPUT_BUCKETS_LAYOUT: [usize; 33] = [
80+
pub const OUTPUT_BUCKETS_LAYOUT: [usize; 33] = [
8181
0, 0, 0, 0, 0, 0, 0, 0, 0,
8282
1, 1, 1, 1,
8383
2, 2, 2, 2,
@@ -233,6 +233,42 @@ impl Network {
233233
(l3_out * NETWORK_SCALE as f32) as i32
234234
}
235235
}
236+
237+
pub fn eval_with_bucket(&mut self, board: &Board, bucket: usize) -> i32 {
238+
self.full_refresh(board);
239+
self.evaluate(board); // just to update internal state
240+
241+
unsafe {
242+
let ft_out =
243+
forward::activate_ft(&self.pst_stack[self.index], &self.threat_stack[self.index], board.side_to_move());
244+
let (nnz_indexes, nnz_count) = forward::find_nnz(&ft_out, &self.nnz_table);
245+
let l1_out = forward::propagate_l1(ft_out, &nnz_indexes[..nnz_count], bucket);
246+
let l2_out = forward::propagate_l2(l1_out, bucket);
247+
let l3_out = forward::propagate_l3(l2_out, bucket);
248+
(l3_out * NETWORK_SCALE as f32) as i32
249+
}
250+
}
251+
252+
pub fn piece_contribution(&mut self, board: &Board, sq: Square) -> Option<i32> {
253+
let piece = board.piece_on(sq);
254+
255+
if piece == Piece::None || piece.piece_type() == PieceType::King {
256+
return None;
257+
}
258+
259+
let baseline = self.evaluate(board);
260+
261+
let mut board_without = board.clone();
262+
board_without.remove_piece(piece, sq);
263+
264+
self.full_refresh(&board_without);
265+
let without = self.evaluate(&board_without);
266+
267+
self.full_refresh(board);
268+
self.evaluate(board);
269+
270+
Some(baseline - without)
271+
}
236272
}
237273

238274
impl Default for Network {

src/uci.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
time::{Limits, TimeManager},
1111
tools,
1212
transposition::DEFAULT_TT_SIZE,
13-
types::{Color, MAX_MOVES, Move, Score, is_decisive, is_loss, is_win},
13+
types::{Color, MAX_MOVES, Move, Piece, Score, Square, is_decisive, is_loss, is_win},
1414
};
1515

1616
#[derive(Copy, Clone, PartialEq, Eq)]
@@ -330,11 +330,60 @@ fn set_option(threads: &mut ThreadPool, settings: &mut Settings, shared: &Arc<Sh
330330

331331
fn eval(td: &mut ThreadData) {
332332
td.nnue.full_refresh(&td.board);
333-
let eval = match td.board.side_to_move() {
334-
Color::White => td.nnue.evaluate(&td.board),
335-
Color::Black => -td.nnue.evaluate(&td.board),
336-
};
337-
println!("{eval}");
333+
td.nnue.evaluate(&td.board);
334+
335+
let side = td.board.side_to_move();
336+
337+
println!("NNUE derived piece values");
338+
println!("+-------+-------+-------+-------+-------+-------+-------+-------+");
339+
for rank in (0..8).rev() {
340+
print!("|");
341+
for file in 0..8 {
342+
let sq = Square::from_rank_file(rank, file);
343+
let piece = td.board.piece_on(sq);
344+
let piece_str = if piece == Piece::None { " ".to_string() } else { piece.to_string() };
345+
print!(" {:^3} |", piece_str);
346+
}
347+
println!();
348+
349+
print!("|");
350+
for file in 0..8 {
351+
let sq = Square::from_rank_file(rank, file);
352+
match td.nnue.piece_contribution(&td.board, sq) {
353+
None => print!(" |"),
354+
Some(v) => {
355+
let val = v as f32 / 100.0;
356+
print!("{:+6.2} |", val);
357+
}
358+
}
359+
}
360+
println!();
361+
println!("+-------+-------+-------+-------+-------+-------+-------+-------+");
362+
}
363+
364+
let used_bucket = crate::nnue::OUTPUT_BUCKETS_LAYOUT[td.board.occupancies().popcount()];
365+
366+
println!("\nNNUE output buckets (White side)");
367+
println!("+------------+------------+");
368+
println!("| Bucket | Total |");
369+
println!("+------------+------------+");
370+
371+
for bucket in 0..8 {
372+
let raw_score = td.nnue.eval_with_bucket(&td.board, bucket);
373+
let white_score = if side == Color::White { raw_score } else { -raw_score };
374+
let total = white_score as f32 / 100.0;
375+
376+
if bucket == used_bucket {
377+
println!("| {:<2} | {:+7.2} | <-- this bucket is used", bucket, total);
378+
} else {
379+
println!("| {:<2} | {:+7.2} |", bucket, total);
380+
}
381+
}
382+
println!("+------------+------------+");
383+
384+
let final_eval = td.nnue.evaluate(&td.board);
385+
let final_total = (if side == Color::White { final_eval } else { -final_eval }) as f32 / 100.0;
386+
println!("\nNNUE evaluation {:+.2} (White side)", final_total);
338387
}
339388

340389
fn parse_limits(color: Color, tokens: &[&str]) -> Limits {

0 commit comments

Comments
 (0)