Upload reference/net.rs with huggingface_hub
Browse files- reference/net.rs +34 -15
reference/net.rs
CHANGED
|
@@ -87,21 +87,39 @@ fn net() -> &'static Net {
|
|
| 87 |
/// two accumulations to rediscover a number computed a microsecond ago is most
|
| 88 |
/// of what the evaluator does.
|
| 89 |
///
|
| 90 |
-
/// One `u64` per slot
|
| 91 |
-
/// the low
|
| 92 |
-
/// overlap
|
| 93 |
-
///
|
| 94 |
-
///
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
const CACHE_BITS: usize = 16;
|
| 96 |
static CACHE: SyncCell<[u64; 1 << CACHE_BITS]> = SyncCell::new([0; 1 << CACHE_BITS]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
/// Not needed for correctness — a cached score is as valid as the day it was
|
| 99 |
/// stored — but `bench` and datagen want each position measured from a cold
|
| 100 |
/// start, and the search's own `clear` is where that is expressed.
|
| 101 |
pub fn clear_cache() {
|
| 102 |
-
let
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
|
@@ -405,15 +423,16 @@ fn propagate(acc: &[i16; H], w: &[i8]) -> i32 {
|
|
| 405 |
|
| 406 |
/// Evaluation in centipawns, from the side to move's point of view.
|
| 407 |
pub fn evaluate(pos: &Position) -> i32 {
|
| 408 |
-
//
|
| 409 |
-
//
|
| 410 |
-
// a
|
| 411 |
let slot = (pos.key as usize) & ((1 << CACHE_BITS) - 1);
|
| 412 |
-
let
|
|
|
|
| 413 |
let c = unsafe { CACHE.as_mut() };
|
| 414 |
let hit = c[slot];
|
| 415 |
-
if
|
| 416 |
-
return hit as
|
| 417 |
}
|
| 418 |
|
| 419 |
let n = net();
|
|
@@ -427,6 +446,6 @@ pub fn evaluate(pos: &Position) -> i32 {
|
|
| 427 |
let w = &n.out_w[b * 2 * H..(b + 1) * 2 * H];
|
| 428 |
let out = propagate(&us, &w[..H]) + propagate(&them, &w[H..]) + n.out_b[b];
|
| 429 |
let score = (out * SCALE / (QA * QB)).clamp(-20_000, 20_000);
|
| 430 |
-
c[slot] =
|
| 431 |
score
|
| 432 |
}
|
|
|
|
| 87 |
/// two accumulations to rediscover a number computed a microsecond ago is most
|
| 88 |
/// of what the evaluator does.
|
| 89 |
///
|
| 90 |
+
/// One `u64` per slot, packed as `tag:40 | generation:8 | score:16`. The index
|
| 91 |
+
/// is the low bits of the key and the tag is bits 24 and up, so the two never
|
| 92 |
+
/// overlap: a slot only answers for a position that agrees on both. Scores are
|
| 93 |
+
/// clamped to ±20,000, so sixteen bits hold one exactly.
|
| 94 |
+
///
|
| 95 |
+
/// The generation is what makes the table cheap to empty. The first version
|
| 96 |
+
/// zeroed all of it, and that memset was large enough to decide the sizing:
|
| 97 |
+
/// 18-bit and 20-bit tables both measured *slower* than 16-bit, because the
|
| 98 |
+
/// clear between bench positions cost more than the extra hits were worth.
|
| 99 |
+
/// Bumping a counter invalidates every entry at once, so the size question is
|
| 100 |
+
/// now about cache footprint alone.
|
| 101 |
const CACHE_BITS: usize = 16;
|
| 102 |
static CACHE: SyncCell<[u64; 1 << CACHE_BITS]> = SyncCell::new([0; 1 << CACHE_BITS]);
|
| 103 |
+
static GEN: SyncCell<u8> = SyncCell::new(0);
|
| 104 |
+
|
| 105 |
+
#[inline(always)]
|
| 106 |
+
fn pack(key: u64, gen: u8, score: i32) -> u64 {
|
| 107 |
+
(key >> 24 << 24) | ((gen as u64) << 16) | (score as i16 as u16 as u64)
|
| 108 |
+
}
|
| 109 |
|
| 110 |
/// Not needed for correctness — a cached score is as valid as the day it was
|
| 111 |
/// stored — but `bench` and datagen want each position measured from a cold
|
| 112 |
/// start, and the search's own `clear` is where that is expressed.
|
| 113 |
pub fn clear_cache() {
|
| 114 |
+
let g = unsafe { GEN.as_mut() };
|
| 115 |
+
*g = g.wrapping_add(1);
|
| 116 |
+
// Eight bits of generation wrap after 256 clears, and an entry that old
|
| 117 |
+
// would start answering again. That only happens once every 256 clears, so
|
| 118 |
+
// pay for the real erase then.
|
| 119 |
+
if *g == 0 {
|
| 120 |
+
for e in unsafe { CACHE.as_mut() }.iter_mut() {
|
| 121 |
+
*e = 0;
|
| 122 |
+
}
|
| 123 |
}
|
| 124 |
}
|
| 125 |
|
|
|
|
| 423 |
|
| 424 |
/// Evaluation in centipawns, from the side to move's point of view.
|
| 425 |
pub fn evaluate(pos: &Position) -> i32 {
|
| 426 |
+
// An erased slot is all-zero, which is a real entry for the one position in
|
| 427 |
+
// a trillion whose key has forty zero bits on top and whose score is zero.
|
| 428 |
+
// That costs a zero instead of a zero; no validity bit is worth the space.
|
| 429 |
let slot = (pos.key as usize) & ((1 << CACHE_BITS) - 1);
|
| 430 |
+
let gen = unsafe { *GEN.as_ref() };
|
| 431 |
+
let want = pack(pos.key, gen, 0);
|
| 432 |
let c = unsafe { CACHE.as_mut() };
|
| 433 |
let hit = c[slot];
|
| 434 |
+
if hit & !0xFFFF == want {
|
| 435 |
+
return hit as u16 as i16 as i32;
|
| 436 |
}
|
| 437 |
|
| 438 |
let n = net();
|
|
|
|
| 446 |
let w = &n.out_w[b * 2 * H..(b + 1) * 2 * H];
|
| 447 |
let out = propagate(&us, &w[..H]) + propagate(&them, &w[H..]) + n.out_b[b];
|
| 448 |
let score = (out * SCALE / (QA * QB)).clamp(-20_000, 20_000);
|
| 449 |
+
c[slot] = pack(pos.key, gen, score);
|
| 450 |
score
|
| 451 |
}
|