LittlePlyResearch notebook

inference.py

python · 81 lines · Exact bytes

Line 1 ↗
Version & provenance · service-20261028

Recorded SHA-256 072486068cc2af9c1fbf963defb66ad149baa22669262723ea8bc019ad4043bfRendered SHA-256 072486068cc2af9c1fbf963defb66ad149baa22669262723ea8bc019ad4043bf

Apache-2.0 for original LittlePly code, declared by the project owner on 2026-10-28. Historical source bytes and hashes are unchanged. Third-party components retain their own licenses; Lichess data remain CC0-1.0. License notes. Line links are stable within this snapshot. Public service source captured on 2026-10-28 after the runtime owner verified CPU-only systemd serving active with zero restarts. One concurrent public casual standard-start game; 10-30 minute initial clocks and 5-30 second increments. Availability depends on the local Windows/Ubuntu host. This is an allowlisted service subset, not a complete portable deployment bundle: launch, release-management scripts and private state are excluded. Source hashes identify this snapshot, not new training or evaluation results.

1"""Single keyed inference owner. CPU only; no concurrent Pipe consumers."""
2import multiprocessing as mp,queue,threading,time,uuid
3
4def child(conn,checkpoint):
5    import torch
6    import training_core as tc
7    import harness as h
8    from scorer import Scorer
9    try:
10        torch.set_num_threads(4);torch.manual_seed(20261028);torch.use_deterministic_algorithms(True)
11        net,meta=tc.load_model_checkpoint(checkpoint,'cpu')
12        from chessdata import tokenizer
13        from pathlib import Path
14        import hashlib
15        if hashlib.sha256(Path(tokenizer.__file__).read_bytes()).hexdigest()!=meta['identity']['tokenizer']['sha256']:
16            raise ValueError('checkpoint tokenizer identity mismatch')
17        model=Scorer(net)
18        model.score(h.chess.Board());conn.send(('ready',{}))
19        while True:
20            rid,board,seconds=conn.recv();start=time.monotonic()
21            proof=h.forced_mate(board,adjudicate=h.outcome,max_nodes=50000,seconds=min(1.,seconds*.2))
22            if proof['status']=='proven':
23                move=h.chess.Move.from_uci(proof['move']);metrics={'decision_source':'forced_mate_override','model_query':'not_queried'}
24            else:
25                move,metrics=model.score(board);metrics.pop('full_legal_scores',None)
26                metrics.update(decision_source='model_policy',model_query='queried')
27            metrics.update(mate_search=proof,decision_seconds=time.monotonic()-start)
28            conn.send((rid,move.uci(),metrics))
29    except EOFError:pass
30    except Exception as exc:
31        try:conn.send(('error',type(exc).__name__))
32        except Exception:pass
33    finally:conn.close()
34
35class Owner:
36    def __init__(self,checkpoint):
37        self.checkpoint=str(checkpoint);self.jobs=queue.Queue(1);self.results=queue.Queue(2)
38        self.stop=threading.Event();self.ready=False;self.busy=False;self.p=None;self.conn=None
39        self.thread=threading.Thread(target=self.run,daemon=True);self.thread.start()
40    def submit(self,key,board,seconds):
41        if self.busy or not self.ready:raise RuntimeError('inference unavailable')
42        self.busy=True;self.jobs.put_nowait((key,board,seconds))
43    def reset(self):
44        self.ready=False
45        if self.p:
46            if self.p.is_alive():self.p.kill()
47            self.p.join(timeout=3)
48        if self.conn:self.conn.close()
49        self.p=None;self.conn=None
50    def run(self):
51        failures=0
52        try:
53            while not self.stop.is_set():
54                if not self.ready:
55                    try:
56                        ctx=mp.get_context('spawn');parent,peer=ctx.Pipe();self.conn=parent
57                        self.p=ctx.Process(target=child,args=(peer,self.checkpoint),daemon=True);self.p.start();peer.close()
58                        if not parent.poll(60) or parent.recv()[0]!='ready':raise RuntimeError('warmup failed')
59                        self.ready=True;failures=0
60                    except Exception:
61                        self.reset();failures+=1
62                        if self.stop.wait(min(60,5*failures)):return
63                        continue
64                try:key,board,seconds=self.jobs.get(timeout=.2)
65                except queue.Empty:continue
66                rid=uuid.uuid4().hex
67                try:
68                    self.conn.send((rid,board,seconds))
69                    if not self.conn.poll(seconds):raise TimeoutError('model deadline')
70                    answer=self.conn.recv()
71                    if answer[0]!=rid:raise RuntimeError('wrong inference response identity')
72                    self.results.put((key,'ok',answer[1:]))
73                except Exception as exc:
74                    self.results.put((key,'error',type(exc).__name__));self.reset()
75                finally:self.busy=False
76        finally:self.reset()
77    def close(self):
78        self.stop.set()
79        if self.p and self.p.is_alive():self.p.kill()
80        self.thread.join(timeout=5)
81