item 21: GSM carry-cot 57.4% (5x prior best); control isolates whiteboard to drop-bucket; E2-N/A2 planned
This commit is contained in:
@@ -51,6 +51,56 @@ protection); (c) easy-bucket damage < carry-always's (83→45 was the
|
|||||||
unprotected number). Hard-bucket *gain* over carry-always is hoped for,
|
unprotected number). Hard-bucket *gain* over carry-always is hoped for,
|
||||||
not predicted.
|
not predicted.
|
||||||
|
|
||||||
|
### E2-L — the internalization ladder (scratchpad → pure latent loop)
|
||||||
|
Goal: a loop that computes internally during generation with NO pauses
|
||||||
|
and NO visible scratchpad — reached by curriculum, never trained cold
|
||||||
|
(cold-trained answer-only carry already failed: 9.4% overall, old carry
|
||||||
|
arm k=2,p=0 cell — a 3-token signal can't teach the whiteboard what to
|
||||||
|
write). Rungs, each warm-started from the previous:
|
||||||
|
A loop + pauses + visible terse scratchpad, dense verified-CoT CE
|
||||||
|
(item 21, running 2026-07-16; control = same supervision, no
|
||||||
|
recurrence — the A-vs-B delta is the gate for everything below)
|
||||||
|
B delete scratchpad steps one at a time, each replaced by extra
|
||||||
|
pauses; brief retrain per rung — visible computation forced onto
|
||||||
|
the pause-chain
|
||||||
|
C pauses only, answer out (latent again, curriculum-reached)
|
||||||
|
C' no pauses either: state carries across answer tokens alone — the
|
||||||
|
pure internal loop
|
||||||
|
Deliverable: the rung where accuracy breaks = measured capacity of this
|
||||||
|
recurrence budget to absorb computation (the paper's number). Proceed
|
||||||
|
past A only if arm A beats its control by >= 3 points overall
|
||||||
|
(pre-registered, item 21b); A ~= B means the scratchpad text carries
|
||||||
|
everything and internalization would only rediscover the C' failure.
|
||||||
|
|
||||||
|
### E2-A2 — on-policy refresh (iterated self-distillation)
|
||||||
|
The exposure gap = training prefixes vs deployment prefixes. Cheapest
|
||||||
|
approximation ladder: (1) self-distilled scratchpads (stage A, done);
|
||||||
|
(2) THIS: re-harvest scratchpads with the CURRENT adapter active each
|
||||||
|
round, verify, retrain (STaR/ReST; DAgger at solution granularity;
|
||||||
|
~3 min/harvest). Signature of working: verified-yield and eval accuracy
|
||||||
|
co-improve across rounds. Gated on the A-vs-B verdict.
|
||||||
|
|
||||||
|
### E2-N — lens-shaped state noise (Nils's idea, 2026-07-16 ~04:15)
|
||||||
|
Harden the whiteboard against its own drift by injecting noise into the
|
||||||
|
carried state during teacher-forced training — SHAPED by the J-lens
|
||||||
|
instead of isotropic:
|
||||||
|
N1 sensitivity-weighted: sample noise in the span of J̄'s top-r right-
|
||||||
|
singular directions at the band entrance (the directions the final
|
||||||
|
readout depends on; isotropic noise wastes signal on the null
|
||||||
|
space). Cheap: jbar.pt exists; --lensnoise rank,scale flag.
|
||||||
|
N2 empirical-drift-matched: measure REAL exposure drift (free-run
|
||||||
|
state minus teacher-forced state at matched positions, few
|
||||||
|
rollouts), fit low-rank covariance, train under samples from it.
|
||||||
|
The lens diagnoses what the drift directions encode — worth running
|
||||||
|
as pure diagnosis regardless of verdicts (paper figure).
|
||||||
|
N3 concept-jitter: lens-read the carried concept (e.g. the
|
||||||
|
intermediate "24"), perturb toward a confusable concept in
|
||||||
|
embedding basis (swap machinery exists from the reproduction);
|
||||||
|
trains re-derivation over blind trust. Most ambitious.
|
||||||
|
Caveats, stated in advance: J̄ is prompt-averaged (N1 directions are
|
||||||
|
global, not per-position); noise norm-matched and magnitude-swept;
|
||||||
|
whole line gated on arm A beating its control.
|
||||||
|
|
||||||
### E3 — power knob (only if E1 or E2 shows clean gating)
|
### E3 — power knob (only if E1 or E2 shows clean gating)
|
||||||
Warm-start rung-2 band-LoRA under the gate; joint fine-tune. Question: do
|
Warm-start rung-2 band-LoRA under the gate; joint fine-tune. Question: do
|
||||||
gate-bought iterations do MORE per iteration with a trainable band?
|
gate-bought iterations do MORE per iteration with a trainable band?
|
||||||
|
|||||||
@@ -494,3 +494,28 @@ no fixed k solves but some k does.
|
|||||||
Honest note: arm outputs are VISIBLE tokens (~40) — this is the
|
Honest note: arm outputs are VISIBLE tokens (~40) — this is the
|
||||||
budget-CoT-with-loop hybrid, a scope change from latent planning,
|
budget-CoT-with-loop hybrid, a scope change from latent planning,
|
||||||
run at Nils's explicit direction ("do gsm8k and such").
|
run at Nils's explicit direction ("do gsm8k and such").
|
||||||
|
|
||||||
|
--- Outcome, item 21 (scored 2026-07-16 ~06:30). Harvest: 427 verified
|
||||||
|
terse CoTs (292 hard, 135 easy; 73.5% yield, ~3 min). Grid (n=256,
|
||||||
|
base 10.9/93.1/0.8/0.0 overall/easy/hard/drop):
|
||||||
|
arm A carry 2:2 53.9 (82.8 / 60.6 / 37.0)
|
||||||
|
arm A carry 2:6 57.4 (72.4 / 63.8 / 45.0)
|
||||||
|
control FF 2:2 49.6 (79.3 / 59.8 / 28.0)
|
||||||
|
control FF 2:6 54.7 (82.8 / 66.9 / 31.0)
|
||||||
|
(a) CONFIRMED, dramatically: best cell 57.4% vs the previous best GSM
|
||||||
|
arm's 12.1% — a 5x jump to full-CoT territory (~53%) at ~1/4 the
|
||||||
|
visible tokens. Dense verified supervision was indeed the binding
|
||||||
|
constraint; the supervision-density theory of the GSM failure is now
|
||||||
|
POSITIVELY confirmed, not just by absence. (b) MIXED: A-vs-B delta
|
||||||
|
+4.3 at p=2 (clears the >=3 bar), +2.7 at p=6 (misses); hard/easy
|
||||||
|
shuffle within noise between arms, BUT the whiteboard shows a
|
||||||
|
consistent, specific signature: DROP items (unreachable by the base
|
||||||
|
model even with full CoT at labeling) — A beats B by +9 and +14 points
|
||||||
|
there in the two cells. Interpretation: scratchpad supervision carries
|
||||||
|
the bulk; the carry chain specifically extends reach into previously
|
||||||
|
unreachable problems. McNemar p-values pending (mcnemar_carrycot.py
|
||||||
|
ready; classifier-blocked at scoring time). (c) CONFIRMED: easy damage
|
||||||
|
much reduced vs answer-only carry (83->72-83 vs 83->45). Ladder gate:
|
||||||
|
technically met at p=2; decision on stage B/A2/E2-N deferred to the
|
||||||
|
morning review with the p-values in hand — the drop-bucket signature,
|
||||||
|
if it survives pairing, is the strongest argument for continuing.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
"""Paired McNemar: carry-cot arm vs feedforward control, both grid cells."""
|
||||||
|
import json
|
||||||
|
from math import comb
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
OUT = Path(__file__).resolve().parent.parent / "results-loop"
|
||||||
|
A = json.load(open(OUT / "eval_gsm_carrycot_e400.json"))
|
||||||
|
B = json.load(open(OUT / "eval_gsm_carrycot_ff_e400.json"))
|
||||||
|
star = {it["idx"]: it["label"]
|
||||||
|
for it in json.load(open(OUT / "star_data.json"))
|
||||||
|
if it["split"] == "test"}
|
||||||
|
for cell in ("2:2", "2:6"):
|
||||||
|
a = {r["idx"]: r["ok"] for r in A["grid"][cell]["per_item"]}
|
||||||
|
b = {r["idx"]: r["ok"] for r in B["grid"][cell]["per_item"]}
|
||||||
|
ids = list(a)
|
||||||
|
x = sum(1 for i in ids if a[i] and not b[i])
|
||||||
|
y = sum(1 for i in ids if b[i] and not a[i])
|
||||||
|
n = x + y
|
||||||
|
p = (min(1, sum(comb(n, k) for k in range(max(x, y), n + 1))
|
||||||
|
/ 2 ** n * 2) if n else 1)
|
||||||
|
dx = sum(1 for i in ids
|
||||||
|
if star.get(i) == "drop" and a[i] and not b[i])
|
||||||
|
dy = sum(1 for i in ids
|
||||||
|
if star.get(i) == "drop" and b[i] and not a[i])
|
||||||
|
print(f"{cell}: carry-only={x} ff-only={y} McNemar p={p:.4f} "
|
||||||
|
f"drop-bucket discordants {dx}-{dy}")
|
||||||
@@ -38,11 +38,42 @@ P_BY_LABEL = {"easy": 2, "hard": 6}
|
|||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument("--feedforward", action="store_true")
|
ap.add_argument("--feedforward", action="store_true")
|
||||||
ap.add_argument("--seed", type=int, default=0)
|
ap.add_argument("--seed", type=int, default=0)
|
||||||
|
ap.add_argument("--lensnoise", default=None, metavar="RANK,SCALE",
|
||||||
|
help="E2-N1: inject noise into the carried state during "
|
||||||
|
"training, shaped by the top-RANK sensitivity "
|
||||||
|
"directions of jbar at the band entrance; noise norm "
|
||||||
|
"= SCALE * per-position state norm (e.g. 32,0.05)")
|
||||||
ARGS = ap.parse_args()
|
ARGS = ap.parse_args()
|
||||||
TAG = ("carrycot_ff" if ARGS.feedforward else "carrycot") + (
|
TAG = ("carrycot_ff" if ARGS.feedforward else "carrycot") + (
|
||||||
|
f"_ln{ARGS.lensnoise.replace(',', '_')}" if ARGS.lensnoise else "") + (
|
||||||
f"_s{ARGS.seed}" if ARGS.seed else "")
|
f"_s{ARGS.seed}" if ARGS.seed else "")
|
||||||
|
|
||||||
|
|
||||||
|
class LensNoiseWrapper(torch.nn.Module):
|
||||||
|
"""Perturb the carried state s (not the anchor e) before the merge,
|
||||||
|
within the span of the lens's top-r readout-sensitive directions.
|
||||||
|
Train-time only (noise_on flag); eval and checkpoints use .base."""
|
||||||
|
|
||||||
|
def __init__(self, base, jbar_layer, rank, scale):
|
||||||
|
super().__init__()
|
||||||
|
self.base = base
|
||||||
|
self.scale = scale
|
||||||
|
self.noise_on = True
|
||||||
|
J = jbar_layer.float()
|
||||||
|
_, _, Vt = torch.linalg.svd(J, full_matrices=False)
|
||||||
|
self.register_buffer("V", Vt[:rank].T.contiguous()) # (d, r)
|
||||||
|
|
||||||
|
def forward(self, e, s):
|
||||||
|
if self.noise_on and self.scale > 0:
|
||||||
|
z = torch.randn(*s.shape[:-1], self.V.shape[1],
|
||||||
|
device=s.device, dtype=torch.float32)
|
||||||
|
n = z @ self.V.T
|
||||||
|
n = n * (s.float().norm(dim=-1, keepdim=True) * self.scale
|
||||||
|
/ (n.norm(dim=-1, keepdim=True) + 1e-6))
|
||||||
|
s = (s.float() + n).to(s.dtype)
|
||||||
|
return self.base(e, s)
|
||||||
|
|
||||||
|
|
||||||
def lr_at(step):
|
def lr_at(step):
|
||||||
if step < WARMUP:
|
if step < WARMUP:
|
||||||
return LR * (step + 1) / WARMUP
|
return LR * (step + 1) / WARMUP
|
||||||
@@ -107,6 +138,15 @@ def main():
|
|||||||
looper = BandLooper(model)
|
looper = BandLooper(model)
|
||||||
adapter = MergeAdapter(
|
adapter = MergeAdapter(
|
||||||
d=model.config.get_text_config().hidden_size).cuda()
|
d=model.config.get_text_config().hidden_size).cuda()
|
||||||
|
if ARGS.lensnoise:
|
||||||
|
r, sc = ARGS.lensnoise.split(",")
|
||||||
|
jbar = torch.load(Path(__file__).resolve().parent.parent
|
||||||
|
/ "results/jbar.pt", map_location="cpu")["Jbar"]
|
||||||
|
from loop_common import BAND
|
||||||
|
adapter = LensNoiseWrapper(adapter, jbar[BAND[0]], int(r),
|
||||||
|
float(sc)).cuda()
|
||||||
|
print(f"lens-noise: rank={r} scale={sc} on jbar L{BAND[0]}",
|
||||||
|
flush=True)
|
||||||
opt = torch.optim.AdamW(adapter.parameters(), lr=LR, weight_decay=0.01)
|
opt = torch.optim.AdamW(adapter.parameters(), lr=LR, weight_decay=0.01)
|
||||||
|
|
||||||
keep = [it for it in data
|
keep = [it for it in data
|
||||||
@@ -146,11 +186,15 @@ def main():
|
|||||||
print(f"step {step:4d} {lbl:4s} loss={loss.item():.4f} "
|
print(f"step {step:4d} {lbl:4s} loss={loss.item():.4f} "
|
||||||
f"({(time.time()-t0)/(step+1):.1f}s/step)", flush=True)
|
f"({(time.time()-t0)/(step+1):.1f}s/step)", flush=True)
|
||||||
if step % 200 == 199 or step == STEPS - 1:
|
if step % 200 == 199 or step == STEPS - 1:
|
||||||
|
if ARGS.lensnoise:
|
||||||
|
adapter.noise_on = False
|
||||||
for l in ("easy", "hard"):
|
for l in ("easy", "hard"):
|
||||||
v = val_loss(looper, adapter, tok, val[l], P_BY_LABEL[l])
|
v = val_loss(looper, adapter, tok, val[l], P_BY_LABEL[l])
|
||||||
print(f" val@{step}: {l}={v:.3f}", flush=True)
|
print(f" val@{step}: {l}={v:.3f}", flush=True)
|
||||||
torch.save(adapter.state_dict(),
|
if ARGS.lensnoise:
|
||||||
OUT / f"adapter_{TAG}_e{step+1}.pt")
|
adapter.noise_on = True
|
||||||
|
sd = (adapter.base if ARGS.lensnoise else adapter).state_dict()
|
||||||
|
torch.save(sd, OUT / f"adapter_{TAG}_e{step+1}.pt")
|
||||||
json.dump(log, open(OUT / f"train_{TAG}_log.json", "w"))
|
json.dump(log, open(OUT / f"train_{TAG}_log.json", "w"))
|
||||||
print("done", flush=True)
|
print("done", flush=True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user