"""Per-prompt gate: probe on the k=0 workspace state predicts 'will looping help?' 1. Features: L30 residual at the last prompt position (plain forward), MBPP train items; labels easy(0)/hard(1) from the STaR pass (free supervision). 2. Logistic probe (d=1536 -> 1), class-balanced. 3. Gated eval on MBPP test: predicted-easy -> k=0, predicted-hard -> k=4 with the dedicated loop adapter. Reports gated pass@1 vs uniform-k references. """ import json import sys import time from concurrent.futures import ThreadPoolExecutor from pathlib import Path import torch from loop_common import BandLooper, MergeAdapter from prep_mbpp import DIRECT_SUFFIX, extract_code, mbpp_prompt, run_tests sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from jlens.core import ResidualCapture, load_model # noqa: E402 OUT = Path(__file__).resolve().parent.parent / "results-loop" LAYER = 30 K_HARD = 4 @torch.no_grad() def collect_states(model, tok, items, batch=16): feats = [] for i in range(0, len(items), batch): chunk = items[i : i + batch] enc = tok([mbpp_prompt(tok, it, DIRECT_SUFFIX) for it in chunk], return_tensors="pt", padding=True, add_special_tokens=False).to("cuda") with ResidualCapture(model, layers=[LAYER]) as cap: model(**enc, use_cache=False, logits_to_keep=1) h = cap.acts[LAYER] # (B, T, d); left padding -> last pos is prompt end feats.append(h[:, -1].float().cpu()) return torch.cat(feats) def fit_probe(X, y, epochs=300, lr=0.05): mu, sd = X.mean(0), X.std(0) + 1e-6 Xn = (X - mu) / sd w = torch.zeros(X.shape[1], requires_grad=True) b = torch.zeros(1, requires_grad=True) opt = torch.optim.Adam([w, b], lr=lr) pos_w = (y == 0).sum() / max(1, (y == 1).sum()) for _ in range(epochs): z = Xn @ w + b loss = torch.nn.functional.binary_cross_entropy_with_logits( z, y.float(), pos_weight=pos_w) opt.zero_grad() loss.backward() opt.step() return w.detach(), b.detach(), mu, sd def main(): model, tok = load_model(dtype=torch.bfloat16) tok.padding_side = "left" looper = BandLooper(model) adapter = MergeAdapter().cuda() adapter.load_state_dict(torch.load(OUT / "adapter_code_e399.pt", map_location="cuda")) data = json.load(open(OUT / "mbpp_data.json")) train = [it for it in data if it["split"] == "train" and it["label"] in ("easy", "hard")] test = [it for it in data if it["split"] == "test"][:250] print(f"collecting states: {len(train)} train, {len(test)} test", flush=True) Xtr = collect_states(model, tok, train) ytr = torch.tensor([it["label"] == "hard" for it in train]).long() Xte = collect_states(model, tok, test) w, b, mu, sd = fit_probe(Xtr, ytr) ptr = torch.sigmoid(((Xtr - mu) / sd) @ w + b) acc_tr = ((ptr > 0.5).long() == ytr).float().mean() pte = torch.sigmoid(((Xte - mu) / sd) @ w + b) pred_hard = (pte > 0.5).tolist() yte = [it["label"] == "hard" for it in test] tp = sum(p and t for p, t in zip(pred_hard, yte)) print(f"probe: train_acc={acc_tr:.3f} test: pred_hard=" f"{sum(pred_hard)} (true hard={sum(yte)}, tp={tp})", flush=True) # gated generation: k=0 for predicted-easy, K_HARD for predicted-hard codes = [None] * len(test) for k, sel in ((0, [i for i, ph in enumerate(pred_hard) if not ph]), (K_HARD, [i for i, ph in enumerate(pred_hard) if ph])): for i in range(0, len(sel), 8): idxs = sel[i : i + 8] chunk = [test[j] for j in idxs] enc = tok([mbpp_prompt(tok, it, DIRECT_SUFFIX) for it in chunk], return_tensors="pt", padding=True, add_special_tokens=False).to("cuda") gen = looper.generate_frozen_prompt( adapter, tok, enc["input_ids"], k, max_new_tokens=220, attention_mask=enc["attention_mask"]) for jj, j in enumerate(idxs): codes[j] = extract_code( tok.decode(gen[jj, enc["input_ids"].shape[1]:], skip_special_tokens=True)) with ThreadPoolExecutor(8) as ex: oks = list(ex.map(lambda ci: run_tests(ci[0], ci[1]), zip(codes, test))) acc = sum(oks) / len(test) by = {} for it, ok in zip(test, oks): d = by.setdefault(it["label"], [0, 0]) d[0] += ok d[1] += 1 print(f"GATED pass@1={acc:.3f} " f"by_label={ {l: round(c/n,3) for l,(c,n) in by.items()} }", flush=True) json.dump({"acc": acc, "by_label": {l: c / n for l, (c, n) in by.items()}, "probe_train_acc": float(acc_tr), "pred_hard_test": int(sum(pred_hard))}, open(OUT / "eval_gated.json", "w"), indent=1) print("wrote", OUT / "eval_gated.json") if __name__ == "__main__": main()