Add static evaluation mode to relabeler (bench 5725647)
This commit is contained in:
@@ -128,7 +128,7 @@ The available options are:
|
||||
|
||||
- `--input` and `--output`: Required input path and output path/prefix.
|
||||
- `--depth`: Optional maximum search depth. When omitted, only the node limits stop each search.
|
||||
- `--nodes-soft` and `--nodes-hard`: Per-position soft and hard node limits (defaults: 5,000 and 1,000,000).
|
||||
- `--nodes-soft` and `--nodes-hard`: Per-position soft and hard node limits (defaults: 5,000 and 1,000,000). When `--nodes-soft` is omitted and `--nodes-hard=1`, relabelling directly evaluates each recorded position with one NNUE inference and preserves its move; explicitly providing a soft limit keeps normal search-limit validation.
|
||||
- `--hash`: Transposition-table size in MiB per worker (default: 1).
|
||||
- `--threads`: Number of independent relabelling workers (default: 1).
|
||||
- `--chunk-size`: Number of games read before work is split across workers (default: 1,024).
|
||||
|
||||
@@ -108,6 +108,7 @@ when isMainModule:
|
||||
relabelInput = none(string)
|
||||
relabelOutput = none(string)
|
||||
relabelDepth = none(int)
|
||||
relabelSoftNodesProvided = false
|
||||
relabelChunk = 1024
|
||||
relabelJoin = false
|
||||
|
||||
@@ -225,6 +226,7 @@ when isMainModule:
|
||||
relabelOutput = some(value)
|
||||
of "nodes-soft":
|
||||
searcherNodes.soft = parseBiggestUInt(value)
|
||||
relabelSoftNodesProvided = true
|
||||
of "nodes-hard":
|
||||
searcherNodes.hard = parseBiggestUInt(value)
|
||||
of "hash":
|
||||
@@ -273,6 +275,7 @@ when isMainModule:
|
||||
relabelViriformat(relabelInput.get(), relabelOutput.get(), RelabelConfig(
|
||||
depth: relabelDepth,
|
||||
nodes: searcherNodes,
|
||||
softNodesProvided: relabelSoftNodesProvided,
|
||||
hashMiB: searcherHash,
|
||||
threads: threads,
|
||||
chunkSize: relabelChunk,
|
||||
|
||||
@@ -20,6 +20,7 @@ type
|
||||
RelabelConfig* = object
|
||||
depth*: Option[int]
|
||||
nodes*: tuple[soft, hard: uint64]
|
||||
softNodesProvided*: bool
|
||||
hashMiB*: uint64
|
||||
threads*: int
|
||||
chunkSize*: int
|
||||
@@ -57,6 +58,10 @@ type
|
||||
active: bool
|
||||
|
||||
|
||||
func usesStaticEval(config: RelabelConfig): bool {.inline.} =
|
||||
not config.softNodesProvided and config.nodes.hard == 1
|
||||
|
||||
|
||||
func shardPath(output: string, worker: int): string =
|
||||
&"{output}.part-{worker:03}"
|
||||
|
||||
@@ -142,26 +147,32 @@ func formatDuration(seconds: float): string =
|
||||
|
||||
|
||||
proc relabelGame(game: ViriformatGame, searcher: var SearchManager,
|
||||
ttable: TranspositionTable, completed: ptr Atomic[int]): string =
|
||||
ttable: TranspositionTable, staticEvalOnly: bool,
|
||||
completed: ptr Atomic[int]): string =
|
||||
var
|
||||
board = newChessboard(@[game.initial.position.clone()])
|
||||
scores = newSeq[int16](game.moves.len)
|
||||
|
||||
# Consecutive positions in a game are closely related, so retain their TT
|
||||
# entries. Clearing once here still isolates independent games.
|
||||
ttable.init(1)
|
||||
if not staticEvalOnly:
|
||||
ttable.init(1)
|
||||
for i, entry in game.moves:
|
||||
# Decode first so malformed input is rejected before doing an expensive
|
||||
# search or emitting a partial game.
|
||||
let move = board.positions[^1].parseViriformatMove(entry.move)
|
||||
|
||||
searcher.setBoard(board.positions)
|
||||
searcher.histories.clear()
|
||||
let variations = searcher.search(silent=true)
|
||||
if variations.len == 0 or variations[0].moves[0] == nullMove():
|
||||
raise newException(ValueError, &"search produced no move for position {board.toFEN()}")
|
||||
|
||||
var score = variations[0].score
|
||||
var score: Score
|
||||
if staticEvalOnly:
|
||||
searcher.evalState.init(board)
|
||||
score = board.evaluate(searcher.evalState)
|
||||
else:
|
||||
searcher.setBoard(board.positions)
|
||||
searcher.histories.clear()
|
||||
let variations = searcher.search(silent=true)
|
||||
if variations.len == 0 or variations[0].moves[0] == nullMove():
|
||||
raise newException(ValueError, &"search produced no move for position {board.toFEN()}")
|
||||
score = variations[0].score
|
||||
if board.sideToMove == Black:
|
||||
score = -score
|
||||
scores[i] = int16(score)
|
||||
@@ -178,9 +189,11 @@ proc workerMain(worker: ptr RelabelWorker) {.thread.} =
|
||||
|
||||
var searcher = newSearchManager(@[startpos()], ttable, getDefaultParameters(),
|
||||
evalState=newEvalState(verbose=false), normalizeScore=false)
|
||||
if worker.config.depth.isSome():
|
||||
searcher.limiter.addLimit(newDepthLimit(worker.config.depth.get()))
|
||||
searcher.limiter.addLimit(newNodeLimit(worker.config.nodes.soft, worker.config.nodes.hard))
|
||||
let staticEvalOnly = worker.config.usesStaticEval()
|
||||
if not staticEvalOnly:
|
||||
if worker.config.depth.isSome():
|
||||
searcher.limiter.addLimit(newDepthLimit(worker.config.depth.get()))
|
||||
searcher.limiter.addLimit(newNodeLimit(worker.config.nodes.soft, worker.config.nodes.hard))
|
||||
|
||||
var output = syncio.open(worker.outputPath, fmWrite)
|
||||
defer: output.close()
|
||||
@@ -198,7 +211,8 @@ proc workerMain(worker: ptr RelabelWorker) {.thread.} =
|
||||
try:
|
||||
response.offset = output.getFilePos()
|
||||
for game in work.games:
|
||||
let encoded = game.relabelGame(searcher, ttable, addr worker.completedPositions)
|
||||
let encoded = game.relabelGame(searcher, ttable, staticEvalOnly,
|
||||
addr worker.completedPositions)
|
||||
output.write(encoded)
|
||||
inc(response.games)
|
||||
inc(response.positions, game.moves.len)
|
||||
@@ -263,10 +277,11 @@ proc joinShards(output: string, shardPaths: openArray[string], ranges: openArray
|
||||
proc validate(config: RelabelConfig) =
|
||||
if config.depth.isSome() and config.depth.get() < 1:
|
||||
raise newException(ValueError, "depth must be at least 1")
|
||||
if config.nodes.soft < 1 or config.nodes.hard < 1:
|
||||
raise newException(ValueError, "soft and hard node limits must be at least 1")
|
||||
if config.nodes.soft > config.nodes.hard:
|
||||
raise newException(ValueError, "soft node limit cannot exceed the hard node limit")
|
||||
if not config.usesStaticEval():
|
||||
if config.nodes.soft < 1 or config.nodes.hard < 1:
|
||||
raise newException(ValueError, "soft and hard node limits must be at least 1")
|
||||
if config.nodes.soft > config.nodes.hard:
|
||||
raise newException(ValueError, "soft node limit cannot exceed the hard node limit")
|
||||
if config.hashMiB < 1:
|
||||
raise newException(ValueError, "hash size must be at least 1 MiB per worker")
|
||||
if config.threads notin 1..1024:
|
||||
@@ -327,9 +342,13 @@ proc relabelViriformat*(inputPath, outputPath: string, config: RelabelConfig) =
|
||||
if ready.error.len > 0:
|
||||
raise newException(IOError, &"worker {i} failed to initialise: {ready.error}")
|
||||
|
||||
let depthDescription = if config.depth.isSome(): &", depth={config.depth.get()}" else: ""
|
||||
echo &"Relabelling '{inputPath}' with {config.threads} worker(s), " &
|
||||
&"nodes={config.nodes.soft}/{config.nodes.hard}{depthDescription}, hash={config.hashMiB} MiB/worker"
|
||||
if config.usesStaticEval():
|
||||
echo &"Relabelling '{inputPath}' with {config.threads} worker(s), " &
|
||||
&"mode=static evaluation, hash={config.hashMiB} MiB/worker"
|
||||
else:
|
||||
let depthDescription = if config.depth.isSome(): &", depth={config.depth.get()}" else: ""
|
||||
echo &"Relabelling '{inputPath}' with {config.threads} worker(s), " &
|
||||
&"nodes={config.nodes.soft}/{config.nodes.hard}{depthDescription}, hash={config.hashMiB} MiB/worker"
|
||||
|
||||
let inputFileSize = getFileSize(inputPath)
|
||||
var
|
||||
|
||||
Reference in New Issue
Block a user