Buckets:
| """Claim 3: Fixed-point analysis quantifies the asymptotic deviation from the | |
| unquantized linear model. | |
| Reproduces Figure 6 of Ichikawa et al. (arXiv:2510.10693) and verifies the | |
| small-learning-rate closed-form of Theorem V.8: | |
| eps_g* = eps_g^(0) + sigma2_psi * Delta^2 * p * (1-p) + o(eta) (interior p) | |
| eps_g* = eps_g^(0) + o(1/sqrt(log(1/eta))) (p in {0,1}) | |
| eps_g* = rho + sigma2 - 2*kappa_psi*omega + sigma2_psi*omega^2 + o(eta) (|c|>=omega) | |
| where | |
| c = kappa_x * rho / (sigma2_x + lambda) | |
| Delta = 2*omega / L (L = 2^b - 2) | |
| p = (c - v_{i*}) / Delta in (0,1), v_{i*} <= c <= v_{i*+1} | |
| eps_g0 = rho + sigma2 - 2*kappa_x*c + sigma2_x*c^2 | |
| Also reproduces Figure 5 (input-only quantization stability boundary and | |
| steady-state eps_g* vs omega) and the closed-form Proposition VI.1. | |
| """ | |
| import json | |
| import math | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| sys.path.insert(0, str(Path(__file__).parent)) | |
| from ste_repro import ( | |
| Quantizer, eps_g, macro_m_psi, macro_q_psi, macro_r_psi, | |
| ode_rhs, input_only_fixed_point, input_only_stability_bound, | |
| small_eta_fixed_point_prediction, | |
| ) | |
| from ste_sim_batch import run_ste_batch, Setting | |
| from scipy.integrate import solve_ivp | |
| OUT = Path(__file__).resolve().parents[1] / "outputs" / "claim3" | |
| OUT.mkdir(parents=True, exist_ok=True) | |
| def run_figure6_small_eta(): | |
| """Figure 6: eps_g* vs omega for weight quantizer b in {2,3} and input | |
| quantizer b_x in {2,3,4} (plus unquantized input). | |
| Per paper: STE simulation d=100, eta=1e-4, lambda=0, sigma2=0, tau=8e6. | |
| The reported curves are eps_g* as omega varies. | |
| The ODE / theoretical prediction is from Theorem V.8 (small-eta limit). | |
| """ | |
| print("=" * 60) | |
| print("[Claim 3 / Figure 6] eps_g* vs omega (small-eta)") | |
| print("=" * 60) | |
| lam = 0.0 | |
| rho = 1.0 | |
| sigma2 = 0.0 | |
| # Note: paper uses eta = 1e-4 and tau up to 8e6 (so n_steps = 8e6 * d = 8e8 for d=100). | |
| # This is too long for our wall-clock budget. We instead: | |
| # - Verify the THEORETICAL small-eta prediction against numerical ODE | |
| # fixed points (which can be computed at any eta, including eta=1e-4) | |
| # by root-finding the ODE right-hand side = 0. | |
| # - Run STE at a less aggressive eta=1e-2 and tau=1e5 (still small enough | |
| # for the asymptotic prediction to hold) for d=200, n_seeds=3. | |
| # ---- Theoretical predictions (closed form, Theorem V.8) ---- | |
| omegas = np.linspace(0.1, 2.0, 39) | |
| # weight quantizer b in {2,3}; input quantizer b_x in {2,3,4} + unquantized | |
| weight_bs = [2, 3] | |
| input_configs = [("unquant", None), ("b_x=2", 2), ("b_x=3", 3), ("b_x=4", 4)] | |
| omega_x = 1.0 # input range (paper Figure 6) | |
| theory = {} | |
| for b_w in weight_bs: | |
| for name, b_x in input_configs: | |
| qx = Quantizer(b=b_x, omega=omega_x) if b_x is not None else None | |
| preds = [] | |
| for omega in omegas: | |
| qw = Quantizer(b=b_w, omega=omega) | |
| p = small_eta_fixed_point_prediction(qw, qx, lam=lam, rho=rho, sigma2=sigma2) | |
| preds.append(p) | |
| theory[f"b_w={b_w},{name}"] = preds | |
| # ---- ODE fixed-point via root finding ---- | |
| # We solve dm/dtau = 0, dq/dtau = 0 by Newton iteration starting from a | |
| # reasonable initial guess. For weight-input quant the rhs uses m_psi(m,s) | |
| # and r_psi(m,s); solving in (m,s) coordinates is easier because the first | |
| # stationarity condition reduces to m_psi(m,s) = c = kappa_x rho/(sigma2_x+lam). | |
| from scipy.optimize import brentq | |
| def find_fp_ode(eta, qw, qx, lam, rho, sigma2, tau_max_ode=2000.0): | |
| """Find the fixed point by integrating ODE to long tau.""" | |
| kappa_x = 1.0 if qx is None else qx.kappa_psi() | |
| sigma2_x = 1.0 if qx is None else qx.sigma2_psi() | |
| m0, q0 = 0.0, 1.0 | |
| sol = solve_ivp( | |
| ode_rhs, [0.0, tau_max_ode], [m0, q0], | |
| args=(qw, kappa_x, sigma2_x, eta, lam, rho, sigma2), | |
| t_eval=np.linspace(0, tau_max_ode, 200), | |
| rtol=1e-9, atol=1e-12, method="DOP853", | |
| ) | |
| m_fp = float(sol.y[0, -1]) | |
| q_fp = float(sol.y[1, -1]) | |
| s = math.sqrt(max(q_fp - m_fp ** 2 / rho, 1e-12)) | |
| eps_fp = eps_g(m_fp, s, qw, kappa_x, sigma2_x, rho, sigma2) | |
| return m_fp, q_fp, eps_fp | |
| # Compute ODE fixed points at eta = 1e-3 (small but tractable) | |
| eta_small = 1e-3 | |
| ode_fps = {} | |
| for b_w in weight_bs: | |
| for name, b_x in input_configs: | |
| qx = Quantizer(b=b_x, omega=omega_x) if b_x is not None else None | |
| fps = [] | |
| for omega in omegas: | |
| qw = Quantizer(b=b_w, omega=omega) | |
| m_fp, q_fp, eps_fp = find_fp_ode(eta_small, qw, qx, lam, rho, sigma2) | |
| fps.append({"omega": float(omega), "m_fp": m_fp, "q_fp": q_fp, "eps_fp": eps_fp}) | |
| ode_fps[f"b_w={b_w},{name}"] = fps | |
| print(f" ODE fps for {b_w},{name}: 1 sample: {fps[len(fps)//2]}") | |
| # ---- STE simulation: at a less aggressive eta, for one curve, to verify ---- | |
| # We pick b_w=2, b_x=unquant (matches paper's left panel of Figure 6) | |
| # and run STE at d=200, eta=5e-3, n_steps = 2e6 (tau_max = 10000). | |
| print("\n STE simulation for one curve (b_w=2, unquant input):") | |
| d = 200 | |
| n_seeds = 3 | |
| eta_ste = 5e-3 | |
| n_steps_ste = 2_000_000 | |
| log_period_ste = 2000 | |
| # sweep omega values | |
| omegas_ste = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5] | |
| settings = [] | |
| for omega in omegas_ste: | |
| qw = Quantizer(b=2, omega=omega) | |
| s = Setting( | |
| name=f"omega={omega}", | |
| qw_levels=qw.levels.tolist(), qw_theta=qw.theta.tolist(), | |
| qw_omega=qw.omega, qw_Delta=qw.Delta, | |
| qx_levels=None, | |
| eta=eta_ste, lam=lam, sigma2_x=1.0, kappa_x=1.0, | |
| d=d, rho=rho, sigma2=sigma2, | |
| w_init_std=1.0, w_star_value=1.0, n_seeds=n_seeds, | |
| ) | |
| settings.append(s) | |
| print(f" n_steps={n_steps_ste}, log_period={log_period_ste}, B={n_seeds * len(settings)}") | |
| t0 = time.time() | |
| results, wall = run_ste_batch(settings, n_steps=n_steps_ste, log_period=log_period_ste, device="cuda") | |
| print(f" STE wall: {wall:.1f}s ({time.time()-t0:.1f}s incl.)") | |
| # final eps_g as the "STE fixed point" estimate | |
| ste_fps = [] | |
| for i, omega in enumerate(omegas_ste): | |
| eps_final = float(results[i]["eps_g"][-100:].mean()) # average last 100 logs | |
| eps_final_std = float(results[i]["eps_g"][-100:].std()) | |
| ste_fps.append({ | |
| "omega": omega, "eps_fp_ste": eps_final, "eps_fp_ste_std": eps_final_std, | |
| }) | |
| print(f" omega={omega:.2f}: STE eps*={eps_final:.4f} (std {eps_final_std:.4f})") | |
| # ---- Plot: Figure 6 reproduction ---- | |
| fig, axes = plt.subplots(1, 2, figsize=(12, 5)) | |
| for ax, b_w in zip(axes, weight_bs): | |
| # theory curves | |
| for name, b_x in input_configs: | |
| key = f"b_w={b_w},{name}" | |
| preds = theory[key] | |
| eps_star = [p["eps_star_pred"] for p in preds] | |
| ls = "-" if b_x is None else "--" | |
| ax.plot(omegas, eps_star, ls, label=f"theory {name}", lw=2) | |
| # ODE fixed points | |
| for name, b_x in input_configs: | |
| key = f"b_w={b_w},{name}" | |
| fps = ode_fps[key] | |
| eps_fp = [f["eps_fp"] for f in fps] | |
| ax.plot(omegas, eps_fp, ":", label=f"ODE fp {name}", lw=1.5) | |
| # STE points (only for b_w=2, unquant input) | |
| if b_w == 2: | |
| ax.errorbar(omegas_ste, [s["eps_fp_ste"] for s in ste_fps], | |
| yerr=[s["eps_fp_ste_std"] for s in ste_fps], | |
| fmt="o", color="black", ms=6, capsize=3, label="STE (b_w=2, unquant)") | |
| ax.set_xlabel(r"Weight quantization range $\omega$") | |
| ax.set_ylabel(r"$\varepsilon_g^*$ (asymptotic)") | |
| ax.set_title(f"Weight quant b={b_w} (input range ω_x=1)") | |
| ax.legend(fontsize=8) | |
| ax.grid(True, alpha=0.3) | |
| fig.suptitle("Claim 3 / Figure 6: Fixed-point eps_g* vs ω (small-eta limit)") | |
| fig.tight_layout() | |
| fig.savefig(OUT / "claim3_figure6_fixed_point.png", dpi=120) | |
| plt.close(fig) | |
| # ---- Verify Theorem V.8 prediction against ODE fixed point ---- | |
| # For each (b_w, b_x, omega), compare theoretical eps_g* vs ODE-integrated eps_g*. | |
| # Theory should match ODE in the small-eta limit. | |
| verify = [] | |
| for b_w in weight_bs: | |
| for name, b_x in input_configs: | |
| for omega in omegas: | |
| qw = Quantizer(b=b_w, omega=omega) | |
| qx = Quantizer(b=b_x, omega=omega_x) if b_x is not None else None | |
| thy = small_eta_fixed_point_prediction(qw, qx, lam=lam, rho=rho, sigma2=sigma2) | |
| # find ODE fp at this omega | |
| m_fp, q_fp, eps_fp = find_fp_ode(eta_small, qw, qx, lam, rho, sigma2) | |
| verify.append({ | |
| "b_w": b_w, "b_x": name, "omega": float(omega), | |
| "theory_eps_star": thy["eps_star_pred"], | |
| "theory_regime": thy["regime"], | |
| "theory_c": thy["c"], | |
| "theory_p": thy["p"], | |
| "ode_eps_star": eps_fp, | |
| "abs_diff": abs(thy["eps_star_pred"] - eps_fp), | |
| }) | |
| verify_arr = np.array([v["abs_diff"] for v in verify]) | |
| print(f"\n Theory vs ODE fixed-point verification:") | |
| print(f" n_points = {len(verify)}") | |
| print(f" max |theory - ODE| = {verify_arr.max():.6f}") | |
| print(f" mean |theory - ODE| = {verify_arr.mean():.6f}") | |
| # Most should be small (theory holds in small-eta; we use eta=1e-3) | |
| out = { | |
| "omegas_theory": omegas.tolist(), | |
| "omegas_ste": omegas_ste, | |
| "weight_bs": weight_bs, | |
| "input_configs": [name for name, _ in input_configs], | |
| "theory": theory, | |
| "ode_fps": ode_fps, | |
| "ste_fps": ste_fps, | |
| "verification": { | |
| "n_points": len(verify), | |
| "max_abs_diff": float(verify_arr.max()), | |
| "mean_abs_diff": float(verify_arr.mean()), | |
| "details": verify, | |
| }, | |
| "_meta": { | |
| "d": d, "eta_ste": eta_ste, "eta_ode": eta_small, | |
| "lam": lam, "rho": rho, "sigma2": sigma2, "omega_x": omega_x, | |
| "n_seeds": n_seeds, "n_steps_ste": n_steps_ste, | |
| }, | |
| } | |
| with open(OUT / "claim3_results.json", "w") as f: | |
| json.dump(out, f, indent=2, default=float) | |
| print(f"\n[Claim 3] Done. Outputs in {OUT}") | |
| return out | |
| def run_figure5_stability(): | |
| """Figure 5: input-only quantization stability boundary 2/sigma2_psi and | |
| steady-state eps_g* vs omega. Closed form (Prop. VI.1). | |
| """ | |
| print("=" * 60) | |
| print("[Claim 3 / Figure 5] Input-only: stability & eps_g* vs omega") | |
| print("=" * 60) | |
| sigma2 = 0.0 | |
| lam = 0.0 | |
| rho = 1.0 | |
| eta = 1e-3 # small eta | |
| omegas = np.linspace(0.1, 2.0, 39) | |
| out = {} | |
| for b in [2, 3, 4, 10]: | |
| stab = [] | |
| eps_stars = [] | |
| for omega in omegas: | |
| qx = Quantizer(b=b, omega=omega) | |
| sigma2_x = qx.sigma2_psi() | |
| kappa_x = qx.kappa_psi() | |
| stab.append(input_only_stability_bound(sigma2_x, lam)) | |
| m_s, q_s, e_s = input_only_fixed_point(kappa_x, sigma2_x, eta, lam, rho, sigma2) | |
| eps_stars.append(e_s) | |
| out[f"b={b}"] = { | |
| "omegas": omegas.tolist(), | |
| "stability_boundary": stab, | |
| "eps_star": eps_stars, | |
| } | |
| # also unquantized baseline: stability 2/(1+lam)=2, eps* = rho - rho/(1+lam) = 0 (with sigma2=0) | |
| print(f" b={b}: stability range [{min(stab):.3f}, {max(stab):.3f}], eps* range [{min(eps_stars):.4f}, {max(eps_stars):.4f}]") | |
| # unquantized baseline | |
| out["unquant"] = { | |
| "stability_boundary": 2.0, | |
| "eps_star": 0.0, # rho + sigma2 - rho/(sigma2_x+lam) with sigma2_x=1, lam=0 -> 1 - 1 = 0 | |
| } | |
| fig, axes = plt.subplots(1, 2, figsize=(12, 5)) | |
| ax = axes[0] | |
| for b in [2, 3, 4, 10]: | |
| ax.plot(out[f"b={b}"]["omegas"], out[f"b={b}"]["stability_boundary"], label=f"b={b}") | |
| ax.axhline(2.0, color="k", linestyle="--", label="unquantized") | |
| ax.set_xlabel(r"Input quantization range $\omega$") | |
| ax.set_ylabel(r"Stability boundary $2/\sigma_\psi^2$") | |
| ax.set_title("Stability boundary (input-only quant, λ=0)") | |
| ax.legend(); ax.grid(True, alpha=0.3) | |
| ax = axes[1] | |
| for b in [2, 3, 4, 10]: | |
| ax.plot(out[f"b={b}"]["omegas"], out[f"b={b}"]["eps_star"], label=f"b={b}") | |
| ax.axhline(0.0, color="k", linestyle="--", label="unquantized") | |
| ax.set_xlabel(r"Input quantization range $\omega$") | |
| ax.set_ylabel(r"$\varepsilon_g^*$") | |
| ax.set_title("Steady-state generalization error (input-only quant, λ=0)") | |
| ax.legend(); ax.grid(True, alpha=0.3) | |
| fig.suptitle("Claim 3 / Figure 5: Input-only quantization stability and eps_g*") | |
| fig.tight_layout() | |
| fig.savefig(OUT / "claim3_figure5_stability.png", dpi=120) | |
| plt.close(fig) | |
| return out | |
| def run_claim3(): | |
| out5 = run_figure5_stability() | |
| out6 = run_figure6_small_eta() | |
| # also report key verification number: theory vs ODE fp max diff | |
| verify = out6["verification"] | |
| print("\n[Claim 3] Summary:") | |
| print(f" Theory vs ODE fixed-point max|diff|: {verify['max_abs_diff']:.6f}") | |
| print(f" Theory vs ODE fixed-point mean|diff|: {verify['mean_abs_diff']:.6f}") | |
| # Note: unquantized baseline eps_g* = rho + sigma2 - kappa^2 rho/(sigma2+lambda) | |
| # For rho=1, sigma2=0, lam=0: eps_g* = 1 - 1 = 0 | |
| # Quantization deviation = eps_g*(quantized) - eps_g*(unquantized) | |
| # For weight b=2, input unquant, small eta: | |
| # c = 1*1/(1+0) = 1, omega >= 1 -> c >= omega case (if omega=1, c=omega exactly -> boundary) | |
| # For omega=0.5: c=1 > omega=0.5 -> boundary case, eps* = 1 - 2*1*0.5 + 1*0.25 = 0.25 | |
| # For omega=1.5: c=1 < omega=1.5, interior | |
| # i*: largest i with v_i <= c=1 | |
| # For b=2, omega=1.5: levels = [-1.5, -0.5, 0.5, 1.5] (L+1=4, Delta=1) | |
| # i* = 2 (v_2=0.5), p = (1 - 0.5)/1 = 0.5 | |
| # eps_g0 = 1 - 2*1*1 + 1*1 = 0 | |
| # correction = 1 * 1^2 * 0.5*0.5 = 0.25 | |
| # eps* = 0 + 0.25 = 0.25 | |
| return {"figure5": out5, "figure6": out6} | |
| if __name__ == "__main__": | |
| run_claim3() | |
Xet Storage Details
- Size:
- 14.4 kB
- Xet hash:
- 91404fe76203880f21d51c94dbe5b4bdcde7c27bbed9de6f74e8939920592757
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.