TrickyRex commited on
Commit
477cc48
·
verified ·
1 Parent(s): f39f8ef

Upload folder using huggingface_hub

Browse files
EVALS.log ADDED
@@ -0,0 +1 @@
 
 
1
+ 2026-06-24T22:03:47Z rob-rbyte-v4 total=1100 overall=0.510 highest_tier_above_90=5 deterministic=True T0=0.300 T1=1.000 T2=1.000 T3=1.000 T4=1.000 T5=1.000 T6=0.020 T7=0.020 T8=0.020 T9=0.020 T10=0.020 official_pipeline=modchallenge_evaluate two_runs_bit_identical=True
README.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # rob-rbyte-v4
2
+
3
+ Residue router for the SAIR Modular Arithmetic Challenge. Entry class
4
+ `model.ResidueRouterV1`, output base 256. Covers tiers 1-5.
5
+
6
+ Routing is by the size of `p`. Operands are reduced mod p inside
7
+ `predict_digits` (the two-argument normalization both reference models use: a
8
+ with p, then b with p, never all three).
9
+
10
+ - **Tiers 1-2 (p <= 251):** the v1 residue specialist. Each operand residue is
11
+ embedded through a shared per-(prime, residue) table; the two vectors are
12
+ added (a discrete-log inductive bias: logs add under multiplication); a
13
+ residual MLP trunk transforms the sum; logits score against a per-(prime,
14
+ class) output table masked to the p classes of the current prime. The answer
15
+ is one base-256 digit. ~2.9M parameters.
16
+
17
+ - **Tier 3 (251 < p < 65536):** two trained shared local-rule step nets
18
+ composed through fixed wiring. After reduction x, y are 16-bit residues. A
19
+ MULTIPLY step learns the shared carry rule over the carry-save column sums
20
+ and, composed closed-loop through a fixed parity readout, emits the exact
21
+ 32-bit product t = x*y. A REDUCTION step learns the shared per-nibble
22
+ borrow/compare rule and, composed through fixed restoring-division wiring,
23
+ emits r = t mod p in [0, p). Plain GELU MLPs, width 96, depth 3, ~20k params
24
+ each.
25
+
26
+ - **Tier 4 (65536 <= p < 2^32):** the SAME two rules at 32-bit geometry. After
27
+ reduction x, y are 32-bit residues. The MULTIPLY step learns the carry rule
28
+ over the 63 carry-save columns (sum <= 32, carry <= 31) and, composed through
29
+ the parity readout widened to 64 bits, emits the 64-bit product as BITS. The
30
+ REDUCTION step is the identical 512-case per-nibble borrow rule, composed over
31
+ 64 division positions x 9 nibbles, emitting r = t mod p in [0, p). Multiply
32
+ step GELU MLP width 128 depth 3 (~35k params), reduction step width 96 depth 3
33
+ (~20k params).
34
+
35
+ - **Tier 5 (2^32 <= p < 2^64):** the SAME two rules at 64-bit geometry. After
36
+ reduction x, y are 64-bit residues. The MULTIPLY step learns the carry rule
37
+ over the 127 carry-save columns (sum <= 64, carry <= 63) and, composed through
38
+ the parity readout widened to 128 bits, emits the 128-bit product as BITS. The
39
+ REDUCTION step is the identical 512-case per-nibble borrow rule, composed over
40
+ 128 division positions x 17 nibbles, emitting r = t mod p in [0, p). Because a
41
+ 64-bit residue and the 65-bit division register both overflow signed int64,
42
+ tier 5 carries operands, p, the product, and the division register as BIT
43
+ tensors and never materializes a wide value as an int64 scalar. Multiply step
44
+ GELU MLP width 160 depth 3 (~55k params), reduction step width 96 depth 3
45
+ (~20k params). The two techniques carried from tier 4: reciprocal-operand
46
+ framing (each triple traced as both (x,y) and (y,x)) and Charton-Kempe two-set
47
+ sampling (a small repeated set + a large fresh set).
48
+
49
+ - **Tiers 6-10 (p >= 2^64):** outside the trained regime; returns [0].
50
+
51
+ ## Provenance
52
+
53
+ In every tier the carry-save column sums, parity readout, bit shifts,
54
+ restoring-division topology, and ge-from-final-borrow decision are fixed
55
+ scaffold. The two nontrivial decisions, the carry rule and the borrow/compare
56
+ rule, reside in trained MLP parameters (separate nets per tier-3 / tier-4 /
57
+ tier-5 geometry). Randomizing a step net collapses its tier:
58
+
59
+ - tier 3 random-weight pipeline: exact = 0.000000. See `t3_collapse_receipt.json`.
60
+ - tier 4 random-weight pipeline: exact = 0.000000. See `t4_collapse_receipt.json`.
61
+ - tier 5 random-weight pipeline: exact = 0.000000; trained mul + random red =
62
+ 0.000000. See `t5_collapse_receipt.json`.
63
+
64
+ Every tier-3/4/5 multiply and reduction step net reaches per-case exactness 1.0
65
+ on its full enumerated domain (tier 5: mul 4160-case / red 512-case), so the
66
+ composed pipelines are exact by the fixed wiring. Five primes per tier are held
67
+ out by identity and appear in no training trace; the composed pipeline is exact
68
+ (1.0) on all five on uniform residue pairs and the four edge cases. The five
69
+ tier-5 gate primes (61-64 bits): 1690313788893089131, 6145258606915434311,
70
+ 8963783833428354709, 11534118763423864511, 14481575096435149429
71
+ (`t5_collapse_receipt.json` and `experiments/014-t5-lifted-step/`).
72
+
73
+ ## Public benchmark (1100 problems, fixed seed)
74
+
75
+ Run through the official pipeline (`modchallenge evaluate ./submission/rob-rbyte-v4
76
+ --total 1100`); the per-tier accuracy and `highest_tier_above_90` come from the
77
+ official decoder, not an internal tensor check:
78
+
79
+ - overall_accuracy = 0.510
80
+ - highest_tier_above_90 = 5
81
+ - deterministic = True (two full runs bit-identical per tier)
82
+ - tier 1 = 1.000, tier 2 = 1.000, tier 3 = 1.000, tier 4 = 1.000, tier 5 = 1.000
83
+ - tiers 6-10 = 0.020 (chance; outside the trained regime, returns [0])
84
+ - full eval wall ~12s
85
+
86
+ See `EVALS.log` and `eval_official_1100.json` for the full breakdown and
87
+ `manifest.json` for the model and training descriptions.
88
+
89
+ Static check: clean. No sympy / gmpy2 / eval / exec / subprocess on any path.
90
+
91
+ ## Files
92
+
93
+ `model.py` (architectures + routing + fixed wiring), `weights.safetensors`
94
+ (tier-1/2 specialist), `t3_mul.safetensors` / `t3_red.safetensors` (tier-3 step
95
+ nets), `t4_mul.safetensors` / `t4_red.safetensors` (tier-4 step nets),
96
+ `t5_mul.safetensors` / `t5_red.safetensors` (tier-5 step nets), `config.json`
97
+ (per-specialist hyperparameters), `manifest.json`, `t3_collapse_receipt.json`,
98
+ `t4_collapse_receipt.json`, `t5_collapse_receipt.json`, `EVALS.log`,
99
+ `eval_official_1100.json`.
config.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "small": {
3
+ "d_model": 128,
4
+ "hidden": 1024
5
+ },
6
+ "t3": {
7
+ "width": 96,
8
+ "depth": 3
9
+ },
10
+ "t4": {
11
+ "mul_width": 128,
12
+ "red_width": 96,
13
+ "depth": 3
14
+ },
15
+ "t5": {
16
+ "mul_width": 160,
17
+ "red_width": 96,
18
+ "depth": 3
19
+ }
20
+ }
eval_official_1100.json ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "overall_accuracy": 0.51,
3
+ "highest_tier_above_90": 5,
4
+ "deterministic": true,
5
+ "tiers": [
6
+ {
7
+ "tier_id": 0,
8
+ "total": 100,
9
+ "correct": 30,
10
+ "accuracy": 0.3,
11
+ "completed": true
12
+ },
13
+ {
14
+ "tier_id": 1,
15
+ "total": 100,
16
+ "correct": 100,
17
+ "accuracy": 1.0,
18
+ "completed": true
19
+ },
20
+ {
21
+ "tier_id": 2,
22
+ "total": 100,
23
+ "correct": 100,
24
+ "accuracy": 1.0,
25
+ "completed": true
26
+ },
27
+ {
28
+ "tier_id": 3,
29
+ "total": 100,
30
+ "correct": 100,
31
+ "accuracy": 1.0,
32
+ "completed": true
33
+ },
34
+ {
35
+ "tier_id": 4,
36
+ "total": 100,
37
+ "correct": 100,
38
+ "accuracy": 1.0,
39
+ "completed": true
40
+ },
41
+ {
42
+ "tier_id": 5,
43
+ "total": 100,
44
+ "correct": 100,
45
+ "accuracy": 1.0,
46
+ "completed": true
47
+ },
48
+ {
49
+ "tier_id": 6,
50
+ "total": 100,
51
+ "correct": 2,
52
+ "accuracy": 0.02,
53
+ "completed": true
54
+ },
55
+ {
56
+ "tier_id": 7,
57
+ "total": 100,
58
+ "correct": 2,
59
+ "accuracy": 0.02,
60
+ "completed": true
61
+ },
62
+ {
63
+ "tier_id": 8,
64
+ "total": 100,
65
+ "correct": 2,
66
+ "accuracy": 0.02,
67
+ "completed": true
68
+ },
69
+ {
70
+ "tier_id": 9,
71
+ "total": 100,
72
+ "correct": 2,
73
+ "accuracy": 0.02,
74
+ "completed": true
75
+ },
76
+ {
77
+ "tier_id": 10,
78
+ "total": 100,
79
+ "correct": 2,
80
+ "accuracy": 0.02,
81
+ "completed": true
82
+ }
83
+ ],
84
+ "repo_id": "",
85
+ "revision": "",
86
+ "eval_period": ""
87
+ }
manifest.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "entry_class": "model.ResidueRouterV1",
3
+ "output_base": 256,
4
+ "framework": "pytorch",
5
+ "model_description": "Router over per-tier specialists, selected by the size of p; inputs above the trained regime return [0]. Operands are reduced mod p inside predict_digits, the same two-argument normalization both reference models use (a with p, then b with p, never all three). TIERS 1-2 (p <= 251): a ~2.9M-parameter residue specialist. Each operand residue is embedded through a shared per-(prime, residue) table, the two vectors are combined by addition (a discrete-log inductive bias: logs add under multiplication), a residual MLP trunk transforms the sum, and logits are scored against a per-(prime, class) output table masked to the p classes of the current prime. The answer is one base-256 digit, below p by construction. TIER 3 (251 < p < 65536): two trained shared local-rule step nets (plain GELU MLPs, width 96, depth 3, ~20k parameters each) composed through fixed wiring. After reduction the operands x, y are 16-bit residues. A MULTIPLY step learns the shared carry rule over the carry-save column sums and, composed closed-loop through a fixed parity readout, emits the exact 32-bit product t = x*y as bits. A REDUCTION step learns the shared per-nibble borrow/compare rule and, composed through fixed restoring-division wiring, emits r = t mod p in [0, p). TIER 4 (65536 <= p < 2^32): the SAME two rules at 32-bit geometry. After reduction x, y are 32-bit residues. The MULTIPLY step learns the carry rule over the 63 carry-save columns (sum <= 32, carry <= 31) and, composed through the parity readout widened to 64 bits, emits the 64-bit product as bits. The REDUCTION step is the identical 512-case per-nibble borrow rule, composed over 64 division positions x 9 nibbles, emitting r = t mod p in [0, p). TIER 5 (2^32 <= p < 2^64): the SAME two rules at 64-bit geometry, GELU MLPs (multiply width 160 depth 3 ~55k params, reduction width 96 depth 3 ~20k params). After reduction x, y are 64-bit residues. Because a 64-bit residue and the 65-bit division register both overflow signed int64, tier 5 carries operands, p, the product, and the division register as BIT tensors and never materializes a wide value as an int64 scalar. The MULTIPLY step learns the carry rule over the 127 carry-save columns (sum <= 64, carry <= 63) and, composed through the parity readout widened to 128 bits, emits the 128-bit product as bits. The REDUCTION step is the identical 512-case per-nibble borrow rule, composed over 128 division positions x 17 nibbles, emitting r = t mod p in [0, p). The answer r is emitted as base-256 digits MSB-first. In every tier the carry-save column sums, parity readout, bit shifts, restoring-division topology, and ge-from-final-borrow decision are fixed scaffold; the two nontrivial decisions, the carry rule and the borrow/compare rule, reside in trained MLP parameters. Randomizing any step net's weights collapses its tier to chance, so the capability is in the trained weights, not the wiring.",
6
+ "training_description": "Four independent training regimes. TIERS 1-2 specialist: trained from random init on the complete synthetic input space for primes <= 251 (all 995,777 triples (x, y, p) with x, y in [0, p) and label (x*y) mod p, edge rows oversampled 8x); cross-entropy on the p-way classification, AdamW (lr 1e-3, cosine), batch 8192, seed 0, 15 epochs to 0 errors on the full space. Because the training set is the entire reachable input space, accuracy is interpolation over trained points; no cross-prime generalization is claimed there. TIER 3 step nets: each trained from random init, teacher-forced on the local-rule transitions of reference traces; the MULTIPLY carry step is saturated over its 272-case domain on full-range 16-bit pairs, the REDUCTION step covers the full 512-case domain from restoring-division traces of random triples over TRAIN primes only. TIER 4 step nets: the same two rules at 32-bit geometry; the MULTIPLY carry step covers its 1056-case domain (sum 0..32, carry 0..31; max carry-out equals max carry-in, a closed cover), the REDUCTION step the same 512-case borrow rule over TRAIN primes. TIER 5 step nets: the same two rules at 64-bit geometry. The MULTIPLY carry step covers its 4160-case domain (sum 0..64, carry 0..63; max carry-out equals max carry-in, a closed cover), trained on full-range 64-bit pairs UNION the full case enumeration so every realizable carry state is seen. The REDUCTION step is the same 512-case borrow rule, trained on restoring-division traces over TRAIN primes using two techniques: reciprocal-operand framing (every triple is traced as both (x, y) and (y, x)) and Charton-Kempe two-set sampling (a small repeated set plus a large fresh set). Optimizer AdamW (lr 2e-3, cosine, no weight decay), seed 0, deterministic CPU. For tiers 3, 4 and 5 alike, five primes in the tier's bit range are held out by identity and feed no training trace; the composed pipeline is exact on all five on uniform residue pairs and the four edge cases. Training code, logs, seeds, and the random-weight-collapse receipts are archived and available on request."
7
+ }
model.py ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Residue router, version 4: small-prime specialist (tiers 1-2), a lifted
2
+ local-step pipeline for tier 3 (16-bit residues), the same two shared rules
3
+ lifted to 32-bit limbs for tier 4 (17-32-bit primes), and lifted again to 64-bit
4
+ limbs for tier 5 (33-64-bit primes, operands to 128 bits).
5
+
6
+ Routing by the size of p:
7
+
8
+ * p <= 251 (tiers 1-2): the v1 residue specialist. Each operand residue is
9
+ looked up in a shared per-(prime, residue) table; the two vectors are
10
+ combined by ADDITION (a discrete-log inductive bias: logs add under
11
+ multiplication); a residual MLP trunk transforms the sum; logits come from a
12
+ per-(prime, class) output table masked to the p classes of the current
13
+ prime. The answer is a single base-256 digit (p <= 251 < 256).
14
+
15
+ * 251 < p < 65536 (tier 3): two trained shared LOCAL-RULE step nets composed
16
+ through fixed wiring. After reduction, x, y are 16-bit residues. A MULTIPLY
17
+ step (the shared carry rule c' = floor((S+c)/2) over the carry-save column
18
+ sums, composed closed-loop through a fixed parity readout) emits the exact
19
+ 32-bit product t = x*y. A REDUCTION step (a shared per-nibble borrow/compare
20
+ rule, composed through fixed restoring-division wiring) emits r = t mod p.
21
+ The answer r is emitted as base-256 digits MSB-first.
22
+
23
+ * 65536 <= p < 2^32 (tier 4): the SAME two rules at 32-bit geometry. After
24
+ reduction, x, y are 32-bit residues. The MULTIPLY step (33x32-case carry
25
+ rule over the 63 carry-save columns, parity readout widened to 64 bits)
26
+ emits the 64-bit product as BITS. The REDUCTION step (the identical 512-case
27
+ borrow rule) composed over 64 division positions x 9 nibbles emits
28
+ r = t mod p. The answer r (< 2^32) is emitted as up to four base-256 digits.
29
+
30
+ * 2^32 <= p < 2^64 (tier 5): the SAME two rules at 64-bit geometry. After
31
+ reduction, x, y are 64-bit residues. Because a 64-bit residue and the 65-bit
32
+ division register both overflow signed int64, tier 5 carries operands, p,
33
+ the product, and the division register as BIT tensors -- no wide value is
34
+ ever materialized as an int64 scalar. The MULTIPLY step (65x64-case carry
35
+ rule over the 127 carry-save columns, parity readout widened to 128 bits)
36
+ emits the 128-bit product as bits. The REDUCTION step (the identical 512-case
37
+ borrow rule) composed over 128 division positions x 17 nibbles emits
38
+ r = t mod p in [0, p). The answer r (< 2^64) is emitted as up to eight
39
+ base-256 digits MSB-first.
40
+
41
+ * p >= 2^64 (tiers 6-10): outside the trained regime; returns [0].
42
+
43
+ Nothing in the forward pass hand-codes the arithmetic over the actual (a, b, p):
44
+ the carry-save column sums, the parity readout, the bit shifts, the restoring-
45
+ division topology, and the ge-from-final-borrow decision are FIXED scaffold; the
46
+ two NONTRIVIAL decisions -- the carry rule and the borrow/compare rule -- live
47
+ in trained MLP parameters (separate nets per tier-3 / tier-4 / tier-5 geometry).
48
+ Randomizing any step net's weights collapses its tier.
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ import json
54
+ from pathlib import Path
55
+
56
+ import torch
57
+ import torch.nn as nn
58
+
59
+ from modchallenge.interface.base_model import ModularMultiplicationModel
60
+
61
+ # ===========================================================================
62
+ # Tier 1-2 specialist (v1 residue net)
63
+ # ===========================================================================
64
+
65
+ PRIMES = (
66
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,
67
+ 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137,
68
+ 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211,
69
+ 223, 227, 229, 233, 239, 241, 251,
70
+ )
71
+ MAX_P = 251
72
+
73
+
74
+ class SmallResidueNet(nn.Module):
75
+ def __init__(self, d_model: int = 128, hidden: int = 1024):
76
+ super().__init__()
77
+ offsets, acc = [], 0
78
+ for p in PRIMES:
79
+ offsets.append(acc)
80
+ acc += p
81
+ table = acc # 6081
82
+ self.pair_emb = nn.Embedding(table, d_model)
83
+ self.out_emb = nn.Embedding(table, d_model)
84
+ self.prime_emb = nn.Embedding(len(PRIMES), d_model)
85
+ self.trunk = nn.Sequential(
86
+ nn.LayerNorm(d_model),
87
+ nn.Linear(d_model, hidden),
88
+ nn.GELU(),
89
+ nn.Linear(hidden, hidden),
90
+ nn.GELU(),
91
+ nn.Linear(hidden, d_model),
92
+ )
93
+ self.ln_out = nn.LayerNorm(d_model)
94
+
95
+ self.register_buffer(
96
+ "primes_t", torch.tensor(PRIMES, dtype=torch.long), persistent=False
97
+ )
98
+ self.register_buffer(
99
+ "offsets_t", torch.tensor(offsets, dtype=torch.long), persistent=False
100
+ )
101
+ lookup = torch.full((MAX_P + 1,), -1, dtype=torch.long)
102
+ for i, p in enumerate(PRIMES):
103
+ lookup[p] = i
104
+ self.register_buffer("prime_lookup", lookup, persistent=False)
105
+ self.register_buffer(
106
+ "class_grid", torch.arange(MAX_P, dtype=torch.long), persistent=False
107
+ )
108
+
109
+ def forward(
110
+ self, ix: torch.Tensor, iy: torch.Tensor, p_idx: torch.Tensor
111
+ ) -> torch.Tensor:
112
+ h = self.pair_emb(ix) + self.pair_emb(iy) + self.prime_emb(p_idx)
113
+ g = self.ln_out(h + self.trunk(h))
114
+ off = self.offsets_t[p_idx]
115
+ pv = self.primes_t[p_idx]
116
+ grid = self.class_grid.unsqueeze(0)
117
+ valid = grid < pv.unsqueeze(1)
118
+ logits = (g @ self.out_emb.weight.t()).gather(1, off.unsqueeze(1) + grid)
119
+ return logits.masked_fill(~valid, float("-inf"))
120
+
121
+ @torch.no_grad()
122
+ def predict(
123
+ self, x: torch.Tensor, y: torch.Tensor, p: torch.Tensor
124
+ ) -> torch.Tensor:
125
+ p_idx = self.prime_lookup[p]
126
+ off = self.offsets_t[p_idx]
127
+ return self.forward(off + x, off + y, p_idx).argmax(dim=-1)
128
+
129
+
130
+ # ===========================================================================
131
+ # Shared step-net architecture (used by tier-3 / tier-4 / tier-5 geometries)
132
+ # ===========================================================================
133
+
134
+ class StepMLP(nn.Module):
135
+ """Plain GELU MLP step: n_in local-state bits -> n_out logits."""
136
+
137
+ def __init__(self, n_in: int, n_out: int, width: int, depth: int):
138
+ super().__init__()
139
+ self.layers = nn.ModuleList([nn.Linear(n_in, width)])
140
+ for _ in range(depth - 1):
141
+ self.layers.append(nn.Linear(width, width))
142
+ self.head = nn.Linear(width, n_out)
143
+ self.act = nn.GELU()
144
+
145
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
146
+ h = x
147
+ for lin in self.layers:
148
+ h = self.act(lin(h))
149
+ return self.head(h)
150
+
151
+
152
+ # ===========================================================================
153
+ # Per-tier multiply / reduction geometries
154
+ # ===========================================================================
155
+
156
+ # Tier 3 (16x16 -> 32-bit; 5-nibble reduction)
157
+ T3_MUL_OPB = 16
158
+ T3_MUL_PRB = 32
159
+ T3_MUL_COLS = 2 * T3_MUL_OPB - 1 # 31
160
+ T3_MUL_SUMB = 5
161
+ T3_MUL_CARB = 4
162
+ T3_MUL_IN = T3_MUL_SUMB + T3_MUL_CARB # 9
163
+ T3_NIB = 4
164
+ T3_RED_NIBBLES = 5
165
+ T3_RED_IN = T3_NIB + T3_NIB + 1 # 9
166
+ T3_RED_OUT = T3_NIB + 1 # 5
167
+ T3_T_BITS = 32
168
+
169
+ # Tier 4 (32x32 -> 64-bit; 9-nibble reduction)
170
+ T4_MUL_OPB = 32
171
+ T4_MUL_PRB = 64
172
+ T4_MUL_COLS = 2 * T4_MUL_OPB - 1 # 63
173
+ T4_MUL_SUMB = 6
174
+ T4_MUL_CARB = 5
175
+ T4_MUL_IN = T4_MUL_SUMB + T4_MUL_CARB # 11
176
+ T4_NIB = 4
177
+ T4_RED_NIBBLES = 9
178
+ T4_RED_IN = T4_NIB + T4_NIB + 1 # 9
179
+ T4_RED_OUT = T4_NIB + 1 # 5
180
+ T4_T_BITS = 64
181
+
182
+ # Tier 5 (64x64 -> 128-bit; 17-nibble reduction). Wide values are bit tensors.
183
+ T5_MUL_OPB = 64
184
+ T5_MUL_PRB = 128
185
+ T5_MUL_COLS = 2 * T5_MUL_OPB - 1 # 127
186
+ T5_MUL_SUMB = 7 # S_c <= 64
187
+ T5_MUL_CARB = 6 # carry <= 63
188
+ T5_MUL_IN = T5_MUL_SUMB + T5_MUL_CARB # 13
189
+ T5_NIB = 4
190
+ T5_RED_NIBBLES = 17 # 65-bit R_pre / 64-bit p
191
+ T5_RED_IN = T5_NIB + T5_NIB + 1 # 9
192
+ T5_RED_OUT = T5_NIB + 1 # 5
193
+ T5_T_BITS = 128
194
+
195
+
196
+ # -- generic carry-save / reduction wiring (parameterized by geometry) -------
197
+
198
+ def _bits(v: torch.Tensor, nb: int) -> torch.Tensor:
199
+ return ((v.unsqueeze(1) >> torch.arange(nb, device=v.device)) & 1).float()
200
+
201
+
202
+ def _column_sums(x_bits: torch.Tensor, y_bits: torch.Tensor, opb: int, cols: int) -> torch.Tensor:
203
+ n = x_bits.shape[0]
204
+ s = torch.zeros(n, cols, dtype=x_bits.dtype, device=x_bits.device)
205
+ for i in range(opb):
206
+ s[:, i:i + opb] += x_bits[:, i:i + 1] * y_bits
207
+ return s
208
+
209
+
210
+ def _encode_carry(s: torch.Tensor, c: torch.Tensor, sumb: int, carb: int) -> torch.Tensor:
211
+ si = torch.arange(sumb, device=s.device)
212
+ ci = torch.arange(carb, device=c.device)
213
+ sb = ((s.unsqueeze(1) >> si) & 1).float()
214
+ cb = ((c.unsqueeze(1) >> ci) & 1).float()
215
+ return torch.cat([sb, cb], dim=1)
216
+
217
+
218
+ def _carry_bits_to_int(bits: torch.Tensor, carb: int) -> torch.Tensor:
219
+ w = (1 << torch.arange(carb, device=bits.device)).long()
220
+ return (bits.round().clamp(0, 1).long() * w).sum(dim=-1)
221
+
222
+
223
+ _SUMB_FOR_COLS = {T3_MUL_COLS: T3_MUL_SUMB, T4_MUL_COLS: T4_MUL_SUMB, T5_MUL_COLS: T5_MUL_SUMB}
224
+
225
+
226
+ @torch.no_grad()
227
+ def _closed_loop_mul(step, col_sums, cols, carb):
228
+ n = col_sums.shape[0]
229
+ s = col_sums.long()
230
+ carry = torch.zeros(n, dtype=torch.long, device=s.device)
231
+ out = torch.empty(n, cols * carb, device=col_sums.device)
232
+ sumb = _SUMB_FOR_COLS[cols]
233
+ for c in range(cols):
234
+ lg = step(_encode_carry(s[:, c], carry, sumb, carb))
235
+ out[:, carb * c:carb * (c + 1)] = lg
236
+ carry = _carry_bits_to_int((lg > 0).float(), carb)
237
+ return out
238
+
239
+
240
+ def _routed_product_bits(carry_logits, col_parity, carb):
241
+ """Fixed parity readout: bit_c = parity(S_c) XOR lsb(carry into c)."""
242
+ BIG = 20.0
243
+ lsb = carry_logits[:, 0::carb]
244
+ bit0 = (2.0 * col_parity[:, 0:1] - 1.0) * BIG
245
+ mid = (1.0 - 2.0 * col_parity[:, 1:]) * lsb[:, :-1]
246
+ bit_last = lsb[:, -1:]
247
+ return torch.cat([bit0, mid, bit_last], dim=1)
248
+
249
+
250
+ @torch.no_grad()
251
+ def _composed_product_bits(step, x_bits, y_bits, opb, cols, prb, carb):
252
+ """Trained carry step (closed loop) + parity readout -> product BITS (B, prb).
253
+
254
+ x_bits, y_bits are (B, opb) LSB-first operand bit tensors.
255
+ """
256
+ col_sums = _column_sums(x_bits, y_bits, opb, cols)
257
+ logits = _closed_loop_mul(step, col_sums, cols, carb)
258
+ col_parity = (col_sums.long() & 1).float()
259
+ bit_logits = _routed_product_bits(logits, col_parity, carb)
260
+ return (bit_logits > 0).long() # (B, prb) bits LSB first
261
+
262
+
263
+ def _encode_red(a, b, bin_, nib):
264
+ ai = torch.arange(nib, device=a.device)
265
+ aa = ((a.unsqueeze(1) >> ai) & 1).float()
266
+ bb = ((b.unsqueeze(1) >> ai) & 1).float()
267
+ cc = bin_.float().unsqueeze(1)
268
+ return torch.cat([aa, bb, cc], dim=1)
269
+
270
+
271
+ def _red_bits_to_out(bits, nib):
272
+ hb = (bits > 0).long()
273
+ w = (1 << torch.arange(nib, device=bits.device)).long()
274
+ d = (hb[:, :nib] * w).sum(dim=1)
275
+ bout = hb[:, nib]
276
+ return d, bout
277
+
278
+
279
+ @torch.no_grad()
280
+ def _composed_reduce_int(step, t_bits, p, nib, nibbles, t_bits_n):
281
+ """Restoring division by p when p and R fit signed int64 (tiers 3-4).
282
+
283
+ R stays in [0, p) (< 2^32), so R never overflows int64 even though the full
284
+ product does. Fixed wiring; per-nibble subtract DECISION is the trained step.
285
+ """
286
+ n = t_bits.shape[0]
287
+ device = t_bits.device
288
+ R = torch.zeros(n, dtype=torch.long, device=device)
289
+ p_nib = torch.stack([(p >> (nib * k)) & 0xF for k in range(nibbles)], dim=1)
290
+ wk = (1 << (nib * torch.arange(nibbles, device=device))).long()
291
+ for i in range(t_bits_n - 1, -1, -1):
292
+ bit = t_bits[:, i].long()
293
+ Rpre = (R << 1) | bit
294
+ borrow = torch.zeros(n, dtype=torch.long, device=device)
295
+ diff_nib = torch.zeros(n, nibbles, dtype=torch.long, device=device)
296
+ for k in range(nibbles):
297
+ an = (Rpre >> (nib * k)) & 0xF
298
+ bn = p_nib[:, k]
299
+ lg = step(_encode_red(an, bn, borrow, nib))
300
+ d, bout = _red_bits_to_out(lg, nib)
301
+ diff_nib[:, k] = d
302
+ borrow = bout
303
+ ge = (borrow == 0).long()
304
+ diff_val = (diff_nib * wk).sum(dim=1)
305
+ R = torch.where(ge.bool(), diff_val, Rpre)
306
+ return R
307
+
308
+
309
+ def _p_nibbles_from_bits(p_bits, nib, nibbles):
310
+ n = p_bits.shape[0]
311
+ out = torch.zeros(n, nibbles, dtype=torch.long, device=p_bits.device)
312
+ wt = (1 << torch.arange(nib, device=p_bits.device)).long()
313
+ for k in range(nibbles):
314
+ chunk = p_bits[:, nib * k:nib * (k + 1)]
315
+ if chunk.shape[1] < nib:
316
+ pad = torch.zeros(n, nib - chunk.shape[1], device=p_bits.device)
317
+ chunk = torch.cat([chunk, pad], dim=1)
318
+ out[:, k] = (chunk.long() * wt).sum(dim=1)
319
+ return out
320
+
321
+
322
+ @torch.no_grad()
323
+ def _composed_reduce_bits(step, t_bits, p_bits, nib, nibbles, t_bits_n):
324
+ """Restoring division by p with R, R_pre, p carried as BIT tensors (tier 5).
325
+
326
+ Nothing overflows int64: R has up to 64 bits, R_pre = 2R + bit up to 65 bits
327
+ -> 17 nibbles. Returns the remainder as (B, nibbles*nib) bits LSB-first.
328
+ """
329
+ n = t_bits.shape[0]
330
+ device = t_bits.device
331
+ RB = nibbles * nib
332
+ R = torch.zeros(n, RB, dtype=torch.long, device=device)
333
+ p_nib = _p_nibbles_from_bits(p_bits, nib, nibbles)
334
+ wt = (1 << torch.arange(nib, device=device)).long()
335
+ di = torch.arange(nib, device=device)
336
+ for i in range(t_bits_n - 1, -1, -1):
337
+ Rpre = torch.zeros(n, RB, dtype=torch.long, device=device)
338
+ Rpre[:, 1:] = R[:, :-1]
339
+ Rpre[:, 0] = t_bits[:, i].long()
340
+ borrow = torch.zeros(n, dtype=torch.long, device=device)
341
+ diff_nib = torch.zeros(n, nibbles, dtype=torch.long, device=device)
342
+ for k in range(nibbles):
343
+ an = (Rpre[:, nib * k:nib * (k + 1)].long() * wt).sum(dim=1)
344
+ bn = p_nib[:, k]
345
+ lg = step(_encode_red(an, bn, borrow, nib))
346
+ d, bout = _red_bits_to_out(lg, nib)
347
+ diff_nib[:, k] = d
348
+ borrow = bout
349
+ ge = (borrow == 0)
350
+ diff_bits = torch.zeros(n, RB, dtype=torch.long, device=device)
351
+ for k in range(nibbles):
352
+ diff_bits[:, nib * k:nib * (k + 1)] = (diff_nib[:, k:k + 1] >> di) & 1
353
+ R = torch.where(ge.unsqueeze(1), diff_bits, Rpre)
354
+ return R.float()
355
+
356
+
357
+ # -- bigint <-> bit-tensor helpers (tier 5: residues exceed signed int64) ----
358
+
359
+ def _int_bits(values, nb: int) -> torch.Tensor:
360
+ out = torch.zeros(len(values), nb, dtype=torch.float32)
361
+ for r, v in enumerate(values):
362
+ v = int(v)
363
+ b = 0
364
+ while v and b < nb:
365
+ out[r, b] = float(v & 1)
366
+ v >>= 1
367
+ b += 1
368
+ return out
369
+
370
+
371
+ def _bits_to_ints(bits: torch.Tensor) -> list[int]:
372
+ hb = (bits > 0.5).long().tolist()
373
+ out = []
374
+ for row in hb:
375
+ v = 0
376
+ for b, bit in enumerate(row):
377
+ if bit:
378
+ v |= (1 << b)
379
+ out.append(v)
380
+ return out
381
+
382
+
383
+ # ===========================================================================
384
+ # Router
385
+ # ===========================================================================
386
+
387
+ T3_MIN_P = MAX_P + 1 # 252
388
+ T3_MAX_P = (1 << 16) - 1
389
+ T4_MIN_P = 1 << 16
390
+ T4_MAX_P = (1 << 32) - 1
391
+ T5_MIN_P = 1 << 32
392
+ T5_MAX_P = (1 << 64) - 1
393
+
394
+
395
+ class ResidueRouterV1(ModularMultiplicationModel):
396
+ """Router over per-tier specialists, selected by the size of p.
397
+
398
+ Kept the class name ``ResidueRouterV1`` so the manifest entry_class is
399
+ stable across versions; this is v4 (tiers 1-5).
400
+ """
401
+
402
+ def __init__(self):
403
+ self.small: SmallResidueNet | None = None
404
+ self.t3_mul: StepMLP | None = None
405
+ self.t3_red: StepMLP | None = None
406
+ self.t4_mul: StepMLP | None = None
407
+ self.t4_red: StepMLP | None = None
408
+ self.t5_mul: StepMLP | None = None
409
+ self.t5_red: StepMLP | None = None
410
+
411
+ def load(self, model_dir: str) -> None:
412
+ from safetensors.torch import load_file
413
+
414
+ torch.manual_seed(0)
415
+ model_dir = Path(model_dir)
416
+ config = json.loads((model_dir / "config.json").read_text())
417
+
418
+ tensors = load_file(str(model_dir / "weights.safetensors"))
419
+ if "small" in config:
420
+ net = SmallResidueNet(**config["small"])
421
+ state = {k[len("small."):]: v for k, v in tensors.items() if k.startswith("small.")}
422
+ net.load_state_dict(state, strict=True)
423
+ net.eval()
424
+ self.small = net
425
+
426
+ if "t3" in config:
427
+ w, d = config["t3"]["width"], config["t3"]["depth"]
428
+ mul = StepMLP(T3_MUL_IN, T3_MUL_CARB, w, d)
429
+ red = StepMLP(T3_RED_IN, T3_RED_OUT, w, d)
430
+ mul.load_state_dict(load_file(str(model_dir / "t3_mul.safetensors")), strict=True)
431
+ red.load_state_dict(load_file(str(model_dir / "t3_red.safetensors")), strict=True)
432
+ mul.eval(); red.eval()
433
+ self.t3_mul, self.t3_red = mul, red
434
+
435
+ if "t4" in config:
436
+ mw, rw, d = config["t4"]["mul_width"], config["t4"]["red_width"], config["t4"]["depth"]
437
+ mul = StepMLP(T4_MUL_IN, T4_MUL_CARB, mw, d)
438
+ red = StepMLP(T4_RED_IN, T4_RED_OUT, rw, d)
439
+ mul.load_state_dict(load_file(str(model_dir / "t4_mul.safetensors")), strict=True)
440
+ red.load_state_dict(load_file(str(model_dir / "t4_red.safetensors")), strict=True)
441
+ mul.eval(); red.eval()
442
+ self.t4_mul, self.t4_red = mul, red
443
+
444
+ if "t5" in config:
445
+ mw, rw, d = config["t5"]["mul_width"], config["t5"]["red_width"], config["t5"]["depth"]
446
+ mul = StepMLP(T5_MUL_IN, T5_MUL_CARB, mw, d)
447
+ red = StepMLP(T5_RED_IN, T5_RED_OUT, rw, d)
448
+ mul.load_state_dict(load_file(str(model_dir / "t5_mul.safetensors")), strict=True)
449
+ red.load_state_dict(load_file(str(model_dir / "t5_red.safetensors")), strict=True)
450
+ mul.eval(); red.eval()
451
+ self.t5_mul, self.t5_red = mul, red
452
+
453
+ def preprocess_a(self, a):
454
+ return a
455
+
456
+ def preprocess_b(self, b):
457
+ return b
458
+
459
+ def preprocess_p(self, p):
460
+ return p
461
+
462
+ @torch.no_grad()
463
+ def predict_digits(self, a_enc, b_enc, p_enc):
464
+ return self.predict_digits_batch([(a_enc, b_enc, p_enc)])[0]
465
+
466
+ @torch.no_grad()
467
+ def predict_digits_batch(self, inputs):
468
+ out: list[list[int] | None] = [None] * len(inputs)
469
+ s_x, s_y, s_p, s_idx = [], [], [], [] # tier 1-2
470
+ t3_x, t3_y, t3_p, t3_idx = [], [], [], [] # tier 3
471
+ t4_x, t4_y, t4_p, t4_idx = [], [], [], [] # tier 4
472
+ t5_x, t5_y, t5_p, t5_idx = [], [], [], [] # tier 5
473
+
474
+ for i, (a_enc, b_enc, p_enc) in enumerate(inputs):
475
+ try:
476
+ p = int(p_enc)
477
+ except (ValueError, TypeError):
478
+ out[i] = [0]
479
+ continue
480
+ # Operand normalization: a with p, then b with p (never all three).
481
+ try:
482
+ xr = int(a_enc) % p
483
+ yr = int(b_enc) % p
484
+ except (ValueError, TypeError):
485
+ out[i] = [0]
486
+ continue
487
+
488
+ if self.small is not None and 2 <= p <= MAX_P and int(self.small.prime_lookup[p]) >= 0:
489
+ s_x.append(xr); s_y.append(yr); s_p.append(p); s_idx.append(i)
490
+ elif self.t3_mul is not None and T3_MIN_P <= p <= T3_MAX_P:
491
+ t3_x.append(xr); t3_y.append(yr); t3_p.append(p); t3_idx.append(i)
492
+ elif self.t4_mul is not None and T4_MIN_P <= p <= T4_MAX_P:
493
+ t4_x.append(xr); t4_y.append(yr); t4_p.append(p); t4_idx.append(i)
494
+ elif self.t5_mul is not None and T5_MIN_P <= p <= T5_MAX_P:
495
+ t5_x.append(xr); t5_y.append(yr); t5_p.append(p); t5_idx.append(i)
496
+ else:
497
+ out[i] = [0] # outside the trained regime -> honest fallback
498
+
499
+ if s_idx:
500
+ preds = self.small.predict(
501
+ torch.tensor(s_x, dtype=torch.long),
502
+ torch.tensor(s_y, dtype=torch.long),
503
+ torch.tensor(s_p, dtype=torch.long),
504
+ ).tolist()
505
+ for j, i in enumerate(s_idx):
506
+ out[i] = [int(preds[j])]
507
+
508
+ if t3_idx:
509
+ xb = _bits(torch.tensor(t3_x, dtype=torch.long), T3_MUL_OPB)
510
+ yb = _bits(torch.tensor(t3_y, dtype=torch.long), T3_MUL_OPB)
511
+ p_t = torch.tensor(t3_p, dtype=torch.long)
512
+ tb = _composed_product_bits(self.t3_mul, xb, yb, T3_MUL_OPB, T3_MUL_COLS,
513
+ T3_MUL_PRB, T3_MUL_CARB)
514
+ r = _composed_reduce_int(self.t3_red, tb, p_t, T3_NIB, T3_RED_NIBBLES, T3_T_BITS)
515
+ for j, i in enumerate(t3_idx):
516
+ out[i] = _digits_msb(int(r[j].item()))
517
+
518
+ if t4_idx:
519
+ xb = _bits(torch.tensor(t4_x, dtype=torch.long), T4_MUL_OPB)
520
+ yb = _bits(torch.tensor(t4_y, dtype=torch.long), T4_MUL_OPB)
521
+ p_t = torch.tensor(t4_p, dtype=torch.long)
522
+ tb = _composed_product_bits(self.t4_mul, xb, yb, T4_MUL_OPB, T4_MUL_COLS,
523
+ T4_MUL_PRB, T4_MUL_CARB)
524
+ r = _composed_reduce_int(self.t4_red, tb, p_t, T4_NIB, T4_RED_NIBBLES, T4_T_BITS)
525
+ for j, i in enumerate(t4_idx):
526
+ out[i] = _digits_msb(int(r[j].item()))
527
+
528
+ if t5_idx:
529
+ # 64-bit residues overflow signed int64: carry x, y, p as bit tensors.
530
+ xb = _int_bits(t5_x, T5_MUL_OPB)
531
+ yb = _int_bits(t5_y, T5_MUL_OPB)
532
+ pb = _int_bits(t5_p, T5_RED_NIBBLES * T5_NIB)
533
+ tb = _composed_product_bits(self.t5_mul, xb, yb, T5_MUL_OPB, T5_MUL_COLS,
534
+ T5_MUL_PRB, T5_MUL_CARB)
535
+ r_bits = _composed_reduce_bits(self.t5_red, tb, pb, T5_NIB, T5_RED_NIBBLES, T5_T_BITS)
536
+ r_vals = _bits_to_ints(r_bits[:, :T5_MUL_OPB])
537
+ for j, i in enumerate(t5_idx):
538
+ out[i] = _digits_msb(r_vals[j])
539
+
540
+ return [o if o is not None else [0] for o in out]
541
+
542
+ def max_batch_size(self) -> int:
543
+ return 512
544
+
545
+
546
+ def _digits_msb(v: int) -> list[int]:
547
+ """Base-256 digits, MSB-first; at least one digit."""
548
+ if v == 0:
549
+ return [0]
550
+ ds = []
551
+ while v > 0:
552
+ ds.append(v & 0xFF)
553
+ v >>= 8
554
+ return ds[::-1]
t3_collapse_receipt.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mode": "fp",
3
+ "params": {
4
+ "mul": 19972,
5
+ "red": 20069
6
+ },
7
+ "train_log": {
8
+ "mul": {
9
+ "final_loss": 8.088716502152593e-11,
10
+ "wall_s": 112.9
11
+ },
12
+ "red": {
13
+ "final_loss": 3.2543610029023284e-09,
14
+ "wall_s": 187.7
15
+ }
16
+ },
17
+ "coverage": {
18
+ "mul_cases": 100,
19
+ "mul_total": 272,
20
+ "red_cases": 512,
21
+ "red_total": 512
22
+ },
23
+ "gate_primes": [
24
+ 33343,
25
+ 45137,
26
+ 54497,
27
+ 55061,
28
+ 62071
29
+ ],
30
+ "per_prime_exact": [
31
+ 1.0,
32
+ 1.0,
33
+ 1.0,
34
+ 1.0,
35
+ 1.0
36
+ ],
37
+ "worst_fresh_exact": 1.0,
38
+ "overall_exact": 1.0,
39
+ "tier3_cleared": true,
40
+ "collapse_mean": 0.0,
41
+ "trained_mul_random_red_mean": 0.0021956087555736305,
42
+ "arch": {
43
+ "width": 96,
44
+ "depth": 3,
45
+ "max_t": 15
46
+ }
47
+ }
t3_mul.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5de58291027b8faf42043252059dc2bc43fac6d380e5259d86056479a1d9d6e3
3
+ size 80480
t3_red.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:90c7b11ea7d8045dc6b02e45df0573e0e1a8422a3c5ce33249bcc31de0cb404b
3
+ size 80868
t4_collapse_receipt.json ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mode": "fp",
3
+ "params": {
4
+ "mul": 35205,
5
+ "red": 20069
6
+ },
7
+ "arch": {
8
+ "mul_width": 128,
9
+ "red_width": 96,
10
+ "depth": 3,
11
+ "max_t": 15
12
+ },
13
+ "train_log": {
14
+ "mul": {
15
+ "final_loss": 4.0139194190658145e-09,
16
+ "wall_s": 67.6
17
+ },
18
+ "red": {
19
+ "final_loss": 5.262944746675657e-09,
20
+ "wall_s": 95.5
21
+ }
22
+ },
23
+ "coverage": {
24
+ "mul_cases": 1056,
25
+ "mul_total": 1056,
26
+ "red_cases": 512,
27
+ "red_total": 512,
28
+ "mul_realizable_per_case_exact": 1.0,
29
+ "red_per_case_exact": 1.0
30
+ },
31
+ "techniques": {
32
+ "reciprocal_operand_framing": true,
33
+ "charton_kempe_two_set": {
34
+ "repeat_triples": 150,
35
+ "repeat_mult": 2,
36
+ "fresh_triples": 1200
37
+ }
38
+ },
39
+ "gate_primes": [
40
+ 267619873,
41
+ 631325533,
42
+ 1806472277,
43
+ 3134734633,
44
+ 3874904347
45
+ ],
46
+ "per_prime_exact": [
47
+ 1.0,
48
+ 1.0,
49
+ 1.0,
50
+ 1.0,
51
+ 1.0
52
+ ],
53
+ "worst_fresh_exact": 1.0,
54
+ "overall_exact": 1.0,
55
+ "tier4_cleared_90": true,
56
+ "tier4_cleared_99": true,
57
+ "collapse_mean": 0.0,
58
+ "trained_mul_random_red_mean": 0.0
59
+ }
t4_mul.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ffcc202cd451944764bd2ad0bc9cca68b45fd0e4139834c7f4f336cb64e6eccf
3
+ size 141428
t4_red.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:05c29eaaaa5ea0aa35b7267ff728299f30ec0a0f222225d08e85911af0d44344
3
+ size 80868
t5_collapse_receipt.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mode": "fp",
3
+ "params": {
4
+ "mul": 54726,
5
+ "red": 20069
6
+ },
7
+ "arch": {
8
+ "mul_width": 160,
9
+ "red_width": 96,
10
+ "depth": 3,
11
+ "max_t": 15
12
+ },
13
+ "train_log": {
14
+ "mul": {
15
+ "final_loss": 2.276625776787e-09,
16
+ "wall_s": 122.4
17
+ },
18
+ "red": {
19
+ "final_loss": 3.874374598922259e-09,
20
+ "wall_s": 207.6
21
+ }
22
+ },
23
+ "coverage": {
24
+ "mul_cases": 4160,
25
+ "mul_total": 4160,
26
+ "red_cases": 512,
27
+ "red_total": 512,
28
+ "mul_realizable_per_case_exact": 1.0,
29
+ "mul_all_enum_per_case_exact": 1.0,
30
+ "red_per_case_exact": 1.0
31
+ },
32
+ "techniques": {
33
+ "reciprocal_operand_framing": true,
34
+ "charton_kempe_two_set": {
35
+ "repeat_triples": 120,
36
+ "repeat_mult": 2,
37
+ "fresh_triples": 900
38
+ }
39
+ },
40
+ "gate_primes": [
41
+ 1690313788893089131,
42
+ 6145258606915434311,
43
+ 8963783833428354709,
44
+ 11534118763423864511,
45
+ 14481575096435149429
46
+ ],
47
+ "per_prime_exact": [
48
+ 1.0,
49
+ 1.0,
50
+ 1.0,
51
+ 1.0,
52
+ 1.0
53
+ ],
54
+ "worst_fresh_exact": 1.0,
55
+ "overall_exact": 1.0,
56
+ "tier5_cleared_90": true,
57
+ "tier5_cleared_99": true,
58
+ "collapse_mean": 0.0,
59
+ "trained_mul_random_red_mean": 0.0
60
+ }
t5_mul.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dbd154fbbe57feb535118236f9a947fada303ee09b62e85904134db8d0797f3d
3
+ size 219520
t5_red.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eb869bfc834a96b8f64dec94753ee9c8163904af496d84ff2f34483e68b94210
3
+ size 80868
weights.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4784c4f82356d120151513ae41da5cd8c53f33be9c01e5cc23f828485ad4ffc6
3
+ size 11509360