SPB Git

spb/wp31_uqo

Public

WP31 PRISME: phases 0-3 implementation, tests, data pipeline, training scripts

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Oct 30, 2026) Browse files

30 changed files +20,001 −0

added .gitignore +24 −0
@@ -0,0 +1,24 @@
1+.venv/
2+__pycache__/
3+*.pyc
4+.pytest_cache/
5+.hypothesis/
6+experiments/runs/**/*.pt
7+experiments/runs/**/*.safetensors
8+experiments/runs/**/*.bin
9+data/raw/
10+data/cache/
11+data/processed/*.bin
12+data/processed/*.npy
13+*.log.gz
14+paper/*.aux
15+paper/*.bbl
16+paper/*.blg
17+paper/*.fls
18+paper/*.fdb_latexmk
19+paper/*.out
20+paper/*.toc
21+paper/*.synctex.gz
22+paper/*.lof
23+paper/*.lot
24+.DS_Store
added CLAUDE.md +15 −0
@@ -0,0 +1,15 @@
1+# CLAUDE.md — WP31: PRISME-SLM (execution mandate)
2+
3+This repository executes UQO Working Paper 31, *PRISME: Intervention-Supervised Sparse Workspace Editing for
4+Small Language Models* (author: Simon-Pierre Boucher, UQO). The full research mandate (phases 0–15, immutable
5+rules, hypotheses H1–H7, baselines, ablations, paper structure) was supplied by the author on 2026-10-29 and is
6+reproduced in `docs/MANDATE.md`. Working rules that bind every session in this repository:
7+
8+1. Scientific truth first: never fabricate runs, metrics, citations, hardware or proofs. Every number in the
9+ manuscript traces to a run id in `experiments/runs/` and `results/claim_registry.csv`.
10+2. Phases are executed in order with acceptance gates logged in `PROGRESS.md`; failures go to `FAILURES.md`,
11+ design changes to `DECISIONS.md`. Unfavourable runs are never deleted.
12+3. Minimal falsifiable version first (no persistent memory, no RAG, no MoE, no RL, no tool use).
13+4. Resource boundaries: laptop (M5 Max) + existing MacLustr SSH access only; no purchases, no new credentials.
14+5. Independent train / calibration / dev / locked-test splits; test splits are not read before
15+ `protocol/preregistration.md` is frozen (see `protocol/locked_test_manifest.json`).
added DECISIONS.md +13 −0
@@ -0,0 +1,13 @@
1+# DECISIONS.md — design decisions (dated)
2+
3+| id | date | decision | rationale |
4+|---|---|---|---|
5+| D1 | 2026-10-29 | Repository lives at `Recherche-UQO/ia/prisme-slm` (new `ia/` category of the UQO series); Python 3.12 venv via `uv`, PyTorch 2.15 (CPU/MPS), fp32 training. | Series convention; bf16 autocast gave no speed-up on MPS (15.3k vs 15.0k tok/s on M3 Ultra). |
6+| D2 | 2026-10-29 | A dedicated `bank_norm` RMSNorm precedes the bank/z projections. | The pre-final-norm residual stream has growing norm; projecting it raw would couple editor dynamics to residual scale. Costs d parameters. |
7+| D3 | 2026-10-29 | Workspace/controller parameter counts are NOT forced to the brief's figures. | The brief's figures are reproduced exactly by a closed form (`4dw+Mw+17w²+6wf_w+15.5w+4`); the implemented design needs 7 fewer w×w projections per config (bank is read directly; readout uses raw slot values; controller shares one state MLP across the 24 actions so that scoring costs O(A·w²) not O(A·w·f_w)). Reconciliation reported in `results/tables/param_counts.json`. |
8+| D4 | 2026-10-29 | STOP is absorbing in forced-action training too. | Needed for incremental/full-prefix equivalence (test revealed the mismatch). |
9+| D5 | 2026-10-29 | Cost proxy c(a) in the controller's Q is the analytic ops ratio of the action (not a learned ĉ); the ĉ head is kept as a diagnostic regressor. | Per-action cost is deterministic given k(a) and T; learning it adds noise without information. |
10+| D6 | 2026-10-29 | Synthetic splits are deduplicated by exact text across splits (train written first); eval items colliding with train are dropped and counted. | Low-depth cells have few distinct items; 995 train dups, 256 dev_iid dups removed. |
11+| D7 | 2026-10-29 | Phase 4 uses a character-level tokenizer and a 2.1M-parameter backbone trained from scratch on the synthetic train split. | Self-contained learnability test independent of the web corpus/tokenizer. |
12+| D8 | 2026-10-29 | 103M pilot backbone: 196.6M tokens (12k steps × 32 × 512) of FineWeb-Edu, one seed; parameter-matched plain decoder baseline (ffn 2144, 102.16M) trained with the identical recipe on a second node. | ~3.7 h per run at 14.8k tok/s on an M3 Ultra; seeds are varied at the editor/controller stage where the pilot's decisive comparison lives. |
13+| D9 | 2026-10-29 | `readout_scale`: Δ = readout_scale · U(r(S_f) − r(S0)), with readout_scale a fixed constant set once at bootstrap to the measured rms of the backbone residual stream (5.6 for the tiny backbone). | Scale mismatch blocked the U = 0 bootstrap (see FAILURES). The final RMSNorm makes logits invariant to the joint scale of h and Δ, so this is a reparametrisation of U's learning rate; identity properties are unchanged (tests pass). |
added FAILURES.md +7 −0
@@ -0,0 +1,7 @@
1+# FAILURES.md — failed hypotheses, tests and attempts (never deleted)
2+
3+| date | what failed | evidence | resolution |
4+|---|---|---|---|
5+| 2026-10-29 | `test_incremental_equals_full_prefix_forced`: cached decoding ≠ full-prefix forward. | pytest, atol 1e-4 | Root cause: forced-action forward let edits resume after STOP; made STOP absorbing (D4). |
6+| 2026-10-29 | rsync exclusion `experiments` also dropped `configs/experiments/`; baseline launch on M3U96b failed twice (`FileNotFoundError`). | `experiments/logs/pretrain_enlarged102m_seed1.log` (first attempts) | anchored pattern `/experiments`. |
7+| 2026-10-29 | Stage B bootstrap degeneracy (brief §13.1 confirmed): with U = 0 and lr 1e-3, 700 steps of forced mixed-budget training left val loss identical at every budget (0.4144 / 0.4143 / 0.4143 / 0.4146 / 0.4164 for budgets 0/1/2/4/8); grad-norm 0.01–0.02. Diagnosis: scale mismatch — backbone residual rms 5.64 vs readout difference rms 0.06, so U must grow by ×100 before edits can affect logits. | `experiments/runs/p4_editor_v1_seed01_*/metrics.jsonl` (stopped at step ~750) | D9: fixed non-learned `readout_scale` = measured backbone residual rms at bootstrap (keeps U = 0 init and exact identity; equivalent to a scale-aware learning rate). Variants with nonzero U init (std 0.02) and lr 3e-3 kept running on M2U64 / M4M64c as evidence. |
added Makefile +10 −0
@@ -0,0 +1,10 @@
1+PY=.venv/bin/python
2+.PHONY: env test audit params flops paper figures smoke
3+env: ; uv venv --python 3.12 .venv && uv pip install -p .venv/bin/python -r requirements-lock.txt
4+test: ; $(PY) -m pytest tests -q
5+audit: ; $(PY) scripts/environment_audit.py --smoke
6+params: ; $(PY) scripts/count_params.py --all-configs --device meta
7+flops: ; $(PY) scripts/estimate_flops.py --config configs/prisme_459m.yaml --T 4096
8+smoke: ; $(PY) -m training.pretrain --config configs/experiments/pretrain_103m.yaml --dry-run
9+figures: ; $(PY) scripts/reproduce_figures.py --results results/raw/
10+paper: ; cd paper && latexmk -pdf -interaction=nonstopmode main.tex
added PROGRESS.md +10 −0
@@ -0,0 +1,10 @@
1+# PROGRESS.md — WP31 PRISME phase log
2+
3+| date | phase | status | commands / evidence | measurements | gate | blockers / next |
4+|---|---|---|---|---|---|---|
5+| 2026-10-29 | 0 env audit | done | `scripts/environment_audit.py --smoke` → `hardware/device_inventory.json`; `scripts/cluster_audit.sh` (read-only SSH) | laptop M5 Max 18c/40 GPU/48 GB, torch 2.15 MPS+CPU smoke OK; 12 LAN nodes reachable incl. 2× M3 Ultra 96 GB, M2 Ultra 64 GB, 3× M4 Max 64 GB | PASS | — |
6+| 2026-10-29 | 1 literature | done | `literature/papers.bib` (77 verified entries), `comparison_matrix.csv`, `novelty_audit.md` | all 12 mandated refs exist incl. RecurTrace 2609.03379 and ELR 2610.12582; C3 (differential zero-init bypass) judged not novel; C2 novel only in its multi-action+propensity form | PASS | preregistration frozen before any test split is read (see protocol/) |
7+| 2026-10-29 | 2 backbone + counts | done | `scripts/count_params.py --all-configs --device meta`; `scripts/estimate_flops.py` | backbone counts match brief exactly (99,502,848 / 446,744,064 / 1,149,339,648); workspace 2,631,173 vs brief 3,088,260 (reconciled, D3); ops table reproduces brief within 1.3 % | PASS | — |
8+| 2026-10-29 | 3 invariants | done | `python -m pytest tests -q` → 22 passed | 15 mandated invariants + controller target-freedom + dense ablation + 5 cache-equivalence tests | PASS | — |
9+| 2026-10-29 | 4 learnability | running | `training.train_editor --config configs/experiments/p4_tiny_backbone.yaml --device mps` (laptop) | — | — | then editor bootstrap on frozen tiny backbone |
10+| 2026-10-29 | 8 pilot (Stage A) | running | M3U96a: `pretrain_103m.yaml` (run `pretrain103m_seed01_*`); M3U96b: `pretrain_enlarged_102m.yaml` | 14.8k tok/s, 196.6M tokens planned (~3.7 h each) | — | editor/controller stages after completion |
added baselines/__init__.py +0 −0
added baselines/global_recurrence.py +78 −0
@@ -0,0 +1,78 @@
1+"""Global latent-recurrence adapter (Huginn/Ouro/ELR-*inspired*, simplified): the whole top representation is
2+refined r times by ONE shared block operating at workspace width w, with a zero-initialised residual back to d.
3+
4+Same frozen backbone, same forced mixed-budget training recipe and same budget semantics as PRISME; the only
5+difference is that every "action" rewrites the entire latent (global, dense) instead of 1-2 of 16 slots, and
6+there is no slot selection to learn. Disclosed simplifications: no prelude/coda retraining, no truncated
7+backprop through time, adapter-level recurrence on a frozen backbone rather than from-scratch recurrent pretraining.
8+Parameter count is matched to the PRISME workspace (ffn width chosen in the experiment config)."""
9+from __future__ import annotations
10+
11+import torch
12+import torch.nn as nn
13+
14+from prisme.attention import RMSNorm, SwiGLU
15+from prisme.backbone import Backbone
16+from prisme.config import PrismeConfig
17+import torch.nn.functional as F
18+
19+
20+class RecurrentBlock(nn.Module):
21+ def __init__(self, w: int, heads: int, ffn: int, eps: float):
22+ super().__init__()
23+ self.h, self.hd = heads, w // heads
24+ self.n1 = RMSNorm(w, eps); self.q = nn.Linear(w, w, bias=False); self.k = nn.Linear(w, w, bias=False)
25+ self.v = nn.Linear(w, w, bias=False); self.o = nn.Linear(w, w, bias=False)
26+ self.n2 = RMSNorm(w, eps); self.ffn = SwiGLU(w, ffn)
27+
28+ def forward(self, x, ctx_mask):
29+ B, T, w = x.shape
30+ xn = self.n1(x)
31+ q = self.q(xn).view(B, T, self.h, self.hd).transpose(1, 2)
32+ k = self.k(xn).view(B, T, self.h, self.hd).transpose(1, 2)
33+ v = self.v(xn).view(B, T, self.h, self.hd).transpose(1, 2)
34+ a = F.scaled_dot_product_attention(q, k, v, attn_mask=ctx_mask)
35+ x = x + self.o(a.transpose(1, 2).reshape(B, T, w))
36+ return x + self.ffn(self.n2(x))
37+
38+
39+class GlobalRecurrenceModel(nn.Module):
40+ """Interface-compatible with PrismeModel.forward(tokens, actions): any nonterminal action = one global
41+ refinement step; STOP = no step. n_edits semantics identical."""
42+
43+ def __init__(self, cfg: PrismeConfig, ffn_w: int | None = None, heads: int | None = None):
44+ super().__init__()
45+ self.cfg = cfg
46+ self.backbone = Backbone(cfg)
47+ w = cfg.w
48+ self.bank_norm = RMSNorm(cfg.d_model, cfg.norm_eps)
49+ self.down = nn.Linear(cfg.d_model, w, bias=False)
50+ self.block = RecurrentBlock(w, heads or cfg.n_editor_heads, ffn_w or cfg.ffn_w, cfg.norm_eps)
51+ self.ctx_proj = nn.Linear(2, w, bias=True)
52+ self.up = nn.Linear(w, cfg.d_model, bias=False)
53+ nn.init.zeros_(self.up.weight); nn.init.zeros_(self.ctx_proj.weight); nn.init.zeros_(self.ctx_proj.bias)
54+ self.controller = nn.Identity() # placeholder so parameter-group helpers work
55+
56+ def forward(self, tokens, actions=None, return_state=False):
57+ cfg = self.cfg
58+ H = self.backbone(tokens)
59+ if actions is None or actions.shape[-1] == 0:
60+ return {"logits": self.backbone.head(H), "H": H, "n_edits": None}
61+ B, T, R = actions.shape
62+ x0 = self.down(self.bank_norm(H))
63+ x = x0
64+ mask = torch.ones(T, T, dtype=torch.bool, device=H.device).tril().view(1, 1, T, T)
65+ stopped = torch.zeros(B, T, dtype=torch.bool, device=H.device)
66+ n_edits = torch.zeros(B, T, dtype=torch.long, device=H.device)
67+ for r in range(R):
68+ a = torch.where(stopped, torch.full_like(actions[:, :, r], cfg.stop_action), actions[:, :, r])
69+ act = (a != cfg.stop_action)
70+ stopped |= ~act
71+ ctx = torch.stack([torch.full((B, T), r / cfg.r_max, device=H.device), torch.full((B, T), (R - r - 1) / cfg.r_max, device=H.device)], -1)
72+ xn = self.block(x + self.ctx_proj(ctx), mask)
73+ x = torch.where(act.unsqueeze(-1), xn, x) # positions that stopped keep their state
74+ n_edits += act.long()
75+ delta = self.up(x - x0)
76+ edited = n_edits > 0
77+ delta = torch.where(edited.unsqueeze(-1), delta, torch.zeros_like(delta))
78+ return {"logits": self.backbone.head(H + delta), "H": H, "n_edits": n_edits, "delta_out": delta}
added baselines/routers.py +54 −0
@@ -0,0 +1,54 @@
1+"""Reference routing policies that share PRISME's editor (actions chosen without the learned value controller).
2+All are target-free and deployable; they differ only in how the action is picked at each step.
3+ random uniform over the 24 nonterminal actions, fixed budget b
4+ uniform round-robin single-slot schedule SINGLE_0, SINGLE_1, ... (deterministic, fixed budget b)
5+ fixed_pair PAIR_0_1 repeated (fixed budget b)
6+ entropy continue (random action) while the current predictive entropy exceeds a threshold, else STOP
7+ norm continue while the last write moved the slot by more than a threshold (RMS), else STOP
8+"""
9+from __future__ import annotations
10+
11+import torch
12+
13+from prisme.model import PrismeModel
14+
15+
16+@torch.no_grad()
17+def rollout(model: PrismeModel, tokens, policy: str, budget: int, threshold: float = 0.0, seed: int = 0):
18+ cfg = model.cfg
19+ ctx = model.encode(tokens)
20+ S = ctx.S0
21+ B, T = tokens.shape
22+ g = torch.Generator(device="cpu").manual_seed(seed)
23+ stopped = torch.zeros(B, T, dtype=torch.bool, device=tokens.device)
24+ n_edits = torch.zeros(B, T, dtype=torch.long, device=tokens.device)
25+ trace = torch.full((B, T, budget), cfg.stop_action, dtype=torch.long, device=tokens.device)
26+ last_rms = torch.full((B, T), float("inf"), device=tokens.device)
27+ for r in range(budget):
28+ if policy == "random":
29+ a = torch.randint(0, cfg.n_actions, (B, T), generator=g).to(tokens.device)
30+ elif policy == "uniform":
31+ a = torch.full((B, T), r % cfg.n_slots, device=tokens.device)
32+ elif policy == "fixed_pair":
33+ a = torch.full((B, T), cfg.n_slots + (r % cfg.n_pairs), device=tokens.device)
34+ elif policy == "entropy":
35+ logits, _ = model.logits_from_state(S, ctx, n_edits > 0)
36+ p = torch.softmax(logits.float(), -1)
37+ ent = -(p * torch.log(p.clamp_min(1e-12))).sum(-1)
38+ a = torch.randint(0, cfg.n_actions, (B, T), generator=g).to(tokens.device)
39+ a = torch.where(ent > threshold, a, torch.full_like(a, cfg.stop_action))
40+ elif policy == "norm":
41+ a = torch.randint(0, cfg.n_actions, (B, T), generator=g).to(tokens.device)
42+ a = torch.where(last_rms > threshold, a, torch.full_like(a, cfg.stop_action))
43+ else:
44+ raise ValueError(policy)
45+ a = torch.where(stopped, torch.full_like(a, cfg.stop_action), a)
46+ stopped |= a == cfg.stop_action
47+ trace[:, :, r] = a
48+ S_new, info = model.apply_action(S, ctx, a, r, budget - r - 1)
49+ moved = (info["eta"].squeeze(-1) * info["delta_rms"] * info["scale"]) # [B,T,k] realised rms step per lane
50+ last_rms = torch.where(a != cfg.stop_action, moved.max(-1).values, last_rms)
51+ S = S_new
52+ n_edits += (a != cfg.stop_action).long()
53+ logits, _ = model.logits_from_state(S, ctx, n_edits > 0)
54+ return {"logits": logits, "actions": trace, "n_edits": n_edits}
added configs/baselines/transformer_enlarged_102m.yaml +23 −0
@@ -0,0 +1,23 @@
1+# PRISME reference configuration: prisme_103m (see docs/architecture_spec.md)
2+model:
3+ name: transformer_enlarged_102m
4+ vocab_size: 32768
5+ d_model: 768
6+ n_layers: 12
7+ n_heads: 12
8+ n_kv_heads: 3
9+ ffn_width: 2144 # parameter-matched plain decoder: +2.65M params ~= PRISME workspace+controller (2.63M)
10+ max_seq_len: 4096
11+ rope_theta: 10000.0
12+ norm_eps: 1.0e-5
13+ tie_embeddings: true
14+ use_workspace: false
15+ w: 256
16+ ffn_w: 768
17+ n_slots: 16
18+ n_editor_blocks: 2
19+ n_editor_heads: 4
20+ r_max: 8
21+ eta_max: 0.25
22+ clip_c: 1.0
23+ readout_zero_init: true
added configs/experiments/controller_p4.yaml +14 −0
@@ -0,0 +1,14 @@
1+# Phase 6 (tiny scale): value controller fit on logged labels; (lambda, kappa) tuned on calibration rollouts.
2+name: controller_p4
3+seed: 1
4+mode: prisme
5+model: {name: tiny_p4, vocab_size: 128, d_model: 192, n_layers: 4, n_heads: 6, n_kv_heads: 2, ffn_width: 512,
6+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
7+checkpoint: CHECKPOINT_DIR
8+labels: {train: LABELS_TRAIN, calib: LABELS_CALIB}
9+train: {steps: 3000, lr: 1.0e-3, weight_decay: 0.01, alpha: 1.0, beta: 0.5, gamma: 0.1, huber_delta: 0.1, log_every: 100, eval_every: 1000, seq_len: 320}
10+lambda_grid: [0.0, 0.01, 0.03, 0.1, 0.3]
11+kappa_grid: [0.0, 0.5]
12+sweep_batches: 10
13+max_budget: 8
14+selection_rule: max_loss_reduction_per_ops
added configs/experiments/labels_p4.yaml +13 −0
@@ -0,0 +1,13 @@
1+# Phase 5 (tiny scale): counterfactual label collection with the fixed Phase 4 editor checkpoint.
2+name: labels_p4
3+seed: 1
4+mode: prisme
5+model: {name: tiny_p4, vocab_size: 128, d_model: 192, n_layers: 4, n_heads: 6, n_kv_heads: 2, ffn_width: 512,
6+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
7+checkpoint: CHECKPOINT_DIR
8+use_policy: false
9+data: {kind: synthetic, tokenizer: char, splits_dir: data/splits/synthetic_v1, loss_span: answer}
10+collect: {batch_size: 32, seq_len: 320, max_prefix: 3, audit_every: 4}
11+partitions:
12+ train: {split: train, n_batches: 120, stream_seed: 11}
13+ calib: {split: calib, n_batches: 30, stream_seed: 12}
added configs/experiments/p4_editor_bootstrap.yaml +16 −0
@@ -0,0 +1,16 @@
1+# Phase 4 step 2: Stage B editor bootstrap on the FROZEN tiny backbone (v1), forced mixed budgets {0,1,2,4,8}.
2+name: p4_editor_v1_scaled
3+seed: 1
4+mode: prisme
5+model: {name: tiny_p4, vocab_size: 128, d_model: 192, n_layers: 4, n_heads: 6, n_kv_heads: 2, ffn_width: 512,
6+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
7+init: {backbone_checkpoint: experiments/runs/p4_tiny_backbone_seed01_20261029232626/checkpoint, freeze_backbone: true, auto_readout_scale: true}
8+data: {kind: synthetic, tokenizer: char, splits_dir: data/splits/synthetic_v1, loss_span: answer,
9+ eval_splits: [dev_iid, dev_template, dev_depth, dev_compose]}
10+budgets: [0, 1, 2, 4, 8]
11+budget_probs: [0.2, 0.2, 0.2, 0.2, 0.2]
12+budget_per: position
13+train: {batch_size: 32, seq_len: 320, steps: 3000, lr: 1.0e-3, warmup: 100, weight_decay: 0.1, grad_clip: 1.0,
14+ log_every: 50, eval_every: 500, eval_batches: 8, ckpt_every: 1500}
15+em_budgets: [0, 2, 8]
16+em_max_items: 400
added configs/experiments/p4_editor_bootstrap_lr3e3.yaml +16 −0
@@ -0,0 +1,16 @@
1+# Phase 4 step 2: Stage B editor bootstrap on the FROZEN tiny backbone (v1), forced mixed budgets {0,1,2,4,8}.
2+name: p4_editor_v1_lr3e3_b64
3+seed: 1
4+mode: prisme
5+model: {name: tiny_p4, vocab_size: 128, d_model: 192, n_layers: 4, n_heads: 6, n_kv_heads: 2, ffn_width: 512,
6+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
7+init: {backbone_checkpoint: experiments/runs/p4_tiny_backbone_seed01_20261029232626/checkpoint, freeze_backbone: true}
8+data: {kind: synthetic, tokenizer: char, splits_dir: data/splits/synthetic_v1, loss_span: answer,
9+ eval_splits: [dev_iid, dev_template, dev_depth, dev_compose]}
10+budgets: [0, 1, 2, 4, 8]
11+budget_probs: [0.2, 0.2, 0.2, 0.2, 0.2]
12+budget_per: position
13+train: {batch_size: 64, seq_len: 320, steps: 2000, lr: 3.0e-3, warmup: 100, weight_decay: 0.1, grad_clip: 1.0,
14+ log_every: 50, eval_every: 500, eval_batches: 8, ckpt_every: 1500}
15+em_budgets: [0, 2, 8]
16+em_max_items: 400
added configs/experiments/p4_editor_bootstrap_nonzeroU.yaml +17 −0
@@ -0,0 +1,17 @@
1+# Phase 4 step 2: Stage B editor bootstrap on the FROZEN tiny backbone (v1), forced mixed budgets {0,1,2,4,8}.
2+name: p4_editor_v1_nonzeroU
3+seed: 1
4+mode: prisme
5+model: {name: tiny_p4, vocab_size: 128, d_model: 192, n_layers: 4, n_heads: 6, n_kv_heads: 2, ffn_width: 512,
6+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
7+init: {backbone_checkpoint: experiments/runs/p4_tiny_backbone_seed01_20261029232626/checkpoint, freeze_backbone: true}
8+data: {kind: synthetic, tokenizer: char, splits_dir: data/splits/synthetic_v1, loss_span: answer,
9+ eval_splits: [dev_iid, dev_template, dev_depth, dev_compose]}
10+budgets: [0, 1, 2, 4, 8]
11+budget_probs: [0.2, 0.2, 0.2, 0.2, 0.2]
12+budget_per: position
13+train: {batch_size: 32, seq_len: 320, steps: 2000, lr: 1.0e-3, warmup: 100, weight_decay: 0.1, grad_clip: 1.0,
14+ log_every: 50, eval_every: 500, eval_batches: 8, ckpt_every: 1500}
15+em_budgets: [0, 2, 8]
16+em_max_items: 400
17+model_overrides: {readout_zero_init: false}
added configs/experiments/p4_tiny_backbone.yaml +12 −0
@@ -0,0 +1,12 @@
1+# Phase 4 step 1: tiny backbone pretrained from scratch on the synthetic TRAIN split (char-level), workspace off.
2+name: p4_tiny_backbone
3+seed: 1
4+mode: backbone_only
5+model: {name: tiny_p4, vocab_size: 128, d_model: 192, n_layers: 4, n_heads: 6, n_kv_heads: 2, ffn_width: 512,
6+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
7+init: {freeze_backbone: false}
8+data: {kind: synthetic, tokenizer: char, splits_dir: data/splits/synthetic_v1, loss_span: all,
9+ eval_splits: [dev_iid, dev_template, dev_depth, dev_compose]}
10+train: {batch_size: 64, seq_len: 320, steps: 6000, lr: 1.0e-3, warmup: 200, weight_decay: 0.1, grad_clip: 1.0,
11+ log_every: 50, eval_every: 500, eval_batches: 10, ckpt_every: 2000}
12+em_max_items: 600
added configs/experiments/p4_tiny_backbone_v2.yaml +12 −0
@@ -0,0 +1,12 @@
1+# Phase 4 step 1 (v2): stronger tiny backbone (≈5.6M params) from scratch on the synthetic TRAIN split, char-level.
2+name: p4_tiny_backbone_v2
3+seed: 1
4+mode: backbone_only
5+model: {name: tiny_p4v2, vocab_size: 128, d_model: 256, n_layers: 6, n_heads: 8, n_kv_heads: 4, ffn_width: 768,
6+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
7+init: {freeze_backbone: false}
8+data: {kind: synthetic, tokenizer: char, splits_dir: data/splits/synthetic_v1, loss_span: all,
9+ eval_splits: [dev_iid, dev_template, dev_depth, dev_compose]}
10+train: {batch_size: 64, seq_len: 320, steps: 15000, lr: 1.0e-3, warmup: 300, weight_decay: 0.1, grad_clip: 1.0,
11+ log_every: 100, eval_every: 1000, eval_batches: 10, ckpt_every: 5000}
12+em_max_items: 800
added configs/experiments/pretrain_103m.yaml +18 −0
@@ -0,0 +1,18 @@
1+name: pretrain103m
2+seed: 1
3+model: configs/prisme_103m.yaml
4+data:
5+ train_bin: data/processed/fineweb_edu_300m/train.bin
6+ val_bin: data/processed/fineweb_edu_300m/val.bin
7+train:
8+ batch_size: 32
9+ seq_len: 512
10+ steps: 12000 # 32*512*12000 = 196.6M tokens (pilot budget; see protocol/preregistration.md)
11+ lr: 6.0e-4
12+ warmup: 300
13+ weight_decay: 0.1
14+ grad_clip: 1.0
15+ log_every: 20
16+ eval_every: 500
17+ eval_batches: 20
18+ ckpt_every: 1000
added configs/experiments/pretrain_enlarged_102m.yaml +18 −0
@@ -0,0 +1,18 @@
1+name: pretrain_enlarged102m
2+seed: 1
3+model: configs/baselines/transformer_enlarged_102m.yaml
4+data:
5+ train_bin: data/processed/fineweb_edu_300m/train.bin
6+ val_bin: data/processed/fineweb_edu_300m/val.bin
7+train:
8+ batch_size: 32
9+ seq_len: 512
10+ steps: 12000 # 32*512*12000 = 196.6M tokens (pilot budget; see protocol/preregistration.md)
11+ lr: 6.0e-4
12+ warmup: 300
13+ weight_decay: 0.1
14+ grad_clip: 1.0
15+ log_every: 20
16+ eval_every: 500
17+ eval_batches: 20
18+ ckpt_every: 1000
added configs/experiments/routing_p4.yaml +13 −0
@@ -0,0 +1,13 @@
1+# Routing comparison (tiny scale) on dev splits with the LOCKED controller checkpoint.
2+seed: 1
3+mode: prisme
4+model: {name: tiny_p4, vocab_size: 128, d_model: 192, n_layers: 4, n_heads: 6, n_kv_heads: 2, ffn_width: 512,
5+ max_seq_len: 320, w: 64, ffn_w: 192, n_slots: 16, n_editor_heads: 2, r_max: 8}
6+checkpoint: CHECKPOINT_DIR
7+data: {kind: synthetic, tokenizer: char, splits_dir: data/splits/synthetic_v1, loss_span: answer, split: dev_iid}
8+batch_size: 32
9+seq_len: 320
10+n_batches: 20
11+budgets: [1, 2, 4, 8]
12+oracle_batches: 4
13+two_step_batches: 1
added configs/prisme_103m.yaml +23 −0
@@ -0,0 +1,23 @@
1+# PRISME reference configuration: prisme_103m (see docs/architecture_spec.md)
2+model:
3+ name: prisme_103m
4+ vocab_size: 32768
5+ d_model: 768
6+ n_layers: 12
7+ n_heads: 12
8+ n_kv_heads: 3
9+ ffn_width: 2048
10+ max_seq_len: 4096
11+ rope_theta: 10000.0
12+ norm_eps: 1.0e-5
13+ tie_embeddings: true
14+ use_workspace: true
15+ w: 256
16+ ffn_w: 768
17+ n_slots: 16
18+ n_editor_blocks: 2
19+ n_editor_heads: 4
20+ r_max: 8
21+ eta_max: 0.25
22+ clip_c: 1.0
23+ readout_zero_init: true
added configs/prisme_1180m.yaml +23 −0
@@ -0,0 +1,23 @@
1+# PRISME reference configuration: prisme_1180m (see docs/architecture_spec.md)
2+model:
3+ name: prisme_1180m
4+ vocab_size: 32768
5+ d_model: 2048
6+ n_layers: 24
7+ n_heads: 32
8+ n_kv_heads: 8
9+ ffn_width: 5632
10+ max_seq_len: 4096
11+ rope_theta: 10000.0
12+ norm_eps: 1.0e-5
13+ tie_embeddings: true
14+ use_workspace: true
15+ w: 768
16+ ffn_w: 2048
17+ n_slots: 16
18+ n_editor_blocks: 2
19+ n_editor_heads: 12
20+ r_max: 8
21+ eta_max: 0.25
22+ clip_c: 1.0
23+ readout_zero_init: true
added configs/prisme_459m.yaml +23 −0
@@ -0,0 +1,23 @@
1+# PRISME reference configuration: prisme_459m (see docs/architecture_spec.md)
2+model:
3+ name: prisme_459m
4+ vocab_size: 32768
5+ d_model: 1536
6+ n_layers: 16
7+ n_heads: 24
8+ n_kv_heads: 6
9+ ffn_width: 4096
10+ max_seq_len: 4096
11+ rope_theta: 10000.0
12+ norm_eps: 1.0e-5
13+ tie_embeddings: true
14+ use_workspace: true
15+ w: 512
16+ ffn_w: 1536
17+ n_slots: 16
18+ n_editor_blocks: 2
19+ n_editor_heads: 8
20+ r_max: 8
21+ eta_max: 0.25
22+ clip_c: 1.0
23+ readout_zero_init: true
added data/__init__.py +0 −0
added data/char_tokenizer.py +29 −0
@@ -0,0 +1,29 @@
1+"""Self-contained character-level tokenizer for the Phase 4 learnability experiments (no corpus dependency)."""
2+from __future__ import annotations
3+
4+import json
5+from pathlib import Path
6+
7+PRINTABLE = [chr(i) for i in range(32, 127)]
8+
9+
10+class CharTokenizer:
11+ def __init__(self):
12+ self.specials = ["<eot>", "<pad>"]
13+ self.itos = self.specials + PRINTABLE
14+ self.stoi = {s: i for i, s in enumerate(self.itos)}
15+ self.eot_id = 0
16+ self.pad_id = 1
17+
18+ @property
19+ def vocab_size(self) -> int:
20+ return len(self.itos)
21+
22+ def encode(self, text: str) -> list[int]:
23+ return [self.stoi.get(ch, self.stoi["?"]) for ch in text]
24+
25+ def decode(self, ids) -> str:
26+ return "".join(self.itos[i] if i >= len(self.specials) else "" for i in ids)
27+
28+ def save(self, path):
29+ Path(path).write_text(json.dumps({"itos": self.itos}))
added data/generators/__init__.py +0 −0
added data/generators/synthetic.py +236 −0
@@ -0,0 +1,236 @@
1+"""Verifiable synthetic task generators with programmatic ground truth.
2+
3+Families (each item is a dict: family, template, depth, prompt, answer, text, meta):
4+ arith multi-step arithmetic chains (depth = number of operations)
5+ bind variable binding + dependent substitution (depth = chain length)
6+ fsm register / finite-state updates (depth = number of updates)
7+ excl conjunctive constraints with distractors (depth = number of constraints)
8+ sched rule-based selection under a budget and exclusions (depth = number of candidates)
9+ compose held-out composition: bind chain feeding an arithmetic expression (depth = total steps)
10+
11+Splitting is done BEFORE sampling: by (family, template id, depth regime, seed stream). Train uses templates
12+T0-T2 and depths <= max_train_depth; held-out "template" items use templates T3-T4; held-out "depth" items use
13+depths > max_train_depth; the compose family is entirely held out from training unless explicitly requested.
14+Text format: "<prompt> Answer: <answer>" ; the answer is the only supervised span for exact-match evaluation.
15+"""
16+from __future__ import annotations
17+
18+import hashlib
19+import json
20+import random
21+from dataclasses import dataclass, asdict
22+from typing import Callable
23+
24+OPS = {"+": lambda a, b: a + b, "-": lambda a, b: a - b, "*": lambda a, b: a * b}
25+
26+# Phrasing templates: index 0..2 = train, 3..4 = held-out paraphrases.
27+TEMPLATES = {
28+ "arith": ["Compute {expr}.", "What is {expr}?", "Evaluate the expression {expr}.", "Calculate {expr} step by step and give the result.", "Find the value of {expr}."],
29+ "bind": ["Let {chain}. What is {var}?", "Given {chain}, compute {var}.", "{chain}. Find {var}.", "Suppose {chain}. Then {var} equals what?", "With {chain}, the value of {var} is?"],
30+ "fsm": ["Start with register {init}. {ops}. What is the register?", "The counter begins at {init}. {ops}. Final counter?", "Initial value {init}. {ops}. Result?", "A register holds {init}. Apply: {ops}. What does it hold now?", "Begin at {init}; {ops}; final value?"],
31+ "excl": ["Items: {items}. Keep the items that are {rules}. Which items remain?", "Consider {items}. Select those that are {rules}. List them.", "{items}. Filter: {rules}. Remaining items?", "Given the list {items}, keep only the ones {rules}. Answer with the names.", "From {items}, which satisfy: {rules}?"],
32+ "sched": ["Budget {budget}. Tasks: {tasks}. Rule: {rule}. Choose the affordable task with the highest value.", "You have {budget} units. Options: {tasks}. Constraint: {rule}. Which affordable option has the largest value?", "Capacity {budget}. Candidates: {tasks}. Exclusion: {rule}. Pick the best affordable candidate.", "With a budget of {budget}, among {tasks}, under the rule {rule}, which task maximises value while staying affordable?", "Budget is {budget}. {tasks}. {rule}. Best affordable task by value?"],
33+ "compose": ["Let {chain}. Compute {expr}.", "Given {chain}, what is {expr}?", "{chain}. Evaluate {expr}.", "Suppose {chain}. Then find {expr}.", "With {chain}, calculate {expr}."],
34+}
35+
36+
37+def _rng(family: str, template: int, depth: int, seed: int, idx: int) -> random.Random:
38+ key = f"{family}|{template}|{depth}|{seed}|{idx}"
39+ return random.Random(int(hashlib.sha256(key.encode()).hexdigest()[:16], 16))
40+
41+
42+def gen_arith(r: random.Random, depth: int, template: int):
43+ vals = [r.randint(1, 20)]
44+ ops = []
45+ expr = str(vals[0])
46+ cur = vals[0]
47+ for _ in range(depth):
48+ op = r.choice(["+", "-", "*"])
49+ b = r.randint(1, 9) if op == "*" else r.randint(1, 30)
50+ cur = OPS[op](cur, b) # left-to-right evaluation (stated below)
51+ expr += f" {op} {b}"
52+ ops.append(op)
53+ prompt = TEMPLATES["arith"][template].format(expr=expr) + " Evaluate left to right."
54+ return prompt, str(cur), {"ops": "".join(ops)}
55+
56+
57+def gen_bind(r: random.Random, depth: int, template: int):
58+ names = r.sample(list("abcdefghjkmnpqrstuvwxyz"), depth + 1)
59+ vals = {names[0]: r.randint(1, 15)}
60+ parts = [f"{names[0]} = {vals[names[0]]}"]
61+ for i in range(1, depth + 1):
62+ op = r.choice(["+", "-", "*"])
63+ b = r.randint(1, 5) if op == "*" else r.randint(1, 12)
64+ src = names[i - 1]
65+ vals[names[i]] = OPS[op](vals[src], b)
66+ parts.append(f"{names[i]} = {src} {op} {b}")
67+ # distractor: an unused variable
68+ d = r.choice([n for n in "ABCDEFG"])
69+ parts.insert(r.randint(1, len(parts)), f"{d} = {r.randint(1, 40)}")
70+ chain = ", ".join(parts)
71+ target = names[depth]
72+ prompt = TEMPLATES["bind"][template].format(chain=chain, var=target)
73+ return prompt, str(vals[target]), {"target": target}
74+
75+
76+def gen_fsm(r: random.Random, depth: int, template: int):
77+ init = r.randint(0, 10)
78+ cur = init
79+ steps = []
80+ for _ in range(depth):
81+ op = r.choice(["add", "sub", "double", "reset", "halve"])
82+ if op == "add":
83+ b = r.randint(1, 9); cur += b; steps.append(f"add {b}")
84+ elif op == "sub":
85+ b = r.randint(1, 9); cur -= b; steps.append(f"subtract {b}")
86+ elif op == "double":
87+ cur *= 2; steps.append("double it")
88+ elif op == "halve":
89+ cur = cur // 2; steps.append("halve it (round down)")
90+ else:
91+ b = r.randint(0, 9); cur = b; steps.append(f"reset to {b}")
92+ prompt = TEMPLATES["fsm"][template].format(init=init, ops=", then ".join(steps))
93+ return prompt, str(cur), {}
94+
95+
96+def gen_excl(r: random.Random, depth: int, template: int):
97+ n = r.randint(4, 7)
98+ names = r.sample(["apple", "pear", "plum", "fig", "kiwi", "lime", "date", "yam", "nut", "bean"], n)
99+ items = {nm: (r.randint(1, 30), r.choice(["red", "green", "blue"])) for nm in names}
100+ rules = []
101+ def keep(v, c):
102+ return True
103+ preds: list[Callable] = []
104+ for _ in range(depth):
105+ kind = r.choice(["gt", "lt", "color", "even"])
106+ if kind == "gt":
107+ t = r.randint(5, 25); rules.append(f"greater than {t}"); preds.append(lambda v, c, t=t: v > t)
108+ elif kind == "lt":
109+ t = r.randint(5, 25); rules.append(f"smaller than {t}"); preds.append(lambda v, c, t=t: v < t)
110+ elif kind == "color":
111+ col = r.choice(["red", "green", "blue"]); rules.append(f"not {col}"); preds.append(lambda v, c, col=col: c != col)
112+ else:
113+ rules.append("even"); preds.append(lambda v, c: v % 2 == 0)
114+ remaining = [nm for nm in names if all(p(*items[nm]) for p in preds)]
115+ items_s = ", ".join(f"{nm} ({v}, {c})" for nm, (v, c) in items.items())
116+ prompt = TEMPLATES["excl"][template].format(items=items_s, rules=" and ".join(rules))
117+ ans = " ".join(remaining) if remaining else "none"
118+ return prompt, ans, {"n_items": n, "n_remaining": len(remaining)}
119+
120+
121+def gen_sched(r: random.Random, depth: int, template: int):
122+ names = [chr(ord("A") + i) for i in range(depth)]
123+ tasks = {nm: (r.randint(1, 12), r.randint(1, 20)) for nm in names}
124+ budget = r.randint(4, 12)
125+ kind = r.choice(["cost_gt", "value_lt", "none"])
126+ if kind == "cost_gt":
127+ t = r.randint(3, 10); rule = f"exclude tasks with cost greater than {t}"; ok = lambda c, v: c <= t
128+ elif kind == "value_lt":
129+ t = r.randint(3, 10); rule = f"exclude tasks with value below {t}"; ok = lambda c, v: v >= t
130+ else:
131+ rule = "no exclusion"; ok = lambda c, v: True
132+ feas = [(v, nm) for nm, (c, v) in tasks.items() if c <= budget and ok(c, v)]
133+ if not feas:
134+ ans = "none"
135+ else:
136+ best = max(v for v, _ in feas)
137+ ans = sorted(nm for v, nm in feas if v == best)[0]
138+ tasks_s = ", ".join(f"{nm} (cost {c}, value {v})" for nm, (c, v) in tasks.items())
139+ prompt = TEMPLATES["sched"][template].format(budget=budget, tasks=tasks_s, rule=rule)
140+ return prompt, ans, {"n_feasible": len(feas)}
141+
142+
143+def gen_compose(r: random.Random, depth: int, template: int):
144+ # bind chain of length depth-2 feeding a 2-op arithmetic expression over two chain variables
145+ k = max(1, depth - 2)
146+ names = r.sample(list("abcdefghjkmnpqrstuvwxyz"), k + 1)
147+ vals = {names[0]: r.randint(1, 10)}
148+ parts = [f"{names[0]} = {vals[names[0]]}"]
149+ for i in range(1, k + 1):
150+ op = r.choice(["+", "-"]); b = r.randint(1, 9); src = names[i - 1]
151+ vals[names[i]] = OPS[op](vals[src], b); parts.append(f"{names[i]} = {src} {op} {b}")
152+ v1, v2 = r.sample(names, 2) if len(names) > 1 else (names[0], names[0])
153+ op1, op2 = r.choice(["+", "-", "*"]), r.choice(["+", "-"])
154+ c = r.randint(1, 9)
155+ val = OPS[op2](OPS[op1](vals[v1], vals[v2]), c)
156+ expr = f"{v1} {op1} {v2} {op2} {c} (left to right)"
157+ prompt = TEMPLATES["compose"][template].format(chain=", ".join(parts), expr=expr)
158+ return prompt, str(val), {}
159+
160+
161+GENERATORS = {"arith": gen_arith, "bind": gen_bind, "fsm": gen_fsm, "excl": gen_excl, "sched": gen_sched, "compose": gen_compose}
162+
163+
164+def make_item(family: str, template: int, depth: int, seed: int, idx: int) -> dict:
165+ r = _rng(family, template, depth, seed, idx)
166+ prompt, answer, meta = GENERATORS[family](r, depth, template)
167+ text = f"{prompt} Answer: {answer}"
168+ return {"family": family, "template": template, "depth": depth, "seed": seed, "idx": idx,
169+ "prompt": prompt + " Answer:", "answer": " " + answer, "text": text, "meta": meta,
170+ "uid": hashlib.sha256(text.encode()).hexdigest()[:16]}
171+
172+
173+@dataclass
174+class SplitSpec:
175+ name: str
176+ families: tuple[str, ...]
177+ templates: tuple[int, ...]
178+ depths: tuple[int, ...]
179+ seed: int
180+ n_per_cell: int
181+
182+
183+def generate_split(spec: SplitSpec) -> list[dict]:
184+ items = []
185+ for fam in spec.families:
186+ for tpl in spec.templates:
187+ for dep in spec.depths:
188+ for i in range(spec.n_per_cell):
189+ items.append(make_item(fam, tpl, dep, spec.seed, i))
190+ return items
191+
192+
193+def default_splits(n_train_per_cell: int = 400, n_eval_per_cell: int = 60) -> dict[str, SplitSpec]:
194+ """Train: 5 families, templates 0-2, depths 1-4, seed 1. Dev: seed 2 same cells (IID generalisation).
195+ Held-out template: templates 3-4. Held-out depth: depths 5-6. Compose: fully held-out family.
196+ Final test: seed 9 across all eval regimes (LOCKED until preregistration is frozen)."""
197+ fams = ("arith", "bind", "fsm", "excl", "sched")
198+ return {
199+ "train": SplitSpec("train", fams, (0, 1, 2), (1, 2, 3, 4), 1, n_train_per_cell),
200+ "calib": SplitSpec("calib", fams, (0, 1, 2), (1, 2, 3, 4), 3, n_eval_per_cell // 2),
201+ "dev_iid": SplitSpec("dev_iid", fams, (0, 1, 2), (1, 2, 3, 4), 2, n_eval_per_cell),
202+ "dev_template": SplitSpec("dev_template", fams, (3, 4), (1, 2, 3, 4), 2, n_eval_per_cell),
203+ "dev_depth": SplitSpec("dev_depth", fams, (0, 1, 2), (5, 6), 2, n_eval_per_cell),
204+ "dev_compose": SplitSpec("dev_compose", ("compose",), (0, 1, 2), (3, 4, 5), 2, n_eval_per_cell),
205+ "test_iid": SplitSpec("test_iid", fams, (0, 1, 2), (1, 2, 3, 4), 9, n_eval_per_cell),
206+ "test_template": SplitSpec("test_template", fams, (3, 4), (1, 2, 3, 4), 9, n_eval_per_cell),
207+ "test_depth": SplitSpec("test_depth", fams, (0, 1, 2), (5, 6), 9, n_eval_per_cell),
208+ "test_compose": SplitSpec("test_compose", ("compose",), (0, 1, 2), (3, 4, 5), 9, n_eval_per_cell),
209+ }
210+
211+
212+def write_splits(out_dir: str, n_train_per_cell: int = 400, n_eval_per_cell: int = 60) -> dict:
213+ from pathlib import Path
214+ out = Path(out_dir); out.mkdir(parents=True, exist_ok=True)
215+ manifest = {}
216+ all_uids: dict[str, str] = {}
217+ for name, spec in default_splits(n_train_per_cell, n_eval_per_cell).items():
218+ items = generate_split(spec)
219+ # cross-split exact-duplicate removal: an item whose full text already appeared in an earlier split
220+ # (train is written first) is DROPPED from the later split; within-split duplicates are dropped too.
221+ kept, dups = [], 0
222+ for it in items:
223+ if it["uid"] in all_uids:
224+ dups += 1
225+ continue
226+ all_uids[it["uid"]] = name
227+ kept.append(it)
228+ items = kept
229+ p = out / f"{name}.jsonl"
230+ with open(p, "w") as f:
231+ for it in items:
232+ f.write(json.dumps(it) + "\n")
233+ manifest[name] = {"spec": asdict(spec), "n": len(items), "duplicates_dropped": dups,
234+ "sha256": hashlib.sha256(p.read_bytes()).hexdigest(), "locked": name.startswith("test")}
235+ (out / "manifest.json").write_text(json.dumps(manifest, indent=2))
236+ return manifest
added data/processed/fineweb_edu_300m/manifest.json +48 −0
@@ -0,0 +1,48 @@
1+{
2+ "source": "HuggingFaceFW/fineweb-edu sample-10BT (streaming, canonical shard order)",
3+ "license": "ODC-By 1.0 (https://opendatacommons.org/licenses/by/1-0/)",
4+ "prepared_at": "2026-10-29T23:19:53-0400",
5+ "args": {
6+ "out": "data/processed/fineweb_edu_300m",
7+ "target_train_tokens": 300000000,
8+ "vocab_size": 32768,
9+ "tokenizer_docs": 60000,
10+ "val_pct": 0.01,
11+ "test_pct": 0.01,
12+ "max_doc_chars": 40000
13+ },
14+ "docs_streamed": 291647,
15+ "docs": {
16+ "train": 285604,
17+ "val": 2957,
18+ "test": 2973
19+ },
20+ "chars": {
21+ "train": 1290001347,
22+ "val": 12988848,
23+ "test": 12933032
24+ },
25+ "splits": {
26+ "train": {
27+ "tokens": 289959006,
28+ "docs": 285604,
29+ "sha256": "e931e83c4ba6e62a2125e50a5390e8df78913dbed31af805d8af17fafad95123",
30+ "locked": false
31+ },
32+ "val": {
33+ "tokens": 2913824,
34+ "docs": 2957,
35+ "sha256": "0565e3f4a4028b241f40ef07f20bc68b24e45d99e147860be066e3667d1532be",
36+ "locked": false
37+ },
38+ "test": {
39+ "tokens": 2883436,
40+ "docs": 2973,
41+ "sha256": "1ce9ca3ab6d659fe08042844fc0589951e09386ee79d2ef76504ffaaebb0947b",
42+ "locked": true
43+ }
44+ },
45+ "tokenizer_sha256": "5a78df618767945743623d5ff238d035839d02388b4c53c548a437b3d8a206ee",
46+ "eot_id": 0,
47+ "dtype": "uint16"
48+}
\ No newline at end of file
added data/processed/fineweb_edu_300m/test.bin +0 −0

Binary file not shown.

added data/processed/fineweb_edu_300m/tokenizer.json +19236 −0
Large diff (19,236 lines) is not loaded by default to keep the page fast.Load diff

Diff truncated — file too large.

Menu

Theme