summaryrefslogtreecommitdiff
path: root/annotate_pgns/run_nnue_pipeline.py
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-20 18:37:20 -0700
committerScott Gasch <[email protected]>2026-10-20 18:37:20 -0700
commit9cf14e14e6999420f234cda951894337e034f57c (patch)
treed4845d0cf127ef4226a5cd2e60472ee750213614 /annotate_pgns/run_nnue_pipeline.py
parentc8c6bbc2c1a103e5b07b079a0f8bdf66c7724d80 (diff)
NNUE: re-fit raw-to-score calibration, NNU4 weight format, super-lazy default-on, lazy probe measured net-negative
Fits an affine (a + b*raw) correction into every exported .nnue file (export_nnue_weights.py's CalibrateScoreMapping) so the engine's consumed centipawn output tracks Stockfish's real score scale rather than the net's nominal raw WDL-sigmoid output -- training variance alone shifts this mapping across retrains even with an identical recipe, so it's re-fit and baked in per export rather than assumed constant. Weight format bumped to "NNU4" to carry the two new floats; older NNU1/2/3 files are no longer readable. Super-lazy (material-only) pre-filter promoted to default-on (g_fUseNnueSuperLazy = TRUE) with a recalibrated per-material-bucket margin table (measured against the recalibrated net, 16-shard cross_sectional_200k.ep_ sample) -- real, measured nps win across all three curated suites. The separate lazy-probe gate (accumulator-based early exit) is measured net-negative on real search traffic at this net's probe-vs-forward latency ratio and stays off by default (g_fUseNnueLazyProbe = FALSE); code kept live since margin tuning could still flip this, per docs/NNUE.md. Also: direct per-call latency instrumentation for NnueProbeScore/ NnueForward (root.c, EVAL_TIME build) to make the lazy-probe breakeven math measurable directly instead of inferred; QSearch futility margins given real-data-derived NNUE_FULL/NNUE_LAZY starting points. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'annotate_pgns/run_nnue_pipeline.py')
-rw-r--r--annotate_pgns/run_nnue_pipeline.py57
1 files changed, 52 insertions, 5 deletions
diff --git a/annotate_pgns/run_nnue_pipeline.py b/annotate_pgns/run_nnue_pipeline.py
index 39bc487..6e51416 100644
--- a/annotate_pgns/run_nnue_pipeline.py
+++ b/annotate_pgns/run_nnue_pipeline.py
@@ -199,6 +199,21 @@ def main():
help="build_training_tensors.py's "
"target = lambda*p_search + (1-lambda)*r_outcome "
"for non-exact rows")
+ ap.add_argument("--k", type=float, default=None,
+ help="nnue_train.py's --k (the WDL-sigmoid scale, "
+ "pred=sigmoid(raw/k)) -- previously NOT passed "
+ "through at all, silently falling back to "
+ "nnue_train.py's own DEFAULT_K (1.3123, a Phase-A "
+ "placeholder), regardless of what K "
+ "fit_k_and_label.py actually fit for this corpus's "
+ "own p_search labels (stage 3 below) -- a real "
+ "label/loss K mismatch, fixed 2026-10-20. Required "
+ "now (no default) specifically so this can't go "
+ "silently unmatched again: read fit_k_and_label.py's "
+ "own printed 'Fitted K = ...' line for this corpus "
+ "and pass that (times whatever saturation-"
+ "correction factor empirical testing supports -- "
+ "see docs/NNUE.md's K-recalibration status).")
ap.add_argument("--epochs", type=int, default=20)
ap.add_argument("--patience", type=int, default=0,
help="nnue_train.py's --patience (0 disables early "
@@ -206,12 +221,21 @@ def main():
ap.add_argument("--batch-size", type=int, default=8192)
ap.add_argument("--lr", type=float, default=1e-3)
ap.add_argument("--loss-exponent", type=float, default=2.0)
- ap.add_argument("--material-loss-weight", type=float, default=0.0)
+ ap.add_argument("--score-loss-weight", type=float, default=0.0)
ap.add_argument("--force", action="store_true",
help="re-run every stage even if its outputs "
"already exist")
args = ap.parse_args()
+ if args.k is None:
+ sys.exit("--k is required (no default, deliberately -- see its "
+ "help text): pass the K you actually want nnue_train.py's "
+ "loss to use. Do not guess -- run fit_k_and_label.py "
+ "first (or check its log from stage 3 below) to see what "
+ "it fits for THIS corpus, and apply whatever saturation-"
+ "correction factor has been validated empirically before "
+ "trusting its raw fit.")
+
pgn_paths = sorted({p for pattern in args.pgn_glob
for p in glob.glob(pattern)})
if not pgn_paths:
@@ -361,8 +385,8 @@ def main():
"--holdout-dir", tensors_holdout_dir,
"--outdir", ckpt_dir, *resume_flag,
"--epochs", args.epochs, "--batch-size", args.batch_size,
- "--lr", args.lr, "--loss-exponent", args.loss_exponent,
- "--material-loss-weight", args.material_loss_weight,
+ "--lr", args.lr, "--k", args.k, "--loss-exponent", args.loss_exponent,
+ "--score-loss-weight", args.score_loss_weight,
"--threads", args.threads, "--device", args.device,
"--patience", args.patience],
args.force)
@@ -372,10 +396,33 @@ def main():
run_stage(
"export_nnue_weights.py", [args.outfile],
[sys.executable, HERE / "export_nnue_weights.py",
- ckpt, args.outfile, "--tensor-dir", tensors_train_dir],
+ ckpt, args.outfile, "--tensor-dir", tensors_train_dir,
+ "--holdout-tensor-dir", tensors_holdout_dir],
+ args.force)
+
+ # 8. Refit the lazy-eval probe (nnue_vs_head.sh's NNUE_PROBE_WEIGHTS /
+ # command.c's "nnue probeload") against THIS checkpoint -- it's a
+ # separate offline ridge regression, not part of the exported .nnue
+ # file, and must be re-fit every generation (same rule as the
+ # calibration inside export_nnue_weights.py: not portable across
+ # checkpoints). Written alongside outfile, not into it. See
+ # fit_lazy_probe.py and the 2026-10-20 "keeping this stable across
+ # retrains" / lazy-eval conversation -- this is the regression fit
+ # only; the real search-node-traffic margin-safety calibration
+ # (CALIBRATE_MARGIN_SAFETY) is a separate, not-yet-automated step,
+ # deliberately not run here every generation (see that conversation
+ # for why it may not need to be).
+ probe_outfile = Path(args.outfile).with_name(
+ Path(args.outfile).stem + "_probe.bin")
+ run_stage(
+ "fit_lazy_probe.py", [probe_outfile],
+ [sys.executable, HERE / "fit_lazy_probe.py",
+ ckpt, probe_outfile,
+ "--train-tensor-dir", tensors_train_dir,
+ "--holdout-tensor-dir", tensors_holdout_dir],
args.force)
- print(f"\nPipeline complete: {args.outfile}")
+ print(f"\nPipeline complete: {args.outfile} (lazy probe: {probe_outfile})")
if __name__ == "__main__":