Files
aare/docs/deck/make_figs.py
T

364 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Figures for ClusterFinderCUDA_optimizations.pptx — deck palette, dark, transparent."""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import FancyArrowPatch, Rectangle
from pathlib import Path
OUT = Path(__file__).parent / "figs"
OUT.mkdir(exist_ok=True)
BG = "#0B1018"
PANEL = "#121A28"
RULE = "#1E2836"
ACCENT = "#1E90C2" # data 1
AMBER = "#E8B25C" # data 2
PALE = "#E7EDF4" # data 3 / primary text
TEXT2 = "#A5B2C4"
MUTED = "#6B7A90" # non-data only: grid, axes, annotation
plt.rcParams.update({
"font.family": "DejaVu Sans", "font.size": 9,
"text.color": PALE, "axes.labelcolor": TEXT2,
"xtick.color": TEXT2, "ytick.color": TEXT2,
"axes.edgecolor": RULE, "axes.facecolor": "none",
"figure.facecolor": BG, "savefig.facecolor": BG,
"axes.grid": False, "svg.fonttype": "none",
})
def save(fig, name):
fig.savefig(OUT / f"{name}.png", dpi=220, transparent=False,
bbox_inches="tight", pad_inches=0.08)
plt.close(fig)
print("wrote", name)
def bare(ax, keep=("left", "bottom")):
for s in ("top", "right", "left", "bottom"):
ax.spines[s].set_visible(s in keep)
# ---------------------------------------------------------------- 1. the arc
def fig_arc():
steps = ["CPU MT\n48 threads", "opt1\n1 stream", "opt2\nstreams+batch",
"opt3\npipeline", "opt4\npinned", "opt5\ngraphs"]
fps = [4761, 14968, 23134, 26588, 36810, 39472]
spd = [1.0, 3.14, 4.86, 5.58, 7.73, 8.29]
colors = [MUTED] + [ACCENT] * 4 + [AMBER]
fig, ax = plt.subplots(figsize=(11.4, 3.5))
x = np.arange(len(steps))
bars = ax.bar(x, fps, width=0.62, color=colors, zorder=3)
for b in bars:
b.set_linewidth(0)
for xi, (f, s) in enumerate(zip(fps, spd)):
ax.text(xi, f + 900, f"{f:,}", ha="center", va="bottom",
color=PALE, fontsize=11, fontweight="bold")
ax.text(xi, f + 3100, ("baseline" if s == 1.0 else f"×{s:.2f}"),
ha="center", va="bottom", color=AMBER if s > 8 else TEXT2, fontsize=9)
ax.set_xticks(x)
ax.set_xticklabels(steps, fontsize=9, color=TEXT2)
ax.set_ylim(0, 47000)
ax.set_yticks([])
bare(ax, keep=("bottom",))
ax.spines["bottom"].set_color(RULE)
ax.set_ylabel("")
ax.text(0, 45500, "frames / second · 3×3 clusters, 100 000 frames, warm run",
color=MUTED, fontsize=9, ha="left")
save(fig, "fig_arc")
# ------------------------------------------------- 2. host overhead collapse
def fig_overhead():
steps = ["opt1", "opt2", "opt3", "opt4"]
ovhd = [44, 21, 14, 3]
kern = [23, 22, 24, 24]
fig, ax = plt.subplots(figsize=(5.6, 3.0))
x = np.arange(len(steps))
ax.bar(x, kern, width=0.55, color=ACCENT, zorder=3, label="kernel (GPU)")
ax.bar(x, ovhd, width=0.55, bottom=kern, color=AMBER, zorder=3,
label="host + PCIe overhead")
for xi, (k, o) in enumerate(zip(kern, ovhd)):
ax.text(xi, k + o + 1.6, f"{o} µs", ha="center", color=AMBER,
fontsize=10, fontweight="bold")
ax.set_xticks(x); ax.set_xticklabels(steps, color=TEXT2)
ax.set_ylabel("µs / frame", color=TEXT2)
ax.set_ylim(0, 78)
bare(ax)
ax.legend(frameon=False, fontsize=8.5, labelcolor=TEXT2, loc="upper right")
save(fig, "fig_overhead")
# ------------------------------------------------------ 3. streams timeline
def fig_streams():
fig, axes = plt.subplots(3, 1, figsize=(7.7, 3.9))
H, K, D = 12, 22, 12
FR = H + K + D
LANE = 0.68
def frame(ax, lane_y, t0):
ax.broken_barh([(t0, H)], (lane_y, LANE), facecolors=AMBER, zorder=3)
ax.broken_barh([(t0 + H, K)], (lane_y, LANE), facecolors=ACCENT, zorder=3)
ax.broken_barh([(t0 + H + K, D)], (lane_y, LANE), facecolors=PALE, zorder=3)
# --- opt1: one stream, strictly serial
ax = axes[0]
for i in range(3):
frame(ax, 1.0, i * FR)
ax.set_ylim(0.4, 2.3)
ax.text(3 * FR + 6, 1.34, "GPU idle between every stage", color=MUTED, fontsize=7.5,
va="center")
# --- opt2: 4 streams, barrier after each round
ax = axes[1]
ROUND = FR + 3 * 8
for r in range(2):
for st in range(4):
frame(ax, 3 - st * 1.0, r * (ROUND + 26) + st * 8)
ax.axvspan(ROUND, ROUND + 26, color=AMBER, alpha=0.13, zorder=1)
ax.text(ROUND + 13, 4.15, "barrier — GPU drains", color=AMBER, fontsize=7.5,
ha="center", va="bottom")
ax.set_ylim(-0.4, 4.9)
# --- opt3: no barriers, continuous
ax = axes[2]
for i in range(11):
frame(ax, 3 - (i % 4) * 1.0, i * 11)
ax.set_ylim(-1.5, 4.5)
ax.text(0, -0.25, "streams never wait on each other — the GPU is continuously busy",
color=ACCENT, fontsize=7.5, va="top")
titles = ["opt1 · 1 stream, synchronous",
"opt2 · 4 streams, sync barrier per round",
"opt3 · 4 streams, barriers removed"]
for ax, t in zip(axes, titles):
ax.set_xlim(-2, 190)
ax.set_yticks([]); ax.set_xticks([])
bare(ax, keep=())
ax.set_title(t, color=TEXT2, fontsize=9, loc="left", pad=4)
handles = [Rectangle((0, 0), 1, 1, color=c) for c in (AMBER, ACCENT, PALE)]
axes[0].legend(handles, ["H2D copy", "kernel", "D2H copy"], frameon=False,
fontsize=8, labelcolor=TEXT2, ncol=3, loc="lower right",
bbox_to_anchor=(1.02, 0.98), handlelength=1.1)
axes[2].set_xlabel("time →", color=MUTED, fontsize=8.5, loc="left")
fig.subplots_adjust(hspace=0.75)
save(fig, "fig_streams")
# ------------------------------------------------------------- 4. pinning
def fig_pinning():
fig = plt.figure(figsize=(7.7, 3.0))
ax = fig.add_axes([0, 0.05, 0.63, 0.95]); ax.axis("off")
ax.set_xlim(0, 10.4); ax.set_ylim(0, 6.4)
def box(x, y, w, h, label, sub=""):
ax.add_patch(Rectangle((x, y), w, h, facecolor=PANEL, edgecolor=RULE, lw=1))
ax.text(x + w / 2, y + h / 2 + 0.26, label, ha="center", va="center",
color=PALE, fontsize=8.5, fontweight="bold")
ax.text(x + w / 2, y + h / 2 - 0.34, sub, ha="center", va="center",
color=MUTED, fontsize=7)
def arrow(x0, x1, y, color, label):
ax.add_patch(FancyArrowPatch((x0, y), (x1, y), arrowstyle="-|>",
mutation_scale=10, color=color, lw=1.6))
ax.text((x0 + x1) / 2, y + 0.22, label, ha="center", va="bottom",
color=color, fontsize=7)
ax.text(0, 5.95, "PAGEABLE · before opt4", color=AMBER, fontsize=8.5,
fontweight="bold")
box(0, 4.05, 2.5, 1.1, "numpy array", "pageable")
box(4.0, 4.05, 2.4, 1.1, "driver staging", "hidden pinned buf")
box(7.9, 4.05, 2.5, 1.1, "GPU", "device memory")
arrow(2.5, 4.0, 4.60, AMBER, "memcpy")
arrow(6.4, 7.9, 4.60, AMBER, "DMA")
ax.text(0, 3.62, "every transfer is copied twice", color=MUTED, fontsize=7)
ax.text(0, 2.75, "PINNED · opt4", color=ACCENT, fontsize=8.5, fontweight="bold")
box(0, 0.85, 2.5, 1.1, "numpy array", "page-locked")
box(7.9, 0.85, 2.5, 1.1, "GPU", "device memory")
arrow(2.5, 7.9, 1.40, ACCENT, "DMA — engine reads host RAM directly")
ax.text(0, 0.42, "no staging copy, no page faults, fully async",
color=MUTED, fontsize=7)
ax2 = fig.add_axes([0.75, 0.16, 0.25, 0.66])
v = [14, 3]
ax2.bar([0, 1], v, width=0.55, color=[AMBER, ACCENT], zorder=3)
for i, val in enumerate(v):
ax2.text(i, val + 0.5, f"{val} µs", ha="center", color=PALE,
fontsize=10, fontweight="bold")
ax2.set_xticks([0, 1])
ax2.set_xticklabels(["opt3\npageable", "opt4\npinned"], color=TEXT2, fontsize=8)
ax2.set_ylim(0, 18); ax2.set_yticks([]); bare(ax2, keep=("bottom",))
ax2.set_title("host overhead / frame", color=MUTED, fontsize=7.5, pad=6)
save(fig, "fig_pinning")
# -------------------------------------------------------------- 5. graphs
def fig_graphs():
fig, ax = plt.subplots(figsize=(7.7, 2.6))
ax.axis("off"); ax.set_xlim(0, 12.6); ax.set_ylim(0, 4.6)
def node(x, y, w, h, t, fc):
ax.add_patch(Rectangle((x, y), w, h, facecolor=fc, edgecolor="none"))
ax.text(x + w / 2, y + h / 2, t, ha="center", va="center",
color=BG, fontsize=7.5, fontweight="bold")
ops = [("H2D", AMBER), ("kernel", ACCENT), ("D2H", PALE)] * 2
ax.text(0, 4.15, "WITHOUT GRAPHS · one driver call per operation, every frame",
color=AMBER, fontsize=8.5, fontweight="bold")
for i, (t, c) in enumerate(ops):
x = 0.1 + i * 1.62
node(x, 2.85, 1.4, 0.6, t, c)
ax.add_patch(FancyArrowPatch((x + 0.7, 3.72), (x + 0.7, 3.52),
arrowstyle="-|>", mutation_scale=7,
color=MUTED, lw=0.9))
ax.text(12.5, 3.15, "CPU cost\n≈ 6 launches", ha="right", va="center",
color=MUTED, fontsize=7.5)
ax.text(0, 2.18, "WITH GRAPHS · opt5 · record once, replay with one launch",
color=ACCENT, fontsize=8.5, fontweight="bold")
ax.add_patch(Rectangle((0.1, 0.72), 9.85, 1.15, facecolor=PANEL,
edgecolor=ACCENT, lw=1.2))
for i, (t, c) in enumerate(ops):
node(0.38 + i * 1.58, 0.98, 1.34, 0.6, t, c)
ax.add_patch(FancyArrowPatch((0.8, 2.02), (0.8, 1.90), arrowstyle="-|>",
mutation_scale=8, color=ACCENT, lw=1.3))
ax.text(12.5, 1.30, "CPU cost\n≈ 1 launch", ha="right", va="center",
color=ACCENT, fontsize=7.5, fontweight="bold")
ax.text(0.1, 0.32, "cudaGraphLaunch() — the whole DAG is submitted as one unit; "
"the driver already knows every dependency",
color=MUTED, fontsize=7)
save(fig, "fig_graphs")
# ------------------------------------------------ 6. f32 kernel (nsys truth)
def fig_f32_kernel():
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(7.7, 2.6),
gridspec_kw={"width_ratios": [1, 1.35]})
v = [43.0, 25.6]
ax.bar([0, 1], v, width=0.5, color=[AMBER, ACCENT], zorder=3)
for i, val in enumerate(v):
ax.text(i, val + 1.2, f"{val} µs", ha="center", color=PALE,
fontsize=11, fontweight="bold")
ax.annotate("", xy=(1, 27.5), xytext=(0, 44.5),
arrowprops=dict(arrowstyle="-|>", color=MUTED, lw=1.2,
connectionstyle="arc3,rad=-0.25"))
ax.text(0.5, 37, "40%", ha="center", color=PALE, fontsize=10,
fontweight="bold")
ax.set_xticks([0, 1]); ax.set_xticklabels(["f64 pedestal", "f32 pedestal"],
color=TEXT2, fontsize=8.5)
ax.set_ylim(0, 52); ax.set_yticks([]); bare(ax, keep=("bottom",))
ax.set_title("kernel, exclusive (nsys, 9×9)", color=MUTED, fontsize=8, pad=8)
labels = ["kernel", "D2H", "H2D"]
f64 = [43.0, 19.4, 13.2]
f32 = [25.6, 19.8, 13.5]
y = np.arange(3); h = 0.35
ax2.barh(y + h / 2, f64, height=h, color=AMBER, zorder=3, label="f64 ped")
ax2.barh(y - h / 2, f32, height=h, color=ACCENT, zorder=3, label="f32 ped")
for yi, (a, b) in enumerate(zip(f64, f32)):
ax2.text(a + 1, yi + h / 2, f"{a:.1f}", va="center", color=TEXT2, fontsize=8)
ax2.text(b + 1, yi - h / 2, f"{b:.1f}", va="center", color=TEXT2, fontsize=8)
ax2.set_yticks(y); ax2.set_yticklabels(labels, color=TEXT2, fontsize=8.5)
ax2.invert_yaxis(); ax2.set_xlim(0, 56); ax2.set_xticks([])
bare(ax2, keep=("left",))
ax2.legend(frameon=False, fontsize=8, labelcolor=TEXT2, loc="lower right")
ax2.set_title("per-frame GPU operations (µs)", color=MUTED, fontsize=8, pad=8)
save(fig, "fig_f32_kernel")
# ------------------------------------------------------- 7. cancellation
def fig_cancellation():
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(7.7, 2.7),
gridspec_kw={"width_ratios": [1.25, 1]})
names = ["E[X²]\n2.17e7", "mean²\n2.17e7", "variance\n2025"]
vals = [2.17e7, 2.17e7, 2025]
ax.bar([0, 1], vals[:2], width=0.5, color=[PALE, PALE], zorder=3)
ax.bar([2], [2025], width=0.5, color=AMBER, zorder=3)
ax.set_yscale("log"); ax.set_ylim(1e2, 2e8)
ax.set_xticks([0, 1, 2]); ax.set_xticklabels(names, color=TEXT2, fontsize=8)
ax.set_yticks([1e3, 1e5, 1e7])
ax.axhline(2048, color=ACCENT, lw=1.3, ls="--", zorder=4)
ax.text(2.42, 3000, "f32 rounding step\nat 2.17e7 = 2048", color=ACCENT,
fontsize=7.5, ha="right", va="bottom")
bare(ax)
ax.set_title("var = E[X²] mean² (f32, mean ≈ 4655 ADU)",
color=MUTED, fontsize=8, pad=8)
rms = np.linspace(0, 12, 200)
ax2.fill_between(rms, 0, np.where(rms < 6.5, 1, 0), color=AMBER, alpha=0.16,
step="pre")
ax2.plot(rms, rms**2, color=PALE, lw=1.8, label="true variance")
ax2.axhline(42, color=ACCENT, lw=1.4, ls="--", label="f32 error floor")
ax2.set_xlabel("pixel rms (ADU)", color=TEXT2, fontsize=8.5)
ax2.set_ylabel("variance", color=TEXT2, fontsize=8.5)
ax2.set_ylim(0, 150); ax2.set_xlim(0, 12)
ax2.set_yticks([]); ax2.tick_params(labelsize=8)
bare(ax2)
ax2.text(1.0, 108, "quiet pixels:\nerror > variance\n→ rms clamped to 0\n→ fires every frame",
color=AMBER, fontsize=7.5, va="top")
ax2.legend(frameon=False, fontsize=7.5, labelcolor=TEXT2, loc="lower right")
save(fig, "fig_cancellation")
# ------------------------------------------------- 8. where f32 pays or not
def fig_bottleneck():
fig, (a1, a2) = plt.subplots(1, 2, figsize=(11.4, 3.0))
for ax, title, kern, floor, gain in [
(a1, "3×3 clusters — pipeline-bound", (24, 13), 25,
"kernel already hidden → 0% end-to-end"),
(a2, "9×9 clusters — kernel-bound (f64)", (43, 26), 32,
"kernel on the critical path → 8% wall"),
]:
x = [0, 1]
ax.bar(x, kern, width=0.5, color=[AMBER, ACCENT], zorder=3)
ax.axhline(floor, color=PALE, lw=1.4, ls="--", zorder=4)
ax.text(1.62, floor + 1.2, "transfer + host floor", color=PALE,
fontsize=8, ha="right")
for i, v in enumerate(kern):
ax.text(i, v + 1.2, f"{v} µs", ha="center", color=PALE,
fontsize=10, fontweight="bold")
ax.set_xticks(x); ax.set_xticklabels(["f64 pedestal", "f32 pedestal"],
color=TEXT2, fontsize=9)
ax.set_xlim(-0.6, 1.7); ax.set_ylim(0, 55); ax.set_yticks([])
bare(ax, keep=("bottom",))
ax.set_title(title, color=PALE, fontsize=9.5, pad=10)
ax.text(-0.55, -9, gain, color=AMBER if "0%" in gain else ACCENT,
fontsize=8.5, fontweight="bold")
fig.subplots_adjust(bottom=0.22)
save(fig, "fig_bottleneck")
# ----------------------------------------------------------- 9. correctness
def fig_correctness():
fig, ax = plt.subplots(figsize=(7.4, 2.4))
names = ["CPU MT", "opt1", "opt2", "opt3", "opt4", "opt5", "opt6 (f32)"]
diff = [0.0, 0.0040, 0.0035, 0.0035, 0.0035, 0.0035, 0.0039]
colors = [MUTED] + [ACCENT] * 5 + [AMBER]
x = np.arange(len(names))
ax.bar(x, diff, width=0.55, color=colors, zorder=3)
for xi, d in enumerate(diff):
ax.text(xi, d + 0.00022, ("reference" if d == 0 else f"{d:.4f}%"),
ha="center", color=PALE if d else MUTED, fontsize=8.5)
ax.axhline(0.01, color=PALE, lw=1.2, ls="--")
ax.text(6.4, 0.0104, "0.01% — well inside statistical noise", color=PALE,
fontsize=8, ha="right")
ax.set_xticks(x); ax.set_xticklabels(names, color=TEXT2, fontsize=8.5)
ax.set_ylim(0, 0.0125); ax.set_yticks([])
bare(ax, keep=("bottom",))
ax.set_title("cluster-count difference vs CPU · 233 million clusters, 3×3",
color=MUTED, fontsize=8.5, pad=8)
save(fig, "fig_correctness")
for f in (fig_arc, fig_overhead, fig_streams, fig_pinning, fig_graphs,
fig_f32_kernel, fig_cancellation, fig_bottleneck, fig_correctness):
f()
print("done ->", OUT)