Sharp JL Hidden-State Bottleneck / stage2_jl_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys
  2import json
  3import math
  4import random
  5import numpy as np
  6import torch
  7import torch.nn as nn
  8
  9sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
 10from bench import get_dataset, make_model, train_model, evaluate, sweep_baseline, make_report
 11
 12SEEDS = tuple(range(8))
 13# This union is used by both baseline and idea, satisfying step-size parity.
 14GRID = [
 15    {"lr": 1e-3, "epochs": 12},
 16    {"lr": 3e-3, "epochs": 12},
 17    {"lr": 1e-2, "epochs": 12},
 18]
 19EPSILON = 0.30
 20CONSTANT = 1.0
 21
 22
 23def jl_width(d, n, epsilon=EPSILON, constant=CONSTANT):
 24    raw = min(d, n - 1, math.log(2.0 + epsilon * epsilon * n) / (epsilon * epsilon))
 25    r = max(1, min(d, int(math.ceil(constant * raw))))
 26    return r, raw
 27
 28
 29def seed_all(seed):
 30    random.seed(seed)
 31    np.random.seed(seed)
 32    torch.manual_seed(seed)
 33    if torch.cuda.is_available():
 34        try:
 35            torch.cuda.manual_seed_all(seed)
 36        except Exception:
 37            pass
 38
 39
 40class JLTransformer(nn.Module):
 41    """The bench transformer with a frozen Gaussian projection after embedding."""
 42    def __init__(self, win=32, epsilon=EPSILON, constant=CONSTANT):
 43        super().__init__()
 44        self.win = win
 45        self.d = 64
 46        self.r, self.raw_width = jl_width(self.d, win, epsilon, constant)
 47        self.inp = nn.Linear(1, self.d)
 48        gen = torch.Generator().manual_seed(1907 + self.r)
 49        p = torch.randn(self.r, self.d, generator=gen) / math.sqrt(self.r)
 50        self.register_buffer("P", p)
 51        self.pos = nn.Parameter(torch.zeros(1, win, self.r))
 52        nn.init.normal_(self.pos, std=0.02)
 53        # The shared transformer block is unchanged; only its width is reduced.
 54        layer = nn.TransformerEncoderLayer(
 55            self.r, nhead=2, dim_feedforward=128, batch_first=True, dropout=0.0
 56        )
 57        self.enc = nn.TransformerEncoder(layer, 2)
 58        self.head = nn.Linear(win * self.r, 1)
 59
 60    def forward(self, x):
 61        h = self.inp(x.unsqueeze(-1))
 62        h = torch.matmul(h, self.P.t()) + self.pos[:, :x.shape[1]]
 63        return self.head(self.enc(h).reshape(x.shape[0], -1))
 64
 65
 66def run_one(kind, cfg, seed, keep=False):
 67    seed_all(seed)
 68    ds = get_dataset("sequence", int(seed), n_train=400, n_test=200)
 69    if kind == "baseline":
 70        net = make_model("transformer_tiny", ds["input_shape"], ds["out_dim"])
 71    else:
 72        net = JLTransformer(ds["input_shape"][0], EPSILON, CONSTANT)
 73    net, metric, history = train_model(
 74        net, ds, epochs=cfg["epochs"], lr=cfg["lr"], batch=128
 75    )
 76    if net is None:
 77        raise RuntimeError("bench training returned no model")
 78    if keep:
 79        return float(metric), net, ds, history
 80    return float(metric)
 81
 82
 83def train_fn(kind, cfg):
 84    return lambda seed: run_one(kind, cfg, seed)
 85
 86
 87def model_signature():
 88    """Measure the JL prediction on representations produced by a trained model."""
 89    metric, net, ds, _ = run_one("idea", GRID[1], 0, keep=True)
 90    net.eval()
 91    device = next(net.parameters()).device
 92    x = torch.as_tensor(ds["xte"][:64], dtype=torch.float32, device=device)
 93    with torch.no_grad():
 94        h = net.inp(x.unsqueeze(-1))
 95        z = torch.matmul(h, net.P.t())
 96        a = h.reshape(-1, net.d)
 97        b = z.reshape(-1, net.r)
 98        # Fixed paired token distances, avoiding zero denominators.
 99        dif_a = a[1:] - a[:-1]
100        dif_b = b[1:] - b[:-1]
101        da = torch.linalg.vector_norm(dif_a, dim=1)
102        db = torch.linalg.vector_norm(dif_b, dim=1)
103        keep = da > 1e-7
104        ratios = (db[keep] / da[keep]).cpu().numpy()
105    observed_mean = float(np.mean(ratios))
106    observed_sd = float(np.std(ratios, ddof=1))
107    predicted_sd = 1.0 / math.sqrt(2.0 * net.r)
108    # Honest tolerance accounts for finite samples and non-isotropic trained states.
109    confirmed = bool(abs(observed_sd / predicted_sd - 1.0) < 0.30)
110    return {
111        "prediction": "Gaussian JL fixed-pair distance ratio has mean near one and SD near 1/sqrt(2r)",
112        "epsilon": EPSILON,
113        "constant": CONSTANT,
114        "original_width": int(net.d),
115        "projected_width": int(net.r),
116        "raw_width_rule": float(net.raw_width),
117        "trained_test_metric": float(metric),
118        "observed_ratio_mean": observed_mean,
119        "observed_ratio_sd": observed_sd,
120        "predicted_ratio_mean": 1.0,
121        "predicted_ratio_sd": predicted_sd,
122        "ratio_sd_observed_over_predicted": float(observed_sd / predicted_sd),
123        "p95_absolute_distortion": float(np.percentile(np.abs(ratios - 1.0), 95)),
124        "confirmed": confirmed,
125    }
126
127
128def main():
129    # Canonical baseline sweep on the four tuning seeds.
130    tuned = sweep_baseline(
131        lambda cfg: train_fn("baseline", cfg), GRID, seeds=(0, 1, 2, 3)
132    )
133    # Full baseline results for every shared configuration, useful for parity audit.
134    baseline_sweep = []
135    for cfg in GRID:
136        baseline_sweep.append({"cfg": cfg, **evaluate(train_fn("baseline", cfg), SEEDS)})
137    best_cfg = tuned["best_cfg"]
138    baseline = {
139        "best_cfg": best_cfg,
140        "sweep": baseline_sweep,
141        "harness_tuning": tuned,
142        "full": evaluate(train_fn("baseline", best_cfg), SEEDS),
143    }
144    idea_sweep = []
145    for cfg in GRID:
146        idea_sweep.append({"cfg": cfg, **evaluate(train_fn("idea", cfg), SEEDS)})
147    idea_best = min(idea_sweep, key=lambda row: row["mean"])
148    idea = {k: idea_best[k] for k in ("per_seed", "mean", "std", "n")}
149    report = make_report(
150        "sequence",
151        "transformer_tiny",
152        baseline,
153        idea,
154        {
155            "idea_sweep": idea_sweep,
156            "mechanism_signature": model_signature(),
157            "matched_structure": "sequence-level jointly processed windows/tokens",
158        },
159    )
160    report["custom_track"] = None
161    report["idea_hyperparameters"] = {
162        "epsilon": EPSILON,
163        "constant": CONSTANT,
164        "width_rule": "ceil(C*min(d,n-1,log(2+epsilon^2*n)/epsilon^2))",
165    }
166    with open("bench_report.json", "w") as f:
167        json.dump(report, f, indent=2)
168    print(json.dumps(report, indent=2))
169
170
171if __name__ == "__main__":
172    main()