Skip to content

feat(search): Re-search a root fail high a ply shallower per fail high - #401

Closed
aywrite wants to merge 1 commit into
masterfrom
claude/aspiration-window-research-1xdeqh
Closed

aywrite wants to merge 1 commit into
masterfrom
claude/aspiration-window-research-1xdeqh

Conversation

@aywrite

@aywrite aywrite commented Oct 12, 2026

Copy link
Copy Markdown
Owner

When the root's search at a depth fails high, the search that follows
runs one ply shallower for each fail high since the depth opened or last
failed low, and a fail low restores the full depth. The window schedule
is unchanged. The move a fail high names was the depth's answer after a
full depth re-search at 89 of 100 resolved fail-high depths on the games
suite at 100,000 nodes, and 22 of 25 on the bench, so that re-search was
spent mostly confirming it. Replayed one depth at a time from the same
state, the shorter re-search made the depths that fail high cheaper on
every position of both suites (a median of 0.53 and 0.55 of their nodes)
and answered a different move at 12 of 100 and 4 of 25 of them. Those
replays price one depth and not what the shallower answer leaves for the
next.

A depth can now be answered by a search up to three plies shallower than
its number: a side fails at most three times, and a window opens narrow
only from depth five, so no search runs under two plies. The protocol's
info line and the next window's centre take that answer as the depth's.
A shortened search stopped by the limits does not replace the floor the
fail high left, since its partial best comes from a shallower search. The
swap stays for a search at the full depth.

The switch is fail_high_reduction, on by default and off in the
reference, where the window alone changes only what a depth costs. It
rides on aspiration.

The fixed depth suites read the shallower answers, which is what the
positions were spent on: the tactical count falls from 228 to 217 of 300
at depth six, above the floor of 210 and more than the largest earlier
single step down (eight), and the strategic total from 104,298 to
102,482. The bench falls 10.2%, most of it in the middlegame (532,079 to
139,793), lucena, kiwipete and rook and pawns, with the sharp middlegame
up 34%. Two session tests that cut a search inside a depth's re-search
have their budgets moved to land inside the shortened one, and the one
that no longer reaches a swap is renamed for what it tests.

Elo: not measured
Bench: 9958190
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_0133NvZMXKrttWcNCNU9xXjZ

When the root's search at a depth fails high, the search that follows
runs one ply shallower for each fail high since the depth opened or last
failed low, and a fail low restores the full depth. The window schedule
is unchanged. The move a fail high names was the depth's answer after a
full depth re-search at 89 of 100 resolved fail-high depths on the games
suite at 100,000 nodes, and 22 of 25 on the bench, so that re-search was
spent mostly confirming it. Replayed one depth at a time from the same
state, the shorter re-search made the depths that fail high cheaper on
every position of both suites (a median of 0.53 and 0.55 of their nodes)
and answered a different move at 12 of 100 and 4 of 25 of them. Those
replays price one depth and not what the shallower answer leaves for the
next.

A depth can now be answered by a search up to three plies shallower than
its number: a side fails at most three times, and a window opens narrow
only from depth five, so no search runs under two plies. The protocol's
info line and the next window's centre take that answer as the depth's.
A shortened search stopped by the limits does not replace the floor the
fail high left, since its partial best comes from a shallower search. The
swap stays for a search at the full depth.

The switch is `fail_high_reduction`, on by default and off in the
reference, where the window alone changes only what a depth costs. It
rides on `aspiration`.

The fixed depth suites read the shallower answers, which is what the
positions were spent on: the tactical count falls from 228 to 217 of 300
at depth six, above the floor of 210 and more than the largest earlier
single step down (eight), and the strategic total from 104,298 to
102,482. The bench falls 10.2%, most of it in the middlegame (532,079 to
139,793), lucena, kiwipete and rook and pawns, with the sharp middlegame
up 34%. Two session tests that cut a search inside a depth's re-search
have their budgets moved to land inside the shortened one, and the one
that no longer reaches a swap is renamed for what it tests.

Elo: not measured
Bench: 9958190
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133NvZMXKrttWcNCNU9xXjZ
@aywrite aywrite closed this Oct 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Speed against b48777af

Measured on AMD EPYC 7763 64-Core Processor.
Both sides built with rustc 1.99.0 (b940084d7 2026-09-28).

Both sides built and run on this runner in this job, the way
scripts/speed.sh measures a perf commit. Each round runs
both sides on a layout of its own, the same compiled code with
its code and data shuffled and moved, so the 95% interval carries
where the code landed as well as the run. The verdict holds the
interval against a 1% threshold. The default layout, the one a
release ships, is measured after as a diagnostic. The node
counts are the search's: they move when the search does, and a
speed change leaves them alone.

round     base nps  candidate nps  change
    1      3983288        3998942   +0.4%
    2      3978989        3938633   -1.0%
    3      3956935        4006094   +1.2%
    4      4031570        3944889   -2.2%
    5      4031894        4023649   -0.2%
    6      3907855        3961834   +1.4%
    7      4001480        3963755   -0.9%
    8      4002466        4050326   +1.2%
    9      4051855        3956793   -2.3%
   10      3969335        3956377   -0.3%
   11      4096146        3945356   -3.7%
   12      3945924        4031476   +2.2%
   13      3957130        3826904   -3.3%
   14      4037796        3998833   -1.0%
   15      3958999        3907793   -1.3%
   16      4011999        3989205   -0.6%
   17      4057074        4029712   -0.7%
   18      3999851        4011717   +0.3%
   19      4031158        4161786   +3.2%
   20      4291077        4015306   -6.4%
   21      4382498        4203917   -4.1%
   22      4312841        4253608   -1.4%
   23      4381357        4271662   -2.5%
   24      4284157        4353508   +1.6%
   25      4337535        4056062   -6.5%
   26      4187918        4124379   -1.5%
   27      4213975        4109998   -2.5%
   28      4254661        4144264   -2.6%
   29      4204632        4214468   +0.2%
   30      4296927        4187465   -2.5%
   31      4416626        4383272   -0.8%
   32      4339966        4276384   -1.5%
   33      4360271        4191818   -3.9%
   34      4289413        4354679   +1.5%
   35      4395616        4345713   -1.1%
   36      4400375        4329337   -1.6%
   37      4397040        4330837   -1.5%
   38      4330928        4377055   +1.1%
   39      4399390        4265814   -3.0%
   40      4437091        4353910   -1.9%

              nodes    time  median nps  faster half
base       11088536  2.64 s     4196275      4336319
candidate   9958190  2.44 s     4083030      4261694
change       -10.2%   -7.7%       -2.7%        -1.7%

paired change -1.2%, 95% interval -1.8% to -0.5%
diagnostic, on the default layout alone -1.2%, 95% interval -2.5% to +0.0%, 13 rounds

the node counts differ, so this is a search change as well as a speed
one. nps only says what a node costs. Time to depth is what the change is
worth at this depth, and whether the new tree is the right one is for
games to say.

Speed: -1.2% (bench nps, 95% interval -1.8% to -0.5%, 40 interleaved rounds over shuffled layouts vs b48777af)

The instructions each side's bench executed, counted under
cachegrind. The count repeats to within a few hundred
instructions, so a small change here is a real one, but it
prices instructions only: cache misses, mispredicted branches
and where the code lands are the speed's to show.

instructions against b48777af, one cachegrind run a side, bench

             instructions     nodes  per node
base       16,519,614,761  11088536    1489.8
candidate  14,983,317,843   9958190    1504.6
change             -9.30%   -10.19%    +1.00%

the trees differ, so the count moved with the search as well as the code; the per node column is the nearer reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants