summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/campaign/CASCADE_ABLATION_PLAN.md24
-rw-r--r--ep_run/casc_eq_train.py20
2 files changed, 40 insertions, 4 deletions
diff --git a/docs/campaign/CASCADE_ABLATION_PLAN.md b/docs/campaign/CASCADE_ABLATION_PLAN.md
index 55df056..2cc9f05 100644
--- a/docs/campaign/CASCADE_ABLATION_PLAN.md
+++ b/docs/campaign/CASCADE_ABLATION_PLAN.md
@@ -342,3 +342,27 @@ val); the D1a "no-death" correction; Delta cancellation scope.
(lr, sharpening-rate driver) and D (BP, EP-specificity).
8. Process fixes: watcher was not harness-tracked (user caught it — now all watchers via tracked bg
tasks); zsh $VAR word-splitting cost two launch retries (all launches now via bash scripts).
+
+### RESULT 6 (2026-07-10 09:35): WALL-2 DIAGNOSED — marginal under-convergence, EP-specific; kretry fix shipped; OLMo2 matrix launched.
+A/B/C/D verdict (resume from pre-bifurcation ckpt-10000, beta floored):
+| arm | skips @ window | note |
+|---|---|---|
+| A ctl (K3, lr1e-3) | **16, accelerating** (val wobble 2.00@12400) | leading indicator REPRODUCES |
+| B K8 | **2** | rejections nearly eliminated |
+| C lr3e-4 | **1**, best 1.5163 (best of all) | never touches the edge |
+| D BP (same ckpt/config/lr) | clean through 12750 | **EP-specific confirmed** |
+**Mechanism (two walls, two levers — revises "K refuted"):**
+- Wall-1 (~2-4k): cos erosion = finite-beta SNR -> beta-floor (K genuinely irrelevant there).
+- Wall-2 (~11k+): operator sharpens -> a growing fraction of batches sit at the CONTRACTIVITY EDGE of
+ the nudged fb relaxation and under-converge at K3 -> drift-guard rejections climb -> gradient bias +
+ occasional marginal escapes -> blowup. K8 CONVERGES those batches (16 -> 2 rejections) => marginal
+ under-convergence, NOT hard divergence. lr modulates when the edge arrives (C: skips~1 and better CE).
+ BP has no relaxation -> no wall-2 (D clean). Original 12100 didn't literally replay in A (fresh Adam
+ + different data order — the recorded confounds) but the leading indicator did.
+**FIX SHIPPED: `--kretry N`** — on drift-reject, RETRY the batch once with N fb rounds (B proved K8
+converges them) instead of dropping it. Converts biased skips into converged gradients; costs extra
+rounds ONLY on marginal batches (~0.1-1% of steps). Telemetry: skips=(d/g/r).
+**OLMo2 4k matrix LAUNCHED** (ol_bp_s1-3 + ol_ep_s1-3, wd 0.1, EP: beta_floor 3e-4 + kretry 8; twin
+step-0 losses bitwise-identical per seed). Watcher auto-computes parity and — if EP mean within 0.05
+of BP — AUTO-LAUNCHES the Stage-1 OLMo2 TinyStories epoch (stage1_ol_ep, 58.8k steps, kretry armed).
+OLMo2's bounded-per-branch signals may also shift wall-2 later; kretry is the belt-and-suspenders.
diff --git a/ep_run/casc_eq_train.py b/ep_run/casc_eq_train.py
index f3b8736..5a2677c 100644
--- a/ep_run/casc_eq_train.py
+++ b/ep_run/casc_eq_train.py
@@ -37,6 +37,7 @@ ap.add_argument('--sig0', type=float, default=-1.0) # override SIG0 (beta-s
ap.add_argument('--olmo2', action='store_true') # OLMo2-standard block: norm-AFTER-sublayer RMSNorm, full-width QK-norm, RoPE(500k), SwiGLU, no-bias, untied head, final RMSNorm, 0.02 init
ap.add_argument('--wd', type=float, default=-1.0) # >=0: grouped weight decay (linear weights+head decay; embeddings/norm-gains none). <0 = legacy uniform 1e-4
ap.add_argument('--zloss', type=float, default=0.0) # z-loss coefficient on train objective (OLMo2-style logit regularizer); 0 = off
+ap.add_argument('--kretry', type=int, default=0) # >0: on drift-reject, RETRY the batch once with this many fb rounds (diag B: K8 converges the marginal batches) instead of dropping it
ap.add_argument('--dtop_every', type=int, default=1) # 1 = exact (DEFAULT, BP-parity); 2 = fast mode (~20% cheaper, ~4% CE tax at high lr)
ap.add_argument('--gate_every', type=int, default=200) # in-training cos(EP,BP) telemetry; <=0 = fully BP-free (no bp_gate at all)
ap.add_argument('--gate_govern', action='store_true') # let gate cos adjust K/bscale (default: observe-only => training control is BP-free)
@@ -290,9 +291,20 @@ def ep_step(x, y):
drift = sum(float((a - b).norm()) for a, b in zip(zp, zs_free)) / max(
sum(float(b.norm()) for b in zs_free), 1e-9)
if (not math.isfinite(drift)) or (drift > 0.5 and not args.noguard):
- GOV['skd'] = GOV.get('skd', 0) + 1 # drift-guard reject (relaxation non-convergence)
- for p in all_params: p.grad = None
- return free_ce, beta_t, GOV['K'], False
+ ok_retry = False
+ if args.kretry > 0 and math.isfinite(drift) and not args.noguard:
+ GOV['skr'] = GOV.get('skr', 0) + 1 # marginal batch: retry once with deeper relaxation
+ z0, zs, ins, outs = free_states_graphed(x)
+ zs_free = [z.clone() for z in zs]
+ zp, last_outs = relax(z0, zs, ins, outs, y, +beta_t, args.kretry, x)
+ with torch.no_grad():
+ drift = sum(float((a - b).norm()) for a, b in zip(zp, zs_free)) / max(
+ sum(float(b.norm()) for b in zs_free), 1e-9)
+ ok_retry = math.isfinite(drift) and drift <= 0.5
+ if not ok_retry:
+ GOV['skd'] = GOV.get('skd', 0) + 1 # drift-guard reject (relaxation non-convergence)
+ for p in all_params: p.grad = None
+ return free_ce, beta_t, GOV['K'], False
GOV['drift'] = drift
E = 0.0
for z, o in zip(zp, last_outs): E = E + 0.5 * ((z.detach() - o) ** 2).sum()
@@ -368,7 +380,7 @@ for step in range(start_step, args.steps + 1):
val = evaluate(); best = min(best, val)
gtag = '' if math.isnan(gcos) else f' cos={gcos:.4f}'
print(f'step {step:5d}/{args.steps} | train {ce:.4f} val {val:.4f} (best {best:.4f}) '
- f'| beta={beta_t:.2e} K={rounds} skips={skips}(d{GOV.get("skd",0)}/g{GOV.get("skg",0)}){gtag} '
+ f'| beta={beta_t:.2e} K={rounds} skips={skips}(d{GOV.get("skd",0)}/g{GOV.get("skg",0)}/r{GOV.get("skr",0)}){gtag} '
f'drift={GOV["drift"]:.3f} gn={GOV["gn"]:.2e} sig={GOV["sig"]:.1f} | {step/max(time.time()-t0,1e-9):.3f} it/s', flush=True)
if wb is not None:
try: wb.log({'train_ce': ce, 'val_ce': val, 'best': best, 'beta_t': beta_t,