cazyundee commited on
Commit
f9afc8e
·
verified ·
1 Parent(s): f31725e

log: v7-v12 stability campaign, 3 bugs fixed, no strength gain

Browse files
Files changed (1) hide show
  1. docs/RESEARCH_LOG.md +124 -0
docs/RESEARCH_LOG.md CHANGED
@@ -2665,3 +2665,127 @@ slower** per update, and 96 threads is 5.8x slower — the update step is small
2665
  enough that thread-synchronisation overhead dominates past 16. More cores do not
2666
  help this workload; the 192-core machine is best used for parallel self-play
2667
  (16 workers x 2 threads) rather than for wider intra-op parallelism.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2665
  enough that thread-synchronisation overhead dominates past 16. More cores do not
2666
  help this workload; the 192-core machine is best used for parallel self-play
2667
  (16 workers x 2 threads) rather than for wider intra-op parallelism.
2668
+
2669
+ ## `main_v6` is INVALID: `--resume` silently fell back to random init (MEASURED, FIXED)
2670
+
2671
+ **Status: MEASURED. `main_v6` must not be cited as a result.**
2672
+
2673
+ `main_v6` was launched with `--resume main_baseline/latest.pt`, a path inside the
2674
+ **hub model repo**, not a local file. The container-rebuild recovery block only
2675
+ knew how to pull `<basename(--out)>/latest.pt` — i.e. `main_v6/latest.pt`, which
2676
+ did not exist yet on a first launch. Both lookups failed, the `if args.resume and
2677
+ os.path.exists(...)` guard was simply false, and training **started from random
2678
+ init with no warning**. The metrics file confirms it: an `init` event is present
2679
+ and the first iteration is `iter 0`, not `iter 270`.
2680
+
2681
+ The damage was not merely a wasted run. `--eval-every` evaluates the current model
2682
+ against `<out>/init.pt`, and `init.pt` was now a **random network**. The loop
2683
+ therefore reported, at iter 9:
2684
+
2685
+ EVAL vs init (adj=material): score=0.850 W42/D18/L0 elo+301
2686
+
2687
+ which reads as a large improvement and is really "a 10-iteration model beats a
2688
+ random one". Direct head-to-head against the real baseline says the opposite:
2689
+
2690
+ | comparison | opening plies | T | score | W/D/L | Wilson CI |
2691
+ |---|---:|---:|---:|---|---|
2692
+ | v6 vs iter269 | 6 | 0.0 | 0.500 | 0/120/0 | [0.412,0.588] |
2693
+ | v6 vs iter269 | 6 | 0.3 | **0.317** | 5/66/49 | [0.240,0.404] |
2694
+ | v6 vs iter269 | 10 | 0.0 | 0.500 | 0/120/0 | [0.412,0.588] |
2695
+ | v6 vs iter269 | 10 | 0.3 | **0.325** | 3/72/45 | [0.248,0.413] |
2696
+ | exact in-loop repro (seed 99, n=60) | 2 | 0.3 | 0.325 | 1/37/22 | — |
2697
+
2698
+ Reproducing the in-loop call exactly but with iter269 as the opponent yields
2699
+ 0.325, not 0.850 — confirming the opponent, not the protocol, was the difference.
2700
+ v6 (10 iterations from scratch) is **clearly weaker** than iter269, as expected.
2701
+
2702
+ Two lessons, both now enforced in code:
2703
+ 1. **An in-loop "EVAL vs init" number is only meaningful if `init.pt` is the
2704
+ intended reference.** It silently redefines itself on a fresh run.
2705
+ 2. **A missing `--resume` target must be fatal.** `train_selfplay.py` now resolves
2706
+ a hub-style path by downloading it, and raises
2707
+ `FATAL: --resume ... Refusing to start from random init silently` otherwise.
2708
+ Verified both branches: bad path -> exit 1; `main_baseline/latest.pt` ->
2709
+ `[recover] pulled ckpt` then `[resume] iter=270`.
2710
+
2711
+ Note the temp-0 rows: 120/120 identical draws. At temperature 0 with only 2-10
2712
+ random opening plies, two deterministic policies replay **the same game**, so a
2713
+ temp-0 arena of any size carries almost no information. Comparisons must use
2714
+ T>0 or heavily randomised openings.
2715
+
2716
+ ### `main_v7` (relaunch, correct resume)
2717
+ Genuinely continues from iter269 (`[resume] iter=270`), 1024 games/192 updates,
2718
+ `--threads 16`. Learn phase **197-220 s vs v6's 359-381 s** — the thread
2719
+ benchmark transferred to the real loop (~1.75x faster per iteration).
2720
+ Early iterations: plies ~144, decisive 0.58-0.67, entropy 1.90 -> 1.76.
2721
+ **No strength claim yet** — pending an eval against iter269 at iter 280.
2722
+
2723
+ ### `main_v7` @ iter 292 — independent verification (MEASURED, NOT SIGNIFICANT)
2724
+
2725
+ In-loop eval vs iter269 (n=60): iter 280 **0.542** (+29 Elo), iter 290 **0.558**
2726
+ (+41 Elo). Independent arena, larger n, several conditions:
2727
+
2728
+ | condition | score | W/D/L | Wilson CI |
2729
+ |---|---:|---|---|
2730
+ | T=0.3, open 6 | 0.506 | 13/136/11 | [0.430,0.583] |
2731
+ | T=0.3, open 10 | 0.491 | 10/137/13 | [0.414,0.567] |
2732
+ | T=0.5, open 6 | 0.591 | 41/107/12 | [0.513,0.664] |
2733
+ | **POOLED n=480** | **0.529** | — | **[0.484,0.573]** |
2734
+
2735
+ **The pooled CI contains 0.5: `main_v7` is NOT yet measurably stronger than
2736
+ iter269.** Only the T=0.5 condition is nominally above 0.5, and selecting it
2737
+ alone would be cherry-picking across three conditions. The in-loop n=60 numbers
2738
+ (+29/+41 Elo) are inside their own noise and must not be quoted as progress.
2739
+
2740
+ `runs/mainval269/latest.pt` and hub `main_baseline/latest.pt` remain canonical.
2741
+ Self-play trend so far is favourable (decisive 0.61 -> 0.77, entropy 1.90 -> 1.46
2742
+ over iters 270-292) but self-play statistics are not strength.
2743
+
2744
+ ## Optimisation-stability campaign v7-v12: three real bugs fixed, NO strength gain (MEASURED)
2745
+
2746
+ All runs resume from iter269 (`main_baseline/latest.pt`), 1024 games/iter.
2747
+
2748
+ ### Bugs found and fixed (all verified in isolation)
2749
+ 1. **`--resume` silently fell back to random init** for hub-style paths. `main_v6`
2750
+ is invalid; its "+301 Elo" was against a random `init.pt`. Now FATAL. Verified.
2751
+ 2. **Repetition feature destroyed by the FEN round-trip.** Fired at play time
2752
+ (0.667), always 0.0 at train time. Now recorded per position. Verified: 83/1343
2753
+ samples restored.
2754
+ 3. **Last-move feature destroyed by the same round-trip.** Play time encodes with
2755
+ the last move, training encoded with `None`. Now recorded. Verified: mean
2756
+ |logp_old - logp_recomputed| on an UNCHANGED model drops **0.0222 -> 0.0080
2757
+ (2.8x)** — that residual was pure spurious PPO ratio error.
2758
+ 4. **Entropy-controller windup.** `ent_coef *= 1.5` every iteration while the
2759
+ target was unreachable (0.01 -> 0.17) as approx_kl went 1.3 -> 59.8 and
2760
+ grad_norm 6 -> 1918. Added `--ent-kl-ceiling` (back off when KL diverges).
2761
+ 5. **`--updates 192` was the dominant divergence driver.** `main_v4` (48 updates)
2762
+ held approx_kl at 0.01 for 75 iterations; v7/v8 (192 updates) reached 70.
2763
+
2764
+ ### Config search over the KL controls (MEASURED)
2765
+
2766
+ | run | updates | KL controls | outcome |
2767
+ |---|---:|---|---|
2768
+ | v7 | 192 | none | KL 1.9 -> 70, grad 1918, decisive 0.77 -> 0.53 |
2769
+ | v8 | 192 | ent-kl-ceiling 6 | ent_coef pinned to min, KL still 63 -> entropy was NOT the cause |
2770
+ | v9 | 48 | ent-kl-ceiling 6 | learn 47 s/iter (8x faster), KL ~6-8, then drift to 33 |
2771
+ | v10 | 48 | + target-kl 1.0, min-updates 8 | KL **0.03-0.13, loss 0.4** for ~30 iters, then collapse (plies 144->78, ent 1.60->0.55) |
2772
+ | v11 | 48 | + kl-hard 3.0, min-updates 4 | **nup=0 on most iterations** — silent no-ops, the known failure mode |
2773
+ | v12 | 48 | kl-hard 3.0, min-updates 8 | no no-ops, but the 8 forced updates keep KL at 18-25; entropy 1.14 -> 0.94 |
2774
+
2775
+ ### Strength: flat throughout
2776
+ In-loop eval vs iter269 (n=60) across v7, v9, v10, v11, v12:
2777
+ +29, +41, +17, -41, +12, -0, -12, +17, -23, +6, -35, -6, +12, -12, +17, -0 Elo.
2778
+ **Every value is inside n=60 noise and the sequence has no trend.**
2779
+
2780
+ Independent arena, `main_v7` @292 vs iter269, n=480 pooled over three conditions:
2781
+ **0.529, Wilson CI [0.484, 0.573]** — contains 0.5.
2782
+
2783
+ **Conclusion (MEASURED, negative): the three encoding/optimisation bugs were real
2784
+ and are fixed, but fixing them did not make the model stronger.** `main_v10`
2785
+ demonstrates that the loop *can* be held in a well-conditioned regime
2786
+ (KL ~0.05, loss ~0.4) — and in that regime the model simply does not improve; it
2787
+ holds until the entropy controller lets it collapse again. This reproduces, with
2788
+ clean optimisation, the standing result that ~100K params plateau at this level.
2789
+
2790
+ `runs/mainval269/latest.pt` (iter 269) REMAINS THE CANONICAL BEST MODEL.
2791
+ No checkpoint from v6-v12 beats it. `main_baseline/latest.pt` was not overwritten.