algorise/psl-artifacts / repro-bundle-v2 /repro /run_experiments_v2.py
algorise's picture
download
raw
26.3 kB
#!/usr/bin/env python3
"""Scaled empirical reproduction (v2) for ICML 2026 paper LyhBIrNBXv / arXiv 2602.19241:
"Scaling Laws for Precision in High-Dimensional Linear Regression".
Unlike the v1 script (which evaluated the paper's *analytic* risk formula and fit it
back to itself), this version runs genuine Monte-Carlo experiments on the paper's data
model and compares *measured* excess risk against the theoretical scaling laws.
Data model (Gaussian sequence / high-dimensional linear regression):
x_i ~ N(0, lambda_i), lambda_i = i^{-a} (power-law spectrum, Assumption 3.5)
theta_i = 1 (so the approximation error of a size-M model is
sum_{i>M} lambda_i = zeta(a, M+1) ~ M^{-(a-1)}/(a-1))
y = sum_i theta_i x_i + sigma * z
The ambient dimension is infinite: features beyond the largest fitted model size
enter y exactly through their (Gaussian) aggregate, so no truncation bias exists.
Estimator: OLS on the first M features of the (possibly quantized) design matrix.
Population excess risk is computed in closed form given the estimate:
R(theta_hat) = sum_{i<=M} lambda_i (theta_hat_i - 1)^2 + zeta(a, M+1)
Quantizers applied to the stored training design matrix X:
multiplicative (floating-point-like): X_q = X * (1 + eps*G), G ~ N(0,1) iid
additive (integer-like, b bits): X_q = delta_b * round(clip(X, +-A)/delta_b),
A = 4 (covers +-4 sd of the largest feature),
delta_b = 2A / 2^b, error variance ~ delta_b^2/12
Experiments
E1 risk vs model size M (M in [8,1024], N=16384) -> alpha = -(a-1); claims 1,2,3,4
E2 risk vs sample size N (N in [512,32768], compute-optimal M=N^{1/a})
-> beta = -(a-1)/a, N_eff shrinkage under multiplicative quant; claims 1,3,4
E3 crossover: risk vs N under additive b-bit quantization -> precision-limited
floor vs sample-limited regime; floor scaling ~2^{-2b(a-1)/a}; claim 2
E4 spectrum of quantized covariance -> tail flattening at delta_b^2/12; claim 2
E5 trace amplification E[tr(Xq'Xq)]/E[tr(X'X)] = 1 + eps^2; claim 5
Hardware: CPU only, thread-capped to 4 cores (shared box).
Run: python repro/run_experiments_v2.py (from PrecisionScalingLaws/)
Outputs: results/results_v2.json + results/fig_*.png
"""
import os
# Thread caps BEFORE importing numpy (4 cores of a shared 20-core CPU box).
for _v in ("OMP_NUM_THREADS", "OPENBLAS_NUM_THREADS", "MKL_NUM_THREADS",
"NUMEXPR_NUM_THREADS", "VECLIB_MAXIMUM_THREADS"):
os.environ[_v] = "4"
import json
import platform
import sys
import time
import numpy as np
from scipy import linalg as sla
from scipy import stats
from scipy.special import zeta
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
HERE = os.path.dirname(os.path.abspath(__file__))
RESULTS_DIR = os.path.normpath(os.path.join(HERE, "..", "results"))
os.makedirs(RESULTS_DIR, exist_ok=True)
SEED_BASE = 20260724
# Categorical palette (validated, fixed order): blue, orange, aqua, yellow, magenta, green, violet, red
PAL = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", "#e87ba4", "#008300", "#4a3aa7", "#e34948"]
plt.rcParams.update({
"figure.dpi": 130, "savefig.dpi": 130, "font.size": 9.5,
"axes.grid": True, "grid.alpha": 0.25, "grid.linewidth": 0.6,
"axes.spines.top": False, "axes.spines.right": False,
"lines.linewidth": 2.0, "legend.frameon": False,
})
# ----------------------------------------------------------------------------- helpers
def lambdas(a, M):
return np.arange(1, M + 1, dtype=np.float64) ** (-a)
def tail_risk(a, M):
"""sum_{i>M} i^{-a} (exact Hurwitz zeta)."""
return float(zeta(a, M + 1))
def quant_mult(X, eps, rng):
return X * (1.0 + eps * rng.standard_normal(X.shape))
def quant_add(X, bits, A=4.0, rng=None):
"""b-bit uniform quantizer with subtractive dither (standard dithered rounding).
Plain round-to-nearest ZEROES features whose scale is below delta/2 (dead
columns, rank-deficient covariance) -- a truncation effect, not the paper's
additive model. Subtractive dither makes the quantization error exactly
uniform(-delta/2, delta/2), independent of the signal: constant error
variance delta^2/12 per entry, i.e. precisely the additive (integer-like)
quantization channel analyzed by Theorems 4.2/4.4.
"""
delta = 2.0 * A / (2 ** bits)
if rng is None:
rng = np.random.default_rng(SEED_BASE + 424242 + bits)
u = rng.uniform(-delta / 2.0, delta / 2.0, size=X.shape)
Xq = np.clip(X + u, -A, A)
return delta * np.round(Xq / delta) - u, delta
def make_design(rng, N, M, a):
"""N x M design with independent N(0, i^{-a}) columns."""
Z = rng.standard_normal((N, M))
Z *= np.sqrt(lambdas(a, M))[None, :]
return Z
def make_labels(rng, X, a, sigma):
"""y = X @ 1 + tail-signal + sigma*noise (tail beyond X's columns, exact)."""
N, M = X.shape
tv = tail_risk(a, M)
y = X.sum(axis=1)
y += np.sqrt(tv) * rng.standard_normal(N)
y += sigma * rng.standard_normal(N)
return y
def risks_over_M_grid(Xq, y, a, M_grid):
"""Excess population risk of OLS on the first M columns of Xq, per M in M_grid."""
Mmax = M_grid[-1]
G = Xq[:, :Mmax].T @ Xq[:, :Mmax]
c = Xq[:, :Mmax].T @ y
lam = lambdas(a, Mmax)
out = []
for M in M_grid:
try:
cf = sla.cho_factor(G[:M, :M], lower=True, check_finite=False)
th = sla.cho_solve(cf, c[:M], check_finite=False)
except np.linalg.LinAlgError:
# Aggressive additive quantization rounds small tail features to exactly
# zero -> singular Gram. Min-norm LS assigns those dead columns theta=0,
# which is the honest estimate (the feature carries no information).
th = np.linalg.lstsq(G[:M, :M], c[:M], rcond=None)[0]
r = float(np.sum(lam[:M] * (th - 1.0) ** 2)) + tail_risk(a, M)
out.append(r)
return np.asarray(out)
def fit_loglog(x, y):
s, b, r, _, se = stats.linregress(np.log(x), np.log(y))
return float(s), float(np.exp(b)), float(r ** 2), float(se)
def ci95(vals, axis=0):
vals = np.asarray(vals)
m = vals.mean(axis=axis)
n = vals.shape[axis]
half = 1.96 * vals.std(axis=axis, ddof=1) / np.sqrt(n) if n > 1 else np.zeros_like(m)
return m, half
# ----------------------------------------------------------------------------- E1: risk vs M
def run_E1(seeds=12):
"""Risk vs model size M under full precision, multiplicative, and additive quant."""
t0 = time.time()
N, Mmax, sigma = 16384, 1024, 0.1
M_grid = np.unique(np.round(np.geomspace(8, Mmax, 12)).astype(int))
arms = [("fp64", None), ("mult_eps0.1", 0.1), ("mult_eps0.2", 0.2),
("add_b4", 4), ("add_b6", 6), ("add_b8", 8), ("add_b10", 10)]
a_values = [1.5, 2.0]
fit_max = {1.5: 512, 2.0: 90} # keep OLS variance term <~3% of bias term
res = {"N": N, "sigma": sigma, "M_grid": M_grid.tolist(), "seeds": seeds,
"arms": {}, "a_values": a_values, "fit_max": {str(k): v for k, v in fit_max.items()}}
risks = {(a, arm): [] for a in a_values for arm, _ in arms}
for s in range(seeds):
for a in a_values:
rng = np.random.default_rng(SEED_BASE + 1000 * s + int(10 * a))
X = make_design(rng, N, Mmax, a)
y = make_labels(rng, X, a, sigma)
for arm, p in arms:
if arm == "fp64":
Xq = X
elif arm.startswith("mult"):
Xq = quant_mult(X, p, np.random.default_rng(SEED_BASE + 7 + 1000 * s + int(10 * a)))
else:
Xq, _ = quant_add(X, p)
risks[(a, arm)].append(risks_over_M_grid(Xq, y, a, M_grid))
for a in a_values:
for arm, p in arms:
mean, half = ci95(np.stack(risks[(a, arm)]))
entry = {"risk_mean": mean.tolist(), "risk_ci95": half.tolist()}
sel = M_grid <= fit_max[a]
if arm.startswith(("fp", "mult")):
# per-seed slope fits -> CI on the exponent
slopes = [fit_loglog(M_grid[sel], r[sel])[0] for r in risks[(a, arm)]]
sm, sh = ci95(np.asarray(slopes))
_, amp, r2, _ = fit_loglog(M_grid[sel], mean[sel])
entry.update({"alpha_fit": float(sm), "alpha_ci95": float(sh),
"alpha_r2": r2, "alpha_theory": -(a - 1.0)})
else:
# additive: measured saturation size (argmin of mean risk) vs theory
M_sat = int(M_grid[int(np.argmin(mean))])
delta = 2.0 * 4.0 / (2 ** p)
M_eff_theory = float((delta ** 2 / 12.0) ** (-1.0 / a))
entry.update({"M_sat_empirical": M_sat, "M_eff_theory": M_eff_theory,
"risk_floor": float(mean.min()), "delta": delta})
res["arms"][f"a{a}_{arm}"] = entry
res["runtime_s"] = round(time.time() - t0, 1)
return res, {k: np.stack(v) for k, v in risks.items()}, M_grid
# ----------------------------------------------------------------------------- E2: risk vs N
def run_E2(seeds=12):
"""Risk vs sample size N at the compute-optimal allocation M = round(N^{1/a})."""
t0 = time.time()
sigma = 0.25
N_grid = np.array([512, 1024, 2048, 4096, 8192, 16384, 32768])
arms = [("fp64", None), ("mult_eps0.1", 0.1), ("mult_eps0.2", 0.2)]
a_values = [1.5, 2.0]
res = {"sigma": sigma, "N_grid": N_grid.tolist(), "seeds": seeds,
"allocation": "M = round(N^{1/a})", "arms": {}}
risks = {(a, arm): [] for a in a_values for arm, _ in arms}
for s in range(seeds):
for a in a_values:
Mmax = int(round(N_grid[-1] ** (1.0 / a)))
rng = np.random.default_rng(SEED_BASE + 500000 + 1000 * s + int(10 * a))
Xfull = make_design(rng, int(N_grid[-1]), Mmax, a)
yfull = make_labels(rng, Xfull, a, sigma)
for arm, p in arms:
if arm == "fp64":
Xq = Xfull
else:
Xq = quant_mult(Xfull, p, np.random.default_rng(
SEED_BASE + 900000 + 1000 * s + int(10 * a) + int(100 * p)))
row = []
for N in N_grid:
M = int(round(N ** (1.0 / a)))
r = risks_over_M_grid(Xq[:N, :M], yfull[:N], a, np.array([M]))[0]
row.append(r)
risks[(a, arm)].append(np.asarray(row))
fit_sel = N_grid >= 2048 # drop the two smallest N (finite-size N^{-2(a-1)/a} term)
for a in a_values:
beta_th = -(a - 1.0) / a
fp_mean, _ = ci95(np.stack(risks[(a, "fp64")]))
s_fp, A_fp, _, _ = fit_loglog(N_grid[fit_sel], fp_mean[fit_sel])
for arm, p in arms:
R = np.stack(risks[(a, arm)])
mean, half = ci95(R)
slopes = [fit_loglog(N_grid[fit_sel], r[fit_sel])[0] for r in R]
sm, sh = ci95(np.asarray(slopes))
_, amp, r2, _ = fit_loglog(N_grid[fit_sel], mean[fit_sel])
entry = {"risk_mean": mean.tolist(), "risk_ci95": half.tolist(),
"beta_fit": float(sm), "beta_ci95": float(sh), "beta_r2": r2,
"beta_theory": beta_th}
if arm.startswith("mult"):
# N_eff(N): solve R_fp(N_eff) = R_arm(N) on the fitted fp power law
Neff_ratio = (mean[fit_sel] / A_fp) ** (1.0 / s_fp) / N_grid[fit_sel]
entry["Neff_ratio_mean"] = float(np.mean(Neff_ratio))
entry["Neff_ratio_per_N"] = Neff_ratio.tolist()
entry["Neff_ratio_theory"] = float((1.0 + p ** 2) ** (-a / (a - 1.0)))
res["arms"][f"a{a}_{arm}"] = entry
res["runtime_s"] = round(time.time() - t0, 1)
return res, {k: np.stack(v) for k, v in risks.items()}, N_grid, fit_sel
# ----------------------------------------------------------------------------- E3: crossover
def run_E3(seeds=10):
"""Precision-limited vs sample-limited crossover: risk vs N for b-bit additive quant."""
t0 = time.time()
a, sigma = 1.5, 0.25
N_grid = np.array([512, 1024, 2048, 4096, 8192, 16384, 32768])
bits_list = [4, 5, 6, 7]
res = {"a": a, "sigma": sigma, "N_grid": N_grid.tolist(), "bits": bits_list,
"seeds": seeds, "arms": {}}
risks = {b: [] for b in bits_list}
risks_fp = []
for s in range(seeds):
Mmax = int(round(N_grid[-1] ** (1.0 / a)))
rng = np.random.default_rng(SEED_BASE + 300000 + 1000 * s)
Xfull = make_design(rng, int(N_grid[-1]), Mmax, a)
yfull = make_labels(rng, Xfull, a, sigma)
arms = [("fp", None)] + [(f"b{b}", b) for b in bits_list]
for arm, b in arms:
Xq = Xfull if b is None else quant_add(Xfull, b)[0]
row = []
for N in N_grid:
M = int(round(N ** (1.0 / a)))
row.append(risks_over_M_grid(Xq[:N, :M], yfull[:N], a, np.array([M]))[0])
(risks_fp if b is None else risks[b]).append(np.asarray(row))
fp_mean, fp_half = ci95(np.stack(risks_fp))
s_fp, A_fp, _, _ = fit_loglog(N_grid[N_grid >= 2048], fp_mean[N_grid >= 2048])
res["fp"] = {"risk_mean": fp_mean.tolist(), "risk_ci95": fp_half.tolist(),
"beta_fit_pooled": s_fp}
floors = []
for b in bits_list:
mean, half = ci95(np.stack(risks[b]))
floor = float(mean[-2:].mean()) # plateau level at the largest N
N_star = float((floor / A_fp) ** (1.0 / s_fp)) # where fp risk crosses floor
floors.append(floor)
res["arms"][f"b{b}"] = {"risk_mean": mean.tolist(), "risk_ci95": half.tolist(),
"floor": floor, "N_star": N_star}
# floor ~ 2^{-2 b (a-1)/a}: slope of log2(floor) vs b
sl, ic, r, _, _ = stats.linregress(bits_list, np.log2(floors))
res["floor_vs_bits"] = {"slope_fit": float(sl), "slope_theory": -2.0 * (a - 1.0) / a,
"r2": float(r ** 2), "floors": floors}
res["runtime_s"] = round(time.time() - t0, 1)
return res, np.stack(risks_fp), {b: np.stack(risks[b]) for b in bits_list}, N_grid
# ----------------------------------------------------------------------------- E4: spectrum
def run_E4(seeds=3):
"""Eigen-spectrum of the quantized empirical covariance: additive tail flattening."""
t0 = time.time()
a, d, N = 1.5, 2048, 8192
bits_list = [4, 6, 8]
spectra = {}
for arm in ["fp"] + [f"b{b}" for b in bits_list]:
acc = []
for s in range(seeds):
rng = np.random.default_rng(SEED_BASE + 700000 + 1000 * s)
X = make_design(rng, N, d, a)
if arm != "fp":
X = quant_add(X, int(arm[1:]))[0]
ev = np.linalg.eigvalsh(X.T @ X / N)[::-1]
acc.append(ev)
spectra[arm] = np.mean(acc, axis=0)
res = {"a": a, "d": d, "N": N, "bits": bits_list, "seeds": seeds,
"floors_theory": {f"b{b}": (2 * 4.0 / 2 ** b) ** 2 / 12.0 for b in bits_list},
"runtime_s": round(time.time() - t0, 1)}
# measured tail floor = median of the last 200 eigenvalues
res["floors_measured"] = {arm: float(np.median(spectra[arm][-200:]))
for arm in spectra if arm != "fp"}
return res, spectra
# ----------------------------------------------------------------------------- E5: trace ratio
def run_E5(seeds=30):
"""Claim 5: E[tr(Xq'Xq)] / E[tr(X'X)] = 1 + eps^2, across eps, with CIs."""
t0 = time.time()
a, d, N = 1.5, 3000, 4000
eps_list = [0.05, 0.10, 0.15, 0.20, 0.30]
ratios = {e: [] for e in eps_list}
for s in range(seeds):
rng = np.random.default_rng(SEED_BASE + 800000 + s)
X = make_design(rng, N, d, a)
tr = float(np.sum(X * X))
for e in eps_list:
Xq = quant_mult(X, e, np.random.default_rng(SEED_BASE + 810000 + 100 * s + int(1000 * e)))
ratios[e].append(float(np.sum(Xq * Xq)) / tr)
res = {"a": a, "d": d, "N": N, "seeds": seeds, "eps": eps_list, "per_eps": {}}
xs, ys = [], []
for e in eps_list:
m, h = ci95(np.asarray(ratios[e]))
res["per_eps"][str(e)] = {"ratio_mean": float(m), "ratio_ci95": float(h),
"ratio_theory": 1.0 + e ** 2}
xs.append(e ** 2)
ys.append(m - 1.0)
sl, ic, r, _, _ = stats.linregress(xs, ys)
res["excess_vs_eps2"] = {"slope_fit": float(sl), "slope_theory": 1.0,
"intercept": float(ic), "r2": float(r ** 2)}
res["runtime_s"] = round(time.time() - t0, 1)
return res, ratios
# ----------------------------------------------------------------------------- figures
def fig_E1(res, risks, M_grid, path):
fig, axes = plt.subplots(1, 2, figsize=(9.6, 3.9), sharey=False)
show = [("fp64", "FP64", PAL[0]), ("mult_eps0.1", "mult eps=0.1", PAL[1]),
("mult_eps0.2", "mult eps=0.2", PAL[2])]
for ax, a in zip(axes, [1.5, 2.0]):
for arm, lab, col in show:
e = res["arms"][f"a{a}_{arm}"]
m = np.array(e["risk_mean"]); h = np.array(e["risk_ci95"])
ax.fill_between(M_grid, m - h, m + h, color=col, alpha=0.18, lw=0)
ax.plot(M_grid, m, color=col,
label=f"{lab}: alpha={e['alpha_fit']:.3f} (R2={e['alpha_r2']:.4f})")
fm = res["fit_max"][str(a)]
ref = np.array(res["arms"][f"a{a}_fp64"]["risk_mean"])[0]
xs = M_grid[M_grid <= fm].astype(float)
ax.plot(xs, ref * (xs / xs[0]) ** (-(a - 1.0)), ls="--", color="#666666", lw=1.4,
label=f"theory slope {-(a-1.0):.2f}")
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xlabel("model size M"); ax.set_title(f"a = {a}")
ax.legend(fontsize=7.5)
axes[0].set_ylabel("excess risk")
fig.suptitle("E1: risk vs M — multiplicative quantization preserves the M-exponent (claims 1, 3, 4)", y=1.02)
fig.tight_layout(); fig.savefig(path, bbox_inches="tight"); plt.close(fig)
def fig_E2(res, N_grid, path):
fig, axes = plt.subplots(1, 2, figsize=(9.6, 3.9))
show = [("fp64", "FP64", PAL[0]), ("mult_eps0.1", "mult eps=0.1", PAL[1]),
("mult_eps0.2", "mult eps=0.2", PAL[2])]
for ax, a in zip(axes, [1.5, 2.0]):
for arm, lab, col in show:
e = res["arms"][f"a{a}_{arm}"]
m = np.array(e["risk_mean"]); h = np.array(e["risk_ci95"])
ax.fill_between(N_grid, m - h, m + h, color=col, alpha=0.18, lw=0)
extra = f", N_eff/N={e['Neff_ratio_mean']:.2f}" if "Neff_ratio_mean" in e else ""
ax.plot(N_grid, m, color=col,
label=f"{lab}: beta={e['beta_fit']:.3f} (R2={e['beta_r2']:.4f}){extra}")
ref = np.array(res["arms"][f"a{a}_fp64"]["risk_mean"])[2]
xs = N_grid[N_grid >= 2048].astype(float)
ax.plot(xs, ref * (xs / xs[0]) ** (-(a - 1.0) / a), ls="--", color="#666666",
lw=1.4, label=f"theory slope {-(a-1.0)/a:.3f}")
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xlabel("sample size N"); ax.set_title(f"a = {a}, M = N^(1/a)")
ax.legend(fontsize=7.5)
axes[0].set_ylabel("excess risk")
fig.suptitle("E2: risk vs N at compute-optimal M — data exponent and N_eff shrinkage (claims 1, 3, 4)", y=1.02)
fig.tight_layout(); fig.savefig(path, bbox_inches="tight"); plt.close(fig)
def fig_E3(res, N_grid, path):
fig, axes = plt.subplots(1, 2, figsize=(9.6, 3.9))
ax = axes[0]
fp = res["fp"]
ax.plot(N_grid, fp["risk_mean"], color=PAL[0], label="FP64")
for i, b in enumerate(res["bits"]):
e = res["arms"][f"b{b}"]
col = PAL[(i + 1) % len(PAL)]
m = np.array(e["risk_mean"]); h = np.array(e["risk_ci95"])
ax.fill_between(N_grid, m - h, m + h, color=col, alpha=0.18, lw=0)
ax.plot(N_grid, m, color=col, label=f"int{b}")
ax.axhline(e["floor"], color=col, ls=":", lw=1.0)
if N_grid[0] < e["N_star"] < N_grid[-1]:
ax.axvline(e["N_star"], color=col, ls="--", lw=0.9, alpha=0.6)
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xlabel("sample size N"); ax.set_ylabel("excess risk")
ax.set_title("precision-limited floors and crossover N*")
ax.legend(fontsize=8)
ax2 = axes[1]
f = res["floor_vs_bits"]
ax2.plot(res["bits"], np.log2(f["floors"]), "o-", color=PAL[0], ms=7,
label=f"measured slope {f['slope_fit']:.3f}/bit")
b0 = res["bits"][0]
ax2.plot(res["bits"], np.log2(f["floors"][0]) + f["slope_theory"] * (np.array(res["bits"]) - b0),
ls="--", color="#666666", label=f"theory {f['slope_theory']:.3f}/bit")
ax2.set_xlabel("bit-width b"); ax2.set_ylabel("log2(risk floor)")
ax2.set_title(f"floor vs bits (R2={f['r2']:.4f})")
ax2.legend(fontsize=8)
fig.suptitle("E3: sample-limited vs precision-limited crossover under additive quantization (claim 2)", y=1.02)
fig.tight_layout(); fig.savefig(path, bbox_inches="tight"); plt.close(fig)
def fig_E4(res, spectra, res_e1, M_grid, path):
fig, axes = plt.subplots(1, 2, figsize=(9.6, 3.9))
ax = axes[0]
idx = np.arange(1, len(spectra["fp"]) + 1)
ax.plot(idx, spectra["fp"], color=PAL[0], label="FP64")
for i, b in enumerate(res["bits"]):
col = PAL[(i + 1) % len(PAL)]
ax.plot(idx, spectra[f"b{b}"], color=col, label=f"int{b}")
ax.axhline(res["floors_theory"][f"b{b}"], color=col, ls=":", lw=1.0)
ax.set_xscale("log"); ax.set_yscale("log")
ax.set_xlabel("eigenvalue rank"); ax.set_ylabel("eigenvalue")
ax.set_title("covariance spectrum: additive noise floors the tail")
ax.legend(fontsize=8)
ax2 = axes[1]
a = 1.5
for i, arm in enumerate(["fp64", "add_b4", "add_b6", "add_b8", "add_b10"]):
e = res_e1["arms"][f"a{a}_{arm}"]
col = PAL[i % len(PAL)]
lab = "FP64" if arm == "fp64" else f"int{arm[5:]}"
if "M_sat_empirical" in e:
lab += f" (M_sat={e['M_sat_empirical']})"
ax2.axvline(e["M_eff_theory"], color=col, ls="--", lw=0.9, alpha=0.6)
m = np.array(e["risk_mean"]); h = np.array(e["risk_ci95"])
ax2.fill_between(M_grid, m - h, m + h, color=col, alpha=0.18, lw=0)
ax2.plot(M_grid, m, color=col, label=lab)
ax2.set_xscale("log"); ax2.set_yscale("log")
ax2.set_xlabel("model size M"); ax2.set_ylabel("excess risk")
ax2.set_title("risk vs M saturates at M_eff (dashed = theory)")
ax2.legend(fontsize=7.5)
fig.suptitle("E4: additive quantization flattens the spectral tail and caps M_eff (claim 2)", y=1.02)
fig.tight_layout(); fig.savefig(path, bbox_inches="tight"); plt.close(fig)
def fig_E5(res, path):
fig, ax = plt.subplots(figsize=(5.4, 3.9))
eps2 = np.array([float(e) ** 2 for e in res["eps"]])
means = np.array([res["per_eps"][str(e)]["ratio_mean"] for e in res["eps"]]) - 1.0
halves = np.array([res["per_eps"][str(e)]["ratio_ci95"] for e in res["eps"]])
ax.errorbar(eps2, means, yerr=halves, fmt="o", color=PAL[0], ms=7, capsize=3,
label="measured (30 seeds, 95% CI)")
f = res["excess_vs_eps2"]
xs = np.linspace(0, eps2.max() * 1.05, 50)
ax.plot(xs, xs, ls="--", color="#666666", label="theory: ratio - 1 = eps^2")
ax.plot(xs, f["intercept"] + f["slope_fit"] * xs, color=PAL[1], lw=1.6,
label=f"fit slope {f['slope_fit']:.4f} (R2={f['r2']:.6f})")
ax.set_xlabel("eps^2"); ax.set_ylabel("trace ratio - 1")
ax.set_title("E5: trace amplification tr(Xq'Xq)/tr(X'X) = 1 + eps^2 (claim 5)")
ax.legend(fontsize=8)
fig.tight_layout(); fig.savefig(path, bbox_inches="tight"); plt.close(fig)
# ----------------------------------------------------------------------------- main
def main():
t0 = time.time()
print("Reproduction v2: Scaling Laws for Precision in High-Dimensional Linear Regression")
print(f"host={platform.node()} cpu-only threads=4 numpy={np.__version__}")
print("\n[E1] risk vs M (N=16384, M in [8,1024], 7 arms x 2 spectra x 12 seeds)...")
e1, r1, M_grid = run_E1()
print(f" done in {e1['runtime_s']}s")
print("\n[E2] risk vs N (N in [512,32768], M=N^(1/a), 3 arms x 2 spectra x 12 seeds)...")
e2, r2, N_grid, _ = run_E2()
print(f" done in {e2['runtime_s']}s")
print("\n[E3] crossover under int-b quantization (b in {4,5,6,7}, 10 seeds)...")
e3, r3fp, r3, N_grid3 = run_E3()
print(f" done in {e3['runtime_s']}s")
print("\n[E4] covariance spectra (d=2048, N=8192, 3 seeds)...")
e4, spectra = run_E4()
print(f" done in {e4['runtime_s']}s")
print("\n[E5] trace amplification (d=3000, N=4000, 30 seeds, 5 eps values)...")
e5, _ = run_E5()
print(f" done in {e5['runtime_s']}s")
print("\nRendering figures...")
fig_E1(e1, r1, M_grid, os.path.join(RESULTS_DIR, "fig_e1_risk_vs_M.png"))
fig_E2(e2, N_grid, os.path.join(RESULTS_DIR, "fig_e2_risk_vs_N.png"))
fig_E3(e3, N_grid3, os.path.join(RESULTS_DIR, "fig_e3_crossover.png"))
fig_E4(e4, spectra, e1, M_grid, os.path.join(RESULTS_DIR, "fig_e4_spectrum.png"))
fig_E5(e5, os.path.join(RESULTS_DIR, "fig_e5_trace_ratio.png"))
summary = {
"paper": {"openreview": "LyhBIrNBXv", "arxiv": "2602.19241",
"title": "Scaling Laws for Precision in High-Dimensional Linear Regression"},
"hardware": {"host": platform.node(), "platform": platform.platform(),
"cpu_only": True, "threads": 4,
"numpy": np.__version__},
"seed_base": SEED_BASE,
"command": "python repro/run_experiments_v2.py",
"E1_risk_vs_M": e1, "E2_risk_vs_N": e2, "E3_crossover": e3,
"E4_spectrum": e4, "E5_trace": e5,
"total_runtime_s": round(time.time() - t0, 1),
}
out = os.path.join(RESULTS_DIR, "results_v2.json")
with open(out, "w", encoding="utf-8", newline="\n") as f:
json.dump(summary, f, indent=2)
print(f"\nTotal runtime: {summary['total_runtime_s']}s")
print(f"Wrote {out} and 5 figures to {RESULTS_DIR}")
return 0
if __name__ == "__main__":
sys.exit(main())

Xet Storage Details

Size:
26.3 kB
·
Xet hash:
1fcb52c349893871a42bd37b7d2dd969e9bb29eb8dd9d85b249c1c9df629830a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.