feat(bot): add Lazy SMP parallel search for the NNUE bot
Adds optional multithreaded search behind a thread count that defaults to 1, so the live bot's play is unchanged until explicitly configured. - ParallelSearch runs N AlphaBetaSearch workers over one shared, already-lock-protected TranspositionTable. Each worker has its own NNUE evaluator (independent accumulator) and ordering state; helpers only deepen the shared TT, the main worker's move is returned. - AlphaBetaSearch gains bestMoveWithTimeSharedTt: the coordinator clears the shared TT once before launching workers, so helpers must not clear. - EvaluationNNUE.freshEvaluator builds independent evaluators sharing the immutable weights (one per thread); the singleton still backs the default single-instance path. - NNUEBot uses ParallelSearch with NNUE_SEARCH_THREADS (default 1). numThreads <= 1 takes the single-worker clearing path, identical to the previous sequential search. Strength can be validated by self-play (threads N vs 1) before promoting the default. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package de.nowchess.bot
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.bot.bots.classic.EvaluationClassic
|
||||
import de.nowchess.bot.logic.{ParallelSearch, TranspositionTable}
|
||||
import de.nowchess.rules.sets.DefaultRules
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class ParallelSearchTest extends AnyFunSuite with Matchers:
|
||||
|
||||
private def search(threads: Int): ParallelSearch =
|
||||
ParallelSearch(DefaultRules, TranspositionTable(), () => EvaluationClassic, threads)
|
||||
|
||||
test("single-threaded coordinator returns a legal move on the initial position"):
|
||||
val move = search(1).bestMoveWithTime(GameContext.initial, 200L)
|
||||
move should not be None
|
||||
DefaultRules.allLegalMoves(GameContext.initial) should contain(move.get)
|
||||
|
||||
test("multi-threaded Lazy SMP returns a legal move and does not crash under concurrency"):
|
||||
val parallel = search(4)
|
||||
for _ <- 1 to 5 do
|
||||
val move = parallel.bestMoveWithTime(GameContext.initial, 200L)
|
||||
move should not be None
|
||||
DefaultRules.allLegalMoves(GameContext.initial) should contain(move.get)
|
||||
|
||||
test("numThreads below one is clamped to a single worker"):
|
||||
search(0).bestMoveWithTime(GameContext.initial, 100L) should not be None
|
||||
Reference in New Issue
Block a user