diff options
| author | Yuren Hao <yurenh2@illinois.edu> | 2026-07-15 10:29:06 -0500 |
|---|---|---|
| committer | Yuren Hao <yurenh2@illinois.edu> | 2026-07-15 10:29:06 -0500 |
| commit | 82da86dbd49a093a24e0827331667e8e9e6217df (patch) | |
| tree | 96c40877ab6888d66c6e7992a8d6bfebb0453ba2 /ep_run | |
| parent | bf83f39828fb79428fe65fe0fef49768351afc05 (diff) | |
Alexi primer deck: 10 slides (arch op-by-op, EP phases, schedule/parallelism, 72M blowup from real logs, loop-gain + beta-window hypotheses, diagnostics table, in-flight outcomes, $50k ladder) + figure scripts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FAPDWQ49M5Ye3NpTndTpn
Diffstat (limited to 'ep_run')
| -rw-r--r-- | ep_run/build_alexi_deck.py | 159 | ||||
| -rw-r--r-- | ep_run/fig_primer_arch.py | 135 | ||||
| -rw-r--r-- | ep_run/fig_primer_block.py | 148 | ||||
| -rw-r--r-- | ep_run/fig_primer_pack2.py | 201 | ||||
| -rw-r--r-- | ep_run/fig_primer_phases.py | 99 |
5 files changed, 742 insertions, 0 deletions
diff --git a/ep_run/build_alexi_deck.py b/ep_run/build_alexi_deck.py new file mode 100644 index 0000000..461b8fb --- /dev/null +++ b/ep_run/build_alexi_deck.py @@ -0,0 +1,159 @@ +"""Alexi technical-primer deck: architecture -> EP update -> schedule -> problems -> +hypotheses -> diagnostics -> likely outcomes -> $50k plan. 16:9, minimal styling.""" +from pptx import Presentation +from pptx.util import Inches, Pt +from pptx.dml.color import RGBColor +from pptx.enum.text import PP_ALIGN + +INK = RGBColor(0x3a, 0x3a, 0x3a); GRAY = RGBColor(0x8a, 0x8a, 0x8a) +BLUE = RGBColor(0x2c, 0x6f, 0xbb); ORAN = RGBColor(0xd9, 0x5f, 0x02) +RED = RGBColor(0xb0, 0x3a, 0x2e); GREEN = RGBColor(0x2e, 0x7d, 0x32) +A = '/home/yurenh2/ept/assets/' + +prs = Presentation() +prs.slide_width = Inches(13.333); prs.slide_height = Inches(7.5) +BLANK = prs.slide_layouts[6] + +def slide(): + return prs.slides.add_slide(BLANK) + +def tbox(s, x, y, w, h): + tb = s.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) + tb.text_frame.word_wrap = True + return tb.text_frame + +def title(s, text, color=INK): + tf = tbox(s, 0.45, 0.22, 12.5, 0.75) + p = tf.paragraphs[0]; r = p.add_run(); r.text = text + r.font.size = Pt(25); r.font.bold = True; r.font.color.rgb = color + +def para(tf, text, size=13, color=INK, bold=False, before=6, bullet=True, first=False): + p = tf.paragraphs[0] if first else tf.add_paragraph() + p.space_before = Pt(0 if first else before) + r = p.add_run(); r.text = ('• ' if bullet else '') + text + r.font.size = Pt(size); r.font.color.rgb = color; r.font.bold = bold + return p + +# ---------- 1 cover ---------- +s = slide() +tf = tbox(s, 0.9, 2.35, 11.5, 2.6) +p = tf.paragraphs[0]; r = p.add_run() +r.text = 'Training transformer LMs without backpropagation' +r.font.size = Pt(34); r.font.bold = True; r.font.color.rgb = INK +p2 = tf.add_paragraph(); p2.space_before = Pt(14); r = p2.add_run() +r.text = 'Standard architecture at inference · equilibrium-propagation gradients in training · GPU now, analog next' +r.font.size = Pt(17); r.font.color.rgb = GRAY +p3 = tf.add_paragraph(); p3.space_before = Pt(30); r = p3.add_run() +r.text = 'Yuren Hao · July 2026 · technical primer' +r.font.size = Pt(14); r.font.color.rgb = INK + +# ---------- 2 architecture ---------- +s = slide(); title(s, 'The model is a stock OLMo2 decoder — op by op') +s.shapes.add_picture(A + 'fig_primer_block.png', Inches(0.5), Inches(1.0), height=Inches(6.3)) +tf = tbox(s, 5.6, 1.35, 7.3, 5.8) +para(tf, 'Decoder-only transformer: L12, C512, H8, T256. RMSNorm, RoPE, QK-norm, SwiGLU, untied readout. No biases.', 14, first=True) +para(tf, 'Two trained instances: 42.75M (TinyStories, 4k BPE) and 72.11M (FineWeb-Edu, 32k BPE).', 14) +para(tf, 'Nothing in the inference graph is modified for our training method — the checkpoint is indistinguishable in form from a conventionally trained model.', 14, bold=True) +para(tf, 'Color code, kept through the whole deck: blue = weight×activation matmuls (7 logical — crossbar-mappable on analog hardware); purple = activation×activation (qkᵀ, Av — must be computed on the fly); cream = RMSNorm; gray = elementwise.', 13, GRAY) +para(tf, 'One honest architecture–method coupling: QK-norm and the final RMSNorm bound internal state scales, which the training estimator relies on. Both are stock components.', 13, GRAY) + +# ---------- 3 EP phases ---------- +s = slide(); title(s, 'Training = two settles and a difference measurement') +s.shapes.add_picture(A + 'fig_primer_phases.png', Inches(0.35), Inches(1.05), height=Inches(6.1)) +tf = tbox(s, 7.35, 1.5, 5.6, 5.6) +para(tf, 'The stack is treated as a system that settles: each layer gets a state zₗ, and the energy penalizes disagreement between zₗ and fₗ(zₗ₋₁).', 14, first=True) +para(tf, 'Free phase: settle with no label. The exact minimum IS the ordinary forward pass (E = 0). Inference is untouched.', 14) +para(tf, 'Nudged phase: weights frozen, states move — the loss pulls the top state with strength β, the whole stack re-settles, every layer picks up a mismatch dₗ.', 14) +para(tf, 'Update: states frozen, weights move — each layer absorbs its own mismatch: Δθₗ ∝ ⟨dₗ, ∂fₗ/∂θₗ⟩/β. Local quantities only; no global tape.', 14) +para(tf, 'The nudged state becomes the new free state: today’s pulled answer is tomorrow’s natural forward output.', 14, ORAN, bold=True) + +# ---------- 4 schedule ---------- +s = slide(); title(s, 'The update schedule: local, pipelinable — and native to physics') +s.shapes.add_picture(A + 'fig_p3_schedule.png', Inches(0.45), Inches(1.15), width=Inches(12.4)) +tf = tbox(s, 0.7, 6.55, 12.0, 0.8) +para(tf, 'Everything that follows is the price of emulating this settle with discrete sweeps on a GPU. Two problems fall out — the physical system has neither.', 14, RED, bold=True, first=True, bullet=False) + +# ---------- 5 what happened ---------- +s = slide(); title(s, 'What actually happened at 72M (real run, real logs)') +s.shapes.add_picture(A + 'fig_p4_blowup.png', Inches(0.4), Inches(1.1), height=Inches(5.5)) +tf = tbox(s, 9.05, 1.35, 4.0, 5.9) +para(tf, 'Same β = 1e-3: safe for 135k steps, lethal at 195k. Something moved under the schedule.', 13.5, RED, bold=True, first=True) +para(tf, 'Guards contained it (15k step-skips, no NaN), a governor finished the token budget — but β starved to 2e-5 and best never improved after 185k.', 13) +para(tf, 'Headline stands: 72.11M × 1.44B tokens fully BP-free, best 3.7117 vs BP twin 3.2884.', 13) +para(tf, 'The 0.43 gap is a BLOWN-SCHEDULE number, not a method number. The honest rerun is in flight (last slide but one).', 13, bold=True) + +# ---------- 6 problem 1 ---------- +s = slide(); title(s, 'Problem 1 — the nudge closes a loop; discrete sweeps can amplify it') +s.shapes.add_picture(A + 'fig_p5_loop.png', Inches(1.35), Inches(1.15), height=Inches(5.6)) +tf = tbox(s, 0.7, 6.85, 12.0, 0.55) +para(tf, 'Why the SAME β became lethal: ρ ≈ β·σ(Wₒᵤₜ)²·‖J‖² crossed 1 as σ and ‖J‖ grew with fit depth.', 14, first=True, bullet=False) + +# ---------- 7 problem 2 ---------- +s = slide(); title(s, 'Problem 2 — β is squeezed from both sides, and the window narrows') +s.shapes.add_picture(A + 'fig_p6_window.png', Inches(0.4), Inches(1.1), height=Inches(5.5)) +tf = tbox(s, 9.05, 1.3, 4.0, 6.0) +para(tf, 'Floor is real and RISING: on the 42M testbed, tail at flat 1e-3 beats decaying to 3e-4 (1.2678 vs 1.2808). Momentum as a substitute for β refuted in 3 doses.', 13, first=True) +para(tf, 'Ceiling is real and FALLING: measured crossings at 195k (≤1e-3), 222k (≤3e-4), late (~2e-5).', 13) +para(tf, 'Window-aware recipe: centered ±β estimator (bias O(β²) → ride high) + a loop-gain governor capping β near ITS ceiling.', 13, bold=True) +para(tf, 'fw72m_cent, in flight, is exactly this recipe.', 13, ORAN, bold=True) + +# ---------- 8 diagnostics ---------- +s = slide(); title(s, 'Diagnostics already done (every number is a run you can open in wandb)') +rows = [ + ('DDP gradient equivalence', 'cos 0.999999999 vs single-GPU big batch (manual all-reduce, synced guards)'), + ('centered + bf16 + DDP', 'cos 1.000000000 vs sequential centered; packed ±β variant identical (relerr 2e-5)'), + ('Error-source decomposition', 'within-block read EXACT (1.0000); ALL bias sits in between-block transmission (0.9987 = the whole EP deficit); K=1 degenerates to BP; K3 = K8 (converged, not truncated)'), + ('Wall-2 probes — 6 arms @72M', 'damping refuted in 3 doses; lowering β sails through; governor completes the budget'), + ('Tail-SNR arms — 7 @42M', 'centered@3e-3 wins CE and cos; ĝ-momentum refuted ×3; Richardson dominated; AdamW = tie'), + ('Gap-vs-size ladder — 8 runs, pre-registered', 'gap tracks FIT DEPTH, not width: 0.004 / 0.004 / 0.015 / 0.013 / 0.050 (C128→C512); C128 has gate cos 0.805 yet ZERO gap — direction noise alone costs nothing until the fit is deep'), + ('bf16 mixed precision', 'epoch-validated lossless (Δ +0.006), 1.56× wall-clock'), + ('Cost of centered', '1.72× step time (1.64× packed); tail-only switching amortizes to ~1.13×'), +] +tbl = s.shapes.add_table(len(rows) + 1, 2, Inches(0.45), Inches(1.15), Inches(12.45), Inches(5.9)).table +tbl.columns[0].width = Inches(3.6); tbl.columns[1].width = Inches(8.85) +hdr = ('diagnostic', 'verdict') +for j in range(2): + c = tbl.cell(0, j); c.text = hdr[j] + c.text_frame.paragraphs[0].runs[0].font.size = Pt(12) + c.text_frame.paragraphs[0].runs[0].font.bold = True +for i, (a, b) in enumerate(rows): + for j, t in enumerate((a, b)): + c = tbl.cell(i + 1, j); c.text = t + r = c.text_frame.paragraphs[0].runs[0] + r.font.size = Pt(10.5); r.font.bold = (j == 0) + r.font.color.rgb = INK + +# ---------- 9 likely outcomes ---------- +s = slide(); title(s, 'In flight right now — and what the outcomes would mean') +tf = tbox(s, 0.55, 1.2, 12.4, 6.0) +para(tf, 'stage1b_cent — 42M TinyStories, centered for the FULL epoch (at 30k/58.8k: best 1.3642, ahead of both originals at matched step).', 14, bold=True, first=True) +para(tf, 'reads: best ≤ ~1.24 ⇒ the 0.050 gap was a noise-recipe account and the zero-gap line extends to C512 · 1.25–1.26 ⇒ a residual needs window management · >1.27 ⇒ an unknown term (falsifier).', 13, GRAY) +para(tf, 'fw72m_cent — 72M FineWeb, the window-aware recipe from scratch (at 48k/234k: best 3.6890 — already below the original’s FULL-RUN best 3.7117, at 20% of the budget).', 14, bold=True, before=14) +para(tf, 'registered predictions: no 195k-style blow; honest gap vs BP twin lands 0.25–0.35 (current trajectory suggests better).', 13, GRAY) +para(tf, 'If both hold: the EP–BP gap is an ENGINEERING account — schedule + estimator — not an architectural tax. The frontier is late-phase signal-to-noise, and we hold validated levers for it.', 14, GREEN, bold=True, before=16) +para(tf, 'Queued next: 3 seeds at C256/C512 (certify the ladder tiers); the est-switch rule (when to start paying for centered); 300M × 6B.', 13, before=14) + +# ---------- 10 the $50k ---------- +s = slide(); title(s, 'How to spend the $50k — a laddered plan with kill-gates') +rows = [ + ('rung', 'est. cost', 'what it buys', 'gate to the next rung'), + ('300M × 6B tokens (rented H100, days)', '~$0.3k', 'recipe + window governor at 4× params; DDP at scale', 'gap & stability hold'), + ('1B × 10B', '~$5k', 'first B-class BP-free LM; the LAST affordable BP twin', 'gap ≤ target at 1B'), + ('7B × 20B (flagship)', '~$30k', 'headline result; compare to published baselines (no twin)', '—'), + ('reserve', '$10–15k', 'seeds, reruns, ablations, surprises', '—'), +] +tbl = s.shapes.add_table(len(rows), 4, Inches(0.45), Inches(1.25), Inches(12.45), Inches(4.2)).table +widths = (3.6, 1.3, 4.6, 2.95) +for j, w in enumerate(widths): tbl.columns[j].width = Inches(w) +for i, row in enumerate(rows): + for j, t in enumerate(row): + c = tbl.cell(i, j); c.text = t + r = c.text_frame.paragraphs[0].runs[0] + r.font.size = Pt(12 if i == 0 else 12.5); r.font.bold = (i == 0 or j == 0) + r.font.color.rgb = INK +tf = tbox(s, 0.55, 5.9, 12.4, 1.3) +para(tf, 'Prices (sourced, July 2026): market H100 $1.87–2.99/GPU·h; AWS p5e $4.97/GPU·h. All estimates include bf16 (1.56×) and EP ≈ 3.2× BP wall-clock; tail-only centered adds ~1.13×.', 12, GRAY, first=True, bullet=False) +para(tf, 'Alternative one-shot: 3B-Chinchilla ≈ $40k fits the envelope alone — but the ladder buys three publishable points and de-risks the flagship.', 12, GRAY, bullet=False) + +prs.save(A + 'alexi_primer_deck.pptx') +print('deck saved:', A + 'alexi_primer_deck.pptx') diff --git a/ep_run/fig_primer_arch.py b/ep_run/fig_primer_arch.py new file mode 100644 index 0000000..1090f45 --- /dev/null +++ b/ep_run/fig_primer_arch.py @@ -0,0 +1,135 @@ +"""Primer figure 1: stock architecture + two-phase gradient measurement + PC contrast. +Vector PDF + PNG preview to ../assets/. Restrained style, no glow.""" +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +plt.rcParams['svg.fonttype'] = 'none' # keep text editable in PPT +from matplotlib.patches import FancyBboxPatch + +INK = '#3a3a3a' +GRAY = '#8a8a8a' +BOXF = '#f4f4f4' +BOXE = '#9a9a9a' +BLUE = '#2c6fbb' +BLUEF = '#eaf1fa' +ORAN = '#d95f02' +RED = '#b03a2e' +CREAM = '#faf6ee' +CREME = '#c9b895' + +fig = plt.figure(figsize=(13.2, 10.0)) +ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, 100); ax.set_ylim(0, 105); ax.axis('off') + +def box(x, y, w, h, fc=BOXF, ec=BOXE, lw=1.0, r=0.6): + b = FancyBboxPatch((x, y), w, h, boxstyle=f'round,pad=0.25,rounding_size={r}', + fc=fc, ec=ec, lw=lw) + ax.add_patch(b); return b + +def txt(x, y, s, size=8.5, color=INK, ha='center', va='center', w='normal', style='normal'): + ax.text(x, y, s, fontsize=size, color=color, ha=ha, va=va, + fontweight=w, fontstyle=style) + +def arrow(x0, y0, x1, y1, color=INK, lw=1.2, style='-|>', ms=11): + ax.annotate('', xy=(x1, y1), xytext=(x0, y0), + arrowprops=dict(arrowstyle=style, color=color, lw=lw, + mutation_scale=ms, shrinkA=0.5, shrinkB=0.5)) + +# ============================= (A) the model ============================= +txt(16, 102.3, '(A) The model — a stock OLMo2-style decoder', 10.5, w='bold', ha='center') +txt(16, 99.6, 'Inference is one ordinary forward pass.', 8.5, GRAY) + +cx = 14 +txt(cx, 93.5, 'logits', 9.5, w='bold') +arrow(cx, 88.6, cx, 91.8) +box(cx - 10, 84.6, 20, 4.0); txt(cx, 86.6, r'linear readout $W_{\mathrm{out}}$ (untied)', 8.5) +arrow(cx, 81.0, cx, 84.2) +box(cx - 10, 77.0, 20, 4.0); txt(cx, 79.0, 'final RMSNorm', 8.5) +arrow(cx, 71.6, cx, 76.6) + +box(cx - 12.5, 53.0, 25, 18.6, fc='#eeeeee', ec='#777777', lw=1.3) +txt(cx, 68.4, r'transformer block ($=f_l$) $\times\,12$', 9, w='bold') +txt(cx, 63.9, r'$h\ =\ z + \mathrm{RMSNorm}(\,\mathrm{Attn}(z)\,)$', 9) +txt(cx, 59.4, r"$z'\ =\ h + \mathrm{RMSNorm}(\,\mathrm{SwiGLU}(h)\,)$", 9) +txt(cx, 55.3, 'causal SDPA · QK-RMSNorm · RoPE', 7.5, GRAY) + +arrow(cx, 48.0, cx, 52.6) +box(cx - 10, 44.0, 20, 4.0); txt(cx, 46.0, 'token embedding', 8.5) + +txt(16, 40.0, '42.75M (TinyStories 4k BPE) / 72.11M (FineWeb-Edu 32k BPE)', 7.8, GRAY) +txt(16, 36.6, 'Nothing in this graph is modified for training: the trained checkpoint\n' + 'is indistinguishable in form from a conventionally trained model.', 8.2) + +# ======================== (B) free phase ======================== +bx = 47 +txt(bx, 102.3, '(B) Free phase (= inference)', 10.5, w='bold') +txt(bx, 99.6, 'give each layer a state $z_l$ (3 of $L$ drawn); settle the disagreement energy', 8.5, GRAY) +txt(bx, 95.9, r'$E(z)\ =\ \sum_l\ \frac{1}{2}\,\|\,z_l - f_l(z_{l-1})\,\|^2$', 10.5) + +def state_col(x0, ys, labels, notes, fc=BLUEF, ec=BLUE, note_c=GRAY): + for y, lab, note in zip(ys, labels, notes): + box(x0 - 9.5, y - 2.0, 19, 4.0, fc=fc, ec=ec, lw=1.1) + txt(x0, y, lab, 9, color=INK) + if note: txt(x0 + 11.0, y, note, 7.8, note_c, ha='left') + for ya, yb in zip(ys[1:], ys[:-1]): + arrow(x0, ya + 2.5, x0, yb - 2.6, lw=1.1) + +ys = [86, 74, 62] +state_col(bx - 6, ys, [r'$z_3 = f_3(z_2)$', r'$z_2 = f_2(z_1)$', r'$z_1 = f_1(\mathrm{emb})$'], + ['term = 0', 'term = 0', 'term = 0']) +arrow(bx - 6, 53.6, bx - 6, 59.0, lw=1.1) +txt(bx - 6, 51.5, r'$\mathrm{emb}(x)$', 9) + +txt(bx, 44.6, 'The minimum is exact: $E=0$, states $\\equiv$ forward activations,', 8.4) +txt(bx, 41.6, 'and one bottom-up pass reaches it.', 8.4) +txt(bx, 37.6, 'The free phase adds nothing and changes nothing at inference.', 8.2, GRAY) + +# ======================== (C) nudged phase ======================== +nx = 81 +txt(nx, 102.3, '(C) Nudged phase (training only)', 10.5, w='bold') +txt(nx, 99.6, r'add the loss at strength $\beta \ll 1$ and settle again:', 8.5, GRAY) +txt(nx, 95.9, r'$E(z)\ +\ \beta\cdot\mathrm{CE}(\mathrm{logits}(z_3),\,y)$', 10.5) + +txt(nx + 2.0, 92.3, r'pull $-\beta\,\nabla\mathrm{CE}$', 8.6, RED, ha='left') +txt(nx + 2.0, 90.0, '(the only place the label enters)', 7.3, RED, ha='left') +arrow(nx + 1.0, 91.4, nx - 4.0, 88.6, color=RED, lw=1.5) + +nys = [86, 74, 62] +state_col(nx - 6, nys, + [r'$z_3^{\beta} = z_3 + d_3$', r'$z_2^{\beta} = z_2 + d_2$', r'$z_1^{\beta} = z_1 + d_1$'], + [None, None, None], fc='#fdeee2', ec=ORAN) +arrow(nx - 6, 53.6, nx - 6, 59.0, lw=1.1) +txt(nx - 6, 51.5, r'$\mathrm{emb}(x)$', 9) + +for (ya, yb, lab) in [(84.0, 76.6, r'$d_2 = J_3^{\top} d_3$'), + (72.0, 64.6, r'$d_1 = J_2^{\top} d_2$')]: + arrow(nx + 5.4, ya, nx + 5.4, yb, color=ORAN, lw=1.5) + txt(nx + 7.0, (ya + yb) / 2, lab, 8.6, ORAN, ha='left') + +txt(nx, 44.6, 'The top state is pulled toward lower loss; each layer\'s mismatch $d_l$', 8.4) +txt(nx, 41.6, 'transmits DOWN through the same weights, and the stack re-settles.', 8.4) +txt(nx, 37.6, r'($J^{\top}$ = the transpose read a bidirectional physical device provides)', 8.2, GRAY) + +# ======================== (D) the update ======================== +box(2, 20.5, 96, 13.5, fc=CREAM, ec=CREME, lw=1.2) +txt(4, 31.6, '(D) The update — a difference measurement between the two settled states', 10, w='bold', ha='left') +txt(50, 27.9, r'$\hat{g}\ =\ \left[\ \partial_\theta E(z^{\beta})\ -\ \partial_\theta E(z^{0})\ \right]\,/\,\beta$' + r'$\qquad\qquad(\partial_\theta E(z^0)\equiv 0\ \mathrm{here,\ since}\ E=0)$', 10.5) +txt(50, 24.5, r'per layer: $\Delta\theta_l\ \propto\ \langle\ d_l\ ,\ \partial f_l(z_{l-1})/\partial\theta_l\ \rangle\ /\ \beta$', 9.5) +txt(50, 21.9, r'Each layer updates from its own boundary mismatch — no global backward graph, no global tape, no loss' + '\n' + r'derivatives except the top nudge. $\beta\to 0$ gives the exact gradient; bias is $O(\beta)$; a $\pm\beta$ two-sided read cancels it to $O(\beta^2)$.', + 8.2) + +# ======================== (E) not PC ======================== +box(2, 2.0, 96, 16.2, fc='#f7f7f7', ec=BOXE, lw=1.1) +txt(4, 15.7, '(E) This is not predictive coding — same energy family, different measurement', 10, w='bold', ha='left') +txt(4.5, 12.4, '· PC (as typically run): ONE settled phase with the target clamped hard; the update uses the raw errors of that single state → finite-clamp', 8.2, ha='left') +txt(4.5, 10.1, ' bias. Its "exact-BP" results require freezing predictions during error transport (fixed-prediction) — backprop re-expressed in local variables.', 8.2, ha='left') +txt(4.5, 7.5, '· EP (here): TWO phases and an infinitesimal nudge; the update is a difference quotient in β → bias is measured and controllable, and the', 8.2, ha='left') +txt(4.5, 5.2, ' settle stays fully self-consistent — which is what physical hardware actually does.', 8.2, ha='left') +txt(4.5, 3.0, '· The free phase is the zero-reference of the measurement: on analog hardware the subtraction cancels state-independent device offsets.', 8.2, ha='left') + +fig.savefig('/home/yurenh2/ept/assets/fig_primer_arch.pdf') +fig.savefig('/home/yurenh2/ept/assets/fig_primer_arch.png', dpi=190) +fig.savefig('/home/yurenh2/ept/assets/fig_primer_arch.svg') +print('saved fig_primer_arch.{pdf,png,svg}') diff --git a/ep_run/fig_primer_block.py b/ep_run/fig_primer_block.py new file mode 100644 index 0000000..ba3b5d0 --- /dev/null +++ b/ep_run/fig_primer_block.py @@ -0,0 +1,148 @@ +"""Primer figure 2: one OLMo2 block at op granularity (companion to fig_primer_arch). +Color semantics: weight-matmuls (crossbar-mappable) vs activation-activation matmuls +vs norms vs elementwise. Vector PDF + PNG to ../assets/.""" +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +plt.rcParams['svg.fonttype'] = 'none' # keep text editable in PPT +from matplotlib.patches import FancyBboxPatch, Circle + +INK = '#3a3a3a' +GRAY = '#8a8a8a' +BOXF = '#f4f4f4' +BOXE = '#9a9a9a' +BLUE = '#2c6fbb' +BLUEF = '#eaf1fa' +PURP = '#7b5aa6' +PURPF = '#f1ebf8' +CREAM = '#faf6ee' +CREME = '#c9b895' +RAIL = '#555555' + +fig = plt.figure(figsize=(10.0, 14.2)) +ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, 100); ax.set_ylim(0, 142); ax.axis('off') + +def box(xc, yc, w, s, fc=BOXF, ec=BOXE, size=8.6, lw=1.1, h=3.2, tc=INK): + b = FancyBboxPatch((xc - w / 2, yc - h / 2), w, h, + boxstyle='round,pad=0.25,rounding_size=0.55', fc=fc, ec=ec, lw=lw) + ax.add_patch(b) + ax.text(xc, yc, s, fontsize=size, color=tc, ha='center', va='center') + +def txt(x, y, s, size=8.5, color=INK, ha='center', va='center', w='normal', style='normal', rot=0): + ax.text(x, y, s, fontsize=size, color=color, ha=ha, va=va, + fontweight=w, fontstyle=style, rotation=rot) + +def arrow(x0, y0, x1, y1, color=INK, lw=1.15, ms=10): + ax.annotate('', xy=(x1, y1), xytext=(x0, y0), + arrowprops=dict(arrowstyle='-|>', color=color, lw=lw, + mutation_scale=ms, shrinkA=0.4, shrinkB=0.4)) + +def rail(pts, color=RAIL, lw=1.4): + xs, ys = zip(*pts) + ax.plot(xs, ys, color=color, lw=lw, solid_capstyle='round', zorder=1) + +# ---------------- title ---------------- +txt(50, 138.8, 'One transformer block, op by op', 12.5, w='bold') +txt(50, 135.9, 'OLMo2 ordering — RMSNorm after each sublayer, inside the residual.' + ' $C{=}512$ $H{=}8$ $h_d{=}64$ $T{=}256$ $h_{ff}{=}1408$', 8.8, GRAY) + +# ---------------- attention sublayer ---------------- +txt(3.2, 100, 'attention sublayer', 9.5, GRAY, style='italic', rot=90) + +txt(40, 131.6, r'input $z$ $(B,T,C)$', 9.5, w='bold') +rail([(40, 130.4), (88, 130.4), (88, 71.8)]); arrow(88, 72.4, 42.0, 71.8, color=RAIL, lw=1.4) +txt(90.2, 101, 'residual rail', 8, GRAY, rot=90) + +arrow(40, 130.2, 40, 128.6) +box(40, 126.9, 30, r'matmul $W_{qkv}: C \to 3C$', fc=BLUEF, ec=BLUE) +txt(56.5, 126.9, r'$(B,T,3C)$ — split into $q,k,v$', 7.6, GRAY, ha='left') + +qx, kx, vx = 16, 40, 64 +for x, s in [(qx, '$q$'), (kx, '$k$'), (vx, '$v$')]: + txt(x, 123.3, s, 9.5, w='bold') +arrow(37, 125.3, qx + 1, 122.5); arrow(40, 125.3, kx, 122.5); arrow(43, 125.3, vx - 1, 122.5) + +box(qx, 120.9, 17.5, r'RMSNorm$_q$', fc=CREAM, ec=CREME) +box(kx, 120.9, 17.5, r'RMSNorm$_k$', fc=CREAM, ec=CREME) +box(qx, 116.2, 17.5, 'split heads ($H{=}8$)') +box(kx, 116.2, 17.5, 'split heads ($H{=}8$)') +box(vx, 116.2, 17.5, 'split heads ($H{=}8$)') +box(qx, 111.5, 17.5, 'RoPE') +box(kx, 111.5, 17.5, 'RoPE') +txt(50.2, 111.5, r'$\theta = 5{\cdot}10^5$', 7.6, GRAY, ha='left') +for x in (qx, kx): + arrow(x, 119.3, x, 117.8); arrow(x, 114.6, x, 113.1) +arrow(vx, 121.9, vx, 117.8) + +arrow(qx, 109.9, 30, 107.2); arrow(kx, 109.9, 40, 107.2); arrow(vx, 114.6, 50, 107.2) +box(40, 105.5, 32, r'matmul $S = q\,k^{\top} \cdot 1/\sqrt{h_d}$', fc=PURPF, ec=PURP) +txt(57.5, 105.5, r'$(B,H,T,T)$', 7.6, GRAY, ha='left') +arrow(40, 103.9, 40, 102.4) +box(40, 100.8, 32, 'causal mask') +arrow(40, 99.2, 40, 97.7) +box(40, 96.1, 32, 'softmax over keys') +arrow(40, 94.5, 40, 93.0) +box(40, 91.4, 32, r'matmul $y = A\,v$', fc=PURPF, ec=PURP) +txt(57.5, 91.4, r'$(B,H,T,h_d)$', 7.6, GRAY, ha='left') +arrow(40, 89.8, 40, 88.3) +box(40, 86.7, 32, 'merge heads') +txt(57.5, 86.7, r'$(B,T,C)$', 7.6, GRAY, ha='left') +arrow(40, 85.1, 40, 83.6) +box(40, 82.0, 32, r'matmul $W_{proj}: C \to C$', fc=BLUEF, ec=BLUE) +arrow(40, 80.4, 40, 78.9) +box(40, 77.3, 32, r'RMSNorm$_{attn}$', fc=CREAM, ec=CREME) +txt(57.5, 77.3, '◄ OLMo2: norm on the sublayer OUTPUT,\n not on its input', 7.6, INK, ha='left') +arrow(40, 75.7, 40, 73.8) +c1 = Circle((40, 71.8), 1.9, fc='white', ec=INK, lw=1.3, zorder=3); ax.add_patch(c1) +txt(40, 71.8, '+', 11, w='bold') + +arrow(40, 69.9, 40, 67.3) +txt(42.2, 68.7, r"$z'$", 10, w='bold', ha='left') + +# ---------------- SwiGLU sublayer ---------------- +txt(3.2, 50, 'SwiGLU sublayer', 9.5, GRAY, style='italic', rot=90) + +rail([(40, 67.0), (88, 67.0), (88, 37.2)]); arrow(88, 37.8, 42.0, 37.2, color=RAIL, lw=1.4) + +arrow(38, 66.4, 27, 64.0); arrow(42, 66.4, 53, 64.0) +box(26, 62.4, 21, r'matmul $W_1: C \to h_{ff}$', fc=BLUEF, ec=BLUE) +box(54, 62.4, 21, r'matmul $W_3: C \to h_{ff}$', fc=BLUEF, ec=BLUE) +txt(65.7, 62.4, r'$h_{ff} = 1408$', 7.6, GRAY, ha='left') +arrow(26, 60.8, 26, 59.3) +box(26, 57.7, 12, 'SiLU') +arrow(26, 56.1, 33.5, 54.1); arrow(54, 60.8, 46.5, 54.1) +box(40, 52.4, 23, '⊙ elementwise gate') +arrow(40, 50.8, 40, 49.3) +box(40, 47.1, 25, r'matmul $W_2: h_{ff} \to C$', fc=BLUEF, ec=BLUE) +arrow(40, 45.5, 40, 44.0) +box(40, 42.4, 25, r'RMSNorm$_{ff}$', fc=CREAM, ec=CREME) +txt(54, 42.4, '◄ norm after the sublayer, again', 7.6, INK, ha='left') +arrow(40, 40.8, 40, 39.2) +c2 = Circle((40, 37.2), 1.9, fc='white', ec=INK, lw=1.3, zorder=3); ax.add_patch(c2) +txt(40, 37.2, '+', 11, w='bold') +arrow(40, 35.3, 40, 32.8) +txt(40, 31.3, r'output $z^{\prime\prime}$ — one full $f_l$ done', 9.5, w='bold') + +# ---------------- legend + notes ---------------- +def chip(x, y, fc, ec): + b = FancyBboxPatch((x, y - 0.9), 3.4, 1.8, boxstyle='round,pad=0.15,rounding_size=0.35', + fc=fc, ec=ec, lw=1.1) + ax.add_patch(b) + +chip(5, 25.5, BLUEF, BLUE) +txt(9.5, 25.5, 'weight × activation matmul — 7 logical ($W_{qkv}$ fuses $W_q,W_k,W_v$); crossbar-mappable on analog hardware', 8.2, ha='left') +chip(5, 22.1, PURPF, PURP) +txt(9.5, 22.1, r'activation × activation matmul — 2 ($q\,k^{\top}$ and $A\,v$); computed on the fly: the non-crossbar part of attention', 8.2, ha='left') +chip(5, 18.7, CREAM, CREME) +txt(9.5, 18.7, 'RMSNorm (learned gain)', 8.2, ha='left') +chip(38, 18.7, BOXF, BOXE) +txt(42.5, 18.7, 'elementwise / reshape', 8.2, ha='left') + +txt(5, 14.6, 'No biases anywhere in the network. The $1/\\sqrt{h_d}$ scale is folded into $S$; mask and softmax run along the key axis.', 8.2, ha='left') +txt(5, 11.9, r'$h_{ff} = 1408 \approx 8C/3$, rounded up to a multiple of 64. QK-norm acts on the full width $C$, before the head split.', 8.2, ha='left') +txt(5, 9.2, r'The boxed pipeline, input $z$ to output $z^{\prime\prime}$, is exactly $f_l$ on the companion page; the state $z_l$ lives on the residual rail.', 8.2, ha='left') + +fig.savefig('/home/yurenh2/ept/assets/fig_primer_block.pdf') +fig.savefig('/home/yurenh2/ept/assets/fig_primer_block.png', dpi=185) +fig.savefig('/home/yurenh2/ept/assets/fig_primer_block.svg') +print('saved fig_primer_block.{pdf,png,svg}') diff --git a/ep_run/fig_primer_pack2.py b/ep_run/fig_primer_pack2.py new file mode 100644 index 0000000..ff28476 --- /dev/null +++ b/ep_run/fig_primer_pack2.py @@ -0,0 +1,201 @@ +"""Primer deck figures: schedule, 72M blowup (real logs), loop-gain, beta window. +PNGs to ../assets/ for the pptx build.""" +import re +import matplotlib +matplotlib.use('Agg') +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import FancyBboxPatch + +INK, GRAY = '#3a3a3a', '#8a8a8a' +BOXF, BOXE = '#f4f4f4', '#9a9a9a' +BLUE, BLUEF = '#2c6fbb', '#eaf1fa' +ORAN, ORANF = '#d95f02', '#fdeee2' +RED = '#b03a2e' +CREAM, CREME = '#faf6ee', '#c9b895' +GREEN = '#2e7d32' +A = '/home/yurenh2/ept/assets/' + +def newfig(w, h, xmax=100, ymax=62): + fig = plt.figure(figsize=(w, h)) + ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, xmax); ax.set_ylim(0, ymax); ax.axis('off') + return fig, ax + +def box(ax, xc, yc, w, s, fc=BOXF, ec=BOXE, size=10, lw=1.2, h=4.6, tc=INK, wt='normal'): + ax.add_patch(FancyBboxPatch((xc - w / 2, yc - h / 2), w, h, + boxstyle='round,pad=0.3,rounding_size=0.7', fc=fc, ec=ec, lw=lw)) + ax.text(xc, yc, s, fontsize=size, color=tc, ha='center', va='center', fontweight=wt) + +def txt(ax, x, y, s, size=9.5, color=INK, ha='center', va='center', w='normal', style='normal'): + ax.text(x, y, s, fontsize=size, color=color, ha=ha, va=va, fontweight=w, fontstyle=style) + +def arrow(ax, x0, y0, x1, y1, color=INK, lw=1.4, ms=12, cs=None): + ax.annotate('', xy=(x1, y1), xytext=(x0, y0), + arrowprops=dict(arrowstyle='-|>', color=color, lw=lw, mutation_scale=ms, + shrinkA=0.5, shrinkB=0.5, connectionstyle=cs)) + +# ================= fig S3: the update schedule ================= +fig, ax = newfig(11.8, 6.0) +dx, zx = 15, 41 +txt(ax, dx, 58.5, 'mismatches derive TOP → BOTTOM', 10, ORAN, w='bold') +txt(ax, zx, 58.5, 'states rebuild BOTTOM → TOP', 10, BLUE, w='bold') +dl = [r'$d_3 = -\beta\,\nabla\mathrm{CE}$', r'$d_2 = J_3^{\top} d_3$', r'$d_1 = J_2^{\top} d_2$'] +dn = ['needs: loss only', 'needs: block 3 only', 'needs: block 2 only'] +for i, (s, n) in enumerate(zip(dl, dn)): + y = 51 - i * 9 + box(ax, dx, y, 20, s, fc=ORANF, ec=ORAN) + txt(ax, dx, y - 3.4, n, 7.6, GRAY) + if i < 2: arrow(ax, dx, y - 2.4 - 2.2, dx, y - 9 + 2.5, color=ORAN) +zl = [r'$z_1 = f_1(\mathrm{emb}) + d_1$', r'$z_2 = f_2(z_1) + d_2$', r'$z_3 = f_3(z_2) + d_3$'] +for i, s in enumerate(zl): + y = 33 + i * 9 + box(ax, zx, y, 20, s, fc=BLUEF, ec=BLUE) + if i < 2: arrow(ax, zx, y + 2.4, zx, y + 9 - 2.5, color=BLUE) +txt(ax, 28, 22.5, 'block $l$ touches NEIGHBORS only — $d$ from the block after it,\n' + '$z$ from the block before it. $\\Delta\\theta_l$ fires as soon as $d_l$ arrives.\n' + 'Repeat $K$ times ($K{=}3$ measured sufficient; $K{=}8$ identical).', 9) + +sx = 76 +txt(ax, sx, 58.5, 'the same settle, three schedules', 10.5, INK, w='bold') +rows = [ + ('sequential (our trainer today)', r'$O(K\!\cdot\!d)$', '3 × 12 = 36 block-ops', BOXF, BOXE), + ('pipelined wavefront (systolic; multi-GPU lever)', r'$O(K\!+\!d\!-\!1)$', '3 + 12 − 1 = 14 slots', BLUEF, BLUE), + ('continuous-time analog: all blocks relax AT ONCE', r'$\sim O(K)$', 'depth-serial factor gone;\n' + r'$K$ dissolves into settling time $\tau$', ORANF, ORAN), +] +for i, (name, comp, det, fc, ec) in enumerate(rows): + y = 50 - i * 12.5 + box(ax, sx, y, 44, '', fc=fc, ec=ec, h=9.5) + txt(ax, sx - 20.5, y + 2.2, name, 9.3, ha='left', w='bold') + txt(ax, sx - 20.5, y - 1.8, det, 8.6, GRAY, ha='left') + txt(ax, sx + 15.5, y + 2.2, comp, 12) +txt(ax, sx, 9.5, 'open question we track: does $\\tau$ itself grow with depth?\n' + '(one transit down the chain is unavoidable)', 8.4, GRAY) +fig.savefig(A + 'fig_p3_schedule.png', dpi=170); plt.close(fig) + +# ================= fig S4: what happened at 72M (real logs) ================= +STEP_RE = re.compile(r'^step\s+(\d+)/\d+ \| train [\d.]+ val ([\d.]+) \(best ([\d.]+)\).*?beta=([\d.e+-]+)') +def parse(fn): + out = {} + for line in open('/home/yurenh2/ept/ep_run/runs/' + fn, errors='replace'): + m = STEP_RE.match(line) + if m: out[int(m.group(1))] = (float(m.group(2)), float(m.group(4))) + return out + +base = parse('fw72m.log') +resc = parse('fw72m_c.log'); resc.update(parse('fw72m_c2.log')) +bs = np.array(sorted(base)); bval = np.array([base[s][0] for s in bs]) +rs = np.array(sorted(k for k in resc if k >= 195000)); rval = np.array([resc[s][0] for s in rs]) +print('base max val post-195k:', bval[bs > 195000].max(), '| resc pts:', len(rs)) +beta_steps = np.array(sorted([k for k in base if k < 195000] + list(rs))) +beta_v = np.array([base[k][1] if k < 195000 else resc[k][1] for k in beta_steps]) + +fig = plt.figure(figsize=(9.2, 6.0)) +axv = fig.add_axes([0.085, 0.12, 0.815, 0.78]) +axv.plot(bs / 1000, np.clip(bval, 0, 6.42), color=BLUE, lw=1.0, zorder=3) +axv.plot(rs / 1000, np.clip(rval, 0, 6.42), color='#999999', lw=0.9, zorder=2) +axv.text(233, 4.32, 'rescue resumes\n(new recipe from 195k / 215k)', fontsize=8, color='#777777', ha='right') +axv.axhline(3.2884, color=GRAY, lw=1.0, ls='--') +axv.text(2, 3.19, 'BP twin final 3.2884', fontsize=8.5, color=GRAY, va='top') +axv.plot([185], [3.7117], marker='*', ms=14, color=GREEN, zorder=5) +axv.text(180, 3.50, 'best 3.7117 @185k (never beaten again)', fontsize=8.5, color=GREEN, ha='right') +axv.annotate('195k: relaxation diverges — val 3.9 → 6.0,\nguard storm (15k step-skips); the same\n$\\beta$ was safe for the previous 135k steps', + xy=(204, 5.95), xytext=(112, 5.62), fontsize=9.5, color=RED, + arrowprops=dict(arrowstyle='-|>', color=RED)) +axv.axvline(60, color=GRAY, lw=0.8, ls=':') +axv.axvline(215, color=GRAY, lw=0.8, ls=':') +axv.text(61, 6.30, 'floor raised\n3e-4 → 1e-3: fine', fontsize=8, color=GRAY, va='top') +axv.text(146, 4.78, 'governor caps $\\beta$: survives,\nbut starved to 2e-5 — best frozen', fontsize=8.5, color=INK) +axv.set_xlabel('step (×1000)', fontsize=10); axv.set_ylabel('val CE', fontsize=10, color=BLUE) +axv.set_ylim(3.0, 6.5); axv.set_xlim(0, 238) +axv.tick_params(labelsize=9) +axb = axv.twinx() +axb.plot(beta_steps / 1000, beta_v, color=ORAN, lw=1.3, alpha=0.9, drawstyle='steps-post') +axb.set_yscale('log'); axb.set_ylim(8e-6, 6e-3) +axb.set_ylabel(r'$\beta$ (log)', fontsize=10, color=ORAN) +axb.tick_params(labelsize=8, colors=ORAN) +for sp in ['top']: axv.spines[sp].set_visible(False); axb.spines[sp].set_visible(False) +axv.set_title('fw72m — 72.11M params × 1.44B FineWeb tokens, fully BP-free (what actually happened)', + fontsize=11, color=INK, pad=10) +fig.savefig(A + 'fig_p4_blowup.png', dpi=170); plt.close(fig) + +# ================= fig S5: the loop ================= +fig, ax = newfig(8.8, 6.2, xmax=100, ymax=70) +bx = 26 +for i, name in enumerate(['block 1', 'block 2', 'block 3']): + box(ax, bx, 16 + i * 15, 22, name, fc=BOXF, ec=BOXE, h=8, size=11) +arrow(ax, bx + 12, 20, bx + 12, 27, color=BLUE, lw=2.0); arrow(ax, bx + 12, 35, bx + 12, 42, color=BLUE, lw=2.0) +arrow(ax, bx - 12, 42, bx - 12, 35, color=ORAN, lw=2.0); arrow(ax, bx - 12, 27, bx - 12, 20, color=ORAN, lw=2.0) +ax.text(bx - 17.5, 31, 'errors flow DOWN $d_l = J_{l+1}^{\\top} d_{l+1}$', fontsize=9, color=ORAN, + ha='center', va='center', rotation=90) +ax.text(bx + 17.5, 31, 'states rebuild UP $z_l = f_l(z_{l-1}) + d_l$', fontsize=9, color=BLUE, + ha='center', va='center', rotation=270) +arrow(ax, bx + 9, 51.5, bx - 9, 51.5, color=RED, lw=2.0, cs='arc3,rad=0.45') +txt(ax, bx, 60, 'top link: $-\\beta\\,\\nabla\\mathrm{CE}$, scale $\\sigma(W_{out})^2$', 9.5, RED) +txt(ax, bx, 6, 'one sweep = down + up:\na closed loop through the stack', 9.5, GRAY) + +L = 50 +box(ax, 72.5, 56, 45, r'per-sweep gain $\rho \;\approx\; \beta\cdot\sigma(W_{out})^2\cdot\|J\mathrm{-chains}\|^2$', + fc=CREAM, ec=CREME, h=8, size=10.5) +txt(ax, L, 46, r'$\rho < 1$: mismatch decays every sweep → settles', 10, GREEN, ha='left') +txt(ax, L, 41, r'$\rho > 1$: every sweep AMPLIFIES the residual → diverges', 10, RED, ha='left') +txt(ax, L, 34.8, 'and $\\rho$ RISES during training:', 10, INK, ha='left') +txt(ax, L, 31.3, r'$\sigma(W_{out})$ and $\|J\|$ grow as the fit deepens', 10, INK, ha='left') +txt(ax, L, 24, 'measured:', 9.5, INK, ha='left', w='bold') +txt(ax, L, 20.3, '· damping refuted in 3 doses (spectrum monotone-positive)', 9, INK, ha='left') +txt(ax, L, 16.8, '· lowering $\\beta$ restores convergence instantly', 9, INK, ha='left') +txt(ax, L, 13.3, '· continuous-time flow is unconditionally stable →', 9, INK, ha='left') +txt(ax, L + 1.5, 9.6, 'a DISCRETE-SOLVER disease; analog hardware\nhas no such ceiling', 9, ORAN, ha='left') +fig.savefig(A + 'fig_p5_loop.png', dpi=170); plt.close(fig) + +# ================= fig S6: the beta window ================= +fig = plt.figure(figsize=(9.2, 6.0)) +ax2 = fig.add_axes([0.10, 0.13, 0.86, 0.76]) +x = np.linspace(0, 234, 500) +cx = [0, 60, 120, 160, 195, 215, 222, 230, 234] +cy = [1.2e-2, 1.0e-2, 6e-3, 2.5e-3, 9.5e-4, 4e-4, 2.6e-4, 4e-5, 2.2e-5] +ceil = np.exp(np.interp(x, cx, np.log(cy))) +fx_ = [0, 60, 120, 180, 234]; fy = [8e-5, 1.1e-4, 1.6e-4, 2.6e-4, 4.5e-4] +floor = np.exp(np.interp(x, fx_, np.log(fy))) +ax2.fill_between(x, ceil, 2e-2, color=RED, alpha=0.10) +ax2.fill_between(x, 8e-6, floor, color='#666666', alpha=0.12) +ax2.fill_between(x, floor, ceil, color=GREEN, alpha=0.07) +ax2.plot(x, ceil, color=RED, lw=1.6) +ax2.plot(x, floor, color='#555555', lw=1.6) +ax2.text(6, 6.5e-3, r'wall-2: $\rho>1$ — relaxation diverges', fontsize=10, color=RED) +ax2.text(6, 2.6e-5, 'wall-1: SNR < 1 — updates dissolve into noise', fontsize=10, color='#555555') +ax2.text(96, 1.35e-3, r'$\beta_{max}(t) \propto \mathrm{margin}/(\sigma^2\|J\|^2)$', fontsize=9.5, color=RED, rotation=-14) +ax2.text(120, 1.05e-4, r'$\beta_{min}(t) \propto \mathrm{noise}/|g|$', fontsize=9.5, color='#555555', rotation=6) +ax2.text(150, 5.5e-4, 'operating window\n(narrows as fit deepens)', fontsize=9.5, color=GREEN, ha='center') +# original schedule +ax2.plot([0, 60], [3e-4, 3e-4], color=INK, lw=2.2) +ax2.plot([60, 60], [3e-4, 1e-3], color=INK, lw=1.0, ls=':') +ax2.plot([60, 195], [1e-3, 1e-3], color=INK, lw=2.2) +ax2.plot([195], [1e-3], marker='x', ms=13, mew=3, color=RED, zorder6b=5) if False else ax2.plot([195], [1e-3], marker='x', ms=13, mew=3, color=RED, zorder=5) +ax2.text(196.5, 1.25e-3, 'fixed floor crosses the\nfalling ceiling → blow', fontsize=8.5, color=RED) +ax2.text(90, 7.6e-4, 'fw72m schedule (fixed floors)', fontsize=8.5, color=INK) +# bcap rescue +bs = x[(x >= 215) & (x <= 234)] +ax2.plot(bs, np.exp(np.interp(bs, [215, 222, 230, 234], np.log([3e-4, 2.2e-4, 3.5e-5, 2e-5]))), + color=GRAY, lw=1.8, ls='-.') +ax2.text(213, 4.8e-5, 'bcap rescue: survives, starved —\nthe window had CLOSED for the\nold recipe\'s margin', fontsize=8, color=GRAY, ha='right') +# cent path +xm = x[x <= 185] +cent = np.where(xm < 20, 3e-4, 3e-3) +cent = np.minimum(cent, np.exp(np.interp(xm, cx, np.log(cy))) * 0.85) +ax2.plot(xm, cent, color=ORAN, lw=2.2, ls='--') +ax2.text(24, 4.25e-3, 'fw72m_cent (in flight): centered + $\\beta$ rides ITS OWN ceiling via bcap 0.9 — where that ceiling sits is being measured now (48k: ahead)', fontsize=8.6, color=ORAN, ha='left') +ax2.text(186.5, 1.62e-3, '→ ?', fontsize=10, color=ORAN, fontweight='bold') +# measured ceiling points +for (mx, my, lab) in [(195, 1e-3, '≤1e-3'), (222, 3e-4, '≤3e-4'), (228, 2.5e-5, '≈2e-5')]: + ax2.plot([mx], [my], marker='v', ms=7, color=RED, zorder=5) +ax2.text(231, 2.3e-3, 'measured ceiling\ncrossings ▾', fontsize=8, color=RED, ha='right') +ax2.set_yscale('log'); ax2.set_ylim(8e-6, 2e-2); ax2.set_xlim(0, 236) +ax2.set_xlabel('training progress (×1000 steps)', fontsize=10) +ax2.set_ylabel(r'$\beta$ (log scale)', fontsize=10) +ax2.tick_params(labelsize=9) +for sp in ['top', 'right']: ax2.spines[sp].set_visible(False) +ax2.set_title(r'$\beta$ is squeezed from both sides — and the window narrows as the model fits deeper', + fontsize=11.5, color=INK, pad=10) +fig.savefig(A + 'fig_p6_window.png', dpi=170); plt.close(fig) +print('figs saved: p3_schedule, p4_blowup, p5_loop, p6_window') diff --git a/ep_run/fig_primer_phases.py b/ep_run/fig_primer_phases.py new file mode 100644 index 0000000..b04ed5f --- /dev/null +++ b/ep_run/fig_primer_phases.py @@ -0,0 +1,99 @@ +"""Slide variant of fig_primer_arch: free/nudged phases + update only (no model panel, +no PC contrast). PDF/PNG/SVG to ../assets/.""" +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt +plt.rcParams['svg.fonttype'] = 'none' # keep text editable in PPT +from matplotlib.patches import FancyBboxPatch + +INK = '#3a3a3a' +GRAY = '#8a8a8a' +BOXF = '#f4f4f4' +BOXE = '#9a9a9a' +BLUE = '#2c6fbb' +BLUEF = '#eaf1fa' +ORAN = '#d95f02' +RED = '#b03a2e' +CREAM = '#faf6ee' +CREME = '#c9b895' + +fig = plt.figure(figsize=(12.2, 11.0)) +ax = fig.add_axes([0, 0, 1, 1]); ax.set_xlim(0, 100); ax.set_ylim(16, 105); ax.axis('off') + +def box(x, y, w, h, fc=BOXF, ec=BOXE, lw=1.0, r=0.6): + b = FancyBboxPatch((x, y), w, h, boxstyle=f'round,pad=0.25,rounding_size={r}', + fc=fc, ec=ec, lw=lw) + ax.add_patch(b); return b + +def txt(x, y, s, size=8.5, color=INK, ha='center', va='center', w='normal', style='normal'): + ax.text(x, y, s, fontsize=size, color=color, ha=ha, va=va, + fontweight=w, fontstyle=style) + +def arrow(x0, y0, x1, y1, color=INK, lw=1.2, style='-|>', ms=11): + ax.annotate('', xy=(x1, y1), xytext=(x0, y0), + arrowprops=dict(arrowstyle=style, color=color, lw=lw, + mutation_scale=ms, shrinkA=0.5, shrinkB=0.5)) + +def state_col(x0, ys, labels, notes, fc=BLUEF, ec=BLUE, note_c=GRAY): + for y, lab, note in zip(ys, labels, notes): + box(x0 - 10.5, y - 2.2, 21, 4.4, fc=fc, ec=ec, lw=1.2) + txt(x0, y, lab, 10, color=INK) + if note: txt(x0 + 12.2, y, note, 8.4, note_c, ha='left') + for ya, yb in zip(ys[1:], ys[:-1]): + arrow(x0, ya + 2.7, x0, yb - 2.8, lw=1.15) + +# ======================== free phase ======================== +bx = 25 +txt(bx, 102.3, 'Free phase (= inference)', 11.5, w='bold') +txt(bx, 99.4, 'give each layer a state $z_l$ (3 of $L$ drawn); settle the disagreement energy', 8.8, GRAY) +txt(bx, 95.4, r'$E(z)\ =\ \sum_l\ \frac{1}{2}\,\|\,z_l - f_l(z_{l-1})\,\|^2$', 11.5) + +ys = [85, 73, 61] +state_col(bx - 6, ys, [r'$z_3 = f_3(z_2)$', r'$z_2 = f_2(z_1)$', r'$z_1 = f_1(\mathrm{emb})$'], + ['term = 0', 'term = 0', 'term = 0']) +arrow(bx - 6, 52.4, bx - 6, 58.0, lw=1.15) +txt(bx - 6, 50.2, r'$\mathrm{emb}(x)$', 10) + +txt(bx, 43.8, 'The minimum is exact: $E=0$, states $\\equiv$ forward activations,', 9) +txt(bx, 40.8, 'and one bottom-up pass reaches it.', 9) +txt(bx, 37.2, 'The free phase adds nothing and changes nothing at inference.', 8.6, GRAY) + +# ======================== nudged phase ======================== +nx = 70 +txt(nx, 102.3, 'Nudged phase (training only)', 11.5, w='bold') +txt(nx, 99.4, r'add the loss at strength $\beta \ll 1$ and settle again:', 8.8, GRAY) +txt(nx, 95.4, r'$E(z)\ +\ \beta\cdot\mathrm{CE}(\mathrm{logits}(z_3),\,y)$', 11.5) + +txt(nx + 3.0, 91.8, r'pull $-\beta\,\nabla\mathrm{CE}$', 9.4, RED, ha='left') +txt(nx + 3.0, 89.4, '(the only place the label enters)', 7.8, RED, ha='left') +arrow(nx + 2.0, 90.9, nx - 3.5, 87.8, color=RED, lw=1.6) + +nys = [85, 73, 61] +state_col(nx - 6, nys, + [r'$z_3^{\beta} = z_3 + d_3$', r'$z_2^{\beta} = z_2 + d_2$', r'$z_1^{\beta} = z_1 + d_1$'], + [None, None, None], fc='#fdeee2', ec=ORAN) +arrow(nx - 6, 52.4, nx - 6, 58.0, lw=1.15) +txt(nx - 6, 50.2, r'$\mathrm{emb}(x)$', 10) + +for (ya, yb, lab) in [(83.0, 75.8, r'$d_2 = J_3^{\top} d_3$'), + (71.0, 63.8, r'$d_1 = J_2^{\top} d_2$')]: + arrow(nx + 6.4, ya, nx + 6.4, yb, color=ORAN, lw=1.6) + txt(nx + 8.2, (ya + yb) / 2, lab, 9.4, ORAN, ha='left') + +txt(nx, 43.8, 'The top state is pulled toward lower loss; each layer\'s mismatch $d_l$', 9) +txt(nx, 40.8, 'transmits DOWN through the same weights, and the stack re-settles.', 9) +txt(nx, 37.2, r'($J^{\top}$ = the transpose read a bidirectional physical device provides)', 8.6, GRAY) + +# ======================== the update ======================== +box(2, 18.5, 96, 15.0, fc=CREAM, ec=CREME, lw=1.2) +txt(4, 30.9, 'The update — a difference measurement between the two settled states', 11, w='bold', ha='left') +txt(50, 27.1, r'$\hat{g}\ =\ \left[\ \partial_\theta E(z^{\beta})\ -\ \partial_\theta E(z^{0})\ \right]\,/\,\beta$' + r'$\qquad\qquad(\partial_\theta E(z^0)\equiv 0\ \mathrm{here,\ since}\ E=0)$', 11) +txt(50, 23.4, r'per layer: $\Delta\theta_l\ \propto\ \langle\ d_l\ ,\ \partial f_l(z_{l-1})/\partial\theta_l\ \rangle\ /\ \beta$', 10) +txt(50, 20.4, r'Each layer updates from its own boundary mismatch — no global backward graph, no global tape, no loss' + r' derivatives except the top nudge.', 8.6) + +fig.savefig('/home/yurenh2/ept/assets/fig_primer_phases.pdf') +fig.savefig('/home/yurenh2/ept/assets/fig_primer_phases.png', dpi=185) +fig.savefig('/home/yurenh2/ept/assets/fig_primer_phases.svg') +print('saved fig_primer_phases.{pdf,png,svg}') |
