AbstractPhil commited on
Commit
408da7e
Β·
verified Β·
1 Parent(s): 9745fcf

Update experiments/exp_007_aleph_routed_attention/aleph_routed_trigram_lm.py

Browse files
experiments/exp_007_aleph_routed_attention/aleph_routed_trigram_lm.py CHANGED
@@ -1,60 +1,67 @@
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
@@ -62,7 +69,7 @@ from __future__ import annotations
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
@@ -76,250 +83,292 @@ from torch import Tensor
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:
@@ -327,206 +376,417 @@ class TrigramLM(nn.Module):
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)
 
 
1
+ # aleph_lm.py
2
  """
3
+ AlephLM β€” prediction through the codebook, with guarantees
4
+ ===========================================================
5
+
6
+ The composite reduction (2026-06-09): a causal trigram LM in which the aleph
7
+ signed-projective address is load-bearing at ALL THREE stations β€” input
8
+ addressing, mixing, and prediction. The codebook receives gradient from
9
+ routing, from the predicted next-address pi, and from every candidate address
10
+ kappa. One geometry, closed loop, smooth everywhere (no argmax in the train
11
+ path): differential trigram-to-trigram prediction.
12
+
13
+ CODEC bytes -> trigrams g_t (stride 3)
14
+ EMBED e_t = sum_c E_c[g_t[c]] (byte-factored)
15
+ MIX AlephRoutedAttention hub layers, shared codebook, causal
16
+ PREDICT pi = softmax([w; -w]), w = W_pi h_t (free antipodal-tied)
17
+ CANDIDATE kappa(tau) = address(normalize(W_k sum_c E_c[tau[c]]))
18
+ SCORE logit(tau) = alpha * log( pi+ . kappa+(tau) + pi- . kappa-(tau) )
19
+ OUTPUT hybrid: P(g) = g_in * P_bank(g | in) + (1-g_in) * P_byte(g)
20
+
21
+ THE GUARANTEE LEDGER (all demonstrated numerically 2026-06-09; see session log):
22
+ T1/T2 pi parameterization: address-constrained pi is projectively UNIMODAL
23
+ (logits linear in x-hat) β€” a two-spike target is unreachable (best
24
+ joint mass 1.4% vs 50% needed). The free antipodal-tied simplex
25
+ represents any tied-logit distribution. DEFAULT: free tied simplex;
26
+ address-constrained is the unimodal ablation (pi_mode='address').
27
+ T3 Tied [w; -w] implies p+k * p-k is CONSTANT across k β€” every axis is
28
+ forced to an orientation stance. Feature-or-bug: empirical.
29
+ T4 The 3x256 byte-product head cannot express within-trigram byte
30
+ correlation (rank-1 tensor over 256^3); it is the guaranteed-floor
31
+ baseline (head='byte'), not the main head.
32
+ T5 The hybrid output is a PROPER full-support distribution and its CE
33
+ decomposes exactly: -log P(g) = -log gate_branch - log P_branch(g).
34
+ Implemented verbatim. "Run all three banks" = ablations inside one
35
+ provably-correct machine.
36
+ T6 Raw-score softmax over a bank has a sharpness ceiling (scores in
37
+ (0,1] => CE floor 7.32 nats at M=4096). Logits are LOG-kernel with a
38
+ learnable scale alpha. Non-negotiable.
39
+ T7 Output logit rank <= 2K (softmax bottleneck): K governs attention
40
+ rank, output rank, and mode capacity β€” one knob, three proven roles.
41
+ T8 The write-head target Delta-z = sum of future addresses is the
42
+ ORDER-MARGINALIZED multiset of the next W trigrams (permutation-
43
+ invariant by commutativity). It predicts WHAT comes, not the order.
44
+ Learnability rests on the empirical rank-10 occupancy result.
45
+ Lit. Sampled softmax requires the log-Q correction; with a uniform
46
+ proposal the correction is constant and cancels in the softmax
47
+ (target always included). head='sampled' implements exactly this.
48
+
49
+ THE BRANCHING GAUGE (the [TAU] kernel invariant, inverted): a single unit row
50
+ has conf = ||(p+ - p-)A|| pinned at f(tau,K,D). A PREDICTED pi is not so
51
+ bound β€” implied confidence below the invariant is the model declaring
52
+ superposition. branching_frac is monitored from step zero.
53
+
54
+ Banks: 'corpus' (top-M trigrams of the training stream), 'wordnet'
55
+ (AbstractPhil/wordnet-lexical-topology char_eng_3gram, frequency-ranked,
56
+ filtered to exact 3-byte UTF-8), or per-step 'sampled' negatives.
57
+
58
+ Usage (Blackwell / A100):
59
+ from aleph_lm import AlephLMConfig, train_aleph_lm
60
+ r = train_aleph_lm(AlephLMConfig(steps=10_000, device='cuda',
61
+ head='hybrid', bank_source='wordnet'))
62
+
63
+ Depends: aleph_routed_attention.py, aleph_trigram_lm.py in the same directory.
64
+ Author: AbstractPhil + Mirel Date: 2026-06-09 License: MIT
65
  """
66
 
67
  from __future__ import annotations
 
69
  import math
70
  import os
71
  import time
72
+ from dataclasses import dataclass
73
  from typing import Dict, List, Optional, Tuple
74
 
75
  import numpy as np
 
83
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
84
 
85
  @dataclass
86
+ class AlephLMConfig:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  # substrate
88
  corpus_id: str = "wikitext-103-raw-v1"
89
  split: str = "train"
90
  max_corpus_bytes: Optional[int] = 100_000_000
91
+ seq_len: int = 256 # trigrams (3*seq_len bytes)
92
  seed: int = 1234
93
 
94
+ # tower
95
  dim: int = 384
96
  n_layers: int = 4
97
  n_heads: int = 6
 
98
  K: int = 64
99
  D_addr: int = 4
100
  tau: float = 0.1
101
+ codebook_init: object = "random"
102
+ shared_codebook: bool = True
103
+
104
+ # prediction head (the ledger's resolutions)
105
+ head: str = "hybrid" # 'hybrid' | 'byte' | 'bank' | 'sampled'
106
+ pi_mode: str = "free" # 'free' (T2 default) | 'address' (unimodal ablation)
107
+ bank_source: str = "corpus" # 'corpus' | 'wordnet'
108
+ bank_size: int = 4096
109
+ n_negatives: int = 1024 # head='sampled'
110
+ logit_scale_init: float = 1.0 # alpha on the log-kernel logits (T6)
111
+
112
+ # hybrid bank scorer: 'kernel' (log-kernel, T6) or 'pmix' β€” a mixture of
113
+ # J pointers on S^(d_point-1): logits(c) = logsumexp_j [log w_j + T yhat_j.c]
114
+ # = Mixture-of-Softmaxes in sphere coordinates. Theorem-backed twice over:
115
+ # raises output rank past the T7 bottleneck (MoS, Yang et al.), and gives
116
+ # the pointer J modes so the barycenter pathology (unimodal aim at a
117
+ # multimodal future) is structurally removed. Full-bank softmax retained:
118
+ # T5 propriety intact. PREREGISTERED statute prediction: pmix candidate
119
+ # coords bypass the codebook (W_cand48), removing prediction-side
120
+ # discrimination pressure -> expect dev near the zero group, vs kernel's
121
+ # +0.013. The dose-response gets a within-architecture test.
122
+ bank_scorer: str = "kernel" # 'kernel' | 'pmix'
123
+ n_pointers: int = 4 # J mixture components (pmix)
124
+
125
+ # pointer head (head='pointer'): NN-on-the-sphere decode
126
+ d_point: int = 48 # pointer sphere dim (band-valid; the
127
+ # capacity table gives the decode
128
+ # budget theta_NN/2 at this D)
129
+ pointer_k: int = 32 # hard negatives = target's k sphere-NN
130
+ pointer_cos_weight: float = 0.5 # aiming regularizer (contrastive CE
131
+ # is the main learner β€” lit. caveat)
132
+ pointer_refresh: int = 200 # steps between NN-table refreshes
133
+ # (candidate coords drift)
134
+
135
+ # write-head (T8, auxiliary multiset prediction)
136
+ write_weight: float = 0.1 # 0 disables
137
+ write_horizon: int = 8 # W: the granularity dial
138
 
139
  # training
140
+ steps: int = 10_000
141
+ batch_size: int = 32
142
+ accum_steps: int = 1
143
  lr: float = 3e-4
144
  lr_decay: bool = True
145
+ div_weight: float = 0.0
146
+ log_every: int = 250
147
  device: str = "cuda" if torch.cuda.is_available() else "cpu"
 
 
148
 
149
  # outputs
150
  snapshot_codebook: bool = True
151
+ snapshot_path: str = "aleph_lm_snaps.pt"
152
+ checkpoint_path: Optional[str] = "aleph_lm.pt"
153
 
154
  def __post_init__(self):
155
+ assert self.head in ("hybrid", "byte", "bank", "sampled", "pointer")
156
+ assert self.pi_mode in ("free", "address")
157
+ assert self.bank_scorer in ("kernel", "pmix")
158
+ assert self.bank_source in ("corpus", "wordnet") \
159
+ or os.path.isfile(str(self.bank_source)), \
160
+ f"bank_source must be 'corpus'|'wordnet'|path to bank .pt"
161
  assert self.dim % self.n_heads == 0
162
+ assert self.write_horizon >= 1
163
+ tag = self.head
164
+ if self.head in ("hybrid", "bank"):
165
+ b = (os.path.splitext(os.path.basename(str(self.bank_source)))[0]
166
+ if os.path.isfile(str(self.bank_source)) else self.bank_source)
167
+ tag += f"_{b}"
168
+ if self.pi_mode != "free":
169
+ tag += f"_{self.pi_mode}"
170
+ if self.head == "hybrid" and self.bank_scorer == "pmix":
171
+ tag += f"_pmix{self.n_pointers}"
172
+ if self.checkpoint_path == "aleph_lm.pt":
173
+ self.checkpoint_path = f"aleph_lm_{tag}.pt"
174
+ if self.snapshot_path == "aleph_lm_snaps.pt":
175
+ self.snapshot_path = f"aleph_lm_snaps_{tag}.pt"
176
 
177
 
178
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
179
+ # Candidate banks
180
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
181
 
182
+ def _tri_ids(tri: Tensor) -> Tensor:
183
+ """(..., 3) bytes -> scalar trigram id in [0, 256^3)."""
184
+ return tri[..., 0] * 65536 + tri[..., 1] * 256 + tri[..., 2]
185
+
186
+
187
+ def build_corpus_bank(stream: TrigramStream, M: int,
188
+ sample_bytes: int = 6_000_000) -> Tensor:
189
+ """Top-M most frequent trigrams of the training stream. (M, 3) long."""
190
+ n = min(sample_bytes, (len(stream.stream) // 3) * 3)
191
+ tri = stream.stream[:n].reshape(-1, 3)
192
+ ids = (tri[:, 0].astype(np.int64) * 65536 + tri[:, 1].astype(np.int64) * 256
193
+ + tri[:, 2].astype(np.int64))
194
+ uniq, counts = np.unique(ids, return_counts=True)
195
+ top = uniq[np.argsort(counts)[::-1][:M]]
196
+ out = np.stack([top // 65536, (top // 256) % 256, top % 256], axis=-1)
197
+ return torch.from_numpy(out.astype(np.int64))
198
+
199
+
200
+ def build_wordnet_bank(M: int) -> Tensor:
201
+ """char_eng_3gram from AbstractPhil/wordnet-lexical-topology, frequency-
202
+ ranked, filtered to exact 3-byte UTF-8. (M', 3) long, M' <= M."""
203
+ from huggingface_hub import hf_hub_download
204
+ import pyarrow.parquet as pq
205
+ p = hf_hub_download("AbstractPhil/wordnet-lexical-topology",
206
+ "data/char_eng_3gram-00000-of-00001.parquet",
207
+ repo_type="dataset")
208
+ t = pq.read_table(p, columns=["ngram", "rank"]).to_pandas()
209
+ t = t.sort_values("rank")
210
+ rows = []
211
+ for s in t["ngram"]:
212
+ b = str(s).encode("utf-8", errors="ignore")
213
+ if len(b) == 3:
214
+ rows.append([b[0], b[1], b[2]])
215
+ if len(rows) >= M:
216
+ break
217
+ assert rows, "wordnet bank empty after 3-byte filter"
218
+ return torch.tensor(rows, dtype=torch.long)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
 
221
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
222
+ # Model
223
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
224
 
225
+ class AlephLM(nn.Module):
226
+ """The composite reduction. forward_loss(ids, targets) -> (loss, logs)."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
+ def __init__(self, cfg: AlephLMConfig, bank: Optional[Tensor] = None):
 
 
 
 
 
 
 
 
 
229
  super().__init__()
230
  self.cfg = cfg
231
  d = cfg.dim
232
+
233
+ # ── EMBED (byte-factored; shared with candidate composition) ──
234
  self.byte_emb = nn.ModuleList([nn.Embedding(256, d) for _ in range(3)])
235
  self.pos = nn.Parameter(0.02 * torch.randn(1, cfg.seq_len, d))
236
 
237
+ # ── MIX (hub tower, shared codebook) ──
238
+ def make_attn():
 
239
  return AlephRoutedAttention(AlephAttentionConfig(
240
+ dim=d, num_heads=cfg.n_heads, mode="hub", K=cfg.K,
241
+ D_addr=cfg.D_addr, tau=cfg.tau, causal=True,
242
  codebook_init=cfg.codebook_init))
 
243
  self.layers = nn.ModuleList([
244
+ nn.ModuleDict({"norm1": nn.LayerNorm(d), "attn": make_attn(),
245
+ "norm2": nn.LayerNorm(d),
246
+ "mlp": nn.Sequential(nn.Linear(d, 4 * d), nn.GELU(),
247
+ nn.Linear(4 * d, d))})
248
+ for _ in range(cfg.n_layers)])
249
+ if cfg.shared_codebook:
 
 
 
 
 
 
250
  shared = self.layers[0]["attn"].codebook
251
  for L in self.layers[1:]:
252
  L["attn"].codebook = shared
253
+ self.norm_f = nn.LayerNorm(d)
254
+
255
+ # ── PREDICT: pi over 2K oriented axes ──
256
+ self.W_pi = nn.Linear(d, cfg.K, bias=True) # tied logits [w; -w]
257
+ if cfg.pi_mode == "address": # unimodal ablation (T2)
258
+ self.W_pi_row = nn.Linear(d, cfg.D_addr, bias=False)
259
+ nn.init.orthogonal_(self.W_pi_row.weight)
260
+
261
+ # ── CANDIDATE: compositional addresses (banked heads) ──
262
+ self.W_kappa = nn.Linear(d, cfg.D_addr, bias=False)
263
+ nn.init.orthogonal_(self.W_kappa.weight)
264
+ self.logit_scale = nn.Parameter(
265
+ torch.tensor(float(cfg.logit_scale_init))) # alpha (T6)
266
+
267
+ # ── byte-product head (T4 floor + hybrid tail) ──
268
+ self.byte_heads = nn.ModuleList([nn.Linear(d, 256) for _ in range(3)])
269
+
270
+ # ── hybrid gate (T5) ──
271
+ self.gate = nn.Linear(d, 1)
272
+
273
+ # ── pmix bank scorer: J-pointer mixture (MoS on the sphere) ──
274
+ if cfg.head == "hybrid" and cfg.bank_scorer == "pmix":
275
+ J = cfg.n_pointers
276
+ self.W_pmix = nn.Linear(d, J * cfg.d_point, bias=False)
277
+ nn.init.orthogonal_(self.W_pmix.weight)
278
+ self.W_mixgate = nn.Linear(d, J)
279
+ self.W_cand48 = nn.Linear(d, cfg.d_point, bias=False)
280
+ nn.init.orthogonal_(self.W_cand48.weight)
281
+ self.point_T = nn.Parameter(torch.tensor(10.0))
282
+
283
+ # ── pointer head: predict a point on S^(d_point-1), decode by NN ──
284
+ if cfg.head == "pointer":
285
+ self.W_point = nn.Linear(d, cfg.d_point, bias=False)
286
+ nn.init.orthogonal_(self.W_point.weight)
287
+ self.W_cand48 = nn.Linear(d, cfg.d_point, bias=False)
288
+ nn.init.orthogonal_(self.W_cand48.weight)
289
+ self.point_T = nn.Parameter(torch.tensor(10.0)) # contrastive inv-temp
290
+ self.register_buffer("_nn_table", torch.zeros(0, dtype=torch.long),
291
+ persistent=False)
292
+ self._nn_step = -1
293
+
294
+ # ── write-head (T8): predicted Delta-z over 2K ──
295
+ if cfg.write_weight > 0:
296
+ self.W_write = nn.Linear(d, 2 * cfg.K)
297
+
298
+ # ── bank registration ──
299
+ if bank is not None:
300
+ self.register_buffer("bank", bank) # (M, 3)
301
+ self.register_buffer("bank_ids_sorted",
302
+ _tri_ids(bank).sort().values) # membership
303
+ self.register_buffer("bank_perm",
304
+ _tri_ids(bank).argsort()) # sorted->orig
305
+ else:
306
+ self.bank = None
307
+
308
+ # ---- shared codebook handle ----
309
+ @property
310
+ def codebook(self) -> Tensor:
311
+ return self.layers[0]["attn"].codebook
312
 
313
  def aleph_layers(self) -> List[AlephRoutedAttention]:
314
  return [m for m in self.modules() if isinstance(m, AlephRoutedAttention)]
315
 
316
+ # ---- address of arbitrary unit rows vs the SHARED codebook ----
317
+ def _address_rows(self, rows: Tensor) -> Tuple[Tensor, Tensor]:
318
+ A = F.normalize(self.codebook, dim=-1)
319
+ u = (rows @ A.t()) / self.cfg.tau
320
+ m = u.abs().amax(-1, keepdim=True)
321
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
322
+ Z = (ep + en).sum(-1, keepdim=True)
323
+ return ep / Z, en / Z
324
+
325
+ # ---- PREDICT ----
326
+ def _pi(self, h: Tensor) -> Tuple[Tensor, Tensor]:
327
+ """pi over 2K oriented axes. 'free': tied simplex (T2 default).
328
+ 'address': unimodal ablation."""
329
+ if self.cfg.pi_mode == "address":
330
+ row = F.normalize(self.W_pi_row(h), dim=-1)
331
+ return self._address_rows(row)
332
+ w = self.W_pi(h) # (..., K)
333
+ m = w.abs().amax(-1, keepdim=True)
334
+ ep, en = torch.exp(w - m), torch.exp(-w - m)
335
+ Z = (ep + en).sum(-1, keepdim=True)
336
+ return ep / Z, en / Z
337
+
338
+ # ---- CANDIDATE addresses for a (M, 3) byte bank ----
339
+ def _kappa(self, bank: Tensor) -> Tuple[Tensor, Tensor]:
340
+ e = sum(emb(bank[:, i]) for i, emb in enumerate(self.byte_emb))
341
+ rows = F.normalize(self.W_kappa(e), dim=-1) # (M, D_addr)
342
+ return self._address_rows(rows)
343
+
344
+ # ---- SCORE: log-kernel logits (T6) ----
345
+ def _bank_logits(self, pi_p: Tensor, pi_m: Tensor,
346
+ k_p: Tensor, k_m: Tensor) -> Tensor:
347
+ s = pi_p @ k_p.t() + pi_m @ k_m.t() # (..., M), > 0
348
+ return self.logit_scale * torch.log(s.clamp_min(1e-9))
349
+
350
+ # ---- pmix bank logits: logsumexp over J pointers (MoS on the sphere) ----
351
+ def _pmix_logits(self, h: Tensor) -> Tuple[Tensor, Dict[str, float]]:
352
+ cfg = self.cfg
353
+ J = cfg.n_pointers
354
+ coords = self._cand_coords() # (M, d_point)
355
+ y = self.W_pmix(h).view(*h.shape[:-1], J, cfg.d_point)
356
+ y = F.normalize(y, dim=-1) # (B,S,J,dp)
357
+ mix = F.log_softmax(self.W_mixgate(h), dim=-1) # (B,S,J)
358
+ sims = torch.einsum("bsjd,md->bsjm", y, coords) * self.point_T
359
+ logits = torch.logsumexp(mix.unsqueeze(-1) + sims, dim=2) # (B,S,M)
360
+ with torch.no_grad(): # mode diagnostics
361
+ pw = torch.einsum("bsjd,bskd->bsjk", y, y)
362
+ off = pw.masked_select(~torch.eye(J, dtype=torch.bool,
363
+ device=h.device)
364
+ .expand_as(pw)).clamp(-1, 1)
365
+ spread = torch.acos(off).mean().item() * 180 / math.pi
366
+ usage = mix.exp().mean(dim=(0, 1))
367
+ ent = -(usage * usage.clamp_min(1e-9).log()).sum().item() / math.log(J)
368
+ return logits, {"mode_spread_deg": spread, "mix_entropy": ent}
369
+
370
+ # ---- tower ----
371
  def backbone(self, ids: Tensor) -> Tensor:
 
372
  x = sum(emb(ids[..., i]) for i, emb in enumerate(self.byte_emb))
373
  x = x + self.pos[:, : ids.shape[1]]
374
  for L in self.layers:
 
376
  x = x + L["mlp"](L["norm2"](x))
377
  return self.norm_f(x)
378
 
379
+ # ---- byte-product log-probs of given targets (T4 tail) ----
380
+ def _byte_logprob(self, h: Tensor, targets: Tensor) -> Tensor:
381
+ lp = 0.0
382
+ for c, head in enumerate(self.byte_heads):
383
+ lp = lp + F.log_softmax(head(h), dim=-1).gather(
384
+ -1, targets[..., c:c + 1]).squeeze(-1)
385
+ return lp # (B, S)
386
+
387
+ # ---- bank membership: target -> bank index or -1 ----
388
+ def _bank_index(self, targets: Tensor) -> Tensor:
389
+ tid = _tri_ids(targets)
390
+ pos = torch.searchsorted(self.bank_ids_sorted, tid)
391
+ pos = pos.clamp_max(len(self.bank_ids_sorted) - 1)
392
+ hit = self.bank_ids_sorted[pos] == tid
393
+ idx = self.bank_perm[pos]
394
+ return torch.where(hit, idx, torch.full_like(idx, -1))
395
+
396
+ # ---- write-head target (T8): order-marginalized future address mass ----
397
+ @torch.no_grad()
398
+ def _write_target(self, ids: Tensor) -> Tensor:
399
+ """Delta-z over 2K for horizon W at each position (normalized)."""
400
+ cfg = self.cfg
401
+ a0 = self.layers[0]["attn"]
402
  x = sum(emb(ids[..., i]) for i, emb in enumerate(self.byte_emb))
403
  x = x + self.pos[:, : ids.shape[1]]
404
+ kh = a0._split_addr(a0.k_addr(self.layers[0]["norm1"](x)),
405
+ ids.shape[0], ids.shape[1])
406
+ pk_p, pk_m = a0._address(kh) # (B,H,S,K)
407
+ p = torch.cat([pk_p, pk_m], dim=-1).mean(dim=1) # (B,S,2K)
408
+ cs = torch.cat([torch.zeros_like(p[:, :1]), p.cumsum(dim=1)], dim=1)
409
+ W = cfg.write_horizon
410
+ B, S, _ = p.shape
411
+ end = torch.arange(S, device=p.device).clamp_max(S - 1)
412
+ lo = cs[:, 1:] # prefix up to t (incl)
413
+ hi = cs[:, torch.clamp(torch.arange(S, device=p.device) + W, max=S)]
414
+ dz = (hi - lo).clamp_min(0)
415
+ valid = (torch.arange(S, device=p.device) + 1 < S) # at least 1 future tok
416
+ dz = dz / dz.sum(-1, keepdim=True).clamp_min(1e-9)
417
+ return dz, valid
418
+
419
+ # ---- pointer head: compositional D=48 candidate coordinates ----
420
+ def _cand_coords(self) -> Tensor:
421
+ e = sum(emb(self.bank[:, i]) for i, emb in enumerate(self.byte_emb))
422
+ return F.normalize(self.W_cand48(e), dim=-1) # (M, d_point)
423
+
424
+ @torch.no_grad()
425
+ def _refresh_nn(self, coords: Tensor, step: int) -> None:
426
+ """Hard-negative table: each candidate's k nearest sphere neighbors
427
+ (excluding self). Refreshed periodically β€” coordinates drift."""
428
+ cos = coords @ coords.t()
429
+ cos.fill_diagonal_(-2.0)
430
+ self._nn_table = cos.topk(self.cfg.pointer_k, dim=-1).indices # (M, k)
431
+ self._nn_step = step
432
+ # decode budget: theta_NN/2 of the CURRENT candidate constellation
433
+ nn_deg = torch.acos(cos.max(dim=-1).values.clamp(-1, 1)) * 180 / math.pi
434
+ self._decode_budget_deg = (nn_deg.median() / 2).item()
435
+
436
+ def _pointer_loss(self, h: Tensor, targets: Tensor,
437
+ step: int) -> Tuple[Tensor, Dict]:
438
+ """NN-on-the-sphere head (T5-chained with the byte tail):
439
+ in-bank: -log gate - log softmax_{target βˆͺ kNN(target)}(T * yhatΒ·c)
440
+ + lambda_cos (1 - yhatΒ·c_target) [aiming term]
441
+ out-bank: -log(1-gate) - log P_byte(g)
442
+ Decode metric: exact-NN rate + median angular error vs the budget
443
+ theta_NN/2 (the decode-correctness theorem)."""
444
+ cfg = self.cfg
445
+ logs: Dict[str, float] = {}
446
+ coords = self._cand_coords() # (M, d_point)
447
+ if step - self._nn_step >= cfg.pointer_refresh or len(self._nn_table) == 0:
448
+ self._refresh_nn(coords.detach(), step)
449
+
450
+ yhat = F.normalize(self.W_point(h), dim=-1) # (B,S,d_point)
451
+ bidx = self._bank_index(targets)
452
+ in_bank = bidx >= 0
453
+ logs["coverage"] = in_bank.float().mean().item()
454
+
455
+ g_logit = self.gate(h).squeeze(-1)
456
+ nll_byte = -self._byte_logprob(h, targets)
457
+
458
+ B, S = bidx.shape
459
+ tgt = bidx.clamp_min(0) # (B,S)
460
+ negs = self._nn_table[tgt] # (B,S,k) hard negatives
461
+ cand_idx = torch.cat([tgt.unsqueeze(-1), negs], dim=-1) # (B,S,1+k)
462
+ c = coords[cand_idx] # (B,S,1+k,d_point)
463
+ logits = torch.einsum("bsd,bsnd->bsn", yhat, c) * self.point_T
464
+ nll_point = F.cross_entropy(
465
+ logits.reshape(-1, logits.shape[-1]),
466
+ torch.zeros(B * S, dtype=torch.long, device=h.device),
467
+ reduction="none").view(B, S)
468
+ cos_t = torch.einsum("bsd,bsd->bs", yhat, coords[tgt])
469
+ aim = cfg.pointer_cos_weight * (1.0 - cos_t)
470
+
471
+ nll = torch.where(in_bank,
472
+ -F.logsigmoid(g_logit) + nll_point + aim,
473
+ -F.logsigmoid(-g_logit) + nll_byte)
474
+ loss = nll.mean()
475
+ logs["bpb"] = loss.item() / 3 / math.log(2)
476
+ logs["gate_acc"] = ((torch.sigmoid(g_logit) > 0.5) == in_bank
477
+ ).float().mean().item()
478
+ with torch.no_grad(): # decode metrics
479
+ if in_bank.any():
480
+ full = (yhat @ coords.t()) # (B,S,M)
481
+ pred = full.argmax(-1)
482
+ logs["nn_exact"] = (pred[in_bank] == tgt[in_bank]
483
+ ).float().mean().item()
484
+ # PROPER eval likelihood: full-bank softmax (comparable to
485
+ # hybrid bpb; the training loss above is contrastive-over-33
486
+ # and is NOT a likelihood β€” do not compare it across heads)
487
+ nll_full = F.cross_entropy(
488
+ (full * self.point_T).reshape(-1, full.shape[-1]),
489
+ tgt.reshape(-1), reduction="none").view_as(tgt)
490
+ nll_eval = torch.where(in_bank,
491
+ -F.logsigmoid(g_logit) + nll_full,
492
+ -F.logsigmoid(-g_logit) + nll_byte)
493
+ logs["bpb_eval"] = nll_eval.mean().item() / 3 / math.log(2)
494
+ ang = torch.acos(cos_t[in_bank].clamp(-1, 1)) * 180 / math.pi
495
+ logs["ang_err_deg"] = ang.median().item()
496
+ logs["budget_deg"] = self._decode_budget_deg
497
+ logs["in_budget"] = (ang < self._decode_budget_deg
498
+ ).float().mean().item()
499
+ return loss, logs
500
+
501
+ # ---- the loss (T5-exact hybrid + auxiliaries) ----
502
+ def forward_loss(self, ids: Tensor, targets: Tensor,
503
+ step: int = 0) -> Tuple[Tensor, Dict]:
504
+ cfg = self.cfg
505
+ h = self.backbone(ids) # (B,S,d)
506
+ logs: Dict[str, float] = {}
507
+
508
+ if cfg.head == "pointer":
509
+ assert self.bank is not None, "pointer head requires a bank"
510
+ return self._pointer_loss(h, targets, step)
511
+
512
+ if cfg.head == "byte":
513
+ nll = -self._byte_logprob(h, targets) # (B,S)
514
+ loss = nll.mean()
515
+ logs["bpb"] = loss.item() / 3 / math.log(2)
516
+ return loss, logs
517
+
518
+ pi_p, pi_m = self._pi(h) # (B,S,K) each
519
+
520
+ if cfg.head == "sampled":
521
+ # uniform negatives + target; uniform proposal => logQ constant,
522
+ # cancels in softmax (literature requirement satisfied trivially)
523
+ B, S, _ = h.shape
524
+ neg = torch.randint(0, 256, (cfg.n_negatives, 3), device=h.device)
525
+ cand = torch.cat([targets.reshape(-1, 3), neg], dim=0)
526
+ cand_ids, inv = torch.unique(_tri_ids(cand), return_inverse=True)
527
+ uniq = torch.stack([cand_ids // 65536, (cand_ids // 256) % 256,
528
+ cand_ids % 256], dim=-1)
529
+ k_p, k_m = self._kappa(uniq)
530
+ logits = self._bank_logits(pi_p.reshape(-1, cfg.K),
531
+ pi_m.reshape(-1, cfg.K), k_p, k_m)
532
+ tgt_idx = inv[: B * S]
533
+ loss = F.cross_entropy(logits, tgt_idx)
534
+ logs["bpb"] = loss.item() / 3 / math.log(2)
535
+ logs["n_cand"] = float(len(uniq))
536
+ return loss, logs
537
+
538
+ # banked heads
539
+ assert self.bank is not None, "head='hybrid'/'bank' requires a bank"
540
+ if cfg.head == "hybrid" and cfg.bank_scorer == "pmix":
541
+ logits, pm_logs = self._pmix_logits(h) # (B,S,M)
542
+ logs.update(pm_logs)
543
+ else:
544
+ k_p, k_m = self._kappa(self.bank) # (M,K) each
545
+ logits = self._bank_logits(pi_p, pi_m, k_p, k_m) # (B,S,M)
546
+ bidx = self._bank_index(targets) # (B,S), -1 = miss
547
+ in_bank = bidx >= 0
548
+ logs["coverage"] = in_bank.float().mean().item()
549
+
550
+ if cfg.head == "bank":
551
+ # ablation head: proper only on covered targets (coverage logged)
552
+ lb = F.log_softmax(logits, dim=-1)
553
+ nll = -lb.gather(-1, bidx.clamp_min(0).unsqueeze(-1)).squeeze(-1)
554
+ loss = nll[in_bank].mean() if in_bank.any() else logits.sum() * 0
555
+ logs["bpb_inbank"] = (loss.item() / 3 / math.log(2)
556
+ if in_bank.any() else float("nan"))
557
+ return loss, logs
558
+
559
+ # ── T5-exact hybrid: -log P(g) per position ──
560
+ g_logit = self.gate(h).squeeze(-1) # (B,S)
561
+ log_g = F.logsigmoid(g_logit)
562
+ log_1mg = F.logsigmoid(-g_logit)
563
+ lb = F.log_softmax(logits, dim=-1)
564
+ nll_bank = -lb.gather(-1, bidx.clamp_min(0).unsqueeze(-1)).squeeze(-1)
565
+ nll_byte = -self._byte_logprob(h, targets)
566
+ nll = torch.where(in_bank, -log_g + nll_bank, -log_1mg + nll_byte)
567
+ loss = nll.mean()
568
+ logs["bpb"] = loss.item() / 3 / math.log(2)
569
+ with torch.no_grad(): # branch-conditional currencies
570
+ if in_bank.any():
571
+ logs["bpb_bank_cond"] = nll_bank[in_bank].mean().item() / 3 / math.log(2)
572
+ if (~in_bank).any():
573
+ logs["bpb_byte_cond"] = nll_byte[~in_bank].mean().item() / 3 / math.log(2)
574
+ logs["gate_acc"] = ((torch.sigmoid(g_logit) > 0.5) == in_bank
575
+ ).float().mean().item()
576
+
577
+ # ── auxiliaries ──
578
+ if cfg.write_weight > 0:
579
+ dz, valid = self._write_target(ids)
580
+ pred = F.log_softmax(self.W_write(h), dim=-1)
581
+ kl = F.kl_div(pred, dz, reduction="none").sum(-1)
582
+ wl = kl[:, valid].mean()
583
+ loss = loss + cfg.write_weight * wl
584
+ logs["write_kl"] = wl.item()
585
+
586
+ return loss, logs
587
+
588
+ # ---- the branching gauge ([TAU] inverted) ----
589
+ @torch.no_grad()
590
+ def branching_gauge(self, ids: Tensor, n_baseline: int = 4096) -> Dict:
591
+ h = self.backbone(ids)
592
+ pi_p, pi_m = self._pi(h)
593
+ A = F.normalize(self.codebook, dim=-1)
594
+ conf = ((pi_p - pi_m) @ A).norm(dim=-1).reshape(-1)
595
+ rows = F.normalize(torch.randn(n_baseline, self.cfg.D_addr,
596
+ device=h.device), dim=-1)
597
+ bp, bm = self._address_rows(rows)
598
+ base = ((bp - bm) @ A).norm(dim=-1)
599
+ mu, sd = base.mean(), base.std()
600
+ return {"conf_mean": conf.mean().item(),
601
+ "kernel_invariant": mu.item(),
602
+ "branching_frac": (conf < mu - 2 * sd).float().mean().item()}
603
 
604
 
605
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
606
  # Training
607
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
608
 
609
+ def train_aleph_lm(cfg: AlephLMConfig,
610
+ stream: Optional[TrigramStream] = None) -> Dict:
611
  torch.manual_seed(cfg.seed)
612
  dev = torch.device(cfg.device)
613
  stream = stream or TrigramStream(cfg.corpus_id, cfg.split,
614
  cfg.max_corpus_bytes, cfg.seed)
615
+ bank = None
616
+ if cfg.head in ("hybrid", "bank", "pointer"):
617
+ if os.path.isfile(str(cfg.bank_source)): # stratified-atlas bank
618
+ d = torch.load(cfg.bank_source, map_location="cpu", weights_only=False)
619
+ bank = d["bank"] if isinstance(d, dict) else d
620
+ print(f"[bank] loaded {len(bank)} trigram candidates "
621
+ f"from {cfg.bank_source}")
622
+ elif cfg.bank_source == "wordnet":
623
+ try:
624
+ bank = build_wordnet_bank(cfg.bank_size)
625
+ print(f"[bank] wordnet char_eng_3gram: {len(bank)} types")
626
+ except Exception as e:
627
+ print(f"[bank] wordnet unavailable ({e}); falling back to corpus")
628
+ if bank is None:
629
+ bank = build_corpus_bank(stream, cfg.bank_size)
630
+ print(f"[bank] corpus top-{len(bank)} trigrams")
631
+
632
+ model = AlephLM(cfg, bank=bank).to(dev)
633
  n_params = sum(p.numel() for p in model.parameters())
634
+ opt = torch.optim.Adam(model.parameters(), lr=cfg.lr) # pure Adam
635
  sched = (torch.optim.lr_scheduler.CosineAnnealingLR(
636
  opt, T_max=cfg.steps, eta_min=cfg.lr * 0.1) if cfg.lr_decay else None)
 
637
  alephs = model.aleph_layers()
638
  for a in alephs:
639
  a.emit_diversity = cfg.div_weight > 0
640
 
641
  snapshots: List[Tuple[int, Tensor]] = []
642
+ if cfg.snapshot_codebook:
643
  snapshots.append((0, alephs[0].export_codebook()))
644
 
645
+ print(f"\n=== AlephLM head={cfg.head} pi={cfg.pi_mode} "
646
+ f"bank={cfg.bank_source if bank is not None else '-'} "
647
+ f"params={n_params:,} ctx={cfg.seq_len} tri "
648
+ f"eff.batch={cfg.batch_size * cfg.accum_steps} dev={dev} ===")
649
+ result: Dict = {"head": cfg.head, "params": n_params}
 
 
 
 
650
  t0 = time.time()
651
 
 
 
652
  for step in range(1, cfg.steps + 1):
653
  opt.zero_grad(set_to_none=True)
654
+ loss_sum, logs_acc = 0.0, {}
655
  for _ in range(cfg.accum_steps):
656
+ ids, targets = stream.sample(cfg.batch_size, cfg.seq_len, dev)
657
+ loss, logs = model.forward_loss(ids, targets, step=step)
658
+ total = loss
659
+ if cfg.div_weight > 0:
660
+ total = total + cfg.div_weight * sum(
661
+ a.diversity_loss() for a in alephs)
662
+ (total / cfg.accum_steps).backward()
663
+ loss_sum += loss.item()
664
+ logs_acc = logs
665
+ loss_avg = loss_sum / cfg.accum_steps
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  gnorm = torch.nn.utils.clip_grad_norm_(
667
+ model.parameters(), max(loss_avg, 1.0))
668
  opt.step()
669
  if sched is not None:
670
  sched.step()
671
 
672
  if step % cfg.log_every == 0 or step == cfg.steps:
673
+ rate = step * cfg.batch_size * cfg.seq_len * cfg.accum_steps \
674
+ / (time.time() - t0)
 
675
  line = (f" step {step:6d} loss {loss_avg:.4f} "
676
+ f"bpb {logs_acc.get('bpb', logs_acc.get('bpb_inbank', float('nan'))):.3f} "
677
+ f"|g| {gnorm:.2f} {rate/1e3:.1f}k tri/s")
678
+ if "coverage" in logs_acc:
679
+ line += f" cov {logs_acc['coverage']:.0%}"
680
+ if "gate_acc" in logs_acc:
681
+ line += f" gate {logs_acc['gate_acc']:.0%}"
682
+ if "nn_exact" in logs_acc:
683
+ line += (f" bpbE {logs_acc.get('bpb_eval', float('nan')):.3f}"
684
+ f" nn {logs_acc['nn_exact']:.0%}"
685
+ f" ang {logs_acc['ang_err_deg']:.1f}/"
686
+ f"{logs_acc['budget_deg']:.1f}deg"
687
+ f" inBudget {logs_acc['in_budget']:.0%}")
688
+ if "bpb_bank_cond" in logs_acc:
689
+ line += (f" inB {logs_acc['bpb_bank_cond']:.3f}"
690
+ f" outB {logs_acc.get('bpb_byte_cond', float('nan')):.3f}")
691
+ if "mode_spread_deg" in logs_acc:
692
+ line += (f" spread {logs_acc['mode_spread_deg']:.0f}deg"
693
+ f" mixH {logs_acc['mix_entropy']:.2f}")
694
+ if "write_kl" in logs_acc:
695
+ line += f" wKL {logs_acc['write_kl']:.3f}"
696
+ model.eval()
697
+ with torch.no_grad():
698
+ ids_p, _ = stream.sample(min(8, cfg.batch_size), cfg.seq_len, dev)
699
+ st = alephs[0].address_stats(model.backbone(ids_p),
700
+ max_rows=200_000)
701
+ bg = model.branching_gauge(ids_p)
702
+ model.train()
703
+ line += (f" ppl {st['perplexity']:.0f}/{st['max_perplexity']:.0f}"
704
+ f" conf {bg['conf_mean']:.3f}"
705
+ f"/{bg['kernel_invariant']:.3f}"
706
+ f" branch {bg['branching_frac']:.0%}")
707
  print(line)
708
+ result.update(logs_acc)
709
+ result.update({"loss": loss_avg, "step": step, **bg})
710
+ if cfg.snapshot_codebook:
711
+ snapshots.append((step, alephs[0].export_codebook()))
712
 
713
  if snapshots:
 
 
 
714
  traj = [(s, statute(cb)) for s, cb in snapshots]
715
  result["statute_trajectory"] = traj
716
  torch.save({"snapshots": snapshots, "statute_trajectory": traj,
717
+ "config": cfg.__dict__}, cfg.snapshot_path)
718
+ d0, d1 = traj[0][1]["deviation"], traj[-1][1]["deviation"]
719
+ print(f"\n[basin] statute: dev {d0:+.4f} -> {d1:+.4f} "
720
+ f"({traj[-1][1]['statute']}); snapshots -> {cfg.snapshot_path}")
 
 
 
 
 
 
 
 
 
721
  if cfg.checkpoint_path:
722
  torch.save({"model_state_dict": model.state_dict(),
723
+ "config": cfg.__dict__,
724
+ "bank": model.bank.cpu() if model.bank is not None else None},
725
+ cfg.checkpoint_path)
726
+ print(f"[ckpt] -> {cfg.checkpoint_path}")
727
  return result
728
 
729
 
730
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
731
+ # Smoke + activation
732
  # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
733
 
734
+ def _smoke():
 
735
  print("=" * 70)
736
+ print("AlephLM β€” smoke")
737
  print("=" * 70)
 
738
  rng = np.random.default_rng(0)
739
+ words = [b"the", b"aleph", b"predicts", b"its", b"own", b"future",
740
+ b"through", b"a", b"codebook"]
741
+ path = "/tmp/_alm_corpus.txt"
742
  with open(path, "wb") as f:
743
+ f.write(b" ".join(words[i] for i in rng.integers(0, 9, 80000)))
744
+
745
+ base = dict(corpus_id=path, max_corpus_bytes=None, steps=25, log_every=25,
746
+ dim=96, n_layers=2, n_heads=4, K=16, seq_len=48, batch_size=8,
747
+ bank_size=256, n_negatives=128, device="cpu",
748
+ checkpoint_path=None, snapshot_path="/tmp/_alm_snaps.pt")
749
+ prior_bpb = 8.0
750
+ for head in ("hybrid", "byte", "bank", "sampled"):
751
+ r = train_aleph_lm(AlephLMConfig(head=head, **base))
752
+ bpb = r.get("bpb", r.get("bpb_inbank", float("nan")))
753
+ assert math.isfinite(r["loss"]), head
754
+ print(f" βœ“ head={head:8s} loss {r['loss']:.3f} bpb {bpb:.2f} "
755
+ f"(uniform prior {prior_bpb:.1f})")
756
+ # pi ablation path + gradient to codebook through the PREDICT/CANDIDATE legs
757
+ cfg = AlephLMConfig(head="hybrid", pi_mode="address", **base)
758
+ stream = TrigramStream(path, max_corpus_bytes=None, seed=0)
759
+ bank = build_corpus_bank(stream, cfg.bank_size)
760
+ m = AlephLM(cfg, bank=bank)
761
+ ids, tg = stream.sample(4, cfg.seq_len, "cpu")
762
+ loss, _ = m.forward_loss(ids, tg)
763
+ loss.backward()
764
+ assert m.codebook.grad is not None and torch.isfinite(m.codebook.grad).all()
765
+ print(f" βœ“ pi_mode='address' ablation runs; codebook grad |{m.codebook.grad.norm():.3f}|")
766
+ print("All smoke tests passed.")
767
 
768
 
769
  #if __name__ == "__main__":
770
  # import argparse
771
+ # ap = argparse.ArgumentParser(description="AlephLM β€” prediction through the codebook")
772
  # ap.add_argument("--smoke-only", action="store_true")
773
+ # ap.add_argument("--head", default="hybrid",
774
+ # choices=["hybrid", "byte", "bank", "sampled", "pointer"])
775
+ # ap.add_argument("--bank", default="corpus", choices=["corpus", "wordnet"])
776
+ # ap.add_argument("--pi", default="free", choices=["free", "address"])
777
+ # ap.add_argument("--scorer", default="kernel", choices=["kernel", "pmix"])
778
+ # ap.add_argument("--pointers", type=int, default=4)
779
+ # ap.add_argument("--steps", type=int, default=10_000)
780
  # ap.add_argument("--corpus-mb", type=int, default=100)
 
781
  # ap.add_argument("--device",
782
  # default="cuda" if torch.cuda.is_available() else "cpu")
783
+ # args, _unknown = ap.parse_known_args()
 
784
  # if args.smoke_only:
785
+ # _smoke()
786
  # else:
787
+ # cfg = AlephLMConfig(head=args.head, bank_source=args.bank,
788
+ # pi_mode=args.pi, steps=args.steps,
789
+ # bank_scorer=args.scorer, n_pointers=args.pointers,
790
+ # max_corpus_bytes=args.corpus_mb * 1_000_000,
791
+ # device=args.device)
792
+ # train_aleph_lm(cfg)