AbstractPhil commited on
Commit
b007aec
·
verified ·
1 Parent(s): d40b627

mini-beatrix-1 pre-classroom annealment point: AutoModel-compatible (trust_remote_code), surgered 58,664 weights (head gate folded, semantic no-op), config from manifest, parity 0.00e+00 vs native stack, generate() verified

Browse files
Files changed (11) hide show
  1. README.md +67 -0
  2. address.py +84 -0
  3. alephlm.py +192 -0
  4. attention.py +154 -0
  5. bank.py +51 -0
  6. config.json +35 -0
  7. embedding.py +44 -0
  8. head.py +51 -0
  9. model.safetensors +3 -0
  10. modeling_minibeatrix.py +93 -0
  11. presets.py +163 -0
README.md CHANGED
@@ -1,3 +1,70 @@
1
  ---
2
  license: mit
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: mit
3
+ library_name: transformers
4
+ pipeline_tag: text-generation
5
+ tags:
6
+ - byte-level
7
+ - tokenizer-free
8
+ - aleph
9
+ - signed-address
10
+ - custom_code
11
  ---
12
+
13
+ # mini-beatrix-1 — pretrain annealment point (pre-classroom)
14
+
15
+ The locked pretrain+anneal state of **mini-beatrix-1**, a 112.5M-parameter
16
+ **byte-level** AlephLM: step **58,664**, **17.301B bytes** seen
17
+ (0.3B wikitext warmup · 15B fineweb-edu · 2B anneal mix), fineweb-holdout
18
+ val **1.045 bits/byte**. This checkpoint is the fixed departure point for
19
+ the staged "early-life curriculum" — later classroom checkpoints live in
20
+ the [training repo](https://huggingface.co/AbstractPhil/alephllm-mini-beatrix-training).
21
+
22
+ No tokenizer: she reads raw UTF-8 bytes (`input_ids` = byte values
23
+ 0–255). Each position composes a byte trigram (dedicated pad row), so
24
+ "tokens" are learned inside the network. Sixteen pre-norm layers where
25
+ routing uses **signed geometric addresses** — `sinh/Σcosh` dispatch over
26
+ learned unit anchors, inhibition as a first-class citizen, no softmax-
27
+ over-choices, no top-k, no balance losses. Each layer carries an
28
+ anchored FFN bank born contributing exactly zero; layers 4/9/14 use a
29
+ linear-cost address read (CausalSplatHUB) instead of softmax attention.
30
+ Both elected themselves into load-bearing work: at this checkpoint,
31
+ removing the banks costs **+2.25 bpb**, removing the hub attention
32
+ **+3.73 bpb** (toggle ledger, fineweb holdout). The dual head's aleph
33
+ read is present with its gate folded to 1.0 (a verified semantic no-op,
34
+ max|logit diff| 2.4e-07) and contributes 0.0000 bpb here — it is the
35
+ live subject of the head-election experiment in the classroom phase.
36
+
37
+ ## Use
38
+
39
+ ```python
40
+ import torch
41
+ from transformers import AutoModelForCausalLM
42
+
43
+ m = AutoModelForCausalLM.from_pretrained(
44
+ "AbstractPhil/mini-beatrix-1", trust_remote_code=True).eval()
45
+
46
+ ids = torch.tensor([list("The history of mathematics begins".encode())])
47
+ out = m.generate(ids, max_new_tokens=96, do_sample=True,
48
+ temperature=0.7, top_p=0.95)
49
+ print(bytes(out[0].tolist()).decode("utf-8", errors="replace"))
50
+ ```
51
+
52
+ Bits-per-byte on your own text: pass `labels=input_ids` (HF shift
53
+ semantics are internal) and divide the returned loss (nats/byte) by
54
+ `ln 2`. No KV cache in this wrapper — generation recomputes the prefix
55
+ each step; for cached decode use the native stack below.
56
+
57
+ ## Honest notes
58
+
59
+ - The 2B anneal mix included dialogue in her chat template and a small
60
+ identity texture, so **the bare model chats and knows her name** —
61
+ behavior we have since ruled OUT of core corpora (conditioning belongs
62
+ in detachable arms; see the [amoe-lora](https://github.com/AbstractEyes/amoe-lora)
63
+ arm system and `mini-beatrix-1/arms/` in the training repo).
64
+ - Small and early: conversational in shape, thin on knowledge,
65
+ confidently wrong at times. Curriculum probe baselines (P0–P8),
66
+ toggle ledgers, and lexicon-census reports for this exact checkpoint
67
+ are in the training repo under `mini-beatrix-1/reports/`.
68
+
69
+ *Code:* [github.com/AbstractEyes/alephllm](https://github.com/AbstractEyes/alephllm) ·
70
+ *talk to her:* [alephllm-chat](https://huggingface.co/spaces/AbstractPhil/alephllm-chat)
address.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AlephAddress — the closed-form signed address over 2K oriented half-axes.
2
+
3
+ The mechanism is reconstructive, never comparative: no softmax-over-choices,
4
+ no argmax, no top-k anywhere. Dispatch weights are
5
+ w_k = sinh(u_k) / sum_j cosh(u_j), u_k = cos(x_hat, a_hat_k) / tau
6
+ which is exactly the signed difference of the two halves of a 2K-softmax
7
+ over oriented axes (+a_k, -a_k). Inhibition (negative w) is first-class.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import torch
12
+ import torch.nn as nn
13
+ import torch.nn.functional as F
14
+
15
+
16
+ def dtype_floor(t: torch.Tensor) -> float:
17
+ """Dtype-aware clamp floor. Half dtypes flush 1e-12 to zero — the
18
+ measured fp16 landmine; use a floor the dtype can actually represent."""
19
+ if t.dtype in (torch.float32, torch.float64):
20
+ return 1e-12
21
+ return float(torch.finfo(t.dtype).tiny) * 8
22
+
23
+
24
+ class AlephAddress(nn.Module):
25
+ """K unit anchors in D dims, cosine-read at temperature tau.
26
+
27
+ signed(x) -> (..., K) w_k = sinh(u_k)/sum_j cosh(u_j)
28
+ oriented(x)-> ((..., K), (..., K)) the two positive halves of the
29
+ 2K-softmax (ep/Z, en/Z); HUB feature map.
30
+ """
31
+
32
+ def __init__(self, K: int, D: int, tau: float = 0.1):
33
+ super().__init__()
34
+ self.K, self.D, self.tau = K, D, tau
35
+ self.codebook = nn.Parameter(F.normalize(torch.randn(K, D), dim=-1))
36
+ self.register_buffer("home", self.codebook.detach().clone())
37
+
38
+ def _u(self, x: torch.Tensor) -> torch.Tensor:
39
+ A = F.normalize(self.codebook, dim=-1)
40
+ return (F.normalize(x, dim=-1) @ A.transpose(-1, -2)) / self.tau
41
+
42
+ def oriented(self, x: torch.Tensor):
43
+ u = self._u(x)
44
+ m = u.abs().amax(dim=-1, keepdim=True)
45
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
46
+ Z = (ep + en).sum(dim=-1, keepdim=True)
47
+ return ep / Z, en / Z
48
+
49
+ def signed(self, x: torch.Tensor) -> torch.Tensor:
50
+ u = self._u(x)
51
+ m = u.abs().amax(dim=-1, keepdim=True)
52
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
53
+ return (ep - en) / (ep + en).sum(dim=-1, keepdim=True)
54
+
55
+ @torch.no_grad()
56
+ def health(self, x_sample: torch.Tensor) -> dict:
57
+ """Codebook + consumption vitals for the instrument suite."""
58
+ A = F.normalize(self.codebook.float(), dim=-1)
59
+ gram = A @ A.T
60
+ off = gram - torch.eye(self.K, device=gram.device)
61
+ drift = 1.0 - F.cosine_similarity(
62
+ A, F.normalize(self.home.float(), dim=-1), dim=-1)
63
+ s = torch.linalg.svdvals(A)
64
+ ps = (s * s) / (s * s).sum().clamp_min(1e-12)
65
+ out = {
66
+ "anchor_max_abs_cos": off.abs().max().item(),
67
+ "anchor_merge_pairs": int((off.abs() > 0.99).sum().item() // 2),
68
+ "drift_mean": drift.mean().item(),
69
+ "drift_max": drift.max().item(),
70
+ # frame health: effective rank of the codebook itself — a
71
+ # collapsing frame (anchors folding into a subspace) shows here
72
+ "codebook_erank": float(
73
+ torch.exp(-(ps * ps.clamp_min(1e-12).log()).sum()).item()),
74
+ }
75
+ if x_sample is not None:
76
+ p, n = self.oriented(x_sample.reshape(-1, x_sample.shape[-1]).float())
77
+ mass = torch.cat([p, n], dim=-1).mean(0) # (2K,) mean usage
78
+ mass = mass / mass.sum().clamp_min(1e-12)
79
+ ent = -(mass * mass.clamp_min(1e-12).log()).sum()
80
+ out["usage_ppl"] = float(ent.exp().item()) # of 2K half-axes
81
+ out["usage_cv"] = float((mass.std() / mass.mean().clamp_min(1e-12)).item())
82
+ w = self.signed(x_sample.reshape(-1, x_sample.shape[-1]).float())
83
+ out["sign_frac_neg"] = float((w < 0).float().mean().item())
84
+ return out
alephlm.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AlephLM — the full craft, config-driven.
2
+
3
+ Trigram byte (or BPE) embedding -> pre-norm stack (CausalSDPA majority,
4
+ CausalSplatHUB at the configured depths) -> LayerNorm -> DualHead.
5
+
6
+ Toggle surface (the causal contribution ledger, run at every eval):
7
+ forward(idx, disable_bank=True) dispatched experts off (exact C6 null)
8
+ forward(idx, disable_hub=True) hub attention residuals skipped
9
+ forward(idx, disable_head_aleph=True) gamma path off
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from typing import NamedTuple, Optional
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+
19
+
20
+ class LMOutput(NamedTuple):
21
+ """Still a tuple — `logits, loss = model(x)` keeps working — but also
22
+ HF-duck-typed (`out.logits`, `out.loss`) so frozen-trunk tooling like
23
+ amoe-lora drives the model natively."""
24
+ logits: torch.Tensor
25
+ loss: Optional[torch.Tensor]
26
+
27
+ from .presets import AlephLMConfig
28
+ from .attention import CausalSDPA, CausalSplatHUB
29
+ from .bank import AnchoredBank
30
+ from .embedding import TrigramByteEmbedding, TokenEmbedding
31
+ from .head import DualHead
32
+
33
+
34
+ class Block(nn.Module):
35
+ def __init__(self, cfg: AlephLMConfig, layer_idx: int):
36
+ super().__init__()
37
+ d = cfg.d_model
38
+ self.is_hub = layer_idx in cfg.hub_layers
39
+ self.n1 = nn.LayerNorm(d)
40
+ self.n2 = nn.LayerNorm(d)
41
+ if self.is_hub:
42
+ self.attn = CausalSplatHUB(d, cfg.hub_K, cfg.hub_D, cfg.tau,
43
+ chunk=cfg.hub_chunk)
44
+ else:
45
+ self.attn = CausalSDPA(d, cfg.n_heads)
46
+ self.bank = AnchoredBank(d, cfg.bank_experts, cfg.bank_ff, cfg.tau,
47
+ cfg.gate_init)
48
+
49
+ def forward(self, x, disable_bank=False, disable_hub=False):
50
+ if not (disable_hub and self.is_hub):
51
+ x = x + self.attn(self.n1(x))
52
+ return x + self.bank(self.n2(x), disable_dispatch=disable_bank)
53
+
54
+ def prefill(self, x):
55
+ a, cache = self.attn.prefill(self.n1(x))
56
+ x = x + a
57
+ return x + self.bank(self.n2(x)), cache
58
+
59
+ def step(self, x_t, cache):
60
+ x_t = x_t + self.attn.step(self.n1(x_t), cache)
61
+ return x_t + self.bank(self.n2(x_t))
62
+
63
+
64
+ class AlephLM(nn.Module):
65
+ def __init__(self, cfg: AlephLMConfig):
66
+ super().__init__()
67
+ self.cfg = cfg
68
+ if cfg.tokenizer == "byte-trigram":
69
+ assert cfg.vocab_size == 256, "byte crafts use vocab 256"
70
+ self.embed = TrigramByteEmbedding(cfg.d_model, cfg.context)
71
+ tied = None
72
+ else:
73
+ self.embed = TokenEmbedding(cfg.vocab_size, cfg.d_model, cfg.context)
74
+ tied = self.embed.emb.weight if cfg.tie_embeddings else None
75
+ self.blocks = nn.ModuleList(
76
+ Block(cfg, i) for i in range(cfg.n_layers))
77
+ self.nf = nn.LayerNorm(cfg.d_model)
78
+ self.head = DualHead(cfg.d_model, cfg.vocab_size, cfg.head_K,
79
+ cfg.head_D, cfg.tau, tied_weight=tied)
80
+
81
+ def forward(self, idx=None, targets=None, disable_bank=False,
82
+ disable_hub=False, disable_head_aleph=False,
83
+ input_ids=None, labels=None, attention_mask=None):
84
+ """HF-style aliases are accepted so frozen-trunk tooling drives the
85
+ model unchanged, WITH HF semantics: `labels` are same-position and
86
+ shifted internally (logits[:-1] vs labels[1:]); `targets` are the
87
+ package's own pre-shifted convention and used as-is. attention_mask
88
+ is deliberately ignored: under causal attention with right-padding
89
+ and -100 label masking, pads can never influence a scored position."""
90
+ if idx is None:
91
+ idx = input_ids
92
+ x = self.embed(idx)
93
+ for b in self.blocks:
94
+ x = b(x, disable_bank=disable_bank, disable_hub=disable_hub)
95
+ h = self.nf(x)
96
+ logits = self.head(h, disable_aleph=disable_head_aleph)
97
+ if targets is not None: # pre-shifted (ours)
98
+ loss = F.cross_entropy(
99
+ logits.reshape(-1, logits.shape[-1]).float(),
100
+ targets.reshape(-1), ignore_index=-100)
101
+ elif labels is not None: # HF: shift internally
102
+ loss = F.cross_entropy(
103
+ logits[:, :-1].reshape(-1, logits.shape[-1]).float(),
104
+ labels[:, 1:].reshape(-1), ignore_index=-100)
105
+ else:
106
+ return LMOutput(logits, None)
107
+ return LMOutput(logits, loss)
108
+
109
+ # ---------------------------------------------------- incremental decode
110
+ @torch.no_grad()
111
+ def prefill(self, idx):
112
+ """Run the prompt once, return (last-position logits, decode cache).
113
+ The cache carries per-layer attention state, the trigram history
114
+ bytes, and the absolute position cursor."""
115
+ self.eval()
116
+ from .embedding import PAD_ROW
117
+ caches = []
118
+ x = self.embed(idx)
119
+ for b in self.blocks:
120
+ x, c = b.prefill(x)
121
+ caches.append(c)
122
+ h = self.nf(x)
123
+ logits = self.head(h[:, -1:])
124
+ n = idx.shape[1]
125
+ prev2 = idx[:, -2] if n >= 2 else torch.full_like(idx[:, -1], PAD_ROW)
126
+ return logits, {"layers": caches, "t": n,
127
+ "prev1": idx[:, -1], "prev2": prev2}
128
+
129
+ @torch.no_grad()
130
+ def decode_step(self, next_id, cache):
131
+ """One token through the cached path. next_id: (B,) or (B,1)."""
132
+ next_id = next_id.reshape(-1)
133
+ t = cache["t"]
134
+ assert t < self.cfg.context, "decode exceeded the position table"
135
+ if isinstance(self.embed, TrigramByteEmbedding):
136
+ e = (self.embed.emb0(next_id) + self.embed.emb1(cache["prev1"])
137
+ + self.embed.emb2(cache["prev2"])).unsqueeze(1) \
138
+ + self.embed.pos[:, t:t + 1]
139
+ cache["prev2"] = cache["prev1"]
140
+ cache["prev1"] = next_id
141
+ else:
142
+ e = self.embed.emb(next_id).unsqueeze(1) + self.embed.pos[:, t:t + 1]
143
+ x = e
144
+ for b, c in zip(self.blocks, cache["layers"]):
145
+ x = b.step(x, c)
146
+ cache["t"] = t + 1
147
+ return self.head(self.nf(x))
148
+
149
+ @staticmethod
150
+ def _sample(logits, temperature, top_p):
151
+ logits = logits[:, -1].float()
152
+ if temperature <= 0.02:
153
+ return logits.argmax(-1, keepdim=True)
154
+ probs = F.softmax(logits / temperature, dim=-1)
155
+ sp, si = probs.sort(dim=-1, descending=True)
156
+ keep = (sp.cumsum(-1) - sp) < top_p
157
+ keep[..., :1] = True # top-1 always survives: top_p<=0 must never
158
+ sp = sp * keep # yield an all-zero row (CUDA multinomial on
159
+ return si.gather(-1, torch.multinomial( # zeros poisons the context)
160
+ sp / sp.sum(-1, keepdim=True), 1))
161
+
162
+ @torch.no_grad()
163
+ def generate(self, idx, max_new: int = 128, temperature: float = 1.0,
164
+ top_p: float = 0.95, use_cache: bool = True):
165
+ """Cached decode while the sequence fits the position table; any
166
+ remainder (long prompts, fills past the context) continues through
167
+ the sliding-window parallel path — the hub's constant-size state
168
+ cannot evict, so sliding continuation must recompute."""
169
+ self.eval()
170
+ ctx = self.cfg.context
171
+ if use_cache and idx.shape[1] < ctx and max_new > 0:
172
+ n_cached = min(max_new, ctx - idx.shape[1])
173
+ logits, cache = self.prefill(idx)
174
+ for i in range(n_cached):
175
+ nxt = self._sample(logits, temperature, top_p)
176
+ idx = torch.cat([idx, nxt], dim=1)
177
+ if i + 1 < n_cached:
178
+ logits = self.decode_step(nxt, cache)
179
+ max_new -= n_cached
180
+ for _ in range(max_new):
181
+ logits, _ = self(idx[:, -ctx:])
182
+ nxt = self._sample(logits, temperature, top_p)
183
+ idx = torch.cat([idx, nxt], dim=1)
184
+ return idx
185
+
186
+ def param_count(self) -> int:
187
+ seen, total = set(), 0
188
+ for p in self.parameters():
189
+ if id(p) not in seen:
190
+ seen.add(id(p))
191
+ total += p.numel()
192
+ return total
attention.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Attention blocks: CausalSDPA (the workhorse) and CausalSplatHUB (the
2
+ instrumented aleph read).
3
+
4
+ CausalSplatHUB is causal linear attention through the oriented address:
5
+ prefix-sum memories over the two K-wide halves of the 2K softmax, read by
6
+ the query's halves and normalized by the scalar agreement mass. O(n·K·d)
7
+ compute, no softmax over positions, no selection event anywhere.
8
+
9
+ The naive cumsum form materializes (B, n, K, d) — fine on probe beds,
10
+ fatal at mission scale. forward() therefore uses an exact chunked scan:
11
+ within-chunk causal affinity (B, C, C) + cross-chunk carried states
12
+ (B, K, d). `forward_naive()` is kept verbatim as the equivalence oracle
13
+ for the test array.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import torch
18
+ import torch.nn as nn
19
+ import torch.nn.functional as F
20
+
21
+ from .address import AlephAddress, dtype_floor
22
+
23
+
24
+ class CausalSDPA(nn.Module):
25
+ def __init__(self, d: int, heads: int = 8):
26
+ super().__init__()
27
+ assert d % heads == 0
28
+ self.h = heads
29
+ self.qkv = nn.Linear(d, 3 * d, bias=False)
30
+ self.o = nn.Linear(d, d, bias=False)
31
+ nn.init.orthogonal_(self.qkv.weight)
32
+ nn.init.orthogonal_(self.o.weight)
33
+
34
+ def forward(self, x):
35
+ B, n, d = x.shape
36
+ q, k, v = self.qkv(x).chunk(3, dim=-1)
37
+ q, k, v = (t.view(B, n, self.h, d // self.h).transpose(1, 2)
38
+ for t in (q, k, v))
39
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
40
+ return self.o(y.transpose(1, 2).reshape(B, n, d))
41
+
42
+ # ---------------------------------------------------- incremental decode
43
+ def prefill(self, x):
44
+ """Full causal pass that also returns the decode cache (K/V)."""
45
+ B, n, d = x.shape
46
+ q, k, v = self.qkv(x).chunk(3, dim=-1)
47
+ q, k, v = (t.view(B, n, self.h, d // self.h).transpose(1, 2)
48
+ for t in (q, k, v))
49
+ y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
50
+ return self.o(y.transpose(1, 2).reshape(B, n, d)), {"k": k, "v": v}
51
+
52
+ def step(self, x_t, cache):
53
+ """One new position attending over everything cached (KV cache)."""
54
+ B, _, d = x_t.shape
55
+ q, k, v = self.qkv(x_t).chunk(3, dim=-1)
56
+ q, k, v = (t.view(B, 1, self.h, d // self.h).transpose(1, 2)
57
+ for t in (q, k, v))
58
+ cache["k"] = torch.cat([cache["k"], k], dim=2)
59
+ cache["v"] = torch.cat([cache["v"], v], dim=2)
60
+ y = F.scaled_dot_product_attention(q, cache["k"], cache["v"])
61
+ return self.o(y.transpose(1, 2).reshape(B, 1, d))
62
+
63
+
64
+ class CausalSplatHUB(nn.Module):
65
+ def __init__(self, d: int, K: int = 512, D: int = 32, tau: float = 0.1,
66
+ chunk: int = 128):
67
+ super().__init__()
68
+ self.addr = AlephAddress(K, D, tau)
69
+ self.chunk = chunk
70
+ self.q = nn.Linear(d, D, bias=False)
71
+ self.k = nn.Linear(d, D, bias=False)
72
+ self.v = nn.Linear(d, d, bias=False)
73
+ self.o = nn.Linear(d, d, bias=False)
74
+ for m in (self.q, self.k, self.v, self.o):
75
+ nn.init.orthogonal_(m.weight)
76
+ self.last_den_stats = None # populated each forward for instruments
77
+
78
+ def _halves(self, x):
79
+ qp, qn = self.addr.oriented(self.q(x))
80
+ kp, kn = self.addr.oriented(self.k(x))
81
+ return qp, qn, kp, kn, self.v(x)
82
+
83
+ def forward(self, x):
84
+ B, n, d = x.shape
85
+ qp, qn, kp, kn, v = self._halves(x)
86
+ C = min(self.chunk, n)
87
+ pad = (-n) % C
88
+ if pad:
89
+ z = lambda t: F.pad(t, (0, 0, 0, pad))
90
+ qp, qn, kp, kn, v = z(qp), z(qn), z(kp), z(kn), z(v)
91
+ nc = (n + pad) // C
92
+ K = qp.shape[-1]
93
+ qp, qn, kp, kn = (t.view(B, nc, C, K) for t in (qp, qn, kp, kn))
94
+ v = v.view(B, nc, C, d)
95
+ mask = torch.tril(torch.ones(C, C, device=x.device, dtype=v.dtype))
96
+
97
+ num = torch.zeros(B, nc, C, d, device=x.device, dtype=v.dtype)
98
+ den = torch.zeros(B, nc, C, 1, device=x.device, dtype=v.dtype)
99
+ for kh, qh in ((kp, qp), (kn, qn)):
100
+ S = torch.einsum("bick,bicd->bikd", kh, v) # per-chunk KxD sums
101
+ P = torch.cumsum(S, dim=1) - S # exclusive prefix
102
+ zS = kh.sum(dim=2) # (B, nc, K)
103
+ zP = torch.cumsum(zS, dim=1) - zS
104
+ att = torch.einsum("bick,bijk->bicj", qh, kh) * mask # (B,nc,C,C)
105
+ num = num + torch.einsum("bick,bikd->bicd", qh, P) + att @ v
106
+ den = den + torch.einsum("bick,bik->bic", qh, zP).unsqueeze(-1) \
107
+ + att.sum(dim=-1, keepdim=True)
108
+ num = num.view(B, nc * C, d)[:, :n]
109
+ den = den.view(B, nc * C, 1)[:, :n]
110
+ cl = dtype_floor(den)
111
+ with torch.no_grad():
112
+ self.last_den_stats = (den.min().item(), den.mean().item(),
113
+ (den <= cl).float().mean().item())
114
+ return self.o(num / den.clamp_min(cl))
115
+
116
+ # ---------------------------------------------------- incremental decode
117
+ def prefill(self, x):
118
+ """Full causal pass plus the decode cache. The hub's cache is the
119
+ CONSTANT-SIZE prefix state (Sp, Sn, zp, zn) — O(K·d) regardless of
120
+ sequence length; this is the linear-attention decode advantage."""
121
+ out = self.forward(x)
122
+ qp, qn, kp, kn, v = self._halves(x)
123
+ cache = {"Sp": torch.einsum("bnk,bnd->bkd", kp, v),
124
+ "Sn": torch.einsum("bnk,bnd->bkd", kn, v),
125
+ "zp": kp.sum(dim=1), "zn": kn.sum(dim=1)}
126
+ return out, cache
127
+
128
+ def step(self, x_t, cache):
129
+ """One new position: fold it into the prefix state, read once."""
130
+ qp, qn, kp, kn, v = self._halves(x_t) # (B,1,K)/(B,1,d)
131
+ kp1, kn1, v1 = kp.squeeze(1), kn.squeeze(1), v.squeeze(1)
132
+ cache["Sp"] = cache["Sp"] + kp1.unsqueeze(-1) * v1.unsqueeze(1)
133
+ cache["Sn"] = cache["Sn"] + kn1.unsqueeze(-1) * v1.unsqueeze(1)
134
+ cache["zp"] = cache["zp"] + kp1
135
+ cache["zn"] = cache["zn"] + kn1
136
+ qp1, qn1 = qp.squeeze(1), qn.squeeze(1)
137
+ num = torch.einsum("bk,bkd->bd", qp1, cache["Sp"]) \
138
+ + torch.einsum("bk,bkd->bd", qn1, cache["Sn"])
139
+ den = ((qp1 * cache["zp"]).sum(-1)
140
+ + (qn1 * cache["zn"]).sum(-1)).unsqueeze(-1)
141
+ return self.o((num / den.clamp_min(dtype_floor(den))).unsqueeze(1))
142
+
143
+ def forward_naive(self, x):
144
+ """Reference cumsum form (the validated probe-bed implementation).
145
+ O(n·K·d) memory — test oracle only."""
146
+ qp, qn, kp, kn, v = self._halves(x)
147
+ Sp = torch.cumsum(torch.einsum("bnk,bnd->bnkd", kp, v), dim=1)
148
+ Sn = torch.cumsum(torch.einsum("bnk,bnd->bnkd", kn, v), dim=1)
149
+ zp = torch.cumsum(kp, dim=1)
150
+ zn = torch.cumsum(kn, dim=1)
151
+ num = torch.einsum("bnk,bnkd->bnd", qp, Sp) \
152
+ + torch.einsum("bnk,bnkd->bnd", qn, Sn)
153
+ den = (qp * zp).sum(-1, keepdim=True) + (qn * zn).sum(-1, keepdim=True)
154
+ return self.o(num / den.clamp_min(dtype_floor(den)))
bank.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AnchoredBank — the E1-form anchored FFN, born on its own null path.
2
+
3
+ Bank(x) = trunk(x) + sum_k w_k(x) * sigmoid(g_k) * expert_k(x)
4
+
5
+ Trunk: always-on d->ff->d GELU expert. Dispatch: the signed aleph address
6
+ over a learned K x d codebook read against the layer input (K=3 fat
7
+ experts, the shape validated at parity under encoder pressure). Gates
8
+ init -3.0; expert OUTPUT projections zero-init, so at birth the dispatch
9
+ contributes exactly zero and the bank is bit-identical to its dense
10
+ control (the C6 null path). No balance machinery of any kind —
11
+ differentiation is an attractor, pressure stays out of the task gradient.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+
19
+ from .address import AlephAddress
20
+
21
+
22
+ class AnchoredBank(nn.Module):
23
+ def __init__(self, d: int, n_experts: int = 3, ff: int | None = None,
24
+ tau: float = 0.1, gate_init: float = -3.0):
25
+ super().__init__()
26
+ ff = ff or d
27
+ self.n_experts = n_experts
28
+ self.t_in = nn.Linear(d, ff, bias=False)
29
+ self.t_out = nn.Linear(ff, d, bias=False)
30
+ nn.init.orthogonal_(self.t_in.weight)
31
+ nn.init.orthogonal_(self.t_out.weight)
32
+ self.addr = AlephAddress(n_experts, d, tau)
33
+ w_in = torch.empty(n_experts, d, ff)
34
+ for k in range(n_experts):
35
+ nn.init.orthogonal_(w_in[k])
36
+ self.w_in = nn.Parameter(w_in)
37
+ self.w_out = nn.Parameter(torch.zeros(n_experts, ff, d)) # null path
38
+ self.gates = nn.Parameter(torch.full((n_experts,), gate_init))
39
+ self.last_dispatch = None # (mean|w| per expert, w sample) for instruments
40
+
41
+ def forward(self, x, disable_dispatch: bool = False):
42
+ trunk = self.t_out(F.gelu(self.t_in(x)))
43
+ if disable_dispatch:
44
+ return trunk
45
+ w = self.addr.signed(x) # (B, n, K)
46
+ with torch.no_grad():
47
+ self.last_dispatch = w.detach()
48
+ h = F.gelu(torch.einsum("bnd,kdf->bnkf", x, self.w_in))
49
+ e = torch.einsum("bnkf,kfd->bnkd", h, self.w_out)
50
+ return trunk + torch.einsum("bnk,bnkd->bnd",
51
+ w * torch.sigmoid(self.gates), e)
config.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "mini-beatrix-1",
3
+ "d_model": 768,
4
+ "n_layers": 16,
5
+ "n_heads": 12,
6
+ "context": 2048,
7
+ "vocab_size": 256,
8
+ "tokenizer": "byte-trigram",
9
+ "hub_layers": [
10
+ 4,
11
+ 9,
12
+ 14
13
+ ],
14
+ "hub_K": 512,
15
+ "hub_D": 32,
16
+ "tau": 0.1,
17
+ "bank_experts": 3,
18
+ "bank_ff": null,
19
+ "head_K": 512,
20
+ "head_D": 32,
21
+ "gate_init": -3.0,
22
+ "tie_embeddings": false,
23
+ "hub_chunk": 128,
24
+ "model_type": "mini-beatrix",
25
+ "architectures": [
26
+ "MiniBeatrixForCausalLM"
27
+ ],
28
+ "auto_map": {
29
+ "AutoConfig": "modeling_minibeatrix.MiniBeatrixConfig",
30
+ "AutoModelForCausalLM": "modeling_minibeatrix.MiniBeatrixForCausalLM"
31
+ },
32
+ "torch_dtype": "float32",
33
+ "use_cache": false,
34
+ "transformers_version": "5.13.0"
35
+ }
embedding.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Embeddings.
2
+
3
+ TrigramByteEmbedding — the validated composed byte embedding:
4
+ e_t = E0[x_t] + E1[x_{t-1}] + E2[x_{t-2}] + P[t]
5
+ with the PAD LAW built in permanently: the shift tables carry a dedicated
6
+ pad row (index 256). Padding trigram shifts with a legal byte conflates
7
+ real history with sequence starts and starves address consumption
8
+ (measured +.05..+.11 on repair) — the fix ships on, not opt-in.
9
+
10
+ TokenEmbedding — plain table + positions for BPE crafts.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import torch
15
+ import torch.nn as nn
16
+ import torch.nn.functional as F
17
+
18
+ BYTE_VOCAB = 256
19
+ PAD_ROW = 256 # dedicated pad index in the shift tables (size 257)
20
+
21
+
22
+ class TrigramByteEmbedding(nn.Module):
23
+ def __init__(self, d: int, context: int):
24
+ super().__init__()
25
+ self.emb0 = nn.Embedding(BYTE_VOCAB, d)
26
+ self.emb1 = nn.Embedding(BYTE_VOCAB + 1, d) # + pad row
27
+ self.emb2 = nn.Embedding(BYTE_VOCAB + 1, d)
28
+ self.pos = nn.Parameter(0.01 * torch.randn(1, context, d))
29
+
30
+ def forward(self, idx):
31
+ x = self.emb0(idx) \
32
+ + self.emb1(F.pad(idx, (1, 0), value=PAD_ROW)[:, :-1]) \
33
+ + self.emb2(F.pad(idx, (2, 0), value=PAD_ROW)[:, :-2])
34
+ return x + self.pos[:, : idx.shape[1]]
35
+
36
+
37
+ class TokenEmbedding(nn.Module):
38
+ def __init__(self, vocab: int, d: int, context: int):
39
+ super().__init__()
40
+ self.emb = nn.Embedding(vocab, d)
41
+ self.pos = nn.Parameter(0.01 * torch.randn(1, context, d))
42
+
43
+ def forward(self, idx):
44
+ return self.emb(idx) + self.pos[:, : idx.shape[1]]
head.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """DualHead — the standard readout plus the L-012 aleph read, born null.
2
+
3
+ logits = W_h h + W_s s(h), W_s == 0 at init
4
+
5
+ s(h) is the signed address of a learned low-D projection of h through a
6
+ K_h-anchor head codebook. New structure enters at zero and earns its way
7
+ in by gradient — via WEIGHT-zero init, never gate-zero: mini-beatrix-1
8
+ measured (8.4B tokens) that a gamma-gated branch DEADLOCKS (W_s grad is
9
+ scaled by gamma=0, gamma grad through a random frozen readout is
10
+ zero-mean noise), while the bank's E_out weight-zero self-started into
11
+ +2 bpb of function. gamma remains as a frozen scalar for checkpoint
12
+ compatibility and as the ablation knob; the election gauge is ||W_s||
13
+ and the head_aleph_off toggle.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import torch
18
+ import torch.nn as nn
19
+
20
+ from .address import AlephAddress
21
+
22
+
23
+ class DualHead(nn.Module):
24
+ def __init__(self, d: int, vocab: int, K: int = 512, D: int = 32,
25
+ tau: float = 0.1, tied_weight: nn.Parameter | None = None):
26
+ super().__init__()
27
+ self.tied = tied_weight is not None
28
+ if self.tied:
29
+ # tuple wrapper: reference the embedding table WITHOUT registering
30
+ # it as a duplicate parameter (state_dict must stay alias-free
31
+ # for safetensors export)
32
+ self._tied_ref = (tied_weight,)
33
+ self.bias = nn.Parameter(torch.zeros(vocab))
34
+ else:
35
+ self.w_h = nn.Linear(d, vocab, bias=True)
36
+ self.proj = nn.Linear(d, D, bias=False)
37
+ nn.init.orthogonal_(self.proj.weight)
38
+ self.addr = AlephAddress(K, D, tau)
39
+ self.w_s = nn.Linear(K, vocab, bias=False)
40
+ nn.init.zeros_(self.w_s.weight) # weight-zero: self-starting
41
+ self.gamma = nn.Parameter(torch.ones(1)) # frozen; ablation knob only
42
+ self.gamma.requires_grad_(False)
43
+
44
+ def forward(self, h, disable_aleph: bool = False):
45
+ if self.tied:
46
+ base = h @ self._tied_ref[0].T + self.bias
47
+ else:
48
+ base = self.w_h(h)
49
+ if disable_aleph:
50
+ return base
51
+ return base + self.gamma * self.w_s(self.addr.signed(self.proj(h)))
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:55d927c2b45e038d889a9bb15b3c794838b2032258c5a630be59f03d2f9fd1c8
3
+ size 450531044
modeling_minibeatrix.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HF AutoModel wrapper for mini-beatrix-1 (AlephLM byte-trigram craft).
2
+
3
+ Loads with:
4
+ AutoModelForCausalLM.from_pretrained("AbstractPhil/mini-beatrix-1",
5
+ trust_remote_code=True)
6
+ The model reads raw UTF-8 bytes: input_ids are byte values 0..255.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import torch
11
+ from transformers import PretrainedConfig, PreTrainedModel
12
+ from transformers.generation import GenerationMixin
13
+ from transformers.modeling_outputs import CausalLMOutputWithPast
14
+
15
+ from .presets import AlephLMConfig
16
+ from .alephlm import AlephLM
17
+
18
+
19
+ class MiniBeatrixConfig(PretrainedConfig):
20
+ model_type = "mini-beatrix"
21
+ attribute_map = {"num_hidden_layers": "n_layers",
22
+ "hidden_size": "d_model",
23
+ "num_attention_heads": "n_heads",
24
+ "max_position_embeddings": "context"}
25
+
26
+ def __init__(self, name="mini-beatrix-1", d_model=768, n_layers=16,
27
+ n_heads=12, context=2048, vocab_size=256,
28
+ tokenizer="byte-trigram", hub_layers=(4, 9, 14),
29
+ hub_K=512, hub_D=32, tau=0.1, bank_experts=3, bank_ff=None,
30
+ head_K=512, head_D=32, gate_init=-3.0,
31
+ tie_embeddings=False, hub_chunk=128, **kwargs):
32
+ self.name = name
33
+ self.d_model = d_model
34
+ self.n_layers = n_layers
35
+ self.n_heads = n_heads
36
+ self.context = context
37
+ self.vocab_size = vocab_size
38
+ self.tokenizer = tokenizer
39
+ self.hub_layers = list(hub_layers)
40
+ self.hub_K = hub_K
41
+ self.hub_D = hub_D
42
+ self.tau = tau
43
+ self.bank_experts = bank_experts
44
+ self.bank_ff = bank_ff
45
+ self.head_K = head_K
46
+ self.head_D = head_D
47
+ self.gate_init = gate_init
48
+ self.tie_embeddings = tie_embeddings
49
+ self.hub_chunk = hub_chunk
50
+ super().__init__(**kwargs)
51
+
52
+ def to_aleph(self) -> AlephLMConfig:
53
+ return AlephLMConfig(
54
+ name=self.name, d_model=self.d_model, n_layers=self.n_layers,
55
+ n_heads=self.n_heads, context=self.context,
56
+ vocab_size=self.vocab_size, tokenizer=self.tokenizer,
57
+ hub_layers=tuple(self.hub_layers), hub_K=self.hub_K,
58
+ hub_D=self.hub_D, tau=self.tau, bank_experts=self.bank_experts,
59
+ bank_ff=self.bank_ff, head_K=self.head_K, head_D=self.head_D,
60
+ gate_init=self.gate_init, tie_embeddings=self.tie_embeddings,
61
+ hub_chunk=self.hub_chunk)
62
+
63
+
64
+ class MiniBeatrixForCausalLM(PreTrainedModel, GenerationMixin):
65
+ config_class = MiniBeatrixConfig
66
+ main_input_name = "input_ids"
67
+ supports_gradient_checkpointing = False
68
+ _tied_weights_keys = {}
69
+
70
+ def __init__(self, config: MiniBeatrixConfig):
71
+ super().__init__(config)
72
+ self.model = AlephLM(config.to_aleph())
73
+ self.post_init()
74
+
75
+ def _init_weights(self, module):
76
+ pass # AlephLM initializes itself; loader must not re-init
77
+
78
+ def forward(self, input_ids=None, labels=None, attention_mask=None,
79
+ past_key_values=None, use_cache=None, **kwargs):
80
+ # attention_mask is safely ignored: causal model, right-padding
81
+ # with label masking is the trained convention. No KV cache in
82
+ # this wrapper; generate() recomputes the prefix each step.
83
+ if input_ids.shape[1] > self.config.context:
84
+ input_ids = input_ids[:, -self.config.context:]
85
+ if labels is not None:
86
+ labels = labels[:, -self.config.context:]
87
+ out = self.model(input_ids, labels=labels)
88
+ logits = out[0]
89
+ loss = out[1] if labels is not None else None
90
+ return CausalLMOutputWithPast(loss=loss, logits=logits)
91
+
92
+ def prepare_inputs_for_generation(self, input_ids, **kwargs):
93
+ return {"input_ids": input_ids[:, -self.config.context:]}
presets.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Mission presets — the Mini-Beatrix ladder.
2
+
3
+ Naming convention (voyager style): numbered missions, each a fixed craft.
4
+ Small crafts are "mini-beatrix-N"; the BPE flagship is "beatrix-voyager".
5
+ Beatrix is the lineage collective name; missions are launched in order and
6
+ all upload to the one training repo (TRAINING_REPO), each craft under its
7
+ own path prefix (checkpoints + manifest + tensorboard).
8
+
9
+ mini-beatrix-0 d512 L12 ctx1024 byte-trigram 37.6M gate craft:
10
+ its first toggle evals ARE the anchored-bank-under-AR
11
+ screen (P1) running live.
12
+ mini-beatrix-1 d768 L16 ctx2048 byte-trigram 112.5M first Colab
13
+ mission (default).
14
+ mini-beatrix-2 d1024 L20 ctx2048 byte-trigram 249.1M
15
+ beatrix-voyager d1536 L24 ctx4096 BPE(gpt2 50k) 775.3M flagship;
16
+ vocab-scale head + BPE screens (P2/P5) still open —
17
+ launch only after mini-beatrix verdicts.
18
+
19
+ Every craft is inference-capable on consumer hardware in its shipped
20
+ form (fp8-e4m3 safetensors variants are exported alongside checkpoints).
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass, field, asdict
25
+ from typing import Optional
26
+
27
+
28
+ @dataclass
29
+ class AlephLMConfig:
30
+ name: str = "mini-beatrix-0"
31
+ d_model: int = 512
32
+ n_layers: int = 12
33
+ n_heads: int = 8
34
+ context: int = 1024
35
+ vocab_size: int = 256 # bytes; BPE presets override
36
+ tokenizer: str = "byte-trigram" # "byte-trigram" | "hf:<repo or name>"
37
+ hub_layers: tuple = (3, 7, 11) # CausalSplatHUB depths; () = pure sdpa control
38
+ hub_K: int = 512
39
+ hub_D: int = 32
40
+ tau: float = 0.1
41
+ bank_experts: int = 3 # E1-validated fat-expert count
42
+ bank_ff: Optional[int] = None # None -> d_model (E1 ratio)
43
+ head_K: int = 512
44
+ head_D: int = 32
45
+ gate_init: float = -3.0
46
+ tie_embeddings: bool = False # BPE crafts tie; byte crafts cannot (trigram)
47
+ hub_chunk: int = 128 # chunked-scan block for the hub prefix memories
48
+
49
+ def to_dict(self):
50
+ d = asdict(self)
51
+ d["hub_layers"] = list(self.hub_layers)
52
+ return d
53
+
54
+ @staticmethod
55
+ def from_dict(d):
56
+ d = dict(d)
57
+ d["hub_layers"] = tuple(d.get("hub_layers", ()))
58
+ return AlephLMConfig(**d)
59
+
60
+
61
+ @dataclass
62
+ class TrainConfig:
63
+ # Optimizer split (measured: momentum-geometric +.09 on the aleph;
64
+ # the mechanism is ~20x more optimizer-sensitive than sdpa).
65
+ muon_lr: float = 2e-2
66
+ muon_momentum: float = 0.95
67
+ adam_lr: float = 3e-4 # pure Adam, wd=0 — never AdamW
68
+ warmup_steps: int = 200 # scale insurance; flat after (flat-LR law)
69
+ grad_clip: float = 1.0
70
+ micro_batch: int = 24
71
+ grad_accum: int = 1
72
+ # Cadences (steps)
73
+ log_every: int = 50
74
+ health_every: int = 500
75
+ eval_every: int = 2000
76
+ ckpt_every: int = 2000 # safetensors + resume .pt
77
+ fp8_every_ckpts: int = 5 # every Nth checkpoint also exports fp8
78
+ tb_upload_every: int = 1000
79
+ # Eval sizes
80
+ val_tokens: int = 262144
81
+ canary_episodes: int = 128
82
+ seed: int = 1337
83
+ compile: bool = False
84
+
85
+
86
+ # All missions upload to the one training repo, each under its own prefix
87
+ # (Phil's repo: checkpoints + manifests + tensorboard for every craft).
88
+ TRAINING_REPO = "AbstractPhil/alephllm-mini-beatrix-training"
89
+
90
+
91
+ @dataclass
92
+ class Preset:
93
+ model: AlephLMConfig
94
+ train: TrainConfig
95
+ hf_repo: str = TRAINING_REPO # run repo (ckpts+manifest+tb)
96
+ curriculum: list = field(default_factory=list) # [(phase, dataset, planned_tokens)]
97
+
98
+ @property
99
+ def prefix(self) -> str: # path prefix inside hf_repo
100
+ return self.model.name
101
+
102
+
103
+ def _curriculum(warm: int, main: int, ext: int):
104
+ return [
105
+ dict(name="warmup_wikitext", dataset="wikitext-103", planned_tokens=warm,
106
+ status="planned"),
107
+ dict(name="fineweb_main", dataset="fineweb-edu", planned_tokens=main,
108
+ status="planned"),
109
+ # Deliberately not prepped beyond a name — the full plan exists in the
110
+ # manifest, the data work happens when the phase activates.
111
+ dict(name="fineweb_extended", dataset="fineweb-edu", planned_tokens=ext,
112
+ status="deferred"),
113
+ # phase C: distribution shift toward chat format / simple register /
114
+ # narrative (incl. moral texture) / binding demand — see streams.ANNEAL_MIX
115
+ dict(name="anneal_mix", dataset="anneal-mix",
116
+ planned_tokens=2_000_000_000, status="deferred"),
117
+ ]
118
+
119
+
120
+ PRESETS: dict[str, Preset] = {
121
+ "mini-beatrix-0": Preset(
122
+ model=AlephLMConfig(name="mini-beatrix-0"),
123
+ train=TrainConfig(micro_batch=96, grad_accum=1),
124
+ curriculum=_curriculum(150_000_000, 1_000_000_000, 2_000_000_000),
125
+ ),
126
+ "mini-beatrix-1": Preset(
127
+ model=AlephLMConfig(name="mini-beatrix-1", d_model=768, n_layers=16,
128
+ n_heads=12, context=2048, hub_layers=(4, 9, 14)),
129
+ train=TrainConfig(micro_batch=48, grad_accum=3),
130
+ curriculum=_curriculum(300_000_000, 3_000_000_000, 6_000_000_000),
131
+ ),
132
+ "mini-beatrix-2": Preset(
133
+ model=AlephLMConfig(name="mini-beatrix-2", d_model=1024, n_layers=20,
134
+ n_heads=16, context=2048, hub_layers=(5, 11, 17)),
135
+ train=TrainConfig(micro_batch=32, grad_accum=6),
136
+ curriculum=_curriculum(300_000_000, 5_000_000_000, 10_000_000_000),
137
+ ),
138
+ "beatrix-voyager": Preset(
139
+ model=AlephLMConfig(name="beatrix-voyager", d_model=1536, n_layers=24,
140
+ n_heads=16, context=4096, vocab_size=50257,
141
+ tokenizer="hf:gpt2", tie_embeddings=True,
142
+ hub_layers=(6, 13, 20)),
143
+ train=TrainConfig(micro_batch=8, grad_accum=16),
144
+ curriculum=_curriculum(500_000_000, 12_000_000_000, 24_000_000_000),
145
+ ),
146
+ }
147
+
148
+ # Pure-sdpa control crafts (hub layers removed) — the running architecture
149
+ # control for any mission: same params otherwise, suffix "-control".
150
+ for _name in list(PRESETS):
151
+ _p = PRESETS[_name]
152
+ _m = AlephLMConfig.from_dict(_p.model.to_dict())
153
+ _m.name = _name + "-control"
154
+ _m.hub_layers = ()
155
+ PRESETS[_name + "-control"] = Preset(
156
+ model=_m, train=_p.train,
157
+ curriculum=[dict(x) for x in _p.curriculum])
158
+
159
+
160
+ def get_preset(name: str) -> Preset:
161
+ if name not in PRESETS:
162
+ raise KeyError(f"unknown preset '{name}' — have: {sorted(PRESETS)}")
163
+ return PRESETS[name]