AbstractPhil commited on
Commit
acf94d3
·
verified ·
1 Parent(s): f29f5dd

mini-beatrix-3: the stage arms (arms off and arms on in one package)

Browse files
README.md CHANGED
@@ -7,20 +7,52 @@ tags:
7
  - linear-attention
8
  - splat-attention
9
  - aleph
 
10
  - custom_code
11
  ---
12
 
13
  # mini-beatrix-3
14
 
15
- **The 32-block rung of the mini-beatrix ladder.** 376M parameters,
16
- byte-level (vocab 256), with a multi-constellation **CausalSplatHUB**
17
- (signed-address linear attention over learned codebook blackboards) and an
18
- anchored expert bank in **every one of its 32 blocks**. No
19
- softmax-over-positions attention anywhere. Trained on 64.4B bytes (245,674
20
  steps, about 295 hours on two RTX 5090s) through a staged curriculum,
21
  completed 2026-10-05.
22
 
23
- This repository is the bare model: no adapter arms are attached or included.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  ## Architecture
26
 
@@ -43,6 +75,121 @@ This repository is the bare model: no adapter arms are attached or included.
43
  the end (+6.13) is nearly twice every earlier one and is being repeated
44
  before it is relied on.
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  ## The special-token control plane
47
 
48
  Thirteen ids that valid UTF-8 can never produce carry structure and are
@@ -59,44 +206,33 @@ Thirteen ids that valid UTF-8 can never produce carry structure and are
59
  | 0xFA 0xF9 0xF8 0xF6 0xC0 0xC1 | THINK DATA SEP CUE RES | reserved/instrument |
60
 
61
  Chat format: `[SYS] text [END] [USER] text [END] [MODEL] text [END]`. The
62
- frame is unforgeable (encoded text cannot contain a special).
 
 
63
 
64
- ## Usage
65
 
66
  ```python
67
- import torch
68
- from transformers import AutoModelForCausalLM
69
-
70
- model = AutoModelForCausalLM.from_pretrained(
71
- "AbstractPhil/mini-beatrix-3", trust_remote_code=True)
72
-
73
  prompt = "The history of astronomy begins"
74
  ids = torch.tensor([list(prompt.encode("utf-8"))])
75
- out = model.generate(ids, max_new_tokens=200, do_sample=True, top_p=0.95)
76
  print(bytes(int(i) for i in out[0]).decode("utf-8", errors="replace"))
77
  ```
78
 
79
- Chat-framed prompting (the frame was taught in the final 4.0B bytes, so
80
- treat it as a young capability):
81
-
82
- ```python
83
- SYS, USER, MODEL, END = 0xFB, 0xFE, 0xFD, 0xFC
84
- turns = ([SYS] + list(b"You are Beatrix, a small byte-level language model.")
85
- + [END] + [USER] + list(b"Who are you?") + [END] + [MODEL])
86
- out = model.generate(torch.tensor([turns]), max_new_tokens=100,
87
- do_sample=True, top_p=0.95)
88
- ```
89
-
90
  ## Weight files
91
 
92
  | file | precision | what it is |
93
  |------|-----------|------------|
94
- | `model.safetensors` | bf16 | the final weights (step 245,674); the default |
95
  | `model.fp32.safetensors` | fp32 | the trainer's master weights at the same step; load with `variant="fp32"` |
 
 
 
96
 
97
  Training ran under bf16 autocast over fp32 master weights. The bf16 file is
98
  those masters rounded to bf16, tensor for tensor. Each file's header names
99
- its precision and step.
 
100
 
101
  ## Training
102
 
@@ -117,20 +253,22 @@ Final validation: **0.9861 bpb** on the fineweb-edu holdout. The web
117
  pretraining phase closed at 0.9838; the curriculum's stage diets moved the
118
  web holdout as high as 1.15, and the anneal brought it back.
119
 
120
- ## Stage arms
121
-
122
- During curriculum stages 1–4 each stage trained a detachable 13.7M-parameter
123
- adapter (an arm) beside the trunk. At step 148,000 the arms were detached and
124
- the trunk finished the curriculum alone. Those arm files are in the training
125
- repo under `mini-beatrix-3/arms/`; they are bound to the trunk states they
126
- were trained with (through step 148,000), not to these final weights. This
127
- repository carries no arms.
128
 
129
  ## Lineage
130
 
131
  Code: [AbstractEyes/alephllm](https://github.com/AbstractEyes/alephllm)
132
- (this repo vendors the model files; alephllm 0.10.5).
 
 
133
  Siblings: [mini-beatrix-2s](https://huggingface.co/AbstractPhil/mini-beatrix-2s)
134
- (237M, 20 blocks, the previous rung) and
 
 
135
  [mini-beatrix-1](https://huggingface.co/AbstractPhil/mini-beatrix-1)
136
  (112M, the 3-hub hybrid).
 
7
  - linear-attention
8
  - splat-attention
9
  - aleph
10
+ - adapters
11
  - custom_code
12
  ---
13
 
14
  # mini-beatrix-3
15
 
16
+ **The 32-block rung of the mini-beatrix ladder, with its stage arms.** 376M
17
+ parameters, byte-level (vocab 256), with a multi-constellation
18
+ **CausalSplatHUB** (signed-address linear attention over learned codebook
19
+ blackboards) and an anchored expert bank in **every one of its 32 blocks**.
20
+ No softmax-over-positions attention anywhere. Trained on 64.4B bytes (245,674
21
  steps, about 295 hours on two RTX 5090s) through a staged curriculum,
22
  completed 2026-10-05.
23
 
24
+ The package ships **both states in one repository**: the bare model (arms
25
+ off) and the model with its **stage arms** mounted (arms on). An arm is a
26
+ small detachable adapter trained on this exact frozen core. The arms are a
27
+ **group**: they were trained switched on together, and they are mounted
28
+ together. Each stage also has a **solo arm**, trained alone, for use one at
29
+ a time. Arms are guests on the core, never a change to it: the core's
30
+ weights are the same files either way, and detaching restores the bare
31
+ model bit for bit.
32
+
33
+ ```python
34
+ import torch
35
+ from transformers import AutoModelForCausalLM
36
+
37
+ m = AutoModelForCausalLM.from_pretrained(
38
+ "AbstractPhil/mini-beatrix-3", trust_remote_code=True).eval()
39
+
40
+ m.say("Who are you?") # arms off: the bare core, its own chat frame
41
+
42
+ m.mount_arm("stages-1-4") # arms on: the stage arms, all on together
43
+ m.detach_arm() # the bare core again, verified bit-exact
44
+ ```
45
+
46
+ Arms on from the first call:
47
+
48
+ ```python
49
+ m = AutoModelForCausalLM.from_pretrained(
50
+ "AbstractPhil/mini-beatrix-3", trust_remote_code=True,
51
+ default_arm="stages-1-4").eval()
52
+ ```
53
+
54
+ The model reads **raw UTF-8 bytes**: `input_ids` are byte values 0–255.
55
+ There is no tokenizer to download.
56
 
57
  ## Architecture
58
 
 
75
  the end (+6.13) is nearly twice every earlier one and is being repeated
76
  before it is relied on.
77
 
78
+ ## The stage arms
79
+
80
+ The curriculum taught the model nine kinds of text, one stage after another,
81
+ and the finished model moved on from each stage's text as the later stages
82
+ came. The stage arms bring that text back without touching the core. They
83
+ were fitted on the finished, frozen core **as a group**: the arms of stages 1 and 2 were trained first and then held fixed, and the arms of stages 3 and 4 were attached over them, one after the other, and trained in their presence, so the group works with every arm on at once.
84
+ `m.arms()` returns the tables below as data, with each row's recipe and full
85
+ measurements.
86
+
87
+ **The group `stages-1-4`** (4 arms, 54.8M parameters; two seeds):
88
+
89
+ | arm | its stage's text | stage text, arms off → the group on (bpb) | this arm alone (bpb) | stage items in the stage's own form, off → group on | stage items in a new form, off → group on |
90
+ |---|---|---|---|---|---|
91
+ | `stages-1-4/s1_perspective` | one small event retold from each side: seen from a person, said to them, told about them | 0.554 → 0.048 | 0.052 | 55% → 96% | 46% → 48% |
92
+ | `stages-1-4/s2_concept` | kinds, properties and differences: what a thing is a kind of, what its kind can do, how two things differ | 0.721 → 0.112 | 0.350 | 30% → 98% | 68% → 78% |
93
+ | `stages-1-4/s3_rules` | if-then rules over made-up words, followed step by step to what follows | 0.930 → 0.224 | 0.824 | 16% → 90% | 7% → 30% |
94
+ | `stages-1-4/s4_arith` | small arithmetic worked out in text | 1.234 → 0.338 | 1.127 | 34% → 61% | 24% → 24% |
95
+
96
+ With the group on, held-out web text moves by +0.0017 bpb (the limit set for it was +0.012), and the nine-suite probe mean reads 0.411 with arms off and 0.419 with the group on.
97
+
98
+ The numbers are those of the packaged weights. A second run of the same recipe from other seeds read the stage texts with its group on at 0.048, 0.121, 0.222, 0.338 bpb and moved web text by +0.0011.
99
+
100
+ Each member mounted alone, and the whole group, on every stage's held-out text (bpb):
101
+
102
+ | mounted | stage 1 text | stage 2 text | stage 3 text | stage 4 text | web text |
103
+ |---|---|---|---|---|---|
104
+ | nothing (arms off) | 0.554 | 0.721 | 0.930 | 1.234 | 0.951 |
105
+ | only `stages-1-4/s1_perspective` | 0.053 | 0.239 | 0.509 | 0.723 | 0.952 |
106
+ | only `stages-1-4/s2_concept` | 0.527 | 0.350 | 0.619 | 0.880 | 0.952 |
107
+ | only `stages-1-4/s3_rules` | 0.554 | 0.724 | 0.824 | 1.234 | 0.951 |
108
+ | only `stages-1-4/s4_arith` | 0.553 | 0.721 | 0.928 | 1.127 | 0.951 |
109
+ | the whole group `stages-1-4` | 0.048 | 0.112 | 0.224 | 0.338 | 0.952 |
110
+
111
+ **How to read the tables.** *Stage text* is the loss, in bits per byte, on
112
+ held-out text of the arm's own stage, with arms off and with the whole group
113
+ on. *This arm alone* is the same loss with only that one arm mounted. *Stage
114
+ items* are short test questions from that stage, scored as the share answered
115
+ correctly, first in the form the stage text uses and then in a form it does
116
+ not. The *web text* figure is the change on held-out ordinary web text
117
+ (fineweb-edu) with the group on: the arms were trained to leave it alone.
118
+
119
+ **The group is the unit.** The group does not divide its work one stage to an arm. The first arm is a generalist: mounted alone it reads every stage's text well below the bare model, and the second holds a part of stages 2 to 4 (second table). The arms of stages 3 and 4 were trained on top of that fixed pair and add what the pair leaves: with only the first two arms on, stage 3 text reads 0.32 bpb and stage 4 text 0.48, and with the whole group they read 0.22 and 0.34. Mounted alone, the last two arms do little. So mount the group as a whole: that is the state it was trained and measured in. For one stage by itself, use its solo arm below. The first two arms also had more training than the last two (3,200 and 2,400 steps against 800), and no arm here is trained to its limit: at 800 steps a solo arm's stage text is still falling.
120
+
121
+ **What the arms do, plainly.** The group recovers each stage's own kind of
122
+ text. The gain is in each stage's own forms. Whether it carries over to new
123
+ forms is shown in the first table's last column, where it was measured.
124
+
125
+ ### Arms off and arms on
126
+
127
+ ```python
128
+ m.arm # None: arms off
129
+ h = m.mount_arm("stages-1-4") # arms on: the whole group
130
+ with h.all_off(): # every member masked: the bare core's logits
131
+ m.say("Hello there.")
132
+ m.detach_arm(verify=True) # raises if the restored core is not bit-exact
133
+ m.mount_arm("stages-1-4/s1_perspective") # one member alone (the "alone" readings)
134
+ ```
135
+
136
+ The group mounts its members in the order they were trained, always on, one
137
+ after another at every block, with no mixer between them. `default_arm` in
138
+ `from_pretrained` (or in `config.json`) mounts the group as the model loads.
139
+ `save_pretrained` refuses while an arm is mounted, so an arm can never be
140
+ written into the core's weight file.
141
+
142
+ ### Solo arms
143
+
144
+ Each stage also has an arm that was trained **alone** on the same frozen
145
+ core, for when one stage's text is all that matters. A solo arm holds its
146
+ whole stage by itself.
147
+
148
+ | arm | its stage's text | stage text, arm off → on (bpb) | web text (bpb) | stage items in the stage's own form | stage items in a new form | seeds |
149
+ |---|---|---|---|---|---|---|
150
+ | `solo/s1_perspective` | one small event retold from each side: seen from a person, said to them, told about them | 0.554 → 0.081 (a second seed: 0.080) | +0.0002 | 55% → 75% | 46% → 41% | two |
151
+ | `solo/s2_concept` | kinds, properties and differences: what a thing is a kind of, what its kind can do, how two things differ | 0.721 → 0.120 (a second seed: 0.120) | +0.0003 | 30% → 84% | 68% → 73% | two |
152
+ | `solo/s3_rules` | if-then rules over made-up words, followed step by step to what follows | 0.930 → 0.372 (a second seed: 0.374) | +0.0009 | 16% → 60% | 7% → 12% | two |
153
+ | `solo/s4_arith` | small arithmetic worked out in text | 1.234 → 0.389 (a second seed: 0.388) | +0.0007 | 34% → 49% | 24% → 24% | two |
154
+
155
+ ```python
156
+ m.mount_arm("solo/s1_perspective") # one solo arm; mounting another swaps it
157
+ ```
158
+
159
+ Solo arms are for use **one at a time**. Each was trained to leave ordinary
160
+ web text alone, but nothing taught it to stay out of the other stages' text.
161
+ When the four solo arms of stages 1 to 4 were switched on together, three of
162
+ the four stage texts read worse than with no arm at all. To have several
163
+ stages on at once, mount the group: its arms were trained together for
164
+ exactly that.
165
+
166
+ ### What an arm is
167
+
168
+ Each arm places one module after every decoder block. At a site it projects
169
+ the block's output into 16 slots of dimension 8, reads them through a 16-atom
170
+ **aleph address** (a closed-form signed read, never a selector), and adds a
171
+ gated patch back into the residual stream:
172
+
173
+ slots = proj(x).view(B, n, 16, 8)
174
+ m_hat = sum_k sinh(u_k) A_k / sum_k cosh(u_k), u = (x_hat . A) / tau
175
+ x = x + sigmoid(gate) * consume(m_hat)
176
+
177
+ 13.7M parameters per arm over the 32 blocks. Every arm was trained with a
178
+ quiet term: beside each chunk of its own stage's text it saw a chunk of text
179
+ that is not its own and was penalised for changing the model's predictions
180
+ there. For a group arm that text was ordinary web text and a partner arm's
181
+ stage text, which is why the group can be left on and why no member harms a
182
+ partner's stage. For a solo arm it was web text only.
183
+
184
+ ### Detach is exact
185
+
186
+ Mounting swaps the model's block list for wrapped blocks and keeps the
187
+ originals. `detach_arm()` puts the originals back and re-runs a fixed probe:
188
+ the restored logits must equal the pre-mount fingerprint bit for bit, or it
189
+ raises. Before release, every packaged row was checked to produce logits
190
+ identical to the adapter library the arms were trained with
191
+ ([amoe-lora](https://github.com/AbstractEyes/amoe-lora)).
192
+
193
  ## The special-token control plane
194
 
195
  Thirteen ids that valid UTF-8 can never produce carry structure and are
 
206
  | 0xFA 0xF9 0xF8 0xF6 0xC0 0xC1 | THINK DATA SEP CUE RES | reserved/instrument |
207
 
208
  Chat format: `[SYS] text [END] [USER] text [END] [MODEL] text [END]`. The
209
+ frame is unforgeable (encoded text cannot contain a special). `m.say()`
210
+ renders it for you; the chat frame was taught in the final 4.0B bytes, so
211
+ treat it as a young capability.
212
 
213
+ ## Plain generation
214
 
215
  ```python
 
 
 
 
 
 
216
  prompt = "The history of astronomy begins"
217
  ids = torch.tensor([list(prompt.encode("utf-8"))])
218
+ out = m.generate(ids, max_new_tokens=200, do_sample=True, top_p=0.95)
219
  print(bytes(int(i) for i in out[0]).decode("utf-8", errors="replace"))
220
  ```
221
 
 
 
 
 
 
 
 
 
 
 
 
222
  ## Weight files
223
 
224
  | file | precision | what it is |
225
  |------|-----------|------------|
226
+ | `model.safetensors` | bf16 | the final weights (step 245,674); the default, and the core every arm was trained on |
227
  | `model.fp32.safetensors` | fp32 | the trainer's master weights at the same step; load with `variant="fp32"` |
228
+ | `arms/<group>/<arm>.safetensors` | fp32 | one file per arm of a group, as trained |
229
+ | `arms/solo/<arm>.safetensors` | fp32 | one file per solo arm, as trained |
230
+ | `arms/index.json` | | the arm table as data |
231
 
232
  Training ran under bf16 autocast over fp32 master weights. The bf16 file is
233
  those masters rounded to bf16, tensor for tensor. Each file's header names
234
+ its precision and step. The arms were fitted on the bf16 file, so that is
235
+ the core their measurements belong to.
236
 
237
  ## Training
238
 
 
253
  pretraining phase closed at 0.9838; the curriculum's stage diets moved the
254
  web holdout as high as 1.15, and the anneal brought it back.
255
 
256
+ **The arms.** During curriculum stages 1–4 an arm was trained beside the
257
+ trunk for each stage; those arms stayed nearly empty, because the trunk took
258
+ each stage's text in before its arm could, and they were detached at step
259
+ 148,000 (their files are in the training repo under `mini-beatrix-3/arms/`,
260
+ bound to those earlier trunk states). The arms in this repository were fitted
261
+ afterwards on the finished, frozen core: as a group in two steps (first the arms of stages 1 and 2, in a run that added the four stages' text one stage at a time with every attached arm training under one pure Adam, 800 steps per stage; then, with those two held fixed and on, a new arm for stage 3 and after it a new arm for stage 4, 800 steps each), every arm kept quiet on ordinary web text and on its partners' stage text; and each alone, 800 steps, kept quiet on web text.
 
 
262
 
263
  ## Lineage
264
 
265
  Code: [AbstractEyes/alephllm](https://github.com/AbstractEyes/alephllm)
266
+ (this repo vendors the model files; alephllm 0.10.5) and
267
+ [AbstractEyes/amoe-lora](https://github.com/AbstractEyes/amoe-lora) (the arm
268
+ library; `arms.py` here is a self-contained re-expression of its runtime).
269
  Siblings: [mini-beatrix-2s](https://huggingface.co/AbstractPhil/mini-beatrix-2s)
270
+ (237M, 20 blocks, the previous rung),
271
+ [mini-beatrix-2.5s](https://huggingface.co/AbstractPhil/mini-beatrix-2.5s)
272
+ (the 2s core with its arms) and
273
  [mini-beatrix-1](https://huggingface.co/AbstractPhil/mini-beatrix-1)
274
  (112M, the 3-hub hybrid).
arms.py ADDED
@@ -0,0 +1,598 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """arms.py — the detachable arm runtime for mini-beatrix-3.
2
+
3
+ An *arm* is a small aleph-addressed patch head placed after every decoder
4
+ block of one exact frozen core. It is not a low-rank matrix: each site
5
+ projects the block output into `n_slots` slots of dimension D, reads them
6
+ through a K-atom aleph address (a closed-form signed read, never a
7
+ selector), and adds a gated patch back into the residual stream:
8
+
9
+ slots = proj(x).view(B, n, n_slots, D)
10
+ m_hat = sum_k sinh(u_k) A_k / sum_k cosh(u_k), u = (x_hat . A) / tau
11
+ x = x + sigmoid(gate) * consume(m_hat)
12
+
13
+ `mount()` makes the live core carry exactly one recipe and returns a
14
+ handle; `handle.detach(verify=True)` restores the untouched blocks and
15
+ asserts the restored logits are BIT-EXACT with a pre-attach fingerprint.
16
+ That exactness is the point of the design: an arm is a guest, never a
17
+ migration of the core.
18
+
19
+ Recipes carried by this package:
20
+ anchor one arm, always on
21
+ stack several anchor arms applied always-on, nested inner to outer,
22
+ with no mixer between them (the ungated composition)
23
+ routed a dense signed dispatch over member arms, optionally with a
24
+ detached bounded per-member gain read from a causal usage EMA
25
+
26
+ THE DAMPING LAW (do not "fix" it): the dispatch denominator sums cosh
27
+ over ALL members, including masked ones, and masking a member removes
28
+ its delta WITHOUT renormalizing. Masked is not solo.
29
+
30
+ This file is self-contained (torch + safetensors). It is a vendored,
31
+ verified re-expression of the amoe-lora runtime the arms were trained
32
+ with (github.com/AbstractEyes/amoe-lora): the packaged arms were checked
33
+ to produce bit-identical logits through both paths before release.
34
+ """
35
+ from __future__ import annotations
36
+
37
+ import contextlib
38
+ import json
39
+ import os
40
+ from dataclasses import dataclass
41
+
42
+ import torch
43
+ import torch.nn as nn
44
+ import torch.nn.functional as F
45
+
46
+ ARM_TYPES = ("anchor", "stack", "routed")
47
+
48
+
49
+ # --------------------------------------------------------------- geometry
50
+ @dataclass
51
+ class AdapterSpec:
52
+ n_slots: int = 16
53
+ K: int = 64
54
+ D: int = 4
55
+ tau: float = 0.1
56
+ hidden: int = 178
57
+ gate_init: float = -3.0
58
+
59
+
60
+ class AlephAddress(nn.Module):
61
+ """m_hat(x) = sum_k sinh(u_k) A_k / sum_k cosh(u_k), max-|u| stabilized.
62
+
63
+ `home` snapshots the codebook at init and is required in every arm
64
+ state (the drift gauge)."""
65
+
66
+ def __init__(self, K: int = 64, D: int = 4, tau: float = 0.1):
67
+ super().__init__()
68
+ self.K, self.D, self.tau = K, D, tau
69
+ self.codebook = nn.Parameter(F.normalize(torch.randn(K, D), dim=-1))
70
+ self.register_buffer("home", self.codebook.detach().clone())
71
+
72
+ def m_hat(self, x: torch.Tensor) -> torch.Tensor:
73
+ A = F.normalize(self.codebook, dim=-1)
74
+ u = (F.normalize(x, dim=-1) @ A.transpose(-1, -2)) / self.tau
75
+ m = u.abs().amax(dim=-1, keepdim=True)
76
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
77
+ return ((ep - en) @ A) / (ep + en).sum(dim=-1, keepdim=True)
78
+
79
+ @torch.no_grad()
80
+ def drift(self) -> float:
81
+ """Mean angular drift (radians) of the codebook from `home`."""
82
+ A = F.normalize(self.codebook, dim=-1)
83
+ H = F.normalize(self.home, dim=-1)
84
+ return float(torch.arccos((A * H).sum(-1).clamp(-1.0, 1.0)).mean())
85
+
86
+
87
+ class SquaredReLU(nn.Module):
88
+ def forward(self, x):
89
+ return F.relu(x) ** 2
90
+
91
+
92
+ class RelayPatchwork(nn.Module):
93
+ """One arm site: projection -> aleph address -> squared-ReLU patch head,
94
+ added through a sigmoid gate."""
95
+
96
+ def __init__(self, d: int, spec: AdapterSpec | None = None):
97
+ super().__init__()
98
+ s = spec or AdapterSpec()
99
+ self.spec = s
100
+ self.n_slots = s.n_slots
101
+ self.proj = nn.Linear(d, s.n_slots * s.D, bias=False)
102
+ self.addr = AlephAddress(s.K, s.D, s.tau)
103
+ self.consume = nn.Sequential(
104
+ nn.Linear(s.n_slots * s.D, s.hidden), SquaredReLU(),
105
+ nn.LayerNorm(s.hidden), nn.Linear(s.hidden, d))
106
+ self.gate = nn.Parameter(torch.tensor(float(s.gate_init)))
107
+
108
+ def forward(self, x):
109
+ B, n, _ = x.shape
110
+ slots = self.proj(x).view(B, n, self.n_slots, self.spec.D)
111
+ feats = self.addr.m_hat(slots).reshape(B, n, -1)
112
+ return x + torch.sigmoid(self.gate) * self.consume(feats)
113
+
114
+
115
+ def spec_from_state(state: dict, prefix: str | None = None) -> AdapterSpec:
116
+ """Arm geometry read from the arm's own tensors (the library holds more
117
+ than one shape, and modules are built before the state loads)."""
118
+ if prefix is None:
119
+ blocks = sorted(int(k.split(".")[0]) for k in state
120
+ if k.endswith(".addr.codebook") and k.split(".")[0].isdigit())
121
+ prefix = f"{blocks[0]}." if blocks else "0."
122
+ cb = state.get(f"{prefix}addr.codebook")
123
+ proj = state.get(f"{prefix}proj.weight")
124
+ cons = state.get(f"{prefix}consume.0.weight")
125
+ if cb is None or proj is None or cons is None:
126
+ return AdapterSpec()
127
+ D = int(cb.shape[1])
128
+ return AdapterSpec(n_slots=int(proj.shape[0] // D), K=int(cb.shape[0]),
129
+ D=D, hidden=int(cons.shape[0]))
130
+
131
+
132
+ # --------------------------------------------------------------- wrappers
133
+ class BlockWithAdapter(nn.Module):
134
+ """Wraps one decoder block. `enabled=False` returns the block output
135
+ untouched — the single-arm half of the toggle law."""
136
+
137
+ def __init__(self, block: nn.Module, adapter: RelayPatchwork):
138
+ super().__init__()
139
+ self.block = block
140
+ self.adapter = adapter
141
+ self.enabled = True
142
+
143
+ def forward(self, *args, **kwargs):
144
+ out = self.block(*args, **kwargs)
145
+ if not self.enabled:
146
+ return out
147
+ if isinstance(out, tuple):
148
+ return (self.adapter(out[0]),) + out[1:]
149
+ return self.adapter(out)
150
+
151
+ def prefill(self, *args, **kwargs):
152
+ out, cache = self.block.prefill(*args, **kwargs)
153
+ return (self.adapter(out) if self.enabled else out), cache
154
+
155
+ def step(self, *args, **kwargs):
156
+ out = self.block.step(*args, **kwargs)
157
+ if not self.enabled:
158
+ return out
159
+ if isinstance(out, tuple):
160
+ return (self.adapter(out[0]),) + out[1:]
161
+ return self.adapter(out)
162
+
163
+
164
+ class AnchorDispatch(nn.Module):
165
+ """Dense signed sinh/cosh dispatch over frozen member arms.
166
+
167
+ out = x + sum_a (sinh u_a / sum_all cosh u) (arm_a(x) - x)
168
+
169
+ No top-k, no softmax over members, no load balancing. The denominator
170
+ sums over every member, masked or not (the damping law)."""
171
+
172
+ def __init__(self, anchors: nn.ModuleList, d: int, emb: int = 64,
173
+ tau: float = 0.1):
174
+ super().__init__()
175
+ self.anchors = anchors
176
+ self.tau = tau
177
+ A = len(anchors)
178
+ self.register_buffer("key_proj", torch.zeros(emb, d))
179
+ self.dispatch = nn.Parameter(torch.zeros(A, emb))
180
+ self.enabled = [True] * A
181
+
182
+ def forward(self, x):
183
+ emb = F.normalize(x @ self.key_proj.T, dim=-1)
184
+ C = F.normalize(self.dispatch, dim=-1)
185
+ u = (emb @ C.T) / self.tau
186
+ m = u.abs().amax(dim=-1, keepdim=True)
187
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
188
+ w = ep - en
189
+ z = (ep + en).sum(dim=-1, keepdim=True) # ALL members (law)
190
+ out = x
191
+ for k, a in enumerate(self.anchors):
192
+ if self.enabled[k]:
193
+ out = out + (w[..., k:k + 1] / z) * (a(x) - x)
194
+ return out
195
+
196
+
197
+ class GainDispatch(AnchorDispatch):
198
+ """AnchorDispatch with a detached bounded per-member gain on the
199
+ numerator (the denominator is untouched, the signs are untouched):
200
+
201
+ s_a(t) = cosh u_a / sum_all cosh u (the member's share)
202
+ ubar_a = causal EMA of s_a over bytes, rate rho, start 1/A
203
+ alpha_a = clamp(1 + gamma (A ubar_a - 1), a_min, a_max)
204
+ out = x + sum_a alpha_a (sinh u_a / sum_all cosh u)(arm_a(x) - x)
205
+
206
+ mode 'growth' lets the mean gain rise toward the cap; 'conserved'
207
+ renormalizes alpha to mean 1 over members at every byte. `coherent`
208
+ reads alpha once at the first site and shares it ('alpha'), and with
209
+ 'full' shares the first site's signed routing weight too."""
210
+
211
+ def configure(self, *, mode="growth", rho=1 / 16, gamma=1.5, a_min=0.5,
212
+ a_max=2.0, coherent=None, role="ref", holder=None):
213
+ self.mode, self.rho, self.gamma = mode, float(rho), float(gamma)
214
+ self.a_min, self.a_max = float(a_min), float(a_max)
215
+ self.coherent, self.role = coherent, role
216
+ self.holder = {} if holder is None else holder
217
+ self._state, self._call = None, "forward"
218
+ return self
219
+
220
+ def reset_state(self):
221
+ self._state = None
222
+
223
+ def _alpha(self, ep, en, z):
224
+ with torch.no_grad():
225
+ s = ((ep + en) / z).float()
226
+ B, T, A = s.shape
227
+ st = (self._state if (self._call == "step" and self._state is not None
228
+ and self._state.shape[0] == B) else None)
229
+ if st is None:
230
+ st = torch.full((B, A), 1.0 / A, device=s.device, dtype=torch.float32)
231
+ if T == 1:
232
+ st = st + self.rho * (s[:, 0] - st)
233
+ ubar = st.unsqueeze(1)
234
+ elif T <= 4200:
235
+ q = 1.0 - self.rho
236
+ t = torch.arange(1, T + 1, device=s.device, dtype=torch.float64)
237
+ acc = torch.cumsum((q ** -t).view(1, T, 1) * s.double(), dim=1)
238
+ ubar = ((q ** t).view(1, T, 1)
239
+ * (st.double().unsqueeze(1) + self.rho * acc)).float()
240
+ st = ubar[:, -1]
241
+ else:
242
+ ubar = torch.empty_like(s)
243
+ for i in range(T):
244
+ st = st + self.rho * (s[:, i] - st)
245
+ ubar[:, i] = st
246
+ self._state = st.clone() if self._call in ("prefill", "step") else None
247
+ alpha = (1.0 + self.gamma * (A * ubar - 1.0)).clamp(self.a_min, self.a_max)
248
+ if self.mode == "conserved":
249
+ alpha = alpha * (A / alpha.sum(dim=-1, keepdim=True))
250
+ return alpha.to(z.dtype)
251
+
252
+ def forward(self, x):
253
+ shared_route = self.coherent == "full" and self.role != "ref"
254
+ if not shared_route:
255
+ emb = F.normalize(x @ self.key_proj.T, dim=-1)
256
+ C = F.normalize(self.dispatch, dim=-1)
257
+ u = (emb @ C.T) / self.tau
258
+ m = u.abs().amax(dim=-1, keepdim=True)
259
+ ep, en = torch.exp(u - m), torch.exp(-u - m)
260
+ z = (ep + en).sum(dim=-1, keepdim=True)
261
+ g = (ep - en) / z
262
+ if self.coherent and self.role != "ref":
263
+ alpha = self.holder["alpha"]
264
+ if shared_route:
265
+ g = self.holder["g"]
266
+ else:
267
+ alpha = self._alpha(ep, en, z)
268
+ if self.coherent:
269
+ self.holder["alpha"], self.holder["g"] = alpha, g
270
+ out = x
271
+ for k, a in enumerate(self.anchors):
272
+ if self.enabled[k]:
273
+ out = out + (g[..., k:k + 1] * alpha[..., k:k + 1]) * (a(x) - x)
274
+ return out
275
+
276
+
277
+ class BlockWithDispatch(nn.Module):
278
+ def __init__(self, block: nn.Module, disp: AnchorDispatch):
279
+ super().__init__()
280
+ self.block = block
281
+ self.disp = disp
282
+
283
+ def forward(self, *args, **kwargs):
284
+ out = self.block(*args, **kwargs)
285
+ if isinstance(out, tuple):
286
+ return (self.disp(out[0]),) + out[1:]
287
+ return self.disp(out)
288
+
289
+ def prefill(self, *args, **kwargs):
290
+ out, cache = self.block.prefill(*args, **kwargs)
291
+ self.disp._call = getattr(self.disp, "_call", "forward")
292
+ if isinstance(self.disp, GainDispatch):
293
+ self.disp._call = "prefill"
294
+ try:
295
+ return self.disp(out), cache
296
+ finally:
297
+ self.disp._call = "forward"
298
+ return self.disp(out), cache
299
+
300
+ def step(self, *args, **kwargs):
301
+ out = self.block.step(*args, **kwargs)
302
+ if isinstance(self.disp, GainDispatch):
303
+ self.disp._call = "step"
304
+ try:
305
+ return self.disp(out[0]) if isinstance(out, tuple) else self.disp(out)
306
+ finally:
307
+ self.disp._call = "forward"
308
+ if isinstance(out, tuple):
309
+ return (self.disp(out[0]),) + out[1:]
310
+ return self.disp(out)
311
+
312
+
313
+ # --------------------------------------------------------------- handles
314
+ def _probe(aleph) -> torch.Tensor:
315
+ """Deterministic logits fingerprint on a tiny fixed input."""
316
+ dev = next(aleph.parameters()).device
317
+ ids = torch.arange(8, dtype=torch.long, device=dev).unsqueeze(0) % 7 + 1
318
+ was_training = aleph.training
319
+ aleph.eval()
320
+ with torch.no_grad():
321
+ out = aleph(ids)
322
+ if was_training:
323
+ aleph.train()
324
+ logits = out[0] if isinstance(out, (tuple, list)) else out.logits
325
+ return logits.detach().float().cpu()
326
+
327
+
328
+ class ArmHandle:
329
+ """One mounted recipe. `detach(verify=True)` restores the core and
330
+ proves the restoration is bit-exact."""
331
+
332
+ def __init__(self, aleph, original, names, wrapped, fingerprint, card=None):
333
+ self.aleph = aleph
334
+ self._original = original
335
+ self.names = list(names)
336
+ self._blocks = wrapped
337
+ self._fingerprint = fingerprint
338
+ self.card = card or {}
339
+
340
+ # -- masking (the toggle law) --------------------------------------
341
+ def set_mask(self, mask: dict) -> None:
342
+ enabled = [bool(mask.get(n, True)) for n in self.names]
343
+ for b in self._blocks:
344
+ if isinstance(b, BlockWithDispatch):
345
+ b.disp.enabled = list(enabled)
346
+ else:
347
+ b.enabled = enabled[0]
348
+
349
+ def enable(self, *names):
350
+ self.set_mask({n: (n in names or not names) for n in self.names})
351
+
352
+ def disable(self, *names):
353
+ self.set_mask({n: n not in names for n in self.names})
354
+
355
+ def _snapshot(self):
356
+ return [list(b.disp.enabled) if isinstance(b, BlockWithDispatch) else b.enabled
357
+ for b in self._blocks]
358
+
359
+ def _restore(self, snap):
360
+ for b, s in zip(self._blocks, snap):
361
+ if isinstance(b, BlockWithDispatch):
362
+ b.disp.enabled = list(s)
363
+ else:
364
+ b.enabled = s
365
+
366
+ @contextlib.contextmanager
367
+ def only(self, *names):
368
+ prev = self._snapshot()
369
+ self.set_mask({n: n in names for n in self.names})
370
+ try:
371
+ yield self
372
+ finally:
373
+ self._restore(prev)
374
+
375
+ @contextlib.contextmanager
376
+ def all_off(self):
377
+ prev = self._snapshot()
378
+ self.set_mask({n: False for n in self.names})
379
+ try:
380
+ yield self
381
+ finally:
382
+ self._restore(prev)
383
+
384
+ # -- decode state ---------------------------------------------------
385
+ def reset_state(self):
386
+ """Forget the carried gain state (a fresh context starts over)."""
387
+ for b in self._blocks:
388
+ if isinstance(b, BlockWithDispatch):
389
+ getattr(b.disp, "reset_state", lambda: None)()
390
+
391
+ # -- detach ---------------------------------------------------------
392
+ def detach(self, *, verify: bool = True):
393
+ self.aleph.blocks = self._original
394
+ if verify:
395
+ post = _probe(self.aleph)
396
+ if not torch.equal(post, self._fingerprint):
397
+ raise RuntimeError(
398
+ "detach verification FAILED: the restored core's logits "
399
+ "are not bit-exact with the pre-mount fingerprint")
400
+ return self.aleph
401
+
402
+
403
+ class StackHandle:
404
+ """Nested always-on handles, detached outermost first."""
405
+
406
+ def __init__(self, handles, names=None, card=None):
407
+ self.handles = list(handles)
408
+ self.names = list(names or [h.names[0] for h in self.handles])
409
+ self.card = card or {}
410
+
411
+ def reset_state(self):
412
+ for h in self.handles:
413
+ h.reset_state()
414
+
415
+ def set_mask(self, mask: dict) -> None:
416
+ for h, n in zip(self.handles, self.names):
417
+ h.set_mask({h.names[0]: bool(mask.get(n, True))})
418
+
419
+ @contextlib.contextmanager
420
+ def all_off(self):
421
+ prev = [h._snapshot() for h in self.handles]
422
+ for h in self.handles:
423
+ h.set_mask({h.names[0]: False})
424
+ try:
425
+ yield self
426
+ finally:
427
+ for h, p in zip(self.handles, prev):
428
+ h._restore(p)
429
+
430
+ def detach(self, *, verify: bool = True):
431
+ for h in reversed(self.handles):
432
+ h.detach(verify=verify)
433
+ return self.handles[0].aleph if self.handles else None
434
+
435
+
436
+ # --------------------------------------------------------------- loading
437
+ def load_arm_file(path: str):
438
+ """-> (tensors, meta). Reads the packaged safetensors arm; also accepts
439
+ an amoe .pt anchor/dispatch checkpoint for local experiments."""
440
+ if str(path).endswith(".safetensors"):
441
+ from safetensors.torch import load_file
442
+ from safetensors import safe_open
443
+ with safe_open(path, framework="pt") as f:
444
+ meta = dict(f.metadata() or {})
445
+ card = json.loads(meta.get("arm", "{}"))
446
+ return load_file(path), card
447
+ blob = torch.load(path, map_location="cpu", weights_only=True)
448
+ if "adapters" in blob:
449
+ return blob["adapters"], dict(blob.get("meta") or {})
450
+ if "dispatch" in blob: # flatten to "<block>.<key>"
451
+ flat = {f"{i}.{k}": v for i, st in enumerate(blob["dispatch"])
452
+ for k, v in st.items()}
453
+ return flat, dict(blob.get("meta") or {})
454
+ raise ValueError(f"unrecognized arm file: {path}")
455
+
456
+
457
+ def _per_block(state: dict, i: int) -> dict:
458
+ pre = f"{i}."
459
+ return {k[len(pre):]: v for k, v in state.items() if k.startswith(pre)}
460
+
461
+
462
+ def _target_blocks(aleph):
463
+ return list(aleph.blocks)
464
+
465
+
466
+ # --------------------------------------------------------------- mounting
467
+ def mount_anchor(aleph, state: dict, name: str = "arm", card=None) -> ArmHandle:
468
+ layers = _target_blocks(aleph)
469
+ d = int(aleph.cfg.d_model)
470
+ fingerprint = _probe(aleph)
471
+ spec = spec_from_state(state)
472
+ blocks, wrapped = [], []
473
+ for i, layer in enumerate(layers):
474
+ st = _per_block(state, i)
475
+ if not st:
476
+ blocks.append(layer)
477
+ continue
478
+ p = next(layer.parameters())
479
+ a = RelayPatchwork(d, spec)
480
+ a.load_state_dict(st, strict=True)
481
+ a.to(device=p.device, dtype=p.dtype)
482
+ w = BlockWithAdapter(layer, a)
483
+ blocks.append(w)
484
+ wrapped.append(w)
485
+ original = aleph.blocks
486
+ aleph.blocks = nn.ModuleList(blocks)
487
+ return ArmHandle(aleph, original, [name], wrapped, fingerprint, card)
488
+
489
+
490
+ def mount_routed(aleph, state: dict, names, gain=None, mode="growth",
491
+ coherent=None, card=None) -> ArmHandle:
492
+ layers = _target_blocks(aleph)
493
+ d = int(aleph.cfg.d_model)
494
+ fingerprint = _probe(aleph)
495
+ A = len(names)
496
+ member0 = {k[len("anchors.0."):]: v for k, v in _per_block(state, 0).items()
497
+ if k.startswith("anchors.0.")}
498
+ spec = spec_from_state(member0, prefix="")
499
+ holder, blocks, wrapped = {}, [], []
500
+ for i, layer in enumerate(layers):
501
+ st = _per_block(state, i)
502
+ p = next(layer.parameters())
503
+ members = nn.ModuleList([RelayPatchwork(d, spec) for _ in range(A)])
504
+ emb = int(st["key_proj"].shape[0])
505
+ dp = AnchorDispatch(members, d, emb=emb)
506
+ dp.load_state_dict(st, strict=True)
507
+ dp.to(device=p.device, dtype=p.dtype)
508
+ if gain:
509
+ dp.__class__ = GainDispatch
510
+ dp.configure(mode=mode, rho=gain["rho"], gamma=gain["gamma"],
511
+ a_min=gain["a_min"], a_max=gain["a_max"],
512
+ coherent=coherent, role="ref" if i == 0 else "follow",
513
+ holder=holder)
514
+ w = BlockWithDispatch(layer, dp)
515
+ blocks.append(w)
516
+ wrapped.append(w)
517
+ original = aleph.blocks
518
+ aleph.blocks = nn.ModuleList(blocks)
519
+ return ArmHandle(aleph, original, list(names), wrapped, fingerprint, card)
520
+
521
+
522
+ def mount(aleph, card: dict, files) -> "ArmHandle | StackHandle":
523
+ """Attach one recipe. `card` is an arms/index.json row; `files` maps the
524
+ row's weight keys to local paths (a stack passes one file per member,
525
+ inner first)."""
526
+ kind = (card.get("mount") or {}).get("type", "anchor")
527
+ if kind == "anchor":
528
+ state, _ = load_arm_file(files[0])
529
+ return mount_anchor(aleph, state, card.get("name", "arm"), card)
530
+ if kind == "stack":
531
+ members = (card.get("mount") or {}).get("members") or []
532
+ handles = []
533
+ try:
534
+ for f, n in zip(files, members or [f"member{i}" for i in range(len(files))]):
535
+ state, _ = load_arm_file(f)
536
+ handles.append(mount_anchor(aleph, state, n, card))
537
+ except Exception:
538
+ StackHandle(handles).detach(verify=False) # never leave half a stack on
539
+ raise
540
+ return StackHandle(handles, members, card)
541
+ if kind == "routed":
542
+ state, _ = load_arm_file(files[0])
543
+ m = card.get("mount") or {}
544
+ return mount_routed(aleph, state, m.get("members") or [],
545
+ gain=m.get("gain"), mode=m.get("gain_mode", "growth"),
546
+ coherent=m.get("coherent"), card=card)
547
+ raise ValueError(f"unknown mount type {kind!r} (packaged types: {ARM_TYPES})")
548
+
549
+
550
+ # --------------------------------------------------------------- frames
551
+ CHAT_HEADER = ("A conversation with Beatrix, a small byte-level "
552
+ "language model still in pretraining.\n")
553
+ USER_TAG = "User: "
554
+ ASSISTANT_TAG = "Beatrix:"
555
+ TEXT_STOPS = ("\nUser:", "\nBeatrix:", "User:", "Beatrix:", "\n\n")
556
+ SPECIALS = {"DOC": 0xFF, "USER": 0xFE, "MODEL": 0xFD, "END": 0xFC, "SYS": 0xFB}
557
+
558
+
559
+ def render_text_frame(prompt: str, history=None) -> bytes:
560
+ """A plain text chat frame: header + 'User: ...\\n' + 'Beatrix:' — the
561
+ reply follows the tag. The stage arms of this package were trained on
562
+ raw stage text, not in this frame; it is kept for arms that are."""
563
+ out = CHAT_HEADER
564
+ for turn in (history or []):
565
+ tag = USER_TAG if turn.get("role") == "user" else ASSISTANT_TAG + " "
566
+ out += f"{tag}{turn.get('content', '')}\n"
567
+ out += f"{USER_TAG}{prompt}\n{ASSISTANT_TAG}"
568
+ return out.encode("utf-8")
569
+
570
+
571
+ def render_specials_frame(prompt: str, system: str | None = None,
572
+ history=None) -> bytes:
573
+ """The core's own annealed frame: [SYS] .. [END] [USER] .. [END] [MODEL].
574
+ Encoded text can never contain a special, so the frame is unforgeable."""
575
+ S = SPECIALS
576
+ ids = []
577
+ if system:
578
+ ids += [S["SYS"]] + list(system.encode("utf-8")) + [S["END"]]
579
+ for turn in (history or []):
580
+ tag = S["USER"] if turn.get("role") == "user" else S["MODEL"]
581
+ ids += [tag] + list(str(turn.get("content", "")).encode("utf-8")) + [S["END"]]
582
+ ids += [S["USER"]] + list(prompt.encode("utf-8")) + [S["END"], S["MODEL"]]
583
+ return bytes(ids)
584
+
585
+
586
+ def cut_text_reply(raw: bytes) -> bytes:
587
+ """Cut a text-frame continuation at the first stop string."""
588
+ best = len(raw)
589
+ for s in TEXT_STOPS:
590
+ j = raw.find(s.encode("utf-8"))
591
+ if j != -1:
592
+ best = min(best, j)
593
+ return raw[:best]
594
+
595
+
596
+ def cut_specials_reply(raw: bytes) -> bytes:
597
+ j = raw.find(bytes([SPECIALS["END"]]))
598
+ return raw if j == -1 else raw[:j]
arms/index.json ADDED
@@ -0,0 +1,854 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "craft": "mini-beatrix-3",
3
+ "core": {
4
+ "craft": "mini-beatrix-3",
5
+ "step": 245674,
6
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
7
+ "note": "every packaged arm was trained on this exact frozen core (the default bf16 weight file); arms are trunk-bound"
8
+ },
9
+ "generated": "2026-10-06 00:09 UTC",
10
+ "note": "the stage arms of curriculum stages 1 to 4, fitted on the finished, frozen core in two forms: as a group that is on together, and each alone; the arms trained beside the trunk during the run are on the training repo",
11
+ "arms": [
12
+ {
13
+ "id": "stages-1-4/s1_perspective",
14
+ "name": "stages-1-4/s1_perspective",
15
+ "group": "stages-1-4",
16
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
17
+ "step": 245674,
18
+ "params": 13692960,
19
+ "sites": 32,
20
+ "weights": [
21
+ "arms/stages-1-4/s1_perspective.safetensors"
22
+ ],
23
+ "mount": {
24
+ "type": "anchor"
25
+ },
26
+ "precision": "fp32",
27
+ "template": {
28
+ "frame": "raw",
29
+ "template": "raw stage text, continued"
30
+ },
31
+ "decode": {
32
+ "temperature": 0.0,
33
+ "top_p": 1.0,
34
+ "max_new": 120,
35
+ "precision": "fp32"
36
+ },
37
+ "source": {
38
+ "file": "gRA_close4_s1_perspective.safetensors",
39
+ "sha256": "ac7dcb7c175c0b17260e2826b5a4a5e911c8d078d1bbc5602996757820c9b2ff"
40
+ },
41
+ "recipe": "trained as an always-on group on the frozen final core, in two steps. First the arms of stages 1 and 2: a run that attached one stage's arm at a time, each new arm over the earlier ones, every attached arm live and trainable under one pure Adam (lr 0.001), 800 steps per stage across the text of stages 1 to 4; the arms that run attached for stages 3 and 4 stayed nearly empty and were dropped. Then, with the arms of stages 1 and 2 held fixed and on, a new arm for stage 3 was attached and trained for 800 steps, and after it a new arm for stage 4 the same way. Each step gives a training arm 2 x 4096 bytes of its own stage's text with every attached arm on, and a quiet term at weight 2 on 2 rows that are not its text (one of web text, one of another member's stage text): the KL from the model with that arm switched off to the model with every arm on",
42
+ "title": "Stage 1: perspective",
43
+ "behavior": "one small event retold from each side: seen from a person, said to them, told about them",
44
+ "family": "stage",
45
+ "status": "a member of the group stages-1-4; trained always on with its partners, so alone is not its trained state",
46
+ "score": "alone: own stage text 0.554 -> 0.052 bpb; web text +0.0014 bpb",
47
+ "measured": {
48
+ "own_stage_bpb_arms_off": 0.5538,
49
+ "own_stage_bpb_group_on": 0.0482,
50
+ "own_stage_bpb_this_arm_alone": 0.0525,
51
+ "share_of_the_group_on_own_stage": -0.4767,
52
+ "share_of_the_group_on_own_stage_se": 0.0046,
53
+ "share_of_the_group_on_web_text": 0.00035,
54
+ "alone_on_web_text": 0.00143,
55
+ "share_of_the_group_on_partner_stages": {
56
+ "s2_concept": -0.2405,
57
+ "s3_rules": -0.1848,
58
+ "s4_arith": -0.1575
59
+ },
60
+ "alone_on_partner_stages": {
61
+ "s2_concept": -0.4817,
62
+ "s3_rules": -0.4209,
63
+ "s4_arith": -0.5113
64
+ },
65
+ "steps_trained": 3200,
66
+ "stage_items_own_form_accuracy": {
67
+ "arms_off": 0.55,
68
+ "group_on": 0.963,
69
+ "this_arm_alone": 0.838
70
+ },
71
+ "stage_items_new_form_accuracy": {
72
+ "arms_off": 0.463,
73
+ "group_on": 0.475,
74
+ "this_arm_alone": 0.463
75
+ },
76
+ "second_seed": {
77
+ "own_stage_bpb_arms_off": 0.5538,
78
+ "own_stage_bpb_group_on": 0.0479,
79
+ "own_stage_bpb_this_arm_alone": 0.0522,
80
+ "share_of_the_group_on_own_stage": -0.4733,
81
+ "share_of_the_group_on_own_stage_se": 0.0047,
82
+ "share_of_the_group_on_web_text": 4e-05,
83
+ "alone_on_web_text": 0.00134,
84
+ "share_of_the_group_on_partner_stages": {
85
+ "s2_concept": -0.238,
86
+ "s3_rules": -0.1951,
87
+ "s4_arith": -0.1591
88
+ },
89
+ "alone_on_partner_stages": {
90
+ "s2_concept": -0.4772,
91
+ "s3_rules": -0.4262,
92
+ "s4_arith": -0.4807
93
+ },
94
+ "steps_trained": 3200,
95
+ "stage_items_own_form_accuracy": {
96
+ "arms_off": 0.55,
97
+ "group_on": 1.0,
98
+ "this_arm_alone": 0.925
99
+ },
100
+ "stage_items_new_form_accuracy": {
101
+ "arms_off": 0.463,
102
+ "group_on": 0.463,
103
+ "this_arm_alone": 0.463
104
+ }
105
+ }
106
+ },
107
+ "examples": [
108
+ "Rui dropped the acorn near the box. Seen from Rui: I dropped the acorn near the box. Said to Rui: you dropped the acorn near the box. Told about Rui: Rui dropped the acorn near the box. Three ways of saying, one thing that happened."
109
+ ]
110
+ },
111
+ {
112
+ "id": "stages-1-4/s2_concept",
113
+ "name": "stages-1-4/s2_concept",
114
+ "group": "stages-1-4",
115
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
116
+ "step": 245674,
117
+ "params": 13692960,
118
+ "sites": 32,
119
+ "weights": [
120
+ "arms/stages-1-4/s2_concept.safetensors"
121
+ ],
122
+ "mount": {
123
+ "type": "anchor"
124
+ },
125
+ "precision": "fp32",
126
+ "template": {
127
+ "frame": "raw",
128
+ "template": "raw stage text, continued"
129
+ },
130
+ "decode": {
131
+ "temperature": 0.0,
132
+ "top_p": 1.0,
133
+ "max_new": 120,
134
+ "precision": "fp32"
135
+ },
136
+ "source": {
137
+ "file": "gRA_close4_s2_concept.safetensors",
138
+ "sha256": "15444e336608c82837c366adc813211d401777aff8ad22ae7b6956bf2c395331"
139
+ },
140
+ "recipe": "trained as an always-on group on the frozen final core, in two steps. First the arms of stages 1 and 2: a run that attached one stage's arm at a time, each new arm over the earlier ones, every attached arm live and trainable under one pure Adam (lr 0.001), 800 steps per stage across the text of stages 1 to 4; the arms that run attached for stages 3 and 4 stayed nearly empty and were dropped. Then, with the arms of stages 1 and 2 held fixed and on, a new arm for stage 3 was attached and trained for 800 steps, and after it a new arm for stage 4 the same way. Each step gives a training arm 2 x 4096 bytes of its own stage's text with every attached arm on, and a quiet term at weight 2 on 2 rows that are not its text (one of web text, one of another member's stage text): the KL from the model with that arm switched off to the model with every arm on",
141
+ "title": "Stage 2: concepts",
142
+ "behavior": "kinds, properties and differences: what a thing is a kind of, what its kind can do, how two things differ",
143
+ "family": "stage",
144
+ "status": "a member of the group stages-1-4; trained always on with its partners, so alone is not its trained state",
145
+ "score": "alone: own stage text 0.721 -> 0.350 bpb; web text +0.0010 bpb",
146
+ "measured": {
147
+ "own_stage_bpb_arms_off": 0.7212,
148
+ "own_stage_bpb_group_on": 0.1123,
149
+ "own_stage_bpb_this_arm_alone": 0.3503,
150
+ "share_of_the_group_on_own_stage": -0.1283,
151
+ "share_of_the_group_on_own_stage_se": 0.0014,
152
+ "share_of_the_group_on_web_text": -0.0001,
153
+ "alone_on_web_text": 0.00105,
154
+ "share_of_the_group_on_partner_stages": {
155
+ "s1_perspective": -0.0043,
156
+ "s3_rules": -0.1064,
157
+ "s4_arith": -0.1204
158
+ },
159
+ "alone_on_partner_stages": {
160
+ "s1_perspective": -0.0273,
161
+ "s3_rules": -0.3108,
162
+ "s4_arith": -0.3537
163
+ },
164
+ "steps_trained": 2400,
165
+ "stage_items_own_form_accuracy": {
166
+ "arms_off": 0.298,
167
+ "group_on": 0.976,
168
+ "this_arm_alone": 0.964
169
+ },
170
+ "stage_items_new_form_accuracy": {
171
+ "arms_off": 0.678,
172
+ "group_on": 0.78,
173
+ "this_arm_alone": 0.72
174
+ },
175
+ "second_seed": {
176
+ "own_stage_bpb_arms_off": 0.7212,
177
+ "own_stage_bpb_group_on": 0.1212,
178
+ "own_stage_bpb_this_arm_alone": 0.3603,
179
+ "share_of_the_group_on_own_stage": -0.1212,
180
+ "share_of_the_group_on_own_stage_se": 0.0014,
181
+ "share_of_the_group_on_web_text": -0.00055,
182
+ "alone_on_web_text": 0.00086,
183
+ "share_of_the_group_on_partner_stages": {
184
+ "s1_perspective": -0.0041,
185
+ "s3_rules": -0.1005,
186
+ "s4_arith": -0.1138
187
+ },
188
+ "alone_on_partner_stages": {
189
+ "s1_perspective": -0.03,
190
+ "s3_rules": -0.3138,
191
+ "s4_arith": -0.3577
192
+ },
193
+ "steps_trained": 2400,
194
+ "stage_items_own_form_accuracy": {
195
+ "arms_off": 0.298,
196
+ "group_on": 0.952,
197
+ "this_arm_alone": 0.905
198
+ },
199
+ "stage_items_new_form_accuracy": {
200
+ "arms_off": 0.678,
201
+ "group_on": 0.754,
202
+ "this_arm_alone": 0.703
203
+ }
204
+ }
205
+ },
206
+ "examples": [
207
+ "A whale is a kind of mammal. Most mammals can swim. A whale is not a beetle; they differ in kind."
208
+ ]
209
+ },
210
+ {
211
+ "id": "stages-1-4/s3_rules",
212
+ "name": "stages-1-4/s3_rules",
213
+ "group": "stages-1-4",
214
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
215
+ "step": 245674,
216
+ "params": 13692960,
217
+ "sites": 32,
218
+ "weights": [
219
+ "arms/stages-1-4/s3_rules.safetensors"
220
+ ],
221
+ "mount": {
222
+ "type": "anchor"
223
+ },
224
+ "precision": "fp32",
225
+ "template": {
226
+ "frame": "raw",
227
+ "template": "raw stage text, continued"
228
+ },
229
+ "decode": {
230
+ "temperature": 0.0,
231
+ "top_p": 1.0,
232
+ "max_new": 120,
233
+ "precision": "fp32"
234
+ },
235
+ "source": {
236
+ "file": "gRA_close4_s3_rules.safetensors",
237
+ "sha256": "ab53da93e6550cc450b29c93dd09ff94f12f8f9a60dafd167507bb4c5503166b"
238
+ },
239
+ "recipe": "trained as an always-on group on the frozen final core, in two steps. First the arms of stages 1 and 2: a run that attached one stage's arm at a time, each new arm over the earlier ones, every attached arm live and trainable under one pure Adam (lr 0.001), 800 steps per stage across the text of stages 1 to 4; the arms that run attached for stages 3 and 4 stayed nearly empty and were dropped. Then, with the arms of stages 1 and 2 held fixed and on, a new arm for stage 3 was attached and trained for 800 steps, and after it a new arm for stage 4 the same way. Each step gives a training arm 2 x 4096 bytes of its own stage's text with every attached arm on, and a quiet term at weight 2 on 2 rows that are not its text (one of web text, one of another member's stage text): the KL from the model with that arm switched off to the model with every arm on",
240
+ "title": "Stage 3: rule chains",
241
+ "behavior": "if-then rules over made-up words, followed step by step to what follows",
242
+ "family": "stage",
243
+ "status": "a member of the group stages-1-4; trained always on with its partners, so alone is not its trained state",
244
+ "score": "alone: own stage text 0.930 -> 0.824 bpb; web text +0.0005 bpb",
245
+ "measured": {
246
+ "own_stage_bpb_arms_off": 0.9296,
247
+ "own_stage_bpb_group_on": 0.2236,
248
+ "own_stage_bpb_this_arm_alone": 0.8236,
249
+ "share_of_the_group_on_own_stage": -0.0952,
250
+ "share_of_the_group_on_own_stage_se": 0.0009,
251
+ "share_of_the_group_on_web_text": 0.0004,
252
+ "alone_on_web_text": 0.00053,
253
+ "share_of_the_group_on_partner_stages": {
254
+ "s1_perspective": 0.0001,
255
+ "s2_concept": 0.0007,
256
+ "s4_arith": 0.0
257
+ },
258
+ "alone_on_partner_stages": {
259
+ "s1_perspective": -0.0003,
260
+ "s2_concept": 0.0031,
261
+ "s4_arith": 0.0001
262
+ },
263
+ "steps_trained": 800,
264
+ "stage_items_own_form_accuracy": {
265
+ "arms_off": 0.155,
266
+ "group_on": 0.905,
267
+ "this_arm_alone": 0.32
268
+ },
269
+ "stage_items_new_form_accuracy": {
270
+ "arms_off": 0.07,
271
+ "group_on": 0.3,
272
+ "this_arm_alone": 0.08
273
+ },
274
+ "second_seed": {
275
+ "own_stage_bpb_arms_off": 0.9296,
276
+ "own_stage_bpb_group_on": 0.2222,
277
+ "own_stage_bpb_this_arm_alone": 0.8702,
278
+ "share_of_the_group_on_own_stage": -0.0958,
279
+ "share_of_the_group_on_own_stage_se": 0.0013,
280
+ "share_of_the_group_on_web_text": 0.00038,
281
+ "alone_on_web_text": 0.00034,
282
+ "share_of_the_group_on_partner_stages": {
283
+ "s1_perspective": 0.0001,
284
+ "s2_concept": -0.0006,
285
+ "s4_arith": -0.0003
286
+ },
287
+ "alone_on_partner_stages": {
288
+ "s1_perspective": -0.0003,
289
+ "s2_concept": 0.0016,
290
+ "s4_arith": -0.0047
291
+ },
292
+ "steps_trained": 800,
293
+ "stage_items_own_form_accuracy": {
294
+ "arms_off": 0.155,
295
+ "group_on": 0.885,
296
+ "this_arm_alone": 0.335
297
+ },
298
+ "stage_items_new_form_accuracy": {
299
+ "arms_off": 0.07,
300
+ "group_on": 0.33,
301
+ "this_arm_alone": 0.1
302
+ }
303
+ }
304
+ },
305
+ "examples": [
306
+ "If someone is hoream, then they are hortil. If someone is hortil, then they are babig. If someone is babig, then they are banng. Vin is hoream. So Vin is hortil. So Vin is babig. So Vin is banng."
307
+ ]
308
+ },
309
+ {
310
+ "id": "stages-1-4/s4_arith",
311
+ "name": "stages-1-4/s4_arith",
312
+ "group": "stages-1-4",
313
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
314
+ "step": 245674,
315
+ "params": 13692960,
316
+ "sites": 32,
317
+ "weights": [
318
+ "arms/stages-1-4/s4_arith.safetensors"
319
+ ],
320
+ "mount": {
321
+ "type": "anchor"
322
+ },
323
+ "precision": "fp32",
324
+ "template": {
325
+ "frame": "raw",
326
+ "template": "raw stage text, continued"
327
+ },
328
+ "decode": {
329
+ "temperature": 0.0,
330
+ "top_p": 1.0,
331
+ "max_new": 120,
332
+ "precision": "fp32"
333
+ },
334
+ "source": {
335
+ "file": "gRA_close4_s4_arith.safetensors",
336
+ "sha256": "c721c34516322ee900f7c49a0de295512113ed49787448b760e52736df049abb"
337
+ },
338
+ "recipe": "trained as an always-on group on the frozen final core, in two steps. First the arms of stages 1 and 2: a run that attached one stage's arm at a time, each new arm over the earlier ones, every attached arm live and trainable under one pure Adam (lr 0.001), 800 steps per stage across the text of stages 1 to 4; the arms that run attached for stages 3 and 4 stayed nearly empty and were dropped. Then, with the arms of stages 1 and 2 held fixed and on, a new arm for stage 3 was attached and trained for 800 steps, and after it a new arm for stage 4 the same way. Each step gives a training arm 2 x 4096 bytes of its own stage's text with every attached arm on, and a quiet term at weight 2 on 2 rows that are not its text (one of web text, one of another member's stage text): the KL from the model with that arm switched off to the model with every arm on",
339
+ "title": "Stage 4: arithmetic",
340
+ "behavior": "small arithmetic worked out in text",
341
+ "family": "stage",
342
+ "status": "a member of the group stages-1-4; trained always on with its partners, so alone is not its trained state",
343
+ "score": "alone: own stage text 1.234 -> 1.127 bpb; web text +0.0001 bpb",
344
+ "measured": {
345
+ "own_stage_bpb_arms_off": 1.2339,
346
+ "own_stage_bpb_group_on": 0.3384,
347
+ "own_stage_bpb_this_arm_alone": 1.1267,
348
+ "share_of_the_group_on_own_stage": -0.1477,
349
+ "share_of_the_group_on_own_stage_se": 0.0022,
350
+ "share_of_the_group_on_web_text": 0.0001,
351
+ "alone_on_web_text": 0.00011,
352
+ "share_of_the_group_on_partner_stages": {
353
+ "s1_perspective": -0.0001,
354
+ "s2_concept": -0.0006,
355
+ "s3_rules": 0.0
356
+ },
357
+ "alone_on_partner_stages": {
358
+ "s1_perspective": -0.0009,
359
+ "s2_concept": -0.0004,
360
+ "s3_rules": -0.0016
361
+ },
362
+ "steps_trained": 800,
363
+ "stage_items_own_form_accuracy": {
364
+ "arms_off": 0.344,
365
+ "group_on": 0.613,
366
+ "this_arm_alone": 0.381
367
+ },
368
+ "stage_items_new_form_accuracy": {
369
+ "arms_off": 0.242,
370
+ "group_on": 0.237,
371
+ "this_arm_alone": 0.246
372
+ },
373
+ "second_seed": {
374
+ "own_stage_bpb_arms_off": 1.2339,
375
+ "own_stage_bpb_group_on": 0.3379,
376
+ "own_stage_bpb_this_arm_alone": 1.155,
377
+ "share_of_the_group_on_own_stage": -0.1737,
378
+ "share_of_the_group_on_own_stage_se": 0.002,
379
+ "share_of_the_group_on_web_text": -1e-05,
380
+ "alone_on_web_text": 6e-05,
381
+ "share_of_the_group_on_partner_stages": {
382
+ "s1_perspective": 0.0,
383
+ "s2_concept": -0.0,
384
+ "s3_rules": 0.0
385
+ },
386
+ "alone_on_partner_stages": {
387
+ "s1_perspective": -0.0013,
388
+ "s2_concept": -0.0015,
389
+ "s3_rules": 0.0005
390
+ },
391
+ "steps_trained": 800,
392
+ "stage_items_own_form_accuracy": {
393
+ "arms_off": 0.344,
394
+ "group_on": 0.594,
395
+ "this_arm_alone": 0.412
396
+ },
397
+ "stage_items_new_form_accuracy": {
398
+ "arms_off": 0.242,
399
+ "group_on": 0.271,
400
+ "this_arm_alone": 0.237
401
+ }
402
+ }
403
+ },
404
+ "examples": [
405
+ "Count by 3s: 1 3s make 3; 2 3s make 6; 3 3s make 9; 4 3s make 12; 5 3s make 15. So 3 times 5 is 15."
406
+ ]
407
+ },
408
+ {
409
+ "id": "stages-1-4",
410
+ "name": "stages-1-4",
411
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
412
+ "step": 245674,
413
+ "params": 54771840,
414
+ "sites": 32,
415
+ "weights": [
416
+ "arms/stages-1-4/s1_perspective.safetensors",
417
+ "arms/stages-1-4/s2_concept.safetensors",
418
+ "arms/stages-1-4/s3_rules.safetensors",
419
+ "arms/stages-1-4/s4_arith.safetensors"
420
+ ],
421
+ "mount": {
422
+ "type": "stack",
423
+ "members": [
424
+ "stages-1-4/s1_perspective",
425
+ "stages-1-4/s2_concept",
426
+ "stages-1-4/s3_rules",
427
+ "stages-1-4/s4_arith"
428
+ ]
429
+ },
430
+ "precision": "fp32",
431
+ "template": {
432
+ "frame": "raw",
433
+ "template": "raw text, continued"
434
+ },
435
+ "decode": {
436
+ "temperature": 0.0,
437
+ "top_p": 1.0,
438
+ "max_new": 120,
439
+ "precision": "fp32"
440
+ },
441
+ "recipe": "trained as an always-on group on the frozen final core, in two steps. First the arms of stages 1 and 2: a run that attached one stage's arm at a time, each new arm over the earlier ones, every attached arm live and trainable under one pure Adam (lr 0.001), 800 steps per stage across the text of stages 1 to 4; the arms that run attached for stages 3 and 4 stayed nearly empty and were dropped. Then, with the arms of stages 1 and 2 held fixed and on, a new arm for stage 3 was attached and trained for 800 steps, and after it a new arm for stage 4 the same way. Each step gives a training arm 2 x 4096 bytes of its own stage's text with every attached arm on, and a quiet term at weight 2 on 2 rows that are not its text (one of web text, one of another member's stage text): the KL from the model with that arm switched off to the model with every arm on",
442
+ "title": "Stages 1 to 4, on together",
443
+ "behavior": "the arms of stage 1: perspective, stage 2: concepts, stage 3: rule chains, stage 4: arithmetic, always on together, as they were trained",
444
+ "family": "group",
445
+ "status": "two seeds",
446
+ "score": "stage text, arms off -> on: s1_perspective 0.554 -> 0.048; s2_concept 0.721 -> 0.112; s3_rules 0.930 -> 0.224; s4_arith 1.234 -> 0.338; web text +0.0017 bpb",
447
+ "measured": {
448
+ "stage_text_bpb": {
449
+ "s1_perspective": {
450
+ "arms_off": 0.5538,
451
+ "group_on": 0.0482,
452
+ "difference": -0.5057,
453
+ "difference_se": 0.0048
454
+ },
455
+ "s2_concept": {
456
+ "arms_off": 0.7212,
457
+ "group_on": 0.1123,
458
+ "difference": -0.6089,
459
+ "difference_se": 0.0038
460
+ },
461
+ "s3_rules": {
462
+ "arms_off": 0.9296,
463
+ "group_on": 0.2236,
464
+ "difference": -0.706,
465
+ "difference_se": 0.0052
466
+ },
467
+ "s4_arith": {
468
+ "arms_off": 1.2339,
469
+ "group_on": 0.3384,
470
+ "difference": -0.8955,
471
+ "difference_se": 0.0059
472
+ }
473
+ },
474
+ "web_text_difference": 0.00171,
475
+ "web_text_difference_se": 0.00035,
476
+ "steps_trained": {
477
+ "s1_perspective": 3200,
478
+ "s2_concept": 2400,
479
+ "s3_rules": 800,
480
+ "s4_arith": 800
481
+ },
482
+ "probe_suite_mean": {
483
+ "arms_off": 0.411,
484
+ "group_on": 0.419
485
+ },
486
+ "second_seed": {
487
+ "stage_text_bpb": {
488
+ "s1_perspective": {
489
+ "arms_off": 0.5538,
490
+ "group_on": 0.0479,
491
+ "difference": -0.506,
492
+ "difference_se": 0.0048
493
+ },
494
+ "s2_concept": {
495
+ "arms_off": 0.7212,
496
+ "group_on": 0.1212,
497
+ "difference": -0.6,
498
+ "difference_se": 0.0037
499
+ },
500
+ "s3_rules": {
501
+ "arms_off": 0.9296,
502
+ "group_on": 0.2222,
503
+ "difference": -0.7074,
504
+ "difference_se": 0.0054
505
+ },
506
+ "s4_arith": {
507
+ "arms_off": 1.2339,
508
+ "group_on": 0.3379,
509
+ "difference": -0.896,
510
+ "difference_se": 0.0059
511
+ }
512
+ },
513
+ "web_text_difference": 0.0011,
514
+ "web_text_difference_se": 0.00039,
515
+ "steps_trained": {
516
+ "s1_perspective": 3200,
517
+ "s2_concept": 2400,
518
+ "s3_rules": 800,
519
+ "s4_arith": 800
520
+ },
521
+ "probe_suite_mean": {
522
+ "arms_off": 0.411,
523
+ "group_on": 0.415
524
+ }
525
+ }
526
+ },
527
+ "examples": []
528
+ },
529
+ {
530
+ "id": "solo/s1_perspective",
531
+ "name": "solo/s1_perspective",
532
+ "group": null,
533
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
534
+ "step": 245674,
535
+ "params": 13692960,
536
+ "sites": 32,
537
+ "weights": [
538
+ "arms/solo/s1_perspective.safetensors"
539
+ ],
540
+ "mount": {
541
+ "type": "anchor"
542
+ },
543
+ "precision": "fp32",
544
+ "template": {
545
+ "frame": "raw",
546
+ "template": "raw stage text, continued"
547
+ },
548
+ "decode": {
549
+ "temperature": 0.0,
550
+ "top_p": 1.0,
551
+ "max_new": 120,
552
+ "precision": "fp32"
553
+ },
554
+ "source": {
555
+ "file": "s1_perspective_fresh_seed1_d20261005_step800.safetensors",
556
+ "sha256": "ff72dd5a8061926b56e09c6da765c1d1ee44cbbd3d4e5e2c30793e04b369532a"
557
+ },
558
+ "recipe": "trained alone on the frozen final core: 800 steps of 2 x 4096 bytes of the stage's own text, pure Adam (lr 0.001), with a quiet term at weight 2 on held-out web text (the KL from the model with the arm off to the model with it on, one chunk beside every task chunk)",
559
+ "title": "Stage 1: perspective (solo)",
560
+ "behavior": "one small event retold from each side: seen from a person, said to them, told about them",
561
+ "family": "solo",
562
+ "status": "two seeds; trained alone: mount one solo arm at a time",
563
+ "score": "own stage text 0.554 -> 0.081 bpb; web text +0.0002 bpb",
564
+ "measured": {
565
+ "steps": 800,
566
+ "own_stage_bpb_arms_off": 0.5538,
567
+ "own_stage_bpb_arm_on": 0.0809,
568
+ "own_stage_difference": -0.4729,
569
+ "own_stage_difference_se": 0.0038,
570
+ "web_text_difference": 0.00018,
571
+ "web_text_difference_se": 0.00019,
572
+ "stage_items_own_form_accuracy": {
573
+ "arms_off": 0.55,
574
+ "arm_on": 0.75
575
+ },
576
+ "stage_items_new_form_accuracy": {
577
+ "arms_off": 0.463,
578
+ "arm_on": 0.412
579
+ },
580
+ "probe_suite_mean": {
581
+ "arms_off": 0.411,
582
+ "arm_on": 0.381
583
+ },
584
+ "second_seed": {
585
+ "steps": 800,
586
+ "own_stage_bpb_arms_off": 0.5538,
587
+ "own_stage_bpb_arm_on": 0.0802,
588
+ "own_stage_difference": -0.4737,
589
+ "own_stage_difference_se": 0.0038,
590
+ "web_text_difference": 0.00036,
591
+ "web_text_difference_se": 0.00016,
592
+ "stage_items_own_form_accuracy": {
593
+ "arms_off": 0.55,
594
+ "arm_on": 0.7
595
+ },
596
+ "stage_items_new_form_accuracy": {
597
+ "arms_off": 0.463,
598
+ "arm_on": 0.4
599
+ },
600
+ "probe_suite_mean": {
601
+ "arms_off": 0.411,
602
+ "arm_on": 0.385
603
+ }
604
+ }
605
+ },
606
+ "examples": [
607
+ "Rui dropped the acorn near the box. Seen from Rui: I dropped the acorn near the box. Said to Rui: you dropped the acorn near the box. Told about Rui: Rui dropped the acorn near the box. Three ways of saying, one thing that happened."
608
+ ]
609
+ },
610
+ {
611
+ "id": "solo/s2_concept",
612
+ "name": "solo/s2_concept",
613
+ "group": null,
614
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
615
+ "step": 245674,
616
+ "params": 13692960,
617
+ "sites": 32,
618
+ "weights": [
619
+ "arms/solo/s2_concept.safetensors"
620
+ ],
621
+ "mount": {
622
+ "type": "anchor"
623
+ },
624
+ "precision": "fp32",
625
+ "template": {
626
+ "frame": "raw",
627
+ "template": "raw stage text, continued"
628
+ },
629
+ "decode": {
630
+ "temperature": 0.0,
631
+ "top_p": 1.0,
632
+ "max_new": 120,
633
+ "precision": "fp32"
634
+ },
635
+ "source": {
636
+ "file": "s2_concept_fresh_seed2_d20261005_step800.safetensors",
637
+ "sha256": "dc76f7c7d713b34d23b553adbb0b6302ec1d1080d7ffe8cc9461c5f5e6a03938"
638
+ },
639
+ "recipe": "trained alone on the frozen final core: 800 steps of 2 x 4096 bytes of the stage's own text, pure Adam (lr 0.001), with a quiet term at weight 2 on held-out web text (the KL from the model with the arm off to the model with it on, one chunk beside every task chunk)",
640
+ "title": "Stage 2: concepts (solo)",
641
+ "behavior": "kinds, properties and differences: what a thing is a kind of, what its kind can do, how two things differ",
642
+ "family": "solo",
643
+ "status": "two seeds; trained alone: mount one solo arm at a time",
644
+ "score": "own stage text 0.721 -> 0.120 bpb; web text +0.0003 bpb",
645
+ "measured": {
646
+ "steps": 800,
647
+ "own_stage_bpb_arms_off": 0.7212,
648
+ "own_stage_bpb_arm_on": 0.1204,
649
+ "own_stage_difference": -0.6008,
650
+ "own_stage_difference_se": 0.0034,
651
+ "web_text_difference": 0.00029,
652
+ "web_text_difference_se": 0.00016,
653
+ "stage_items_own_form_accuracy": {
654
+ "arms_off": 0.298,
655
+ "arm_on": 0.845
656
+ },
657
+ "stage_items_new_form_accuracy": {
658
+ "arms_off": 0.678,
659
+ "arm_on": 0.729
660
+ },
661
+ "probe_suite_mean": {
662
+ "arms_off": 0.411,
663
+ "arm_on": 0.404
664
+ },
665
+ "second_seed": {
666
+ "steps": 800,
667
+ "own_stage_bpb_arms_off": 0.7212,
668
+ "own_stage_bpb_arm_on": 0.1199,
669
+ "own_stage_difference": -0.6013,
670
+ "own_stage_difference_se": 0.0035,
671
+ "web_text_difference": 0.00043,
672
+ "web_text_difference_se": 0.00018,
673
+ "stage_items_own_form_accuracy": {
674
+ "arms_off": 0.298,
675
+ "arm_on": 0.881
676
+ },
677
+ "stage_items_new_form_accuracy": {
678
+ "arms_off": 0.678,
679
+ "arm_on": 0.695
680
+ },
681
+ "probe_suite_mean": {
682
+ "arms_off": 0.411,
683
+ "arm_on": 0.389
684
+ }
685
+ }
686
+ },
687
+ "examples": [
688
+ "A whale is a kind of mammal. Most mammals can swim. A whale is not a beetle; they differ in kind."
689
+ ]
690
+ },
691
+ {
692
+ "id": "solo/s3_rules",
693
+ "name": "solo/s3_rules",
694
+ "group": null,
695
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
696
+ "step": 245674,
697
+ "params": 13692960,
698
+ "sites": 32,
699
+ "weights": [
700
+ "arms/solo/s3_rules.safetensors"
701
+ ],
702
+ "mount": {
703
+ "type": "anchor"
704
+ },
705
+ "precision": "fp32",
706
+ "template": {
707
+ "frame": "raw",
708
+ "template": "raw stage text, continued"
709
+ },
710
+ "decode": {
711
+ "temperature": 0.0,
712
+ "top_p": 1.0,
713
+ "max_new": 120,
714
+ "precision": "fp32"
715
+ },
716
+ "source": {
717
+ "file": "s3_rules_fresh_seed3_d20261005_step800.safetensors",
718
+ "sha256": "11bc779e8853bab9ecb6cdff3419673e39a59c55fc91e8842aaef6ffc23c3b47"
719
+ },
720
+ "recipe": "trained alone on the frozen final core: 800 steps of 2 x 4096 bytes of the stage's own text, pure Adam (lr 0.001), with a quiet term at weight 2 on held-out web text (the KL from the model with the arm off to the model with it on, one chunk beside every task chunk)",
721
+ "title": "Stage 3: rule chains (solo)",
722
+ "behavior": "if-then rules over made-up words, followed step by step to what follows",
723
+ "family": "solo",
724
+ "status": "two seeds; trained alone: mount one solo arm at a time",
725
+ "score": "own stage text 0.930 -> 0.372 bpb; web text +0.0009 bpb",
726
+ "measured": {
727
+ "steps": 800,
728
+ "own_stage_bpb_arms_off": 0.9296,
729
+ "own_stage_bpb_arm_on": 0.3724,
730
+ "own_stage_difference": -0.5572,
731
+ "own_stage_difference_se": 0.0054,
732
+ "web_text_difference": 0.00095,
733
+ "web_text_difference_se": 0.00011,
734
+ "stage_items_own_form_accuracy": {
735
+ "arms_off": 0.155,
736
+ "arm_on": 0.6
737
+ },
738
+ "stage_items_new_form_accuracy": {
739
+ "arms_off": 0.07,
740
+ "arm_on": 0.12
741
+ },
742
+ "probe_suite_mean": {
743
+ "arms_off": 0.411,
744
+ "arm_on": 0.407
745
+ },
746
+ "second_seed": {
747
+ "steps": 800,
748
+ "own_stage_bpb_arms_off": 0.9296,
749
+ "own_stage_bpb_arm_on": 0.3737,
750
+ "own_stage_difference": -0.5559,
751
+ "own_stage_difference_se": 0.0054,
752
+ "web_text_difference": 0.00079,
753
+ "web_text_difference_se": 0.00015,
754
+ "stage_items_own_form_accuracy": {
755
+ "arms_off": 0.155,
756
+ "arm_on": 0.58
757
+ },
758
+ "stage_items_new_form_accuracy": {
759
+ "arms_off": 0.07,
760
+ "arm_on": 0.12
761
+ },
762
+ "probe_suite_mean": {
763
+ "arms_off": 0.411,
764
+ "arm_on": 0.4
765
+ }
766
+ }
767
+ },
768
+ "examples": [
769
+ "If someone is hoream, then they are hortil. If someone is hortil, then they are babig. If someone is babig, then they are banng. Vin is hoream. So Vin is hortil. So Vin is babig. So Vin is banng."
770
+ ]
771
+ },
772
+ {
773
+ "id": "solo/s4_arith",
774
+ "name": "solo/s4_arith",
775
+ "group": null,
776
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
777
+ "step": 245674,
778
+ "params": 13692960,
779
+ "sites": 32,
780
+ "weights": [
781
+ "arms/solo/s4_arith.safetensors"
782
+ ],
783
+ "mount": {
784
+ "type": "anchor"
785
+ },
786
+ "precision": "fp32",
787
+ "template": {
788
+ "frame": "raw",
789
+ "template": "raw stage text, continued"
790
+ },
791
+ "decode": {
792
+ "temperature": 0.0,
793
+ "top_p": 1.0,
794
+ "max_new": 120,
795
+ "precision": "fp32"
796
+ },
797
+ "source": {
798
+ "file": "s4_arith_fresh_seed4_d20261005_step800.safetensors",
799
+ "sha256": "90774072859c74888b7e83298867cddbb7f12c7c9b9e9206a417c08545a6244d"
800
+ },
801
+ "recipe": "trained alone on the frozen final core: 800 steps of 2 x 4096 bytes of the stage's own text, pure Adam (lr 0.001), with a quiet term at weight 2 on held-out web text (the KL from the model with the arm off to the model with it on, one chunk beside every task chunk)",
802
+ "title": "Stage 4: arithmetic (solo)",
803
+ "behavior": "small arithmetic worked out in text",
804
+ "family": "solo",
805
+ "status": "two seeds; trained alone: mount one solo arm at a time",
806
+ "score": "own stage text 1.234 -> 0.389 bpb; web text +0.0007 bpb",
807
+ "measured": {
808
+ "steps": 800,
809
+ "own_stage_bpb_arms_off": 1.2339,
810
+ "own_stage_bpb_arm_on": 0.3889,
811
+ "own_stage_difference": -0.845,
812
+ "own_stage_difference_se": 0.0048,
813
+ "web_text_difference": 0.00069,
814
+ "web_text_difference_se": 0.00023,
815
+ "stage_items_own_form_accuracy": {
816
+ "arms_off": 0.344,
817
+ "arm_on": 0.487
818
+ },
819
+ "stage_items_new_form_accuracy": {
820
+ "arms_off": 0.242,
821
+ "arm_on": 0.242
822
+ },
823
+ "probe_suite_mean": {
824
+ "arms_off": 0.411,
825
+ "arm_on": 0.389
826
+ },
827
+ "second_seed": {
828
+ "steps": 800,
829
+ "own_stage_bpb_arms_off": 1.2339,
830
+ "own_stage_bpb_arm_on": 0.3884,
831
+ "own_stage_difference": -0.8455,
832
+ "own_stage_difference_se": 0.005,
833
+ "web_text_difference": 0.00056,
834
+ "web_text_difference_se": 0.00024,
835
+ "stage_items_own_form_accuracy": {
836
+ "arms_off": 0.344,
837
+ "arm_on": 0.487
838
+ },
839
+ "stage_items_new_form_accuracy": {
840
+ "arms_off": 0.242,
841
+ "arm_on": 0.208
842
+ },
843
+ "probe_suite_mean": {
844
+ "arms_off": 0.411,
845
+ "arm_on": 0.4
846
+ }
847
+ }
848
+ },
849
+ "examples": [
850
+ "Count by 3s: 1 3s make 3; 2 3s make 6; 3 3s make 9; 4 3s make 12; 5 3s make 15. So 3 times 5 is 15."
851
+ ]
852
+ }
853
+ ]
854
+ }
arms/solo/s1_perspective.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3c9d862b4b6832ab8fc6bccf177c87882a1b0ab78e91b2c01a952f7b0f9328cd
3
+ size 54817760
arms/solo/s2_concept.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8a90104ddeace651612a0bf0015250cefbd104a29ece1cf6c064d4854cafa52d
3
+ size 54817632
arms/solo/s3_rules.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d26218539c5f1328f30a2ce40c4d5ccc5cf01a64d03c50469183ad64c565553c
3
+ size 54817680
arms/solo/s4_arith.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:358c996148f9bb8c785f6fc2ec565ca5f08ba2cb252cb85b3ae388655e5626ed
3
+ size 54817552
arms/stages-1-4/s1_perspective.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:656d03203726ea77aa758a636424db6f21a39e379c34697fa10debbe017c48f9
3
+ size 54819000
arms/stages-1-4/s2_concept.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:436ce53452aa7264ef3fb5fea28b6a7077b843c4e65f1538932114b99fe380d5
3
+ size 54818880
arms/stages-1-4/s3_rules.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c76ee31256d3c36597b7791ab0deab31507d625ff68ccdac10f28a5c4a007a07
3
+ size 54818928
arms/stages-1-4/s4_arith.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9c6080918b0ea2b107fa8392901cc26628934c1d8ec7c095db6cd1f86b212cc8
3
+ size 54818792
config.json CHANGED
@@ -61,5 +61,12 @@
61
  "AutoModelForCausalLM": "modeling_minibeatrix.MiniBeatrixForCausalLM"
62
  },
63
  "torch_dtype": "float32",
64
- "use_cache": false
 
 
 
 
 
 
 
65
  }
 
61
  "AutoModelForCausalLM": "modeling_minibeatrix.MiniBeatrixForCausalLM"
62
  },
63
  "torch_dtype": "float32",
64
+ "use_cache": false,
65
+ "trunk": {
66
+ "craft": "mini-beatrix-3",
67
+ "step": 245674,
68
+ "base_model_id": "alephllm/mini-beatrix-3@step245674",
69
+ "note": "the core weights are the same with arms off and arms on; the arms are separate files"
70
+ },
71
+ "default_arm": null
72
  }
modeling_minibeatrix.py CHANGED
@@ -1,23 +1,43 @@
1
  """HF AutoModel wrapper for mini-beatrix-3 — the 32-block full-splat craft
2
  (a CausalSplatHUB constellation and an anchored expert bank in every block;
3
- 64.4B-byte mission, completed 2026-10-05).
4
-
5
- Loads with:
6
- AutoModelForCausalLM.from_pretrained("AbstractPhil/mini-beatrix-3",
7
- trust_remote_code=True)
8
- The model reads raw UTF-8 bytes: input_ids are byte values 0..255.
9
- The default weight file is bf16; the fp32 master weights load with
10
- variant="fp32".
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  """
12
  from __future__ import annotations
13
 
 
 
 
14
  import torch
15
  from transformers import PretrainedConfig, PreTrainedModel
16
  from transformers.generation import GenerationMixin
17
  from transformers.modeling_outputs import CausalLMOutputWithPast
18
 
19
- from .presets import AlephLMConfig
 
20
  from .alephlm import AlephLM
 
 
 
21
 
22
 
23
  class MiniBeatrixConfig(PretrainedConfig):
@@ -33,7 +53,7 @@ class MiniBeatrixConfig(PretrainedConfig):
33
  hub_K=64, hub_D=128, tau=0.1, bank_experts=3, bank_ff=1024,
34
  head_K=256, head_D=256, gate_init=-3.0,
35
  tie_embeddings=False, hub_chunk=256, hub_const=4,
36
- hub_ckpt=0, **kwargs):
37
  self.name = name
38
  self.d_model = d_model
39
  self.n_layers = n_layers
@@ -54,6 +74,11 @@ class MiniBeatrixConfig(PretrainedConfig):
54
  self.hub_chunk = hub_chunk
55
  self.hub_const = hub_const
56
  self.hub_ckpt = hub_ckpt # inference: always 0
 
 
 
 
 
57
  super().__init__(**kwargs)
58
 
59
  def to_aleph(self) -> AlephLMConfig:
@@ -78,16 +103,31 @@ class MiniBeatrixForCausalLM(PreTrainedModel, GenerationMixin):
78
  def __init__(self, config: MiniBeatrixConfig):
79
  super().__init__(config)
80
  self.model = AlephLM(config.to_aleph())
 
 
81
  self.post_init()
82
 
83
  def _init_weights(self, module):
84
- pass # AlephLM initializes itself; loader must not re-init
 
 
 
 
 
 
 
 
 
 
 
85
 
 
86
  def forward(self, input_ids=None, labels=None, attention_mask=None,
87
  past_key_values=None, use_cache=None, **kwargs):
88
  # attention_mask is safely ignored: causal model, right-padding
89
  # with label masking is the trained convention. No KV cache in
90
- # this wrapper; generate() recomputes the prefix each step.
 
91
  if input_ids.shape[1] > self.config.context:
92
  input_ids = input_ids[:, -self.config.context:]
93
  if labels is not None:
@@ -99,3 +139,154 @@ class MiniBeatrixForCausalLM(PreTrainedModel, GenerationMixin):
99
 
100
  def prepare_inputs_for_generation(self, input_ids, **kwargs):
101
  return {"input_ids": input_ids[:, -self.config.context:]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """HF AutoModel wrapper for mini-beatrix-3 — the 32-block full-splat craft
2
  (a CausalSplatHUB constellation and an anchored expert bank in every block;
3
+ 64.4B-byte mission, completed 2026-10-05), packaged with its detachable
4
+ stage arms.
5
+
6
+ from transformers import AutoModelForCausalLM
7
+ m = AutoModelForCausalLM.from_pretrained("AbstractPhil/mini-beatrix-3",
8
+ trust_remote_code=True)
9
+ m.say("Who are you?") # arms off: the bare core
10
+
11
+ m.mount_arm("s3_rules") # arms on: one stage arm ...
12
+ m.mount_arm("stages-1-4") # ... or a stack of them
13
+ m.detach_arm() # bit-exact core restored
14
+
15
+ # arms on from the first call
16
+ m = AutoModelForCausalLM.from_pretrained("AbstractPhil/mini-beatrix-3",
17
+ trust_remote_code=True,
18
+ default_arm="stages-1-4")
19
+
20
+ The model reads raw UTF-8 bytes: input_ids are byte values 0..255. The
21
+ default weight file is bf16; the fp32 master weights load with
22
+ variant="fp32". The core's weights are the same with arms off and on: an
23
+ arm is a guest on the core, never a change to it.
24
  """
25
  from __future__ import annotations
26
 
27
+ import json
28
+ import os
29
+
30
  import torch
31
  from transformers import PretrainedConfig, PreTrainedModel
32
  from transformers.generation import GenerationMixin
33
  from transformers.modeling_outputs import CausalLMOutputWithPast
34
 
35
+ from .arms import (SPECIALS, TEXT_STOPS, cut_specials_reply, cut_text_reply,
36
+ mount, render_specials_frame, render_text_frame)
37
  from .alephlm import AlephLM
38
+ from .presets import AlephLMConfig
39
+
40
+ ARMS_INDEX = "arms/index.json"
41
 
42
 
43
  class MiniBeatrixConfig(PretrainedConfig):
 
53
  hub_K=64, hub_D=128, tau=0.1, bank_experts=3, bank_ff=1024,
54
  head_K=256, head_D=256, gate_init=-3.0,
55
  tie_embeddings=False, hub_chunk=256, hub_const=4,
56
+ hub_ckpt=0, trunk=None, default_arm=None, **kwargs):
57
  self.name = name
58
  self.d_model = d_model
59
  self.n_layers = n_layers
 
74
  self.hub_chunk = hub_chunk
75
  self.hub_const = hub_const
76
  self.hub_ckpt = hub_ckpt # inference: always 0
77
+ # the frozen core the packaged arms were trained on (provenance)
78
+ self.trunk = trunk or {"craft": "mini-beatrix-3", "step": 245674,
79
+ "base_model_id": "alephllm/mini-beatrix-3@step245674"}
80
+ # None = arms off at load; an arm id = that arm mounted at load
81
+ self.default_arm = default_arm
82
  super().__init__(**kwargs)
83
 
84
  def to_aleph(self) -> AlephLMConfig:
 
103
  def __init__(self, config: MiniBeatrixConfig):
104
  super().__init__(config)
105
  self.model = AlephLM(config.to_aleph())
106
+ self._arm = None # the live handle, if an arm is mounted
107
+ self._index = None
108
  self.post_init()
109
 
110
  def _init_weights(self, module):
111
+ pass # AlephLM initializes itself; the loader must not re-init
112
+
113
+ @classmethod
114
+ def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
115
+ """Loads the bare core (arms off). With default_arm="<id>", passed
116
+ here or set in config.json, that arm is mounted once the core's
117
+ weights are in (arms on)."""
118
+ out = super().from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs)
119
+ model = out[0] if isinstance(out, tuple) else out
120
+ if getattr(model.config, "default_arm", None):
121
+ model.mount_arm(model.config.default_arm)
122
+ return out
123
 
124
+ # ------------------------------------------------------------ core
125
  def forward(self, input_ids=None, labels=None, attention_mask=None,
126
  past_key_values=None, use_cache=None, **kwargs):
127
  # attention_mask is safely ignored: causal model, right-padding
128
  # with label masking is the trained convention. No KV cache in
129
+ # this wrapper; generate() recomputes the prefix each step — the
130
+ # model's own cached path is used by say() instead.
131
  if input_ids.shape[1] > self.config.context:
132
  input_ids = input_ids[:, -self.config.context:]
133
  if labels is not None:
 
139
 
140
  def prepare_inputs_for_generation(self, input_ids, **kwargs):
141
  return {"input_ids": input_ids[:, -self.config.context:]}
142
+
143
+ def save_pretrained(self, *args, **kwargs):
144
+ if self._arm is not None:
145
+ raise RuntimeError(
146
+ "an arm is mounted: detach_arm() before saving, or the arm's "
147
+ "parameters would be written into the core's weight file")
148
+ return super().save_pretrained(*args, **kwargs)
149
+
150
+ # ------------------------------------------------------------ files
151
+ def _repo_file(self, relpath: str) -> str:
152
+ """A file of this package: from the local snapshot if the model was
153
+ loaded from a directory, else fetched from the hub."""
154
+ root = str(getattr(self.config, "_name_or_path", "") or "")
155
+ local = os.path.join(root, *relpath.split("/"))
156
+ if root and os.path.isfile(local):
157
+ return local
158
+ if root and os.path.isdir(root):
159
+ raise FileNotFoundError(
160
+ f"{relpath} is missing from {root}: this model was loaded from a "
161
+ "directory, so the arms are served from that directory too — "
162
+ "copy arms/ next to config.json, or load by repo id")
163
+ from huggingface_hub import hf_hub_download
164
+ return hf_hub_download(root, relpath)
165
+
166
+ # ------------------------------------------------------------- arms
167
+ def arms(self) -> list:
168
+ """The packaged arm index: one row per arm or stack, with what it
169
+ does, the text it was trained on and what it measured."""
170
+ if self._index is None:
171
+ with open(self._repo_file(ARMS_INDEX), encoding="utf-8") as f:
172
+ self._index = json.load(f)
173
+ return list(self._index.get("arms", []))
174
+
175
+ def arm_card(self, name: str) -> dict:
176
+ for row in self.arms():
177
+ if row.get("name") == name or row.get("id") == name:
178
+ return row
179
+ known = ", ".join(sorted(r.get("id", r.get("name", "?")) for r in self.arms()))
180
+ raise KeyError(f"no packaged arm {name!r}. Available: {known}")
181
+
182
+ @property
183
+ def arm(self):
184
+ """The live arm handle, or None when the bare core is serving."""
185
+ return self._arm
186
+
187
+ def mount_arm(self, name: str, *, verify_base: bool = True):
188
+ """Mount one packaged arm (or stack) on the core and return its
189
+ handle. Mounting a second arm detaches the first."""
190
+ card = self.arm_card(name)
191
+ if verify_base:
192
+ want = str((self.config.trunk or {}).get("base_model_id") or "")
193
+ got = str(card.get("base_model_id") or "")
194
+ if want and got and not (got.startswith(want) or want.startswith(got)):
195
+ raise ValueError(
196
+ f"arm {name!r} was trained on {got}, this core is {want} — "
197
+ "arms are trunk-bound (pass verify_base=False to override)")
198
+ if self._arm is not None:
199
+ self.detach_arm()
200
+ files = [self._repo_file(w) for w in card["weights"]]
201
+ self._arm = mount(self.model, card, files)
202
+ return self._arm
203
+
204
+ def detach_arm(self, *, verify: bool = True):
205
+ """Restore the bare core. With verify=True the restored logits are
206
+ asserted BIT-EXACT against the pre-mount fingerprint."""
207
+ if self._arm is None:
208
+ return None
209
+ h, self._arm = self._arm, None
210
+ h.detach(verify=verify)
211
+ return h
212
+
213
+ # ------------------------------------------------------------ frames
214
+ def render(self, prompt: str, *, frame: str | None = None, history=None,
215
+ system: str | None = None) -> torch.Tensor:
216
+ """Prompt bytes in a frame: 'specials' (the core's own chat frame),
217
+ 'raw' (the bytes as they are) or 'text'."""
218
+ frame = frame or self.default_frame()
219
+ if frame == "specials":
220
+ ids = render_specials_frame(prompt, system=system, history=history)
221
+ elif frame == "text":
222
+ ids = render_text_frame(prompt, history=history)
223
+ elif frame == "raw":
224
+ ids = prompt.encode("utf-8")
225
+ else:
226
+ raise ValueError(f"unknown frame {frame!r} (raw | text | specials)")
227
+ return torch.tensor([list(ids)], dtype=torch.long, device=self.device)
228
+
229
+ def default_frame(self) -> str:
230
+ """The live arm's own frame (the stage arms continue raw text); the
231
+ core's chat frame when nothing is mounted."""
232
+ if self._arm is not None:
233
+ return ((self._arm.card.get("template") or {}).get("frame")
234
+ or "raw")
235
+ return "specials"
236
+
237
+ @torch.no_grad()
238
+ def say(self, prompt: str, *, frame: str | None = None, history=None,
239
+ system: str | None = None, max_new: int = 120,
240
+ temperature: float = 0.0, top_p: float = 1.0,
241
+ use_cache: bool = True, raw: bool = False) -> str:
242
+ """Render, continue and cut at the frame's stop (greedy by default).
243
+ Returns the continuation text."""
244
+ frame = frame or self.default_frame()
245
+ ids = self.render(prompt, frame=frame, history=history, system=system)
246
+ if getattr(self._arm, "reset_state", None):
247
+ self._arm.reset_state()
248
+ out = self._continue(ids, max_new=max_new, temperature=temperature,
249
+ top_p=top_p, use_cache=use_cache, frame=frame)
250
+ gen = bytes(int(b) for b in out[0, ids.shape[1]:].tolist())
251
+ if raw or frame == "raw":
252
+ return gen.decode("utf-8", errors="replace")
253
+ cut = (cut_specials_reply(gen) if frame == "specials"
254
+ else cut_text_reply(gen))
255
+ return cut.decode("utf-8", errors="replace").strip()
256
+
257
+ @torch.no_grad()
258
+ def _continue(self, idx: torch.Tensor, *, max_new: int, temperature: float,
259
+ top_p: float, use_cache: bool,
260
+ frame: str = "raw") -> torch.Tensor:
261
+ """The core's own decode: constant-size cached state while the
262
+ sequence fits the position table. Stops early on the frame's own
263
+ stop bytes: [END] or a document boundary in every frame, and the
264
+ text frame's stop strings in that frame only."""
265
+ m = self.model
266
+ m.eval()
267
+ ctx = int(self.config.context)
268
+ stops = [bytes([SPECIALS["END"]]), bytes([SPECIALS["DOC"]])]
269
+ if frame == "text":
270
+ stops += [s.encode("utf-8") for s in TEXT_STOPS]
271
+ start = idx.shape[1]
272
+ if not (use_cache and idx.shape[1] < ctx):
273
+ for _ in range(max_new):
274
+ logits = self(input_ids=idx[:, -ctx:]).logits
275
+ nxt = m._sample(logits, temperature, top_p)
276
+ idx = torch.cat([idx, nxt], dim=1)
277
+ if _hit_stop(idx, start, stops):
278
+ break
279
+ return idx
280
+ logits, cache = m.prefill(idx)
281
+ for i in range(min(max_new, ctx - start)):
282
+ nxt = m._sample(logits, temperature, top_p)
283
+ idx = torch.cat([idx, nxt], dim=1)
284
+ if _hit_stop(idx, start, stops):
285
+ break
286
+ logits = m.decode_step(nxt, cache)
287
+ return idx
288
+
289
+
290
+ def _hit_stop(idx: torch.Tensor, start: int, stops) -> bool:
291
+ tail = bytes(int(b) for b in idx[0, start:].tolist())[-8:]
292
+ return any(tail.endswith(s) for s in stops)