Add brains/sovereign-v3/train_notes.md
Browse files
brains/sovereign-v3/train_notes.md
CHANGED
|
@@ -1,29 +1,27 @@
|
|
| 1 |
-
# SOVEREIGN v3 — Training Doctrine (
|
| 2 |
|
| 3 |
Exact recipe for **2× T4 (16 GB)**, shared shard format
|
| 4 |
-
`rl/shards_v2/*.npz`. Memory ≈9.
|
| 5 |
-
|
| 6 |
-
`
|
| 7 |
|
| 8 |
---
|
| 9 |
|
| 10 |
-
## Stage A — Return-conditioned filtered BC +
|
| 11 |
|
| 12 |
### A.1 Sample preparation (`ShardPrepper`)
|
| 13 |
|
| 14 |
-
1. **Returns / RTG**: per-episode `R_t = r_t + γR_{t+1}`, γ=0.995;
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
frame repeats).
|
| 18 |
2. **Weights** `w = clip(exp(R̄/τ), 0.2, 8)` τ=1; ×4 survivor episodes;
|
| 19 |
-
×2 ±2-frame kill windows; mean-normalized
|
| 20 |
-
3. **Free-label targets**: `lab64`, `lab64_next`
|
| 21 |
-
|
| 22 |
-
`
|
| 23 |
4. **Arena filtering**: drop >85%-water episodes.
|
| 24 |
-
5. Frame stacks / sequence windows: opt-in (`stack=`, `build_sequences`).
|
| 25 |
|
| 26 |
-
### A.2
|
| 27 |
|
| 28 |
```
|
| 29 |
L = w · [ λk CE(kind; ls=0.05)
|
|
@@ -33,81 +31,89 @@ L = w · [ λk CE(kind; ls=0.05)
|
|
| 33 |
+ λw CE(win_logits, win_lab; ls=0.05)
|
| 34 |
+ λg BCE(gate, gate_mask)·1[valid]
|
| 35 |
+ λs CE(seg64, lab64)
|
| 36 |
-
+ λd CE(next_seg, lab64_next)
|
| 37 |
-
+ λt BCE(threat
|
| 38 |
+ λe smoothL1(econ, cat(nums_next, rtg_next))
|
| 39 |
− λent (H(kind)+H(cell)+H(pct)) ]
|
| 40 |
λ = (0.5, 1.0, 0.25, 0.1, 0.1, 0.5, 0.5, 0.4, 0.2, 0.2, 0.05, 0.003)
|
| 41 |
```
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
farmer buffer. Both are supported; ablation #6 decides.
|
| 46 |
|
| 47 |
-
### A.3
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
---
|
| 55 |
|
| 56 |
-
## Stage B — RPO
|
| 57 |
|
| 58 |
### B.1 Rollouts
|
| 59 |
|
| 60 |
- `model.act(..., rtg=rtg_actual)` per tick; store `logprob`, `value`,
|
| 61 |
-
`win_prob`. **8 envs/GPU × 256 ticks** on CPU workers
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
|
| 76 |
-
### B.
|
| 77 |
|
| 78 |
-
|
| 79 |
-
rtg-conditioned — that is the whole point; the critic never has to
|
| 80 |
-
guess the aspiration).
|
| 81 |
-
- GAE(γ=0.995, λ=0.95) over `A_t`; z-normalize; clip ε=0.2 on the full
|
| 82 |
-
action logprob.
|
| 83 |
-
- Value loss: 0.5×clipped Huber (double clip ±0.2 in return space) +
|
| 84 |
-
**win head CE on episode outcomes** + optional cell-value auxiliary
|
| 85 |
-
(Huber vs per-cell utility bootstrap — PPO-friendly spatial critic).
|
| 86 |
-
- 4 epochs/round, minibatch 64/GPU, AdamW lr 3e-4 + cosine tail, grad
|
| 87 |
-
clip 5.0, **AMP fp16 + fp32 master**.
|
| 88 |
-
- **KL-to-pretrain guard β=0.1** (frozen Stage-A prior); roll-back if mean
|
| 89 |
-
KL > 0.15 for 3 rounds (β→0.02). ICLR 2025 evidence: pretrain-init +
|
| 90 |
-
guard, no offline replay retention — do NOT mix shards into the PPO
|
| 91 |
-
batches.
|
| 92 |
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
stay inside the trained range.
|
| 98 |
|
| 99 |
-
### B.4
|
| 100 |
|
| 101 |
-
`
|
| 102 |
-
|
|
|
|
|
|
|
| 103 |
|
| 104 |
-
### B.5
|
| 105 |
|
| 106 |
-
|
| 107 |
-
current policy against the pool; promote if it wins >55%. Once promoted,
|
| 108 |
-
the sim runs our policy vs 7 bots + 2 frozen past selves — the only
|
| 109 |
-
"league" a 2×T4 budget can afford, and it directly targets the weakness
|
| 110 |
-
of the last-survivor objective (myopic bot-exploit policies).
|
| 111 |
|
| 112 |
---
|
| 113 |
|
|
@@ -115,11 +121,12 @@ of the last-survivor objective (myopic bot-exploit policies).
|
|
| 115 |
|
| 116 |
- 100+ episodes vs **9 mixed bots**, fixed seeds; metric **win-rate +
|
| 117 |
mean rank**; save best on rank; report binomial CI.
|
| 118 |
-
- **Wake-sleep
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
|
|
|
| 123 |
|
| 124 |
## Curriculum
|
| 125 |
|
|
@@ -131,31 +138,33 @@ Epoch 1 of Stage A on early-game frames (tick_frac < 0.35) → full data →
|
|
| 131 |
| # | Question | Change | Resolves |
|
| 132 |
|---|---|---|---|
|
| 133 |
| 1 | Aspiration rtg at play time | {0.5, 1.0, 1.5, 2.0} | the RPO knob |
|
| 134 |
-
| 2 | Return conditioning itself | rtg≡0
|
| 135 |
| 3 | Intent mirrors | λt=λx=0 vs default | opponent modeling |
|
| 136 |
| 4 | Dynamics head | λd=0 vs default | world-model value |
|
| 137 |
-
| 5 |
|
| 138 |
-
| 6 |
|
| 139 |
-
| 7 |
|
| 140 |
| 8 | League-lite | pool vs no pool | self-play payoff |
|
| 141 |
| 9 | Wake-sleep | 0 vs 3 distill cycles | scaling loop |
|
|
|
|
| 142 |
|
| 143 |
## Expected compute (2× T4, fp16)
|
| 144 |
|
| 145 |
-
- Stage A: ~
|
| 146 |
-
- Stage B: ~2–3 updates/min
|
| 147 |
-
~
|
| 148 |
-
|
| 149 |
-
|
| 150 |
|
| 151 |
## Risk register
|
| 152 |
|
| 153 |
| Risk | Mitigation |
|
| 154 |
|---|---|
|
| 155 |
-
| fp16 NaN in attention | fp32 master, grad clip 5, warmup
|
| 156 |
-
| Unlearning the BC prior | KL guard + roll-back (B.2)
|
| 157 |
-
|
|
| 158 |
-
|
|
| 159 |
-
|
|
| 160 |
-
|
|
| 161 |
-
|
|
|
|
|
|
|
| 1 |
+
# SOVEREIGN v3 — Training Doctrine (≈290M dense + MCTS planning)
|
| 2 |
|
| 3 |
Exact recipe for **2× T4 (16 GB)**, shared shard format
|
| 4 |
+
`rl/shards_v2/*.npz`. Memory ≈9.5 GB/GPU at bs 16 + fp16 (ARCHITECTURE.md
|
| 5 |
+
§3). Everything below is implemented: `model.py`, `data_adapters.py`,
|
| 6 |
+
`search.py`, `league.py`, `distill.py`.
|
| 7 |
|
| 8 |
---
|
| 9 |
|
| 10 |
+
## Stage A — Return-conditioned filtered BC + world model (offline, ~2–3 epochs)
|
| 11 |
|
| 12 |
### A.1 Sample preparation (`ShardPrepper`)
|
| 13 |
|
| 14 |
+
1. **Returns / RTG**: per-episode `R_t = r_t + γR_{t+1}`, γ=0.995; +2
|
| 15 |
+
survive / +5 win terminal bonuses; z-scored per shard. `rtg` conditions
|
| 16 |
+
every head; `rtg_next` shifts within episodes.
|
|
|
|
| 17 |
2. **Weights** `w = clip(exp(R̄/τ), 0.2, 8)` τ=1; ×4 survivor episodes;
|
| 18 |
+
×2 ±2-frame kill windows; mean-normalized (AWR soft filtering).
|
| 19 |
+
3. **Free-label targets**: `lab64`, `lab64_next`, `threat`/`expand`
|
| 20 |
+
intent mirrors, `gate_mask`/`gate_valid`, `win_lab`, `nums_next`,
|
| 21 |
+
`rgb_next` (consecutive frames), `reward`.
|
| 22 |
4. **Arena filtering**: drop >85%-water episodes.
|
|
|
|
| 23 |
|
| 24 |
+
### A.2 Policy loss (exact)
|
| 25 |
|
| 26 |
```
|
| 27 |
L = w · [ λk CE(kind; ls=0.05)
|
|
|
|
| 31 |
+ λw CE(win_logits, win_lab; ls=0.05)
|
| 32 |
+ λg BCE(gate, gate_mask)·1[valid]
|
| 33 |
+ λs CE(seg64, lab64)
|
| 34 |
+
+ λd CE(next_seg, lab64_next)
|
| 35 |
+
+ λt BCE(threat) + λx BCE(expand)
|
| 36 |
+ λe smoothL1(econ, cat(nums_next, rtg_next))
|
| 37 |
− λent (H(kind)+H(cell)+H(pct)) ]
|
| 38 |
λ = (0.5, 1.0, 0.25, 0.1, 0.1, 0.5, 0.5, 0.4, 0.2, 0.2, 0.05, 0.003)
|
| 39 |
```
|
| 40 |
|
| 41 |
+
Value option: τ=0.9 expectile instead of Huber (IQL-style robustness on
|
| 42 |
+
the weak buffer).
|
|
|
|
| 43 |
|
| 44 |
+
### A.3 World-model loss (the new thing — trains the search block)
|
| 45 |
|
| 46 |
+
```
|
| 47 |
+
z_t = model.encode(rgb_t, nums_t, rtg_t) # stopgrad target
|
| 48 |
+
ẑ_{t+1} = model.dynamics(z_t, kind_t, cell_t, pct_t)
|
| 49 |
+
prior̂, v̂, win̂ = model.prediction(z_t)
|
| 50 |
+
z_{t+1} = model.encode(rgb_next_t, ...).detach()
|
| 51 |
+
|
| 52 |
+
L_wm = λz MSE(ẑ_{t+1}, z_{t+1}) # latent transition
|
| 53 |
+
+ λr Huber(r̂, reward_t) # reward model
|
| 54 |
+
+ λp KL(softmax(prior̂), softmax(cell_logits_t).detach()) # prior consistency
|
| 55 |
+
+ λv̂ Huber(v̂, R̄_t) + λŵ CE(win̂, win_lab_t)
|
| 56 |
+
λ_wm = (0.5, 0.1, 0.3, 0.1, 0.1)
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
This is MuZero-lite: representation + dynamics + prediction trained
|
| 60 |
+
entirely on labels the shards already contain (consecutive frames +
|
| 61 |
+
reward). Implement with `encode()/prediction()/dynamics()` + a small loop
|
| 62 |
+
over the loader (10 lines; both loss blocks share the same batch).
|
| 63 |
+
|
| 64 |
+
### A.4 Optimizer & schedule
|
| 65 |
+
|
| 66 |
+
- AdamW lr **3e-4 → 3e-5** cosine, **300-step warmup** (cortex), wd 1e-4,
|
| 67 |
+
grad clip 5.0, **AMP fp16**, bs **16/GPU × accum 2**. 2–3 epochs.
|
| 68 |
+
- Smoke tests first: `python model.py`, `python data_adapters.py`,
|
| 69 |
+
`python search.py`, `python league.py`, `python distill.py`.
|
| 70 |
|
| 71 |
---
|
| 72 |
|
| 73 |
+
## Stage B — RPO + AlphaZero-lite policy improvement (online, in sim)
|
| 74 |
|
| 75 |
### B.1 Rollouts
|
| 76 |
|
| 77 |
- `model.act(..., rtg=rtg_actual)` per tick; store `logprob`, `value`,
|
| 78 |
+
`win_prob`. **8 envs/GPU × 256 ticks** on CPU workers.
|
| 79 |
+
- **Search rollouts (the planning loop)**: every 4th action, call
|
| 80 |
+
`Searcher(model, sims=64, max_depth=12).search(rgb, nums, rtg, mask)`
|
| 81 |
+
and execute the search-chosen action. Store the **visit distribution**
|
| 82 |
+
`visit_probs` alongside the transition (single-sample search; batch over
|
| 83 |
+
envs by running search per env — 64 sims × 12 depth ≈ 20–40 ms on T4,
|
| 84 |
+
well inside the 560 ms tick).
|
| 85 |
+
|
| 86 |
+
### B.2 RPO loss (unchanged core)
|
| 87 |
+
|
| 88 |
+
`A_t = R_t − V(s_t, R_t)` (rtg-consistent critic), GAE(0.995, 0.95),
|
| 89 |
+
z-normalize, clip 0.2 on the action logprob, 0.5×clipped Huber value,
|
| 90 |
+
win-head CE on outcomes, entropy 0.01→0.002. 4 epochs/round, minibatch
|
| 91 |
+
64/GPU, AdamW 3e-4 + cosine tail, grad clip 5.0, **fp16 + fp32 master**.
|
| 92 |
|
| 93 |
+
### B.3 Search-target distillation (AlphaZero's policy improvement)
|
| 94 |
|
| 95 |
+
On rollout states where search ran, add:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
+
```
|
| 98 |
+
L_search = CE(kind_logits, kind_from_search)
|
| 99 |
+
+ KL(softmax(cell_logits[·,kind]), visit_probs_kind)
|
| 100 |
+
+ Huber(value, q_search)
|
| 101 |
+
λ_search = 0.5 (weighted by search-depth, first N=500k transitions)
|
| 102 |
+
```
|
| 103 |
|
| 104 |
+
This lifts the policy toward the planner's improved distribution —
|
| 105 |
+
exactly the loop AlphaZero proved, scaled to a 3×1024 action space.
|
|
|
|
| 106 |
|
| 107 |
+
### B.4 League-lite self-play
|
| 108 |
|
| 109 |
+
Use `league.py`: after each eval round, `update_elo` vs the pool; when the
|
| 110 |
+
current agent beats the pool (`maybe_promote`, threshold 0.55), freeze a
|
| 111 |
+
snapshot. Rollouts run vs **7 bots + 2 frozen past selves** picked by
|
| 112 |
+
`pick_opponents(2)` (alternating weakest/strongest — curriculum pressure).
|
| 113 |
|
| 114 |
+
### B.5 KL guard
|
| 115 |
|
| 116 |
+
β=0.1 KL-to-Stage-A-prior; roll-back if mean KL > 0.15 for 3 rounds.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
---
|
| 119 |
|
|
|
|
| 121 |
|
| 122 |
- 100+ episodes vs **9 mixed bots**, fixed seeds; metric **win-rate +
|
| 123 |
mean rank**; save best on rank; report binomial CI.
|
| 124 |
+
- **Wake-sleep** (`distill.py`): when PPO plateaus, distill the teacher
|
| 125 |
+
into the ~35M student (`small_config()`), including any stored
|
| 126 |
+
search targets. The student rolls out 2–3× faster → richer league data
|
| 127 |
+
→ teacher trains again. Cap 3 cycles.
|
| 128 |
+
- **Search eval**: measure win-rate with search ON vs OFF (the pure
|
| 129 |
+
planning ablation — it isolates how much "thinking" is worth).
|
| 130 |
|
| 131 |
## Curriculum
|
| 132 |
|
|
|
|
| 138 |
| # | Question | Change | Resolves |
|
| 139 |
|---|---|---|---|
|
| 140 |
| 1 | Aspiration rtg at play time | {0.5, 1.0, 1.5, 2.0} | the RPO knob |
|
| 141 |
+
| 2 | Return conditioning itself | rtg≡0 vs conditioned | Bet 1's value |
|
| 142 |
| 3 | Intent mirrors | λt=λx=0 vs default | opponent modeling |
|
| 143 |
| 4 | Dynamics head | λd=0 vs default | world-model value |
|
| 144 |
+
| 5 | **Search on/off** | Searcher vs policy-only at eval | how much MCTS is worth |
|
| 145 |
+
| 6 | **Search sims** | {16, 64, 128, 256} | compute vs strength |
|
| 146 |
+
| 7 | Value objective | Huber vs τ=0.9 expectile | robustness |
|
| 147 |
| 8 | League-lite | pool vs no pool | self-play payoff |
|
| 148 |
| 9 | Wake-sleep | 0 vs 3 distill cycles | scaling loop |
|
| 149 |
+
| 10 | Search-target distillation | λ_search ∈ {0, 0.5, 1.0} | policy-improvement op |
|
| 150 |
|
| 151 |
## Expected compute (2× T4, fp16)
|
| 152 |
|
| 153 |
+
- Stage A: ~10–16 h (≈290M incl. world-model losses).
|
| 154 |
+
- Stage B: ~2–3 updates/min; search on 8 envs every 4th tick adds
|
| 155 |
+
~0.3 s/env/step of T4 time. One full experiment ≈ 1–2 days.
|
| 156 |
+
- Sanctioned trims (in order): `head_hidden` 3072→2048,
|
| 157 |
+
`cortex_layers` 6→4, `enc_ch` top 832→640, `sims` 64→32.
|
| 158 |
|
| 159 |
## Risk register
|
| 160 |
|
| 161 |
| Risk | Mitigation |
|
| 162 |
|---|---|
|
| 163 |
+
| fp16 NaN in attention | fp32 master, grad clip 5, warmup |
|
| 164 |
+
| Unlearning the BC prior | KL guard + roll-back (B.2) |
|
| 165 |
+
| Search too slow in rollouts | sims 32, top_k 8, depth 8; distill student |
|
| 166 |
+
| World model collapses (z→ẑ≈z) | residual dynamics + λz; monitor MSE curve |
|
| 167 |
+
| Threat/expand label imbalance | BCE weights 8:1 positive:negative |
|
| 168 |
+
| Aspiration OOD (rtg>2σ) | clamp rtg to ±2.5 |
|
| 169 |
+
| League pool drifts stale | promotion threshold 0.55 + periodic re-eval |
|
| 170 |
+
| GRU rollout complexity | default off; env sketch in README |
|