diff options
| author | Scott Gasch <[email protected]> | 2026-10-20 18:37:20 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-10-20 18:37:20 -0700 |
| commit | 9cf14e14e6999420f234cda951894337e034f57c (patch) | |
| tree | d4845d0cf127ef4226a5cd2e60472ee750213614 /annotate_pgns/run_nnue_pipeline.py | |
| parent | c8c6bbc2c1a103e5b07b079a0f8bdf66c7724d80 (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.py | 57 |
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__": |
