summaryrefslogtreecommitdiff
path: root/ep_run/casc_eq_train.py
diff options
context:
space:
mode:
Diffstat (limited to 'ep_run/casc_eq_train.py')
-rw-r--r--ep_run/casc_eq_train.py69
1 files changed, 52 insertions, 17 deletions
diff --git a/ep_run/casc_eq_train.py b/ep_run/casc_eq_train.py
index f360df1..dfb7098 100644
--- a/ep_run/casc_eq_train.py
+++ b/ep_run/casc_eq_train.py
@@ -62,6 +62,11 @@ ap.add_argument('--adam_b1', type=float, default=0.9) # AdamW beta1 (late-SN
ap.add_argument('--est', choices=['single', 'centered', 'richardson'], default='single')
# centered: [g(+b)+g(-b)]/2 (O(b^2) bias, 2x relax cost)
# richardson: 2g(b)-g(2b) (O(b^2) bias, large-b friendly)
+ap.add_argument('--est_late', choices=['', 'centered'], default='')
+ap.add_argument('--est_late_at', type=int, default=0) # switch --est -> --est_late at this step (process-local,
+ # bf_late_at semantics); centered is TAIL medicine
+ap.add_argument('--centfast', action='store_true') # centered via ONE doubled batch [x;x], +beta/-beta halves
+ # (shared kernels; math identical to sequential centered)
args = ap.parse_args()
if args.olmo2:
args.untie = True
@@ -300,11 +305,13 @@ def tok_sigma(iters=8):
v = W.t() @ u; sig = v.norm(); v /= max(sig, 1e-12)
return float(sig)
-def relax(z0, zs, ins, outs, y, beta, K, x):
+def relax(z0, zs, ins, outs, y, beta, K, x, bmask=None):
"""K fb rounds with GRAPH REUSE + two dedups: (a) the top CE force d_top is refreshed on
even rounds only (states move O(beta) per round -> O(beta^2) error); (b) the LAST rebuild
keeps graphs (layer-0 fed a graphed emb) and returns (ins, outs) so the theta-readout
- reuses them instead of re-running a full graphed chain."""
+ reuses them instead of re-running a full graphed chain.
+ bmask (rows,1,1): per-row multiplier on the top force (centfast +/-1 halves); the
+ rows*T-aware scale keeps per-row d identical to the sequential B-sized run."""
d = [None] * args.L
geta_l = args.geta
adaptive = args.relax_tol > 0
@@ -313,7 +320,10 @@ def relax(z0, zs, ins, outs, y, beta, K, x):
if refresh_top or d[args.L - 1] is None:
zc = zs[args.L - 1].detach().requires_grad_(True)
ce = obj_loss(readout(zc).reshape(-1, vocab), y.reshape(-1))
- d[args.L - 1] = (-beta * NBT * torch.autograd.grad(ce, zc)[0]).detach()
+ nbt_loc = zc.shape[0] * zc.shape[1]
+ g = torch.autograd.grad(ce, zc)[0]
+ if bmask is not None: g = g * bmask
+ d[args.L - 1] = (-beta * nbt_loc * g).detach()
for l in range(args.L - 2, -1, -1):
d[l] = torch.autograd.grad(outs[l + 1], ins[l + 1], grad_outputs=d[l + 1].to(outs[l + 1].dtype))[0].detach().float()
@@ -408,24 +418,38 @@ def ep_step(x, y):
beta_t = beta_t * GOV.get('cap', 1.0) # wall-2 loop-gain cap OVERRIDES the floor (the ceiling
# can sit below the floor near the wall; survival first)
if args.bsign_rand and torch.rand((), generator=BGEN).item() < 0.5: beta_t = -beta_t
- z0, zs, ins, outs = free_states_graphed(x)
+ EST = args.est
+ if args.est_late and GOV['step'] >= args.est_late_at: EST = args.est_late
+ CF = (EST == 'centered' and args.centfast)
+ if CF: # doubled batch [x;x]: +beta half / -beta half share every kernel (holofast pattern)
+ x_in, y_in = torch.cat([x, x], 0), torch.cat([y, y], 0)
+ bmask = torch.ones(x_in.shape[0], 1, 1, device=dev); bmask[args.B:] = -1.0
+ halves = (slice(0, args.B), slice(args.B, None))
+ else:
+ x_in, y_in, bmask, halves = x, y, None, (slice(None),)
+ z0, zs, ins, outs = free_states_graphed(x_in)
zs_free = [z.clone() for z in zs]
- free_ce = F.cross_entropy(readout(zs_free[-1]).reshape(-1, vocab), y.reshape(-1)).item()
- zp, last_outs = relax(z0, zs, ins, outs, y, +beta_t, GOV['K'], x)
+ free_ce = F.cross_entropy(readout(zs_free[-1][:args.B]).reshape(-1, vocab), y.reshape(-1)).item()
+ zp, last_outs = relax(z0, zs, ins, outs, y_in, +beta_t, GOV['K'], x_in, bmask=bmask)
+ def _drift(zp_, zf_):
+ dr = 0.0
+ for h in halves: # per-half worst drift == sequential guard decisions (max over passes)
+ num = sum(float((a[h] - b[h]).norm()) for a, b in zip(zp_, zf_))
+ den = sum(float(b[h].norm()) for b in zf_)
+ dr = max(dr, num / max(den, 1e-9))
+ return dr
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)
+ drift = _drift(zp, zs_free)
gdrift = ddp_max_scalar(drift) # guard DECISIONS on the global worst -> identical on every rank
if (not math.isfinite(gdrift)) or (gdrift > 0.5 and not args.noguard):
ok_retry = False
if args.kretry > 0 and math.isfinite(gdrift) 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)
+ z0, zs, ins, outs = free_states_graphed(x_in)
zs_free = [z.clone() for z in zs]
- zp, last_outs = relax(z0, zs, ins, outs, y, +beta_t, args.kretry, x)
+ zp, last_outs = relax(z0, zs, ins, outs, y_in, +beta_t, args.kretry, x_in, bmask=bmask)
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)
+ drift = _drift(zp, zs_free)
gdrift = ddp_max_scalar(drift)
ok_retry = math.isfinite(gdrift) and gdrift <= 0.5
if not ok_retry:
@@ -442,19 +466,30 @@ def ep_step(x, y):
GOV['cap'] = max(GOV.get('cap', 1.0) * 0.85, 0.05) # attack (gentler than v1)
elif res_g < 0.01 or rho_g < 0.5 * args.beta_cap_rho:
GOV['cap'] = min(GOV.get('cap', 1.0) * 1.02, 1.0) # recover
- E = 0.0
- for z, o in zip(zp, last_outs): E = E + 0.5 * ((z.detach().float() - o.float()) ** 2).sum() # fp32 accumulation (bf16-safe; no-op in fp32)
- if args.est == 'single':
+ if CF:
+ # one-graph centered: [g(+b)+g(-b)]/2 = d[(E+ - E-)/(2b·NBT)]/dtheta; CE-head term from
+ # the +beta half only (matches sequential centered's gsC at the +beta top states).
+ Ec = 0.0
+ for z, o in zip(zp, last_outs):
+ df = z.detach().float() - o.float()
+ Ec = Ec + 0.5 * (df[:args.B] ** 2).sum() - 0.5 * (df[args.B:] ** 2).sum()
+ obj = Ec / (NBT * 2.0 * beta_t) + obj_loss(readout(zp[-1][:args.B].detach()).reshape(-1, vocab), y.reshape(-1))
+ gs = torch.autograd.grad(obj, all_params, allow_unused=True)
+ elif EST == 'single':
+ E = 0.0
+ for z, o in zip(zp, last_outs): E = E + 0.5 * ((z.detach().float() - o.float()) ** 2).sum() # fp32 accumulation (bf16-safe; no-op in fp32)
obj = E / (NBT * beta_t) + obj_loss(readout(zp[-1].detach()).reshape(-1, vocab), y.reshape(-1))
gs = torch.autograd.grad(obj, all_params, allow_unused=True)
else:
# two-pass estimators: g(b) := d[E(b)]/dtheta / (NBT*b) => single-sided bias g_true + c*b.
# centered: [g(+b) + g(-b)] / 2 (1/b sign inside => average cancels c*b)
# richardson: 2*g(b) - g(2b) (extrapolation cancels c*b at large b)
+ E = 0.0
+ for z, o in zip(zp, last_outs): E = E + 0.5 * ((z.detach().float() - o.float()) ** 2).sum()
gsE = torch.autograd.grad(E / (NBT * beta_t), all_params, allow_unused=True)
gsC = torch.autograd.grad(obj_loss(readout(zp[-1].detach()).reshape(-1, vocab), y.reshape(-1)),
all_params, allow_unused=True)
- b2 = -beta_t if args.est == 'centered' else 2.0 * beta_t
+ b2 = -beta_t if EST == 'centered' else 2.0 * beta_t
z0b, zsb, insb, outsb = free_states_graphed(x)
zsb_free = [z.clone() for z in zsb]
zpb, lob = relax(z0b, zsb, insb, outsb, y, b2, GOV['K'], x)
@@ -473,7 +508,7 @@ def ep_step(x, y):
if a is None and b is None: return None
a = a if a is not None else torch.zeros_like(b)
b = b if b is not None else torch.zeros_like(a)
- return (a + b) / 2.0 if args.est == 'centered' else (2.0 * a - b)
+ return (a + b) / 2.0 if EST == 'centered' else (2.0 * a - b)
gs = [(_comb(e, e2) if (e is not None or e2 is not None) else None) for e, e2 in zip(gsE, gsE2)]
gs = [ (g if g is not None else c) if c is None or g is None else g + c for g, c in zip(gs, gsC) ]
gs = ddp_avg(gs, all_params) # global-batch gradient; gn/gema/guard below see identical values on all ranks