Create aleph_routed_trigram_lm.py
Browse files
experiments/exp_007_aleph_routed_attention/aleph_routed_trigram_lm.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# aleph_trigram_lm.py
|
| 2 |
+
"""
|
| 3 |
+
Aleph-Routed Trigram LM β the basin-test vehicle
|
| 4 |
+
=================================================
|
| 5 |
+
|
| 6 |
+
Causal language model over the SAME substrate the geolip-svae aleph batteries
|
| 7 |
+
were fed: WikiText-103 as a raw UTF-8 byte stream, trigram-packed (3 bytes per
|
| 8 |
+
position, stride 3 β the sequence analogue of ByteTrigramDataset's 3-bytes-per-
|
| 9 |
+
cell RGB encoding). One token position = one trigram, embedded byte-factored
|
| 10 |
+
(sum of 3 byte embeddings + position) and predicted as 3 independent 256-way
|
| 11 |
+
byte heads β exactly the substrate, no 256^3 softmax.
|
| 12 |
+
|
| 13 |
+
Purpose (preregistered β CORRECTED per RESEARCH_HISTORY.md):
|
| 14 |
+
The program established (Phase 2, discoveries #13/#15) at least TWO stable
|
| 15 |
+
codebook statutes, SELECTED BY SUBSTRATE: uniform-class (|dev| < 0.05; the
|
| 16 |
+
noise solvers) and polytope-class (dev > +0.05, pair fraction >= 45%;
|
| 17 |
+
repulsive packing β the BYTE-TRIGRAM solvers, in-distribution dev +0.083).
|
| 18 |
+
dev < -0.05 is degenerate (clumping) β the failure statute. Statute is a
|
| 19 |
+
property of (model x calibration), not the model alone.
|
| 20 |
+
|
| 21 |
+
Therefore, on THIS substrate, the basin question is statute-resolved:
|
| 22 |
+
- random init -> STATUTE-SELECTION test. Codebook settling polytope-class
|
| 23 |
+
(the substrate-matched statute) or uniform-class under pure attention
|
| 24 |
+
gradients = cross-objective attractor evidence. Degenerate = failure.
|
| 25 |
+
- fibonacci init -> DRIFT test. Starts in the uniform basin; migration
|
| 26 |
+
OUT toward polytope under the symbolic substrate = substrate-driven
|
| 27 |
+
statute selection in a new objective (the stronger result).
|
| 28 |
+
Run BOTH inits. Statute (deviation + pair fraction, computed per the
|
| 29 |
+
program's own Sec 3.11 definitions) is logged per snapshot inline below;
|
| 30 |
+
void-richness (beta_2/axis via ripser on projective angular distances,
|
| 31 |
+
the symbolic-substrate fingerprint, discovery #20) is the deeper follow-up
|
| 32 |
+
on the saved snapshots. Note the two non-interchangeable "margins"
|
| 33 |
+
(top-1 softmax probability vs projective |<m,a>|): the inline monitors
|
| 34 |
+
detect collapse only; structure evidence is deviation/statute/beta_2.
|
| 35 |
+
|
| 36 |
+
Substrate fidelity (mirrors geolip_svae.dataset_presets.ByteTrigramDataset):
|
| 37 |
+
- corpus: 'wikitext-103-raw-v1' via the Salesforce/wikitext namespace
|
| 38 |
+
(the bare 'wikitext' hub name is deprecated), or any local .txt path
|
| 39 |
+
- raw bytes in a single uint8 numpy array (prototypes/CLAUDE.md trap #3:
|
| 40 |
+
Python lists of ints are 5-7x the memory; never materialize them)
|
| 41 |
+
- seeded window sampling over the stream
|
| 42 |
+
Repo invariants honored: pure Adam (never AdamW), no BatchNorm/Dropout on the
|
| 43 |
+
geometric path, grad clip = max(task_loss, 1.0).
|
| 44 |
+
|
| 45 |
+
Usage (Colab / Blackwell):
|
| 46 |
+
from aleph_trigram_lm import TrigramLMConfig, train_trigram_lm
|
| 47 |
+
cfg = TrigramLMConfig(steps=10_000, device='cuda',
|
| 48 |
+
attn_mode='hub', codebook_init='random')
|
| 49 |
+
result = train_trigram_lm(cfg)
|
| 50 |
+
# result['codebook_snapshots'] -> [(step, (K, D_addr) tensor), ...]
|
| 51 |
+
# also saved to cfg.snapshot_path for the extraction pass
|
| 52 |
+
|
| 53 |
+
Baseline A/B: attn_mode='standard' runs the same LM with softmax attention.
|
| 54 |
+
|
| 55 |
+
Author: AbstractPhil + Mirel
|
| 56 |
+
Date: 2026-06-09
|
| 57 |
+
License: MIT
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
from __future__ import annotations
|
| 61 |
+
|
| 62 |
+
import math
|
| 63 |
+
import os
|
| 64 |
+
import time
|
| 65 |
+
from dataclasses import dataclass, field
|
| 66 |
+
from typing import Dict, List, Optional, Tuple
|
| 67 |
+
|
| 68 |
+
import numpy as np
|
| 69 |
+
import torch
|
| 70 |
+
import torch.nn as nn
|
| 71 |
+
import torch.nn.functional as F
|
| 72 |
+
from torch import Tensor
|
| 73 |
+
|
| 74 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 75 |
+
# Config
|
| 76 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 77 |
+
|
| 78 |
+
@dataclass
|
| 79 |
+
class TrigramLMConfig:
|
| 80 |
+
"""Everything for one basin run.
|
| 81 |
+
|
| 82 |
+
Substrate:
|
| 83 |
+
corpus_id: HF dataset config name (default = the aleph batteries'
|
| 84 |
+
corpus) OR a local .txt/.text path
|
| 85 |
+
max_corpus_bytes: cap on bytes loaded (None = whole corpus, ~520 MB for
|
| 86 |
+
wikitext-103). 50β100 MB is plenty for these runs.
|
| 87 |
+
seq_len: context length in TRIGRAMS (bytes seen = 3*seq_len)
|
| 88 |
+
|
| 89 |
+
Model:
|
| 90 |
+
dim/n_layers/n_heads: transformer shell
|
| 91 |
+
attn_mode: 'hub' | 'bucket' | 'standard'
|
| 92 |
+
K/D_addr/tau: aleph routing knobs (ignored for 'standard')
|
| 93 |
+
codebook_init: 'random' for the basin test (MANDATORY there) |
|
| 94 |
+
'fibonacci' | (K, D_addr) array transplant
|
| 95 |
+
|
| 96 |
+
Training:
|
| 97 |
+
pure Adam + cosine decay to 10%; loss reported in nats and bits/byte.
|
| 98 |
+
"""
|
| 99 |
+
# substrate
|
| 100 |
+
corpus_id: str = "wikitext-103-raw-v1"
|
| 101 |
+
split: str = "train"
|
| 102 |
+
max_corpus_bytes: Optional[int] = 100_000_000
|
| 103 |
+
seq_len: int = 256 # trigrams (= 768 bytes of context)
|
| 104 |
+
seed: int = 1234
|
| 105 |
+
|
| 106 |
+
# model
|
| 107 |
+
dim: int = 384
|
| 108 |
+
n_layers: int = 4
|
| 109 |
+
n_heads: int = 6
|
| 110 |
+
attn_mode: str = "hub" # 'hub' | 'bucket' | 'standard'
|
| 111 |
+
K: int = 64
|
| 112 |
+
D_addr: int = 4
|
| 113 |
+
tau: float = 0.1
|
| 114 |
+
codebook_init: object = "random" # basin test requires 'random'
|
| 115 |
+
div_weight: float = 0.0 # anti-collapse; run 0 first, observe
|
| 116 |
+
|
| 117 |
+
# paradigm + scale
|
| 118 |
+
shared_codebook: bool = True # ONE vocabulary, many speakers: all
|
| 119 |
+
# layers address the same (K,D) param,
|
| 120 |
+
# concentrating address pressure n_layers-x
|
| 121 |
+
accum_steps: int = 1 # gradient accumulation (effective batch
|
| 122 |
+
# = batch_size * accum_steps)
|
| 123 |
+
stream_segments: int = 1 # segments per sample, each seq_len long;
|
| 124 |
+
# codebook-memory state carried across
|
| 125 |
+
# (TBPTT, detached between segments).
|
| 126 |
+
# context = seq_len * stream_segments
|
| 127 |
+
# at CONSTANT attention memory. hub-only.
|
| 128 |
+
probe_rows: int = 200_000 # address-stats sample size (ppl estimator)
|
| 129 |
+
|
| 130 |
+
# training
|
| 131 |
+
steps: int = 2600 # optimizer steps (micro-batches =
|
| 132 |
+
# steps * accum_steps)
|
| 133 |
+
batch_size: int = 512
|
| 134 |
+
lr: float = 3e-4
|
| 135 |
+
lr_decay: bool = True
|
| 136 |
+
log_every: int = 100
|
| 137 |
+
eval_batches: int = 8
|
| 138 |
+
device: str = "cuda" if torch.cuda.is_available() else "cpu"
|
| 139 |
+
amp: bool = False # bf16 autocast on the shell (the
|
| 140 |
+
# address stays fp32 inside)
|
| 141 |
+
|
| 142 |
+
# outputs
|
| 143 |
+
snapshot_codebook: bool = True
|
| 144 |
+
snapshot_path: str = "aleph_lm_codebook_snapshots.pt"
|
| 145 |
+
checkpoint_path: Optional[str] = "aleph_trigram_lm.pt"
|
| 146 |
+
|
| 147 |
+
def __post_init__(self):
|
| 148 |
+
assert self.attn_mode in ("hub", "bucket", "standard")
|
| 149 |
+
assert self.dim % self.n_heads == 0
|
| 150 |
+
assert self.accum_steps >= 1 and self.stream_segments >= 1
|
| 151 |
+
if self.stream_segments > 1:
|
| 152 |
+
assert self.attn_mode == "hub", \
|
| 153 |
+
"streaming requires mode='hub' (bucket sorts globally)"
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 159 |
+
# Statute monitor β the program's own diagnostic geometry (Sec 3.11)
|
| 160 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 161 |
+
|
| 162 |
+
def _canon(x: Tensor) -> Tensor:
|
| 163 |
+
"""Sign-canonicalize onto RP^(D-1): flip so the first nonzero coord is
|
| 164 |
+
positive (antipodes map to one representative)."""
|
| 165 |
+
x = F.normalize(x, dim=-1)
|
| 166 |
+
first_nz = x[torch.arange(len(x)), x.abs().argmax(dim=-1)]
|
| 167 |
+
return x * torch.sign(first_nz).unsqueeze(-1)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def _mean_projective_angle(X: Tensor) -> float:
|
| 171 |
+
"""Mean pairwise acos|cos| over distinct pairs (radians)."""
|
| 172 |
+
c = (X @ X.t()).clamp(-1.0, 1.0).abs()
|
| 173 |
+
iu = torch.triu_indices(len(X), len(X), offset=1)
|
| 174 |
+
return torch.acos(c[iu[0], iu[1]]).mean().item()
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
_UNIFORM_BASELINE: Dict[int, float] = {}
|
| 178 |
+
|
| 179 |
+
def projective_deviation(axes: Tensor, n_ref: int = 4096,
|
| 180 |
+
seed: int = 0) -> float:
|
| 181 |
+
"""Uniformity deviation per the program definition: mean pairwise
|
| 182 |
+
projective angle of the axes MINUS the same statistic for n_ref uniform
|
| 183 |
+
random projective points at the same D. Signed; sign matters."""
|
| 184 |
+
D = axes.shape[-1]
|
| 185 |
+
if D not in _UNIFORM_BASELINE:
|
| 186 |
+
g = torch.Generator().manual_seed(seed)
|
| 187 |
+
ref = F.normalize(torch.randn(n_ref, D, generator=g), dim=-1)
|
| 188 |
+
_UNIFORM_BASELINE[D] = _mean_projective_angle(ref)
|
| 189 |
+
return _mean_projective_angle(F.normalize(axes.float(), dim=-1)) \
|
| 190 |
+
- _UNIFORM_BASELINE[D]
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def antipodal_pair_fraction(axes: Tensor, thresh: float = -0.9) -> float:
|
| 194 |
+
"""Fraction of rows in mutual most-negative pairs with cos < thresh
|
| 195 |
+
(the antipodal-collapse acceptance rule)."""
|
| 196 |
+
A = F.normalize(axes.float(), dim=-1)
|
| 197 |
+
c = A @ A.t()
|
| 198 |
+
c.fill_diagonal_(0.0)
|
| 199 |
+
partner = c.argmin(dim=-1)
|
| 200 |
+
val = c.gather(-1, partner.unsqueeze(-1)).squeeze(-1)
|
| 201 |
+
mutual = partner[partner] == torch.arange(len(A))
|
| 202 |
+
return ((val < thresh) & mutual).float().mean().item()
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def statute(axes: Tensor) -> Dict[str, object]:
|
| 206 |
+
"""Classify per the program taxonomy: dev > +0.05 polytope-class
|
| 207 |
+
(repulsive packing); |dev| < 0.05 uniform-class; dev < -0.05 degenerate
|
| 208 |
+
(clumping, the failure statute)."""
|
| 209 |
+
dev = projective_deviation(axes)
|
| 210 |
+
pf = antipodal_pair_fraction(axes)
|
| 211 |
+
cls = ("polytope" if dev > 0.05 else
|
| 212 |
+
"degenerate" if dev < -0.05 else "uniform")
|
| 213 |
+
return {"deviation": dev, "pair_fraction": pf, "statute": cls}
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 217 |
+
# Substrate β trigram stream (mirrors ByteTrigramDataset's loading)
|
| 218 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 219 |
+
|
| 220 |
+
class TrigramStream:
|
| 221 |
+
"""WikiText-103 (or local .txt) as a uint8 byte stream, sampled as
|
| 222 |
+
causal trigram sequences.
|
| 223 |
+
|
| 224 |
+
__call__(batch, seq_len) -> (ids, targets):
|
| 225 |
+
ids: (B, S, 3) uint8->long β trigram t = bytes[3t : 3t+3]
|
| 226 |
+
targets: (B, S, 3) β trigram t+1 (next-trigram prediction)
|
| 227 |
+
Windows are sampled at byte offsets aligned to stride 3 so the trigram
|
| 228 |
+
framing matches the image packing (cell i = bytes[3i : 3i+3])."""
|
| 229 |
+
|
| 230 |
+
def __init__(self, corpus_id: str, split: str = "train",
|
| 231 |
+
max_corpus_bytes: Optional[int] = None, seed: int = 1234):
|
| 232 |
+
if os.path.isfile(corpus_id) and corpus_id.endswith((".txt", ".text")):
|
| 233 |
+
print(f"[TrigramStream] loading local corpus {corpus_id} ...")
|
| 234 |
+
with open(corpus_id, "rb") as f:
|
| 235 |
+
raw = f.read(max_corpus_bytes) if max_corpus_bytes else f.read()
|
| 236 |
+
self.stream = np.frombuffer(raw, dtype=np.uint8).copy()
|
| 237 |
+
else:
|
| 238 |
+
print(f"[TrigramStream] loading HF corpus {corpus_id} ...")
|
| 239 |
+
from datasets import load_dataset
|
| 240 |
+
if corpus_id.startswith("wikitext"):
|
| 241 |
+
ds = load_dataset("Salesforce/wikitext", corpus_id, split=split)
|
| 242 |
+
else:
|
| 243 |
+
ds = load_dataset(corpus_id, split=split)
|
| 244 |
+
# accumulate utf-8 bytes directly into a byte buffer β never a
|
| 245 |
+
# Python list of ints (prototypes/CLAUDE.md memory trap #3)
|
| 246 |
+
buf = bytearray()
|
| 247 |
+
cap = max_corpus_bytes or float("inf")
|
| 248 |
+
for row in ds:
|
| 249 |
+
t = row.get("text", "")
|
| 250 |
+
if t:
|
| 251 |
+
buf.extend(t.encode("utf-8", errors="ignore"))
|
| 252 |
+
if len(buf) >= cap:
|
| 253 |
+
break
|
| 254 |
+
self.stream = np.frombuffer(
|
| 255 |
+
bytes(buf[: max_corpus_bytes] if max_corpus_bytes else buf),
|
| 256 |
+
dtype=np.uint8).copy()
|
| 257 |
+
n_tri = len(self.stream) // 3
|
| 258 |
+
print(f"[TrigramStream] {len(self.stream):,} bytes "
|
| 259 |
+
f"= {n_tri:,} trigrams")
|
| 260 |
+
assert n_tri > 0, "corpus too small"
|
| 261 |
+
self._rng = np.random.default_rng(seed)
|
| 262 |
+
|
| 263 |
+
def sample(self, batch: int, seq_len: int,
|
| 264 |
+
device) -> Tuple[Tensor, Tensor]:
|
| 265 |
+
need = 3 * (seq_len + 1) # +1 trigram for targets
|
| 266 |
+
hi = len(self.stream) - need
|
| 267 |
+
assert hi > 0, f"corpus shorter than one window ({need} bytes)"
|
| 268 |
+
starts = self._rng.integers(0, hi // 3, size=batch) * 3 # stride-3 aligned
|
| 269 |
+
idx = starts[:, None] + np.arange(need)[None, :] # (B, need)
|
| 270 |
+
window = self.stream[idx] # (B, need) uint8
|
| 271 |
+
tri = torch.from_numpy(window.astype(np.int64)) \
|
| 272 |
+
.view(batch, seq_len + 1, 3)
|
| 273 |
+
ids, targets = tri[:, :-1], tri[:, 1:]
|
| 274 |
+
return ids.to(device), targets.to(device)
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 278 |
+
# Model β byte-factored trigram LM
|
| 279 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 280 |
+
|
| 281 |
+
class TrigramLM(nn.Module):
|
| 282 |
+
"""Causal LM over trigram positions. Embedding = sum of three per-slot
|
| 283 |
+
byte embeddings (+ learned positions); head = three 256-way byte heads.
|
| 284 |
+
Geometric-path hygiene: no BatchNorm/Dropout, pure pre-LN residual shell."""
|
| 285 |
+
|
| 286 |
+
def __init__(self, cfg: TrigramLMConfig):
|
| 287 |
+
super().__init__()
|
| 288 |
+
self.cfg = cfg
|
| 289 |
+
d = cfg.dim
|
| 290 |
+
self.byte_emb = nn.ModuleList([nn.Embedding(256, d) for _ in range(3)])
|
| 291 |
+
self.pos = nn.Parameter(0.02 * torch.randn(1, cfg.seq_len, d))
|
| 292 |
+
|
| 293 |
+
def make_attn() -> nn.Module:
|
| 294 |
+
if cfg.attn_mode == "standard":
|
| 295 |
+
return StandardAttention(d, cfg.n_heads, causal=True)
|
| 296 |
+
return AlephRoutedAttention(AlephAttentionConfig(
|
| 297 |
+
dim=d, num_heads=cfg.n_heads, mode=cfg.attn_mode,
|
| 298 |
+
K=cfg.K, D_addr=cfg.D_addr, tau=cfg.tau, causal=True,
|
| 299 |
+
codebook_init=cfg.codebook_init))
|
| 300 |
+
|
| 301 |
+
self.layers = nn.ModuleList([
|
| 302 |
+
nn.ModuleDict({
|
| 303 |
+
"norm1": nn.LayerNorm(d), "attn": make_attn(),
|
| 304 |
+
"norm2": nn.LayerNorm(d),
|
| 305 |
+
"mlp": nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(),
|
| 306 |
+
nn.Linear(4 * d, d)),
|
| 307 |
+
}) for _ in range(cfg.n_layers)
|
| 308 |
+
])
|
| 309 |
+
self.norm_f = nn.LayerNorm(d)
|
| 310 |
+
self.heads = nn.ModuleList([nn.Linear(d, 256) for _ in range(3)])
|
| 311 |
+
|
| 312 |
+
# one vocabulary, many speakers: tie every layer's codebook to layer 0's
|
| 313 |
+
if cfg.shared_codebook and cfg.attn_mode in ("hub", "bucket"):
|
| 314 |
+
shared = self.layers[0]["attn"].codebook
|
| 315 |
+
for L in self.layers[1:]:
|
| 316 |
+
L["attn"].codebook = shared
|
| 317 |
+
|
| 318 |
+
def aleph_layers(self) -> List[AlephRoutedAttention]:
|
| 319 |
+
return [m for m in self.modules() if isinstance(m, AlephRoutedAttention)]
|
| 320 |
+
|
| 321 |
+
def backbone(self, ids: Tensor) -> Tensor:
|
| 322 |
+
"""ids: (B, S, 3) -> (B, S, dim)"""
|
| 323 |
+
x = sum(emb(ids[..., i]) for i, emb in enumerate(self.byte_emb))
|
| 324 |
+
x = x + self.pos[:, : ids.shape[1]]
|
| 325 |
+
for L in self.layers:
|
| 326 |
+
x = x + L["attn"](L["norm1"](x))
|
| 327 |
+
x = x + L["mlp"](L["norm2"](x))
|
| 328 |
+
return self.norm_f(x)
|
| 329 |
+
|
| 330 |
+
def forward(self, ids: Tensor) -> Tensor:
|
| 331 |
+
"""(B, S, 3) -> byte logits (B, S, 3, 256)"""
|
| 332 |
+
h = self.backbone(ids)
|
| 333 |
+
return torch.stack([head(h) for head in self.heads], dim=2)
|
| 334 |
+
|
| 335 |
+
def loss(self, ids: Tensor, targets: Tensor) -> Tensor:
|
| 336 |
+
"""Mean cross-entropy per byte (nats/byte). bpb = loss / ln 2."""
|
| 337 |
+
logits = self(ids) # (B,S,3,256)
|
| 338 |
+
return F.cross_entropy(logits.reshape(-1, 256), targets.reshape(-1))
|
| 339 |
+
|
| 340 |
+
# ββ streaming: segment-recurrent backbone (codebook memory carried) ββ
|
| 341 |
+
def stream_loss(self, ids: Tensor, targets: Tensor,
|
| 342 |
+
states: Optional[List] = None
|
| 343 |
+
) -> Tuple[Tensor, List]:
|
| 344 |
+
"""One SEGMENT with per-layer carried states. states[i] is layer i's
|
| 345 |
+
(Mp, Mm, zp, zm) or None. Returns (loss, new_states) β caller detaches
|
| 346 |
+
between backward passes (TBPTT-1: grads flow within segment; values
|
| 347 |
+
flow forever)."""
|
| 348 |
+
x = sum(emb(ids[..., i]) for i, emb in enumerate(self.byte_emb))
|
| 349 |
+
x = x + self.pos[:, : ids.shape[1]]
|
| 350 |
+
new_states: List = []
|
| 351 |
+
states = states or [None] * len(self.layers)
|
| 352 |
+
for L, st in zip(self.layers, states):
|
| 353 |
+
a, ns = L["attn"].forward_stream(L["norm1"](x), state=st)
|
| 354 |
+
x = x + a
|
| 355 |
+
x = x + L["mlp"](L["norm2"](x))
|
| 356 |
+
new_states.append(ns)
|
| 357 |
+
h = self.norm_f(x)
|
| 358 |
+
logits = torch.stack([head(h) for head in self.heads], dim=2)
|
| 359 |
+
loss = F.cross_entropy(logits.reshape(-1, 256), targets.reshape(-1))
|
| 360 |
+
return loss, new_states
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 364 |
+
# Training
|
| 365 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 366 |
+
|
| 367 |
+
def train_trigram_lm(cfg: TrigramLMConfig,
|
| 368 |
+
stream: Optional[TrigramStream] = None) -> Dict:
|
| 369 |
+
torch.manual_seed(cfg.seed)
|
| 370 |
+
dev = torch.device(cfg.device)
|
| 371 |
+
stream = stream or TrigramStream(cfg.corpus_id, cfg.split,
|
| 372 |
+
cfg.max_corpus_bytes, cfg.seed)
|
| 373 |
+
model = TrigramLM(cfg).to(dev)
|
| 374 |
+
n_params = sum(p.numel() for p in model.parameters())
|
| 375 |
+
opt = torch.optim.Adam(model.parameters(), lr=cfg.lr) # pure Adam, never AdamW
|
| 376 |
+
sched = (torch.optim.lr_scheduler.CosineAnnealingLR(
|
| 377 |
+
opt, T_max=cfg.steps, eta_min=cfg.lr * 0.1) if cfg.lr_decay else None)
|
| 378 |
+
|
| 379 |
+
alephs = model.aleph_layers()
|
| 380 |
+
for a in alephs:
|
| 381 |
+
a.emit_diversity = cfg.div_weight > 0
|
| 382 |
+
|
| 383 |
+
snapshots: List[Tuple[int, Tensor]] = []
|
| 384 |
+
if cfg.snapshot_codebook and alephs:
|
| 385 |
+
snapshots.append((0, alephs[0].export_codebook()))
|
| 386 |
+
|
| 387 |
+
print(f"\n=== trigram LM mode={cfg.attn_mode} params={n_params:,} "
|
| 388 |
+
f"ctx={cfg.seq_len}x{cfg.stream_segments} trigrams "
|
| 389 |
+
f"({3*cfg.seq_len*cfg.stream_segments} bytes) "
|
| 390 |
+
f"eff.batch={cfg.batch_size*cfg.accum_steps} "
|
| 391 |
+
f"shared_cb={cfg.shared_codebook} "
|
| 392 |
+
f"device={dev} ===")
|
| 393 |
+
autocast = (torch.autocast(device_type=dev.type, dtype=torch.bfloat16)
|
| 394 |
+
if cfg.amp and dev.type == "cuda" else None)
|
| 395 |
+
result: Dict = {"mode": cfg.attn_mode, "params": n_params}
|
| 396 |
+
t0 = time.time()
|
| 397 |
+
|
| 398 |
+
segs = cfg.stream_segments
|
| 399 |
+
micro_scale = 1.0 / (cfg.accum_steps * segs)
|
| 400 |
+
for step in range(1, cfg.steps + 1):
|
| 401 |
+
opt.zero_grad(set_to_none=True)
|
| 402 |
+
loss_sum, n_micro = 0.0, 0
|
| 403 |
+
for _ in range(cfg.accum_steps):
|
| 404 |
+
# one long sample, split into `segs` carried segments
|
| 405 |
+
ids, targets = stream.sample(cfg.batch_size, cfg.seq_len * segs, dev)
|
| 406 |
+
states = None
|
| 407 |
+
for s in range(segs):
|
| 408 |
+
sl = slice(s * cfg.seq_len, (s + 1) * cfg.seq_len)
|
| 409 |
+
seg_ids, seg_tgt = ids[:, sl], targets[:, sl]
|
| 410 |
+
if autocast:
|
| 411 |
+
with autocast:
|
| 412 |
+
if segs > 1:
|
| 413 |
+
loss, states = model.stream_loss(seg_ids, seg_tgt, states)
|
| 414 |
+
else:
|
| 415 |
+
loss = model.loss(seg_ids, seg_tgt)
|
| 416 |
+
else:
|
| 417 |
+
if segs > 1:
|
| 418 |
+
loss, states = model.stream_loss(seg_ids, seg_tgt, states)
|
| 419 |
+
else:
|
| 420 |
+
loss = model.loss(seg_ids, seg_tgt)
|
| 421 |
+
total = loss
|
| 422 |
+
if cfg.div_weight > 0 and alephs:
|
| 423 |
+
total = total + cfg.div_weight * sum(
|
| 424 |
+
a.diversity_loss() for a in alephs)
|
| 425 |
+
(total * micro_scale).backward()
|
| 426 |
+
loss_sum += loss.item(); n_micro += 1
|
| 427 |
+
if states is not None: # TBPTT boundary
|
| 428 |
+
states = [tuple(t.detach() for t in st) for st in states]
|
| 429 |
+
loss_avg = loss_sum / n_micro
|
| 430 |
+
gnorm = torch.nn.utils.clip_grad_norm_(
|
| 431 |
+
model.parameters(), max(loss_avg, 1.0)) # the clip rule
|
| 432 |
+
opt.step()
|
| 433 |
+
if sched is not None:
|
| 434 |
+
sched.step()
|
| 435 |
+
|
| 436 |
+
if step % cfg.log_every == 0 or step == cfg.steps:
|
| 437 |
+
bpb = loss_avg / math.log(2)
|
| 438 |
+
rate = (step * cfg.batch_size * cfg.seq_len * segs
|
| 439 |
+
* cfg.accum_steps) / (time.time() - t0)
|
| 440 |
+
line = (f" step {step:6d} loss {loss_avg:.4f} "
|
| 441 |
+
f"bpb {bpb:.3f} |g| {gnorm:.2f} {rate/1e3:.1f}k tri/s")
|
| 442 |
+
if alephs:
|
| 443 |
+
model.eval()
|
| 444 |
+
with torch.no_grad():
|
| 445 |
+
x_probe = model.backbone(
|
| 446 |
+
ids[: min(8, cfg.batch_size), -cfg.seq_len:])
|
| 447 |
+
st = alephs[0].address_stats(x_probe, max_rows=cfg.probe_rows)
|
| 448 |
+
model.train()
|
| 449 |
+
line += (f" ppl {st['perplexity']:.1f}/{st['max_perplexity']:.0f}"
|
| 450 |
+
f" margin {st['margin']:.4f}"
|
| 451 |
+
f" conf {st['confidence']:.3f}")
|
| 452 |
+
result.update(st)
|
| 453 |
+
if cfg.snapshot_codebook:
|
| 454 |
+
snapshots.append((step, alephs[0].export_codebook()))
|
| 455 |
+
print(line)
|
| 456 |
+
result.update({"loss": loss_avg, "bpb": bpb, "step": step})
|
| 457 |
+
|
| 458 |
+
if snapshots:
|
| 459 |
+
result["codebook_snapshots"] = snapshots
|
| 460 |
+
drift = (snapshots[-1][1] - snapshots[0][1]).norm().item()
|
| 461 |
+
result["codebook_drift"] = drift
|
| 462 |
+
traj = [(s, statute(cb)) for s, cb in snapshots]
|
| 463 |
+
result["statute_trajectory"] = traj
|
| 464 |
+
torch.save({"snapshots": snapshots, "statute_trajectory": traj,
|
| 465 |
+
"config": cfg.__dict__, "K": cfg.K, "D_addr": cfg.D_addr},
|
| 466 |
+
cfg.snapshot_path)
|
| 467 |
+
print(f"\n[basin] {len(snapshots)} snapshots -> {cfg.snapshot_path}"
|
| 468 |
+
f" drift |A_end - A_0| = {drift:.4f}")
|
| 469 |
+
print("[basin] statute trajectory (program taxonomy: polytope is the "
|
| 470 |
+
"substrate-matched\n statute for byte-trigram; uniform is "
|
| 471 |
+
"the noise/OOD statute; degenerate = failure):")
|
| 472 |
+
for s, st in traj:
|
| 473 |
+
print(f" step {s:6d} dev {st['deviation']:+.4f} "
|
| 474 |
+
f"pairs {st['pair_fraction']:.0%} -> {st['statute']}")
|
| 475 |
+
print("[basin] deeper follow-up on saved snapshots: beta_2/axis via "
|
| 476 |
+
"ripser on projective\n angular distances (the "
|
| 477 |
+
"void/symbolic fingerprint, discovery #20).")
|
| 478 |
+
if cfg.checkpoint_path:
|
| 479 |
+
torch.save({"model_state_dict": model.state_dict(),
|
| 480 |
+
"config": cfg.__dict__}, cfg.checkpoint_path)
|
| 481 |
+
print(f"[ckpt] saved -> {cfg.checkpoint_path}")
|
| 482 |
+
return result
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 486 |
+
# Activation
|
| 487 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 488 |
+
|
| 489 |
+
def _smoke(device: str = "cpu"):
|
| 490 |
+
"""End-to-end on a synthetic local corpus β no downloads."""
|
| 491 |
+
print("=" * 70)
|
| 492 |
+
print("aleph_trigram_lm β smoke (synthetic corpus)")
|
| 493 |
+
print("=" * 70)
|
| 494 |
+
path = "/tmp/_smoke_corpus.txt"
|
| 495 |
+
rng = np.random.default_rng(0)
|
| 496 |
+
words = [b"the", b"aleph", b"address", b"routes", b"attention",
|
| 497 |
+
b"through", b"a", b"learned", b"projective", b"codebook"]
|
| 498 |
+
with open(path, "wb") as f:
|
| 499 |
+
f.write(b" ".join(words[i] for i in rng.integers(0, len(words), 60_000)))
|
| 500 |
+
cfg = TrigramLMConfig(corpus_id=path, steps=30, log_every=10,
|
| 501 |
+
dim=96, n_layers=2, n_heads=4, K=16, seq_len=64,
|
| 502 |
+
batch_size=8, device=device,
|
| 503 |
+
snapshot_path="/tmp/_smoke_snaps.pt",
|
| 504 |
+
checkpoint_path=None)
|
| 505 |
+
r = train_trigram_lm(cfg)
|
| 506 |
+
assert "codebook_snapshots" in r and len(r["codebook_snapshots"]) >= 2
|
| 507 |
+
assert math.isfinite(r["loss"]) and r["loss"] < math.log(256)
|
| 508 |
+
print(f"\nsmoke OK β loss {r['loss']:.3f} (< ln256={math.log(256):.3f} prior), "
|
| 509 |
+
f"drift {r['codebook_drift']:.4f}, "
|
| 510 |
+
f"{len(r['codebook_snapshots'])} snapshots saved")
|
| 511 |
+
|
| 512 |
+
|
| 513 |
+
#if __name__ == "__main__":
|
| 514 |
+
# import argparse
|
| 515 |
+
# ap = argparse.ArgumentParser(description="Aleph trigram LM β basin run")
|
| 516 |
+
# ap.add_argument("--smoke-only", action="store_true")
|
| 517 |
+
# ap.add_argument("--mode", default="hub", choices=["hub", "bucket", "standard"])
|
| 518 |
+
# ap.add_argument("--steps", type=int, default=2600)
|
| 519 |
+
# ap.add_argument("--corpus-mb", type=int, default=100)
|
| 520 |
+
# ap.add_argument("--codebook-init", default="random")
|
| 521 |
+
# ap.add_argument("--device",
|
| 522 |
+
# default="cuda" if torch.cuda.is_available() else "cpu")
|
| 523 |
+
# args, _unknown = ap.parse_known_args() # notebook-safe (ignores -f kernel.json)
|
| 524 |
+
#
|
| 525 |
+
# if args.smoke_only:
|
| 526 |
+
# _smoke(device="cpu")
|
| 527 |
+
# else:
|
| 528 |
+
# cfg = TrigramLMConfig(attn_mode=args.mode, steps=args.steps,
|
| 529 |
+
# max_corpus_bytes=args.corpus_mb * 1_000_000,
|
| 530 |
+
# codebook_init=args.codebook_init,
|
| 531 |
+
# device=args.device)
|
| 532 |
+
# train_trigram_lm(cfg)
|