month_twenty.py
python · 177 lines · Sanitized derivative
Sanitized derivative: private paths or personal test identities use explicit placeholders. Original line count is preserved.
Version & provenance · match-20261028
Recorded SHA-256 28245689f1b09742216bb411997476166973c0bea850c8ecb3f7e423b44d19e3Rendered SHA-256 c1d3fd8cfddf7fa8b979ce2a7361ffeb14b74da9d5b8ece6ef0a687bbc505262
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. SHA-256 compared to persisted run identity/manifest. Unavailable files are never replaced with newer versions. Rendered derivatives preserve line numbers and redact private paths; original and rendered hashes are distinct.
1"""Authorized 20-game full-month checkpoint match. No training or data evaluation."""
2import os,sys,json,subprocess,time,re,statistics,io
3from pathlib import Path
4os.environ.update(HF_HUB_OFFLINE='1',TRANSFORMERS_OFFLINE='1',CUBLAS_WORKSPACE_CONFIG=':4096:8',PYTHONDONTWRITEBYTECODE='1')
5sys.path.insert(0,'<PRIVATE_PATH>')
6import training_core as tc
7import torch,transformers,chess,chess.pgn
8import harness as h
9
10OUT=h.ROOT/'month-best-20-games-seed20261028'
11CK=Path(tc.__file__).parent/'training-v1/month-2h/best.pt'
12EXPECTED='6133648b2926b38542ee3ee1251a1ba111d61da9a8ea69d1c5f0559b09933643'
13SEED=20261028
14# Hand-authored standard opening sequences; no engine selection, database,
15# training/test lookup or result-dependent selection. Each appears in both colours.
16OPENINGS=[
17 ('Ruy Lopez','e2e4 e7e5 g1f3 b8c6 f1b5 a7a6'),
18 ('Sicilian','e2e4 c7c5 g1f3 d7d6 d2d4 c5d4'),
19 ('French','e2e4 e7e6 d2d4 d7d5 b1c3 g8f6'),
20 ('Caro-Kann','e2e4 c7c6 d2d4 d7d5 b1c3 d5e4'),
21 ('Queens Gambit Declined','d2d4 d7d5 c2c4 e7e6 b1c3 g8f6'),
22 ('Kings Indian','d2d4 g8f6 c2c4 g7g6 b1c3 f8g7'),
23 ('Queens Indian','d2d4 g8f6 c2c4 e7e6 g1f3 b7b6'),
24 ('English','c2c4 e7e5 b1c3 g8f6 g2g3 d7d5'),
25 ('Reti','g1f3 d7d5 g2g3 g8f6 f1g2 e7e6'),
26 ('Italian','e2e4 e7e5 f1c4 g8f6 d2d3 f8c5'),
27]
28
29def write_json(path,value):
30 tmp=path.with_suffix(path.suffix+'.tmp')
31 tmp.write_text(json.dumps(value,indent=2));tmp.replace(path)
32
33def verify_proof(board,proof,attacker,depth=0):
34 assert depth<=3 and board.fen()==proof['fen']
35 if proof['kind']=='checkmate':
36 assert board.is_checkmate() and board.turn!=attacker;return
37 assert h.outcome(board) is None and depth<3
38 branches=proof['branches']
39 if board.turn!=attacker:
40 assert [x['move'] for x in branches]==sorted(m.uci() for m in board.legal_moves)
41 else:assert len(branches)==1
42 for branch in branches:
43 m=chess.Move.from_uci(branch['move']);assert m in board.legal_moves
44 board.push(m)
45 try:verify_proof(board,branch['child'],attacker,depth+1)
46 finally:board.pop()
47
48def audit(folder,report,opening,color):
49 with (folder/'game.pgn').open() as f:g=chess.pgn.read_game(f)
50 assert g and not g.errors
51 moves=list(g.mainline_moves());assert [m.uci() for m in moves[:6]]==opening
52 board=chess.Board();by_ply={r['ply']:r for r in report['moves']}
53 for m in moves:
54 assert m in board.legal_moves
55 row=by_ply.get(board.ply()+1)
56 if row and row.get('decision_source')=='forced_mate_override':
57 assert row['model_query']=='not_queried' and 'raw_greedy_legal' not in row
58 assert row['mate_search']['move']==m.uci()
59 verify_proof(board,row['mate_search']['proof'],color)
60 board.push(m)
61 assert board.fen()==report['final_fen']
62 end=board.outcome(claim_draw=True)
63 if report['result']!='*':assert end and end.result()==report['result']
64 queried=[r for r in report['moves'] if r.get('model_query')=='queried']
65 nodes=[]
66 log=folder/'uci.log'
67 if log.exists():
68 for part in log.read_text().split('> go nodes 1000')[1:]:
69 found=re.findall(r' nodes (\d+)',part)
70 nodes.append(int(found[-1]) if found else None)
71 return g,{'plies':report['plies'],'result':report['result'],'termination':report['termination'],
72 'error':report['error'],'cleanup_error':report['cleanup_error'],'seconds':report['seconds'],
73 'model_queries':len(queried),'raw_greedy_legal':sum(r['raw_greedy_legal'] for r in queried),
74 'raw_legal_probability_sum':sum(r['legal_joint_probability'] for r in queried),
75 'overrides':sum(r.get('decision_source')=='forced_mate_override' for r in report['moves']),
76 'tactical_unknown':sum(r.get('mate_search',{}).get('status')=='unknown' for r in report['moves']),
77 'sunfish_reported_nodes':nodes,'all_moves_legal':True,'pgn_roundtrip_verified':True,
78 'proofs_replayed':True}
79
80def publish(results,games,started):
81 summary={'completed_games':len(results),'seconds_since_start':time.monotonic()-started,'by_colour':{}}
82 for colour in ['white','black']:
83 rows=[r for r in results if r['model_colour']==colour]
84 win='1-0' if colour=='white' else '0-1';loss='0-1' if colour=='white' else '1-0'
85 summary['by_colour'][colour]={'wins':sum(r['result']==win for r in rows),
86 'draws':sum(r['result']=='1/2-1/2' for r in rows),'losses':sum(r['result']==loss for r in rows),
87 'unfinished':sum(r['result']=='*' for r in rows)}
88 for key in ['overrides','model_queries','raw_greedy_legal','tactical_unknown']:
89 summary[key]=sum(r[key] for r in results)
90 summary['mean_raw_legal_probability']=sum(r['raw_legal_probability_sum'] for r in results)/max(1,summary['model_queries'])
91 observed=[n for r in results for n in r['sunfish_reported_nodes'] if n is not None]
92 summary['sunfish_requested_nodes']=1000
93 summary['sunfish_reported_nodes_range']=[min(observed),max(observed)] if observed else None
94 summary['terminal_games']=sum(r['result']!='*' for r in results)
95 summary['capped_games']=sum(r['termination']=='ply_cap_unfinished' for r in results)
96 summary['timeout_games']=sum(r['termination']=='timeout_unfinished' for r in results)
97 summary['failed_games']=sum(r['termination']=='error_unfinished' for r in results)
98 summary['total_game_seconds']=sum(r['seconds'] for r in results)
99 write_json(OUT/'results.json',results);write_json(OUT/'summary.json',summary)
100 text='\n\n'.join(str(g) for g in games)+'\n'
101 stream=io.StringIO(text)
102 for original in games:
103 replay=chess.pgn.read_game(stream)
104 assert replay and not replay.errors and list(replay.mainline_moves())==list(original.mainline_moves())
105 assert replay.headers['Result']==original.headers['Result']
106 assert chess.pgn.read_game(stream) is None
107 tmp=OUT/'full-month-vs-sunfish-20-games.pgn.tmp';tmp.write_text(text)
108 tmp.replace(OUT/'full-month-vs-sunfish-20-games.pgn')
109 return summary
110
111def run():
112 assert h.sha(CK)==EXPECTED,'checkpoint hash mismatch'
113 saved=tc.load_checkpoint(CK)
114 cfg=saved['model_config']
115 assert (cfg['n_layer'],cfg['n_embd'],cfg['n_head'],cfg['vocab_size'],cfg['n_positions'])==(12,768,12,128,1024)
116 assert saved['identity']['tokenizer']['sha256']==h.sha(h.tokenizer.__file__)
117 openings=[]
118 for name,line in OPENINGS:
119 b=chess.Board()
120 for uci in line.split():
121 assert h.outcome(b) is None;b.push_uci(uci)
122 assert h.outcome(b) is None
123 openings.append({'name':name,'uci':line.split(),'final_fen':b.fen()})
124 assert len(set(o['final_fen'] for o in openings))==10
125 git=['git','-c','core.autocrlf=true','-c','core.filemode=false','-C',str(h.ROOT/'sunfish')]
126 assert subprocess.check_output(git+['rev-parse','HEAD'],text=True).strip()==h.PIN
127 assert not subprocess.check_output(git+['status','--porcelain'],text=True).strip()
128 occupancy=subprocess.check_output(['nvidia-smi','--query-compute-apps=pid,process_name','--format=csv,noheader'],text=True)
129 assert not occupancy.strip(),'GPU busy: '+occupancy
130 OUT.mkdir(exist_ok=False)
131 (OUT/'gpu-preflight.txt').write_text(subprocess.check_output(['nvidia-smi'],text=True))
132 manifest={'label':'FULL-MONTH TRAINED MODEL + THREE-PLY FORCED-MATE OVERRIDE',
133 'checkpoint':str(CK),'checkpoint_sha256':EXPECTED,'checkpoint_identity':saved['identity'],
134 'checkpoint_cursor':saved['cursor'],'model_config':cfg,'seed':SEED,
135 'opening_provenance':'Ten hand-authored common opening sequences selected before play; no database/engine/test-set selection',
136 'openings':openings,'games':20,'sunfish_commit':h.PIN,'sunfish_runtime':sys.version,
137 'sunfish_requested_nodes':1000,'engine_timeout_seconds':10,'model_timeout_seconds':60,
138 'game_walltime_seconds':180,'ply_cap_including_opening':256,
139 'mate_max_plies':3,'mate_nodes':50000,'mate_seconds':1.,
140 'draw_policy':'immediate symmetric python-chess outcome(claim_draw=True)',
141 'decoding':'full legal-move joint argmax; raw probabilities separately logged; tactical override skips model',
142 'torch':torch.__version__,'transformers':transformers.__version__,'python_chess':chess.__version__,
143 'device':'cuda:1','dtype':'float32','executable':sys.executable,
144 'source_hashes':{p.name:h.sha(p) for p in [Path(__file__),Path(h.__file__),h.ROOT/'mate_override.py',Path(tc.__file__),Path(tc.__file__).with_name('training_adapter.py')]}}
145 write_json(OUT/'manifest.json',manifest)
146 del saved
147 os.environ['CHESS_INFERENCE_DEVICE']='cuda:1'
148 results=[];games=[];started=time.monotonic();worker=None
149 try:
150 for index,opening in enumerate(openings,1):
151 for color in [True,False]:
152 name=f'{index:02d}-model-'+('white' if color else 'black')
153 if worker is None or not worker.p.is_alive():
154 if worker is not None:worker.close()
155 worker=h.ModelWorker('training_adapter:load',CK,SEED,60)
156 worker.timeout=60
157 print('START '+name+' '+opening['name'],flush=True)
158 report=h.game(worker,lambda:h.UCI(h.engine_command(),10),color,OUT/name,
159 cap=256,nodes=1000,opening=opening['uci'],walltime=180,
160 label='FULL-MONTH TRAINED; '+opening['name']+'; no Elo claim',
161 mate_override=True,mate_nodes=50000,mate_seconds=1.)
162 g,row=audit(OUT/name,report,opening['uci'],color)
163 g.headers.update(Round=str(len(results)+1),Opening=opening['name'],
164 White='Full-month model + mate override' if color else 'Sunfish classic',
165 Black='Sunfish classic' if color else 'Full-month model + mate override',
166 CheckpointSHA256=EXPECTED,SunfishCommit=h.PIN)
167 (OUT/name/'game.pgn').write_text(str(g)+'\n')
168 row.update(game=name,opening=opening['name'],model_colour='white' if color else 'black',seed=SEED,
169 pgn_sha256=h.sha(OUT/name/'game.pgn'))
170 results.append(row);games.append(g);summary=publish(results,games,started)
171 print(json.dumps({k:v for k,v in row.items() if k!='sunfish_reported_nodes'}),flush=True)
172 print('FINAL '+json.dumps(summary),flush=True)
173 finally:
174 if worker is not None:worker.close()
175
176if __name__=='__main__':run()
177