threshold-computers / src /make_figures.py
phanerozoic's picture
serialize the host netlist unit by unit and instantiate each generation from the emitted bytes
0654a8d
Raw
History Blame Contribute Delete
10.7 kB
"""Emit the TikZ for the deviation-rate figure from the recorded run.
The figure plots, against the noise level, the measured probability that a step
of the levelized host deviates from the exact orbit and the union bound of the
random-perturbation proposition. Both axes are read from
paper/runs/paper_noise_curve.json.
python src/make_figures.py > /tmp/curve.tex
"""
import json
import math
import os
import sys
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def load(name):
return json.load(open(os.path.join(REPO, "paper", "runs", name)))
def group(n):
return f"{n:,}".replace(",", "{,}")
def sci(x, digits=2):
"""LaTeX scientific notation."""
if x == 0:
return "$0$"
e = math.floor(math.log10(abs(x)))
m = x / 10 ** e
if e == 0:
return f"${m:.{digits}f}$"
return rf"${m:.{digits}f}\times 10^{{{e}}}$"
def omega_table():
d = load("paper_noise_omega.json")
rows = []
for r in d["rows"]:
if r["complete"]:
outcome = "emitted the whole instance"
elif r["first_wrong_byte"] is not None:
outcome = (f"a wrong byte after {group(r['first_wrong_byte'])} "
f"correct")
elif r["halted"]:
outcome = "the halt bit was set"
else:
outcome = "the budget was exhausted"
rows.append(f"${r['sigma']:.2f}$ & {group(r['steps'])} & "
f"{group(r['bytes'])} & {outcome}\\\\")
body = "\n".join(rows)
return rf"""\begin{{table}}[ht]
\centering\small
\begin{{tabular}}{{@{{}}lrrl@{{}}}}
\toprule
$s$ & Steps executed & Bytes emitted & Outcome\\
\midrule
{body}
\bottomrule
\end{{tabular}}
\caption{{The self-reproducing instance $\Omega^\ast$ on $\Lev(\Nhost)$ with
additive Gaussian read noise of standard deviation $s$ applied independently to
every pre-activation of every layer at every step, thresholded at
$-\tfrac12$. A run ends when it emits a byte that differs from
$\ser(\Omega^\ast)$, when the halt bit is set, or when the whole instance has
been emitted. The target is {group(d['target_bytes'])} bytes.}}
\label{{tab:noise}}
\end{{table}}"""
def leak_table():
d = load("paper_noise_leak.json")
rows = []
for r in d["rows"]:
first = (f"deviates at step {group(r['first_deviation'])}"
if r["first_deviation"] else
f"exact for {group(d['steps'])} steps")
rows.append(f"{sci(r['epsilon'])} & {sci(r['gamma'])} & "
f"${r['worst_bound']:.3f}$ & "
f"{'yes' if r['certified_by_corollary'] else 'no'} & "
f"{first}\\\\")
body = "\n".join(rows)
return rf"""\begin{{table}}[ht]
\centering\small
\begin{{tabular}}{{@{{}}lllll@{{}}}}
\toprule
$\varepsilon$ & $\gamma$ & $\max_{{\ell,i}}(\varepsilon k+\gamma(n-k))$ &
Certified & Outcome\\
\midrule
{body}
\bottomrule
\end{{tabular}}
\caption{{Static weight error on $\Lev(\Nhost)$: each programmed weight is
perturbed uniformly in $[-\varepsilon,\varepsilon]$ and each zero entry
uniformly in $[-\gamma,\gamma]$, and the perturbed map is run beside the exact
one from the initial state of the self-describing instance until their states
differ. The widest layer has $n_{{\max}}={group(d['widest_layer'])}$ and the
largest effective fan-in is {d['largest_fanin']}, so
Corollary~\ref{{cor:mismatch}} certifies exactly the rows whose third column is
below $\tfrac12$.}}
\label{{tab:leak}}
\end{{table}}"""
def curve_table():
d = load("paper_noise_curve.json")
rows = []
for r in d["rows"]:
rate = (sci(r["empirical_rate_per_step"])
if r["empirical_rate_per_step"] else "none seen")
ratio = f"${r['looseness']:.1f}$" if r["looseness"] else "---"
med = (group(r["median_first_deviation"])
if r["median_first_deviation"] else "---")
rows.append(f"${r['sigma']:.2f}$ & {r['deviated']}/{r['copies']} & "
f"{med} & {group(r['step_exposure'])} & {rate} & "
f"{sci(r['bound_rate_per_step'])} & {ratio}\\\\")
body = "\n".join(rows)
return rf"""\begin{{table}}[ht]
\centering\small
\begin{{tabular}}{{@{{}}lllrllr@{{}}}}
\toprule
$s$ & Deviated & Median step & Exposure & Measured rate & Bound $Mp(s)$ &
Ratio\\
\midrule
{body}
\bottomrule
\end{{tabular}}
\caption{{The probability that one step of $\Lev(\Nhost)$ deviates from the
exact orbit under read noise of standard deviation $s$. For each $s$, one
noise-free copy and {d['rows'][0]['copies']} noisy copies were stepped together
from the initial state of the self-describing instance and the full state
compared after each step; the exposure is the number of step--copy pairs before
a first deviation. The bound is that of Proposition~\ref{{prop:random}} with
$M={group(d['M'])}$, and the last column is the ratio of the two. Below
$s=0.08$ the bound is smaller than any rate this experiment could resolve.}}
\label{{tab:curve}}
\end{{table}}"""
def speed_table():
d = load("paper_throughput.json")
T = json.load(open(os.path.join(REPO, "paper", "runs",
"paper_runs_reference.json"))
)["selfrep"]["generations"][0]["steps"]
order = [("reference", "integer reference, Definition 3.1"),
("net_cpu", "the netlist of $\\sigma$, unit by unit, one core"),
("net_gpu", "the netlist of $\\sigma$, unit by unit, GPU"),
("dense_gpu", "$\\Lev(\\Nhost)$ as dense matrices, GPU"),
("graph_gpu", "$\\Lev(\\Nhost)$ from its nonzero entries, GPU"),
("sparse_cpu", "$\\Lev(\\Nhost)$ from its nonzero entries, one core")]
rows = []
for key, what in order:
if key not in d:
continue
r = d[key]["steps_per_second"]
gen = T / r
span = (f"{gen:.1f} s" if gen < 90 else f"{gen / 60:.1f} min")
rows.append(f"{what} & {group(round(r))} & {span}\\\\")
body = "\n".join(rows)
ent = d.get("dense_gpu", {}).get("entries", 0)
nz = d.get("dense_gpu", {}).get("nonzero", 0)
bw = d.get("dense_gpu", {}).get("bandwidth_gbs", 0)
return rf"""\begin{{table}}[ht]
\centering\small
\begin{{tabular}}{{@{{}}lrr@{{}}}}
\toprule
Evaluator & Steps per second & One generation\\
\midrule
{body}
\bottomrule
\end{{tabular}}
\caption{{What one generation of $\Omega^\ast$ costs on each evaluator, measured
on an otherwise idle machine over $20{{,}}000$ steps of the self-describing
instance after a warm-up. The dense form reads all
{group(ent)} matrix entries at every step, of which {group(nz)} are nonzero,
which is {ent * 4 / 1e6:.0f}~MB and {bw:.0f}~GB/s, close to the memory bandwidth
of the accelerator. Evaluating the same map from its nonzero entries replaces
that traffic by {group(nz)} gathers and is four times faster once the level plan
is captured as a single CUDA graph, which issues one operation per step in place
of some hundreds; on one processor core, where there is no launch to amortise,
the gathers cost more than the dense arithmetic saves. The netlist carries
$50{{,}}250$ weighted predecessor entries against the {group(nz)} of its
levelization and is faster in that proportion on either processor.}}
\label{{tab:speed}}
\end{{table}}"""
def main() -> int:
if len(sys.argv) > 1 and sys.argv[1] == "speed":
print(speed_table())
return 0
if len(sys.argv) > 1 and sys.argv[1] == "tables":
print(omega_table())
print()
print(curve_table())
print()
print(leak_table())
return 0
d = json.load(open(os.path.join(REPO, "paper", "runs",
"paper_noise_curve.json")))
rows = [r for r in d["rows"]]
xs = [r["sigma"] for r in rows]
emp = [(r["sigma"], r["empirical_rate_per_step"]) for r in rows
if r["empirical_rate_per_step"] > 0]
bnd = [(r["sigma"], r["bound_rate_per_step"]) for r in rows]
lo = math.floor(min([math.log10(y) for _, y in emp]
+ [math.log10(y) for _, y in bnd if y > 0]))
hi = math.ceil(max([math.log10(y) for _, y in emp]
+ [math.log10(y) for _, y in bnd]))
x0, x1 = min(xs), max(xs)
W, H = 105.0, 55.0
def px(s):
return W * (s - x0) / (x1 - x0)
def py(v):
return H * (math.log10(v) - lo) / (hi - lo)
out = []
a = out.append
a(r"\begin{figure}[ht]")
a(r"\centering")
a(r"\begin{tikzpicture}[font=\small, x=1mm, y=1mm]")
a(rf"\draw[->,>=stealth] (-2,0) -- ({W + 6:.1f},0) "
rf"node[right,font=\footnotesize] {{$s$}};")
a(rf"\draw[->,>=stealth] (-2,-2) -- (-2,{H + 6:.1f});")
for e in range(lo, hi + 1):
y = H * (e - lo) / (hi - lo)
a(rf"\draw[black!20] (-2,{y:.2f}) -- ({W:.2f},{y:.2f});")
a(rf"\node[left,font=\scriptsize] at (-2.5,{y:.2f}) "
rf"{{$10^{{{e}}}$}};")
for s in xs:
a(rf"\draw (({px(s):.2f}),0) -- ({px(s):.2f},-1.2);")
a(rf"\node[below,font=\scriptsize] at ({px(s):.2f},-1.2) "
rf"{{${s:g}$}};")
a(rf"\node[rotate=90,font=\footnotesize] at (-12,{H / 2:.1f}) "
rf"{{probability per step}};")
pts = " -- ".join(f"({px(s):.2f},{py(v):.2f})" for s, v in bnd)
a(rf"\draw[thick,dashed] {pts};")
for s, v in bnd:
a(rf"\fill ({px(s):.2f},{py(v):.2f}) circle (0.7mm);")
pts = " -- ".join(f"({px(s):.2f},{py(v):.2f})" for s, v in emp)
a(rf"\draw[thick] {pts};")
for s, v in emp:
a(rf"\draw[fill=white,thick] ({px(s):.2f},{py(v):.2f}) "
rf"circle (0.8mm);")
a(rf"\draw[dashed,black!50] ({px(x0):.2f},{py(1.0):.2f}) -- "
rf"({W:.2f},{py(1.0):.2f});")
a(rf"\node[right,font=\scriptsize] at ({W - 30:.1f},{py(1.0) + 3:.2f}) "
rf"{{bound vacuous above here}};")
a(rf"\node[font=\scriptsize,anchor=west] at (4,{py(bnd[-1][1]) - 5:.2f}) "
rf"{{union bound $Mp(s)$}};")
a(rf"\node[font=\scriptsize,anchor=west] at (20,{py(emp[0][1]) - 5:.2f}) "
rf"{{measured}};")
a(r"\end{tikzpicture}")
a(r"\caption{The probability that one step of $\Lev(\Nhost)$ deviates from "
r"the exact orbit under additive Gaussian read noise of standard "
r"deviation $s$, measured by running noise-free and noisy copies together "
r"and comparing the full state after every step, against the union bound "
r"$Mp(s)$ of Proposition~\ref{prop:random} with $M=87{,}294$. Both curves "
r"are the same quantity, the upper one as bounded above.}")
a(r"\label{fig:curve}")
a(r"\end{figure}")
print("\n".join(out))
return 0
if __name__ == "__main__":
sys.exit(main())