Moment-preserving HT compression / bench_moment_preserving.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import json
  2import os
  3import sys
  4from pathlib import Path
  5
  6import numpy as np
  7import torch
  8import torch.nn as nn
  9
 10# Import the read-only shared benchmark package.
 11sys.path.insert(0, "/home/maxwelhelp/all/math2nn")
 12from bench import get_dataset, train_model, evaluate, sweep_baseline, make_report
 13
 14SEEDS = [0, 1, 2, 3, 4, 5, 6, 7]
 15# The union of baseline and idea learning rates is identical.
 16GRID = [{"lr": 1e-3}, {"lr": 3e-3}, {"lr": 6e-3}]
 17EPOCHS = 8
 18BATCH = 128
 19NTRAIN = 400
 20NTEST = 400
 21
 22
 23def make_phi(device):
 24    z = torch.linspace(-1.0, 1.0, 8, device=device)
 25    z1, z2 = torch.meshgrid(z, z, indexing="ij")
 26    return torch.stack((torch.ones_like(z1), z1, z2, 0.5 * (z1 * z1 + z2 * z2)))
 27
 28
 29def conserve_hidden(h, rank=2):
 30    """Rank compress each 8x8 hidden state and restore four weighted moments."""
 31    b = h.shape[0]
 32    x = h.reshape(b, 8, 8)
 33    # Batched truncated SVD is the low-rank compression intervention.
 34    u, s, vh = torch.linalg.svd(x, full_matrices=False)
 35    xt = (u[:, :, :rank] * s[:, None, :rank]) @ vh[:, :rank, :]
 36    phi = make_phi(h.device)
 37    flat_phi = phi.reshape(4, -1)
 38    flat_x = x.reshape(b, -1)
 39    flat_t = xt.reshape(b, -1)
 40    gram = flat_phi @ flat_phi.T
 41    delta = (flat_phi @ (flat_x - flat_t).T).T
 42    coeff = torch.linalg.solve(gram + 1e-8 * torch.eye(4, device=h.device), delta.T).T
 43    return (flat_t + coeff @ flat_phi).reshape(b, 64)
 44
 45
 46class IdeaRNN(nn.Module):
 47    """Same rnn_small architecture, with conservative hidden compression."""
 48    def __init__(self, hidden=64, rank=2):
 49        super().__init__()
 50        self.rnn = nn.GRU(3, hidden, batch_first=True)
 51        self.head = nn.Linear(hidden, 1)
 52        self.rank = rank
 53        self.last_signature = {}
 54
 55    def forward(self, x):
 56        seq = x.view(x.shape[0], -1, 3)
 57        try:
 58            _, h = self.rnn(seq)
 59        except RuntimeError:
 60            old = torch.backends.cudnn.enabled
 61            torch.backends.cudnn.enabled = False
 62            try:
 63                _, h = self.rnn(seq)
 64            finally:
 65                torch.backends.cudnn.enabled = old
 66        raw = h[-1]
 67        out = conserve_hidden(raw, self.rank)
 68        if not self.training:
 69            with torch.no_grad():
 70                phi = make_phi(raw.device).reshape(4, -1)
 71                self.last_signature = {
 72                    "raw_norm": float(raw.norm().cpu()),
 73                    "compressed_relative_error": float((out - raw).norm().cpu() / (raw.norm().cpu() + 1e-12)),
 74                    "moment_residual": float(((out - raw) @ phi.T).abs().max().cpu()),
 75                }
 76        return self.head(out)
 77
 78
 79def make_baseline(cfg):
 80    def fn(seed):
 81        torch.manual_seed(seed)
 82        np.random.seed(seed)
 83        from bench import make_model
 84        ds = get_dataset("dynamics", seed, NTRAIN, NTEST)
 85        model = make_model("rnn_small", ds["input_shape"], ds["out_dim"])
 86        _, metric, _ = train_model(model, ds, epochs=EPOCHS, lr=cfg["lr"], batch=BATCH, log=lambda *_: None)
 87        return metric
 88    return fn
 89
 90
 91def make_idea(cfg):
 92    def fn(seed):
 93        torch.manual_seed(seed)
 94        np.random.seed(seed)
 95        ds = get_dataset("dynamics", seed, NTRAIN, NTEST)
 96        model = IdeaRNN(rank=2)
 97        _, metric, _ = train_model(model, ds, epochs=EPOCHS, lr=cfg["lr"], batch=BATCH, log=lambda *_: None)
 98        return metric
 99    return fn
100
101
102def inspect_signature(seed, cfg):
103    """Measure the proposed preservation on a trained benchmark model."""
104    torch.manual_seed(seed)
105    ds = get_dataset("dynamics", seed, NTRAIN, NTEST)
106    model = IdeaRNN(rank=2)
107    trained, _, _ = train_model(model, ds, epochs=EPOCHS, lr=cfg["lr"], batch=BATCH, log=lambda *_: None)
108    trained.eval()
109    device = next(trained.parameters()).device
110    with torch.no_grad():
111        _ = trained(ds["xte"].to(device))
112    sig = dict(trained.last_signature)
113    sig.update({"target": "hidden moments", "predicted_residual": 0.0, "observed_residual": sig.get("moment_residual", float("nan"))})
114    sig["confirmed"] = bool(sig.get("observed_residual", 1.0) < 1e-5)
115    return sig
116
117
118def main():
119    # Cheap numerical verification before any benchmark training.
120    torch.manual_seed(329)
121    h = torch.randn(5, 64)
122    before = h.reshape(5, 8, 8)
123    phi = make_phi(h.device).reshape(4, -1)
124    after = conserve_hidden(h).reshape(5, -1)
125    math_resid = float(((after - before.reshape(5, -1)) @ phi.T).abs().max())
126    assert math_resid < 1e-5
127
128    base = sweep_baseline(make_baseline, GRID, seeds=SEEDS)
129    idea_candidates = []
130    for cfg in GRID:
131        r = evaluate(make_idea(cfg), seeds=SEEDS)
132        idea_candidates.append((r["mean"], cfg, r))
133    _, best_cfg, idea = min(idea_candidates, key=lambda q: q[0])
134    trained_sig = inspect_signature(SEEDS[0], best_cfg)
135    report = make_report(
136        "dynamics", "rnn_small", base, idea,
137        {"mechanism_signature": {
138            "math_check_max_moment_residual": math_resid,
139            "trained_model": trained_sig,
140            "predicted": "four hidden-state moments preserved after rank-2 compression",
141            "observed": "test-time hidden-state moment residual measured from trained model",
142            "confirmed": bool(math_resid < 1e-5 and trained_sig.get("confirmed", False)),
143        }, "protocol": {"epochs": EPOCHS, "n_train": NTRAIN, "n_test": NTEST, "grid": GRID, "seeds": SEEDS}})
144    report["math_verification"] = {"max_moment_residual": math_resid, "passed": math_resid < 1e-5}
145    Path("bench_report.json").write_text(json.dumps(report, indent=2))
146    print(json.dumps(report, indent=2))
147
148
149if __name__ == "__main__":
150    main()