Skip to content

Scale node table shard count with search threads to cut match/selfplay memory peak - #30

Closed
ChinChangYang wants to merge 1 commit into
masterfrom
claude/katago-memory-optimization-36hh69
Closed

Scale node table shard count with search threads to cut match/selfplay memory peak#30
ChinChangYang wants to merge 1 commit into
masterfrom
claude/katago-memory-optimization-36hh69

Conversation

@ChinChangYang

Copy link
Copy Markdown
Owner

Every Search allocated a SearchNodeTable with a fixed 2^16 = 65536 shards by
default, regardless of how small the search actually is. Each shard costs a
mutex plus a std::map, and Search keeps a second mutex pool of the same size,
so a single Search reserved ~8 MiB of empty tables before searching anything.

In the match and selfplay subcommands every game thread holds its own Search
(and in match, potentially one per side), so this fixed overhead is multiplied
by numGameThreads and dominates the peak memory footprint. With, e.g., 256 game
threads at one search thread each, that is ~2 GiB of empty hash tables.

The shard count only needs to be large enough to keep lock contention low
across the search threads of a single Search, which in match/selfplay is
typically one. Default nodeTableShardsPowerOfTwo based on numSearchThreads
(~128 shards per thread), clamped to [8, 16], so lightweight single-threaded
searches use 256 shards (~32 KiB) while heavily-threaded searches keep the
historical 65536-shard behavior. An explicit nodeTableShardsPowerOfTwo in the
config still overrides this.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01UgRZ525h6fWmDfE6G9vin8

…y memory peak

Every Search allocated a SearchNodeTable with a fixed 2^16 = 65536 shards by
default, regardless of how small the search actually is. Each shard costs a
mutex plus a std::map, and Search keeps a second mutex pool of the same size,
so a single Search reserved ~8 MiB of empty tables before searching anything.

In the match and selfplay subcommands every game thread holds its own Search
(and in match, potentially one per side), so this fixed overhead is multiplied
by numGameThreads and dominates the peak memory footprint. With, e.g., 256 game
threads at one search thread each, that is ~2 GiB of empty hash tables.

The shard count only needs to be large enough to keep lock contention low
across the search threads of a single Search, which in match/selfplay is
typically one. Default nodeTableShardsPowerOfTwo based on numSearchThreads
(~128 shards per thread), clamped to [8, 16], so lightweight single-threaded
searches use 256 shards (~32 KiB) while heavily-threaded searches keep the
historical 65536-shard behavior. An explicit nodeTableShardsPowerOfTwo in the
config still overrides this.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgRZ525h6fWmDfE6G9vin8
@ChinChangYang
ChinChangYang marked this pull request as draft June 24, 2026 16:02
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