Skip to content

1.0.0

Latest

Choose a tag to compare

@looak looak released this 14 Oct 05:25
· 18 commits to main since this release
79e3c82

v1.0.0 "Herd"

The first complete, multi threaded Elephant. Elephants move as a herd, so do the search threads now: they share one
transposition table and spread out over different depths instead of all doing the same work. 1.0 also means every
advertised UCI option works and every valid UCI command gets an answer.

On four threads 1.0 is about +200 Elo over 0.15.2 on one. On a single thread it plays like 0.15.2, this cycle was
about correctness and robustness, the next one is about strength again.

Transposition table

  • Buckets of 8 entries, one 64 byte cache line, a probe reads a single line. Each entry is one 64-bit word written and
    read atomically, threads sharing the table never see half an entry.
  • Twice the entries per megabyte, and a new result always finds a slot, the least valuable entry in the bucket makes
    room. The old table dropped shallower results, exact scores too, once a slot held a deeper entry from the same search.
  • A fail-low no longer wipes the stored move.
  • Every megabyte of Hash is used, Hash 100 was 64 MB before. The maximum goes from 1 GB to 32 GB, and the table is
    cleared with all threads.
  • info hashfull.

Lazy SMP

  • Thread 0 is the main thread, it reports, manages time and its move is played. It stops the helpers when it's done.
  • Helpers skip iterations in staggered blocks, they search ahead of the main thread and fill the table with deeper
    results.
  • info nodes and go nodes count every thread.
  • debug on prints what the helper threads search as info strings.

UCI

  • ucinewgame clears the transposition table, games no longer influence each other.
  • go nodes, go mate and go searchmoves work, they used to stop the engine without a bestmove.
  • A malformed command reports an info string error and the engine keeps running.
  • Threads 1–64, Hash 1–32768.

Fixes

  • Threefold repetitions after a double pawn push were missed: the en passant square was set and hashed even when no
    pawn could capture, so the same position reached two ways hashed differently. fastchess reported "PV continues after
    threefold repetition".
  • A FEN with black to move was hashed without the side to move key, the same position reached by moves hashed
    differently (#13).
  • A non-pawn move onto the en passant square was flagged as an en passant capture (#14).
  • The fifty move counter wasn't reset by a pawn push (#15).
  • The bench measured mostly the OS mapping in a new 64 MB table for every position, it now measures the search.

Under the hood

  • The legacy move generator is gone, the staged tusk generator the search has used since 0.14 is the only one. The
    move generator tests stay parameterized on the generator, ready to A/B a new one.
  • Builds with assertions check at every node that the incremental hash equals a full recompute.
  • Two test tiers: a smoke tier, unit tests, perft, bench and node-limited EPD suites (WAC, STS) in about a minute
    before every merge, and an extended tier (WAC, STS, Arasan 2024 with 1 and 4 threads) in CI. Every suite has a
    baseline that warns and a minimum that fails. EPD files with comments containing =, coordinate notation in STS
    score lists and lines missing a comma are read correctly.

OpenBench

1.0.0 on 4 threads vs 0.15.2 on 1 thread

Elo   | 197.63 +- 26.49 (95%)
SPRT  | 8.0+0.08s Threads=4 Hash=32MB
LLR   | 2.98 (-2.94, 2.94) [0.00, 5.00]
Games | N: 414 W: 233 L: 20 D: 161
Penta | [0, 4, 49, 91, 63]

1.0.0 vs 0.15.2, 1 thread. Stopped, the gainer bounds weren't going to pass: at this time control and hash the
two play alike.

Elo   | -2.22 +- 5.41 (95%)
SPRT  | 8.0+0.08s Threads=1 Hash=16MB
LLR   | -1.75 (-2.94, 2.94) [0.00, 5.00]
Games | N: 7362 W: 1785 L: 1832 D: 3745
Penta | [212, 906, 1471, 901, 191]

The changes on their own. The first two ran while OpenBench scaled the time control by about 5x, an 8+0.08 test
played close to 42+0.42 (fixed during the cycle, the bench now measures the search). The transposition table work
shows at longer time controls and small hash, where the table fills up; at a real 8+0.08 with 16 MB it barely does.

herd-tt-clusters vs 0.15.2, non-regression (effectively about 42+0.42)

Elo   | 40.45 +- 11.72 (95%)
SPRT  | 8.0+0.08s Threads=1 Hash=8MB
LLR   | 2.96 (-2.94, 2.94) [-3.00, 1.00]
Games | N: 1458 W: 408 L: 239 D: 811
Penta | [19, 128, 300, 229, 53]

herd-lazy-smp vs herd, 4 threads each (effectively about 42+0.42)

Elo   | 34.91 +- 11.62 (95%)
SPRT  | 8.0+0.08s Threads=4 Hash=32MB
LLR   | 2.96 (-2.94, 2.94) [0.00, 5.00]
Games | N: 1378 W: 352 L: 214 D: 812
Penta | [16, 122, 302, 206, 43]

herd-tt-polish vs herd, non-regression. Stopped undecided, merged: the search is the same, node for node.

Elo   | -2.83 +- 10.91 (95%)
SPRT  | 8.0+0.08s Threads=1 Hash=24MB
LLR   | -0.18 (-2.94, 2.94) [-3.00, 1.00]
Games | N: 1596 W: 320 L: 333 D: 943
Penta | [36, 193, 348, 190, 31]

herd-ep-repetition vs herd, non-regression. Stopped undecided, merged as a correctness fix.

Elo   | -3.76 +- 6.60 (95%)
SPRT  | 8.0+0.08s Threads=1 Hash=8MB
LLR   | -0.83 (-2.94, 2.94) [-7.00, 1.00]
Games | N: 4984 W: 1186 L: 1240 D: 2558
Penta | [146, 629, 975, 617, 125]

Speed

Perft 6 from the start position about 140M moves/s, search from the start position about 4.5M nps, bench 503240
nodes at 4.4M nps. One thread, the g++ build OpenBench uses (-O3 -march=native), AMD Ryzen 9 9900X.