summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--LICENSE21
-rw-r--r--README.md34
-rw-r--r--design/experiment-spec.md127
-rw-r--r--design/preregistration.md68
5 files changed, 255 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f9bf0eb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+__pycache__/
+*.pyc
+.venv/
+runs/
+.pytest_cache/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6de4252
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 jayrup
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bae6cf7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,34 @@
+# Prime Grokking
+
+Can minimal architectures learn the next-prime function — or *grok* it rather than memorize it?
+
+Experiment 1 (seed 0): weight-tied 2-layer RNN cell (K=20 tied steps, ACT learned halting)
+vs. a fixed-d_model transformer baseline, range n ∈ [2, 100], 30% holdout.
+
+## Layout
+
+```
+design/experiment-spec.md design doc (copied from the research repo, provenance noted)
+design/preregistration.md pre-registered outcome→interpretation matrix (LOCK: commit 00c696d in research repo)
+src/ config, data, model API, models, train, eval
+tests/ pytest suite (data correctness, model shapes, halting)
+scripts/ run_experiment.sh, plot.py
+runs/ metrics CSVs + plots (gitignored)
+```
+
+## Quick start
+
+```bash
+python3 -m venv .venv
+.venv/bin/pip install torch --index-url https://download.pytorch.org/whl/cpu numpy matplotlib pytest
+.venv/bin/pytest tests/ -q
+scripts/run_experiment.sh rnn 0 # full run, ~35 min on 2 cores
+scripts/run_experiment.sh transformer 0
+scripts/plot.py
+```
+
+## Notes
+
+- Results are interpreted ONLY against `design/preregistration.md` (outcome codes O1–O8, H1–H4, P1–P4).
+- Seed-0 runs are anecdotes until seeds {1, 2}; no hyperparameter tuning on the val set.
+- Remote: `ssh://meru/~/projects/prime-grokking.git` (private; MIT license included for future publishing).
diff --git a/design/experiment-spec.md b/design/experiment-spec.md
new file mode 100644
index 0000000..c814057
--- /dev/null
+++ b/design/experiment-spec.md
@@ -0,0 +1,127 @@
+# Prime Grokking — Can minimal architectures learn the next-prime function?
+
+**Question:** If you strip a model down to digit tokens (0-9 + EOS), feed it
+a single number, and train it to output the next prime in sequence — with no
+chain-of-thought, no external memory, pure feedforward or minimal recurrence —
+will it work? Can it *grok* the underlying algorithm rather than memorizing a
+lookup table?
+
+## The core tension
+
+The next-prime function is deceptive. For small ranges it looks learnable —
+skip evens, skip 5s, and you're most of the way there. But fundamentally it's
+a combinatorial search problem:
+
+1. Iterate candidates c = n+1, n+2, ... (gap ≈ log n on average, but unbounded)
+2. For each candidate, check divisibility by all primes ≤ √c
+3. Output the first candidate with zero divisors
+
+A fixed-depth circuit cannot do an unbounded loop. So the question isn't "will
+it work for arbitrary n" (it won't) — it's "can the model discover *any*
+structured algorithm beyond pure memorization, and under what conditions?"
+
+## Why grokking is plausible (but harder than modular arithmetic)
+
+Modular addition has a clean group structure — one low-complexity generalizing
+solution (rotate on a circle). Next-prime doesn't. But for a **bounded range**
+the algorithmic solution does exist in circuit space:
+
+- For N ≤ 1000: √N ≈ 31, so ~11 divisibility checks per candidate, ~6 average
+ gap steps → ~60-70 modular operations. A transformer or RNN with enough
+ depth *can* represent this.
+- Weight decay could push from memorization to this structured sieve.
+
+**Why it's harder than modular addition:**
+
+1. **Memorization is a stronger attractor.** The lookup table fits easily in
+ the weights. The algorithmic circuit's weight norm might be *larger*.
+2. **Brittle loss landscape.** One mis-calibrated divisibility check → wrong
+ output for a whole class. Narrow basin.
+3. **No guaranteed continuous interpolation path** from memorization to
+ generalization in weight space.
+
+## Architecture design space
+
+### 1. Weight-tied recurrence (highest-signal first experiment)
+
+```
+state = embed(input_number)
+for step in range(max_steps):
+ state = step_module(state) # same weights, every iteration
+ if halt_condition(state): break
+output = project(state)
+```
+
+One step module, applied repeatedly. Weight sharing means the generalizing
+solution (one divisibility operation, reused K times) has *smaller* effective
+parameter count than memorization. This is the mechanism that makes grokking
+possible — the simpler solution wins under regularization.
+
+### 2. Explicit modulo gating
+
+Give the architecture a modulo gate directly. Not "learn integer division from
+scratch" — give it access to the atomic operation and let it learn *when* and
+*how* to route through it.
+
+### 3. Two-level recurrence (nested loop structure)
+
+The algorithm has nested loops: outer (candidate search) and inner
+(divisibility check). A flat recurrence interleaves them awkwardly.
+Consider:
+- **Outer level:** advance candidate, check halt signal
+- **Inner level:** iterate through divisors, check modulo
+- Stack-augmented: push candidate, run inner loop, pop, advance
+
+### 4. Adaptive computation time (the honest answer)
+
+Any fixed-budget architecture will fail at some range. The real solution is
+dynamic depth — run until a halt neuron fires. ACT, ponder networks, or
+simply "halt when confidence exceeds threshold."
+
+## First experiment
+
+**Setup:**
+- Range: n ∈ [2, 100], hold out 30% randomly
+- Architecture: 2-layer weight-tied RNN cell, K=20 steps, learned halting gate
+- Embedding: digit tokens + positional encoding
+- Loss: standard next-token prediction on digit sequence output
+- Regularization: heavy weight decay + small training set (force grokking)
+- Baseline: same-parameter-count transformer
+
+**What to watch:**
+- Training/val loss divergence curve
+- If val drops suddenly after train → 0 → grokking-like behavior
+- If val never drops → limitation is deeper than architecture
+
+**Extensions:**
+- Scale range: [2, 200], [2, 500], [2, 1000]
+- Vary training set size (40%, 50%, 70% of range)
+- Vary weight decay magnitude
+- Add explicit modulo gate vs. learned
+- Test generalization: train on [2, 100], test on [101, 200]
+
+## Key references
+
+- Power et al. (2022) — "Grokking: Generalization Beyond Overfitting on Small
+ Algorithmic Datasets"
+- Xu et al. (ICLR 2020) — "What Can Neural Networks Reason About?"
+ (algorithmic alignment framework)
+- Graves (2016) — "Adaptive Computation Time for Recurrent Neural Networks"
+- Dehghani et al. (ICLR 2019) — "Universal Transformers"
+- Banino et al. (NeurIPS 2021) — "PonderNet: Learning to Ponder"
+
+## Why this matters beyond primes
+
+This is a clean, minimal test case for a much bigger question: can neural
+networks discover algorithmic structure from input-output pairs alone when
+memorization is the path of least resistance? Next-prime strips away all the
+ambiguity — no semantics, no language, no multimodality. Just numbers and a
+function that looks smooth but is structurally algorithmic.
+
+If grokking can't happen here, in this maximally simple setting, it's strong
+evidence that current architectures need something fundamentally different to
+cross the gap from pattern matching to computation.
+
+---
+
+> Copied from the research repo `prime-grokking/main.md` @ 546dc2c (provenance: `~/Projects/research`, remote ssh://meru/~/projects/research.git).
diff --git a/design/preregistration.md b/design/preregistration.md
new file mode 100644
index 0000000..d879912
--- /dev/null
+++ b/design/preregistration.md
@@ -0,0 +1,68 @@
+# Pre-registration: Prime-Grokking — Experiment 1 (seed 0)
+
+**Status:** PRE-REGISTERED before any training runs.
+**Date:** 2026-08-14
+**Spec:** `prime-grokking/main.md` (research repo, commit 546dc2c)
+**Lock:** this file is committed to the research repo before experiment code runs; the commit hash is the lock.
+
+## Experiment summary
+
+- **Task:** map n → next prime, range n ∈ [2, 100], 30% random holdout (seed 0), tokenized as decimal digits + EOS, teacher forcing on outputs.
+- **Arms:** (A) weight-tied 2-layer RNN cell, K = 20 tied steps, ACT learned halting (λ = 0.01, warmup 1000 steps); (B) GPT-style transformer baseline, d_model = 128, 2 layers, 4 heads.
+- **Comparison control:** fixed d_model = 128 for both arms. NO parameter-parity gate — weight sharing is the variable under study (param-matching a tied RNN against a GPT would kill the very property the spec hypothesizes). Param counts are logged per run for the record.
+- **Optimizer:** AdamW, lr = 1e-3, weight decay = 1.0 (sweep {0.3, 1.0, 3.0, 10.0} is a later experiment, not tuning on v1). Budget: 200k steps cap, eval every 200, early stop on val exact-match = 1.0 (patience 5).
+- **Primary metric:** per-example exact-match accuracy on held-out inputs (token accuracy reported alongside). Per-example correctness on 10 fixed val inputs logged every eval. Mean halt steps logged every eval (RNN arm).
+- **Probe:** generalization on [101, 200] is diagnostic-only (range extension is its own experiment).
+
+## Pre-registered outcome → interpretation matrix
+
+### In-range outcomes ([2, 100] held-out)
+
+| Code | Observable | Interpretation (locked) | Next step |
+|---|---|---|---|
+| **O1** | Sharp transition: train EM ≥ 0.95 sustained ≥ 10 evals, THEN val EM rises 0.2 → 0.9 within ≤ 5 evals | Grokking-like. Tied cell + heavy wd found a structured in-range solution; memorization repelled. NOT yet evidence of the full sieve — may be skip-evens/5s + divisibility heuristics. | Identify structure (per-example log, halting pattern); minimal-conditions ablations (halting=False, wd sweep, seeds) — jayrup's call. |
+| **O2** | Train EM → ~1.0, val EM stays low (≤ 0.3 at run end) | Memorization won. Lookup table is the lower-norm solution under these hyperparams — consistent with spec's "memorization is a stronger attractor". | wd sweep, smaller train fraction (40%/50%), longer budget — jayrup's call. |
+| **O3** | Gradual val rise to ≥ 0.9, no sharp transition | Smooth heuristic learning — NOT grokking by our operational definition. A "fast" generalizing solution exists that GD finds directly. | Distinguish from O1 by transition sharpness; report both. |
+| **O4** | Train EM never ≥ 0.95 within budget | Optimization/setup failure (lr, K, halting collapse, bug). NO scientific interpretation until fixed. | Inspect losses + halt steps; fix; rerun. |
+
+### Architecture comparison (same hyperparams, seed 0)
+
+| Code | Observable | Interpretation (locked) |
+|---|---|---|
+| **O5** | RNN O1, transformer O2/O3 | Weight-tied recurrence is the enabling mechanism at this scale — evidence for the spec's core hypothesis (weight sharing makes the algorithmic solution cheaper). |
+| **O6** | Transformer O1, RNN O2/O3 | Recurrence + halting not necessary; fixed-depth transformer suffices in-range. Weight-tying not the key variable — depth/regularization is. |
+| **O7** | Both O1 | Grokking robust to architecture at this scale; the variable is data/regularization, not recurrence. |
+| **O8** | Neither | See O2/O4. Next-prime may be fundamentally harder than modular addition as the spec hypothesizes; wd/data sweeps decide. |
+
+Caveat: single seed — all architecture comparisons are seed-0 anecdotes until seeds {1, 2}.
+
+### Halting structure (RNN arm only)
+
+| Code | Observable | Interpretation (locked) |
+|---|---|---|
+| **H1** | Mean steps collapse to 1 by run end | ACT failed (penalty/init issue); architecture conclusions read with a collapsed gate; fixed-K ablation becomes the informative run. |
+| **H2** | Mean steps pinned at K = 20 | Never learned to halt (penalty too weak). Same caveat as H1. |
+| **H3** | Mean steps intermediate (2–18), evolves during training, ideally correlates with gap-to-next-prime | Learned computation budget — evidence of structured algorithm. Check per-input steps on the 10 logged val examples. |
+| **H4** | Steps fluctuate noisily | Halt signal not used meaningfully. |
+
+### Generalization probe [101, 200] (diagnostic-only)
+
+| Code | Observable | Interpretation (locked) |
+|---|---|---|
+| **P1** | Errors concentrated on {121, 143, 169, 187} — composites with factors 11, 13 (divisors beyond the {2, 3, 5, 7} sieve of the training range) | Definitive evidence of a learned sieve with the training-range divisor set. Strongest positive result available at this scale. |
+| **P2** | Errors scattered uniformly over composites | Memorization or non-transferable heuristics; no evidence of divisibility-based algorithm. |
+| **P3** | High probe accuracy beyond {121, 143, 169, 187} | Surprising — implies richer algorithm than the {2,3,5,7} sieve. Treat with suspicion; verify across seeds before claiming anything. |
+| **P4** | Probe fails on ALL of [101, 200] incl. easy evens / skip-5s | In-range solution didn't transfer even trivial heuristics — strong memorization evidence. |
+
+### Operational definition of "grokking-like" (locked)
+
+- train exact-match ≥ 0.95 sustained for ≥ 10 consecutive evals (train saturated), AND
+- val exact-match transition from ≤ 0.2 to ≥ 0.9 within ≤ 5 consecutive evals (eval_every = 200).
+- eval order: evals happen every 200 steps; indices counted in evals, not steps.
+
+## Statistical hygiene
+
+1. Seed 0 for v1; seeds {1, 2} required before ANY claim beyond "seed-0 result".
+2. No hyperparameter tuning on the val set. The wd sweep is a separate experiment run only after v1 results, at jayrup's call.
+3. Probe interpretation locked above; NOTES.md must compare outcomes against this matrix verbatim (cite codes).
+4. Training-range caveat recorded: for n ≤ 100 the sieve only needs divisors {2, 3, 5, 7}; "grokking the algorithm" in-range does not imply the general sieve.