Text Generation
Transformers
Safetensors
mini-beatrix
byte-level
tokenizer-free
aleph
signed-address
custom_code
Instructions to use AbstractPhil/mini-beatrix-1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use AbstractPhil/mini-beatrix-1 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="AbstractPhil/mini-beatrix-1", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("AbstractPhil/mini-beatrix-1", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use AbstractPhil/mini-beatrix-1 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "AbstractPhil/mini-beatrix-1" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "AbstractPhil/mini-beatrix-1", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/AbstractPhil/mini-beatrix-1
- SGLang
How to use AbstractPhil/mini-beatrix-1 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "AbstractPhil/mini-beatrix-1" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "AbstractPhil/mini-beatrix-1", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "AbstractPhil/mini-beatrix-1" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "AbstractPhil/mini-beatrix-1", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use AbstractPhil/mini-beatrix-1 with Docker Model Runner:
docker model run hf.co/AbstractPhil/mini-beatrix-1
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- README.md +67 -0
- address.py +84 -0
- alephlm.py +192 -0
- attention.py +154 -0
- bank.py +51 -0
- config.json +35 -0
- embedding.py +44 -0
- head.py +51 -0
- model.safetensors +3 -0
- modeling_minibeatrix.py +93 -0
- 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]
|