Skip to content

Pygin v61

Choose a tag to compare

@IchNukeDichWeg IchNukeDichWeg released this 28 Aug 22:55
· 195 commits to main since this release

v61 -- dead entries in the transposition table are now evicted first.
Material is irreversible, so a TT entry whose stored piece count exceeds the
current root's describes a position that can never occur again. It is garbage
with certainty rather than a guess, so it is taken first, while a
still-reachable old entry is depth-protected instead of clobbered on age alone.
Nothing is swept: this is purely a better choice of victim, made at store time.

Elo   | +15.89 +/- 4.2 (52.29%; FIXED budget, so the magnitude is unbiased)
SPRT  | none -- no early stop; post-hoc GSPRT[0,4] LLR +7.771 vs +/-2.944
Games | N: 10000  W: 2826  L: 2369  D: 4805
Penta | [146, 1044, 2226, 1375, 209]   5000 pairs, pair ratio 1.33, norm Elo +25.77
Conf  | TIMED 50+0.50, x86, workers 48, Threads=1, Hash=192MB BOTH SIDES
Base  | vs Old Engine/60 (same nnue_v12 net both arms), SUBSET_SEED 59
Bench | 1,140,099 nodes (unchanged -- see Known limits)

What changed

  • FI-115 dead-entry TT replacement (TT_DEADTAG, armed). The piece count
    rides in 6 spare bits of the entry and is stamped whether or not the rule is
    on, so with it off the store path is byte-identical to v60.
  • FI-113 TT prefetch now covers both cache lines of the 24-byte entry.
    Node-identical, +1.09% NPS, no A/B slot spent.

Why the measurement took two attempts

The first screen accepted this at 10+0.1 with a 24 MiB table on both sides,
chosen to maximise replacement pressure. A verdict from the regime built to
favour a change does not transfer, so it was re-run at the shipped 192 MiB.
192 MiB at 10+0.1 would have measured nothing either: at 0.15s/move the table
only reaches about a 22-bit working set by ply 40, so almost nothing is evicted
and a victim rule has nothing to improve. It took the shipped size and a
clock long enough to saturate it.

Testing

  • testing/test_tt_deadtag.py -- randomised stress test written for this
    release: a 1.5 MB table kept warm across thousands of searches so every store
    is an eviction, driven by uniform-random legal positions rather than
    game-derived ones. Checks inertness with the tag off, determinism with it on,
    that ON actually differs from OFF, descending material against a warm table,
    and 3-to-5-man endings at the bottom of the 6-bit field.

Known limits

  • The bench signature and the depth-14 ladder are blind to this change by
    construction
    -- both search from a cold table, so it never fills and the
    replacement rule never fires. Do not use either to verify FI-115 is active.
  • Measured on x86. arm64 is unconfirmed.
  • The gain is table-size dependent. At a very large Hash the table may never
    saturate, in which case the rule rarely fires; that case is unmeasured.
  • Rejected alongside it: a growing TT that starts at 24 MiB and widens at
    75% occupancy measured -2.88 +/- 4.4 and was dropped. Without a rehash on
    each widen it discards half the live table twice mid-game. The copy-on-grow
    variant is untested.

Self-contained binary: drop into any UCI GUI or lichess-bot, no Python or repo
needed. Options: Hash, Threads, OwnBook, UseTB, Move Overhead.

Full Changelog: v60...v61