Skip to content

Commit

Permalink
Add dirty and possibly short lived tuner based on bench
Browse files Browse the repository at this point in the history
  • Loading branch information
bsamseth committed Aug 21, 2024
1 parent 46d7812 commit b5fada3
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 0 deletions.
1 change: 1 addition & 0 deletions stats/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
quiet-labeled.v7.epd.gz
2 changes: 2 additions & 0 deletions stats/nodebench/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
__pycache__
49 changes: 49 additions & 0 deletions stats/nodebench/bench.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"depth": 6,
"uci_options": {
"Hash": "128",
"SyzygyPath": "../../syzygy",
"max_history_stats_impact": "20"
},
"cases": [
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11",
"4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19",
"rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6",
"r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4",
"r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15",
"r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13",
"r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16",
"4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17",
"2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11",
"r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16",
"3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22",
"r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18",
"4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22",
"3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26",
"6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1",
"3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1",
"2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3",
"8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1",
"7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1",
"8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1",
"8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1",
"8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1",
"8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1",
"5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1",
"6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1",
"1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1",
"6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1",
"8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1",
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1",
"8/8/8/5N2/8/p7/8/2NK3k w - - 0 1",
"8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1",
"8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1",
"8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1",
"8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1",
"8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124",
"6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1",
"r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1"
]
}
73 changes: 73 additions & 0 deletions stats/nodebench/bench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import argparse
import subprocess
from pathlib import Path

from pydantic import BaseModel
from tqdm import tqdm


class BenchSuite(BaseModel):
depth: int
uci_options: dict[str, str]
cases: list[str]


class Case(BaseModel):
depth: int
uci_options: dict[str, str]
fen: str


def run_bench(engine: Path, case: Case) -> int:
proc = subprocess.Popen([engine], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
assert proc.stdout is not None
assert proc.stdin is not None

indata = "\n".join(
["uci"]
+ [f"setoption name {k} value {v}" for k, v in case.uci_options.items()]
+ [f"position fen {case.fen}", f"go depth {case.depth}\n"]
).encode()
proc.stdin.write(indata)
proc.stdin.flush()

nodes = None
for line in map(bytes.decode, iter(proc.stdout.readline, b"")):
if line.startswith("bestmove"):
break
if line.startswith("info"):
words = line.split()
for label, value in zip(words[1:], words[2:]):
if label == "nodes":
nodes = int(value)

proc.stdin.write(b"quit\n")
proc.stdin.flush()
proc.wait()
assert nodes is not None, f"no nodes counted for {case.fen}"
return nodes


def run_suite(engine: Path, suite: BenchSuite, progress: bool = True) -> int:
return sum(
run_bench(
engine, Case(depth=suite.depth, uci_options=suite.uci_options, fen=fen)
)
for fen in (suite.cases if not progress else tqdm(suite.cases))
)


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("engine", type=Path)
parser.add_argument("benchfile", type=Path)
parser.add_argument("--no-progress", action="store_true")
args = parser.parse_args()

assert args.engine.exists() and args.engine.is_file()
assert args.benchfile.exists() and args.benchfile.is_file()

with open(args.benchfile) as f:
suite = BenchSuite.model_validate_json(f.read())

print(f"{run_suite(args.engine, suite, progress=not args.no_progress):_} nodes")
11 changes: 11 additions & 0 deletions stats/nodebench/tune.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"options": [
{
"name": "max_history_stats_impact",
"bounds": [
7,
13
]
}
]
}
73 changes: 73 additions & 0 deletions stats/nodebench/tune.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import argparse
from functools import cache
from pathlib import Path

import numpy as np
from pydantic import BaseModel
from scipy.optimize import brute
from tqdm import tqdm

from bench import BenchSuite, run_suite


class OptionConfig(BaseModel):
name: str
bounds: tuple[int, int]


class TuneConfig(BaseModel):
options: list[OptionConfig]


def tune(
engine: Path, config: TuneConfig, suite: BenchSuite
) -> tuple[int, dict[str, int]]:

@cache
def inner(args: tuple[str]) -> int:
print("Running with args:", args)
for i, opt in enumerate(config.options):
suite.uci_options[opt.name] = args[i]
return run_suite(engine, suite, progress=True)

def objective(x: np.ndarray) -> int:
x = np.round(x).astype(int)
args = tuple(str(xi) for xi in x)
return inner(args)

def callback(x: np.ndarray, f: float, context: int):
print(f"\n\nnodes: {f} params: {x}, context: {context}\n")

bounds = [slice(*opt.bounds) for opt in config.options]

result = brute(objective, bounds, Ns=1, disp=True, full_output=True)

with open("/tmp/tune_result.pickl", "wb") as f:
import pickle

pickle.dump(result, f)

print(
"Global minimum:",
", ".join(f"{opt.name}: {x} " for x, opt in zip(result[0], config.options)),
)
print("Nodes:", result[1])


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("engine", type=Path)
parser.add_argument("benchfile", type=Path)
parser.add_argument("configfile", type=Path)
args = parser.parse_args()

assert args.engine.exists() and args.engine.is_file()
assert args.benchfile.exists() and args.benchfile.is_file()
assert args.configfile.exists() and args.configfile.is_file()

with open(args.benchfile) as f:
suite = BenchSuite.model_validate_json(f.read())
with open(args.configfile) as f:
config = TuneConfig.model_validate_json(f.read())

tune(args.engine, config, suite)

0 comments on commit b5fada3

Please sign in to comment.