Looped transformers × RSI-style iterative adaptation — a reproducible parity study of recurrent depth, length generalization, and bounded loop-schedule search.
At a Glance · Paths · Quick Start · Results · Notes · RSI Mode
A minimal, reproducible experiment comparing a conventional transformer with a shared-weight recurrent/looped transformer on binary sequence parity. The experiment probes iterative computation, compute scaling, and length generalization; it does not test or claim general reasoning ability.
On top of that baseline, a separate mode performs RSI-style iterative adaptation over the recurrent loop schedule — bounded candidate loop counts, dev-set selection, and a final post-selection held-out evaluation. It is not unrestricted or general Recursive Self-Improvement and makes no claim of intelligence improvement.
| Task | Binary sequence parity; train/dev lengths 4–8, held-out lengths 12–16 |
| Models | Conventional transformer vs. shared-weight looped transformer |
| Backends | NumPy (frozen features) and PyTorch (end-to-end) |
| RSI mode | Bounded loop-schedule search, dev selection, post-selection held-out |
| Splits | Example-disjoint by construction; zero-overlap asserted per seed |
| Tests | Full suite in CI (a NumPy job and a Torch-CPU job) |
RLT baseline (rlt_rsi.train)
- conventional single-pass
baselinereference - shared-weight
loopedmodel at loop counts 1, 2, 4 - loop-count scaling vs. measured wall-clock compute
- length generalization to held-out lengths 12–16
RSI-style iterative adaptation (rlt_rsi.train_rsi)
- bounded candidate loop counts
- per-candidate training on the train split
- dev-set evaluation and selection
- explicit lineage per generation
- final held-out evaluation, post-selection only
flowchart TD
K[Current loop count] --> G[Generate k-1, k, k+1 - clamped to 1-8]
G --> T[Train each candidate on train]
T --> D[Evaluate on dev]
D --> S[Select best dev accuracy - tie-break lower dev BCE]
S --> N[Next generation]
N -->|more generations| G
N -->|done| H[Final held-out evaluation - post-selection only]
# Setup
python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e '.[dev]'
# Optional: real training backend (not required for the NumPy smoke path)
python -m pip install -e '.[torch]'
# NumPy smoke pipeline (frozen features; plumbing check only)
python -m rlt_rsi.train --backend numpy --seeds 7,42,123 --output results/smoke.json
# Torch pipeline (device auto -> cuda, else mps, else cpu)
python -m rlt_rsi.train --backend torch --device auto --seeds 7,42,123 --epochs 80 \
--artifacts-dir results/torch-<device>-<date>
# Test suite
pytest -qPrefer no local setup? Open notebooks/rlt_rsi_colab.ipynb
in Colab. Stage A is a CPU-safe NumPy smoke and RSI run; Stage B runs the Torch path only
when a GPU is available.
The runner evaluates 4 configurations across 3 seeds (7, 42, 123):
baseline(loops=1): single-pass referencelooped(loops=1): shared-weight model, single passlooped(loops=2): 2 sequential block applications per steplooped(loops=4): 4 sequential block applications per step
NumPy (--backend numpy) |
PyTorch (--backend torch) |
|
|---|---|---|
| Attention | manual scaled dot-product | nn.MultiheadAttention |
| Normalization | RMS-style | nn.LayerNorm |
| Training scope | frozen embedding + block; only the linear readout + bias are trained | all parameters, end-to-end |
| Parameters | total 4,681 = frozen 4,656 + trainable 25 | total 4,945, all trainable |
| dtype / device | float64 / CPU | float32 / resolved torch device |
| Purpose | smoke + plumbing check; not evidence about trained recurrence | intended research run |
The two architectures are not parameter-identical, so their parameter counts are not
comparable across backends. architecture_parameters() is retained for backward
compatibility and returns the frozen backbone subtotal only (embedding + block); it is
never the total. Use total_parameters() / trainable_parameters() / frozen_parameters().
--device auto|cpu|mps|cuda (default auto):
cpu→ CPUcuda→ requirestorch.cuda.is_available(), otherwise raisesRuntimeErrormps→ requirestorch.backends.mps.is_available(), otherwise raisesRuntimeErrorauto→ cuda if available, else mps if available, else cpu
There is no silent fallback; the requested and resolved devices are recorded in the payload and report.
estimated_block_callscounts sequential applications of the shared transformer block. It is a structural counter — not a FLOP count and not a latency measurement. Measured wall-clock training time is reported separately asseconds.- Equal epoch counts are not equal compute budgets. A
loop_count=kconfiguration performskblock applications per optimisation step, so higher loop counts do more work per step. Compare the measuredsecondscolumn (hardware-specific). - The ±5 percentage-point rule is an operational decision rule only. It is not a statistical significance test, confidence interval, or equivalence test. Results are scoped to this run, task, seeds, and sample sizes.
- Paired per-seed deltas (
paired_delta_vs_baseline, with mean/std/stderr) are reported per configuration alongside aggregate means. - H1, H2 and H0 are operational rules. H2 uses
drop = dev_accuracy(lengths 4–8) - heldout_accuracy(lengths 12–16), excluding training examples. A smaller gap can reflect worse dev performance, so it is exploratory and must be read alongside absolute accuracies. The corrected reference isresults/torch-cpu-dev-reference/. - Splits are example-disjoint by construction. Train/dev are drawn as one joint pool
of unique examples and randomly partitioned; held-out uses disjoint lengths and excludes
train/dev examples. The overlap count per seed is recorded and asserted to be zero;
make_splitsraises rather than returning overlapping examples. - Because unique examples are required, the empirical length distribution deviates from
uniform (the short-length example spaces are tiny); per-split
length_countsare recorded in the split manifest.
rlt_rsi.train_rsi adds a bounded iterative adaptation mode over the same parity task.
The search only mutates the recurrent loop schedule, selection uses the dev split, and
the held-out split is read only once at the end as a post-selection evaluation.
current loop count
→ bounded candidate loop counts (current, current + 1, current - 1, clamped to 1-8)
→ train each candidate on the train split for --rsi-epochs-per-gen
→ evaluate each candidate on the dev split
→ select the candidate with the best dev accuracy (ties broken by lower dev BCE)
→ carry the selected loop count into the next generation
→ final held-out evaluation of the selected lineage (post-selection only)
The held-out split is never used during candidate generation, scoring, or selection. Each
lineage entry records the candidate proposals, the accepted loop count, the post-training
train BCE, and dev accuracy/BCE; the run reports a finite final_train_bce.
# NumPy backend (frozen features; plumbing check, no torch required)
python -m rlt_rsi.train_rsi --backend numpy --seeds 7,42,123 \
--rsi-generations 5 --rsi-epochs-per-gen 8 --output results/rsi_smoke.json
# Torch backend (end-to-end training; device auto -> cuda, else mps, else cpu)
python -m rlt_rsi.train_rsi --backend torch --device auto --seeds 7,42,123 \
--rsi-generations 5 --rsi-epochs-per-gen 8 --output results/rsi_torch.json| Path | Contents |
|---|---|
results/smoke.json, results/smoke.md |
NumPy smoke output (frozen features; plumbing check only) |
results/archive-v1/ |
Superseded v1 smoke output, retained as evidence |
results/torch-smoke/ |
Tiny CPU torch smoke run (--epochs 2) |
results/torch-cpu-dev-reference/ |
Corrected CPU torch reference (JSON + manifest + report) |
results/torch-mps-2026-09-15/ |
Recorded MPS run discussed in the evaluation notes |
Checkpoint .pt files are small but not portable (machine/torch-build specific) and
are gitignored under results/*/checkpoints/; only the JSON manifest with SHA-256 hashes
is tracked. Regenerate them with the rerun command recorded in the manifest.
- Measured MPS run-to-run instability (this round). Re-running the recorded command
(
--backend torch --device auto --seeds 7,42,123 --epochs 80) reproduced the baseline and looped-2 results but changed looped-4: held-out accuracy 0.5052 → 0.5208 and paired delta +0.0417 → +0.0573, flipping the operational label fromflattoimprovement. MPS kernels are not bit-exact, so the ±0.05 label near the threshold is not stable across identical re-runs. - The hypotheses and decision rule are specified in advance of the run but are not a
preregistered protocol:
DESIGN.mdand the first results were committed in the same commit (161752a), so commit history does not establish precedence. - NumPy frozen-feature smoke results do not measure whether trained recurrence helps; they verify plumbing, determinism, and aggregation only.
- Torch runs train end-to-end, but with 3 seeds and small sample sizes the ±0.05 rule cannot establish significance or equivalence. Any "improvement" label is an operational-rule outcome for this run only.
- CI runs
pytest -qplus a NumPy smoke in a Python 3.11 job without torch, and the Torch-CPU job installs CPU torch and runs the full suite including the Torch RSI regression test.
Design references (scoped theory context)
- Khattab et al. (2024). DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines. ICLR 2024. arXiv:2310.03714.
- Chiang, Cholak & Pillay (2023). Tighter Bounds on the Expressivity of Transformer Encoders. ICML 2023, PMLR 202:5544–5562. link. Fixed-precision transformer encoders recognize only languages in uniform TC⁰; parity is in TC⁰, so no claim is made that transformers can never represent parity.
- Anil et al. (2022). Exploring Length Generalization in Large Language Models. NeurIPS 2022. link.
- Dehghani et al. (2019). Universal Transformers. ICLR 2019. arXiv:1807.03819.
- Giannou et al. (2023). Looped Transformers as Programmable Computers. ICML 2023. link.
| Path | Contents |
|---|---|
rlt_rsi/train.py |
Baseline and looped training, backends, evaluation |
rlt_rsi/train_rsi.py |
RSI-style iterative adaptation CLI and reporting |
rlt_rsi/rsi.py |
RSI loop (run_rsi_numpy, run_rsi_torch) |
rlt_rsi/data.py |
Example-disjoint split generation and manifests |
rlt_rsi/model.py |
NumPy model and configuration |
tests/ |
Data, model, generalization, RSI, and torch tests |
results/ |
Committed experiment artifacts |
notebooks/rlt_rsi_colab.ipynb |
CPU smoke + optional GPU walkthrough |
MIT — see LICENSE.