Balanced design attention / balanced_design_attention.py

Failed on benchmark

Raw ⬇ ZIP
  1import json, math, time, random
  2from pathlib import Path
  3import numpy as np
  4
  5try:
  6    import torch
  7    import torch.nn.functional as F
  8except Exception:
  9    torch = None
 10
 11
 12def cyclic_design(v, base):
 13    blocks = [sorted({(x + t) % v for x in base}) for t in range(v)]
 14    return np.asarray(blocks, dtype=np.int64)
 15
 16
 17def make_designs():
 18    # Cyclic (7,3,1) and (13,4,1) projective-plane designs.
 19    return {
 20        "Fano_7": cyclic_design(7, [0, 1, 3]),
 21        "projective_13": cyclic_design(13, [0, 1, 3, 9]),
 22    }
 23
 24
 25def incidence(blocks, v):
 26    M = np.zeros((v, len(blocks)), dtype=np.int64)
 27    for j, b in enumerate(blocks):
 28        M[b, j] = 1
 29    return M
 30
 31
 32def math_check(name, blocks):
 33    v = int(blocks.max()) + 1
 34    M = incidence(blocks, v)
 35    G = M @ M.T
 36    r = int(M.sum(1)[0])
 37    off = G[~np.eye(v, dtype=bool)]
 38    lam = int(off[0])
 39    pair_vals = off.tolist()
 40    # Z(x,y) is the column-vector membership difference.
 41    norm_err = []
 42    for x in range(v):
 43        for y in range(x + 1, v):
 44            z = M[x] - M[y]
 45            norm_err.append(int(z @ z))
 46    return {
 47        "name": name, "v": v, "b": len(blocks), "k": len(blocks[0]),
 48        "r": r, "lambda": lam, "row_min": int(M.sum(1).min()),
 49        "row_max": int(M.sum(1).max()), "pair_min": int(min(pair_vals)),
 50        "pair_max": int(max(pair_vals)), "pair_variance": float(np.var(pair_vals)),
 51        "mmT_identity_maxerr": int(np.max(np.abs(G - ((r-lam)*np.eye(v, dtype=int)+lam*np.ones((v,v), dtype=int))))),
 52        "predicted_z_norm_sq": 2*(r-lam), "observed_z_norm_sq_min": min(norm_err),
 53        "observed_z_norm_sq_max": max(norm_err), "z_norm_sq_mean": float(np.mean(norm_err)),
 54    }
 55
 56
 57def random_blocks(v, k, b, seed=0):
 58    rng = np.random.default_rng(seed)
 59    return np.asarray([rng.choice(v, k, replace=False) for _ in range(b)], dtype=np.int64)
 60
 61
 62def contiguous_blocks(v, k, b):
 63    return np.asarray([[(i + t) % v for t in range(k)] for i in range(b)], dtype=np.int64)
 64
 65
 66def torch_attention(x, blocks, heads=2):
 67    # One-head equivalent batched block attention, with scatter-and-average.
 68    n, d = x.shape
 69    q, k, val = x, x, x
 70    y = torch.zeros_like(x)
 71    counts = torch.zeros(n, device=x.device)
 72    scale = math.sqrt(d)
 73    for idx_np in blocks:
 74        idx = torch.as_tensor(idx_np, device=x.device, dtype=torch.long)
 75        s = (q[idx] @ k[idx].T) / scale
 76        a = F.softmax(s, dim=-1)
 77        y.index_add_(0, idx, a @ val[idx])
 78        counts.index_add_(0, idx, torch.ones(len(idx), device=x.device))
 79    return y / counts[:, None]
 80
 81
 82def dense_attention(x):
 83    return F.softmax(x @ x.T / math.sqrt(x.shape[1]), dim=-1) @ x
 84
 85
 86def benchmark(seed=123):
 87    random.seed(seed); np.random.seed(seed)
 88    if torch is None:
 89        return {"error": "torch unavailable"}
 90    try:
 91        device = "cuda" if torch.cuda.is_available() else "cpu"
 92        torch.manual_seed(seed)
 93        if device == "cuda": torch.cuda.manual_seed_all(seed)
 94        # Genuine balanced designs are benchmarked on their complete point sets.
 95        # This avoids incorrectly claiming that disjoint copies preserve global coverage.
 96        cases_by_n = {
 97            7: {"dense": None, "contiguous": contiguous_blocks(7, 3, 7),
 98                "random": random_blocks(7, 3, 7, seed), "design": make_designs()["Fano_7"]},
 99            13: {"dense": None, "contiguous": contiguous_blocks(13, 4, 13),
100                 "random": random_blocks(13, 4, 13, seed), "design": make_designs()["projective_13"]},
101        }
102        out = {"device": device, "results": {}}
103        for n, cases in cases_by_n.items():
104            d = 32; k = len(cases["design"][0]); x = torch.randn(n, d, device=device)
105            out["results"][str(n)] = {}
106            for name, blocks in cases.items():
107                if name == "dense":
108                    fn = lambda: dense_attention(x)
109                    flops = n*n*d*2; pair_var = 0.0; row_var = 0.0
110                else:
111                    fn = lambda blocks=blocks: torch_attention(x, blocks)
112                    flops = len(blocks)*k*k*d*2
113                    M = incidence(blocks, n); co = M @ M.T
114                    pair_var = float(np.var(co[~np.eye(n, dtype=bool)]))
115                    row_var = float(np.var(M.sum(1)))
116                fn()
117                if device == "cuda": torch.cuda.synchronize()
118                ts=[]
119                for _ in range(5):
120                    t=time.perf_counter(); fn()
121                    if device == "cuda": torch.cuda.synchronize()
122                    ts.append(time.perf_counter()-t)
123                out["results"][str(n)][name] = {
124                    "median_ms": 1000*float(np.median(ts)),
125                    "attention_score_flops": int(flops),
126                    "pair_coverage_variance": pair_var, "row_degree_variance": row_var}
127        return out
128    except Exception as e:
129        return {"error": repr(e)}
130
131
132def main():
133    designs = make_designs()
134    checks = [math_check(name, blocks) for name, blocks in designs.items()]
135    # Prediction sweep: for every exact design, all pair norms must equal 2(r-lambda).
136    # A second prediction is invariance under scaling the incidence matrix by s copies:
137    # r, lambda, and ||Z||^2 scale by s, while pair variance remains zero.
138    base = designs["Fano_7"]
139    sweep=[]
140    for copies in [1,2,4,8]:
141        b=np.tile(base, (copies,1))
142        c=math_check(f"Fano copies={copies}", b)
143        c["predicted_r"] = 3*copies
144        c["predicted_lambda"] = 1*copies
145        c["predicted_z_norm_sq"] = 2*(3-1)*copies
146        sweep.append(c)
147    report={"math_checks": checks, "scaling_sweep": sweep, "benchmark": benchmark()}
148    Path("results.json").write_text(json.dumps(report, indent=2))
149    print(json.dumps(report, indent=2))
150
151if __name__ == "__main__": main()