appvoid commited on
Commit
bafa3ba
·
verified ·
1 Parent(s): 00a9e5e

unified-v1-blt-none-s42: state and inference synchronized at checkpoint-000000000000

Browse files
Files changed (34) hide show
  1. experiments.csv +1 -0
  2. unified/ablations/unified-v1-blt-none-s42/checkpoint-000000000000/COMPLETE +1 -0
  3. unified/ablations/unified-v1-blt-none-s42/checkpoint-000000000000/metadata.json +9 -0
  4. unified/ablations/unified-v1-blt-none-s42/checkpoint-000000000000/training.pt +3 -0
  5. unified/ablations/unified-v1-blt-none-s42/inference/data_progress.json +47 -0
  6. unified/ablations/unified-v1-blt-none-s42/inference/experiment_config.json +97 -0
  7. unified/ablations/unified-v1-blt-none-s42/inference/model.safetensors +3 -0
  8. unified/ablations/unified-v1-blt-none-s42/source/__init__.py +1 -0
  9. unified/ablations/unified-v1-blt-none-s42/source/audio_codec.py +101 -0
  10. unified/ablations/unified-v1-blt-none-s42/source/bet_model.py +488 -0
  11. unified/ablations/unified-v1-blt-none-s42/source/braille_encoder.py +1532 -0
  12. unified/ablations/unified-v1-blt-none-s42/source/canvas_flow.py +1025 -0
  13. unified/ablations/unified-v1-blt-none-s42/source/causal_arch.py +747 -0
  14. unified/ablations/unified-v1-blt-none-s42/source/checkpoints.py +190 -0
  15. unified/ablations/unified-v1-blt-none-s42/source/configuration_bet.py +64 -0
  16. unified/ablations/unified-v1-blt-none-s42/source/cortex_curriculum_v16.py +0 -0
  17. unified/ablations/unified-v1-blt-none-s42/source/cortex_data.py +132 -0
  18. unified/ablations/unified-v1-blt-none-s42/source/cortex_oracles.py +250 -0
  19. unified/ablations/unified-v1-blt-none-s42/source/cortex_source.py +83 -0
  20. unified/ablations/unified-v1-blt-none-s42/source/cortex_tasks.py +521 -0
  21. unified/ablations/unified-v1-blt-none-s42/source/data_stream.py +193 -0
  22. unified/ablations/unified-v1-blt-none-s42/source/dataset_registry.py +46 -0
  23. unified/ablations/unified-v1-blt-none-s42/source/inference.py +43 -0
  24. unified/ablations/unified-v1-blt-none-s42/source/media_data.py +88 -0
  25. unified/ablations/unified-v1-blt-none-s42/source/modeling_bet.py +76 -0
  26. unified/ablations/unified-v1-blt-none-s42/source/parquet_stream.py +37 -0
  27. unified/ablations/unified-v1-blt-none-s42/source/prepared_data.py +445 -0
  28. unified/ablations/unified-v1-blt-none-s42/source/records.py +37 -0
  29. unified/ablations/unified-v1-blt-none-s42/source/tokenization_bet.py +137 -0
  30. unified/ablations/unified-v1-blt-none-s42/source/trainer.py +481 -0
  31. unified/ablations/unified-v1-blt-none-s42/source/unified_data.py +157 -0
  32. unified/ablations/unified-v1-blt-none-s42/source/unified_models.py +91 -0
  33. unified/ablations/unified-v1-blt-none-s42/source/unified_runner.py +330 -0
  34. unified/ablations/unified-v1-blt-none-s42/source/unified_state.py +153 -0
experiments.csv CHANGED
@@ -14,3 +14,4 @@ unified-v1-evabyte-none-s42,ablation,causal,evabyte,none,42,25,0,running,fresh_m
14
  unified-v1-evabyte-none-s42,ablation,causal,evabyte,none,42,50,0,running,fresh_matched_seed,3644,3644,1.026456674999963,0.0,10668800,1024,3.0752868056297302,6.275834560394287,2420984,20.154760339000404,120119.71163533424,0.12007386237382889,0,bf16,0,269ba207e744573779747b593ffbcd8a87683fd55c83f6b168c658206d9aeb98,af33c522ea4349d5aae0491f7fe085d4bea1b044b08ff57eb064354bbb96435f,"{""climbmix"": {""documents_emitted"": 530, ""epoch"": 0, ""prepared_position"": 1664, ""rows_read"": 530, ""shard"": 0, ""shards"": 6543}, ""rewrite6"": {""documents_emitted"": 214, ""epoch"": 0, ""prepared_position"": 273, ""rows_read"": 215, ""shard"": 0, ""shards"": 1}, ""ultra_qa"": {""documents_emitted"": 109, ""epoch"": 0, ""prepared_position"": 475, ""rows_read"": 110, ""shard"": 0, ""shards"": 616}, ""ultra_style"": {""documents_emitted"": 136, ""epoch"": 0, ""prepared_position"": 481, ""rows_read"": 137, ""shard"": 0, ""shards"": 552}}"
15
  unified-v1-evabyte-none-s42,ablation,causal,evabyte,none,42,75,0,running,fresh_matched_seed,5450,5450,1.026456674999963,0.0,10668800,1024,2.74536669254303,6.275834560394287,3637662,29.178130834000626,124670.83723406687,0.1350708305835724,0,bf16,0,269ba207e744573779747b593ffbcd8a87683fd55c83f6b168c658206d9aeb98,af33c522ea4349d5aae0491f7fe085d4bea1b044b08ff57eb064354bbb96435f,"{""climbmix"": {""documents_emitted"": 750, ""epoch"": 0, ""prepared_position"": 2482, ""rows_read"": 751, ""shard"": 0, ""shards"": 6543}, ""rewrite6"": {""documents_emitted"": 307, ""epoch"": 0, ""prepared_position"": 405, ""rows_read"": 308, ""shard"": 0, ""shards"": 1}, ""ultra_qa"": {""documents_emitted"": 163, ""epoch"": 0, ""prepared_position"": 712, ""rows_read"": 164, ""shard"": 0, ""shards"": 616}, ""ultra_style"": {""documents_emitted"": 204, ""epoch"": 0, ""prepared_position"": 720, ""rows_read"": 205, ""shard"": 0, ""shards"": 552}}"
16
  unified-v1-evabyte-none-s42,ablation,causal,evabyte,none,42,100,0,complete,fresh_matched_seed,7230,7230,1.026456674999963,0.0,10668800,1024,2.70014488697052,5.0743002961663635,4841990,38.114180579000845,127039.06856829327,0.1096256747841835,0,bf16,0,269ba207e744573779747b593ffbcd8a87683fd55c83f6b168c658206d9aeb98,af33c522ea4349d5aae0491f7fe085d4bea1b044b08ff57eb064354bbb96435f,"{""climbmix"": {""documents_emitted"": 989, ""epoch"": 0, ""prepared_position"": 3299, ""rows_read"": 990, ""shard"": 0, ""shards"": 6543}, ""rewrite6"": {""documents_emitted"": 389, ""epoch"": 0, ""prepared_position"": 522, ""rows_read"": 390, ""shard"": 0, ""shards"": 1}, ""ultra_qa"": {""documents_emitted"": 217, ""epoch"": 0, ""prepared_position"": 950, ""rows_read"": 218, ""shard"": 0, ""shards"": 616}, ""ultra_style"": {""documents_emitted"": 273, ""epoch"": 0, ""prepared_position"": 959, ""rows_read"": 274, ""shard"": 0, ""shards"": 552}}"
 
 
14
  unified-v1-evabyte-none-s42,ablation,causal,evabyte,none,42,50,0,running,fresh_matched_seed,3644,3644,1.026456674999963,0.0,10668800,1024,3.0752868056297302,6.275834560394287,2420984,20.154760339000404,120119.71163533424,0.12007386237382889,0,bf16,0,269ba207e744573779747b593ffbcd8a87683fd55c83f6b168c658206d9aeb98,af33c522ea4349d5aae0491f7fe085d4bea1b044b08ff57eb064354bbb96435f,"{""climbmix"": {""documents_emitted"": 530, ""epoch"": 0, ""prepared_position"": 1664, ""rows_read"": 530, ""shard"": 0, ""shards"": 6543}, ""rewrite6"": {""documents_emitted"": 214, ""epoch"": 0, ""prepared_position"": 273, ""rows_read"": 215, ""shard"": 0, ""shards"": 1}, ""ultra_qa"": {""documents_emitted"": 109, ""epoch"": 0, ""prepared_position"": 475, ""rows_read"": 110, ""shard"": 0, ""shards"": 616}, ""ultra_style"": {""documents_emitted"": 136, ""epoch"": 0, ""prepared_position"": 481, ""rows_read"": 137, ""shard"": 0, ""shards"": 552}}"
15
  unified-v1-evabyte-none-s42,ablation,causal,evabyte,none,42,75,0,running,fresh_matched_seed,5450,5450,1.026456674999963,0.0,10668800,1024,2.74536669254303,6.275834560394287,3637662,29.178130834000626,124670.83723406687,0.1350708305835724,0,bf16,0,269ba207e744573779747b593ffbcd8a87683fd55c83f6b168c658206d9aeb98,af33c522ea4349d5aae0491f7fe085d4bea1b044b08ff57eb064354bbb96435f,"{""climbmix"": {""documents_emitted"": 750, ""epoch"": 0, ""prepared_position"": 2482, ""rows_read"": 751, ""shard"": 0, ""shards"": 6543}, ""rewrite6"": {""documents_emitted"": 307, ""epoch"": 0, ""prepared_position"": 405, ""rows_read"": 308, ""shard"": 0, ""shards"": 1}, ""ultra_qa"": {""documents_emitted"": 163, ""epoch"": 0, ""prepared_position"": 712, ""rows_read"": 164, ""shard"": 0, ""shards"": 616}, ""ultra_style"": {""documents_emitted"": 204, ""epoch"": 0, ""prepared_position"": 720, ""rows_read"": 205, ""shard"": 0, ""shards"": 552}}"
16
  unified-v1-evabyte-none-s42,ablation,causal,evabyte,none,42,100,0,complete,fresh_matched_seed,7230,7230,1.026456674999963,0.0,10668800,1024,2.70014488697052,5.0743002961663635,4841990,38.114180579000845,127039.06856829327,0.1096256747841835,0,bf16,0,269ba207e744573779747b593ffbcd8a87683fd55c83f6b168c658206d9aeb98,af33c522ea4349d5aae0491f7fe085d4bea1b044b08ff57eb064354bbb96435f,"{""climbmix"": {""documents_emitted"": 989, ""epoch"": 0, ""prepared_position"": 3299, ""rows_read"": 990, ""shard"": 0, ""shards"": 6543}, ""rewrite6"": {""documents_emitted"": 389, ""epoch"": 0, ""prepared_position"": 522, ""rows_read"": 390, ""shard"": 0, ""shards"": 1}, ""ultra_qa"": {""documents_emitted"": 217, ""epoch"": 0, ""prepared_position"": 950, ""rows_read"": 218, ""shard"": 0, ""shards"": 616}, ""ultra_style"": {""documents_emitted"": 273, ""epoch"": 0, ""prepared_position"": 959, ""rows_read"": 274, ""shard"": 0, ""shards"": 552}}"
17
+ unified-v1-blt-none-s42,ablation,causal,blt,none,42,0,0,initialized,fresh_matched_seed,0,0,0.8857087290000436,19.13628118099996,10425600,1024,,,0,0.0,0.0,,0,,100,269ba207e744573779747b593ffbcd8a87683fd55c83f6b168c658206d9aeb98,af33c522ea4349d5aae0491f7fe085d4bea1b044b08ff57eb064354bbb96435f,"{""climbmix"": {""documents_emitted"": 0, ""epoch"": 0, ""prepared_position"": null, ""rows_read"": 0, ""shard"": 0, ""shards"": 6543}, ""rewrite6"": {""documents_emitted"": 0, ""epoch"": 0, ""prepared_position"": null, ""rows_read"": 0, ""shard"": 0, ""shards"": 1}, ""ultra_qa"": {""documents_emitted"": 0, ""epoch"": 0, ""prepared_position"": null, ""rows_read"": 0, ""shard"": 0, ""shards"": 616}, ""ultra_style"": {""documents_emitted"": 0, ""epoch"": 0, ""prepared_position"": null, ""rows_read"": 0, ""shard"": 0, ""shards"": 552}}"
unified/ablations/unified-v1-blt-none-s42/checkpoint-000000000000/COMPLETE ADDED
@@ -0,0 +1 @@
 
 
1
+ complete
unified/ablations/unified-v1-blt-none-s42/checkpoint-000000000000/metadata.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "cortex-unified-v1",
3
+ "step": 0,
4
+ "lineage": "08a9fd42eadb43a3861b2c914ee6cbab",
5
+ "signature": "20afdd7a8d6a2f194a6a1772bd00d11fb1c26476d78881d3364c0bdd8421d331",
6
+ "sha256": "825adbb7a6662705a98219b3cc73321d13d64fe83c7f6c0d09283ec5bceec749",
7
+ "bytes": 42090107,
8
+ "saved_at": 1790265642.8075945
9
+ }
unified/ablations/unified-v1-blt-none-s42/checkpoint-000000000000/training.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:825adbb7a6662705a98219b3cc73321d13d64fe83c7f6c0d09283ec5bceec749
3
+ size 42090107
unified/ablations/unified-v1-blt-none-s42/inference/data_progress.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "step": 0,
3
+ "prepared_dataset": {
4
+ "repo": "appvoid/rewrite7",
5
+ "revision": "5b26a55d9021b2ab1e3e3ea35e4f0aba8bd5a930",
6
+ "recipe": "794a6de09517a367ca643fda6b9a2a511c975f0a62cb0aa3fbdd0321a6a34e80"
7
+ },
8
+ "observations": {
9
+ "presentations": 0,
10
+ "unique_samples": 0,
11
+ "ranges": {}
12
+ },
13
+ "coverage": {
14
+ "climbmix": {
15
+ "epoch": 0,
16
+ "shard": 0,
17
+ "shards": 6543,
18
+ "rows_read": 0,
19
+ "prepared_position": null,
20
+ "documents_emitted": 0
21
+ },
22
+ "rewrite6": {
23
+ "epoch": 0,
24
+ "shard": 0,
25
+ "shards": 1,
26
+ "rows_read": 0,
27
+ "prepared_position": null,
28
+ "documents_emitted": 0
29
+ },
30
+ "ultra_style": {
31
+ "epoch": 0,
32
+ "shard": 0,
33
+ "shards": 552,
34
+ "rows_read": 0,
35
+ "prepared_position": null,
36
+ "documents_emitted": 0
37
+ },
38
+ "ultra_qa": {
39
+ "epoch": 0,
40
+ "shard": 0,
41
+ "shards": 616,
42
+ "rows_read": 0,
43
+ "prepared_position": null,
44
+ "documents_emitted": 0
45
+ }
46
+ }
47
+ }
unified/ablations/unified-v1-blt-none-s42/inference/experiment_config.json ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "variant": "blt",
3
+ "config": {
4
+ "dim": 320,
5
+ "ff": 864,
6
+ "heads": 5,
7
+ "context": 1024,
8
+ "body_layers": 6,
9
+ "loops": 1,
10
+ "local_dim": 128,
11
+ "local_ff": 384,
12
+ "local_heads": 2,
13
+ "encoder_layers": 1,
14
+ "decoder_layers": 1,
15
+ "local_window": 128,
16
+ "query_block": 128,
17
+ "eva_window": 256,
18
+ "eva_chunk": 16,
19
+ "eva_heads": 8,
20
+ "hash_buckets": 1024,
21
+ "hash_sizes": [
22
+ 3,
23
+ 4,
24
+ 5,
25
+ 6,
26
+ 7,
27
+ 8
28
+ ],
29
+ "cross_slots": 2,
30
+ "entropy_dim": 64,
31
+ "entropy_ff": 192,
32
+ "entropy_heads": 1,
33
+ "entropy_layers": 2,
34
+ "patch_size": 4.0,
35
+ "byteflow_bins": 16,
36
+ "byteflow_eps2": 1.0,
37
+ "byteflow_score": "l2",
38
+ "router_window": 64,
39
+ "grad_checkpoint": false
40
+ },
41
+ "arch": "blt",
42
+ "total_params": 10425600,
43
+ "trainable_params": 10285632,
44
+ "target_params": 10826304,
45
+ "depth": 7,
46
+ "frozen_params": 139968,
47
+ "param_delta_pct": -3.701207725184883,
48
+ "causal_ranking_eligible": true,
49
+ "fidelity": "scaled architecture",
50
+ "data_progress": {
51
+ "step": 0,
52
+ "prepared_dataset": {
53
+ "repo": "appvoid/rewrite7",
54
+ "revision": "5b26a55d9021b2ab1e3e3ea35e4f0aba8bd5a930",
55
+ "recipe": "794a6de09517a367ca643fda6b9a2a511c975f0a62cb0aa3fbdd0321a6a34e80"
56
+ },
57
+ "observations": {
58
+ "presentations": 0,
59
+ "unique_samples": 0,
60
+ "ranges": {}
61
+ },
62
+ "coverage": {
63
+ "climbmix": {
64
+ "epoch": 0,
65
+ "shard": 0,
66
+ "shards": 6543,
67
+ "rows_read": 0,
68
+ "prepared_position": null,
69
+ "documents_emitted": 0
70
+ },
71
+ "rewrite6": {
72
+ "epoch": 0,
73
+ "shard": 0,
74
+ "shards": 1,
75
+ "rows_read": 0,
76
+ "prepared_position": null,
77
+ "documents_emitted": 0
78
+ },
79
+ "ultra_style": {
80
+ "epoch": 0,
81
+ "shard": 0,
82
+ "shards": 552,
83
+ "rows_read": 0,
84
+ "prepared_position": null,
85
+ "documents_emitted": 0
86
+ },
87
+ "ultra_qa": {
88
+ "epoch": 0,
89
+ "shard": 0,
90
+ "shards": 616,
91
+ "rows_read": 0,
92
+ "prepared_position": null,
93
+ "documents_emitted": 0
94
+ }
95
+ }
96
+ }
97
+ }
unified/ablations/unified-v1-blt-none-s42/inference/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:df2b648db355f91327dbe8e2c259e618ff4861a1b186e89909fd2de52a2bab5e
3
+ size 41714820
unified/ablations/unified-v1-blt-none-s42/source/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Self-contained SparkBET-9M training runtime and Hugging Face export helpers."""
unified/ablations/unified-v1-blt-none-s42/source/audio_codec.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Phase-preserving spectral Braille codec, version 1.
2
+
3
+ Each quantized spectral byte is one Braille cell (its eight dots are eight bits).
4
+ Two binary raster planes, both <=80x80: 8-bit log magnitude and 8-bit phase.
5
+ This is a lossy, deterministic codec, not a neural audio tokenizer.
6
+ """
7
+ import io,json,math
8
+ import numpy as np
9
+ from scipy import signal
10
+ from PIL import Image
11
+ from braille_encoder import binary_to_braille,local_contrast,floyd_steinberg
12
+ RATE=16000
13
+ NFFT=128
14
+ HOP=64
15
+ CHUNK=384 # 24 ms; 65 bins x 7 frames per plane fits in 80x48 pixels.
16
+ DB=80.0
17
+
18
+ def canonical_audio(value):
19
+ if isinstance(value,dict) and 'array' in value:
20
+ x=np.asarray(value['array'],dtype=np.float32);sr=int(value['sampling_rate'])
21
+ else:
22
+ import soundfile as sf
23
+ raw=value.get('bytes') if isinstance(value,dict) else None
24
+ path=value.get('path') if isinstance(value,dict) else value
25
+ x,sr=sf.read(io.BytesIO(raw) if raw is not None else path,dtype='float32',always_2d=False)
26
+ if x.ndim==2:x=x.mean(axis=1)
27
+ if x.ndim!=1 or not len(x) or sr<=0 or not np.isfinite(x).all():raise ValueError('Invalid waveform')
28
+ if len(x)>sr*120:raise ValueError('Audio exceeds 120-second decoding bound')
29
+ if sr!=RATE:
30
+ g=math.gcd(sr,RATE);x=signal.resample_poly(x,RATE//g,sr//g).astype(np.float32)
31
+ return np.clip(x,-1,1).astype(np.float32)
32
+
33
+ def byte_plane(values,width=40):
34
+ raw=np.asarray(values,dtype=np.uint8).reshape(-1)
35
+ rows=(len(raw)+width-1)//width
36
+ if rows*4>80 or width*2>80:raise ValueError('Spectral plane exceeds 80x80')
37
+ padded=np.pad(raw,(0,rows*width-len(raw)))
38
+ return '\n'.join(''.join(chr(0x2800+int(v)) for v in row) for row in padded.reshape(rows,width))
39
+
40
+ def read_plane(text,count):
41
+ lines=text.splitlines()
42
+ if not lines or len(lines)>20 or any(len(r)!=40 for r in lines):raise ValueError('Malformed Braille plane shape')
43
+ raw=[ord(c)-0x2800 for row in lines for c in row]
44
+ if any(v<0 or v>255 for v in raw) or len(raw)!=math.ceil(count/40)*40:raise ValueError('Malformed Braille payload')
45
+ if any(raw[count:]):raise ValueError('Nonzero spectral padding')
46
+ return np.array(raw[:count],dtype=np.uint8)
47
+
48
+ def encode_tile(wave):
49
+ x=np.asarray(wave,dtype=np.float32)
50
+ if x.ndim!=1 or not 1<=len(x)<=CHUNK or not np.isfinite(x).all():raise ValueError('Invalid tile')
51
+ n=len(x);padded=np.pad(x,(0,CHUNK-n))
52
+ _,_,z=signal.stft(padded,fs=RATE,window='hann',nperseg=NFFT,noverlap=NFFT-HOP,
53
+ nfft=NFFT,boundary='zeros',padded=True)
54
+ mag=np.abs(z);scale=max(float(mag.max()),1e-12)
55
+ # Code 0 is exact silence; 1..255 represent [-80,0] dB relative to scale.
56
+ db=20*np.log10(np.maximum(mag/scale,1e-12))
57
+ qm=np.where(db < -DB,0,1+np.rint((np.clip(db,-DB,0)+DB)*254/DB)).astype(np.uint8)
58
+ qp=(np.rint((np.angle(z)+np.pi)*(256/(2*np.pi))).astype(np.int32)%256).astype(np.uint8)
59
+ qp[qm==0]=128
60
+ meta=dict(v=1,sr=RATE,n=n,fft=NFFT,hop=HOP,frames=z.shape[1],scale=scale,db=DB)
61
+ return '<audio_tile>'+json.dumps(meta,separators=(',',':'))+'\nMAG\n'+byte_plane(qm)+'\nPHASE\n'+byte_plane(qp)+'\n</audio_tile>'
62
+
63
+ def decode_tile(text):
64
+ if not text.startswith('<audio_tile>') or not text.endswith('\n</audio_tile>'):raise ValueError('Incomplete audio tile')
65
+ header,planes=text[len('<audio_tile>'):].split('\nMAG\n',1)
66
+ meta=json.loads(header)
67
+ if (meta['v'],meta['sr'],meta['fft'],meta['hop'],meta['frames'],meta['db'])!=(1,RATE,NFFT,HOP,7,DB):raise ValueError('Unsupported codec parameters')
68
+ if not 1<=meta['n']<=CHUNK or not 0<float(meta['scale'])<=2:raise ValueError('Invalid audio metadata')
69
+ magtxt,phasetxt=planes[:-len('\n</audio_tile>')].split('\nPHASE\n')
70
+ shape=(NFFT//2+1,meta['frames']);count=math.prod(shape)
71
+ qm=read_plane(magtxt,count).reshape(shape).astype(np.float64)
72
+ qp=read_plane(phasetxt,count).reshape(shape).astype(np.float64)
73
+ mag=np.where(qm==0,0,float(meta['scale'])*10**(((qm-1)*DB/254-DB)/20))
74
+ phase=qp*(2*np.pi/256)-np.pi
75
+ z=mag*np.exp(1j*phase)
76
+ # Real-valued signal: DC and Nyquist bins must be real.
77
+ z[0]=z[0].real;z[-1]=z[-1].real
78
+ _,wave=signal.istft(z,fs=RATE,window='hann',nperseg=NFFT,noverlap=NFFT-HOP,nfft=NFFT,boundary=True)
79
+ return np.clip(wave[:meta['n']],-1,1).astype(np.float32),RATE
80
+
81
+ def encode_audio(wave):
82
+ return [encode_tile(wave[i:i+CHUNK]) for i in range(0,len(wave),CHUNK)]
83
+
84
+ def decode_audio(tiles):
85
+ if not tiles:raise ValueError('No tiles')
86
+ return np.concatenate([decode_tile(t)[0] for t in tiles]),RATE
87
+
88
+ def overview(wave,grid=80):
89
+ """Whole-clip log spectrum for semantic tasks; never used as reversible audio."""
90
+ x=np.asarray(wave,dtype=np.float32);x=np.pad(x,(0,max(0,256-len(x))))
91
+ _,_,z=signal.stft(x,fs=RATE,nperseg=256,noverlap=128,boundary='zeros')
92
+ power=20*np.log10(np.maximum(np.abs(z),1e-6))
93
+ hi=float(power.max());image=np.clip((power-(hi-70))/70,0,1)
94
+ # Frequency high at top; time left to right. No saliency crop or phase resizing.
95
+ im=Image.fromarray(np.uint8((1-image[::-1])*255)).resize((grid,grid),Image.Resampling.BOX).convert('RGBA')
96
+ enhanced,alpha=local_contrast(im,amount=0)
97
+ return binary_to_braille(floyd_steinberg(enhanced,alpha),alpha)
98
+
99
+ def snr(reference,decoded):
100
+ x=np.asarray(reference,dtype=float);y=np.asarray(decoded,dtype=float)
101
+ return float(10*np.log10((np.sum(x*x)+1e-20)/(np.sum((x-y)**2)+1e-20)))
unified/ablations/unified-v1-blt-none-s42/source/bet_model.py ADDED
@@ -0,0 +1,488 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SparkBET-9M: min-spark-style looped core with BET phase conditioning."""
2
+ import math, hashlib
3
+ from dataclasses import dataclass, asdict
4
+ import torch
5
+ from torch import nn
6
+ import torch.nn.functional as F
7
+ from torch.utils.checkpoint import checkpoint
8
+
9
+ SEQ_LEN = 1024
10
+ MAX_LOOPS = 8
11
+ PAD_ID, BOS_ID, EOS_ID = 256, 257, 258
12
+ VOCAB_SIZE = 259
13
+ EXPECTED_PARAM_COUNT = 9_353_876
14
+ EXPECTED_ARCH_SHAPE_SHA256 = "c85415fc50d03a23f89ccd870ebf53a3a00c5a61017c13a28f6444fdd71b72b9"
15
+ _USE_GRAD_CHECKPOINTING = False
16
+ _STATE_NOISE_SIGMA = 0.0
17
+
18
+
19
+ def set_gradient_checkpointing(enabled):
20
+ global _USE_GRAD_CHECKPOINTING
21
+ _USE_GRAD_CHECKPOINTING = bool(enabled)
22
+
23
+
24
+ def set_state_noise_sigma(value):
25
+ global _STATE_NOISE_SIGMA
26
+ value = float(value)
27
+ if value < 0:
28
+ raise ValueError("state noise sigma must be >= 0")
29
+ _STATE_NOISE_SIGMA = value
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class BETConfig:
34
+ vocab_size: int = VOCAB_SIZE
35
+ hidden_size: int = 324
36
+ intermediate_size: int = 864
37
+ prelude_layers: int = 1
38
+ body_blocks: int = 6
39
+ coda_layers: int = 1
40
+ num_heads: int = 6
41
+ num_kv_heads: int = 2
42
+ head_dim: int = 54
43
+ lora_rank: int = 16
44
+ hyper_lanes: int = 2
45
+ max_seq_len: int = SEQ_LEN
46
+ max_loops: int = MAX_LOOPS
47
+ rope_theta: float = 10_000.0
48
+ rms_eps: float = 1e-6
49
+ ddl_beta_init: float = 1.0
50
+ ddl_k_eps: float = 1e-2
51
+ ddl_v_sigmoid_scale: float = 4.0
52
+
53
+ @property
54
+ def q_dim(self):
55
+ return self.num_heads * self.head_dim
56
+
57
+ @property
58
+ def kv_dim(self):
59
+ return self.num_kv_heads * self.head_dim
60
+
61
+ @property
62
+ def qkv_dim(self):
63
+ return self.q_dim + 2 * self.kv_dim
64
+
65
+
66
+ CFG = BETConfig()
67
+
68
+
69
+ class RMSNorm(nn.Module):
70
+ def __init__(self, dim, eps=1e-6):
71
+ super().__init__()
72
+ self.weight = nn.Parameter(torch.ones(dim))
73
+ self.eps = eps
74
+
75
+ def forward(self, x):
76
+ dtype = x.dtype
77
+ y = x.float()
78
+ y = y * torch.rsqrt(y.pow(2).mean(-1, keepdim=True) + self.eps)
79
+ return (y * self.weight.float()).to(dtype)
80
+
81
+
82
+ def rope_cos_sin(position_ids, dim, theta, dtype):
83
+ inv = 1.0 / (theta ** (torch.arange(0, dim, 2, device=position_ids.device, dtype=torch.float32) / dim))
84
+ f = position_ids.float().unsqueeze(-1) * inv
85
+ return f.cos().unsqueeze(1).to(dtype), f.sin().unsqueeze(1).to(dtype)
86
+
87
+
88
+ def apply_rope(x, cos, sin):
89
+ # RoPE tables may be prepared before the first autocast linear, when the
90
+ # embedding stream is FP32. Cast them to the projected Q/K dtype here so
91
+ # attention remains FP16 on every CUDA profile instead of being promoted.
92
+ cos, sin = cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
93
+ xe, xo = x[..., 0::2], x[..., 1::2]
94
+ return torch.stack((xe * cos - xo * sin, xe * sin + xo * cos), dim=-1).flatten(-2)
95
+
96
+
97
+ def attention_mask_and_positions(input_ids, attention_mask=None):
98
+ b, t = input_ids.shape
99
+ if attention_mask is None:
100
+ pos = torch.arange(t, device=input_ids.device).view(1, t).expand(b, t)
101
+ return None, pos
102
+ mask = attention_mask.to(device=input_ids.device, dtype=torch.bool)
103
+ if mask.shape != input_ids.shape:
104
+ raise ValueError(f"attention_mask shape {tuple(mask.shape)} != input_ids {tuple(input_ids.shape)}")
105
+ pos = (mask.long().cumsum(-1) - 1).clamp_min(0)
106
+ if bool(mask.all()):
107
+ return None, pos
108
+ causal = torch.ones((t, t), device=input_ids.device, dtype=torch.bool).tril()[None, None]
109
+ allowed = causal & mask[:, None, None, :]
110
+ return allowed, pos
111
+
112
+
113
+ class Attention(nn.Module):
114
+ """6Q/2KV GQA with per-head QK RMSNorm before RoPE."""
115
+ def __init__(self, c):
116
+ super().__init__()
117
+ if c.hidden_size != c.q_dim:
118
+ raise ValueError("hidden_size must equal num_heads * head_dim")
119
+ if c.num_heads % c.num_kv_heads:
120
+ raise ValueError("num_heads must be divisible by num_kv_heads")
121
+ self.qkv = nn.Linear(c.hidden_size, c.qkv_dim, bias=False)
122
+ self.out = nn.Linear(c.q_dim, c.hidden_size, bias=False)
123
+ self.qn = RMSNorm(c.head_dim, c.rms_eps)
124
+ self.kn = RMSNorm(c.head_dim, c.rms_eps)
125
+ self.nh, self.nkv, self.hd = c.num_heads, c.num_kv_heads, c.head_dim
126
+ self.q_dim, self.kv_dim = c.q_dim, c.kv_dim
127
+
128
+ def forward(self, x, cos, sin, qkv_delta=None, attn_mask=None):
129
+ b, t, _ = x.shape
130
+ qkv = self.qkv(x)
131
+ if qkv_delta is not None:
132
+ if qkv_delta.shape != qkv.shape:
133
+ raise RuntimeError("phase LoRA QKV delta shape mismatch")
134
+ qkv = qkv + qkv_delta
135
+ q, k, v = qkv.split([self.q_dim, self.kv_dim, self.kv_dim], dim=-1)
136
+ q = q.view(b, t, self.nh, self.hd).transpose(1, 2)
137
+ k = k.view(b, t, self.nkv, self.hd).transpose(1, 2)
138
+ v = v.view(b, t, self.nkv, self.hd).transpose(1, 2)
139
+ q = apply_rope(self.qn(q), cos, sin)
140
+ k = apply_rope(self.kn(k), cos, sin)
141
+ if self.nkv != self.nh:
142
+ repeat = self.nh // self.nkv
143
+ k = k.repeat_interleave(repeat, dim=1)
144
+ v = v.repeat_interleave(repeat, dim=1)
145
+ if attn_mask is None:
146
+ z = F.scaled_dot_product_attention(q, k, v, is_causal=True, dropout_p=0.0)
147
+ else:
148
+ z = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask, dropout_p=0.0)
149
+ return self.out(z.transpose(1, 2).contiguous().view(b, t, self.q_dim))
150
+
151
+
152
+ class SwiGLU(nn.Module):
153
+ def __init__(self, c):
154
+ super().__init__()
155
+ self.gate_up = nn.Linear(c.hidden_size, 2 * c.intermediate_size, bias=False)
156
+ self.down = nn.Linear(c.intermediate_size, c.hidden_size, bias=False)
157
+
158
+ def forward(self, x):
159
+ gate, up = self.gate_up(x).chunk(2, dim=-1)
160
+ return self.down(F.silu(gate) * up)
161
+
162
+
163
+ class DeepDeltaResidual(nn.Module):
164
+ """Scalar Deep-Delta residual update used only in the shared recurrent body."""
165
+ def __init__(self, c):
166
+ super().__init__()
167
+ self.k_eps = c.ddl_k_eps
168
+ self.v_sigmoid_scale = c.ddl_v_sigmoid_scale
169
+ self.beta_init = c.ddl_beta_init
170
+ self.beta = nn.Linear(c.hidden_size, 1, bias=True)
171
+ self.v_proj = nn.Linear(c.hidden_size, 1, bias=True)
172
+
173
+ @torch.no_grad()
174
+ def reset_beta_bias(self):
175
+ p = min(max(self.beta_init, 0.0), 2.0) / 2.0
176
+ p = min(max(p, 1e-6), 1.0 - 1e-6)
177
+ self.beta.bias.fill_(math.log(p) - math.log(1.0 - p))
178
+
179
+ def forward(self, x, *, k_in, context):
180
+ d = k_in.size(-1)
181
+ eps_rms = (self.k_eps * self.k_eps) / d
182
+ k_rms = F.rms_norm(k_in, [d], eps=eps_rms)
183
+ scale = 1.0 / math.sqrt(d)
184
+ beta = 2.0 * torch.sigmoid(self.beta(context).float())
185
+ proj = torch.sum(k_rms * x, dim=-1, keepdim=True, dtype=torch.float32) * scale
186
+ v = torch.sigmoid(self.v_proj(x).float()) * self.v_sigmoid_scale
187
+ delta = ((beta * (v - proj)) * scale).to(dtype=x.dtype)
188
+ return x + delta * k_rms
189
+
190
+
191
+ class PlainBlock(nn.Module):
192
+ def __init__(self, c):
193
+ super().__init__()
194
+ self.attn_norm = RMSNorm(c.hidden_size, c.rms_eps)
195
+ self.attn = Attention(c)
196
+ self.ffn_norm = RMSNorm(c.hidden_size, c.rms_eps)
197
+ self.ffn = SwiGLU(c)
198
+
199
+ def forward(self, x, cos, sin, attn_mask):
200
+ x = x + self.attn(self.attn_norm(x), cos, sin, attn_mask=attn_mask)
201
+ return x + self.ffn(self.ffn_norm(x))
202
+
203
+
204
+ class ContinuousLoopConditioner(nn.Module):
205
+ def __init__(self, d):
206
+ super().__init__()
207
+ self.net = nn.Sequential(nn.Linear(8, d), nn.SiLU(), nn.Linear(d, 2 * d))
208
+
209
+ @staticmethod
210
+ def features(t, dt, device, dtype):
211
+ return torch.tensor([
212
+ t, dt,
213
+ math.sin(math.pi * t), math.cos(math.pi * t),
214
+ math.sin(2 * math.pi * t), math.cos(2 * math.pi * t),
215
+ math.log(max(dt, 1e-6)), math.log(max(1.0 - t + dt, 1e-6)),
216
+ ], device=device, dtype=dtype)
217
+
218
+ def parameters_for(self, t, dt, device, dtype):
219
+ f = self.features(t, dt, device, dtype)
220
+ scale, shift = self.net(f).chunk(2, dim=-1)
221
+ return f, scale, shift
222
+
223
+ @staticmethod
224
+ def modulate(x, scale, shift):
225
+ return x * (1.0 + 0.1 * scale) + 0.1 * shift
226
+
227
+
228
+ class PhaseLoRA(nn.Module):
229
+ def __init__(self, c):
230
+ super().__init__()
231
+ self.down = nn.Linear(c.hidden_size, c.lora_rank, bias=False)
232
+ self.up = nn.Linear(c.lora_rank, c.qkv_dim, bias=False)
233
+ self.gate = nn.Linear(8, c.lora_rank, bias=True)
234
+
235
+ def forward(self, x, phase_features):
236
+ gate = (2.0 * torch.sigmoid(self.gate(phase_features).float())).to(dtype=x.dtype)
237
+ return self.up(self.down(x) * gate)
238
+
239
+
240
+ class LoopedBlock(nn.Module):
241
+ def __init__(self, c):
242
+ super().__init__()
243
+ self.attn_norm = RMSNorm(c.hidden_size, c.rms_eps)
244
+ self.attn = Attention(c)
245
+ self.phase_lora = PhaseLoRA(c)
246
+ self.ddl_attn = DeepDeltaResidual(c)
247
+ self.ffn_norm = RMSNorm(c.hidden_size, c.rms_eps)
248
+ self.ffn = SwiGLU(c)
249
+ self.ddl_ffn = DeepDeltaResidual(c)
250
+
251
+ def forward(self, x, cos, sin, phase_scale, phase_shift, phase_features, attn_mask):
252
+ attn_context = ContinuousLoopConditioner.modulate(x, phase_scale, phase_shift)
253
+ qkv_delta = self.phase_lora(attn_context, phase_features)
254
+ x_norm = self.attn_norm(attn_context)
255
+ x = self.ddl_attn(
256
+ x,
257
+ k_in=self.attn(x_norm, cos, sin, qkv_delta=qkv_delta, attn_mask=attn_mask),
258
+ context=x_norm,
259
+ )
260
+ ffn_context = ContinuousLoopConditioner.modulate(x, phase_scale, phase_shift)
261
+ x_norm = self.ffn_norm(ffn_context)
262
+ return self.ddl_ffn(x, k_in=self.ffn(x_norm), context=x_norm)
263
+
264
+
265
+ class LoopHyperConnection(nn.Module):
266
+ """Two persistent loop lanes with per-budget read/mix/write scalars."""
267
+ def __init__(self, c):
268
+ super().__init__()
269
+ self.k = int(c.hyper_lanes)
270
+ self.max_loops = int(c.max_loops)
271
+ if self.k != 2:
272
+ raise ValueError("SparkBET is defined for two Hyper-Connection lanes")
273
+ shape = (self.max_loops, self.max_loops)
274
+ self.alpha = nn.Parameter(torch.zeros(*shape, self.k))
275
+ self.mix = nn.Parameter(torch.zeros(*shape, self.k, self.k))
276
+ self.beta = nn.Parameter(torch.zeros(*shape, self.k))
277
+ self.reset_parameters()
278
+
279
+ @torch.no_grad()
280
+ def reset_parameters(self):
281
+ self.alpha.zero_(); self.mix.zero_(); self.beta.zero_()
282
+ eye = torch.eye(self.k, device=self.mix.device, dtype=self.mix.dtype)
283
+ for budget in range(1, self.max_loops + 1):
284
+ b = budget - 1
285
+ for i in range(budget):
286
+ self.alpha[b, i, i % self.k] = 1.0
287
+ self.mix[b, i].copy_(eye)
288
+ self.beta[b, i].fill_(1.0)
289
+
290
+ def init_lanes(self, prelude_state):
291
+ return prelude_state.unsqueeze(0).expand(self.k, *prelude_state.shape)
292
+
293
+ def read(self, lanes, loops, iteration):
294
+ a = self.alpha[loops - 1, iteration].to(dtype=lanes.dtype)
295
+ return torch.einsum("k,kbtd->btd", a, lanes)
296
+
297
+ def write(self, lanes, branch_delta, loops, iteration):
298
+ m = self.mix[loops - 1, iteration].to(dtype=lanes.dtype)
299
+ b = self.beta[loops - 1, iteration].to(dtype=lanes.dtype)
300
+ mixed = torch.einsum("kj,jbtd->kbtd", m, lanes)
301
+ return mixed + b[:, None, None, None] * branch_delta.unsqueeze(0)
302
+
303
+ @staticmethod
304
+ def pool(lanes):
305
+ return lanes.mean(dim=0)
306
+
307
+
308
+ class SparkBET(nn.Module):
309
+ """Prelude -> six shared recurrent blocks -> coda, with exact loop budgets 1..8."""
310
+ def __init__(self, c=CFG):
311
+ super().__init__()
312
+ self.c = c
313
+ self.embed = nn.Embedding(c.vocab_size, c.hidden_size)
314
+ self.prelude = nn.ModuleList([PlainBlock(c) for _ in range(c.prelude_layers)])
315
+ self.body = nn.ModuleList([LoopedBlock(c) for _ in range(c.body_blocks)])
316
+ self.time_cond = ContinuousLoopConditioner(c.hidden_size)
317
+ self.loop_hyper = LoopHyperConnection(c)
318
+ self.coda = nn.ModuleList([PlainBlock(c) for _ in range(c.coda_layers)])
319
+ self.final_norm = RMSNorm(c.hidden_size, c.rms_eps)
320
+ self.apply(self._generic_init)
321
+ self._mechanism_init()
322
+
323
+ @staticmethod
324
+ def _generic_init(m):
325
+ if isinstance(m, nn.Linear):
326
+ nn.init.normal_(m.weight, 0.0, 0.02)
327
+ if m.bias is not None: nn.init.zeros_(m.bias)
328
+ elif isinstance(m, nn.Embedding):
329
+ nn.init.normal_(m.weight, 0.0, 0.02)
330
+
331
+ @torch.no_grad()
332
+ def _mechanism_init(self):
333
+ nn.init.zeros_(self.time_cond.net[-1].weight)
334
+ nn.init.zeros_(self.time_cond.net[-1].bias)
335
+ self.loop_hyper.reset_parameters()
336
+ for block in self.body:
337
+ nn.init.zeros_(block.phase_lora.up.weight)
338
+ nn.init.zeros_(block.phase_lora.gate.weight)
339
+ nn.init.zeros_(block.phase_lora.gate.bias)
340
+ for block in [*self.prelude, *self.body, *self.coda]:
341
+ nn.init.zeros_(block.attn.out.weight)
342
+ nn.init.zeros_(block.ffn.down.weight)
343
+ for block in self.body:
344
+ block.ddl_attn.reset_beta_bias(); block.ddl_ffn.reset_beta_bias()
345
+
346
+ def _run_plain(self, block, x, cos, sin, attn_mask):
347
+ if _USE_GRAD_CHECKPOINTING and self.training:
348
+ return checkpoint(block, x, cos, sin, attn_mask, use_reentrant=False)
349
+ return block(x, cos, sin, attn_mask)
350
+
351
+ def _run_looped(self, block, x, cos, sin, scale, shift, features, attn_mask):
352
+ if _USE_GRAD_CHECKPOINTING and self.training:
353
+ return checkpoint(block, x, cos, sin, scale, shift, features, attn_mask, use_reentrant=False)
354
+ return block(x, cos, sin, scale, shift, features, attn_mask)
355
+
356
+ def _readout(self, x, cos, sin, attn_mask):
357
+ h = x
358
+ for block in self.coda:
359
+ h = self._run_plain(block, h, cos, sin, attn_mask)
360
+ h = self.final_norm(h)
361
+ return F.linear(h, self.embed.weight)
362
+
363
+ @staticmethod
364
+ def _schedule(step_sizes):
365
+ if isinstance(step_sizes, int):
366
+ n = int(step_sizes)
367
+ step_sizes = uniform_steps(n)
368
+ if not step_sizes:
369
+ raise ValueError("empty refinement schedule")
370
+ values = [float(v) for v in step_sizes]
371
+ if any(v <= 0 for v in values):
372
+ raise ValueError("refinement strides must be positive")
373
+ if abs(sum(values) - 1.0) > 1e-5:
374
+ raise ValueError("refinement strides must sum to 1")
375
+ return values
376
+
377
+ def _prepare(self, input_ids, step_sizes, attention_mask=None):
378
+ steps = self._schedule(step_sizes)
379
+ loops = len(steps)
380
+ if loops > self.c.max_loops:
381
+ raise ValueError(f"loops {loops} > configured max_loops {self.c.max_loops}")
382
+ if input_ids.shape[1] > self.c.max_seq_len:
383
+ raise ValueError("context exceeds max_seq_len")
384
+ x = self.embed(input_ids)
385
+ attn_mask, pos = attention_mask_and_positions(input_ids, attention_mask)
386
+ cos, sin = rope_cos_sin(pos, self.c.head_dim, self.c.rope_theta, x.dtype)
387
+ for block in self.prelude:
388
+ x = self._run_plain(block, x, cos, sin, attn_mask)
389
+ lanes = self.loop_hyper.init_lanes(x)
390
+ shared_noise = torch.randn_like(x) if self.training and _STATE_NOISE_SIGMA > 0 else None
391
+ return steps, lanes, shared_noise, cos, sin, attn_mask
392
+
393
+ def _advance(self, lanes, steps, iteration, elapsed, shared_noise, cos, sin, attn_mask):
394
+ dt = steps[iteration]
395
+ t_mid = elapsed + 0.5 * dt
396
+ features, scale, shift = self.time_cond.parameters_for(t_mid, dt, lanes.device, lanes.dtype)
397
+ branch_input = self.loop_hyper.read(lanes, len(steps), iteration)
398
+ if shared_noise is not None:
399
+ t_end = elapsed + dt
400
+ sigma = _STATE_NOISE_SIGMA * max(0.0, 1.0 - t_end)
401
+ if sigma:
402
+ branch_input = branch_input + sigma * shared_noise
403
+ h = branch_input
404
+ for block in self.body:
405
+ h = self._run_looped(block, h, cos, sin, scale, shift, features, attn_mask)
406
+ return self.loop_hyper.write(lanes, h - branch_input, len(steps), iteration)
407
+
408
+ def _run_recurrence(self, input_ids, step_sizes, attention_mask=None, collect_states=False):
409
+ steps, lanes, noise, cos, sin, attn_mask = self._prepare(input_ids, step_sizes, attention_mask)
410
+ states = [] if collect_states else None
411
+ elapsed = 0.0
412
+ for i, dt in enumerate(steps):
413
+ lanes = self._advance(lanes, steps, i, elapsed, noise, cos, sin, attn_mask)
414
+ elapsed += dt
415
+ if collect_states: states.append(self.loop_hyper.pool(lanes))
416
+ return self.loop_hyper.pool(lanes), states, cos, sin, attn_mask
417
+
418
+ def forward(self, input_ids, step_sizes=None, attention_mask=None):
419
+ if step_sizes is None: step_sizes = uniform_steps(self.c.max_loops)
420
+ x, _, cos, sin, attn_mask = self._run_recurrence(input_ids, step_sizes, attention_mask, False)
421
+ return self._readout(x, cos, sin, attn_mask)
422
+
423
+ def forward_loop_exits(self, input_ids, step_sizes=None, attention_mask=None):
424
+ if step_sizes is None: step_sizes = uniform_steps(self.c.max_loops)
425
+ _, states, cos, sin, attn_mask = self._run_recurrence(input_ids, step_sizes, attention_mask, True)
426
+ return [self._readout(h, cos, sin, attn_mask) for h in states]
427
+
428
+ def count_params(self):
429
+ return sum(p.numel() for p in self.parameters())
430
+
431
+
432
+ # Preserve the old trainer/import name while changing the implementation.
433
+ BETFog = SparkBET
434
+
435
+
436
+ def uniform_steps(n):
437
+ n = int(n)
438
+ if not 1 <= n <= MAX_LOOPS:
439
+ raise ValueError(f"loop budget must be in [1,{MAX_LOOPS}]")
440
+ return [1.0 / n] * n
441
+
442
+
443
+ def architecture_shape_signature(model):
444
+ lines = [f"{k}:{tuple(v.shape)}:{v.dtype}" for k, v in model.state_dict().items()]
445
+ return hashlib.sha256("\n".join(lines).encode()).hexdigest()
446
+
447
+
448
+ @torch.no_grad()
449
+ def verify_architecture(model, rank=0):
450
+ expected = dict(
451
+ vocab_size=259, hidden_size=324, intermediate_size=864,
452
+ prelude_layers=1, body_blocks=6, coda_layers=1,
453
+ num_heads=6, num_kv_heads=2, head_dim=54,
454
+ lora_rank=16, hyper_lanes=2, max_seq_len=1024, max_loops=8,
455
+ rope_theta=10_000.0, rms_eps=1e-6,
456
+ ddl_beta_init=1.0, ddl_k_eps=1e-2, ddl_v_sigmoid_scale=4.0,
457
+ )
458
+ actual = asdict(model.c)
459
+ for k, v in expected.items():
460
+ if actual[k] != v:
461
+ raise AssertionError(f"Architecture drift: {k}={actual[k]} expected {v}")
462
+ if model.count_params() != EXPECTED_PARAM_COUNT:
463
+ raise AssertionError(f"Parameter drift: {model.count_params():,} != {EXPECTED_PARAM_COUNT:,}")
464
+ sig = architecture_shape_signature(model)
465
+ if sig != EXPECTED_ARCH_SHAPE_SHA256:
466
+ raise AssertionError(f"Architecture SHA drift: {sig} != {EXPECTED_ARCH_SHAPE_SHA256}")
467
+ if not torch.allclose(model.time_cond.net[-1].weight, torch.zeros_like(model.time_cond.net[-1].weight)):
468
+ raise AssertionError("time conditioner must start as identity")
469
+ for i, block in enumerate(model.body):
470
+ if not torch.allclose(block.phase_lora.up.weight, torch.zeros_like(block.phase_lora.up.weight)):
471
+ raise AssertionError(f"phase LoRA {i} up projection must start zero")
472
+ for name, ddl in (("attn", block.ddl_attn), ("ffn", block.ddl_ffn)):
473
+ beta = (2.0 * torch.sigmoid(ddl.beta.bias.float())).item()
474
+ if abs(beta - 1.0) > 1e-6:
475
+ raise AssertionError(f"body {i} {name} DDL beta init={beta}")
476
+ hc = model.loop_hyper
477
+ eye = torch.eye(hc.k, device=hc.mix.device, dtype=hc.mix.dtype)
478
+ for budget in range(1, model.c.max_loops + 1):
479
+ for i in range(budget):
480
+ expected_alpha = torch.zeros_like(hc.alpha[budget - 1, i]); expected_alpha[i % hc.k] = 1
481
+ if not torch.allclose(hc.alpha[budget - 1, i], expected_alpha): raise AssertionError("Hyper alpha init drift")
482
+ if not torch.allclose(hc.mix[budget - 1, i], eye): raise AssertionError("Hyper mix init drift")
483
+ if not torch.allclose(hc.beta[budget - 1, i], torch.ones_like(hc.beta[budget - 1, i])): raise AssertionError("Hyper beta init drift")
484
+ if rank == 0:
485
+ print("[verify] SparkBET architecture PASSED")
486
+ print(f"[verify] params: {model.count_params():,}")
487
+ print(f"[verify] physical blocks: 1 prelude + 6 shared body + 1 coda; L8 applications=50")
488
+ print(f"[verify] architecture SHA256: {sig}")
unified/ablations/unified-v1-blt-none-s42/source/braille_encoder.py ADDED
@@ -0,0 +1,1532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Supported input sources
4
+ -----------------------
5
+ 1. Single image file
6
+ 2. Folder of images
7
+ 3. CSV / TSV with an image path column
8
+ 4. JSON / JSONL with an image/path field
9
+ 5. Parquet with an image path column
10
+ 6. Hugging Face dataset repo via datasets.load_dataset(...)
11
+ 7. Hugging Face dataset already saved locally via datasets.load_from_disk(...)
12
+
13
+ Supported output formats
14
+ ------------------------
15
+ - txt : single-image Braille text
16
+ - jsonl : dataset rows
17
+ - csv : dataset rows
18
+ - parquet : dataset rows
19
+ - hf-disk : datasets.Dataset.save_to_disk(...)
20
+ - hf-push : datasets.Dataset.push_to_hub(...)
21
+
22
+ Dependencies
23
+ ------------
24
+ Core:
25
+ pip install pillow numpy opencv-python
26
+
27
+ Dataset features:
28
+ pip install datasets pandas pyarrow huggingface_hub
29
+
30
+ Single image usage
31
+ ------------------
32
+ python smart_braille_encoder_v12.py input.png
33
+ python smart_braille_encoder_v12.py input.png --save-debug
34
+
35
+ Dataset usage
36
+ -------------
37
+ python smart_braille_encoder_v12.py ./images --dataset-out out.jsonl
38
+ python smart_braille_encoder_v12.py metadata.csv --image-column image_path --dataset-out out.parquet
39
+ python smart_braille_encoder_v12.py --hf-dataset user/dataset --split train --dataset-out out.jsonl
40
+ python smart_braille_encoder_v12.py --hf-dataset user/dataset --split train --hf-out braille_ds
41
+ python smart_braille_encoder_v12.py --hf-dataset user/dataset --split train --push-to-hub user/my-braille-dataset
42
+
43
+ Notes
44
+ -----
45
+ - The exported dataset includes:
46
+ braille_text
47
+ braille_lines
48
+ braille_codepoints
49
+ braille_flat_codepoints
50
+ decision metadata
51
+ - This makes it easy to train your own “ASCII tokenized” / Braille-tokenized models.
52
+ """
53
+
54
+ from __future__ import annotations
55
+
56
+ import argparse
57
+ import io
58
+ import json
59
+ from pathlib import Path
60
+ from typing import Any
61
+
62
+ import cv2
63
+ import numpy as np
64
+ from PIL import Image, ImageDraw, ImageFilter
65
+
66
+
67
+ # ============================================================
68
+ # Utility
69
+ # ============================================================
70
+
71
+ IMAGE_EXTENSIONS = {
72
+ ".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif", ".tif", ".tiff"
73
+ }
74
+
75
+
76
+ def clamp(v: int, lo: int, hi: int) -> int:
77
+ return max(lo, min(v, hi))
78
+
79
+
80
+ def robust_norm_masked(
81
+ x: np.ndarray,
82
+ mask: np.ndarray | None = None,
83
+ low: float = 1.0,
84
+ high: float = 99.0,
85
+ ) -> np.ndarray:
86
+ x = np.asarray(x, dtype=np.float32)
87
+
88
+ if mask is None:
89
+ vals = x.ravel()
90
+ else:
91
+ vals = x[mask.astype(bool)]
92
+
93
+ if vals.size == 0:
94
+ return np.zeros_like(x, dtype=np.float32)
95
+
96
+ lo = np.percentile(vals, low)
97
+ hi = np.percentile(vals, high)
98
+
99
+ out = np.clip((x - lo) / (hi - lo + 1e-6), 0.0, 1.0)
100
+
101
+ if mask is not None:
102
+ out *= mask.astype(np.float32)
103
+
104
+ return out.astype(np.float32)
105
+
106
+
107
+ def is_image_path(path: str | Path) -> bool:
108
+ return Path(path).suffix.lower() in IMAGE_EXTENSIONS
109
+
110
+
111
+ def jsonable(value: Any) -> Any:
112
+ if isinstance(value, (str, int, float, bool)) or value is None:
113
+ return value
114
+ if isinstance(value, Path):
115
+ return str(value)
116
+ if isinstance(value, (list, dict)):
117
+ return value
118
+ if isinstance(value, tuple):
119
+ return list(value)
120
+ if isinstance(value, np.generic):
121
+ return value.item()
122
+ return str(value)
123
+
124
+
125
+ # ============================================================
126
+ # Load / alpha handling
127
+ # ============================================================
128
+
129
+ def load_image(path: str | Path) -> Image.Image:
130
+ return Image.open(path).convert("RGBA")
131
+
132
+
133
+ def alpha_mask(
134
+ image: Image.Image,
135
+ alpha_threshold: int = 8,
136
+ ) -> np.ndarray:
137
+ rgba = np.asarray(image.convert("RGBA"), dtype=np.uint8)
138
+ return rgba[..., 3] >= alpha_threshold
139
+
140
+
141
+ def visible_bbox(
142
+ image: Image.Image,
143
+ alpha_threshold: int = 8,
144
+ padding_ratio: float = 0.02,
145
+ ) -> tuple[int, int, int, int]:
146
+ mask = alpha_mask(image, alpha_threshold)
147
+ h, w = mask.shape
148
+ ys, xs = np.where(mask)
149
+
150
+ if xs.size == 0:
151
+ return (0, 0, w, h)
152
+
153
+ x0 = int(xs.min())
154
+ y0 = int(ys.min())
155
+ x1 = int(xs.max()) + 1
156
+ y1 = int(ys.max()) + 1
157
+
158
+ bw = x1 - x0
159
+ bh = y1 - y0
160
+ pad = int(round(max(bw, bh) * padding_ratio))
161
+
162
+ return (
163
+ max(0, x0 - pad),
164
+ max(0, y0 - pad),
165
+ min(w, x1 + pad),
166
+ min(h, y1 + pad),
167
+ )
168
+
169
+
170
+ # ============================================================
171
+ # Attention analysis
172
+ # ============================================================
173
+
174
+ def compute_maps(
175
+ image: Image.Image,
176
+ alpha_threshold: int = 8,
177
+ ) -> dict:
178
+ rgba = np.asarray(image.convert("RGBA"), dtype=np.float32)
179
+
180
+ rgb = rgba[..., :3]
181
+ alpha_u8 = rgba[..., 3]
182
+ alpha = alpha_u8 / 255.0
183
+
184
+ visible = alpha_u8 >= alpha_threshold
185
+ visible_f = visible.astype(np.float32)
186
+
187
+ gray = (
188
+ 0.299 * rgb[..., 0]
189
+ + 0.587 * rgb[..., 1]
190
+ + 0.114 * rgb[..., 2]
191
+ ).astype(np.float32)
192
+
193
+ h, w = gray.shape
194
+
195
+ if not np.any(visible):
196
+ z = np.zeros((h, w), dtype=np.float32)
197
+ return {
198
+ "attention": z,
199
+ "edge_energy": z,
200
+ "colorfulness": z,
201
+ "visible": visible,
202
+ "alpha": alpha,
203
+ "gray": gray,
204
+ }
205
+
206
+ gx = cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3)
207
+ gy = cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3)
208
+ edge_energy = np.hypot(gx, gy)
209
+
210
+ edges = robust_norm_masked(edge_energy, visible, 5, 99.5)
211
+
212
+ local_contrast = np.zeros_like(gray)
213
+ for sigma in (2, 8, 24):
214
+ blur = cv2.GaussianBlur(gray, (0, 0), sigma)
215
+ local_contrast += np.abs(gray - blur)
216
+ local_contrast /= 3.0
217
+ local_contrast = robust_norm_masked(local_contrast, visible, 5, 99)
218
+
219
+ median_luma = np.median(gray[visible])
220
+ luma_surprise = robust_norm_masked(np.abs(gray - median_luma), visible, 5, 99)
221
+
222
+ median_rgb = np.median(rgb[visible], axis=0)
223
+ color_distance = np.linalg.norm(rgb - median_rgb, axis=2)
224
+ color_surprise = robust_norm_masked(color_distance, visible, 5, 99)
225
+
226
+ bins = 32
227
+ bin_id = np.clip((gray / 256.0 * bins).astype(np.int32), 0, bins - 1)
228
+ hist = np.bincount(bin_id[visible].ravel(), minlength=bins).astype(np.float32)
229
+ prob = hist / (hist.sum() + 1e-9)
230
+ rarity = -np.log(prob[bin_id] + 1e-9)
231
+ rarity = robust_norm_masked(rarity, visible, 1, 99)
232
+
233
+ raw = (
234
+ 0.18 * edges
235
+ + 0.20 * local_contrast
236
+ + 0.26 * luma_surprise
237
+ + 0.20 * color_surprise
238
+ + 0.16 * rarity
239
+ )
240
+ raw *= visible_f
241
+
242
+ blurred_raw = cv2.GaussianBlur(raw, (0, 0), 10)
243
+ blurred_vis = cv2.GaussianBlur(visible_f, (0, 0), 10)
244
+ coherent = blurred_raw / (blurred_vis + 1e-6)
245
+ coherent = robust_norm_masked(coherent, visible, 1, 99.5)
246
+
247
+ yy, xx = np.mgrid[0:h, 0:w]
248
+ cx = w / 2.0
249
+ cy = h / 2.0
250
+ sx = w * 0.48
251
+ sy = h * 0.40
252
+
253
+ center = np.exp(
254
+ -(((xx - cx) ** 2) / (2 * sx ** 2) + ((yy - cy) ** 2) / (2 * sy ** 2))
255
+ )
256
+
257
+ attention = coherent * (0.84 + 0.16 * center)
258
+ attention *= visible_f
259
+ attention = robust_norm_masked(attention, visible, 0, 99.5)
260
+
261
+ rgb_u8 = np.clip(rgb, 0, 255).astype(np.uint8)
262
+ hsv = cv2.cvtColor(rgb_u8, cv2.COLOR_RGB2HSV).astype(np.float32)
263
+
264
+ saturation = hsv[..., 1] / 255.0
265
+ value = hsv[..., 2] / 255.0
266
+ colorfulness = saturation * (0.35 + 0.65 * value)
267
+ colorfulness *= visible_f
268
+ colorfulness = robust_norm_masked(colorfulness, visible, 2, 99)
269
+
270
+ return {
271
+ "attention": attention.astype(np.float32),
272
+ "edge_energy": (edge_energy * visible_f).astype(np.float32),
273
+ "colorfulness": colorfulness.astype(np.float32),
274
+ "visible": visible,
275
+ "alpha": alpha.astype(np.float32),
276
+ "gray": gray,
277
+ }
278
+
279
+
280
+ # ============================================================
281
+ # Saliency geometry
282
+ # ============================================================
283
+
284
+ def salient_mask(
285
+ attention: np.ndarray,
286
+ visible: np.ndarray,
287
+ percentile: float = 87.0,
288
+ ) -> np.ndarray:
289
+ vals = attention[visible]
290
+ if vals.size == 0:
291
+ return np.zeros_like(attention, dtype=np.uint8)
292
+
293
+ threshold = np.percentile(vals, percentile)
294
+ mask = ((attention >= threshold) & visible).astype(np.uint8)
295
+ kernel = np.ones((5, 5), np.uint8)
296
+ mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
297
+ mask *= visible.astype(np.uint8)
298
+ return mask
299
+
300
+
301
+ def bbox_from_mask(
302
+ mask: np.ndarray,
303
+ fallback: tuple[int, int, int, int],
304
+ ) -> tuple[int, int, int, int]:
305
+ ys, xs = np.where(mask > 0)
306
+ if xs.size == 0:
307
+ return fallback
308
+
309
+ return (
310
+ int(xs.min()),
311
+ int(ys.min()),
312
+ int(xs.max()) + 1,
313
+ int(ys.max()) + 1,
314
+ )
315
+
316
+
317
+ def border_spread_in_visible_frame(
318
+ mask: np.ndarray,
319
+ visible_box: tuple[int, int, int, int],
320
+ border_ratio: float = 0.08,
321
+ ) -> dict:
322
+ x0, y0, x1, y1 = visible_box
323
+ local = mask[y0:y1, x0:x1]
324
+ h, w = local.shape
325
+
326
+ if h <= 0 or w <= 0:
327
+ return {k: 0.0 for k in ("left", "right", "top", "bottom", "tl", "tr", "bl", "br")}
328
+
329
+ total = float(local.sum()) + 1e-9
330
+
331
+ bx = max(1, int(w * border_ratio))
332
+ by = max(1, int(h * border_ratio))
333
+ ch = max(1, int(h * 0.18))
334
+ cw = max(1, int(w * 0.18))
335
+
336
+ return {
337
+ "left": float(local[:, :bx].sum() / total),
338
+ "right": float(local[:, w - bx:].sum() / total),
339
+ "top": float(local[:by, :].sum() / total),
340
+ "bottom": float(local[h - by:, :].sum() / total),
341
+ "tl": float(local[:ch, :cw].sum() / total),
342
+ "tr": float(local[:ch, w - cw:].sum() / total),
343
+ "bl": float(local[h - ch:, :cw].sum() / total),
344
+ "br": float(local[h - ch:, w - cw:].sum() / total),
345
+ }
346
+
347
+
348
+ def features_span_visible_frame(
349
+ mask: np.ndarray,
350
+ salient_box: tuple[int, int, int, int],
351
+ visible_box: tuple[int, int, int, int],
352
+ side_presence_threshold: float = 0.012,
353
+ bbox_span_threshold: float = 0.88,
354
+ ) -> tuple[bool, dict]:
355
+ vx0, vy0, vx1, vy1 = visible_box
356
+ sx0, sy0, sx1, sy1 = salient_box
357
+
358
+ vw = max(vx1 - vx0, 1)
359
+ vh = max(vy1 - vy0, 1)
360
+
361
+ span_x = (sx1 - sx0) / vw
362
+ span_y = (sy1 - sy0) / vh
363
+
364
+ spread = border_spread_in_visible_frame(mask, visible_box)
365
+
366
+ all_sides = (
367
+ spread["left"] >= side_presence_threshold
368
+ and spread["right"] >= side_presence_threshold
369
+ and spread["top"] >= side_presence_threshold
370
+ and spread["bottom"] >= side_presence_threshold
371
+ )
372
+
373
+ corner_hits = sum(
374
+ spread[k] >= side_presence_threshold * 0.65
375
+ for k in ("tl", "tr", "bl", "br")
376
+ )
377
+
378
+ spans = span_x >= bbox_span_threshold and span_y >= bbox_span_threshold and all_sides
379
+ touches_four_corners = corner_hits == 4
380
+
381
+ return bool(spans or touches_four_corners), {
382
+ "span_x": float(span_x),
383
+ "span_y": float(span_y),
384
+ "corner_hits": int(corner_hits),
385
+ **spread,
386
+ }
387
+
388
+
389
+ # ============================================================
390
+ # Colorful center detector
391
+ # ============================================================
392
+
393
+ def detect_central_color_feature(
394
+ maps: dict,
395
+ visible_box: tuple[int, int, int, int],
396
+ ) -> dict | None:
397
+ colorfulness = maps["colorfulness"]
398
+ attention = maps["attention"]
399
+ visible = maps["visible"]
400
+
401
+ vals = colorfulness[visible]
402
+ if vals.size == 0:
403
+ return None
404
+
405
+ threshold = max(float(np.percentile(vals, 72)), 0.22)
406
+
407
+ att_vals = attention[visible]
408
+ att_threshold = float(np.percentile(att_vals, 55)) if att_vals.size else 0.0
409
+
410
+ seed = ((colorfulness >= threshold) & visible).astype(np.uint8)
411
+ support = ((colorfulness >= threshold * 0.62) & (attention >= att_threshold) & visible).astype(np.uint8)
412
+
413
+ seed = cv2.morphologyEx(seed, cv2.MORPH_CLOSE, np.ones((5, 5), np.uint8))
414
+ seed = cv2.dilate(seed, np.ones((3, 3), np.uint8), iterations=1)
415
+
416
+ candidate = ((seed > 0) & (support > 0)).astype(np.uint8)
417
+
418
+ n, labels, stats, centroids = cv2.connectedComponentsWithStats(candidate, connectivity=8)
419
+ if n <= 1:
420
+ return None
421
+
422
+ vx0, vy0, vx1, vy1 = visible_box
423
+ vis_w = max(vx1 - vx0, 1)
424
+ vis_h = max(vy1 - vy0, 1)
425
+ frame_cx = (vx0 + vx1) / 2.0
426
+ frame_cy = (vy0 + vy1) / 2.0
427
+
428
+ visible_area = float(visible.sum()) + 1e-9
429
+ total_color_mass = float((colorfulness * visible.astype(np.float32)).sum()) + 1e-9
430
+
431
+ best = None
432
+
433
+ for label in range(1, n):
434
+ area = int(stats[label, cv2.CC_STAT_AREA])
435
+ if area < max(8, int(visible_area * 0.002)):
436
+ continue
437
+
438
+ component = labels == label
439
+ cx, cy = centroids[label]
440
+
441
+ dx = (cx - frame_cx) / (vis_w / 2.0 + 1e-9)
442
+ dy = (cy - frame_cy) / (vis_h / 2.0 + 1e-9)
443
+ center_distance = float(np.sqrt(dx * dx + dy * dy))
444
+
445
+ if center_distance > 0.72:
446
+ continue
447
+
448
+ mean_color = float(colorfulness[component].mean())
449
+ mean_attention = float(attention[component].mean())
450
+ color_mass = float(colorfulness[component].sum())
451
+ dominance = color_mass / total_color_mass
452
+ area_ratio = area / visible_area
453
+
454
+ center_score = np.exp(-(center_distance ** 2) / (2 * 0.42 ** 2))
455
+ score = (
456
+ 0.34 * mean_color
457
+ + 0.24 * mean_attention
458
+ + 0.22 * center_score
459
+ + 0.14 * min(dominance * 2.0, 1.0)
460
+ + 0.06 * min(area_ratio * 8.0, 1.0)
461
+ )
462
+
463
+ if best is None or score > best["score"]:
464
+ best = {
465
+ "score": float(score),
466
+ "mask": component.astype(np.uint8),
467
+ "centroid": (float(cx), float(cy)),
468
+ "center_distance": center_distance,
469
+ "mean_color": mean_color,
470
+ "mean_attention": mean_attention,
471
+ "dominance": float(dominance),
472
+ "area_ratio": float(area_ratio),
473
+ }
474
+
475
+ if best is None:
476
+ return None
477
+ if best["score"] < 0.44:
478
+ return None
479
+ if best["mean_color"] < 0.28:
480
+ return None
481
+ if best["mean_attention"] < 0.20:
482
+ return None
483
+
484
+ return best
485
+
486
+
487
+ def color_outline_crop(
488
+ feature: dict,
489
+ image_size: tuple[int, int],
490
+ visible_box: tuple[int, int, int, int],
491
+ ) -> tuple[tuple[int, int, int, int], dict]:
492
+ mask = feature["mask"].astype(np.uint8)
493
+ ys, xs = np.where(mask > 0)
494
+
495
+ if xs.size == 0:
496
+ return visible_box, {
497
+ "outline_layers": 0,
498
+ "layer_px": 0,
499
+ "object_scale": 1.0,
500
+ "expanded_mask": mask,
501
+ }
502
+
503
+ x0 = int(xs.min())
504
+ y0 = int(ys.min())
505
+ x1 = int(xs.max()) + 1
506
+ y1 = int(ys.max()) + 1
507
+
508
+ obj_side = max(x1 - x0, y1 - y0)
509
+
510
+ vx0, vy0, vx1, vy1 = visible_box
511
+ visible_side = max(vx1 - vx0, vy1 - vy0)
512
+
513
+ object_scale = obj_side / max(visible_side, 1)
514
+ layers = 3 if object_scale < 0.30 else 2
515
+
516
+ layer_px = max(2, int(round(obj_side * 0.045)))
517
+ kernel_size = layer_px * 2 + 1
518
+
519
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (kernel_size, kernel_size))
520
+
521
+ expanded = mask.copy()
522
+ for _ in range(layers):
523
+ expanded = cv2.dilate(expanded, kernel, iterations=1)
524
+
525
+ eys, exs = np.where(expanded > 0)
526
+
527
+ ex0 = int(exs.min())
528
+ ey0 = int(eys.min())
529
+ ex1 = int(exs.max()) + 1
530
+ ey1 = int(eys.max()) + 1
531
+
532
+ side = max(ex1 - ex0, ey1 - ey0)
533
+
534
+ img_w, img_h = image_size
535
+ visible_w = vx1 - vx0
536
+ visible_h = vy1 - vy0
537
+ max_useful_side = max(visible_w, visible_h)
538
+
539
+ side = max(2, min(side, max_useful_side, min(img_w, img_h)))
540
+
541
+ cx = (ex0 + ex1) / 2.0
542
+ cy = (ey0 + ey1) / 2.0
543
+
544
+ sx0 = clamp(int(round(cx - side / 2)), 0, img_w - side)
545
+ sy0 = clamp(int(round(cy - side / 2)), 0, img_h - side)
546
+
547
+ return (
548
+ sx0,
549
+ sy0,
550
+ sx0 + side,
551
+ sy0 + side,
552
+ ), {
553
+ "outline_layers": int(layers),
554
+ "layer_px": int(layer_px),
555
+ "object_scale": float(object_scale),
556
+ "expanded_mask": expanded,
557
+ }
558
+
559
+
560
+ # ============================================================
561
+ # Generic v6 crop geometry / evaluation
562
+ # ============================================================
563
+
564
+ def expanded_square_bbox(
565
+ salient_box: tuple[int, int, int, int],
566
+ visible_box: tuple[int, int, int, int],
567
+ image_size: tuple[int, int],
568
+ margin_ratio: float = 0.12,
569
+ ) -> tuple[int, int, int, int]:
570
+ img_w, img_h = image_size
571
+ sx0, sy0, sx1, sy1 = salient_box
572
+ vx0, vy0, vx1, vy1 = visible_box
573
+
574
+ bw = sx1 - sx0
575
+ bh = sy1 - sy0
576
+ side = max(bw, bh)
577
+
578
+ side = int(round(side * (1.0 + 2.0 * margin_ratio)))
579
+
580
+ visible_w = vx1 - vx0
581
+ visible_h = vy1 - vy0
582
+ max_useful_side = max(visible_w, visible_h)
583
+
584
+ side = min(side, max_useful_side, min(img_w, img_h))
585
+ side = max(side, 2)
586
+
587
+ cx = (sx0 + sx1) / 2.0
588
+ cy = (sy0 + sy1) / 2.0
589
+
590
+ x0 = int(round(cx - side / 2))
591
+ y0 = int(round(cy - side / 2))
592
+
593
+ x0 = clamp(x0, 0, img_w - side)
594
+ y0 = clamp(y0, 0, img_h - side)
595
+
596
+ return (x0, y0, x0 + side, y0 + side)
597
+
598
+
599
+ def evaluate_crop(
600
+ crop_box: tuple[int, int, int, int],
601
+ visible_box: tuple[int, int, int, int],
602
+ attention: np.ndarray,
603
+ edge_energy: np.ndarray,
604
+ visible: np.ndarray,
605
+ ) -> dict:
606
+ x0, y0, x1, y1 = crop_box
607
+ vx0, vy0, vx1, vy1 = visible_box
608
+
609
+ visible_f = visible.astype(np.float32)
610
+
611
+ saliency_weighted = attention * visible_f
612
+ total_saliency = float(saliency_weighted.sum()) + 1e-9
613
+ crop_saliency = float(saliency_weighted[y0:y1, x0:x1].sum())
614
+
615
+ vals = attention[visible]
616
+ strong_threshold = np.percentile(vals, 90) if vals.size else 1.0
617
+
618
+ strong = attention * (attention >= strong_threshold) * visible_f
619
+ total_strong = float(strong.sum()) + 1e-9
620
+ crop_strong = float(strong[y0:y1, x0:x1].sum())
621
+
622
+ weighted_edges = edge_energy * (0.35 + 0.65 * attention) * visible_f
623
+ total_detail = float(weighted_edges.sum()) + 1e-9
624
+ crop_detail = float(weighted_edges[y0:y1, x0:x1].sum())
625
+
626
+ total_visible = float(visible_f.sum()) + 1e-9
627
+ crop_visible = float(visible_f[y0:y1, x0:x1].sum())
628
+ visible_coverage = crop_visible / total_visible
629
+
630
+ visible_w = vx1 - vx0
631
+ visible_h = vy1 - vy0
632
+ base_side = max(visible_w, visible_h)
633
+ crop_side = max(x1 - x0, y1 - y0)
634
+ resolution_gain = base_side / max(crop_side, 1)
635
+
636
+ return {
637
+ "saliency_coverage": float(crop_saliency / total_saliency),
638
+ "strong_saliency_coverage": float(crop_strong / total_strong),
639
+ "detail_coverage": float(crop_detail / total_detail),
640
+ "visible_pixel_coverage": float(visible_coverage),
641
+ "resolution_gain": float(resolution_gain),
642
+ "crop_ratio_of_visible_frame": float(crop_side / max(base_side, 1)),
643
+ }
644
+
645
+
646
+ def should_crop_v6(metrics: dict) -> tuple[bool, float, float]:
647
+ saliency_loss = 1.0 - metrics["saliency_coverage"]
648
+ strong_loss = 1.0 - metrics["strong_saliency_coverage"]
649
+ detail_loss = 1.0 - metrics["detail_coverage"]
650
+
651
+ gain = np.log2(max(metrics["resolution_gain"], 1.0))
652
+ benefit = 1.30 * gain
653
+ cost = 1.30 * strong_loss + 0.85 * saliency_loss + 0.95 * detail_loss
654
+
655
+ if metrics["strong_saliency_coverage"] < 0.93:
656
+ return False, float(benefit), float(cost)
657
+ if metrics["detail_coverage"] < 0.72:
658
+ return False, float(benefit), float(cost)
659
+ if metrics["resolution_gain"] < 1.12:
660
+ return False, float(benefit), float(cost)
661
+
662
+ return bool(benefit > cost), float(benefit), float(cost)
663
+
664
+
665
+ def should_crop_color_feature(
666
+ metrics: dict,
667
+ feature: dict,
668
+ ) -> tuple[bool, float, float]:
669
+ saliency_loss = 1.0 - metrics["saliency_coverage"]
670
+ strong_loss = 1.0 - metrics["strong_saliency_coverage"]
671
+ detail_loss = 1.0 - metrics["detail_coverage"]
672
+
673
+ gain = np.log2(max(metrics["resolution_gain"], 1.0))
674
+
675
+ center_bonus = np.exp(-(feature["center_distance"] ** 2) / 0.18)
676
+ feature_bonus = (
677
+ 0.34 * feature["mean_color"]
678
+ + 0.28 * feature["mean_attention"]
679
+ + 0.22 * min(feature["dominance"] * 2.0, 1.0)
680
+ + 0.16 * center_bonus
681
+ )
682
+
683
+ benefit = 1.30 * gain + 0.45 * feature_bonus
684
+ cost = 1.22 * strong_loss + 0.72 * saliency_loss + 0.82 * detail_loss
685
+
686
+ if metrics["strong_saliency_coverage"] < 0.91:
687
+ return False, float(benefit), float(cost)
688
+ if metrics["detail_coverage"] < 0.66:
689
+ return False, float(benefit), float(cost)
690
+ if metrics["resolution_gain"] < 1.07:
691
+ return False, float(benefit), float(cost)
692
+
693
+ return bool(benefit > cost), float(benefit), float(cost)
694
+
695
+
696
+ # ============================================================
697
+ # View selection
698
+ # ============================================================
699
+
700
+ def select_view(
701
+ image: Image.Image,
702
+ alpha_threshold: int = 8,
703
+ saliency_percentile: float = 87.0,
704
+ margin_ratio: float = 0.12,
705
+ ) -> dict:
706
+ w, h = image.size
707
+
708
+ vis_box = visible_bbox(image, alpha_threshold=alpha_threshold)
709
+ maps = compute_maps(image, alpha_threshold=alpha_threshold)
710
+
711
+ attention = maps["attention"]
712
+ edge_energy = maps["edge_energy"]
713
+ visible = maps["visible"]
714
+
715
+ if not np.any(visible):
716
+ return {
717
+ "mode": "empty",
718
+ "box": vis_box,
719
+ "visible_box": vis_box,
720
+ "attention": attention,
721
+ "mask": np.zeros((h, w), dtype=np.uint8),
722
+ "bbox": vis_box,
723
+ "maps": maps,
724
+ "spread": {},
725
+ "metrics": None,
726
+ "color_feature": None,
727
+ "outline_info": None,
728
+ "reason": "image has no visible pixels",
729
+ }
730
+
731
+ mask = salient_mask(attention, visible, percentile=saliency_percentile)
732
+ sal_box = bbox_from_mask(mask, fallback=vis_box)
733
+
734
+ spans, spread = features_span_visible_frame(mask, sal_box, vis_box)
735
+
736
+ if spans:
737
+ return {
738
+ "mode": "visible_full",
739
+ "box": vis_box,
740
+ "visible_box": vis_box,
741
+ "attention": attention,
742
+ "mask": mask,
743
+ "bbox": sal_box,
744
+ "maps": maps,
745
+ "spread": spread,
746
+ "metrics": None,
747
+ "color_feature": None,
748
+ "outline_info": None,
749
+ "reason": "v6 full-frame rule: salient features span the visible frame / all four sides or corners",
750
+ }
751
+
752
+ color_feature = detect_central_color_feature(maps, vis_box)
753
+ if color_feature is not None:
754
+ color_box, outline_info = color_outline_crop(color_feature, image.size, vis_box)
755
+
756
+ color_metrics = evaluate_crop(color_box, vis_box, attention, edge_energy, visible)
757
+ color_ok, benefit, cost = should_crop_color_feature(color_metrics, color_feature)
758
+
759
+ color_metrics["benefit"] = benefit
760
+ color_metrics["cost"] = cost
761
+
762
+ if color_ok:
763
+ return {
764
+ "mode": "color_outline_crop",
765
+ "box": color_box,
766
+ "visible_box": vis_box,
767
+ "attention": attention,
768
+ "mask": mask,
769
+ "bbox": sal_box,
770
+ "maps": maps,
771
+ "spread": spread,
772
+ "metrics": color_metrics,
773
+ "color_feature": color_feature,
774
+ "outline_info": outline_info,
775
+ "reason": f'central colorful feature detected after v6 full-frame check; kept {outline_info["outline_layers"]} outer outline layers',
776
+ }
777
+
778
+ candidate = expanded_square_bbox(sal_box, vis_box, image.size, margin_ratio=margin_ratio)
779
+ metrics = evaluate_crop(candidate, vis_box, attention, edge_energy, visible)
780
+ crop_ok, benefit, cost = should_crop_v6(metrics)
781
+
782
+ metrics["benefit"] = benefit
783
+ metrics["cost"] = cost
784
+
785
+ if crop_ok:
786
+ return {
787
+ "mode": "crop",
788
+ "box": candidate,
789
+ "visible_box": vis_box,
790
+ "attention": attention,
791
+ "mask": mask,
792
+ "bbox": sal_box,
793
+ "maps": maps,
794
+ "spread": spread,
795
+ "metrics": metrics,
796
+ "color_feature": color_feature,
797
+ "outline_info": None,
798
+ "reason": "v6 saliency/detail crop accepted because resolution gain exceeds weighted information loss",
799
+ }
800
+
801
+ return {
802
+ "mode": "visible_full",
803
+ "box": vis_box,
804
+ "visible_box": vis_box,
805
+ "attention": attention,
806
+ "mask": mask,
807
+ "bbox": sal_box,
808
+ "maps": maps,
809
+ "spread": spread,
810
+ "metrics": metrics,
811
+ "color_feature": color_feature,
812
+ "outline_info": None,
813
+ "reason": "transparent margins removed for free; v6 crop test rejected further cropping",
814
+ }
815
+
816
+
817
+ # ============================================================
818
+ # Fit selected box to 64x64
819
+ # ============================================================
820
+
821
+ def fit_box_to_square(
822
+ image: Image.Image,
823
+ box: tuple[int, int, int, int],
824
+ size: int = 64,
825
+ ) -> Image.Image:
826
+ region = image.crop(box).convert("RGBA")
827
+ w, h = region.size
828
+
829
+ scale = min(size / max(w, 1), size / max(h, 1))
830
+
831
+ nw = max(1, int(round(w * scale)))
832
+ nh = max(1, int(round(h * scale)))
833
+
834
+ reduced = region.resize((nw, nh), Image.Resampling.LANCZOS)
835
+
836
+ canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0))
837
+
838
+ ox = (size - nw) // 2
839
+ oy = (size - nh) // 2
840
+
841
+ canvas.alpha_composite(reduced, (ox, oy))
842
+ return canvas
843
+
844
+
845
+ # ============================================================
846
+ # Braille conversion
847
+ # ============================================================
848
+
849
+ BRAILLE_BITS = {
850
+ (0, 0): 0,
851
+ (0, 1): 1,
852
+ (0, 2): 2,
853
+ (1, 0): 3,
854
+ (1, 1): 4,
855
+ (1, 2): 5,
856
+ (0, 3): 6,
857
+ (1, 3): 7,
858
+ }
859
+
860
+
861
+ def local_contrast(
862
+ rgba_image: Image.Image,
863
+ amount: float = 1.5,
864
+ blur_radius: float = 2.4,
865
+ ) -> tuple[np.ndarray, np.ndarray]:
866
+ rgba = np.asarray(rgba_image.convert("RGBA"), dtype=np.float32)
867
+ rgb = rgba[..., :3]
868
+ alpha = rgba[..., 3]
869
+
870
+ gray = (
871
+ 0.299 * rgb[..., 0]
872
+ + 0.587 * rgb[..., 1]
873
+ + 0.114 * rgb[..., 2]
874
+ ).astype(np.float32)
875
+
876
+ gray_img = Image.fromarray(np.clip(gray, 0, 255).astype(np.uint8), mode="L")
877
+ local = np.asarray(gray_img.filter(ImageFilter.GaussianBlur(blur_radius)), dtype=np.float32)
878
+
879
+ enhanced = np.clip(gray + amount * (gray - local), 0, 255)
880
+ return enhanced, alpha
881
+
882
+
883
+ def floyd_steinberg(
884
+ arr: np.ndarray,
885
+ alpha: np.ndarray,
886
+ alpha_threshold: int = 8,
887
+ ) -> np.ndarray:
888
+ work = arr.astype(np.float32).copy()
889
+
890
+ h, w = work.shape
891
+ out = np.full((h, w), 255, dtype=np.uint8)
892
+ visible = alpha >= alpha_threshold
893
+
894
+ for y in range(h):
895
+ for x in range(w):
896
+ if not visible[y, x]:
897
+ continue
898
+
899
+ old = work[y, x]
900
+ new = 255.0 if old >= 128 else 0.0
901
+ out[y, x] = int(new)
902
+
903
+ err = old - new
904
+
905
+ if x + 1 < w and visible[y, x + 1]:
906
+ work[y, x + 1] += err * 7 / 16
907
+ if y + 1 < h and x > 0 and visible[y + 1, x - 1]:
908
+ work[y + 1, x - 1] += err * 3 / 16
909
+ if y + 1 < h and visible[y + 1, x]:
910
+ work[y + 1, x] += err * 5 / 16
911
+ if y + 1 < h and x + 1 < w and visible[y + 1, x + 1]:
912
+ work[y + 1, x + 1] += err * 1 / 16
913
+
914
+ return out
915
+
916
+
917
+ def binary_to_braille(
918
+ binary: np.ndarray,
919
+ alpha: np.ndarray,
920
+ alpha_threshold: int = 8,
921
+ ) -> str:
922
+ """
923
+ 64x64 binary source grid -> 32x16 Unicode Braille.
924
+ """
925
+ h, w = binary.shape
926
+
927
+ if h % 4 != 0 or w % 2 != 0:
928
+ raise ValueError("Braille grid must be divisible by 4 vertically and 2 horizontally.")
929
+
930
+ lines = []
931
+
932
+ for cy in range(h // 4):
933
+ line = []
934
+ for cx in range(w // 2):
935
+ code = 0
936
+ any_visible = False
937
+
938
+ for sy in range(4):
939
+ for sx in range(2):
940
+ yy = cy * 4 + sy
941
+ xx = cx * 2 + sx
942
+
943
+ if alpha[yy, xx] < alpha_threshold:
944
+ continue
945
+
946
+ any_visible = True
947
+
948
+ if binary[yy, xx] < 128:
949
+ code |= 1 << BRAILLE_BITS[(sx, sy)]
950
+
951
+ if not any_visible:
952
+ line.append(" ")
953
+ else:
954
+ line.append(chr(0x2800 + code))
955
+
956
+ lines.append("".join(line))
957
+
958
+ return "\n".join(lines)
959
+
960
+
961
+ def braille_metadata(braille_text: str) -> dict:
962
+ lines = braille_text.splitlines()
963
+ codepoints_2d = [[ord(ch) for ch in line] for line in lines]
964
+ flat_codepoints = [cp for row in codepoints_2d for cp in row]
965
+
966
+ return {
967
+ "braille_lines": lines,
968
+ "braille_width_chars": max((len(line) for line in lines), default=0),
969
+ "braille_height_chars": len(lines),
970
+ "braille_codepoints": codepoints_2d,
971
+ "braille_flat_codepoints": flat_codepoints,
972
+ }
973
+
974
+
975
+ # ============================================================
976
+ # Full pipeline
977
+ # ============================================================
978
+
979
+ def encode_image(
980
+ image_path: str | Path | None = None,
981
+ image: Image.Image | None = None,
982
+ grid_size: int = 64,
983
+ alpha_threshold: int = 8,
984
+ ) -> dict:
985
+ if image is None:
986
+ if image_path is None:
987
+ raise ValueError("Either image_path or image must be provided.")
988
+ image = load_image(image_path)
989
+ else:
990
+ image = image.convert("RGBA")
991
+
992
+ decision = select_view(image, alpha_threshold=alpha_threshold)
993
+
994
+ small = fit_box_to_square(image, decision["box"], size=grid_size)
995
+
996
+ enhanced, alpha = local_contrast(small)
997
+ binary = floyd_steinberg(enhanced, alpha, alpha_threshold=alpha_threshold)
998
+ braille = binary_to_braille(binary, alpha, alpha_threshold=alpha_threshold)
999
+
1000
+ return {
1001
+ "image": image,
1002
+ "decision": decision,
1003
+ "small": small,
1004
+ "enhanced": enhanced,
1005
+ "alpha_small": alpha,
1006
+ "binary": binary,
1007
+ "braille": braille,
1008
+ "grid_size": grid_size,
1009
+ }
1010
+
1011
+
1012
+ # ============================================================
1013
+ # Debug output
1014
+ # ============================================================
1015
+
1016
+ def save_debug(result: dict, out_dir: Path) -> None:
1017
+ out_dir.mkdir(parents=True, exist_ok=True)
1018
+
1019
+ image = result["image"]
1020
+ d = result["decision"]
1021
+ maps = d["maps"]
1022
+
1023
+ overlay = image.convert("RGBA").copy()
1024
+ draw = ImageDraw.Draw(overlay)
1025
+
1026
+ draw.rectangle(d["visible_box"], outline=(0, 128, 255, 255), width=5)
1027
+ draw.rectangle(d["bbox"], outline=(0, 255, 0, 255), width=3)
1028
+ draw.rectangle(d["box"], outline=(255, 0, 0, 255), width=4)
1029
+
1030
+ overlay.save(out_dir / "decision_overlay.png")
1031
+
1032
+ def save_map(filename: str, arr: np.ndarray):
1033
+ Image.fromarray(np.clip(arr * 255, 0, 255).astype(np.uint8), mode="L").save(out_dir / filename)
1034
+
1035
+ save_map("attention_map.png", maps["attention"])
1036
+
1037
+ weighted_detail = (
1038
+ maps["edge_energy"]
1039
+ * (0.35 + 0.65 * maps["attention"])
1040
+ * maps["visible"].astype(np.float32)
1041
+ )
1042
+
1043
+ if weighted_detail.max() > 0:
1044
+ weighted_detail = weighted_detail / (weighted_detail.max() + 1e-9)
1045
+
1046
+ save_map("weighted_detail_map.png", weighted_detail)
1047
+ save_map("colorfulness_map.png", maps["colorfulness"])
1048
+
1049
+ Image.fromarray(d["mask"] * 255, mode="L").save(out_dir / "salient_mask.png")
1050
+
1051
+ if d["color_feature"] is not None:
1052
+ Image.fromarray(d["color_feature"]["mask"] * 255, mode="L").save(out_dir / "central_color_feature.png")
1053
+
1054
+ if d["outline_info"] is not None and "expanded_mask" in d["outline_info"]:
1055
+ Image.fromarray(d["outline_info"]["expanded_mask"] * 255, mode="L").save(out_dir / "color_outline_expanded.png")
1056
+
1057
+ result["small"].save(out_dir / "selected_64x64.png")
1058
+ Image.fromarray(result["binary"], mode="L").save(out_dir / "binary_64x64.png")
1059
+ Image.fromarray(np.clip(result["alpha_small"], 0, 255).astype(np.uint8), mode="L").save(out_dir / "alpha_64x64.png")
1060
+ (out_dir / "braille.txt").write_text(result["braille"], encoding="utf-8")
1061
+
1062
+
1063
+ # ============================================================
1064
+ # Dataset ingestion helpers
1065
+ # ============================================================
1066
+
1067
+ def image_from_value(value: Any, base_dir: Path | None = None) -> tuple[Image.Image, str | None]:
1068
+ """
1069
+ Supports:
1070
+ - local file path string
1071
+ - PIL Image
1072
+ - numpy array
1073
+ - dicts like {"path": ...} or {"bytes": ...}
1074
+ """
1075
+ if isinstance(value, Image.Image):
1076
+ return value.convert("RGBA"), None
1077
+
1078
+ if isinstance(value, np.ndarray):
1079
+ return Image.fromarray(value).convert("RGBA"), None
1080
+
1081
+ if isinstance(value, dict):
1082
+ if "bytes" in value and value["bytes"] is not None:
1083
+ raw = value["bytes"]
1084
+ return Image.open(io.BytesIO(raw)).convert("RGBA"), value.get("path")
1085
+ for key in ("path", "file_name", "filename", "filepath", "image_path"):
1086
+ if key in value and value[key]:
1087
+ p = Path(value[key])
1088
+ if base_dir is not None and not p.is_absolute():
1089
+ p = base_dir / p
1090
+ return load_image(p), str(p)
1091
+
1092
+ if isinstance(value, (str, Path)):
1093
+ p = Path(value)
1094
+ if base_dir is not None and not p.is_absolute():
1095
+ p = base_dir / p
1096
+ return load_image(p), str(p)
1097
+
1098
+ raise ValueError(f"Unsupported image value type: {type(value)}")
1099
+
1100
+
1101
+ def iter_folder_images(folder: Path):
1102
+ for path in sorted(folder.rglob("*")):
1103
+ if path.is_file() and is_image_path(path):
1104
+ yield {
1105
+ "id": path.stem,
1106
+ "image_value": str(path),
1107
+ "source": str(path),
1108
+ "extra": {},
1109
+ }
1110
+
1111
+
1112
+ def iter_tabular_records(path: Path, image_column: str):
1113
+ import pandas as pd
1114
+
1115
+ suffix = path.suffix.lower()
1116
+
1117
+ if suffix == ".csv":
1118
+ df = pd.read_csv(path)
1119
+ elif suffix == ".tsv":
1120
+ df = pd.read_csv(path, sep="\t")
1121
+ elif suffix == ".parquet":
1122
+ df = pd.read_parquet(path)
1123
+ elif suffix == ".json":
1124
+ raw = json.loads(path.read_text(encoding="utf-8"))
1125
+ if isinstance(raw, dict):
1126
+ raw = raw.get("data", raw.get("items", raw))
1127
+ if isinstance(raw, dict):
1128
+ raw = [raw]
1129
+ df = pd.DataFrame(raw)
1130
+ elif suffix == ".jsonl":
1131
+ rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
1132
+ df = pd.DataFrame(rows)
1133
+ else:
1134
+ raise ValueError(f"Unsupported tabular file: {path}")
1135
+
1136
+ if image_column not in df.columns:
1137
+ fallback_cols = [c for c in df.columns if c.lower() in {"image", "image_path", "path", "file_name", "filename"}]
1138
+ if not fallback_cols:
1139
+ raise ValueError(f'Image column "{image_column}" not found.')
1140
+ image_column = fallback_cols[0]
1141
+
1142
+ base_dir = path.parent
1143
+
1144
+ for idx, row in df.iterrows():
1145
+ rec = row.to_dict()
1146
+ yield {
1147
+ "id": str(rec.get("id", idx)),
1148
+ "image_value": rec[image_column],
1149
+ "source": f"{path}:{idx}",
1150
+ "base_dir": base_dir,
1151
+ "extra": rec,
1152
+ }
1153
+
1154
+
1155
+ def iter_hf_dataset_repo(dataset_name: str, split: str, config_name: str | None):
1156
+ try:
1157
+ from datasets import load_dataset
1158
+ except Exception as e:
1159
+ raise RuntimeError("datasets is required for --hf-dataset. Install with: pip install datasets") from e
1160
+
1161
+ kwargs = {}
1162
+ if config_name:
1163
+ kwargs["name"] = config_name
1164
+
1165
+ ds = load_dataset(dataset_name, **kwargs, split=split)
1166
+
1167
+ image_column = None
1168
+ for c in ds.column_names:
1169
+ if c.lower() in {"image", "img"}:
1170
+ image_column = c
1171
+ break
1172
+ if image_column is None:
1173
+ for c in ds.column_names:
1174
+ if "image" in c.lower():
1175
+ image_column = c
1176
+ break
1177
+ if image_column is None:
1178
+ raise ValueError("Could not find an image column in the HF dataset.")
1179
+
1180
+ for idx, row in enumerate(ds):
1181
+ yield {
1182
+ "id": str(row.get("id", idx)),
1183
+ "image_value": row[image_column],
1184
+ "source": f"{dataset_name}:{split}:{idx}",
1185
+ "extra": dict(row),
1186
+ }
1187
+
1188
+
1189
+ def iter_hf_local_disk(path: Path):
1190
+ try:
1191
+ from datasets import load_from_disk
1192
+ except Exception as e:
1193
+ raise RuntimeError("datasets is required for --hf-load-from-disk. Install with: pip install datasets") from e
1194
+
1195
+ ds = load_from_disk(str(path))
1196
+
1197
+ if hasattr(ds, "keys"):
1198
+ if "train" in ds:
1199
+ ds = ds["train"]
1200
+ else:
1201
+ first_key = next(iter(ds.keys()))
1202
+ ds = ds[first_key]
1203
+
1204
+ image_column = None
1205
+ for c in ds.column_names:
1206
+ if c.lower() in {"image", "img"}:
1207
+ image_column = c
1208
+ break
1209
+ if image_column is None:
1210
+ for c in ds.column_names:
1211
+ if "image" in c.lower():
1212
+ image_column = c
1213
+ break
1214
+ if image_column is None:
1215
+ raise ValueError("Could not find an image column in the local HF dataset.")
1216
+
1217
+ for idx, row in enumerate(ds):
1218
+ yield {
1219
+ "id": str(row.get("id", idx)),
1220
+ "image_value": row[image_column],
1221
+ "source": f"{path}:{idx}",
1222
+ "extra": dict(row),
1223
+ }
1224
+
1225
+
1226
+ def pick_input_iterator(
1227
+ input_path: str | None,
1228
+ hf_dataset: str | None,
1229
+ hf_load_from_disk: str | None,
1230
+ image_column: str,
1231
+ ):
1232
+ if hf_dataset:
1233
+ return iter_hf_dataset_repo(hf_dataset, split="train", config_name=None)
1234
+
1235
+ if hf_load_from_disk:
1236
+ return iter_hf_local_disk(Path(hf_load_from_disk))
1237
+
1238
+ if input_path is None:
1239
+ raise ValueError("You must provide an input path or a Hugging Face dataset source.")
1240
+
1241
+ path = Path(input_path)
1242
+
1243
+ if path.is_dir():
1244
+ return iter_folder_images(path)
1245
+
1246
+ if path.is_file() and is_image_path(path):
1247
+ return [{
1248
+ "id": path.stem,
1249
+ "image_value": str(path),
1250
+ "source": str(path),
1251
+ "extra": {},
1252
+ }]
1253
+
1254
+ if path.is_file() and path.suffix.lower() in {".csv", ".tsv", ".json", ".jsonl", ".parquet"}:
1255
+ return iter_tabular_records(path, image_column=image_column)
1256
+
1257
+ raise ValueError(f"Unsupported input source: {input_path}")
1258
+
1259
+
1260
+ # ============================================================
1261
+ # Dataset row building
1262
+ # ============================================================
1263
+
1264
+ def make_dataset_row(
1265
+ item: dict,
1266
+ grid_size: int,
1267
+ alpha_threshold: int,
1268
+ ) -> dict:
1269
+ image, resolved_path = image_from_value(item["image_value"], base_dir=item.get("base_dir"))
1270
+ result = encode_image(image=image, grid_size=grid_size, alpha_threshold=alpha_threshold)
1271
+
1272
+ d = result["decision"]
1273
+ braille = result["braille"]
1274
+ meta = braille_metadata(braille)
1275
+
1276
+ width, height = result["image"].size
1277
+
1278
+ row = {
1279
+ "id": item.get("id"),
1280
+ "source": item.get("source"),
1281
+ "image": resolved_path if resolved_path is not None else None,
1282
+ "original_width": width,
1283
+ "original_height": height,
1284
+ "grid_size": grid_size,
1285
+ "braille_text": braille,
1286
+ **meta,
1287
+ "mode": d["mode"],
1288
+ "reason": d["reason"],
1289
+ "visible_box": list(map(int, d["visible_box"])),
1290
+ "selected_box": list(map(int, d["box"])),
1291
+ "salient_box": list(map(int, d["bbox"])),
1292
+ "metrics": {k: jsonable(v) for k, v in (d["metrics"] or {}).items()},
1293
+ "outline_info": {k: jsonable(v) for k, v in (d["outline_info"] or {}).items() if k != "expanded_mask"},
1294
+ }
1295
+
1296
+ extra = item.get("extra", {})
1297
+ for k, v in extra.items():
1298
+ if k not in row:
1299
+ row[k] = jsonable(v)
1300
+
1301
+ return row
1302
+
1303
+
1304
+ def build_dataset_rows(
1305
+ iterator,
1306
+ grid_size: int,
1307
+ alpha_threshold: int,
1308
+ limit: int | None = None,
1309
+ ):
1310
+ rows = []
1311
+ for idx, item in enumerate(iterator):
1312
+ if limit is not None and idx >= limit:
1313
+ break
1314
+ rows.append(make_dataset_row(item, grid_size=grid_size, alpha_threshold=alpha_threshold))
1315
+ return rows
1316
+
1317
+
1318
+ # ============================================================
1319
+ # Dataset export
1320
+ # ============================================================
1321
+
1322
+ def export_jsonl(rows: list[dict], out_path: Path):
1323
+ with out_path.open("w", encoding="utf-8") as f:
1324
+ for row in rows:
1325
+ f.write(json.dumps(row, ensure_ascii=False) + "\n")
1326
+
1327
+
1328
+ def export_csv(rows: list[dict], out_path: Path):
1329
+ import pandas as pd
1330
+
1331
+ serializable_rows = []
1332
+ for row in rows:
1333
+ new_row = {}
1334
+ for k, v in row.items():
1335
+ if isinstance(v, (list, dict)):
1336
+ new_row[k] = json.dumps(v, ensure_ascii=False)
1337
+ else:
1338
+ new_row[k] = v
1339
+ serializable_rows.append(new_row)
1340
+
1341
+ pd.DataFrame(serializable_rows).to_csv(out_path, index=False)
1342
+
1343
+
1344
+ def export_parquet(rows: list[dict], out_path: Path):
1345
+ import pandas as pd
1346
+
1347
+ serializable_rows = []
1348
+ for row in rows:
1349
+ new_row = {}
1350
+ for k, v in row.items():
1351
+ if isinstance(v, (list, dict)):
1352
+ new_row[k] = json.dumps(v, ensure_ascii=False)
1353
+ else:
1354
+ new_row[k] = v
1355
+ serializable_rows.append(new_row)
1356
+
1357
+ pd.DataFrame(serializable_rows).to_parquet(out_path, index=False)
1358
+
1359
+
1360
+ def rows_to_hf_dataset(rows: list[dict]):
1361
+ try:
1362
+ from datasets import Dataset, Image as HFImage
1363
+ except Exception as e:
1364
+ raise RuntimeError("datasets is required for HF export. Install with: pip install datasets") from e
1365
+
1366
+ ds = Dataset.from_list(rows)
1367
+
1368
+ # If the dataset has an image-path column with local files, cast it.
1369
+ if "image" in ds.column_names:
1370
+ image_values = ds["image"]
1371
+ if any(v for v in image_values if isinstance(v, str)):
1372
+ try:
1373
+ ds = ds.cast_column("image", HFImage())
1374
+ except Exception:
1375
+ pass
1376
+
1377
+ return ds
1378
+
1379
+
1380
+ def export_hf_disk(rows: list[dict], out_dir: Path):
1381
+ ds = rows_to_hf_dataset(rows)
1382
+ ds.save_to_disk(str(out_dir))
1383
+ return ds
1384
+
1385
+
1386
+ def push_hf_dataset(rows: list[dict], repo_id: str, private: bool = False):
1387
+ ds = rows_to_hf_dataset(rows)
1388
+ ds.push_to_hub(repo_id, private=private)
1389
+ return ds
1390
+
1391
+
1392
+ # ============================================================
1393
+ # CLI
1394
+ # ============================================================
1395
+
1396
+ def main():
1397
+ parser = argparse.ArgumentParser(
1398
+ description="v12 attention-based RGBA -> 64x64 -> Unicode Braille, with HF-friendly dataset export"
1399
+ )
1400
+
1401
+ parser.add_argument("input", nargs="?", help="Image file, folder, or table file")
1402
+ parser.add_argument("-o", "--output", default="braille_output.txt", help="Single-image text output path")
1403
+ parser.add_argument("--grid-size", type=int, default=64, help="Default: 64")
1404
+ parser.add_argument("--alpha-threshold", type=int, default=8)
1405
+ parser.add_argument("--save-debug", action="store_true")
1406
+ parser.add_argument("--debug-dir", default="braille_debug")
1407
+
1408
+ # Dataset / HF options
1409
+ parser.add_argument("--dataset-out", help="Write dataset rows to .jsonl / .csv / .parquet")
1410
+ parser.add_argument("--hf-out", help="Save as datasets.Dataset.save_to_disk(...) folder")
1411
+ parser.add_argument("--push-to-hub", help="Push the generated dataset to Hugging Face Hub")
1412
+ parser.add_argument("--private", action="store_true", help="Use with --push-to-hub")
1413
+ parser.add_argument("--image-column", default="image", help='For CSV/JSON/Parquet inputs. Default: "image"')
1414
+ parser.add_argument("--limit", type=int, default=None, help="Optional row limit for dataset mode")
1415
+
1416
+ # HF input
1417
+ parser.add_argument("--hf-dataset", help="Load a Hugging Face dataset repo, e.g. user/dataset")
1418
+ parser.add_argument("--hf-load-from-disk", help="Load a dataset previously saved with save_to_disk(...)")
1419
+ parser.add_argument("--split", default="train", help="HF split. Default: train")
1420
+ parser.add_argument("--config-name", default=None, help="Optional HF config/subset name")
1421
+
1422
+ args = parser.parse_args()
1423
+
1424
+ # -----------------------------------------
1425
+ # Single-image mode
1426
+ # -----------------------------------------
1427
+ if (
1428
+ args.input
1429
+ and Path(args.input).is_file()
1430
+ and is_image_path(args.input)
1431
+ and not args.dataset_out
1432
+ and not args.hf_out
1433
+ and not args.push_to_hub
1434
+ and not args.hf_dataset
1435
+ and not args.hf_load_from_disk
1436
+ ):
1437
+ result = encode_image(
1438
+ image_path=args.input,
1439
+ grid_size=args.grid_size,
1440
+ alpha_threshold=args.alpha_threshold,
1441
+ )
1442
+
1443
+ Path(args.output).write_text(result["braille"], encoding="utf-8")
1444
+
1445
+ d = result["decision"]
1446
+
1447
+ print(result["braille"])
1448
+ print()
1449
+ print("Mode:", d["mode"])
1450
+ print("Reason:", d["reason"])
1451
+ print("Visible box:", d["visible_box"])
1452
+ print("Selected box:", d["box"])
1453
+
1454
+ if d["outline_info"] is not None:
1455
+ print("Color outline layers:", d["outline_info"]["outline_layers"])
1456
+ print("Pixels per outline layer:", d["outline_info"]["layer_px"])
1457
+
1458
+ if d["metrics"] is not None:
1459
+ m = d["metrics"]
1460
+ print("Strong saliency retained:", f'{m["strong_saliency_coverage"] * 100:.1f}%')
1461
+ print("Saliency retained:", f'{m["saliency_coverage"] * 100:.1f}%')
1462
+ print("Weighted detail retained:", f'{m["detail_coverage"] * 100:.1f}%')
1463
+ print("Visible pixels retained:", f'{m["visible_pixel_coverage"] * 100:.1f}%')
1464
+ print("Resolution gain:", f'{m["resolution_gain"]:.2f}x')
1465
+
1466
+ print("Saved:", args.output)
1467
+
1468
+ if args.save_debug:
1469
+ save_debug(result, Path(args.debug_dir))
1470
+ print("Debug files:", args.debug_dir)
1471
+
1472
+ return
1473
+
1474
+ # -----------------------------------------
1475
+ # Dataset mode
1476
+ # -----------------------------------------
1477
+ if args.hf_dataset:
1478
+ iterator = iter_hf_dataset_repo(args.hf_dataset, split=args.split, config_name=args.config_name)
1479
+ elif args.hf_load_from_disk:
1480
+ iterator = iter_hf_local_disk(Path(args.hf_load_from_disk))
1481
+ else:
1482
+ iterator = pick_input_iterator(
1483
+ input_path=args.input,
1484
+ hf_dataset=None,
1485
+ hf_load_from_disk=None,
1486
+ image_column=args.image_column,
1487
+ )
1488
+
1489
+ rows = build_dataset_rows(
1490
+ iterator,
1491
+ grid_size=args.grid_size,
1492
+ alpha_threshold=args.alpha_threshold,
1493
+ limit=args.limit,
1494
+ )
1495
+
1496
+ if args.dataset_out:
1497
+ out_path = Path(args.dataset_out)
1498
+ suffix = out_path.suffix.lower()
1499
+ if suffix == ".jsonl":
1500
+ export_jsonl(rows, out_path)
1501
+ elif suffix == ".csv":
1502
+ export_csv(rows, out_path)
1503
+ elif suffix == ".parquet":
1504
+ export_parquet(rows, out_path)
1505
+ else:
1506
+ raise ValueError("dataset-out must end with .jsonl, .csv, or .parquet")
1507
+ print("Saved dataset rows:", out_path)
1508
+
1509
+ ds = None
1510
+
1511
+ if args.hf_out:
1512
+ ds = export_hf_disk(rows, Path(args.hf_out))
1513
+ print("Saved HF dataset to disk:", args.hf_out)
1514
+
1515
+ if args.push_to_hub:
1516
+ ds = push_hf_dataset(rows, args.push_to_hub, private=args.private)
1517
+ print("Pushed HF dataset:", args.push_to_hub)
1518
+
1519
+ if not args.dataset_out and not args.hf_out and not args.push_to_hub:
1520
+ # Default dataset fallback: write JSONL if not explicitly specified.
1521
+ out_path = Path("braille_dataset.jsonl")
1522
+ export_jsonl(rows, out_path)
1523
+ print("Saved dataset rows:", out_path)
1524
+
1525
+ print("Rows:", len(rows))
1526
+ if rows:
1527
+ print("Braille size:", f'{rows[0]["braille_width_chars"]}x{rows[0]["braille_height_chars"]} chars')
1528
+ print("Source grid:", f'{rows[0]["grid_size"]}x{rows[0]["grid_size"]}')
1529
+
1530
+
1531
+ if __name__ == "__main__":
1532
+ main()
unified/ablations/unified-v1-blt-none-s42/source/canvas_flow.py ADDED
@@ -0,0 +1,1025 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CanvasFlow Lab: independently implemented discrete-flow canvas experiments.
2
+
3
+ No recurrent-depth weight sharing. Image locations are arbitrary masks. Text is
4
+ an ordered byte lane; geometry supplies capacity coordinates, the final renderer
5
+ supplies exact word-wrapped coordinates. See the notebook for the distinction.
6
+ """
7
+ from __future__ import annotations
8
+ import argparse, contextlib, copy, hashlib, html, json, math, os, random, re
9
+ import tempfile, time, warnings, pickle, inspect
10
+ from dataclasses import asdict, dataclass, replace
11
+ from pathlib import Path
12
+ import numpy as np
13
+ from PIL import Image, ImageDraw
14
+ import torch
15
+ from torch import nn
16
+ import torch.nn.functional as F
17
+
18
+ FREE, TEXT, IMAGE, RESERVED, UNKNOWN = range(5)
19
+ EOS, PAD, MASK = 256, 257, 258
20
+ BYTE_VOCAB = 259
21
+ SCHEMA = 1
22
+ FAMILIES = ('shapes', 'tree', 'house', 'fish', 'rocket', 'chart', 'waves', 'terrain', 'network', 'texture')
23
+ POLICIES = ('row', 'columns', 'reverse_columns')
24
+ DOTS = ((0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (3, 0), (3, 1))
25
+
26
+ def digest(obj):
27
+ return hashlib.sha256(json.dumps(obj, sort_keys=True).encode()).hexdigest()
28
+
29
+ def atomic_json(path, obj):
30
+ path = Path(path); path.parent.mkdir(parents=True, exist_ok=True)
31
+ tmp = path.with_suffix(path.suffix + '.tmp')
32
+ tmp.write_text(json.dumps(obj, indent=2, allow_nan=False)); os.replace(tmp, path)
33
+
34
+ def seed_all(seed):
35
+ random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
36
+ if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
37
+
38
+ def pack_braille(bitmap):
39
+ a = np.asarray(bitmap, dtype=bool)
40
+ if a.ndim != 2 or a.shape[0] % 4 or a.shape[1] % 2:
41
+ raise ValueError('Braille bitmap must have height divisible by 4 and width by 2')
42
+ out = np.zeros((a.shape[0] // 4, a.shape[1] // 2), np.uint8)
43
+ for bit, (y, x) in enumerate(DOTS): out |= a[y::4, x::2].astype(np.uint8) << bit
44
+ return out
45
+
46
+ def unpack_braille(cells):
47
+ a = np.asarray(cells, np.uint8); out = np.zeros((a.shape[0] * 4, a.shape[1] * 2), bool)
48
+ for bit, (y, x) in enumerate(DOTS): out[y::4, x::2] = ((a >> bit) & 1).astype(bool)
49
+ return out
50
+
51
+ def dilate(mask, radius=1):
52
+ mask = np.asarray(mask, bool)
53
+ if radius < 0: raise ValueError('negative margin')
54
+ p = np.pad(mask, radius)
55
+ out = np.zeros_like(mask)
56
+ for dy in range(2 * radius + 1):
57
+ for dx in range(2 * radius + 1): out |= p[dy:dy+mask.shape[0], dx:dx+mask.shape[1]]
58
+ return out
59
+
60
+ def flow_runs(allowed, policy='columns', region_order=None):
61
+ """Horizontal runs linked only through unambiguous overlap across rows.
62
+
63
+ Split/merge events start new regions: left/right columns beside an obstacle
64
+ do not collapse into one region merely because they connect below it.
65
+ region_order can explicitly supply any permutation of the resulting regions.
66
+ """
67
+ allowed = np.asarray(allowed, bool); runs = []; previous = []; next_region = 0
68
+ for y, row in enumerate(allowed):
69
+ edges = np.flatnonzero(np.diff(np.r_[False, row, False].astype(np.int8)))
70
+ current = [(y, int(a), int(b)) for a, b in zip(edges[::2], edges[1::2])]
71
+ overlap = [[j for j, p in enumerate(previous) if max(a, p[1]) < min(b, p[2])]
72
+ for _, a, b in current]
73
+ uses = [sum(j in js for js in overlap) for j in range(len(previous))]
74
+ tagged = []
75
+ for r, js in zip(current, overlap):
76
+ if len(js) == 1 and uses[js[0]] == 1: region = previous[js[0]][3]
77
+ else: region = next_region; next_region += 1
78
+ tagged.append((*r, region))
79
+ runs.extend(tagged); previous = tagged
80
+ if policy == 'row' and region_order is None: return runs
81
+ first = {}
82
+ for y, a, b, region in runs: first.setdefault(region, (y, a))
83
+ if region_order is not None:
84
+ if sorted(region_order) != sorted(first): raise ValueError('region_order must be a permutation')
85
+ rank = {r: i for i, r in enumerate(region_order)}
86
+ else:
87
+ if policy not in POLICIES: raise ValueError(policy)
88
+ sign = -1 if policy == 'reverse_columns' else 1
89
+ # Regions beginning on the same row are columns; merging regions follow.
90
+ order = sorted(first, key=lambda r: (first[r][0], sign * first[r][1]))
91
+ rank = {r: i for i, r in enumerate(order)}
92
+ return sorted(runs, key=lambda r: (rank[r[3]], r[0], r[1]))
93
+
94
+ def capacity_map(layout, length, policy='columns', region_order=None):
95
+ """Content-independent, provisional coordinates. NEVER derived from targets."""
96
+ coords = np.zeros((length, 2), np.int16); regions = np.zeros(length, np.int16)
97
+ valid = np.zeros(length, bool); k = 0
98
+ for y, a, b, region in flow_runs(np.asarray(layout) == TEXT, policy, region_order):
99
+ n = min(b - a, length - k)
100
+ if n <= 0: break
101
+ coords[k:k+n, 0] = y; coords[k:k+n, 1] = np.arange(a, a+n)
102
+ regions[k:k+n] = region + 1; valid[k:k+n] = True; k += n
103
+ return coords, regions, valid
104
+
105
+ def wrap_words(text, layout, policy='columns', region_order=None):
106
+ """ASCII wrapper. Preserves normalized byte order and never crosses obstacles.
107
+
108
+ A word is moved intact to the next run that can hold it. Only words longer
109
+ than EVERY run are split. Whitespace is normalized explicitly. Overflow is
110
+ returned, never silently lost. Returned mapping is the exact rendered map.
111
+ """
112
+ text.encode('ascii', errors='strict')
113
+ normalized = ' '.join(text.split())
114
+ runs = flow_runs(np.asarray(layout) == TEXT, policy, region_order)
115
+ coords, indices, rendered = [], [], []
116
+ if not runs: return dict(text=normalized, chars='', coords=[], indices=[], overflow=normalized)
117
+ longest = max(b-a for _, a, b, _ in runs)
118
+ ri = 0; cursor = runs[0][1]; consumed = 0
119
+ for wi, word in enumerate(normalized.split(' ')):
120
+ if not word: continue
121
+ piece = (' ' if wi else '') + word
122
+ # Keep the actual separating space in the byte-to-cell map.
123
+ if len(piece) <= longest:
124
+ while ri < len(runs) and runs[ri][2] - cursor < len(piece):
125
+ ri += 1
126
+ if ri < len(runs): cursor = runs[ri][1]
127
+ if ri == len(runs): break
128
+ for ch in piece:
129
+ while ri < len(runs) and cursor >= runs[ri][2]:
130
+ ri += 1
131
+ if ri < len(runs): cursor = runs[ri][1]
132
+ if ri == len(runs): break
133
+ coords.append((runs[ri][0], cursor)); indices.append(consumed)
134
+ rendered.append(ch); cursor += 1; consumed += 1
135
+ if ri == len(runs): break
136
+ return dict(text=normalized, chars=''.join(rendered), coords=coords,
137
+ indices=indices, overflow=normalized[consumed:])
138
+
139
+ def draw_picture(rng, family, side=64):
140
+ """Original procedural binary graphics, not downloaded or generated captions."""
141
+ im = Image.new('L', (side, side), 0); d = ImageDraw.Draw(im)
142
+ p = lambda x: int(x * side / 64)
143
+ def line(points, width=2): d.line([(p(x), p(y)) for x,y in points], fill=255, width=max(1,p(width)))
144
+ if family == 'tree':
145
+ d.polygon([(p(32),p(3)),(p(8),p(42)),(p(56),p(42))], fill=255)
146
+ d.rectangle((p(28),p(39),p(36),p(61)),fill=255)
147
+ elif family == 'house':
148
+ d.polygon([(p(4),p(28)),(p(32),p(4)),(p(60),p(28))],fill=255)
149
+ d.rectangle((p(12),p(28),p(52),p(60)),outline=255,width=2)
150
+ d.rectangle((p(27),p(39),p(38),p(60)),outline=255,width=2)
151
+ d.rectangle((p(17),p(34),p(23),p(42)),fill=255)
152
+ elif family == 'fish':
153
+ d.ellipse((p(5),p(15),p(48),p(48)), fill=255)
154
+ d.polygon([(p(43),p(31)),(p(61),p(15)),(p(61),p(49))],fill=255)
155
+ d.ellipse((p(13),p(24),p(18),p(29)),fill=0)
156
+ elif family == 'rocket':
157
+ d.polygon([(p(32),p(2)),(p(43),p(24)),(p(43),p(48)),(p(21),p(48)),(p(21),p(24))],fill=255)
158
+ d.ellipse((p(27),p(23),p(37),p(33)),fill=0)
159
+ line([(24,50),(20,62)]); line([(32,50),(32,61)]); line([(40,50),(44,62)])
160
+ elif family == 'chart':
161
+ line([(5,4),(5,58),(61,58)],1)
162
+ for j in range(5):
163
+ x = 10+j*10; h = int(rng.integers(5,48))
164
+ d.rectangle((p(x),p(57-h),p(x+6),p(57)),fill=255)
165
+ elif family == 'waves':
166
+ for j in range(4):
167
+ yy = 10+j*14 + np.sin(np.linspace(0, rng.uniform(5,15),side))*rng.uniform(2,5)
168
+ d.line(list(zip(range(side),[p(v) for v in yy])),fill=255,width=1)
169
+ elif family == 'terrain':
170
+ heights = rng.integers(12,42,9)
171
+ line([(i*8,int(y)) for i,y in enumerate(heights)])
172
+ line([(0,57),(64,57)],1); d.ellipse((p(44),p(2),p(55),p(13)),outline=255,width=2)
173
+ elif family == 'network':
174
+ pts = rng.integers(8,56,(7,2))
175
+ for a,b in zip(pts, np.roll(pts,1,axis=0)): line([a,b],1)
176
+ for x,y in pts: d.ellipse((p(x-3),p(y-3),p(x+3),p(y+3)),fill=255)
177
+ elif family == 'texture':
178
+ x,y=np.meshgrid(np.arange(side),np.arange(side))
179
+ arr = ((x//int(rng.integers(3,10)) + y//int(rng.integers(3,10)))%2)*255
180
+ return arr.astype(np.uint8)>0
181
+ else:
182
+ for _ in range(int(rng.integers(2,6))):
183
+ x,y = rng.integers(2,40,2); w,h = rng.integers(8,23,2)
184
+ box=(p(x),p(y),p(x+w),p(y+h))
185
+ if rng.random()<.5: d.ellipse(box,outline=255,width=2)
186
+ else: d.rectangle(box,outline=255,width=2)
187
+ if rng.random()<.5: im=im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
188
+ # Shift the drawing within its source crop, not just the canvas placement.
189
+ a=np.asarray(im)>127
190
+ return np.roll(a, tuple(rng.integers(-3,4,2)),axis=(0,1))
191
+
192
+ def source_text(rng, families, scene_id):
193
+ subjects = ', '.join(families) if families else 'an empty page'
194
+ verbs = ('examined','described','compared','sketched','measured','revisited')
195
+ names = ('Mira','Leo','Ada','Noah','Iris','Omar','Lina','Theo')
196
+ name = str(rng.choice(names)); verb = str(rng.choice(verbs)); n=int(rng.integers(2,40))
197
+ types = [
198
+ f'{name} {verb} {subjects}. The picture belongs to study {scene_id}. The notes stay in reading order even when the page has several separate regions.',
199
+ f'Observation {scene_id}: {subjects}. We counted {n} marks in the draft and added {n+3} in the revision. The new total is {2*n+3}. This is a worked arithmetic example, not a count of visible objects.',
200
+ f'Field notes by {name}. First inspect {subjects}. Next compare the outline and the empty spaces. Finally write a short explanation. Keep every word away from the drawing.',
201
+ f'{name} opened a notebook at sunrise. A study of {subjects} filled part of the page. The remaining space held a short story about a careful observer and a surprising discovery.',
202
+ f'Record {scene_id}. Subject: {subjects}. Author: {name}. Revision: {n}. Status: reviewed. The diagram and the text are two views of the same small experiment.',
203
+ ]
204
+ text = str(rng.choice(types))
205
+ if rng.random()<.35: text += ' ' + str(rng.choice(types))
206
+ return text
207
+
208
+ def make_example(index, split='train', size=128, text_len=512, prompt_len=192, seed=731, encoding_override=None):
209
+ split_code={'train':0,'val':1,'test':2,'ood':3}[split]
210
+ rng=np.random.default_rng(np.random.SeedSequence([seed,split_code,index]))
211
+ kind=('text','image','mixed')[index%3]
212
+ encoding=int(rng.random()<.25) # 0=Braille bytes, 1=ASCII image glyphs
213
+ if encoding_override is not None:encoding=int(encoding_override)
214
+ policy=POLICIES[int(rng.integers(0,3))]
215
+ image=np.zeros((size,size),np.uint8); occupied=np.zeros((size,size),bool)
216
+ families=[]; objects=[]
217
+ if kind!='text':
218
+ count=int(rng.integers(1,4 if split!='ood' else 6))
219
+ for j in range(count):
220
+ family=str(rng.choice(FAMILIES)); families.append(family)
221
+ source_side=int(rng.choice([32,64,64,64,96,128]))
222
+ bitmap=draw_picture(rng,family,source_side)
223
+ if rng.random()<.3: bitmap=np.rot90(bitmap,int(rng.integers(1,4)))
224
+ cells=pack_braille(bitmap)
225
+ if encoding:
226
+ block=bitmap.reshape(source_side//4,4,source_side//2,2).mean(axis=(1,3))
227
+ ramp=np.frombuffer(b' .:-=+*#%@',np.uint8)
228
+ cells=ramp[np.round(block*(len(ramp)-1)).astype(int)]
229
+ ink=cells!=32
230
+ else: ink=cells!=0
231
+ # 64x64 source pixels -> 16x32 Braille cells. Small test canvases resize.
232
+ h=min(cells.shape[0],max(4,size//2)); w=min(cells.shape[1],max(4,size//2))
233
+ if (h,w)!=cells.shape:
234
+ cells=np.asarray(Image.fromarray(cells).resize((w,h),Image.Resampling.NEAREST))
235
+ ink=cells!=(32 if encoding else 0)
236
+ y=int(rng.integers(0,size-h+1)); x=int(rng.integers(0,size-w+1))
237
+ if split=='ood': # Unseen edge contacts and diagonal chained placement.
238
+ y=int((size-h)*j/max(1,count-1)); x=int((size-w)*(count-1-j)/max(1,count-1))
239
+ box=bool(rng.random()<.25)
240
+ mask=np.ones_like(ink) if box else ink
241
+ sub=image[y:y+h,x:x+w]; sub[mask]=cells[mask]
242
+ occupied[y:y+h,x:x+w]|=mask
243
+ objects.append(dict(family=family,y=y,x=x,h=h,w=w,box=box))
244
+ margin=int(rng.integers(1,4)); blocked=dilate(occupied,margin)
245
+ layout=np.full((size,size),FREE,np.uint8)
246
+ if kind!='image':
247
+ if objects and rng.random()<.8:
248
+ # Put the reading region around an image wherever it happens to be.
249
+ # Otherwise short 512-byte text at row zero would rarely test wrapping.
250
+ focus=objects[int(rng.integers(len(objects)))]; pad=int(rng.integers(4,17))
251
+ y0=max(0,focus['y']-2); y1=min(size,focus['y']+focus['h']+pad)
252
+ x0=max(0,focus['x']-pad); x1=min(size,focus['x']+focus['w']+pad)
253
+ elif rng.random()<.5:
254
+ y0=int(rng.integers(0,max(1,size//2))); y1=size
255
+ x0=int(rng.integers(0,max(1,size//3))); x1=size-int(rng.integers(0,max(1,size//4)))
256
+ else: y0,x0,y1,x1=0,0,size,size
257
+ layout[y0:y1,x0:x1]=TEXT
258
+ # Additional gutters can create disconnected text regions without image boxes.
259
+ if kind!='image' and rng.random()<.4:
260
+ mid=int(rng.integers(size//3,2*size//3)); layout[:,mid:mid+1]=RESERVED
261
+ layout[blocked]=RESERVED; layout[occupied]=IMAGE
262
+ text=source_text(rng,families,f'{split}-{index}') if kind!='image' else ''
263
+ text=text[:text_len-1]
264
+ if len(text)==text_len-1 and ' ' in text: text=text.rsplit(' ',1)[0]
265
+ # Short canvases have a real capacity constraint. Determine a fitting prefix.
266
+ rendered=wrap_words(text,layout,policy)
267
+ if rendered['overflow']: text=rendered['chars'].rstrip()
268
+ tokens=np.full(text_len,PAD,np.int16); b=text.encode('ascii')
269
+ tokens[:len(b)]=list(b); tokens[len(b)]=EOS
270
+ geometry='; '.join(f"{o['family']} at {o['x']},{o['y']}" for o in objects)
271
+ prompt=f"{kind}; {'ASCII' if encoding else 'Braille'}; order {policy}; margin {margin}; {geometry}. Write notes."[:prompt_len]
272
+ p=np.full(prompt_len,PAD,np.int16); p[:len(prompt)]=list(prompt.encode('ascii'))
273
+ coords,regions,capacity=capacity_map(layout,text_len,policy)
274
+ return dict(image=image,layout=layout,text=tokens,prompt=p,coords=coords,
275
+ regions=regions,capacity=capacity,encoding=np.int64(encoding),order=np.int64(POLICIES.index(policy)),
276
+ meta=dict(index=index,split=split,kind=kind,families=families,objects=objects,
277
+ text=text,prompt=prompt,policy=policy,margin=margin))
278
+
279
+ ARRAY_KEYS=('image','layout','text','prompt','coords','regions','capacity','encoding','order')
280
+
281
+ def build_data(root, count=4096, size=128, text_len=512, prompt_len=192, seed=731):
282
+ root=Path(root); root.mkdir(parents=True,exist_ok=True)
283
+ generator_source='\n'.join(inspect.getsource(fn) for fn in
284
+ (pack_braille,dilate,flow_runs,capacity_map,wrap_words,draw_picture,source_text,make_example))
285
+ config=dict(schema=SCHEMA,count=count,size=size,text_len=text_len,prompt_len=prompt_len,seed=seed,
286
+ generator_sha=digest(dict(source=generator_source,families=FAMILIES,policies=POLICIES,dots=DOTS)))
287
+ manifest=root/'manifest.json'
288
+ if manifest.exists():
289
+ old=json.loads(manifest.read_text())
290
+ if old['config']!=config: raise ValueError('Dataset config/code mismatch; choose a fresh data directory')
291
+ for name,sha in old['sha256'].items():
292
+ if hashlib.sha256((root/name).read_bytes()).hexdigest()!=sha: raise ValueError(f'Data checksum mismatch: {name}')
293
+ return old
294
+ checksums={}; counts={'train':count,'val':max(32,count//16),'test':max(32,count//16),'ood':max(32,count//16)}
295
+ for split,n in counts.items():
296
+ arrays=None; metadata=[]
297
+ for i in range(n):
298
+ ex=make_example(i,split,size,text_len,prompt_len,seed)
299
+ if arrays is None: arrays={k:np.empty((n,*np.asarray(ex[k]).shape),np.asarray(ex[k]).dtype) for k in ARRAY_KEYS}
300
+ for k in ARRAY_KEYS: arrays[k][i]=ex[k]
301
+ metadata.append(ex['meta'])
302
+ for k,a in arrays.items():
303
+ name=f'{split}_{k}.npy'; tmp=root/(name+'.tmp')
304
+ with open(tmp,'wb') as f: np.save(f,a)
305
+ os.replace(tmp,root/name); checksums[name]=hashlib.sha256((root/name).read_bytes()).hexdigest()
306
+ name=f'{split}_meta.json'; atomic_json(root/name,metadata)
307
+ checksums[name]=hashlib.sha256((root/name).read_bytes()).hexdigest()
308
+ print(f'data {split}: {n} examples',flush=True)
309
+ result=dict(config=config,counts=counts,sha256=checksums,fingerprint=digest(checksums))
310
+ atomic_json(manifest,result); return result
311
+
312
+ class CanvasData:
313
+ def __init__(self, root, split='train', device='cpu', preload=False):
314
+ root=Path(root); self.manifest=json.loads((root/'manifest.json').read_text())
315
+ self.data={k:np.load(root/f'{split}_{k}.npy',mmap_mode='r') for k in ARRAY_KEYS}
316
+ self.n=len(self.data['text']); self.device=device
317
+ self.raster_cache=None
318
+ if preload:
319
+ self.data={k:torch.from_numpy(np.array(v)).to(device) for k,v in self.data.items()}
320
+ def batch(self, indices, variant='full'):
321
+ idx=np.asarray(indices)%self.n
322
+ out={k:(v[torch.as_tensor(idx,device=v.device)].long() if torch.is_tensor(v)
323
+ else torch.as_tensor(np.array(v[idx]),device=self.device).long()) for k,v in self.data.items()}
324
+ if variant=='raster':
325
+ if self.raster_cache is None:
326
+ # Build ONCE, outside steady-state timings after the first batch.
327
+ layouts=self.data['layout'].cpu().numpy() if torch.is_tensor(self.data['layout']) else self.data['layout']
328
+ maps=[capacity_map(x,out['text'].shape[1],'row') for x in layouts]
329
+ self.raster_cache=tuple(torch.as_tensor(np.stack([x[j] for x in maps]),device=self.device).long() for j in (0,1))
330
+ out['coords']=self.raster_cache[0][torch.as_tensor(idx,device=self.device)]
331
+ out['regions']=self.raster_cache[1][torch.as_tensor(idx,device=self.device)]
332
+ return out
333
+
334
+ def decode_text(tokens):
335
+ values=[]
336
+ for t in np.asarray(tokens).reshape(-1):
337
+ if t==EOS: break
338
+ if 0<=t<256: values.append(int(t))
339
+ return bytes(values).decode('ascii',errors='replace')
340
+
341
+ def page_html(image, layout, text, encoding=0, policy='columns', title='Canvas'):
342
+ rendered=wrap_words(text,layout,policy); size=len(layout)
343
+ page=np.full((size,size),' ',dtype='<U1')
344
+ for y,x in np.argwhere(layout==IMAGE):
345
+ v=int(image[y,x]); page[y,x]=chr(0x2800+v) if not encoding else chr(v if 32<=v<127 else 63)
346
+ for (y,x),ch in zip(rendered['coords'],rendered['chars']): page[y,x]=ch
347
+ body='\n'.join(''.join(row) for row in page)
348
+ return f'<section><h2>{html.escape(title)}</h2><pre>{html.escape(body)}</pre><p>Overflow: {html.escape(rendered["overflow"])}</p></section>'
349
+
350
+ def gallery(root, output, n=9, split='val'):
351
+ root=Path(root); meta=json.loads((root/f'{split}_meta.json').read_text())
352
+ images=np.load(root/f'{split}_image.npy',mmap_mode='r'); layouts=np.load(root/f'{split}_layout.npy',mmap_mode='r')
353
+ enc=np.load(root/f'{split}_encoding.npy',mmap_mode='r')
354
+ parts=[page_html(images[i],layouts[i],meta[i]['text'],int(enc[i]),meta[i]['policy'],meta[i]['prompt']) for i in range(min(n,len(meta)))]
355
+ Path(output).write_text('<!doctype html><meta charset="utf-8"><style>body{background:#111827;color:#e5e7eb;font:14px system-ui}section{margin:24px}pre{font:10px/12px monospace;white-space:pre;border:1px solid #475569;display:inline-block;padding:12px}h2{font-size:15px}</style>'+''.join(parts))
356
+
357
+ @dataclass
358
+ class Config:
359
+ size: int=128
360
+ text_len: int=512
361
+ prompt_len: int=192
362
+ dim: int=256
363
+ depth: int=6
364
+ heads: int=4
365
+ patch: int=4
366
+ local_dim: int=32
367
+ variant: str='full'
368
+ objective: str='dfm'
369
+ checkpoint: bool=False
370
+ def validate(self):
371
+ if self.size%self.patch or self.dim%self.heads or self.dim%2:
372
+ raise ValueError('Incompatible canvas/patch/head dimensions')
373
+ if self.variant not in ('full','no_flow','raster','patch8','mdlm'): raise ValueError(self.variant)
374
+ if self.objective not in ('dfm','mdlm'): raise ValueError(self.objective)
375
+
376
+ def positional(t, dim):
377
+ freq=torch.exp(-math.log(10000)*torch.arange(dim//2,device=t.device).float()/max(1,dim//2-1))
378
+ phase=t.float()[...,None]*freq
379
+ return torch.cat((phase.sin(),phase.cos()),-1)
380
+
381
+ class Block(nn.Module):
382
+ def __init__(self, dim, heads):
383
+ super().__init__(); self.heads=heads
384
+ self.n1=nn.LayerNorm(dim); self.n2=nn.LayerNorm(dim)
385
+ self.qkv=nn.Linear(dim,dim*3,bias=False); self.proj=nn.Linear(dim,dim,bias=False)
386
+ self.up=nn.Linear(dim,dim*8,bias=False); self.down=nn.Linear(dim*4,dim,bias=False)
387
+ def forward(self,h):
388
+ b,n,d=h.shape
389
+ q,k,v=self.qkv(self.n1(h)).reshape(b,n,3,self.heads,d//self.heads).permute(2,0,3,1,4).unbind(0)
390
+ # Fused SDPA chooses a supported kernel. No 16K x 16K full-canvas mask.
391
+ z=F.scaled_dot_product_attention(q,k,v,dropout_p=0.0,is_causal=False)
392
+ h=h+self.proj(z.transpose(1,2).reshape(b,n,d))
393
+ a,g=self.up(self.n2(h)).chunk(2,-1)
394
+ return h+self.down(F.silu(a)*g)
395
+
396
+ class CanvasModel(nn.Module):
397
+ """Two modes, same implementation: small layout planner or content denoiser.
398
+
399
+ Content uses a single shared byte embedding and transformer for the spatial
400
+ patches, linguistic byte lane, and byte prompt. Every block has unique weights.
401
+ """
402
+ def __init__(self,c,planner=False):
403
+ super().__init__(); c.validate(); self.c=c; self.planner=planner
404
+ self.byte=nn.Embedding(BYTE_VOCAB,c.dim)
405
+ self.local=nn.Linear(c.dim,c.local_dim,bias=False)
406
+ self.occupancy=nn.Embedding(5,c.local_dim)
407
+ self.to_patch=nn.Linear(c.local_dim*c.patch*c.patch,c.dim)
408
+ self.to_cells=nn.Linear(c.dim,c.local_dim*c.patch*c.patch)
409
+ self.x=nn.Embedding(c.size,c.dim); self.y=nn.Embedding(c.size,c.dim)
410
+ self.region=nn.Embedding(257,c.dim)
411
+ self.modality=nn.Embedding(4,c.dim)
412
+ self.format=nn.Embedding(2,c.dim)
413
+ self.time=nn.Sequential(nn.Linear(c.dim,c.dim),nn.SiLU(),nn.Linear(c.dim,c.dim))
414
+ self.blocks=nn.ModuleList([Block(c.dim,c.heads) for _ in range(c.depth)])
415
+ self.norm=nn.LayerNorm(c.dim)
416
+ self.local_norm=nn.LayerNorm(c.local_dim)
417
+ self.local_conv=nn.Conv2d(c.local_dim,c.local_dim,3,padding=1,groups=c.local_dim)
418
+ self.image_head=nn.Linear(c.local_dim,4 if planner else 256)
419
+ self.text_head=nn.Linear(c.dim,258,bias=False) if not planner else None
420
+ self.order_head=nn.Linear(c.dim,3) if planner else None
421
+ gy,gx=torch.meshgrid(torch.arange(c.size//c.patch)*c.patch+c.patch//2,
422
+ torch.arange(c.size//c.patch)*c.patch+c.patch//2,indexing='ij')
423
+ self.register_buffer('gx',gx.flatten(),persistent=False); self.register_buffer('gy',gy.flatten(),persistent=False)
424
+ self.apply(self._init)
425
+ for block in self.blocks:
426
+ nn.init.normal_(block.proj.weight,std=.02/math.sqrt(2*c.depth))
427
+ nn.init.normal_(block.down.weight,std=.02/math.sqrt(2*c.depth))
428
+ @staticmethod
429
+ def _init(m):
430
+ if isinstance(m,(nn.Linear,nn.Embedding)):
431
+ nn.init.normal_(m.weight,std=.02)
432
+ if isinstance(m,nn.Linear) and m.bias is not None: nn.init.zeros_(m.bias)
433
+ def forward(self,image,text,layout,prompt,coords,regions,encoding,t):
434
+ c=self.c; b=image.shape[0]; s=c.size; p=c.patch; g=s//p
435
+ local=self.local(self.byte(image))+self.occupancy(layout)
436
+ patches=local.reshape(b,g,p,g,p,c.local_dim).permute(0,1,3,2,4,5).reshape(b,g*g,-1)
437
+ spatial=self.to_patch(patches)+self.x(self.gx)+self.y(self.gy)+self.modality.weight[0]+self.format(encoding)[:,None]
438
+ words=self.byte(text)+self.modality.weight[1]
439
+ if not self.planner:
440
+ words=words+self.y(coords[...,0])+self.x(coords[...,1])
441
+ if c.variant!='no_flow':
442
+ flow=positional(torch.arange(c.text_len,device=image.device),c.dim)
443
+ words=words+flow[None]
444
+ words=words+self.region(regions.clamp(0,256))
445
+ else:
446
+ words=words+positional(torch.arange(c.text_len,device=image.device),c.dim)[None]
447
+ condition=self.byte(prompt)+self.modality.weight[2]
448
+ condition=condition+positional(torch.arange(c.prompt_len,device=image.device),c.dim)[None]
449
+ h=torch.cat((spatial,words,condition),1)+self.time(positional(t*1000,c.dim))[:,None]
450
+ for block in self.blocks:
451
+ if c.checkpoint and self.training:
452
+ from torch.utils.checkpoint import checkpoint
453
+ h=checkpoint(block,h,use_reentrant=False)
454
+ else: h=block(h)
455
+ h=self.norm(h)
456
+ z=self.to_cells(h[:,:g*g]).reshape(b,g,g,p,p,c.local_dim).permute(0,1,3,2,4,5).reshape(b,s,s,c.local_dim)
457
+ z=z+local
458
+ z=z+self.local_conv(z.permute(0,3,1,2)).permute(0,2,3,1)
459
+ img=self.image_head(self.local_norm(z))
460
+ if self.planner:
461
+ return img,self.order_head(h[:,-c.prompt_len:].mean(1))
462
+ return img,self.text_head(h[:,g*g:g*g+c.text_len])
463
+
464
+ def stratified_time(batch,device):
465
+ # Forward time: 0=all masked, 1=data. A random shift stratifies the batch.
466
+ return ((torch.rand((),device=device)+torch.arange(batch,device=device)/batch)%1)*.998+.001
467
+
468
+ def conditioning(batch):
469
+ """Task dropout: joint / image->text / text->image / arbitrary infill."""
470
+ img=batch['image']; txt=batch['text']; device=img.device; b=len(img)
471
+ mode=torch.randint(0,4,(b,),device=device)
472
+ has_image=(batch['layout']==IMAGE).flatten(1).any(-1)
473
+ has_text=(batch['layout']==TEXT).flatten(1).any(-1)
474
+ # Do not ask a text-only page to generate an absent image (or vice versa).
475
+ mode=torch.where(((mode==2)&~has_image)|((mode==1)&~has_text),0,mode)
476
+ ki=(mode==1)[:,None,None].expand_as(img).clone()
477
+ kt=(mode==2)[:,None].expand_as(txt).clone()
478
+ ki|=(mode==3)[:,None,None] & (torch.rand(img.shape,device=device)<.35)
479
+ kt|=(mode==3)[:,None] & (torch.rand(txt.shape,device=device)<.35)
480
+ ki&=batch['layout']==IMAGE
481
+ kt|=(~has_text)[:,None]
482
+ return ki,kt
483
+
484
+ def masked_ce(logits,target,masked,eligible,t,objective,weights=None):
485
+ """Explicit modality-normalized posterior CE, optionally 1/(1-t) weighted.
486
+
487
+ MDLM mode uses the absorbing linear-path time weight; optional class/PAD
488
+ balancing makes the overall canvas objective a surrogate, not a reported ELBO.
489
+ """
490
+ ce=F.cross_entropy(logits.float().reshape(-1,logits.shape[-1]),target.reshape(-1),reduction='none').reshape_as(target)
491
+ w=eligible.float() if weights is None else eligible.float()*weights
492
+ den=w.flatten(1).sum(-1).clamp_min(1)
493
+ value=(ce*w*masked).flatten(1).sum(-1)/den
494
+ if objective=='mdlm': value=value/(1-t)
495
+ return value.mean()
496
+
497
+ def make_loss(model,batch,planner=False,objective='dfm',fixed_t=None):
498
+ b=len(batch['image']); dev=batch['image'].device
499
+ t=stratified_time(b,dev) if fixed_t is None else torch.full((b,),fixed_t,device=dev)
500
+ ki,kt=conditioning(batch); active=batch['layout']==IMAGE
501
+ im=torch.where(ki,batch['image'],MASK); txt=torch.where(kt,batch['text'],MASK)
502
+ if planner:
503
+ # Observed symbols constrain occupancy; unknown cells carry no GT class.
504
+ known=ki.clone()
505
+ known|=torch.rand(active.shape,device=dev)<.10
506
+ noise=(torch.rand(active.shape,device=dev)>t[:,None,None]) & ~known
507
+ layout=torch.where(noise,UNKNOWN,batch['layout'])
508
+ logits,order=model(im,txt,layout,batch['prompt'],batch['coords'],batch['regions'],batch['encoding'],t)
509
+ weights=torch.tensor([1.,1.,6.,2.],device=dev)[batch['layout']]
510
+ loss=masked_ce(logits,batch['layout'],noise,~known,t,objective,weights)
511
+ loss=loss+.15*F.cross_entropy(order.float(),batch['order'])
512
+ return loss
513
+ mi=(torch.rand(active.shape,device=dev)>t[:,None,None])&~ki&active
514
+ mt=(torch.rand(txt.shape,device=dev)>t[:,None])&~kt
515
+ im=torch.where(mi,MASK,batch['image']); im=torch.where(active,im,PAD)
516
+ txt=torch.where(mt,MASK,batch['text'])
517
+ il,tl=model(im,txt,batch['layout'],batch['prompt'],batch['coords'],batch['regions'],batch['encoding'],t)
518
+ text_weight=torch.where(batch['text']==PAD,.05,1.)
519
+ li=masked_ce(il,batch['image'],mi,active&~ki,t,objective)
520
+ lt=masked_ce(tl,batch['text'],mt,~kt,t,objective,text_weight)
521
+ return li+lt
522
+
523
+ def autocast(device,enabled=True):
524
+ return torch.autocast('cuda',dtype=torch.bfloat16,enabled=enabled) if str(device).startswith('cuda') else contextlib.nullcontext()
525
+
526
+ def sample_categorical(logits,temperature=1.,greedy=False):
527
+ if greedy or temperature==0: return logits.argmax(-1)
528
+ probs=(logits.float()/temperature).softmax(-1)
529
+ return torch.multinomial(probs.reshape(-1,probs.shape[-1]),1).reshape(probs.shape[:-1])
530
+
531
+ @torch.no_grad()
532
+ def sample_layout(model,batch,steps=8,known_layout=None,known_image=None,known_text=None,greedy=False):
533
+ if steps<1: raise ValueError('At least one sampling step is required')
534
+ c=model.c; dev=batch['image'].device; b=len(batch['image'])
535
+ # A known layout uses UNKNOWN at unconstrained cells, including free cells.
536
+ lay=torch.full_like(batch['layout'],UNKNOWN) if known_layout is None else known_layout.clone()
537
+ locked=lay!=UNKNOWN
538
+ im=torch.full_like(batch['image'],MASK) if known_image is None else known_image
539
+ txt=torch.full_like(batch['text'],MASK) if known_text is None else known_text
540
+ was=model.training; model.eval()
541
+ for i in range(steps):
542
+ t=torch.full((b,),i/steps,device=dev)
543
+ with autocast(dev): logits,order=model(im,txt,lay,batch['prompt'],batch['coords'],batch['regions'],batch['encoding'],t)
544
+ proposals=sample_categorical(logits,greedy=greedy)
545
+ # Integrated linear-path reveal hazard: (t_next-t)/(1-t).
546
+ reveal=(torch.rand(lay.shape,device=dev)<1/(steps-i))&(lay==UNKNOWN)&~locked
547
+ lay=torch.where(reveal,proposals,lay)
548
+ model.train(was)
549
+ return lay,order.argmax(-1)
550
+
551
+ def remap_batch(batch,layout,order,variant='full'):
552
+ result=dict(batch); result['layout']=layout
553
+ maps=[capacity_map(l.cpu().numpy(),batch['text'].shape[1],
554
+ 'row' if variant=='raster' else POLICIES[int(o)]) for l,o in zip(layout,order)]
555
+ result['coords']=torch.as_tensor(np.stack([m[0] for m in maps]),device=layout.device).long()
556
+ result['regions']=torch.as_tensor(np.stack([m[1] for m in maps]),device=layout.device).long()
557
+ result['capacity']=torch.as_tensor(np.stack([m[2] for m in maps]),device=layout.device)
558
+ return result
559
+
560
+ @torch.no_grad()
561
+ def sample_content(model,batch,steps=8,known_image=None,known_text=None,greedy=False):
562
+ if steps<1: raise ValueError('At least one sampling step is required')
563
+ dev=batch['image'].device; b=len(batch['image']); active=batch['layout']==IMAGE
564
+ im=torch.full_like(batch['image'],MASK) if known_image is None else known_image.clone()
565
+ im=torch.where(active,im,PAD)
566
+ txt=torch.full_like(batch['text'],MASK) if known_text is None else known_text.clone()
567
+ no_text=~(batch['layout']==TEXT).flatten(1).any(-1)
568
+ empty=torch.full_like(txt,PAD); empty[:,0]=EOS
569
+ # A layout with no text region has an empty new text lane. Explicit supplied
570
+ # text is preserved so overflow can be reported rather than silently erased.
571
+ if known_text is None:
572
+ txt=torch.where(no_text[:,None],empty,txt)
573
+ else:
574
+ entirely_unknown=(known_text==MASK).all(-1)
575
+ txt=torch.where((no_text&entirely_unknown)[:,None],empty,txt)
576
+ was=model.training; model.eval()
577
+ for i in range(steps):
578
+ t=torch.full((b,),i/steps,device=dev)
579
+ with autocast(dev): il,tl=model(im,txt,batch['layout'],batch['prompt'],batch['coords'],batch['regions'],batch['encoding'],t)
580
+ # Image glyphs are printable ASCII; text remains unrestricted UTF-8 bytes.
581
+ allowed=torch.arange(256,device=dev)
582
+ il=il.float().masked_fill((batch['encoding']==1)[:,None,None,None]&((allowed<32)|(allowed>126)), -torch.inf)
583
+ tl=tl.float() # Preserve all 256 text bytes, EOS and PAD.
584
+ ip=sample_categorical(il,greedy=greedy); tp=sample_categorical(tl,greedy=greedy)
585
+ ri=(torch.rand(im.shape,device=dev)<1/(steps-i))&(im==MASK)&active
586
+ rt=(torch.rand(txt.shape,device=dev)<1/(steps-i))&(txt==MASK)
587
+ im=torch.where(ri,ip,im); txt=torch.where(rt,tp,txt)
588
+ model.train(was)
589
+ return torch.where(active,im,0),txt
590
+
591
+ def rng_state():
592
+ return dict(torch=torch.get_rng_state(),cuda=torch.cuda.get_rng_state_all() if torch.cuda.is_available() else [],
593
+ numpy=np.random.get_state(),python=random.getstate())
594
+
595
+ def restore_rng(state):
596
+ torch.set_rng_state(state['torch'].cpu()); np.random.set_state(state['numpy']); random.setstate(state['python'])
597
+ if state['cuda']: torch.cuda.set_rng_state_all([x.cpu() for x in state['cuda']])
598
+
599
+ def cpu_tree(x):
600
+ if torch.is_tensor(x): return x.detach().cpu().clone()
601
+ if isinstance(x,dict): return {k:cpu_tree(v) for k,v in x.items()}
602
+ if isinstance(x,list): return [cpu_tree(v) for v in x]
603
+ if isinstance(x,tuple): return tuple(cpu_tree(v) for v in x)
604
+ return x
605
+
606
+ def save_checkpoint(path,model,opt,step,signature,extra):
607
+ path=Path(path); path.parent.mkdir(parents=True,exist_ok=True)
608
+ payload=dict(model=cpu_tree(model.state_dict()),optimizer=cpu_tree(opt.state_dict()),step=step,
609
+ rng=rng_state(),signature=signature,extra=extra,config=asdict(model.c),planner=model.planner)
610
+ tmp=path.with_suffix('.tmp'); torch.save(payload,tmp)
611
+ # Previous generation survives an interrupted write or corrupt last file.
612
+ if path.exists(): os.replace(path,path.with_suffix('.previous.pt'))
613
+ os.replace(tmp,path)
614
+
615
+ def read_checkpoint(path,signature=None):
616
+ path=Path(path); errors=[]
617
+ for candidate in (path,path.with_suffix('.previous.pt')):
618
+ if not candidate.exists(): continue
619
+ try: value=torch.load(candidate,map_location='cpu',weights_only=False)
620
+ except (OSError,RuntimeError,EOFError,ValueError,IndexError,pickle.UnpicklingError) as e:
621
+ errors.append(str(e)); continue
622
+ if signature is not None and value['signature']!=signature:
623
+ raise ValueError('Checkpoint config/data/code/runtime differs. Use a new run directory.')
624
+ return value
625
+ raise RuntimeError(f'No readable checkpoint: {errors}')
626
+
627
+ def finite_tensors(tensors):
628
+ tensors=[x for x in tensors if x.numel()]
629
+ if not tensors: return True
630
+ # One host synchronization, not one per tensor.
631
+ return bool(torch.stack([torch.isfinite(x).all() for x in tensors]).all())
632
+
633
+ def finite_optimizer(opt):
634
+ return finite_tensors([v for s in opt.state.values() for v in s.values() if torch.is_tensor(v)])
635
+
636
+ def batch_indices(step,batch_size,accum,n,seed):
637
+ # A full deterministic permutation per epoch; resume depends only on accepted step.
638
+ ids=np.arange(step*batch_size*accum,(step+1)*batch_size*accum)
639
+ result=np.empty_like(ids)
640
+ for epoch in np.unique(ids//n):
641
+ sel=ids//n==epoch
642
+ perm=np.random.default_rng(np.random.SeedSequence([seed,int(epoch)])).permutation(n)
643
+ result[sel]=perm[ids[sel]%n]
644
+ return result.reshape(accum,batch_size)
645
+
646
+ def synchronize(device):
647
+ if str(device).startswith('cuda'): torch.cuda.synchronize()
648
+
649
+ def train(root,run,c,steps=300,batch_size=4,accum=1,lr=3e-4,seed=42,planner=False,
650
+ compile_model=False,preload=False,save_every=50,resume=True,device=None,
651
+ stop_after=None,inject_nonfinite_at=None,inject_poststep_at=None):
652
+ """Single-GPU training. No train-time diffusion rollouts; one time per sample.
653
+
654
+ stop_after is for exact-resume tests, and does not alter the LR schedule.
655
+ Fallback changes are counted, logged, and invalidate strict untouched comparisons.
656
+ """
657
+ device=device or ('cuda' if torch.cuda.is_available() else 'cpu')
658
+ run=Path(run); run.mkdir(parents=True,exist_ok=True); seed_all(seed)
659
+ if str(device).startswith('cuda'):
660
+ if not torch.cuda.is_bf16_supported(): raise RuntimeError('This profile requires CUDA BF16 support')
661
+ torch.set_float32_matmul_precision('high')
662
+ data=CanvasData(root,'train',device,preload)
663
+ c.validate(); model=CanvasModel(c,planner).to(device)
664
+ opt=torch.optim.AdamW(model.parameters(),lr=lr,betas=(.9,.95),weight_decay=.1,
665
+ fused=str(device).startswith('cuda'))
666
+ # Architecture-dependent parameter initialization must not change paired
667
+ # task/corruption draws across ablations with different projection sizes.
668
+ seed_all(seed+1_000_000)
669
+ signature=digest(dict(config=asdict(c),planner=planner,data=data.manifest['fingerprint'],steps=steps,
670
+ batch_size=batch_size,accum=accum,lr=lr,seed=seed,torch=torch.__version__,
671
+ device_type=str(device).split(':')[0],compile=compile_model,
672
+ code=hashlib.sha256(Path(__file__).read_bytes()).hexdigest()))
673
+ last=run/'last.pt'; start=0; retries=0; rollbacks=0; lr_mult=1.; ema=0.
674
+ if resume and last.exists():
675
+ state=read_checkpoint(last,signature); model.load_state_dict(state['model']); opt.load_state_dict(state['optimizer'])
676
+ restore_rng(state['rng']); start=state['step']; extra=state['extra']
677
+ retries=extra['retries']; rollbacks=extra['rollbacks']; lr_mult=extra['lr_mult']; ema=extra['grad_ema']
678
+ if start==steps and (run/'summary.json').exists():
679
+ saved=json.loads((run/'summary.json').read_text())
680
+ if saved.get('signature')==signature:
681
+ print(f'Already complete: {run}; retaining measured timing summary',flush=True)
682
+ return model,saved
683
+ elif last.exists(): raise FileExistsError('Refusing to overwrite existing run; use a fresh directory')
684
+ else:
685
+ save_checkpoint(last,model,opt,0,signature,dict(retries=0,rollbacks=0,lr_mult=1.,grad_ema=0.))
686
+ forward=torch.compile(model,dynamic=False) if compile_model else model
687
+ compile_active=compile_model
688
+ print(json.dumps(dict(run=str(run),planner=planner,params=sum(p.numel() for p in model.parameters()),
689
+ attention_tokens=(c.size//c.patch)**2+c.text_len+c.prompt_len,device=str(device),start=start)),flush=True)
690
+ step=start; wall=time.perf_counter(); timings=[]; injected=False; injected_post=False
691
+ log=run/'train.jsonl'
692
+ if str(device).startswith('cuda'): torch.cuda.reset_peak_memory_stats()
693
+ while step<steps and (stop_after is None or step<stop_after):
694
+ ids=batch_indices(step,batch_size,accum,data.n,seed)
695
+ batches=[data.batch(i,c.variant) for i in ids]
696
+ replay=rng_state(); base_lr=lr*min(1.,(step+1)/min(30,max(1,steps//10)))
697
+ base_lr*=.1+.9*.5*(1+math.cos(math.pi*step/max(1,steps)))
698
+ accepted=False; attempt_records=[]; synchronize(device); begin=time.perf_counter()
699
+ try:
700
+ for attempt,(amp,backoff,clip) in enumerate(((True,1.,1.),(True,.25,.5),(False,.1,.25))):
701
+ restore_rng(replay); opt.zero_grad(set_to_none=True); loss_value=0.
702
+ for group in opt.param_groups: group['lr']=base_lr*lr_mult*backoff
703
+ good=True
704
+ for batch in batches:
705
+ with autocast(device,amp): loss=make_loss(forward,batch,planner,c.objective)/accum
706
+ if not bool(torch.isfinite(loss)):
707
+ good=False; break
708
+ loss_value+=float(loss.detach()); loss.backward()
709
+ if inject_nonfinite_at==step and not injected:
710
+ next(p for p in model.parameters() if p.grad is not None).grad.flatten()[0]=torch.inf
711
+ injected=True
712
+ params=[p for p in model.parameters() if p.grad is not None]
713
+ good=good and finite_tensors([p.grad for p in params])
714
+ norm=float(torch.nn.utils.clip_grad_norm_(params,clip)) if good else float('inf')
715
+ spike=math.isfinite(norm) and norm>max(100.,20*ema)
716
+ if not good or not math.isfinite(norm) or (spike and attempt==0):
717
+ retries+=1; attempt_records.append(dict(attempt=attempt,reason='spike' if spike else 'nonfinite'))
718
+ continue
719
+ opt.step()
720
+ if inject_poststep_at==step and not injected_post:
721
+ with torch.no_grad(): next(model.parameters()).flatten()[0]=torch.nan
722
+ injected_post=True
723
+ if not finite_tensors(list(model.parameters())) or not finite_optimizer(opt):
724
+ raise FloatingPointError('Nonfinite parameter or optimizer state after update')
725
+ # Reduced LR affects this update only; all fallback events are counted.
726
+ accepted=True; ema=norm if ema==0 else .95*ema+.05*norm; break
727
+ if not accepted: raise FloatingPointError('All precision/clip/LR fallbacks exhausted')
728
+ except torch.OutOfMemoryError:
729
+ opt.zero_grad(set_to_none=True)
730
+ raise RuntimeError(f'CUDA OOM. Last known-good checkpoint: {last}. Reduce the common batch or enable checkpointing in a NEW suite; no examples were silently skipped.')
731
+ except FloatingPointError as exc:
732
+ rollbacks+=1
733
+ if rollbacks>3: raise RuntimeError(f'Recovery budget exhausted: {exc}') from exc
734
+ state=read_checkpoint(last,signature); model.load_state_dict(state['model']); opt.load_state_dict(state['optimizer'])
735
+ restore_rng(state['rng']); step=state['step']; ema=state['extra']['grad_ema']; lr_mult*=.25
736
+ with open(log,'a') as f: f.write(json.dumps(dict(event='rollback',step=step,reason=str(exc),lr_mult=lr_mult))+'\n')
737
+ continue
738
+ except Exception as exc:
739
+ if compile_active and (type(exc).__module__.startswith(('torch._dynamo','torch._inductor'))):
740
+ compile_active=False; forward=model; restore_rng(replay)
741
+ with open(log,'a') as f: f.write(json.dumps(dict(event='compile_fallback',reason=str(exc)[:300]))+'\n')
742
+ continue
743
+ raise
744
+ synchronize(device); elapsed=time.perf_counter()-begin
745
+ step+=1
746
+ if step>start+3: timings.append(elapsed)
747
+ record=dict(step=step,loss=loss_value,grad_norm=norm,seconds=elapsed,lr=opt.param_groups[0]['lr'],
748
+ retries=retries,rollbacks=rollbacks,attempts=attempt_records)
749
+ with open(log,'a') as f: f.write(json.dumps(record,allow_nan=False)+'\n')
750
+ if step==1 or step%25==0: print(json.dumps(record),flush=True)
751
+ if step%save_every==0 or step==steps or step==stop_after:
752
+ save_checkpoint(last,model,opt,step,signature,dict(retries=retries,rollbacks=rollbacks,lr_mult=lr_mult,grad_ema=ema))
753
+ summary=dict(config=asdict(c),planner=planner,seed=seed,step=step,target_steps=steps,params=sum(p.numel() for p in model.parameters()),
754
+ accepted_pages=step*batch_size*accum,batch_size=batch_size,accum=accum,
755
+ retries=retries,rollbacks=rollbacks,unchanged_recipe=retries==0 and rollbacks==0,
756
+ resume_from=start,session_seconds=time.perf_counter()-wall,
757
+ median_step_seconds=float(np.median(timings)) if timings else None,
758
+ pages_per_second=batch_size*accum/float(np.median(timings)) if timings else None,
759
+ peak_gpu_GB=torch.cuda.max_memory_allocated()/1e9 if str(device).startswith('cuda') else None,
760
+ compile_requested=compile_model,compile_active=compile_active,signature=signature,
761
+ data_fingerprint=data.manifest['fingerprint'],device=str(device),torch=torch.__version__)
762
+ atomic_json(run/'summary.json',summary)
763
+ return model,summary
764
+
765
+ def load_model(path,device=None):
766
+ device=device or ('cuda' if torch.cuda.is_available() else 'cpu')
767
+ ck=read_checkpoint(path); c=Config(**ck['config']); model=CanvasModel(c,ck['planner']).to(device)
768
+ model.load_state_dict(ck['model']); model.eval(); return model
769
+
770
+ @torch.no_grad()
771
+ def evaluate(model,data,planner_model=None,batches=4,batch_size=2,steps=8):
772
+ """Loss probes and true rollout metrics. Oracle geometry is labeled explicitly."""
773
+ was=model.training; model.eval(); device=next(model.parameters()).device
774
+ losses=[]; text_hits=0; text_count=0; image_hits=0; image_count=0
775
+ dot_correct=0; dot_count=0; known_checks=0; known_total=0; overflow=0
776
+ intersections=0; unions=0; generated_pages=0; invalid_padding=0; elapsed=0.
777
+ prior=rng_state(); seed_all(2029)
778
+ try:
779
+ for i in range(batches):
780
+ b=data.batch(np.arange(i*batch_size,(i+1)*batch_size),model.c.variant)
781
+ for t in (.1,.5,.9):
782
+ # A common unweighted probe is comparable across training objectives.
783
+ with autocast(device): losses.append(float(make_loss(model,b,False,'dfm',t)))
784
+ if planner_model is not None:
785
+ synchronize(device); begin=time.perf_counter()
786
+ layout,order=sample_layout(planner_model,b,steps)
787
+ used=remap_batch(b,layout,order,model.c.variant)
788
+ else:
789
+ synchronize(device); begin=time.perf_counter(); used=b; layout=b['layout']; order=b['order']
790
+ img,txt=sample_content(model,used,steps)
791
+ synchronize(device); elapsed+=time.perf_counter()-begin
792
+ active=b['layout']==IMAGE; predicted=layout==IMAGE
793
+ intersections+=int((active&predicted).sum()); unions+=int((active|predicted).sum())
794
+ image_hits+=int(((img==b['image'])&active&predicted).sum()); image_count+=int(active.sum())
795
+ mask=b['text']!=PAD; text_hits+=int(((txt==b['text'])&mask).sum()); text_count+=int(mask.sum())
796
+ for j in range(len(img)):
797
+ truth=b['image'][j].cpu().numpy().astype(np.uint8); guess=img[j].cpu().numpy().astype(np.uint8)
798
+ if int(b['encoding'][j])==0:
799
+ bits=unpack_braille(truth)^unpack_braille(guess)
800
+ eval_mask=np.repeat(np.repeat((active[j]|predicted[j]).cpu().numpy(),4,0),2,1)
801
+ dot_correct+=int((~bits&eval_mask).sum()); dot_count+=int(eval_mask.sum())
802
+ pol='row' if model.c.variant=='raster' else POLICIES[int(order[j])]
803
+ wrapped=wrap_words(decode_text(txt[j].cpu().numpy()).replace('\ufffd','?'),layout[j].cpu().numpy(),pol)
804
+ overflow+=int(bool(wrapped['overflow'])); generated_pages+=1
805
+ ts=txt[j].cpu().numpy(); end=np.flatnonzero(ts==EOS)
806
+ invalid_padding+=int(bool(np.any(ts[:end[0] if len(end) else len(ts)]==PAD)))
807
+ # A separate arbitrary partial-content test verifies clamping exactly.
808
+ ki=torch.rand(b['image'].shape,device=device)<.2; kt=torch.rand(b['text'].shape,device=device)<.2
809
+ known_i=torch.where(ki,b['image'],MASK); known_t=torch.where(kt,b['text'],MASK)
810
+ ci,ct=sample_content(model,b,min(steps,2),known_i,known_t)
811
+ ki&=active
812
+ known_checks+=int(((ci==b['image'])&ki).sum()+((ct==b['text'])&kt).sum())
813
+ known_total+=int(ki.sum()+kt.sum())
814
+ finally: restore_rng(prior); model.train(was)
815
+ return dict(geometry='predicted' if planner_model is not None else 'oracle_diagnostic',sampling_steps=steps,
816
+ content_nfe=steps,planner_nfe=steps if planner_model is not None else 0,
817
+ common_masked_ce=float(np.mean(losses)),text_byte_accuracy=text_hits/max(1,text_count),
818
+ image_cell_accuracy=image_hits/max(1,image_count),image_occupancy_iou=intersections/max(1,unions),
819
+ braille_dot_accuracy=dot_correct/max(1,dot_count),known_preservation=known_checks/max(1,known_total),
820
+ overflow_page_rate=overflow/max(1,generated_pages),early_pad_page_rate=invalid_padding/max(1,generated_pages),
821
+ generation_seconds_per_page=elapsed/max(1,generated_pages),pages=generated_pages,
822
+ metric_note='Exact target accuracy is a paired diagnostic; multiple valid texts/layouts exist. This is not perplexity or FID.')
823
+
824
+ def benchmark(root,c,device='cuda',batches=(1,2,4,8),repeats=5):
825
+ """Actual forward+backward+Adam timing, including steady-state memory.
826
+
827
+ A disposable model keeps tuning updates out of the experiment checkpoints.
828
+ Run per candidate variant; choose ONE common batch for controlled comparisons.
829
+ """
830
+ results=[]
831
+ for batch_size in batches:
832
+ model=opt=data=batch=None
833
+ try:
834
+ seed_all(42); model=CanvasModel(c).to(device)
835
+ opt=torch.optim.AdamW(model.parameters(),lr=1e-4,fused=str(device).startswith('cuda'))
836
+ data=CanvasData(root,'train',device); batch=data.batch(np.arange(batch_size),c.variant)
837
+ if str(device).startswith('cuda'): torch.cuda.reset_peak_memory_stats()
838
+ samples=[]
839
+ for j in range(repeats+2):
840
+ synchronize(device); begin=time.perf_counter(); opt.zero_grad(set_to_none=True)
841
+ with autocast(device): loss=make_loss(model,batch,objective=c.objective)
842
+ loss.backward(); torch.nn.utils.clip_grad_norm_(model.parameters(),1.); opt.step(); synchronize(device)
843
+ if j>=2: samples.append(time.perf_counter()-begin)
844
+ med=float(np.median(samples))
845
+ results.append(dict(batch_size=batch_size,seconds=med,pages_per_second=batch_size/med,
846
+ peak_GB=torch.cuda.max_memory_allocated()/1e9 if str(device).startswith('cuda') else None))
847
+ except torch.OutOfMemoryError:
848
+ results.append(dict(batch_size=batch_size,oom=True)); break
849
+ finally:
850
+ del model,opt,data,batch
851
+ if str(device).startswith('cuda'): torch.cuda.empty_cache()
852
+ return results
853
+
854
+ def generate_page(model,planner,prompt,layout=None,image=None,image_known=None,
855
+ text=None,text_tokens=None,fixed_text=None,encoding=0,policy=None,
856
+ steps=8,planner_steps=8,seed=123,greedy=False,region_order=None):
857
+ """Public generation API; no source example or ground truth is needed.
858
+
859
+ layout: full explicit occupancy, or UNKNOWN at unplanned cells.
860
+ image_known: arbitrary cell mask; zero-valued known Braille cells stay known.
861
+ text: optional complete ASCII text, or text_tokens with MASK for unknown bytes.
862
+ fixed_text: {(y,x): ASCII character}; immutable canvas text layer, separate
863
+ from the newly generated text lane. Planner is forbidden to overwrite it.
864
+ policy: optional reading order override; otherwise planner predicts one.
865
+ """
866
+ c=model.c; dev=next(model.parameters()).device; s=c.size
867
+ if policy is not None and policy not in POLICIES: raise ValueError(policy)
868
+ if encoding not in (0,1): raise ValueError('encoding must be 0 Braille or 1 ASCII')
869
+ lay=np.full((s,s),UNKNOWN,np.int64) if layout is None else np.array(layout,dtype=np.int64,copy=True)
870
+ if lay.shape!=(s,s) or np.any((lay<0)|(lay>UNKNOWN)): raise ValueError('Invalid layout')
871
+ im=np.zeros((s,s),np.int64) if image is None else np.asarray(image,dtype=np.int64)
872
+ known=np.zeros((s,s),bool) if image_known is None else np.asarray(image_known,bool)
873
+ if im.shape!=(s,s) or known.shape!=(s,s) or np.any((im<0)|(im>255)): raise ValueError('Invalid image')
874
+ if np.any(known&(lay!=UNKNOWN)&(lay!=IMAGE)): raise ValueError('Known image conflicts with explicit occupancy')
875
+ lay[known]=IMAGE
876
+ fixed_text=fixed_text or {}
877
+ for (y,x),char in fixed_text.items():
878
+ if not (0<=y<s and 0<=x<s and len(char)==1 and 32<=ord(char)<127): raise ValueError('Invalid fixed text cell')
879
+ if known[y,x] or lay[y,x]==IMAGE: raise ValueError('Text/image overlap in user constraints')
880
+ lay[y,x]=RESERVED
881
+ if fixed_text:
882
+ context=''.join(fixed_text[p] for p in sorted(fixed_text))
883
+ prompt=f'{prompt} Existing text: {context}'
884
+ prompt.encode('ascii',errors='strict')
885
+ if len(prompt)>c.prompt_len: raise ValueError(f'Prompt exceeds {c.prompt_len} ASCII bytes; shorten it explicitly')
886
+ p=np.full(c.prompt_len,PAD,np.int64); p[:len(prompt)]=list(prompt.encode('ascii'))
887
+ kt=np.full(c.text_len,MASK,np.int64)
888
+ if text is not None and text_tokens is not None: raise ValueError('Pass text OR text_tokens')
889
+ if text is not None:
890
+ raw=text.encode('ascii',errors='strict')
891
+ if len(raw)>=c.text_len: raise ValueError('Text exceeds lane capacity including EOS')
892
+ kt[:]=PAD; kt[:len(raw)]=list(raw); kt[len(raw)]=EOS
893
+ if text_tokens is not None:
894
+ kt=np.asarray(text_tokens,np.int64)
895
+ if kt.shape!=(c.text_len,) or np.any((kt<0)|(kt>MASK)): raise ValueError('Invalid text_tokens')
896
+ tensor=lambda a:torch.as_tensor(a,device=dev).long()[None]
897
+ b=dict(image=tensor(im),layout=tensor(lay),text=tensor(kt),prompt=tensor(p),
898
+ coords=torch.zeros(1,c.text_len,2,dtype=torch.long,device=dev),
899
+ regions=torch.zeros(1,c.text_len,dtype=torch.long,device=dev),encoding=tensor(np.asarray(encoding)),
900
+ order=tensor(np.asarray(POLICIES.index(policy or 'columns'))))
901
+ old=rng_state(); seed_all(seed)
902
+ try:
903
+ known_i=tensor(np.where(known,im,MASK)); known_t=tensor(kt)
904
+ if np.any(lay==UNKNOWN):
905
+ if planner is None: raise ValueError('Unknown occupancy requires a trained planner')
906
+ planned,order=sample_layout(planner,b,planner_steps,b['layout'],known_i,known_t,greedy)
907
+ else: planned,order=b['layout'],b['order']
908
+ if policy is not None: order.fill_(POLICIES.index(policy))
909
+ mapped=remap_batch(b,planned,order,c.variant)
910
+ if region_order is not None:
911
+ cc,rr,_=capacity_map(planned[0].cpu().numpy(),c.text_len,POLICIES[int(order[0])],region_order)
912
+ mapped['coords']=tensor(cc); mapped['regions']=tensor(rr)
913
+ generated,tokens=sample_content(model,mapped,steps,known_i,known_t,greedy)
914
+ finally: restore_rng(old)
915
+ out_image=generated[0].cpu().numpy().astype(np.uint8); out_layout=planned[0].cpu().numpy().astype(np.uint8)
916
+ out_text=decode_text(tokens[0].cpu().numpy()).replace('\ufffd','?')
917
+ out_policy='row' if c.variant=='raster' else POLICIES[int(order[0])]
918
+ rendered=wrap_words(out_text,out_layout,out_policy,region_order)
919
+ # Final canvas retains fixed text cells exactly; byte indices apply to new lane.
920
+ canvas=np.full((s,s),' ',dtype='<U1')
921
+ for y,x in np.argwhere(out_layout==IMAGE): canvas[y,x]=chr(0x2800+int(out_image[y,x])) if encoding==0 else chr(int(out_image[y,x]))
922
+ for (y,x),char in zip(rendered['coords'],rendered['chars']): canvas[y,x]=char
923
+ for (y,x),char in fixed_text.items(): canvas[y,x]=char
924
+ return dict(canvas=canvas,image=out_image,layout=out_layout,text=out_text,
925
+ tokens=tokens[0].cpu().numpy(),flow_map=rendered,policy=out_policy,
926
+ known_image_preserved=bool(np.array_equal(out_image[known],im[known])),
927
+ content_nfe=steps,planner_nfe=planner_steps if np.any(lay==UNKNOWN) else 0)
928
+
929
+ def self_test():
930
+ """Small CPU tests check contracts; they are not CUDA throughput evidence."""
931
+ torch.set_num_threads(min(4,os.cpu_count() or 1)); seed_all(3); results=[]
932
+ def ok(name): results.append(name); print('PASS',name,flush=True)
933
+ cells=np.arange(256,dtype=np.uint8).reshape(16,16)
934
+ assert np.array_equal(pack_braille(unpack_braille(cells)),cells)
935
+ for bit,(y,x) in enumerate(DOTS): assert unpack_braille(np.array([[1<<bit]],np.uint8))[y,x]
936
+ ok('all 256 Braille dot patterns round trip')
937
+ layout=np.full((24,24),TEXT,np.uint8); layout[:16,8:16]=IMAGE
938
+ runs=flow_runs(layout==TEXT,'columns')
939
+ assert runs[1][0]==1 and runs[1][1]==0 # Column-first, not raster hopping.
940
+ text='This is a test with several words beside a tall image.'
941
+ wrapped=wrap_words(text,layout,'columns')
942
+ assert wrapped['chars']==text and not wrapped['overflow']
943
+ assert len(set(wrapped['coords']))==len(wrapped['coords'])
944
+ assert all(layout[y,x]==TEXT for y,x in wrapped['coords'])
945
+ tiny=np.full((3,4),TEXT,np.uint8)
946
+ w=wrap_words('abcdefghijklmnopqrst',tiny); assert w['chars']+w['overflow']=='abcdefghijklmnopqrst'
947
+ assert wrap_words('hello',np.full((3,3),IMAGE))['overflow']=='hello'
948
+ ok('geometry regions, exact byte order, long-word splitting, explicit overflow')
949
+ for split in ('train','val','test','ood'):
950
+ for i in range(9):
951
+ e=make_example(i,split,size=32,text_len=64,prompt_len=96)
952
+ assert np.array_equal(e['coords'],capacity_map(e['layout'],64,e['meta']['policy'])[0])
953
+ w=wrap_words(e['meta']['text'],e['layout'],e['meta']['policy'])
954
+ assert not w['overflow'] and all(e['layout'][y,x]==TEXT for y,x in w['coords'])
955
+ assert make_example(0,'train')['meta']!=make_example(0,'val')['meta']
956
+ ok('deterministic split streams, arbitrary masks, no target-derived coordinates')
957
+ examples=[make_example(i,'train',16,32,64) for i in range(3)]
958
+ batch={k:torch.tensor(np.stack([e[k] for e in examples])).long() for k in ARRAY_KEYS}
959
+ config=Config(size=16,text_len=32,prompt_len=64,dim=32,heads=2,depth=2,patch=4,local_dim=8)
960
+ for planner in (False,True):
961
+ for variant in (('full','no_flow','raster','patch8','mdlm') if not planner else ('full',)):
962
+ c=replace(config,variant=variant,patch=8 if variant=='patch8' else 4,objective='mdlm' if variant=='mdlm' else 'dfm')
963
+ model=CanvasModel(c,planner); loss=make_loss(model,batch,planner,c.objective); loss.backward()
964
+ assert torch.isfinite(loss) and finite_tensors([p.grad for p in model.parameters() if p.grad is not None])
965
+ assert len({id(b.qkv.weight) for b in model.blocks})==c.depth
966
+ ok('all five ablations and planner forward/backward; unique block weights')
967
+ logits=torch.zeros(1,4,2); target=torch.zeros(1,4,dtype=torch.long)
968
+ mask=torch.tensor([[True,True,False,False]]); eligible=torch.ones_like(mask); t=torch.tensor([.5])
969
+ assert abs(float(masked_ce(logits,target,mask,eligible,t,'dfm'))-.5*math.log(2))<1e-6
970
+ assert abs(float(masked_ce(logits,target,mask,eligible,t,'mdlm'))-math.log(2))<1e-6
971
+ ok('DFM posterior CE and absorbing-path time weight analytic check')
972
+ model=CanvasModel(config); planner=CanvasModel(config,True)
973
+ for n in (1,4):
974
+ known_i=torch.full_like(batch['image'],MASK); active=batch['layout']==IMAGE
975
+ known_i[active]=batch['image'][active]
976
+ known_t=torch.full_like(batch['text'],MASK); known_t[:,:4]=batch['text'][:,:4]
977
+ img,txt=sample_content(model,batch,n,known_i,known_t)
978
+ assert not (img==MASK).any() and not (txt==MASK).any()
979
+ assert torch.equal(img[active],batch['image'][active]) and torch.equal(txt[:,:4],batch['text'][:,:4])
980
+ known_layout=torch.full_like(batch['layout'],UNKNOWN); known_layout[:,:,0]=RESERVED
981
+ out,order=sample_layout(planner,batch,n,known_layout)
982
+ assert not (out==UNKNOWN).any() and (out[:,:,0]==RESERVED).all()
983
+ ok('one/four-step sampling terminates; arbitrary known values and geometry clamped')
984
+ explicit=np.full((16,16),TEXT); explicit[5:8,7:11]=IMAGE
985
+ image=np.zeros((16,16),np.uint8); image[5:8,7:11]=255; known=explicit==IMAGE
986
+ result=generate_page(model,None,'mixed page',explicit,image,known,text='Hello world.',fixed_text={(15,15):'X'},steps=1)
987
+ assert result['known_image_preserved'] and result['canvas'][15,15]=='X' and result['text']=='Hello world.'
988
+ ok('public generation API preserves known image/text and fixed canvas cells')
989
+ with tempfile.TemporaryDirectory() as tmp:
990
+ root=Path(tmp)/'data'; build_data(root,count=9,size=16,text_len=32,prompt_len=64)
991
+ args=dict(root=root,c=config,steps=3,batch_size=2,accum=1,device='cpu',save_every=1,seed=91)
992
+ m1,_=train(run=Path(tmp)/'whole',**args)
993
+ train(run=Path(tmp)/'resume',stop_after=1,**args)
994
+ m2,_=train(run=Path(tmp)/'resume',**args)
995
+ assert all(torch.equal(v,m2.state_dict()[k]) for k,v in m1.state_dict().items())
996
+ a=read_checkpoint(Path(tmp)/'whole/last.pt'); b=read_checkpoint(Path(tmp)/'resume/last.pt')
997
+ for key in a['optimizer']['state']:
998
+ for field,x in a['optimizer']['state'][key].items():
999
+ assert torch.equal(x,b['optimizer']['state'][key][field]) if torch.is_tensor(x) else x==b['optimizer']['state'][key][field]
1000
+ _,summary=train(run=Path(tmp)/'retry',inject_nonfinite_at=0,**args)
1001
+ assert summary['step']==3 and summary['retries']==1
1002
+ _,rollback=train(run=Path(tmp)/'rollback',inject_poststep_at=1,**args)
1003
+ assert rollback['step']==3 and rollback['rollbacks']==1
1004
+ corrupt=Path(tmp)/'rollback/last.pt'; corrupt.write_bytes(b'broken checkpoint')
1005
+ recovered=read_checkpoint(corrupt); assert recovered['step']==2
1006
+ try: read_checkpoint(Path(tmp)/'whole/last.pt',signature='wrong')
1007
+ except ValueError: pass
1008
+ else: raise AssertionError('Incompatible resume should fail')
1009
+ ok('bit-exact interrupted resume including Adam; injected Inf replays without skips')
1010
+ ok('post-update NaN rollback, previous-checkpoint recovery, signature rejection')
1011
+ print(json.dumps(dict(passed=len(results),tests=results)),flush=True)
1012
+ return results
1013
+
1014
+ def main():
1015
+ p=argparse.ArgumentParser(description=__doc__); sub=p.add_subparsers(dest='command',required=True)
1016
+ sub.add_parser('selftest')
1017
+ prep=sub.add_parser('prepare'); prep.add_argument('--root',required=True); prep.add_argument('--count',type=int,default=4096)
1018
+ prep.add_argument('--size',type=int,default=128); prep.add_argument('--text-len',type=int,default=512)
1019
+ gen=sub.add_parser('gallery'); gen.add_argument('--root',required=True); gen.add_argument('--output',required=True)
1020
+ a=p.parse_args()
1021
+ if a.command=='selftest': self_test()
1022
+ elif a.command=='prepare': build_data(a.root,a.count,a.size,a.text_len)
1023
+ else: gallery(a.root,a.output)
1024
+
1025
+ if __name__=='__main__': main()
unified/ablations/unified-v1-blt-none-s42/source/causal_arch.py ADDED
@@ -0,0 +1,747 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Audited byte architecture screen. See the notebook's fidelity table and sources.
2
+
3
+ This is a scaled, from-scratch study, not a reproduction of published checkpoints.
4
+ ByteFlow's whole-sequence Top-K mode is deliberately excluded from causal rankings.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import contextlib
10
+ import copy
11
+ import hashlib
12
+ import json
13
+ import math
14
+ import os
15
+ import random
16
+ import sys
17
+ import time
18
+ from dataclasses import asdict, dataclass, replace
19
+ from datetime import timedelta
20
+ from pathlib import Path
21
+
22
+ import numpy as np
23
+ import torch
24
+ from torch import nn
25
+ from torch.nn import functional as F
26
+ from torch.utils.checkpoint import checkpoint
27
+
28
+ PAD, BOS, EOS, VOCAB = 256, 257, 258, 259
29
+ SCHEMA = 3
30
+ FINEWEB_REVISION = "9bb295ddab0e05d785b879661af7260fed5140fc"
31
+ ARCHS = ("ouro_1pass", "evabyte", "blt", "byteflow_causal")
32
+
33
+
34
+ @dataclass
35
+ class ModelCfg:
36
+ dim: int = 640
37
+ ff: int = 1536
38
+ heads: int = 10
39
+ context: int = 4096
40
+ body_layers: int = 6
41
+ loops: int = 1
42
+ local_dim: int = 256
43
+ local_ff: int = 768
44
+ local_heads: int = 4
45
+ encoder_layers: int = 2
46
+ decoder_layers: int = 2
47
+ local_window: int = 512
48
+ query_block: int = 128
49
+ # Released EvaByte configuration, NOT the generic class defaults.
50
+ eva_window: int = 2048
51
+ eva_chunk: int = 16
52
+ eva_heads: int = 8
53
+ # Scaled BLT capacities. Hash sizes 3..8 and two latent slots are retained.
54
+ hash_buckets: int = 4096
55
+ hash_sizes: tuple = (3, 4, 5, 6, 7, 8)
56
+ cross_slots: int = 2
57
+ entropy_dim: int = 128
58
+ entropy_ff: int = 384
59
+ entropy_heads: int = 2
60
+ entropy_layers: int = 4
61
+ patch_size: float = 4.0
62
+ byteflow_bins: int = 16
63
+ byteflow_eps2: float = 1.0
64
+ byteflow_score: str = "l2" # Appendix B trace approximation, on RAW states.
65
+ router_window: int = 64 # Explicit causal adaptation, not paper Top-K.
66
+ grad_checkpoint: bool = True
67
+
68
+ def validate(self):
69
+ for d, h in ((self.dim, self.heads), (self.local_dim, self.local_heads),
70
+ (self.entropy_dim, self.entropy_heads)):
71
+ if d % h or (d // h) % 2:
72
+ raise ValueError("Each head must have an even integral dimension.")
73
+ if self.context < 2 or self.patch_size < 1 or self.loops < 1:
74
+ raise ValueError("Invalid context, patch size, or loop count.")
75
+ if self.eva_window % self.eva_chunk or self.eva_chunk < 1:
76
+ raise ValueError("EVA window must be divisible by chunk size.")
77
+ if min(self.local_window, self.query_block, self.router_window) < 1:
78
+ raise ValueError("Attention/router windows must be positive.")
79
+
80
+
81
+ def seed_all(seed):
82
+ random.seed(seed)
83
+ np.random.seed(seed)
84
+ torch.manual_seed(seed)
85
+ if torch.cuda.is_available():
86
+ torch.cuda.manual_seed_all(seed)
87
+
88
+
89
+ def documents(ids):
90
+ """BOS starts a new document; the first row position may be a continuation."""
91
+ return (ids == BOS).long().cumsum(-1)
92
+
93
+
94
+ def ckpt(module, *args, enabled=False):
95
+ if enabled and module.training and torch.is_grad_enabled():
96
+ return checkpoint(module, *args, use_reentrant=False, preserve_rng_state=True)
97
+ return module(*args)
98
+
99
+
100
+ class RMSNorm(nn.Module):
101
+ def __init__(self, dim, eps=1e-6, unit_offset=False):
102
+ super().__init__()
103
+ self.weight = nn.Parameter(torch.zeros(dim) if unit_offset else torch.ones(dim))
104
+ self.eps, self.unit_offset = eps, unit_offset
105
+
106
+ def forward(self, x):
107
+ # FP32 statistics AND multiplication; the residual stream stays FP32.
108
+ z = x.float()
109
+ w = self.weight.float() + (1.0 if self.unit_offset else 0.0)
110
+ return (z * torch.rsqrt(z.square().mean(-1, keepdim=True) + self.eps) * w).to(x.dtype)
111
+
112
+
113
+ def rotary(q, k, theta):
114
+ t, d = q.shape[-2:]
115
+ with torch.autocast(q.device.type, enabled=False):
116
+ freq = theta ** (-torch.arange(0, d, 2, device=q.device, dtype=torch.float32) / d)
117
+ phase = torch.arange(t, device=q.device, dtype=torch.float32)[:, None] * freq
118
+ cs = torch.cat((phase, phase), -1).cos()[None, None]
119
+ sn = torch.cat((phase, phase), -1).sin()[None, None]
120
+ def rotate(x):
121
+ a, b = x.float().chunk(2, -1)
122
+ return (x.float() * cs + torch.cat((-b, a), -1) * sn).to(x.dtype)
123
+ return rotate(q), rotate(k)
124
+
125
+
126
+ def exclusive(z, v):
127
+ # Half precision dot products here used to overflow before GradScaler could help.
128
+ a, b = z.float(), v.float()
129
+ return (a - (a * b).sum(-1, keepdim=True) * b /
130
+ b.square().sum(-1, keepdim=True).clamp_min(1e-12)).to(z.dtype)
131
+
132
+
133
+ def sdpa(q, k, v, allowed):
134
+ # At least one allowed key per real query is guaranteed by callers. Empty
135
+ # padding queries receive a dummy key and are explicitly zeroed afterward.
136
+ return F.scaled_dot_product_attention(q, k, v, attn_mask=allowed,
137
+ dropout_p=0.0, is_causal=False)
138
+
139
+
140
+ class Attention(nn.Module):
141
+ def __init__(self, d, heads, window=None, theta=100000.0,
142
+ qk_norm=False, gated=False, xsa=False, query_block=128):
143
+ super().__init__()
144
+ self.d, self.h, self.hd = d, heads, d // heads
145
+ self.window, self.theta, self.query_block = window, theta, query_block
146
+ self.gated, self.xsa = gated, xsa
147
+ self.q = nn.Linear(d, d * (2 if gated else 1), bias=False)
148
+ self.k, self.v, self.o = [nn.Linear(d, d, bias=False) for _ in range(3)]
149
+ self.qn = RMSNorm(self.hd) if qk_norm else nn.Identity()
150
+ self.kn = RMSNorm(self.hd) if qk_norm else nn.Identity()
151
+
152
+ def forward(self, x, doc):
153
+ b, t, d = x.shape
154
+ qr = self.q(x)
155
+ qr, gate = qr.chunk(2, -1) if self.gated else (qr, None)
156
+ shape = lambda z: z.reshape(b, t, self.h, self.hd).transpose(1, 2)
157
+ q, k, v = self.qn(shape(qr)), self.kn(shape(self.k(x))), shape(self.v(x))
158
+ q, k = rotary(q, k, self.theta)
159
+ pieces = []
160
+ # Chunk queries; SWA also slices keys. There is no T x T SWA allocation.
161
+ for start in range(0, t, self.query_block):
162
+ end = min(t, start + self.query_block)
163
+ lo = max(0, start - self.window + 1) if self.window else 0
164
+ qi = torch.arange(start, end, device=x.device)[:, None]
165
+ ki = torch.arange(lo, end, device=x.device)[None, :]
166
+ allow = ki <= qi
167
+ if self.window:
168
+ allow = allow & (ki > qi - self.window)
169
+ allow = allow[None] & (doc[:, start:end, None] == doc[:, None, lo:end])
170
+ pieces.append(sdpa(q[:, :, start:end], k[:, :, lo:end], v[:, :, lo:end], allow[:, None]))
171
+ z = torch.cat(pieces, -2)
172
+ if self.xsa:
173
+ z = exclusive(z, v)
174
+ if gate is not None:
175
+ # Query-dependent bounded G1 sigmoid gate, not unbounded SiLU.
176
+ z = z * shape(gate).float().sigmoid().to(z.dtype)
177
+ return self.o(z.transpose(1, 2).reshape(b, t, d))
178
+
179
+
180
+ class SwiGLU(nn.Module):
181
+ def __init__(self, d, ff):
182
+ super().__init__()
183
+ self.g = nn.Linear(d, ff, bias=False)
184
+ self.u = nn.Linear(d, ff, bias=False)
185
+ self.o = nn.Linear(ff, d, bias=False)
186
+
187
+ def forward(self, x):
188
+ return self.o(F.silu(self.g(x)) * self.u(x))
189
+
190
+
191
+ class Block(nn.Module):
192
+ def __init__(self, d, ff, heads, *, window=None, theta=100000.0,
193
+ sandwich=False, ouro=False, query_block=128, norm_eps=1e-6,
194
+ unit_offset=False):
195
+ super().__init__()
196
+ self.n1, self.n2 = [RMSNorm(d, norm_eps, unit_offset) for _ in range(2)]
197
+ self.attn = Attention(d, heads, window, theta, ouro, ouro, ouro, query_block)
198
+ self.ff = SwiGLU(d, ff)
199
+ self.post_a = RMSNorm(d) if sandwich else nn.Identity()
200
+ self.post_f = RMSNorm(d) if sandwich else nn.Identity()
201
+
202
+ def forward(self, x, doc):
203
+ # Published Ouro: x + Norm(Attention(Norm(x))). Never Norm(x + branch).
204
+ x = x + self.post_a(self.attn(self.n1(x), doc))
205
+ return x + self.post_f(self.ff(self.n2(x)))
206
+
207
+
208
+ def initialize(m):
209
+ if isinstance(m, (nn.Linear, nn.Embedding)):
210
+ nn.init.normal_(m.weight, std=0.02)
211
+ if isinstance(m, nn.Linear) and m.bias is not None:
212
+ nn.init.zeros_(m.bias)
213
+
214
+
215
+ class OuroLM(nn.Module):
216
+ """User's prelude/shared-body/coda baseline, with corrected branch norms.
217
+
218
+ This deliberately does not add ByteDance's early-exit training objective.
219
+ """
220
+ def __init__(self, c):
221
+ super().__init__()
222
+ self.c = c
223
+ self.embed = nn.Embedding(VOCAB, c.dim)
224
+ make = lambda: Block(c.dim, c.ff, c.heads, sandwich=True, ouro=True,
225
+ query_block=c.query_block)
226
+ self.prelude, self.coda = make(), make()
227
+ self.body = nn.ModuleList([make() for _ in range(c.body_layers)])
228
+ self.loop_norm, self.norm = RMSNorm(c.dim), RMSNorm(c.dim)
229
+ self.apply(initialize)
230
+
231
+ def forward(self, ids, loops=None):
232
+ loops = self.c.loops if loops is None else loops
233
+ if loops < 1:
234
+ raise ValueError("loops must be positive")
235
+ doc = documents(ids)
236
+ h = ckpt(self.prelude, self.embed(ids), doc, enabled=self.c.grad_checkpoint)
237
+ for _ in range(loops):
238
+ for layer in self.body:
239
+ h = ckpt(layer, h, doc, enabled=self.c.grad_checkpoint)
240
+ h = self.loop_norm(h) # Also used at one pass; no unused DDP parameter.
241
+ h = ckpt(self.coda, h, doc, enabled=self.c.grad_checkpoint)
242
+ return {"logits": F.linear(self.norm(h), self.embed.weight), "heads": []}
243
+
244
+
245
+ class EVAAttention(nn.Module):
246
+ """PyTorch reference-equation EVA for arbitrary lengths, including decode.
247
+
248
+ Exact intra-window attention + learned summaries from strictly earlier
249
+ windows. Document-crossing chunks are split into document-specific slots.
250
+ """
251
+ def __init__(self, c):
252
+ super().__init__()
253
+ self.c, self.h, self.hd = c, c.heads, c.dim // c.heads
254
+ self.q, self.k, self.v, self.o = [nn.Linear(c.dim, c.dim, bias=False) for _ in range(4)]
255
+ self.mu = nn.Parameter(torch.randn(1, self.h, 1, 1, self.hd).clamp(-1, 1) / math.sqrt(self.hd))
256
+ self.phi = nn.Parameter(torch.randn(1, self.h, 1, 1, self.hd).clamp(-1, 1) / math.sqrt(self.hd))
257
+
258
+ def summaries(self, k, v, doc):
259
+ b, h, t, d = k.shape
260
+ chunk = self.c.eva_chunk
261
+ # A summary cannot mix documents. A chunk boundary or BOS starts a slot.
262
+ starts = torch.zeros_like(doc, dtype=torch.bool)
263
+ starts[:, ::chunk] = True
264
+ starts[:, 1:] |= doc[:, 1:] != doc[:, :-1]
265
+ seg, counts = starts.long().cumsum(-1) - 1, starts.sum(-1)
266
+ n = int(counts.max().item())
267
+ pos = torch.arange(t, device=k.device).expand(b, t)
268
+ first = torch.full((b, n), t, device=k.device, dtype=torch.long)
269
+ first.scatter_reduce_(1, seg, pos, reduce="amin", include_self=True)
270
+ last = torch.full_like(first, -1)
271
+ last.scatter_reduce_(1, seg, pos, reduce="amax", include_self=True)
272
+ offsets = pos - first.gather(1, seg)
273
+ flat = (seg * chunk + offsets)[:, None, :, None].expand(b, h, t, d)
274
+ # Accumulate products in FP32, including key squared norms.
275
+ kk = k.float().new_zeros(b, h, n * chunk, d).scatter(2, flat, k.float())
276
+ vv = v.float().new_zeros(b, h, n * chunk, d).scatter(2, flat, v.float())
277
+ kk, vv = kk.view(b, h, n, chunk, d), vv.view(b, h, n, chunk, d)
278
+ valid = torch.arange(chunk, device=k.device)[None, None, None, :] <= (last-first)[:, None, :, None]
279
+ active = first < t
280
+ safe = valid.clone()
281
+ safe[:, :, :, 0] |= ~active[:, None, :]
282
+ with torch.autocast(k.device.type, enabled=False):
283
+ wm = ((kk * self.mu.float()).sum(-1)).masked_fill(~safe, -torch.inf).softmax(-1)
284
+ wp = (((kk * self.phi.float()).sum(-1) - 0.5 * kk.square().sum(-1)) /
285
+ math.sqrt(d)).masked_fill(~safe, -torch.inf).softmax(-1)
286
+ kb = (wm[..., None] * kk).sum(-2)
287
+ vb = (wp[..., None] * vv).sum(-2)
288
+ sdoc = doc.gather(1, first.clamp_max(t - 1))
289
+ return kb.to(k.dtype), vb.to(v.dtype), last, sdoc, active
290
+
291
+ def forward(self, x, doc):
292
+ b, t, d = x.shape
293
+ reshape = lambda z: z.view(b, t, self.h, self.hd).transpose(1, 2)
294
+ q, k, v = reshape(self.q(x)), reshape(self.k(x)), reshape(self.v(x))
295
+ q, k = rotary(q, k, 100000.0)
296
+ kb, vb, last, sdoc, active = self.summaries(k, v, doc)
297
+ outputs = []
298
+ w = self.c.eva_window
299
+ for ws in range(0, t, w):
300
+ we = min(t, ws + w)
301
+ # Keep a zero dependency when there are no earlier slots; the
302
+ # mathematical EVA path is unchanged and reducer participation is safe.
303
+ keys = torch.cat((k[:, :, ws:we], kb), -2)
304
+ vals = torch.cat((v[:, :, ws:we], vb), -2)
305
+ for a in range(ws, we, self.c.query_block):
306
+ z = min(we, a + self.c.query_block)
307
+ qi = torch.arange(a, z, device=x.device)[:, None]
308
+ ki = torch.arange(ws, we, device=x.device)[None, :]
309
+ local = (ki <= qi)[None] & (doc[:, a:z, None] == doc[:, None, ws:we])
310
+ summary = (last[:, None, :] < ws) & active[:, None, :]
311
+ summary = summary & (doc[:, a:z, None] == sdoc[:, None, :])
312
+ allow = torch.cat((local, summary), -1)
313
+ outputs.append(sdpa(q[:, :, a:z], keys, vals, allow[:, None]))
314
+ h = torch.cat(outputs, -2).transpose(1, 2).reshape(b, t, d)
315
+ return self.o(h)
316
+
317
+
318
+ class EVABlock(nn.Module):
319
+ def __init__(self, c):
320
+ super().__init__()
321
+ self.n1, self.n2 = [RMSNorm(c.dim, 1e-5, True) for _ in range(2)]
322
+ self.attn, self.ff = EVAAttention(c), SwiGLU(c.dim, c.ff)
323
+
324
+ def forward(self, x, doc):
325
+ x = x + self.attn(self.n1(x), doc)
326
+ return x + self.ff(self.n2(x))
327
+
328
+
329
+ class EvaByteLM(nn.Module):
330
+ def __init__(self, c, depth):
331
+ super().__init__()
332
+ self.c = c
333
+ self.embed = nn.Embedding(VOCAB, c.dim)
334
+ self.layers = nn.ModuleList([EVABlock(c) for _ in range(depth)])
335
+ self.norm = RMSNorm(c.dim, 1e-5, True)
336
+ self.output = nn.Linear(c.dim, VOCAB * c.eva_heads, bias=False)
337
+ self.apply(initialize)
338
+
339
+ def forward(self, ids, loops=None):
340
+ doc, h = documents(ids), self.embed(ids)
341
+ for layer in self.layers:
342
+ h = ckpt(layer, h, doc, enabled=self.c.grad_checkpoint)
343
+ all_heads = self.output(self.norm(h)).view(*ids.shape, self.c.eva_heads, VOCAB)
344
+ return {"logits": all_heads[:, :, 0], "heads": list(all_heads.unbind(2))}
345
+
346
+
347
+ class EntropyLM(nn.Module):
348
+ def __init__(self, c):
349
+ super().__init__()
350
+ self.c = c
351
+ self.embed = nn.Embedding(VOCAB, c.entropy_dim)
352
+ self.layers = nn.ModuleList([Block(c.entropy_dim, c.entropy_ff, c.entropy_heads,
353
+ window=c.local_window, theta=500000.0, query_block=c.query_block)
354
+ for _ in range(c.entropy_layers)])
355
+ self.norm = RMSNorm(c.entropy_dim)
356
+ self.output = nn.Linear(c.entropy_dim, VOCAB, bias=False)
357
+ self.register_buffer("threshold", torch.tensor(float("nan")))
358
+ self.apply(initialize)
359
+
360
+ def forward(self, ids, loops=None):
361
+ h, doc = self.embed(ids), documents(ids)
362
+ for layer in self.layers:
363
+ h = ckpt(layer, h, doc, enabled=self.c.grad_checkpoint)
364
+ return {"logits": self.output(self.norm(h)), "heads": []}
365
+
366
+ @torch.no_grad()
367
+ def entropy(self, ids):
368
+ logits = self(ids)["logits"].float()
369
+ lp = logits.log_softmax(-1)
370
+ return -(lp.exp() * lp).sum(-1)
371
+
372
+
373
+ def segments(starts):
374
+ """Ragged routing metadata; never truncate excess patches."""
375
+ b, t = starts.shape
376
+ ids = starts.long().cumsum(-1) - 1
377
+ counts = starts.sum(-1)
378
+ n = int(counts.max().item())
379
+ pos = torch.arange(t, device=starts.device).expand(b, t)
380
+ first = torch.full((b, n), t, device=starts.device, dtype=torch.long)
381
+ first.scatter_reduce_(1, ids, pos, reduce="amin", include_self=True)
382
+ valid = torch.arange(n, device=starts.device)[None] < counts[:, None]
383
+ return ids, first.clamp_max(t - 1), valid
384
+
385
+
386
+ def gather(states, idx):
387
+ return states.gather(1, idx[..., None].expand(*idx.shape, states.shape[-1]))
388
+
389
+
390
+ class CrossAttention(nn.Module):
391
+ def __init__(self, d, heads, block):
392
+ super().__init__()
393
+ self.h, self.hd, self.block = heads, d // heads, block
394
+ self.nq, self.nkv = RMSNorm(d), RMSNorm(d)
395
+ self.q, self.k, self.v, self.o = [nn.Linear(d, d, bias=False) for _ in range(4)]
396
+
397
+ def forward(self, x, kv, qgroup, kgroup, valid_q=None):
398
+ b, t, d = x.shape
399
+ nk = kv.shape[1]
400
+ shape = lambda a: a.view(b, -1, self.h, self.hd).transpose(1, 2)
401
+ q, kn = shape(self.q(self.nq(x))), self.nkv(kv)
402
+ k, v = shape(self.k(kn)), shape(self.v(kn))
403
+ chunks = []
404
+ for a in range(0, t, self.block):
405
+ z = min(t, a + self.block)
406
+ allow = qgroup[:, a:z, None] == kgroup[:, None, :]
407
+ real = allow.any(-1)
408
+ if valid_q is not None:
409
+ real = real & valid_q[:, a:z]
410
+ # Avoid all-masked softmax on padded/no-context queries.
411
+ allow[:, :, 0] |= ~real
412
+ y = sdpa(q[:, :, a:z], k, v, allow[:, None])
413
+ y = y * real[:, None, :, None]
414
+ chunks.append(y)
415
+ z = torch.cat(chunks, -2).transpose(1, 2).reshape(b, t, d)
416
+ return x + self.o(z)
417
+
418
+
419
+ class GlobalStack(nn.Module):
420
+ def __init__(self, c, depth, theta=500000.0):
421
+ super().__init__()
422
+ self.c = c
423
+ self.layers = nn.ModuleList([Block(c.dim, c.ff, c.heads,
424
+ theta=theta, query_block=c.query_block) for _ in range(depth)])
425
+ self.norm = RMSNorm(c.dim)
426
+
427
+ def forward(self, x, doc):
428
+ for layer in self.layers:
429
+ x = ckpt(layer, x, doc, enabled=self.c.grad_checkpoint)
430
+ return self.norm(x)
431
+
432
+
433
+ class BLTLM(nn.Module):
434
+ """Scaled paper BLT: frozen independent patcher, ngrams, cross-attention.
435
+
436
+ Decoder access is aligned with the NEXT byte's patch. A current patch is
437
+ visible only when the prefix-only entropy decision has closed it.
438
+ """
439
+ def __init__(self, c, depth):
440
+ super().__init__()
441
+ self.c = c
442
+ self.patcher = EntropyLM(c).requires_grad_(False)
443
+ self.embed = nn.Embedding(VOCAB, c.local_dim)
444
+ self.hashes = nn.ModuleList([nn.Embedding(c.hash_buckets, c.local_dim) for _ in c.hash_sizes])
445
+ make = lambda: Block(c.local_dim, c.local_ff, c.local_heads,
446
+ window=c.local_window, theta=500000.0, query_block=c.query_block)
447
+ self.enc = nn.ModuleList([make() for _ in range(c.encoder_layers)])
448
+ self.dec = nn.ModuleList([make() for _ in range(c.decoder_layers)])
449
+ make_cross = lambda: CrossAttention(c.local_dim, c.local_heads, c.query_block)
450
+ self.enc_cross = nn.ModuleList([make_cross() for _ in self.enc])
451
+ self.dec_cross = nn.ModuleList([make_cross() for _ in self.dec])
452
+ self.pool_proj = nn.Linear(c.local_dim, c.local_dim * c.cross_slots, bias=False)
453
+ self.to_global = nn.Linear(c.local_dim * c.cross_slots, c.dim, bias=False)
454
+ self.to_local = nn.Linear(c.dim, c.local_dim * c.cross_slots, bias=False)
455
+ self.global_stack = GlobalStack(c, depth)
456
+ self.norm = RMSNorm(c.local_dim)
457
+ self.output = nn.Linear(c.local_dim, VOCAB, bias=False)
458
+ self.apply(initialize)
459
+
460
+ def train(self, mode=True):
461
+ super().train(mode)
462
+ self.patcher.eval()
463
+ return self
464
+
465
+ def byte_embeddings(self, x, doc):
466
+ b, t = x.shape
467
+ emb = self.embed(x)
468
+ # Polynomial hashes, causal and reset at document boundaries. Modular
469
+ # arithmetic avoids signed int64 overflow and is platform-independent.
470
+ for size, table in zip(self.c.hash_sizes, self.hashes):
471
+ if size > t:
472
+ emb = emb + table.weight[0] * 0
473
+ continue
474
+ h = torch.zeros_like(x)
475
+ valid = torch.arange(t, device=x.device)[None] >= size - 1
476
+ valid = valid.expand(b, t).clone()
477
+ for lag in range(size - 1, -1, -1):
478
+ shifted = F.pad(x[:, :t-lag], (lag, 0), value=0) if lag else x
479
+ h = (h * 257 + shifted) % self.c.hash_buckets
480
+ valid &= (shifted < 256)
481
+ if lag:
482
+ prevdoc = F.pad(doc[:, :t-lag], (lag, 0), value=-1)
483
+ valid &= prevdoc == doc
484
+ emb = emb + table(h) * valid[..., None]
485
+ return emb / (1 + len(self.hashes))
486
+
487
+ def forward(self, x, loops=None):
488
+ if not torch.isfinite(self.patcher.threshold):
489
+ raise RuntimeError("BLT requires a trained, calibrated entropy checkpoint; run the patcher stage first.")
490
+ b, t = x.shape
491
+ doc = documents(x)
492
+ with torch.no_grad():
493
+ e = self.patcher.entropy(x)
494
+ close = (e > self.patcher.threshold) | (x == BOS) | (x == EOS)
495
+ starts = torch.cat((torch.ones_like(close[:, :1]), close[:, :-1]), 1)
496
+ starts |= x == BOS
497
+ pid, first, valid = segments(starts)
498
+ n = first.shape[1]
499
+ pdoc = doc.gather(1, first)
500
+ pdoc = torch.where(valid, pdoc, -1)
501
+ groups = torch.arange(n, device=x.device)[None].expand(b, n)
502
+ pgroups = groups.repeat_interleave(self.c.cross_slots, 1)
503
+ pvalid = valid.repeat_interleave(self.c.cross_slots, 1)
504
+ # No peeking at x[t+1] to decide decoder conditioning.
505
+ context_id = pid + close.long() - 1
506
+ ok = context_id >= 0
507
+ context_doc = pdoc.gather(1, context_id.clamp_min(0))
508
+ ok &= context_doc == doc
509
+ context_id = torch.where(ok, context_id, -2)
510
+ h = self.byte_embeddings(x, doc)
511
+ p = None
512
+ for layer, cross in zip(self.enc, self.enc_cross):
513
+ h = ckpt(layer, h, doc, enabled=self.c.grad_checkpoint)
514
+ if p is None:
515
+ pooled = h.float().new_full((b, n, h.shape[-1]), -torch.inf)
516
+ pooled = pooled.scatter_reduce(1, pid[..., None].expand_as(h), h.float(),
517
+ reduce="amax", include_self=True)
518
+ pooled = torch.where(valid[..., None], pooled, torch.zeros_like(pooled))
519
+ p = self.pool_proj(pooled.to(h.dtype)).reshape(b, n * self.c.cross_slots, -1)
520
+ p = ckpt(cross, p, h, pgroups, pid, pvalid, enabled=self.c.grad_checkpoint)
521
+ g = self.global_stack(self.to_global(p.reshape(b, n, -1)), pdoc)
522
+ kv = self.to_local(g).reshape(b, n * self.c.cross_slots, -1)
523
+ for cross, layer in zip(self.dec_cross, self.dec):
524
+ h = ckpt(cross, h, kv, context_id, pgroups, ok, enabled=self.c.grad_checkpoint)
525
+ h = ckpt(layer, h, doc, enabled=self.c.grad_checkpoint)
526
+ return {"logits": self.output(self.norm(h)), "heads": [],
527
+ "patches": valid.sum().detach(), "positions": x.numel()}
528
+
529
+
530
+ class Canon(nn.Module):
531
+ def __init__(self, d):
532
+ super().__init__()
533
+ # Identity initialization is stable without adding a second residual
534
+ # around Canon. The paper's operator replaces its input.
535
+ w = torch.zeros(4, d)
536
+ w[0] = 1
537
+ self.weight = nn.Parameter(w)
538
+
539
+ def forward(self, x, doc):
540
+ y = x * self.weight[0]
541
+ for lag in range(1, min(4, x.shape[1])):
542
+ h = F.pad(x[:, :-lag], (0, 0, lag, 0))
543
+ same = F.pad(doc[:, :-lag], (lag, 0), value=-1) == doc
544
+ y = y + h * self.weight[lag] * same[..., None]
545
+ return y
546
+
547
+
548
+ class CanonBlock(nn.Module):
549
+ def __init__(self, c):
550
+ super().__init__()
551
+ # Main paper equations explicitly specify LayerNorm in local modules.
552
+ self.n1, self.n2 = nn.LayerNorm(c.local_dim), nn.LayerNorm(c.local_dim)
553
+ self.a = Attention(c.local_dim, c.local_heads, c.local_window,
554
+ 500000.0, query_block=c.query_block)
555
+ self.ff = SwiGLU(c.local_dim, c.local_ff)
556
+ self.c1, self.c2 = Canon(c.local_dim), Canon(c.local_dim)
557
+
558
+ def forward(self, x, doc):
559
+ x = self.c1(x + self.a(self.n1(x), doc), doc)
560
+ return self.c2(x + self.ff(self.n2(x)), doc)
561
+
562
+
563
+ def coding_scores(h, eps2=1.0, mode="l2", doc=None):
564
+ # Detaching boundary decisions follows discrete Top-K selection. Selected
565
+ # encoder states still receive the normal language-model gradients.
566
+ with torch.no_grad(), torch.autocast(h.device.type, enabled=False):
567
+ x = h.detach().float()
568
+ b, t, d = x.shape
569
+ alpha = d / eps2
570
+ if mode == "l2":
571
+ return 0.5 * alpha * x.square().sum(-1)
572
+ if mode != "exact_smw":
573
+ raise ValueError(mode)
574
+ # Expensive diagnostic; unnormalized states are essential to Eq. 11.
575
+ eye = torch.eye(d, device=x.device, dtype=torch.float64).expand(b, d, d)
576
+ inv = eye.clone()
577
+ scores = []
578
+ for i in range(t):
579
+ if i and doc is not None:
580
+ inv = torch.where((doc[:, i] != doc[:, i-1])[:, None, None], eye, inv)
581
+ v = x[:, i].double().unsqueeze(-1)
582
+ av = inv @ v
583
+ q = (v.transpose(1, 2) @ av).flatten().clamp_min(0)
584
+ den = 1 + alpha * q
585
+ scores.append(0.5 * den.log())
586
+ inv = inv - (alpha / den)[:, None, None] * (av @ av.transpose(1, 2))
587
+ inv = (inv + inv.transpose(1, 2)) * 0.5
588
+ return torch.stack(scores, 1).float()
589
+
590
+
591
+ def causal_promotions(scores, doc, patch_size, window):
592
+ """Irrevocable trailing-window rank gate. A disclosed ByteFlow adaptation.
593
+
594
+ Current position is selected if its score is in the top 1/patch_size of
595
+ the observed window. Ties choose earlier positions. No future rank or cap.
596
+ """
597
+ s = F.pad(scores, (window - 1, 0), value=-torch.inf).unfold(1, window, 1)
598
+ d = F.pad(doc, (window - 1, 0), value=-1).unfold(1, window, 1)
599
+ valid = d == doc[..., None]
600
+ rank = ((s >= scores[..., None]) & valid).sum(-1)
601
+ quota = (valid.sum(-1).float() / patch_size).ceil().clamp_min(1)
602
+ starts = rank <= quota
603
+ starts[:, 0] = True
604
+ starts[:, 1:] |= doc[:, 1:] != doc[:, :-1]
605
+ return starts
606
+
607
+
608
+ def paper_promotions(scores, patch_size):
609
+ b, t = scores.shape
610
+ k = max(1, min(t, math.ceil(t / patch_size)))
611
+ starts = torch.zeros_like(scores, dtype=torch.bool)
612
+ starts[:, 0] = True
613
+ if k > 1:
614
+ # Stable sort provides a deterministic tie policy.
615
+ ix = torch.argsort(scores[:, 1:], dim=-1, descending=True, stable=True)[:, :k-1] + 1
616
+ starts.scatter_(1, ix, True)
617
+ return starts
618
+
619
+
620
+ class ByteFlowLM(nn.Module):
621
+ def __init__(self, c, depth, paper=False):
622
+ super().__init__()
623
+ self.c, self.paper = c, paper
624
+ self.embed = nn.Embedding(VOCAB, c.local_dim)
625
+ self.enc = nn.ModuleList([CanonBlock(c) for _ in range(c.encoder_layers)])
626
+ self.dec = nn.ModuleList([CanonBlock(c) for _ in range(c.decoder_layers)])
627
+ self.to_global = nn.Linear(c.local_dim, c.dim, bias=False)
628
+ self.global_stack = GlobalStack(c, depth)
629
+ self.up = nn.ModuleList([nn.Linear(c.dim, c.local_dim, bias=False) for _ in range(c.byteflow_bins)])
630
+ self.norm = nn.LayerNorm(c.local_dim)
631
+ self.output = nn.Linear(c.local_dim, VOCAB, bias=False)
632
+ self.apply(initialize)
633
+
634
+ def forward(self, x, loops=None):
635
+ b, t = x.shape
636
+ doc, h = documents(x), self.embed(x)
637
+ for layer in self.enc:
638
+ h = ckpt(layer, h, doc, enabled=self.c.grad_checkpoint)
639
+ scores = coding_scores(h, self.c.byteflow_eps2, self.c.byteflow_score, doc)
640
+ if self.paper:
641
+ starts = paper_promotions(scores, self.c.patch_size)
642
+ # EOS/BOS isolation is a common data-protocol adaptation.
643
+ starts[:, 1:] |= doc[:, 1:] != doc[:, :-1]
644
+ else:
645
+ starts = causal_promotions(scores, doc, self.c.patch_size, self.c.router_window)
646
+ pid, first, valid = segments(starts)
647
+ pdoc = torch.where(valid, doc.gather(1, first), -1)
648
+ z = self.to_global(gather(h, first))
649
+ g = self.global_stack(z, pdoc)
650
+ lifted = gather(g, pid)
651
+ # Paper Eq. 15 is length-dependent. Causal mode fixes the horizon to
652
+ # the configured context so extending a prefix cannot change old bins.
653
+ horizon = t if self.paper else self.c.context
654
+ bins = (torch.arange(t, device=x.device) * self.c.byteflow_bins // horizon).clamp_max(self.c.byteflow_bins - 1)
655
+ # Compute by contiguous bin spans; never materialize [T, G, local_dim].
656
+ up = []
657
+ for i, proj in enumerate(self.up):
658
+ selected = torch.nonzero(bins == i, as_tuple=False).flatten()
659
+ if selected.numel():
660
+ up.append(proj(lifted[:, selected]))
661
+ s = h + torch.cat(up, 1)
662
+ # Unused bins at short prefixes should have zero gradients, not missing
663
+ # ones in DDP. This does not alter any activation.
664
+ s = s + sum(proj.weight.reshape(-1)[0] * 0 for proj in self.up)
665
+ for layer in self.dec:
666
+ s = ckpt(layer, s, doc, enabled=self.c.grad_checkpoint)
667
+ return {"logits": self.output(self.norm(s)), "heads": [],
668
+ "patches": valid.sum().detach(), "positions": x.numel()}
669
+
670
+
671
+ def nparams(model, trainable=False):
672
+ return sum(p.numel() for p in model.parameters() if not trainable or p.requires_grad)
673
+
674
+
675
+ def build_model(arch, c, seed=42):
676
+ if arch not in ARCHS + ("entropy",): raise ValueError("Unsupported architecture; recurrent ouro is removed")
677
+ c.validate()
678
+ if c.loops != 1: raise ValueError("This unified screen supports one-pass Ouro only")
679
+ builders = {
680
+ "evabyte": lambda n: EvaByteLM(c, n),
681
+ "blt": lambda n: BLTLM(c, n),
682
+ "byteflow_causal": lambda n: ByteFlowLM(c, n, False),
683
+ "byteflow_paper": lambda n: ByteFlowLM(c, n, True),
684
+ }
685
+ # Meta construction does not allocate model weights or perturb the seed
686
+ # subsequently used for initialization. Frozen patcher counts are INCLUDED.
687
+ with torch.device("meta"):
688
+ target = nparams(OuroLM(c))
689
+ depth = c.body_layers + 2
690
+ if arch in builders:
691
+ base, one = nparams(builders[arch](0)), nparams(builders[arch](1))
692
+ per = one - base
693
+ depth = min(range(1, 33), key=lambda n: abs(base + n * per - target))
694
+ seed_all(seed)
695
+ if arch in ("ouro", "ouro_1pass"):
696
+ model = OuroLM(c)
697
+ elif arch == "entropy":
698
+ model = EntropyLM(c)
699
+ depth = c.entropy_layers
700
+ else:
701
+ model = builders[arch](depth)
702
+ meta = {"arch": arch, "total_params": nparams(model), "trainable_params": nparams(model, True),
703
+ "target_params": target, "depth": depth, "frozen_params": nparams(model)-nparams(model, True),
704
+ "param_delta_pct": 100 * (nparams(model)-target)/target,
705
+ "causal_ranking_eligible": arch != "byteflow_paper",
706
+ "fidelity": "paper-style diagnostic" if arch == "byteflow_paper" else
707
+ "explicit causal adaptation" if arch == "byteflow_causal" else "scaled architecture"}
708
+ return model, meta
709
+
710
+
711
+ def forward_model(model, ids, arch, loops=None):
712
+ return model(ids, loops=1 if arch == "ouro_1pass" else loops)
713
+
714
+
715
+ def targets_and_mask(sequence, offset=1):
716
+ # The caller forwards sequence[:, :-1]. Auxiliary heads lose their tail.
717
+ n = sequence.shape[1] - offset
718
+ target = sequence[:, offset:]
719
+ doc = documents(sequence)
720
+ valid = (target != BOS) & (target != PAD) & (doc[:, :n] == doc[:, offset:])
721
+ return target, valid
722
+
723
+
724
+ def head_counts(batches, nheads):
725
+ return torch.tensor([sum(int(targets_and_mask(x, j+1)[1].sum()) for x in batches)
726
+ for j in range(nheads)], dtype=torch.float64)
727
+
728
+
729
+ def loss_and_stats(out, sequence, denominators=None, world=1):
730
+ heads = out.get("heads") or [out["logits"]]
731
+ loss = out["logits"].float().sum() * 0
732
+ for j, logits in enumerate(heads):
733
+ target, valid = targets_and_mask(sequence, j+1)
734
+ n = target.shape[1]
735
+ ce = F.cross_entropy(logits[:, :n].float().reshape(-1, VOCAB), target.reshape(-1), reduction="none").view_as(target)
736
+ denom = valid.sum().clamp_min(1) if denominators is None else denominators[j].clamp_min(1)
737
+ # DDP averages gradients; world/whole-update denominator makes this a
738
+ # true global token mean even with varying numbers of document markers.
739
+ loss = loss + (ce * valid).sum() * world / denom / len(heads)
740
+ if j == 0:
741
+ byte = valid & (target < 256)
742
+ correct = logits[:, :n].argmax(-1) == target
743
+ stats = torch.stack(((ce * valid).sum(), valid.sum(), (ce * byte).sum(),
744
+ byte.sum(), (correct & byte).sum())).detach().double()
745
+ return loss, stats
746
+
747
+
unified/ablations/unified-v1-blt-none-s42/source/checkpoints.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Atomic full-state checkpoints, verified newest-first local/Hub recovery."""
2
+ import os,json,time,hashlib,shutil,uuid
3
+ from pathlib import Path
4
+ import torch
5
+ from huggingface_hub import HfApi,hf_hub_download,CommitOperationAdd,CommitOperationDelete
6
+ from huggingface_hub.errors import RepositoryNotFoundError
7
+ from dataset_registry import retry
8
+ from bet_model import BETConfig,EXPECTED_ARCH_SHAPE_SHA256
9
+
10
+ FORMAT='cortex-sparkbet9m-full-v1'
11
+ DEFAULT_HUB_CHECKPOINT_PREFIX='checkpoints/sparkbet9m'
12
+
13
+
14
+ def digest(path):
15
+ h=hashlib.sha256()
16
+ with open(path,'rb') as f:
17
+ for b in iter(lambda:f.read(4*1024*1024),b''):h.update(b)
18
+ return h.hexdigest()
19
+
20
+
21
+ def atomic_json(path,value):
22
+ path=Path(path);path.parent.mkdir(parents=True,exist_ok=True);tmp=path.with_name(path.name+'.tmp')
23
+ with open(tmp,'w') as f:json.dump(value,f,indent=2);f.flush();os.fsync(f.fileno())
24
+ os.replace(tmp,path)
25
+
26
+
27
+ def pipeline_hash(root):
28
+ h=hashlib.sha256()
29
+ for p in sorted(Path(root).glob('*.py')):h.update(p.name.encode());h.update(p.read_bytes())
30
+ return h.hexdigest()
31
+
32
+
33
+ def save(run,state,keep=3):
34
+ root=Path(run)/'checkpoints';root.mkdir(parents=True,exist_ok=True)
35
+ name=f'checkpoint-{state["step"]:012d}';dest=root/name
36
+ if (dest/'COMPLETE').exists():return dest
37
+ tmp=root/('.incomplete-'+uuid.uuid4().hex);tmp.mkdir()
38
+ try:
39
+ with open(tmp/'training.pt','wb') as f:torch.save(state,f);f.flush();os.fsync(f.fileno())
40
+ meta=dict(format=FORMAT,step=state['step'],lineage=state['lineage'],architecture=EXPECTED_ARCH_SHAPE_SHA256,
41
+ pipeline=state['pipeline'],sha256=digest(tmp/'training.pt'),bytes=(tmp/'training.pt').stat().st_size,saved_at=time.time())
42
+ atomic_json(tmp/'metadata.json',meta);(tmp/'COMPLETE').write_text('complete\n')
43
+ if dest.exists():shutil.rmtree(dest)
44
+ os.replace(tmp,dest);atomic_json(Path(run)/'latest.json',dict(path=str(dest),**meta))
45
+ complete=sorted(p for p in root.glob('checkpoint-*') if (p/'COMPLETE').exists())
46
+ for p in complete[:-keep]:shutil.rmtree(p)
47
+ return dest
48
+ except BaseException:
49
+ if tmp.exists():shutil.rmtree(tmp)
50
+ raise
51
+
52
+
53
+ def verified_load(path):
54
+ path=Path(path);meta=json.loads((path/'metadata.json').read_text())
55
+ if meta['format']!=FORMAT or meta['architecture']!=EXPECTED_ARCH_SHAPE_SHA256:raise ValueError('Incompatible checkpoint metadata')
56
+ file=path/'training.pt'
57
+ if file.stat().st_size!=meta['bytes'] or digest(file)!=meta['sha256']:raise ValueError('Checkpoint checksum mismatch')
58
+ state=torch.load(file,map_location='cpu',weights_only=True)
59
+ if state['format']!=FORMAT or state['step']!=meta['step'] or state['lineage']!=meta['lineage']:raise ValueError('Checkpoint state/metadata mismatch')
60
+ return state
61
+
62
+
63
+ def candidates(roots,repo=None,cache=None,checkpoint_prefix=DEFAULT_HUB_CHECKPOINT_PREFIX):
64
+ items=[]
65
+ for root in roots:
66
+ root=Path(root)
67
+ if not root.exists():continue
68
+ for p in root.rglob('metadata.json'):
69
+ if not (p.parent/'COMPLETE').exists() or not (p.parent/'training.pt').exists():continue
70
+ try:
71
+ m=json.loads(p.read_text())
72
+ if m.get('format')==FORMAT and m.get('architecture')==EXPECTED_ARCH_SHAPE_SHA256:
73
+ items.append(dict(meta=m,path=p.parent,remote=False))
74
+ except (ValueError,OSError):continue
75
+ if repo:
76
+ api=HfApi()
77
+ try:
78
+ info=retry(lambda:api.model_info(repo));files=[f.rfilename for f in info.siblings];revision=info.sha
79
+ except RepositoryNotFoundError:files=[];revision=None
80
+ prefix=checkpoint_prefix.strip('/')+'/'
81
+ for f in files:
82
+ if f.startswith(prefix) and f.endswith('/metadata.json') and f.rsplit('/',1)[0]+'/COMPLETE' in files:
83
+ local=retry(lambda:hf_hub_download(repo,f,revision=revision,cache_dir=cache));m=json.loads(Path(local).read_text())
84
+ if m.get('format')==FORMAT and m.get('architecture')==EXPECTED_ARCH_SHAPE_SHA256:
85
+ items.append(dict(meta=m,prefix=f.rsplit('/',1)[0],remote=True,revision=revision))
86
+ return sorted(items,key=lambda x:(x['meta']['step'],x['meta']['saved_at']),reverse=True)
87
+
88
+
89
+ def resume(roots,repo,run,expected_pipeline,checkpoint_prefix=DEFAULT_HUB_CHECKPOINT_PREFIX,
90
+ allowed_previous_pipelines=(),migration_schedule='progressive_data_stages_v2',
91
+ aux_stage_base_updates=128):
92
+ items=candidates(roots,repo,str(Path(run)/'hub_cache'),checkpoint_prefix)
93
+ lineages={i['meta']['lineage'] for i in items}
94
+ if len(lineages)>1:
95
+ raise RuntimeError('Multiple SparkBET run lineages found. Select the intended run instead of mixing checkpoint histories.')
96
+ for item in items:
97
+ try:
98
+ if item['remote']:
99
+ dest=Path(run)/'hub_resume'/Path(item['prefix']).name;dest.mkdir(parents=True,exist_ok=True)
100
+ for n in ['metadata.json','training.pt','COMPLETE']:
101
+ retry(lambda n=n:hf_hub_download(repo,item['prefix']+'/'+n,revision=item['revision'],local_dir=Path(run)/'hub_download'))
102
+ source=Path(run)/'hub_download'/item['prefix']/n;os.replace(source,dest/n)
103
+ path=dest
104
+ else:path=item['path']
105
+ state=verified_load(path)
106
+ except (OSError,ValueError,EOFError,RuntimeError) as exc:
107
+ print(f'Resume candidate step {item["meta"]["step"]} unusable: {type(exc).__name__}; trying older complete checkpoint.',flush=True);continue
108
+ if state['pipeline']!=expected_pipeline:
109
+ # Only an explicitly listed, exact previous pipeline fingerprint can
110
+ # cross the auxiliary-scheduling objective boundary. Never accept
111
+ # unrelated code/data changes or silently restart data cursors.
112
+ previous=state['pipeline']
113
+ if previous not in allowed_previous_pipelines:
114
+ raise RuntimeError('Training/data code differs from this SparkBET checkpoint. Exact data cursors are preserved by refusing an implicit migration.')
115
+ # Start the new long-form curriculum at L1 right here, not at
116
+ # an arbitrary stage computed from the historical global step.
117
+ config=state.setdefault('config',{})
118
+ config['aux_loop_schedule']=migration_schedule
119
+ config['aux_stage_base_updates']=int(aux_stage_base_updates)
120
+ config['aux_curriculum_origin_step']=int(state['step'])
121
+ state.setdefault('schedule_migrations',[]).append(dict(
122
+ step=state['step'],previous_pipeline=previous,
123
+ new_pipeline=expected_pipeline,schedule=migration_schedule,
124
+ curriculum_origin_step=int(state['step']),
125
+ aux_stage_base_updates=int(aux_stage_base_updates)))
126
+ state['pipeline']=expected_pipeline
127
+ print(f'Verified data-stage migration at update {state["step"]:,}; preserving model, optimizer, scaler and data cursors. New curriculum begins at L1.',flush=True)
128
+ print(f'Resumed complete state at update {state["step"]:,} from {"Hub" if item["remote"] else "local"}.',flush=True)
129
+ return state
130
+ if items:raise RuntimeError('Checkpoints exist but none passed verification; refusing a silent fresh run')
131
+ return None
132
+
133
+
134
+ def legacy_import(roots,explicit=None):
135
+ # The previous BET-74M tensors/optimizer are shape-incompatible with SparkBET-9M.
136
+ # Do not silently splice two architectures. New SparkBET sessions can still
137
+ # recover complete SparkBET state from local/Hub checkpoints above.
138
+ if explicit:
139
+ raise ValueError('The legacy BET-74M checkpoint cannot be migrated into the 9.35M SparkBET architecture. Start fresh or use a SparkBET full-state checkpoint.')
140
+ return None
141
+
142
+
143
+ class Publisher:
144
+ def __init__(self,repo,run,keep=2,checkpoint_prefix=DEFAULT_HUB_CHECKPOINT_PREFIX):
145
+ self.repo,self.run,self.keep=repo,Path(run),keep
146
+ self.checkpoint_prefix=checkpoint_prefix.strip('/')
147
+ self.api=HfApi()
148
+ def authenticate(self):
149
+ retry(lambda:self.api.whoami());retry(lambda:self.api.create_repo(self.repo,repo_type='model',exist_ok=True))
150
+ def logs(self):
151
+ folder=self.run/'publish_logs'
152
+ if folder.exists():
153
+ retry(lambda:self.api.upload_folder(repo_id=self.repo,folder_path=folder,path_in_repo='runs',commit_message='Cortex training metrics'))
154
+ events=sorted(folder.rglob('*tfevents*'),key=lambda p:p.stat().st_mtime)
155
+ for p in events[:-2]:p.unlink()
156
+ def checkpoint(self,path):
157
+ path=Path(path);prefix=self.checkpoint_prefix+'/'+path.name;files=retry(lambda:self.api.list_repo_files(self.repo))
158
+ base=self.checkpoint_prefix+'/'
159
+ old=sorted({f[len(base):].split('/')[0] for f in files if f.startswith(base+'checkpoint-') and f.endswith('/COMPLETE')})
160
+ keep=set(sorted(set(old+[path.name]))[-self.keep:])
161
+ operations=[CommitOperationAdd(path_in_repo=prefix+'/'+p.name,path_or_fileobj=str(p)) for p in path.iterdir() if p.is_file()]
162
+ for f in files:
163
+ if f.startswith(base+'checkpoint-'):
164
+ name=f[len(base):].split('/')[0]
165
+ if name not in keep:operations.append(CommitOperationDelete(path_in_repo=f))
166
+ retry(lambda:self.api.create_commit(repo_id=self.repo,operations=operations,commit_message=f'Full resumable SparkBET-9M state at {path.name}'))
167
+ def export(self,model,step,manifest,source_root):
168
+ from safetensors.torch import save_file
169
+ from dataclasses import asdict
170
+ dest=self.run/'export';dest.mkdir(exist_ok=True)
171
+ # HF wrapper owns the training model as `core`, so exported tensor names are
172
+ # prefixed accordingly and load strictly through AutoModelForCausalLM.
173
+ weights={f'core.{k}':v.detach().cpu().contiguous() for k,v in model.state_dict().items()}
174
+ save_file(weights,str(dest/'model.safetensors'),metadata={'format':'pt'})
175
+ c=asdict(model.c)
176
+ config=dict(
177
+ model_type='bet',architectures=['BETForCausalLM'],step=step,
178
+ vocab_size=c['vocab_size'],hidden_size=c['hidden_size'],intermediate_size=c['intermediate_size'],
179
+ prelude_layers=c['prelude_layers'],body_blocks=c['body_blocks'],coda_layers=c['coda_layers'],
180
+ num_attention_heads=c['num_heads'],num_key_value_heads=c['num_kv_heads'],head_dim=c['head_dim'],
181
+ lora_rank=c['lora_rank'],hyper_lanes=c['hyper_lanes'],max_position_embeddings=c['max_seq_len'],max_loops=c['max_loops'],
182
+ rope_theta=c['rope_theta'],rms_norm_eps=c['rms_eps'],ddl_beta_init=c['ddl_beta_init'],ddl_k_eps=c['ddl_k_eps'],
183
+ ddl_v_sigmoid_scale=c['ddl_v_sigmoid_scale'],refinement_cycles=8,use_cache=False,tie_word_embeddings=True,
184
+ pad_token_id=256,bos_token_id=257,eos_token_id=258,precision='fp16 autocast / fp32 master',
185
+ auto_map={'AutoConfig':'configuration_bet.BETConfig','AutoModelForCausalLM':'modeling_bet.BETForCausalLM','AutoTokenizer':['tokenization_bet.BETByteTokenizer',None]},
186
+ )
187
+ atomic_json(dest/'config.json',config);atomic_json(dest/'dataset_manifest.json',manifest)
188
+ for name in ['bet_model.py','inference.py','records.py','README.md','configuration_bet.py','modeling_bet.py','tokenization_bet.py','tokenizer_config.json']:
189
+ shutil.copy2(Path(source_root)/name,dest/name)
190
+ retry(lambda:self.api.upload_folder(repo_id=self.repo,folder_path=dest,commit_message=f'SparkBET-9M inference export at update {step}'))
unified/ablations/unified-v1-blt-none-s42/source/configuration_bet.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+
4
+ class BETConfig(PretrainedConfig):
5
+ model_type = "bet"
6
+
7
+ def __init__(
8
+ self,
9
+ vocab_size=259,
10
+ hidden_size=324,
11
+ intermediate_size=864,
12
+ prelude_layers=1,
13
+ body_blocks=6,
14
+ coda_layers=1,
15
+ num_attention_heads=6,
16
+ num_key_value_heads=2,
17
+ head_dim=54,
18
+ lora_rank=16,
19
+ hyper_lanes=2,
20
+ max_position_embeddings=1024,
21
+ max_loops=8,
22
+ rope_theta=10_000.0,
23
+ rms_norm_eps=1e-6,
24
+ ddl_beta_init=1.0,
25
+ ddl_k_eps=1e-2,
26
+ ddl_v_sigmoid_scale=4.0,
27
+ refinement_cycles=8,
28
+ use_cache=False,
29
+ tie_word_embeddings=True,
30
+ pad_token_id=256,
31
+ bos_token_id=257,
32
+ eos_token_id=258,
33
+ **kwargs,
34
+ ):
35
+ super().__init__(
36
+ pad_token_id=pad_token_id,
37
+ bos_token_id=bos_token_id,
38
+ eos_token_id=eos_token_id,
39
+ tie_word_embeddings=tie_word_embeddings,
40
+ is_encoder_decoder=False,
41
+ **kwargs,
42
+ )
43
+ self.vocab_size=int(vocab_size)
44
+ self.hidden_size=int(hidden_size)
45
+ self.intermediate_size=int(intermediate_size)
46
+ self.prelude_layers=int(prelude_layers)
47
+ self.body_blocks=int(body_blocks)
48
+ self.coda_layers=int(coda_layers)
49
+ # Common HF tooling expects num_hidden_layers even though only the body loops.
50
+ self.num_hidden_layers=int(prelude_layers+body_blocks+coda_layers)
51
+ self.num_attention_heads=int(num_attention_heads)
52
+ self.num_key_value_heads=int(num_key_value_heads)
53
+ self.head_dim=int(head_dim)
54
+ self.lora_rank=int(lora_rank)
55
+ self.hyper_lanes=int(hyper_lanes)
56
+ self.max_position_embeddings=int(max_position_embeddings)
57
+ self.max_loops=int(max_loops)
58
+ self.rope_theta=float(rope_theta)
59
+ self.rms_norm_eps=float(rms_norm_eps)
60
+ self.ddl_beta_init=float(ddl_beta_init)
61
+ self.ddl_k_eps=float(ddl_k_eps)
62
+ self.ddl_v_sigmoid_scale=float(ddl_v_sigmoid_scale)
63
+ self.refinement_cycles=int(refinement_cycles)
64
+ self.use_cache=bool(use_cache)
unified/ablations/unified-v1-blt-none-s42/source/cortex_curriculum_v16.py ADDED
The diff for this file is too large to render. See raw diff
 
unified/ablations/unified-v1-blt-none-s42/source/cortex_data.py ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deterministic answer supervision and executable compositional train/test splits."""
2
+ import hashlib
3
+ import random
4
+ import numpy as np
5
+
6
+ REVISION = 'cortex-answer-execution-v1'
7
+ HELD_PAIRS = {(1, 2), (2, 1)} # rotate -> swap and swap -> rotate, by semantic operation
8
+
9
+
10
+ def program_split(program):
11
+ return 'composition' if any(pair in HELD_PAIRS for pair in zip(program, program[1:])) else 'train'
12
+
13
+
14
+ def sample_program(rng, split, depth):
15
+ for _ in range(10000):
16
+ program = tuple(rng.randrange(4) for _ in range(depth))
17
+ if program_split(program) == ('composition' if split == 'composition' else 'train'):
18
+ return program
19
+ raise RuntimeError('could not sample program split')
20
+
21
+
22
+ def execute(values, program, permutation):
23
+ states = []
24
+ values = list(values)
25
+ for op in program:
26
+ if op == 0:
27
+ values = values[::-1]
28
+ elif op == 1:
29
+ values = values[1:] + values[:1]
30
+ elif op == 2:
31
+ values = values[:]
32
+ for i in range(0, len(values) - 1, 2):
33
+ values[i], values[i + 1] = values[i + 1], values[i]
34
+ elif op == 3:
35
+ values = [permutation[x] for x in values]
36
+ else:
37
+ raise ValueError(op)
38
+ states.append(values[:])
39
+ return states
40
+
41
+
42
+ def composition_episode(c, seed, context, split='train', difficulty=1, trace=None):
43
+ """Reusable sequence operations with held-out *semantic* ordered pairs.
44
+
45
+ Byte roles, alphabets and substitution maps are independently randomized.
46
+ Training/query demonstrations NEVER include held-out pairs or depths > 4.
47
+ Trace mode emits intermediate states autoregressively; direct mode emits only
48
+ the result. The mode and separators are grounded by demonstrations in both.
49
+ """
50
+ rng = random.Random(int(seed))
51
+ surface = c.Surface(rng)
52
+ atoms = surface.atoms(6)
53
+ ops = surface.atoms(4)
54
+ begin, sep, end, chain, mode, state_sep = surface.take(6)
55
+ perm = list(range(6)); rng.shuffle(perm)
56
+ trace = bool(rng.randrange(2)) if trace is None else bool(trace)
57
+ depth = rng.randint(5, 8) if split == 'depth' else rng.randint(2 if split == 'composition' else 1, 2 if difficulty == 0 else 4)
58
+ length = rng.randint(13, 20) if split == 'length' else rng.randint(4, 8 if difficulty == 0 else 12)
59
+ program = sample_program(rng, split, depth)
60
+ b = c.Builder(25, 'compositional_execution')
61
+ def encode(xs): return [atoms[x] for x in xs]
62
+ def query(xs, prog): return [begin] + encode(xs) + [chain] + [ops[o] for o in prog] + [sep]
63
+ def answer(states, traced):
64
+ selected = states if traced else states[-1:]
65
+ result = []
66
+ for i, state in enumerate(selected):
67
+ if i: result.append(state_sep)
68
+ result.extend(encode(state))
69
+ return result
70
+ # Complete substitution table; unlike a few random demos, covers every query symbol.
71
+ for x in range(6):
72
+ b.emit(query([x], (3,)) + encode([perm[x]]) + [end])
73
+ # Distinct symbols and odd/even widths disambiguate positional primitives.
74
+ for op in range(3):
75
+ for width in (5, 6):
76
+ xs = rng.sample(range(6), width)
77
+ b.emit(query(xs, (op,)) + encode(execute(xs, (op,), perm)[-1]) + [end])
78
+ # The same local mode marker/grammar is used for demonstrations and query.
79
+ for demo_depth in (2, 3):
80
+ xs = [rng.randrange(6) for _ in range(5)]
81
+ prog = sample_program(rng, 'train', demo_depth)
82
+ b.emit([mode] + query(xs, prog) + answer(execute(xs, prog, perm), trace) + [end])
83
+ xs = [rng.randrange(6) for _ in range(length)]
84
+ b.emit([mode] + query(xs, program)); b.mark_prompt()
85
+ b.emit(answer(execute(xs, program, perm), trace), c.W_TARGET)
86
+ ep = b.finish(context)
87
+ ep.program = program; ep.split = split; ep.trace = trace
88
+ ep.latent_input = xs; ep.permutation = perm; ep.atoms = atoms; ep.state_sep = state_sep
89
+ ep.weights = [0] * ep.prompt_len + [c.W_TARGET] * (len(ep.tokens) - ep.prompt_len)
90
+ return ep
91
+
92
+
93
+ def answer_episode(c, entry, difficulty, context, seed, candidates=1):
94
+ """One inference protocol for both training and evaluation, including EOS.
95
+
96
+ Remove ignored suffix terminators instead of feeding an unsupervised byte
97
+ during generation. Keep grid shape/format tokens and supervise the complete
98
+ continuation. Random demonstration outputs are context, never loss targets.
99
+ """
100
+ public = entry['name'] == 'gen_arc_static_public_train'
101
+ ep, _ = c._v12_generate_episode(entry, difficulty, context, seed,
102
+ 'weighted' if public else 'answer', candidates)
103
+ tail=ep.tokens[ep.prompt_len:]
104
+ weights=ep.weights[ep.prompt_len:]
105
+ last=max((i for i,(t,w) in enumerate(zip(tail,weights)) if w>0 and t!=c.EOS),default=-1)
106
+ suffix=tail[:last+1]
107
+ if not suffix:
108
+ raise ValueError(f"empty answer: {entry['name']}")
109
+ ep.tokens = ep.tokens[:ep.prompt_len] + suffix + [c.EOS]
110
+ ep.weights = [0] * ep.prompt_len + [c.W_TARGET] * (len(suffix) + 1)
111
+ ep.validate(context)
112
+ return ep
113
+
114
+
115
+ def padded_batch(c, episodes):
116
+ # Each row is an independent episode: no cross-example attention, no dropped
117
+ # overflow bins, no long-episode preference. Bucket only after sampling.
118
+ width = ((max(len(ep.tokens) - 1 for ep in episodes) + 31) // 32) * 32 + 1
119
+ tok = np.full((len(episodes), width), c.PAD, dtype=np.int64)
120
+ wt = np.zeros(tok.shape, dtype=np.float32)
121
+ seg = np.full(tok.shape, -1, dtype=np.int64)
122
+ for i, ep in enumerate(episodes):
123
+ n = len(ep.tokens)
124
+ tok[i, :n] = ep.tokens; wt[i, :n] = ep.weights; seg[i, :n] = i
125
+ return tok, wt, seg, len(episodes), float(np.mean(tok != c.PAD))
126
+
127
+
128
+ def source_digest(*paths):
129
+ from pathlib import Path
130
+ h = hashlib.sha256()
131
+ for path in paths: h.update(Path(path).read_bytes())
132
+ return h.hexdigest()
unified/ablations/unified-v1-blt-none-s42/source/cortex_oracles.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Independent small-domain reference algorithms used only by tests/audits."""
2
+ from itertools import permutations, product
3
+ from functools import reduce
4
+
5
+ def paths(edges,start,goal=None):
6
+ out=[]
7
+ def visit(path):
8
+ if goal is None or path[-1]==goal:out.append(path)
9
+ for a,b in edges:
10
+ if a==path[-1] and b not in path:visit(path+[b])
11
+ visit([start]);return out
12
+
13
+ def program_reference(xs,program,mapping):
14
+ states=[];xs=list(xs)
15
+ for op in program:
16
+ n=len(xs)
17
+ if op=='reverse':indices=range(n-1,-1,-1)
18
+ elif op=='rotate':indices=[(i+1)%n for i in range(n)]
19
+ elif op=='swap':indices=[i+1 if i%2==0 and i+1<n else i-1 if i%2 else i for i in range(n)]
20
+ elif op=='map':xs=[dict(enumerate(mapping))[v] for v in xs];states.append(xs[:]);continue
21
+ elif op=='repeat':indices=[i for i in range(n) for _ in range(2)]
22
+ elif op=='evens':indices=range(0,n,2)
23
+ elif op=='dedup':indices=[i for i in range(n) if xs[i] not in xs[:i]]
24
+ else:raise KeyError(op)
25
+ xs=[xs[i] for i in indices];states.append(xs[:])
26
+ return states
27
+
28
+ def oracle(name,x):
29
+ if name=='selection_sort':
30
+ a=x[:];states=[]
31
+ for i in range(len(a)-1):
32
+ value=sorted(a[i:])[0];j=a.index(value,i);a[j],a[i]=a[i],a[j];states.append(a[:])
33
+ return states
34
+ if name=='insertion_sort':return [sorted(x[:i+1])+x[i+1:] for i in range(1,len(x))]
35
+ if name=='minmax_scan':return [[sorted(x[:i])[0],sorted(x[:i])[-1]] for i in range(1,len(x)+1)]
36
+ if name=='linear_search':
37
+ a,t=x;return [a.index(t) if t in a else -1]
38
+ if name=='binary_search':
39
+ a,t=x
40
+ def visit(lo,hi):
41
+ if lo>=hi:return []
42
+ m=(lo+hi-1)//2
43
+ return [m]+([] if a[m]==t else visit(m+1,hi) if a[m]<t else visit(lo,m))
44
+ return [visit(0,len(a)),a.index(t) if t in a else -1]
45
+ if name=='string_match':
46
+ a,b=x;return [i for i in range(len(a)) if tuple(a[i:i+len(b)])==tuple(b)]
47
+ if name in ('bfs_order','dfs_order'):
48
+ edges,start=x
49
+ if name=='bfs_order':
50
+ # Minimal distance and lexicographically smallest discovery path.
51
+ pp=paths(edges,start);best={}
52
+ for p in pp:
53
+ k=(len(p),p)
54
+ if p[-1] not in best or k<best[p[-1]]:best[p[-1]]=k
55
+ return sorted(best,key=best.__getitem__)
56
+ out=[]
57
+ def visit(v):
58
+ if v in out:return
59
+ out.append(v)
60
+ for z in sorted(b for a,b in edges if a==v):visit(z)
61
+ visit(start);return out
62
+ if name=='topo_sort':
63
+ nodes,edges=x
64
+ return list(min(p for p in permutations(nodes) if all(p.index(a)<p.index(b) for a,b in edges)))
65
+ if name=='shortest_path':
66
+ pp=paths(*x);return min(pp,key=lambda p:(len(p),p)) if pp else []
67
+ if name=='union_find':
68
+ edges,a,b=x;components=[{v} for edge in edges for v in edge]+[{a},{b}]
69
+ for u,v in edges:
70
+ joined=set().union(*(s for s in components if u in s or v in s));components=[s for s in components if not(s&joined)]+[joined]
71
+ return any({a,b}<=s for s in components)
72
+ if name=='interval_overlap':
73
+ a,b,c,d=x;return bool(set(range(a,b+1))&set(range(c,d+1)))
74
+ if name in ('bracket_balance','bracket_depth'):
75
+ balances=[sum(1 if t=='open' else -1 for t in x[:i]) for i in range(1,len(x)+1)]
76
+ return (all(v>=0 for v in balances) and (balances[-1] if balances else 0)==0) if name=='bracket_balance' else max([0]+balances)
77
+ if name in ('queue','deque'):
78
+ q=[];removed=[]
79
+ for cmd,*args in x:
80
+ if cmd=='left':q=[args[0]]+q
81
+ elif cmd=='right':q=q+[args[0]]
82
+ elif not q:removed.append('empty')
83
+ elif cmd=='pop_left':removed.append(q[0]);q=q[1:]
84
+ elif cmd=='pop_right':removed.append(q[-1]);q=q[:-1]
85
+ else:raise KeyError(cmd)
86
+ return [removed,q]
87
+ if name in ('kv_lookup','role_filler','assoc_distractors','nested_lookup','mutable_dictionary','delayed_recall'):
88
+ records,(key,depth)=x
89
+ for _ in range(depth):key=next(v for k,v in reversed(records) if k==key)
90
+ return key
91
+ if name in ('register_swap','register_update','multi_register','program_trace'):
92
+ state,cmds=x;versions=[tuple(state)]
93
+ for op,a,b in cmds:
94
+ old=versions[-1]
95
+ versions.append(tuple(b if op=='set' and i==a else old[b] if op in ('swap','copy') and i==a else old[a] if op=='swap' and i==b else old[i] for i in range(len(old))))
96
+ return [list(v) for v in versions[1:]] if name=='program_trace' else list(versions[-1])
97
+ if name=='conditional_branch':return x[1:][0 if x[0] else 1]
98
+ if name=='loop_counter':return sorted(range(x+1),reverse=True)
99
+ if name in ('stack_expr','prefix_expr','postfix_expr'):
100
+ # Convert either notation to a syntax tree, then evaluate the tree.
101
+ tokens=[t for t in x if t!='push'];stack=[]
102
+ for t in (reversed(tokens) if name=='prefix_expr' else tokens):
103
+ if type(t) is int:stack.append(t)
104
+ else:
105
+ a,b=stack.pop(),stack.pop();stack.append((t,a,b))
106
+ def evaluate(t):
107
+ if type(t) is int:return t
108
+ op,a,b=t;a,b=evaluate(a),evaluate(b)
109
+ return sum([a,b]) if op=='add' else sum(a for _ in range(b))
110
+ return evaluate(stack[0])
111
+ if name=='relative_coords':
112
+ n,pos,moves=x;out=list(pos)
113
+ for move in moves:out=[sorted([0,v+d,n-1])[1] for v,d in zip(out,move)]
114
+ return out
115
+ if name=='toroidal':
116
+ n,p,moves=x;return (p+sum(moves))%n
117
+ if name=='collision':
118
+ a,va,b,vb,t=x;out=[]
119
+ # Equal masses in 1D = freely crossing ghost particles with sorted positions.
120
+ for k in range(1,t+1):
121
+ ghosts=sorted([(a+k*va,va),(b+k*vb,vb)],key=lambda z:(z[0],z[1]))
122
+ out.append([ghosts[0][0],ghosts[1][0],ghosts[0][1],ghosts[1][1]])
123
+ return out
124
+ if name=='falling':return list(map(list,zip(*(sorted(col) for col in zip(*x)))))
125
+ if name=='obstacle_motion':
126
+ n,p,blocked,moves=x;history=[p]
127
+ for m in moves:history.append(history[-1]+m if history[-1]+m in set(range(n))-set(blocked) else history[-1])
128
+ return history
129
+ if name=='line_of_sight':
130
+ a,b,blocked=x;return len(set(range(min(a,b)+1,max(a,b)))&set(blocked))==0
131
+ if name=='containment':
132
+ (a,b),(c,d)=x;return set(range(c,d+1))<=set(range(a,b+1))
133
+ if name in ('partial_map','dynamic_plan'):
134
+ if name=='partial_map':
135
+ observations,start,goal=x;edges=set()
136
+ for view in observations:edges.update(map(tuple,view))
137
+ else:
138
+ original,changes,start,goal=x
139
+ # For each edge, its last update determines membership.
140
+ candidates=set(map(tuple,original))|{(a,b) for _,a,b in changes}
141
+ edges={edge for edge in candidates if next((op=='add' for op,a,b in reversed(changes) if (a,b)==edge),edge in set(map(tuple,original)))}
142
+ choices=paths(sorted(edges),start,goal)
143
+ return min(choices,key=lambda p:(len(p),p)) if choices else []
144
+ if name in ('path_keys','door_key','sparse_nav','irreversible_action'):
145
+ edges,start,goal,keys,locks=x;front=[(start,start in keys,[])];seen=set()
146
+ while front:
147
+ node,key,p=front.pop(0)
148
+ if node==goal:return p
149
+ if (node,key) in seen:continue
150
+ seen.add((node,key))
151
+ for i,(a,b) in enumerate(edges):
152
+ if a==node and (key or i not in locks):front.append((b,key or b in keys,p+[i]))
153
+ return []
154
+ if name=='causal_abduction':
155
+ a,b,y=x;return next(u for u in (0,1) if ((u+a+b)%2)==y)
156
+ if name=='anomaly_cause':
157
+ u,a,b,vx,vy=x;return [key for key,actual,expected in [('x',vx,(u+a)%2),('y',vy,(vx+b)%2)] if actual!=expected]
158
+ if name in ('direct_cause','confounding','causal_intervention'):
159
+ u,a,b,(target,value)=x
160
+ vx=(u+a)%2
161
+ if name=='confounding':return [vx,(u+b)%2]
162
+ if target=='x':vx=value
163
+ return [vx,value if target=='y' else (vx+b)%2]
164
+ if name in ('competing_hypotheses','noisy_evidence','qual_bayes'):
165
+ priors,table,obs=x;score=[p*reduce(lambda a,b:a*b,(row[e] for e in obs),1) for p,row in zip(priors,table)]
166
+ return [i for i,s in enumerate(score) if all(s>=other for other in score)]
167
+ if name=='active_disambiguation':
168
+ columns=list(zip(*x));counts=[sum(a!=b for i,a in enumerate(c) for b in c[i+1:]) for c in columns]
169
+ return sorted(range(len(counts)),key=lambda i:(-counts[i],i))[0]
170
+ if name in ('rule_change','rule_switch'):
171
+ maps,events,start=x;out=[]
172
+ for k in range(1,len(events)+1):
173
+ mode=0;v=start
174
+ for op,arg in events[:k]:
175
+ if op=='switch':mode=arg
176
+ else:v=dict(enumerate(maps[mode]))[v]
177
+ out.append(v)
178
+ return out
179
+ if name=='goal_switch':
180
+ edges,start,goals=x;return [oracle('shortest_path',[edges,a,b]) for a,b in zip([start]+goals,goals)]
181
+ if name=='unknown_goal':return sorted(set(x[0])&set(x[1])-set(x[2]))
182
+ if name=='tool_affordance':return [i for i,tool in enumerate(x[0]) if set(x[1])<=set(tool)]
183
+ if name=='select_cell':return [list(divmod(i,len(x[0][0]))) for i,v in enumerate(sum(x[0],[])) if v==x[1]]
184
+ if name=='hidden_rule':
185
+ rules,demos,q=x;answers={(q+r)%7 for r in rules if all((b-a)%7==r for a,b in demos)}
186
+ assert len(answers)==1;return next(iter(answers))
187
+ if name=='explore_infer_exploit':
188
+ tables,obs,state,goal=x;accepted=[t for t in tables if [t[s][a] for s,a,_ in obs]==[z for _,_,z in obs]]
189
+ choices=[tuple(i for i,v in enumerate(t[state]) if v==goal) for t in accepted]
190
+ assert choices and len(set(choices))==1;return list(choices[0])
191
+ if name in ('sequence_composition','instruction_comp','modifier_comp','compositional_holdout','program_states'):
192
+ states=program_reference(*x);return states if name=='program_states' else states[-1]
193
+ if name in ('novel_primitive','rule_remap'):return dict(enumerate(x[0]))[x[1]]
194
+ if name=='representation_remap':return [dict(enumerate(x[0]))[v] for v in x[1]]
195
+ if name=='cross_domain':
196
+ phi,p,values=x;return [phi[p[phi.index(v)]] for v in values]
197
+ if name=='context_operator':return program_reference(x[0],['reverse' if len(x[1])%2 else 'rotate'],list(range(5)))[0]
198
+ if name=='meta_rule':return program_reference(x[0],[x[2][op] for op in x[1]],x[3])[-1]
199
+ if name=='mini_dsl':
200
+ pending=[(x,False)];values=[]
201
+ while pending:
202
+ node,visited=pending.pop();op,*args=node
203
+ if op=='literal':values.append(args[0])
204
+ elif not visited:
205
+ pending.append((node,True))
206
+ kids=[args[1]] if op=='repeat' else args
207
+ pending.extend((z,False) for z in reversed(kids))
208
+ elif op=='reverse':values.append(list(reversed(values.pop())))
209
+ elif op=='repeat':values.append(sum([values.pop()]*args[0],[]))
210
+ else:b,a=values.pop(),values.pop();values.append(a+b)
211
+ return values[0]
212
+ if name=='recursive_dsl':
213
+ rules,root,depth=x;word=[root]
214
+ for _ in range(depth):word=[v for t in word for v in rules.get(t,[t])]
215
+ return word
216
+ if name=='grammar_expansion':
217
+ rules,word,depth=x
218
+ def expand(t,n):return [t] if n==0 else sum([expand(z,n-1) for z in rules.get(t,[t])],[])
219
+ return sum([expand(t,depth) for t in word],[])
220
+ if name=='stable_partition':return sorted(x[0],key=lambda v:v not in x[1])
221
+ if name=='run_length_codec':
222
+ from itertools import groupby
223
+ return [[v,len(list(group))] for v,group in groupby(x)]
224
+ if name=='scope_shadowing':
225
+ merged={}
226
+ for frame in x[0]:merged.update(frame)
227
+ return merged.get(x[1],'missing')
228
+ if name=='relational_join':return [[a[0],a[1],b[1]] for a,b in product(*x) if a[0]==b[0]]
229
+ if name=='edit_script':
230
+ seq,edits=x;seq=seq[:]
231
+ for op,i,v in edits:
232
+ if op=='insert':seq=seq[:i]+[v]+seq[i:]
233
+ elif op=='delete':seq=seq[:i]+seq[i+1:]
234
+ else:seq=seq[:i]+[v]+seq[i+1:]
235
+ return seq
236
+ if name=='conditional_rewrite':
237
+ xs,a,b,guard=x;return [b if (prev,value)==(guard,a) else value for prev,value in zip([None]+xs,xs)]
238
+ if name=='span_reordering':
239
+ xs,a,b=x;return [xs[a+b-i-1] if a<=i<b else xs[i] for i in range(len(xs))]
240
+ if name=='nested_substitution':
241
+ tree,mapping=x
242
+ import copy
243
+ out=copy.deepcopy(tree);pending=[out]
244
+ while pending:
245
+ node=pending.pop()
246
+ for i,v in enumerate(node):
247
+ if isinstance(v,list):pending.append(v)
248
+ else:node[i]=mapping.get(v,v)
249
+ return out
250
+ raise KeyError(f'No independent oracle for {name}')
unified/ablations/unified-v1-blt-none-s42/source/cortex_source.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Paired symbolic/nonce tasks with bounded rejection and preserved answer grammar."""
2
+ import random,hashlib,json,string
3
+ import cortex_curriculum_v16 as kernel
4
+ from cortex_data import answer_episode,composition_episode
5
+ from records import BOS,EOS,ids,validate,Oversize
6
+ CATALOG=kernel._v11_family_catalog(max(kernel.STAGES),False)
7
+ FAMILIES=[e['name'] for e in CATALOG]+['compositional_execution']
8
+
9
+ def seed_for(namespace,index):return int.from_bytes(hashlib.blake2b(f'{namespace}:{index}'.encode(),digest_size=8).digest(),'big')
10
+
11
+ class WordSurface:
12
+ def __init__(self,seed):
13
+ rng=random.Random(seed);self.codes=rng.sample([a+b for a in string.ascii_lowercase for b in string.ascii_lowercase],256)
14
+ self.inverse={v:i for i,v in enumerate(self.codes)}
15
+ def encode(self,raw):return ' '.join(self.codes[b] for b in raw)
16
+ def decode(self,text):return [self.inverse[s] for s in text.split()]
17
+
18
+ def render(ep,family,seed,wordlike,limit):
19
+ p=ep.tokens[1:ep.prompt_len];a=ep.tokens[ep.prompt_len:-1]
20
+ if not a or any(not 0<=t<256 for t in p+a):raise ValueError('Invalid kernel payload')
21
+ header='Cortex: infer the demonstrated rule and complete the query.\n'
22
+ if hasattr(ep,'audit'):
23
+ au=ep.audit
24
+ # Previously string operations (e.g. left/right/pop) had ungrounded random
25
+ # labels and sometimes appeared for the first time in the query. Ground
26
+ # their interpretation explicitly without printing any query solution.
27
+ bindings={f'{v:02x}':key[1] for key,v in au['symbols'].items() if key[0]!='int'}
28
+ start,close,sep,end,unit,neg=au['markers']
29
+ header+=f"Mechanism: {au['mechanism']}. Indices are zero-based; graph ties use increasing numeric order.\n"
30
+ header+='Byte bindings (hex): '+json.dumps(bindings,ensure_ascii=True,separators=(',',':'))+'\n'
31
+ header+=f'Containers {start:02x}/{close:02x}; input/output {sep:02x}; demo end {end:02x}. Numeric values are grounded by unary examples.\n'
32
+ # Every referenced binding byte is present in the binary context too.
33
+ p=list(bindings_byte for bindings_byte in au['symbols'].values() if bindings_byte not in p)+p
34
+ if wordlike:
35
+ surface=WordSurface(seed)
36
+ # Map only legend-referenced roles; the rest are learned by matching demos.
37
+ if hasattr(ep,'audit'):
38
+ used=ep.audit['symbols'].values();header+='Hex-to-nonce: '+' '.join(f'{b:02x}={surface.codes[b]}' for b in used)+'\n'
39
+ header+='Container codes: '+' '.join(f'{b:02x}={surface.codes[b]}' for b in ep.audit['markers'])+'\n'
40
+ prefix=ids(header+'Nonce examples:\n'+surface.encode(p)+'\nAnswer:\n')
41
+ answer=ids(surface.encode(a))
42
+ else:
43
+ prefix=ids(header+'Byte examples:\n')+p;answer=a
44
+ r=dict(ids=[BOS]+prefix+answer+[EOS],weights=[0]*(len(prefix)+1)+[1]*(len(answer)+1),
45
+ source='cortex',prompt_len=len(prefix)+1,meta={'family':family,'surface':'nonce' if wordlike else 'symbolic'})
46
+ if len(r['ids'])>limit+1:raise Oversize(f'{family}: expanded {len(r["ids"])}')
47
+ return validate(r,limit)
48
+
49
+ def episode_pair(index,namespace='cortex74/train/v2',family=None,split='train',limit=1024):
50
+ pair=index//2;cycle,offset=divmod(pair,len(FAMILIES));order=list(range(len(FAMILIES)))
51
+ random.Random(seed_for(namespace+'/order',cycle)).shuffle(order)
52
+ selected=order[offset] if family is None else FAMILIES.index(family)
53
+ name=FAMILIES[selected]
54
+ for attempt in range(96):
55
+ seed=seed_for(namespace,(pair,attempt));difficulty=seed%3
56
+ try:
57
+ if name=='compositional_execution':ep=composition_episode(kernel,seed,2048,split,difficulty)
58
+ else:ep=answer_episode(kernel,CATALOG[selected],difficulty,2048,seed)
59
+ # Preserve the alternating nonce surface whenever it fits. At the 1K
60
+ # model context, a few nonce expansions are intrinsically >1K even when
61
+ # the underlying symbolic task fits; those records fall back to the
62
+ # complete symbolic view rather than truncating or dropping the family.
63
+ want_wordlike=bool(index%2)
64
+ try:
65
+ result=render(ep,name,seed,want_wordlike,limit)
66
+ except Oversize:
67
+ if not want_wordlike:raise
68
+ result=render(ep,name,seed,False,limit)
69
+ result['meta']['surface_fallback']='symbolic_due_to_context'
70
+ result['meta']['split']=split
71
+ return result
72
+ except Oversize:continue
73
+ raise RuntimeError(f'{name}: no complete paired example fits {limit} bytes')
74
+
75
+ class CortexSource:
76
+ def __init__(self,state=None,limit=1024):self.s=dict(state or {'index':0});self.limit=limit
77
+ def record(self):
78
+ r=episode_pair(self.s['index'],limit=self.limit);self.s['index']+=1;return r
79
+ def state_dict(self):return dict(self.s)
80
+ def validation(self):
81
+ return [episode_pair(2*i+j,'cortex74/validation/v2',f,split,self.limit)
82
+ for i,(f,split) in enumerate([(FAMILIES[k],'train') for k in range(0,len(FAMILIES)-1,17)]+
83
+ [('compositional_execution',s) for s in ['train','composition','depth','length']]) for j in (0,1)]
unified/ablations/unified-v1-blt-none-s42/source/cortex_tasks.py ADDED
@@ -0,0 +1,521 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Explicit executable tasks. Shared encoding never substitutes one task for another."""
2
+ from collections import deque, Counter
3
+ from dataclasses import dataclass
4
+ from itertools import product
5
+ import random
6
+
7
+ REVISION = 'cortex-explicit-mechanisms-v1'
8
+
9
+ # Operations deliberately have separate semantics; unknown operations are errors.
10
+ def sequence_execute(xs, program, mapping):
11
+ xs=list(xs);trace=[]
12
+ for op in program:
13
+ if op=='reverse': xs=list(reversed(xs))
14
+ elif op=='rotate': xs=xs[1:]+xs[:1]
15
+ elif op=='swap':
16
+ for i in range(0,len(xs)-1,2):xs[i],xs[i+1]=xs[i+1],xs[i]
17
+ elif op=='map':xs=[mapping[x] for x in xs]
18
+ elif op=='dedup':xs=list(dict.fromkeys(xs))
19
+ elif op=='repeat':xs=[x for x in xs for _ in range(2)]
20
+ elif op=='evens':xs=xs[::2]
21
+ else:raise ValueError(op)
22
+ trace.append(xs[:])
23
+ return trace
24
+
25
+
26
+ def traverse(edges,start,depth_first=False):
27
+ adj={}
28
+ for a,b in edges:adj.setdefault(a,[]).append(b)
29
+ seen=set();out=[];frontier=[start]
30
+ while frontier:
31
+ x=frontier.pop() if depth_first else frontier.pop(0)
32
+ if x in seen:continue
33
+ seen.add(x);out.append(x)
34
+ children=sorted(adj.get(x,[]))
35
+ frontier.extend(reversed(children) if depth_first else children)
36
+ return out
37
+
38
+
39
+ def shortest(edges,start,goal):
40
+ q=deque([(start,[start])]);seen={start}
41
+ while q:
42
+ x,path=q.popleft()
43
+ if x==goal:return path
44
+ for a,b in sorted(edges):
45
+ if a==x and b not in seen:seen.add(b);q.append((b,path+[b]))
46
+ return []
47
+
48
+
49
+ def selection_trace(xs):
50
+ xs=xs[:];out=[]
51
+ for i in range(len(xs)-1):
52
+ j=min(range(i,len(xs)),key=xs.__getitem__);xs[i],xs[j]=xs[j],xs[i];out.append(xs[:])
53
+ return out
54
+
55
+
56
+ def insertion_trace(xs):
57
+ xs=xs[:];out=[]
58
+ for i in range(1,len(xs)):
59
+ x=xs[i];j=i
60
+ while j and xs[j-1]>x:xs[j]=xs[j-1];j-=1
61
+ xs[j]=x;out.append(xs[:])
62
+ return out
63
+
64
+
65
+ def solve(name,x):
66
+ """Production semantics on structured latent inputs; no family-name fallback."""
67
+ if name=='selection_sort':return selection_trace(x)
68
+ if name=='insertion_sort':return insertion_trace(x)
69
+ if name=='minmax_scan':return [[min(x[:i]),max(x[:i])] for i in range(1,len(x)+1)]
70
+ if name=='linear_search':
71
+ arr,target=x;return [i for i,v in enumerate(arr) if v==target][:1] or [-1]
72
+ if name=='binary_search':
73
+ arr,target=x;lo,hi=0,len(arr)-1;visited=[]
74
+ while lo<=hi:
75
+ m=(lo+hi)//2;visited.append(m)
76
+ if arr[m]==target:break
77
+ if arr[m]<target:lo=m+1
78
+ else:hi=m-1
79
+ return [visited, m if lo<=hi else -1]
80
+ if name=='string_match':
81
+ text,pat=x;return [i for i in range(len(text)-len(pat)+1) if text[i:i+len(pat)]==pat]
82
+ if name in ('bfs_order','dfs_order'):return traverse(x[0],x[1],name=='dfs_order')
83
+ if name=='topo_sort':
84
+ nodes,edges=x;out=[]
85
+ while len(out)<len(nodes):
86
+ ready=[n for n in nodes if n not in out and all(a in out for a,b in edges if b==n)]
87
+ if not ready:raise ValueError('Cycle in topological task')
88
+ out.append(min(ready))
89
+ return out
90
+ if name=='shortest_path':return shortest(*x)
91
+ if name=='union_find':
92
+ edges,a,b=x;return b in traverse(edges+[[v,u] for u,v in edges],a)
93
+ if name=='interval_overlap':
94
+ a,b,c,d=x;return max(a,c)<=min(b,d)
95
+ if name=='bracket_balance':
96
+ depth=0
97
+ for op in x:
98
+ depth+=1 if op=='open' else -1
99
+ if depth<0:return False
100
+ return depth==0
101
+ if name=='bracket_depth':
102
+ d=best=0
103
+ for op in x:d+=1 if op=='open' else -1;best=max(best,d)
104
+ return best
105
+ if name in ('queue','deque'):
106
+ q=deque();out=[]
107
+ for op,*args in x:
108
+ if op=='right':q.append(args[0])
109
+ elif op=='left':q.appendleft(args[0])
110
+ elif op=='pop_left':out.append(q.popleft() if q else 'empty')
111
+ elif op=='pop_right':out.append(q.pop() if q else 'empty')
112
+ else:raise ValueError(op)
113
+ return [out,list(q)]
114
+ if name in ('kv_lookup','role_filler','assoc_distractors','nested_lookup','mutable_dictionary','delayed_recall'):
115
+ records,query=x;table={}
116
+ for k,v in records:table[k]=v
117
+ value=query[0]
118
+ for _ in range(query[1]):value=table[value]
119
+ return value
120
+ if name in ('register_swap','register_update','multi_register','program_trace'):
121
+ state,program=x;state=state[:];trace=[]
122
+ for op,a,b in program:
123
+ if op=='set':state[a]=b
124
+ elif op=='swap':state[a],state[b]=state[b],state[a]
125
+ elif op=='copy':state[a]=state[b]
126
+ else:raise ValueError(op)
127
+ trace.append(state[:])
128
+ return trace if name=='program_trace' else state
129
+ if name=='conditional_branch':
130
+ pred,a,b=x;return a if pred else b
131
+ if name=='loop_counter':return list(range(x,-1,-1))
132
+ if name in ('stack_expr','prefix_expr','postfix_expr'):
133
+ if name=='prefix_expr':
134
+ it=iter(x)
135
+ def parse():
136
+ t=next(it)
137
+ if isinstance(t,int):return t
138
+ a,b=parse(),parse()
139
+ return a+b if t=='add' else a*b
140
+ return parse()
141
+ stack=[]
142
+ for t in x:
143
+ if isinstance(t,int):stack.append(t)
144
+ elif t=='push':continue
145
+ else:
146
+ b,a=stack.pop(),stack.pop();stack.append(a+b if t=='add' else a*b)
147
+ if len(stack)!=1:raise ValueError('Bad expression')
148
+ return stack[0]
149
+ if name=='relative_coords':
150
+ n,(a,b),moves=x
151
+ for dx,dy in moves:a=max(0,min(n-1,a+dx));b=max(0,min(n-1,b+dy))
152
+ return [a,b]
153
+ if name=='toroidal':
154
+ n,p,moves=x
155
+ for delta in moves:p=(p+delta)%n
156
+ return p
157
+ if name=='collision':
158
+ a,va,b,vb,steps=x;trace=[]
159
+ for _ in range(steps):
160
+ na,nb=a+va,b+vb
161
+ a,b=min(na,nb),max(na,nb)
162
+ if na>=nb:va,vb=vb,va
163
+ trace.append([a,b,va,vb])
164
+ return trace
165
+ if name=='falling':
166
+ # Downward simultaneous settling: each column conserves occupied cells.
167
+ h=len(x);return [[1 if r>=h-sum(row[c] for row in x) else 0 for c in range(len(x[0]))] for r in range(h)]
168
+ if name=='obstacle_motion':
169
+ n,p,blocked,moves=x;visited=[p]
170
+ for delta in moves:
171
+ q=p+delta
172
+ if 0<=q<n and q not in blocked:p=q
173
+ visited.append(p)
174
+ return visited
175
+ if name=='line_of_sight':
176
+ a,b,blocked=x;return not any(min(a,b)<v<max(a,b) for v in blocked)
177
+ if name=='containment':
178
+ outer,inner=x;return outer[0]<=inner[0] and inner[1]<=outer[1]
179
+ if name=='partial_map':
180
+ observations,start,goal=x
181
+ edges=sorted({tuple(edge) for observation in observations for edge in observation})
182
+ return shortest(edges,start,goal)
183
+ if name=='dynamic_plan':
184
+ edges,changes,start,goal=x; edges=[e[:] for e in edges]
185
+ for op,a,b in changes:
186
+ if op=='remove':edges=[e for e in edges if e!=[a,b]]
187
+ elif op=='add' and [a,b] not in edges:edges.append([a,b])
188
+ return shortest(edges,start,goal)
189
+ if name in ('path_keys','door_key','sparse_nav','irreversible_action'):
190
+ # Real search in a directed graph; keys change which edges are traversable.
191
+ edges,start,goal,key_nodes,locks=x;q=deque([(start,0,[])]);seen={(start,0)}
192
+ while q:
193
+ node,key,path=q.popleft();key=key or int(node in key_nodes)
194
+ if node==goal:return path
195
+ for action,(a,b) in enumerate(edges):
196
+ if a!=node or (action in locks and not key):continue
197
+ state=(b,key)
198
+ if state not in seen:seen.add(state);q.append((b,key,path+[action]))
199
+ return []
200
+ if name=='causal_abduction':
201
+ a,b,y=x;return a^b^y
202
+ if name=='anomaly_cause':
203
+ u,a,b,observed_x,observed_y=x
204
+ return (['x'] if observed_x!=(u^a) else [])+(['y'] if observed_y!=(observed_x^b) else [])
205
+ if name in ('direct_cause','causal_intervention','confounding'):
206
+ # X=U xor A; Y=X xor B, except explicit interventions override equations.
207
+ u,a,b,intervention=x
208
+ xv=u^a;yv=xv^b
209
+ if intervention[0]=='x':xv=intervention[1];yv=xv^b
210
+ elif intervention[0]=='y':yv=intervention[1]
211
+ if name=='confounding':return [u^a,u^b] # fork U -> X and U -> Y
212
+ return [xv,yv]
213
+ if name=='abduce_noise':
214
+ a,b,observed_y=x;return observed_y^b^a
215
+ if name in ('competing_hypotheses','noisy_evidence','qual_bayes'):
216
+ # Integer likelihoods: exact posterior ranking, with priors and repeated evidence.
217
+ priors,likelihood,observations=x;score=priors[:]
218
+ for obs in observations:score=[s*row[obs] for s,row in zip(score,likelihood)]
219
+ best=max(score);return [i for i,v in enumerate(score) if v==best]
220
+ if name=='active_disambiguation':
221
+ # Pick the smallest test separating the largest number of hypothesis pairs.
222
+ table=x;counts=[sum(row[i]!=other[i] for j,row in enumerate(table) for other in table[j+1:]) for i in range(len(table[0]))]
223
+ return counts.index(max(counts))
224
+ if name in ('rule_change','rule_switch'):
225
+ maps,events,start=x;regime=0;state=start;out=[]
226
+ for op,arg in events:
227
+ if op=='switch':regime=arg
228
+ elif op=='step':state=maps[regime][state]
229
+ else:raise ValueError(op)
230
+ out.append(state)
231
+ return out
232
+ if name=='goal_switch':
233
+ edges,start,goals=x;out=[]
234
+ for goal in goals:
235
+ path=shortest(edges,start,goal);out.append(path);start=goal
236
+ return out
237
+ if name=='unknown_goal':
238
+ candidates,rewarded,rejected=x;return [n for n in candidates if n in rewarded and n not in rejected]
239
+ if name=='tool_affordance':
240
+ tools,requirements=x;return [i for i,t in enumerate(tools) if all(v in t for v in requirements)]
241
+ if name=='select_cell':
242
+ grid,color=x;return [[r,c] for r,row in enumerate(grid) for c,v in enumerate(row) if v==color]
243
+ if name=='hidden_rule':
244
+ rules,demos,query=x;valid=[r for r in rules if all((a+r)%7==b for a,b in demos)]
245
+ out={(query+r)%7 for r in valid}
246
+ if len(out)!=1:raise ValueError('Unidentifiable hidden rule')
247
+ return out.pop()
248
+ if name=='explore_infer_exploit':
249
+ # Hidden environment version; observations identify it, then pick rewarding action.
250
+ tables,observations,state,goal=x
251
+ valid=[t for t in tables if all(t[s][a]==z for s,a,z in observations)]
252
+ answers={tuple(a for a,z in enumerate(t[state]) if z==goal) for t in valid}
253
+ if len(answers)!=1:raise ValueError('Unidentifiable action')
254
+ return list(answers.pop())
255
+ if name in ('sequence_composition','instruction_comp','modifier_comp','compositional_holdout'):
256
+ xs,program,mapping=x;return sequence_execute(xs,program,mapping)[-1]
257
+ if name=='program_states':
258
+ xs,program,mapping=x;return sequence_execute(xs,program,mapping)
259
+ if name in ('novel_primitive','rule_remap'):
260
+ table,xv=x;return table[xv]
261
+ if name=='representation_remap':
262
+ mapping,values=x;return [mapping[v] for v in values]
263
+ if name=='cross_domain':
264
+ phi,perm,values=x;inv={v:i for i,v in enumerate(phi)};return [phi[perm[inv[v]]] for v in values]
265
+ if name=='context_operator':
266
+ xs,context=x;return list(reversed(xs)) if len(context)%2 else xs[1:]+xs[:1]
267
+ if name=='meta_rule':
268
+ xs,program,remap,perm=x;return sequence_execute(xs,[remap[o] for o in program],perm)[-1]
269
+ if name=='mini_dsl':
270
+ def run(node):
271
+ op,*args=node
272
+ if op=='literal':return args[0]
273
+ if op=='reverse':return run(args[0])[::-1]
274
+ if op=='concat':return run(args[0])+run(args[1])
275
+ if op=='repeat':return run(args[1])*args[0]
276
+ raise ValueError(op)
277
+ return run(x)
278
+ if name=='grammar_expansion':
279
+ rules,word,steps=x
280
+ for _ in range(steps):word=sum((rules.get(t,[t]) for t in word),[])
281
+ return word
282
+ if name=='recursive_dsl':
283
+ rules,root,depth=x
284
+ def expand(t,d):return [t] if not d or t not in rules else sum((expand(z,d-1) for z in rules[t]),[])
285
+ return expand(root,depth)
286
+ if name=='stable_partition':
287
+ xs,selected=x;return [v for v in xs if v in selected]+[v for v in xs if v not in selected]
288
+ if name=='run_length_codec':
289
+ out=[]
290
+ for v in x:
291
+ if out and out[-1][0]==v:out[-1][1]+=1
292
+ else:out.append([v,1])
293
+ return out
294
+ if name=='scope_shadowing':
295
+ frames,key=x
296
+ for frame in reversed(frames):
297
+ if key in frame:return frame[key]
298
+ return 'missing'
299
+ if name=='relational_join':
300
+ left,right=x;return [[k,a,b] for k,a in left for j,b in right if k==j]
301
+ if name=='edit_script':
302
+ xs,edits=x;xs=xs[:]
303
+ for op,i,v in edits:
304
+ if op=='insert':xs.insert(i,v)
305
+ elif op=='replace':xs[i]=v
306
+ elif op=='delete':xs.pop(i)
307
+ else:raise ValueError(op)
308
+ return xs
309
+ if name=='conditional_rewrite':
310
+ xs,a,b,guard=x;return [b if v==a and i and xs[i-1]==guard else v for i,v in enumerate(xs)]
311
+ if name=='span_reordering':
312
+ xs,start,end=x;return xs[:start]+list(reversed(xs[start:end]))+xs[end:]
313
+ if name=='nested_substitution':
314
+ tree,mapping=x
315
+ def walk(v):return [walk(z) for z in v] if isinstance(v,list) else mapping.get(v,v)
316
+ return walk(tree)
317
+ raise KeyError(f'No semantics for {name}')
318
+
319
+
320
+ ALGORITHM_NAMES = 'selection_sort insertion_sort minmax_scan linear_search binary_search string_match bfs_order dfs_order topo_sort union_find shortest_path interval_overlap'.split()
321
+ MEMORY_NAMES = 'kv_lookup role_filler assoc_distractors nested_lookup mutable_dictionary queue deque delayed_recall register_swap register_update conditional_branch loop_counter multi_register program_trace stack_expr prefix_expr postfix_expr bracket_balance bracket_depth'.split()
322
+ WORLD_NAMES = 'relative_coords line_of_sight containment collision toroidal falling obstacle_motion path_keys dynamic_plan direct_cause confounding competing_hypotheses noisy_evidence active_disambiguation qual_bayes causal_intervention causal_abduction rule_change anomaly_cause'.split()
323
+ META_NAMES = 'novel_primitive modifier_comp mini_dsl cross_domain context_operator meta_rule representation_remap compositional_holdout recursive_dsl instruction_comp rule_remap'.split()
324
+ INTERACTIVE_NAMES = 'hidden_rule unknown_goal door_key tool_affordance sparse_nav partial_map irreversible_action explore_infer_exploit rule_switch goal_switch select_cell'.split()
325
+ NEW_NAMES = 'sequence_composition program_states grammar_expansion stable_partition run_length_codec scope_shadowing relational_join edit_script conditional_rewrite span_reordering nested_substitution'.split()
326
+ EXPLICIT_NAMES = ALGORITHM_NAMES+MEMORY_NAMES+WORLD_NAMES+META_NAMES+INTERACTIVE_NAMES+NEW_NAMES
327
+
328
+
329
+ def sample_input(name,rng,difficulty=1,split='train'):
330
+ n=rng.randint(3,5) if split!='length' else rng.randint(8,10)
331
+ arr=lambda:[rng.randrange(5) for _ in range(n)]
332
+ order=rng.sample(range(6),6)
333
+ edges=[[order[i],order[i+1]] for i in range(5)]
334
+ edges += [[order[i],order[j]] for i in range(4) for j in range(i+2,6) if rng.random()<.35]
335
+ if name in ('selection_sort','insertion_sort','minmax_scan','run_length_codec'):return arr()
336
+ if name in ('linear_search','binary_search'):
337
+ xs=sorted(set(arr())) if name=='binary_search' else arr();return [xs,rng.choice(xs+[7])]
338
+ if name=='string_match':
339
+ xs=arr()+arr();i=rng.randrange(len(xs)-2);return [xs,xs[i:i+2]]
340
+ if name in ('bfs_order','dfs_order'):return [edges,rng.choice(order[:3])]
341
+ if name=='topo_sort':return [list(range(6)),[e for e in edges if rng.random()<.8]]
342
+ if name=='shortest_path':return [edges,order[0],rng.choice(order[2:])]
343
+ if name=='union_find':return [[[0,1],[1,2],[3,4]],rng.randrange(5),rng.randrange(5)]
344
+ if name=='interval_overlap':
345
+ return sorted(rng.sample(range(7),2))+sorted(rng.sample(range(7),2))
346
+ if name in ('bracket_balance','bracket_depth'):
347
+ depth=rng.randint(1,4 if split!='depth' else 7);seq=['open']*depth+['close']*depth
348
+ if name=='bracket_balance' and rng.random()<.5:seq=rng.choice([seq[:-1],['close']+seq])
349
+ return seq
350
+ if name in ('queue','deque'):
351
+ ops=[['right',rng.randrange(5)] for _ in range(n)]
352
+ if name=='deque':ops.insert(1,['left',rng.randrange(5)]);ops.append(['pop_right'])
353
+ return ops+[['pop_left'] for _ in range(rng.randint(1,n+1))]
354
+ if name in ('kv_lookup','role_filler','assoc_distractors','nested_lookup','mutable_dictionary','delayed_recall'):
355
+ keys=['a','b','c'];records=[[k,rng.randrange(5)] for k in keys];key=rng.choice(keys)
356
+ if name=='role_filler':records=[['agent',records[0][1]],['object',records[1][1]],['location',records[2][1]]];key=rng.choice(['agent','object','location'])
357
+ if name=='mutable_dictionary':records.append([key,rng.randrange(5)])
358
+ if name in ('assoc_distractors','delayed_recall'):records.extend([[f'noise{i}',rng.randrange(5)] for i in range(n)])
359
+ if name=='nested_lookup':
360
+ records=[['a','b'],['b','c'],['c',rng.randrange(5)]];return [records,['a',3]]
361
+ return [records,[key,1]]
362
+ if name in ('register_swap','register_update','multi_register','program_trace'):
363
+ ops=['swap'] if name=='register_swap' else ['set'] if name=='register_update' else ['set','copy','swap']
364
+ program=[[rng.choice(ops),rng.randrange(3),rng.randrange(3)] for _ in range(n)]
365
+ return [arr()[:3],program]
366
+ if name=='conditional_branch':return [bool(rng.randrange(2)),arr(),arr()]
367
+ if name=='loop_counter':return rng.randint(2,7)
368
+ if name in ('stack_expr','prefix_expr','postfix_expr'):
369
+ a,b,c=[rng.randint(0,2) for _ in range(3)];op=rng.choice(['add','mul'])
370
+ if name=='prefix_expr':return [op,'add',a,b,c]
371
+ if name=='postfix_expr':return [a,b,'add',c,op]
372
+ return ['push',a,'push',b,'add','push',c,op]
373
+ if name=='relative_coords':return [7,[rng.randrange(7),rng.randrange(7)],[rng.choice([[1,0],[-1,0],[0,1],[0,-1]]) for _ in range(n)]]
374
+ if name=='toroidal':return [5,rng.randrange(5),[rng.choice([-1,1]) for _ in range(n)]]
375
+ if name=='collision':return [0,1,rng.choice([2,3,4]),-1,rng.randint(2,3)]
376
+ if name=='falling':return [[rng.randrange(2) for _ in range(3)] for _ in range(4)]
377
+ if name=='obstacle_motion':return [7,2,[4],[rng.choice([-1,1]) for _ in range(n)]]
378
+ if name=='line_of_sight':return [0,6,rng.sample(range(8),rng.randrange(4))]
379
+ if name=='containment':return [sorted(rng.sample(range(8),2)),sorted(rng.sample(range(8),2))]
380
+ if name=='partial_map':
381
+ shuffled=edges[:];rng.shuffle(shuffled)
382
+ observations=[shuffled[::2],shuffled[1::2]]
383
+ # Neither observation alone is guaranteed to expose the full route.
384
+ return [observations,order[0],order[-1]]
385
+ if name=='dynamic_plan':
386
+ # A formerly shortest direct route disappears; another edge may appear.
387
+ start,goal=order[0],order[-1]
388
+ base=edges+[[start,goal]]
389
+ changes=[['remove',start,goal]]
390
+ if rng.random()<.5:changes.append(['add',order[1],goal])
391
+ return [base,changes,start,goal]
392
+ if name in ('path_keys','door_key'):
393
+ nodes=rng.sample(range(8),8);start,key,trap=nodes[:3]
394
+ path=[start]+nodes[3:3+rng.randint(2,4)]
395
+ road=[[a,b] for a,b in zip(path,path[1:])]
396
+ gate=road[rng.randrange(len(road))]
397
+ graph=road+[[start,key],[key,start],[start,trap]]
398
+ rng.shuffle(graph)
399
+ return [graph,start,path[-1],[key],[graph.index(gate)]]
400
+ if name=='irreversible_action':
401
+ nodes=rng.sample(range(7),7);start,trap=nodes[:2]
402
+ route=[start]+nodes[2:rng.randint(5,7)]
403
+ graph=[[a,b] for a,b in zip(route,route[1:])]+[[start,trap],[trap,trap]]
404
+ rng.shuffle(graph)
405
+ return [graph,start,route[-1],[],[]]
406
+ if name=='sparse_nav':
407
+ rng.shuffle(edges)
408
+ return [edges,order[0],rng.choice(order[2:]),[],[]]
409
+ if name=='causal_abduction':return [rng.randrange(2),rng.randrange(2),rng.randrange(2)]
410
+ if name=='anomaly_cause':
411
+ u,a,b=[rng.randrange(2) for _ in range(3)];vx=u^a;vy=vx^b
412
+ if rng.random()<.5:vx^=1;vy=vx^b
413
+ else:vy^=1
414
+ return [u,a,b,vx,vy]
415
+ if name in ('direct_cause','causal_intervention','confounding'):
416
+ iv=['none',0] if name in ('direct_cause','confounding') else [rng.choice(['x','y']),rng.randrange(2)]
417
+ return [rng.randrange(2),rng.randrange(2),rng.randrange(2),iv]
418
+ if name in ('competing_hypotheses','noisy_evidence','qual_bayes'):
419
+ prior=[1,1] if name=='competing_hypotheses' else [rng.randint(1,3),rng.randint(1,3)]
420
+ return [prior,[[3,1],[1,3]],[rng.randrange(2) for _ in range(n if name!='qual_bayes' else 1)]]
421
+ if name=='active_disambiguation':return [[rng.randrange(2) for _ in range(4)] for _ in range(3)]
422
+ if name in ('rule_change','rule_switch'):
423
+ maps=[rng.sample(range(5),5),rng.sample(range(5),5)]
424
+ return [maps,[['step',0],['switch',1],['step',0],['step',0]],rng.randrange(5)]
425
+ if name=='goal_switch':return [edges+[[5,0]],0,[rng.choice([3,4]),5,0]]
426
+ if name=='unknown_goal':
427
+ goal=rng.randrange(5);return [list(range(5)),[goal],[v for v in range(5) if v!=goal]]
428
+ if name=='tool_affordance':return [[rng.sample(range(5),rng.randint(1,5)) for _ in range(3)],rng.sample(range(5),2)]
429
+ if name=='select_cell':return [[[rng.randrange(3) for _ in range(3)] for _ in range(3)],rng.randrange(3)]
430
+ if name=='hidden_rule':
431
+ delta=rng.randrange(7);a=rng.randrange(7);return [list(range(7)),[[a,(a+delta)%7]],rng.randrange(7)]
432
+ if name=='explore_infer_exploit':
433
+ tables=[[[0,1],[1,0]],[[1,0],[0,1]]];world=rng.randrange(2);return [tables,[[0,0,tables[world][0][0]]],rng.randrange(2),1]
434
+ if name in ('sequence_composition','program_states','instruction_comp','modifier_comp','compositional_holdout','meta_rule'):
435
+ mapping=rng.sample(range(5),5);ops=['reverse','rotate','swap','map'];depth=rng.randint(1,3) if split!='depth' else rng.randint(4,6)
436
+ if split=='composition':program=['rotate','swap']
437
+ else:
438
+ while True:
439
+ program=[rng.choice(ops) for _ in range(depth)]
440
+ if all((a,b) not in [('rotate','swap'),('swap','rotate')] for a,b in zip(program,program[1:])):break
441
+ if name=='modifier_comp':program=['reverse']+program[:1]+['reverse']
442
+ if name=='meta_rule':return [arr(),program,dict(zip(ops,rng.sample(ops,len(ops)))),mapping]
443
+ return [arr(),program,mapping]
444
+ if name in ('novel_primitive','rule_remap'):return [rng.sample(range(7),7),rng.randrange(7)]
445
+ if name=='representation_remap':return [rng.sample(range(5),5),arr()]
446
+ if name=='cross_domain':return [rng.sample(range(5),5),rng.sample(range(5),5),arr()]
447
+ if name=='context_operator':return [arr(),arr()[:rng.randint(1,n)]]
448
+ if name=='mini_dsl':return ['concat',['reverse',['literal',arr()]],['repeat',2,['literal',arr()[:2]]]]
449
+ if name=='grammar_expansion':return [{0:[1,2],1:[0],2:[3]},arr()[:2],rng.randint(1,3)]
450
+ if name=='recursive_dsl':
451
+ depth=rng.randint(1,3) if split!='depth' else 4
452
+ return [{'a':['b','c'],'b':['a'],'c':['d']},rng.choice(['a','b']),depth]
453
+ if name=='stable_partition':return [arr(),rng.sample(range(5),2)]
454
+ if name=='scope_shadowing':return [[{'a':1,'b':2},{'a':rng.randrange(5)},{'b':rng.randrange(5)}],rng.choice(['a','b'])]
455
+ if name=='relational_join':return [[[rng.randrange(3),rng.randrange(5)] for _ in range(3)],[[rng.randrange(3),rng.randrange(5)] for _ in range(3)]]
456
+ if name=='edit_script':return [arr(),[['insert',1,rng.randrange(5)],['replace',0,rng.randrange(5)],['delete',2,0]]]
457
+ if name=='conditional_rewrite':return [arr()+[1,2],2,3,1]
458
+ if name=='span_reordering':return [arr(),1,n]
459
+ if name=='nested_substitution':return [[arr()[:2],[arr()[:2],rng.randrange(5)]],{0:1,1:2,2:3,3:4,4:0}]
460
+ raise KeyError(f'No sampler for {name}')
461
+
462
+
463
+ def answer(name,x):return solve(name,x)
464
+
465
+
466
+ def build_episode(c,name,rng,context,stage,split='train',difficulty=1):
467
+ cases=[]
468
+ if name in ('queue','deque'):
469
+ v=[['pop_left']];cases.append((v,answer(name,v)))
470
+ if name in ('sequence_composition','program_states','instruction_comp','modifier_comp','compositional_holdout'):
471
+ for op in ('reverse','rotate','swap','map'):
472
+ v=[[0,1,2,3,4],[op],[1,2,3,4,0]]
473
+ cases.append((v,answer(name,v)))
474
+ for _ in range(3):
475
+ x=sample_input(name,rng,difficulty,'train');cases.append((x,answer(name,x)))
476
+ x=sample_input(name,rng,difficulty,split)
477
+ # Avoid exact query replay from the demonstrations when the support permits it.
478
+ for _ in range(30):
479
+ if all(x!=a for a,_ in cases):break
480
+ x=sample_input(name,rng,difficulty,split)
481
+ query=(x,answer(name,x))
482
+ s=c.Surface(rng);start,close,sep,end,unit,neg=s.take(6);symbols={}
483
+ def symbol(v):
484
+ key=(type(v).__name__,v)
485
+ if key not in symbols:symbols[key]=s.one()
486
+ return symbols[key]
487
+ def encode(v):
488
+ if isinstance(v,dict):return [start]+sum((encode([k,z]) for k,z in v.items()),[])+[close]
489
+ if isinstance(v,(list,tuple)):return [start]+sum((encode(z) for z in v),[])+[close]
490
+ return [symbol(v)]
491
+ # Ground numeric order and magnitude once, not English number words.
492
+ def integers(v):
493
+ if type(v) is int:yield v
494
+ elif isinstance(v,dict):
495
+ for a,b in v.items():yield from integers(a);yield from integers(b)
496
+ elif isinstance(v,(list,tuple)):
497
+ for z in v:yield from integers(z)
498
+ values=set(integers([cases,query]));b=c.Builder(stage,name)
499
+ if not values.issubset(set(range(-8,17))):raise ValueError(f'Numeric domain overflow: {name}')
500
+ for n in range(-8,17):b.emit(([neg] if n<0 else [])+[unit]*abs(n)+[sep,symbol(n),end])
501
+ b.emit([symbol(False),sep,symbol(0),end,symbol(True),sep,symbol(1),end])
502
+ for a,y in cases:b.emit(encode(a)+[sep]+encode(y)+[end])
503
+ b.emit(encode(query[0])+[sep]);b.mark_prompt();b.emit(encode(query[1]),c.W_TARGET)
504
+ ep=b.finish(context);ep.audit={'mechanism':name,'cases':cases,'query':query,'symbols':symbols,'markers':[start,close,sep,end,unit,neg],'split':split}
505
+ return ep
506
+
507
+
508
+ def install(namespace):
509
+ # Replace each misleading v16 alias with an explicitly implemented mechanism.
510
+ for stage,names in [(23,MEMORY_NAMES),(25,WORLD_NAMES),(26,ALGORITHM_NAMES),(27,META_NAMES),(28,INTERACTIVE_NAMES),(29,NEW_NAMES)]:
511
+ funcs=[]
512
+ for name in names:
513
+ def generator(rng,context,st=stage,_name=name):
514
+ class Kernel:pass
515
+ c=Kernel()
516
+ for key in ['Surface','Builder','W_TARGET']:setattr(c,key,namespace[key])
517
+ return build_episode(c,_name,rng,context,st,difficulty=namespace['_g16_diff']())
518
+ generator.__name__='gen_'+name;namespace[generator.__name__]=generator;funcs.append(generator)
519
+ namespace['STAGES'][stage]=funcs
520
+ namespace['_V10_CONTEXT_TABLE'][stage]=(1536,1536,1536)
521
+ namespace['STAGE_NAMES'][29]='compositional_structures_and_pseudoword_transfer'
unified/ablations/unified-v1-blt-none-s42/source/data_stream.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pinned, repeatable shard streams. A single coordinator owns all cursors."""
2
+ import io,json,gzip,copy,hashlib,random,math
3
+ from pathlib import Path
4
+ from collections import Counter
5
+ import numpy as np
6
+ import httpx,requests,time
7
+ from huggingface_hub import HfFileSystem,get_token
8
+ from parquet_stream import ParquetStream
9
+ from records import record,plain_chunks,validate,Oversize,InvalidRecord,PAD
10
+ from media_data import visual_records,audio_records
11
+ from cortex_source import CortexSource
12
+ from dataset_registry import SOURCES,CORTEX_WEIGHT,retry
13
+
14
+ HOLDOUT=2
15
+
16
+ def row_key(spec,row):
17
+ # Same source media bytes map to the same key across repositories and captions.
18
+ h=hashlib.sha256()
19
+ media=row.get('images', [row.get('image')]) if spec['kind']=='image' else [row.get('audio')] if spec['kind']=='audio' else []
20
+ for v in media:
21
+ if isinstance(v,dict) and v.get('bytes') is not None:h.update(v['bytes'])
22
+ else:h.update(repr(v).encode())
23
+ if not media:h.update(json.dumps(row,sort_keys=True,ensure_ascii=False,default=str).encode())
24
+ return h.hexdigest()
25
+
26
+ class JSONRows:
27
+ """Compressed JSONL resume replays only the current pinned shard, never the corpus."""
28
+ def __init__(self,spec,filename,state=None,opener=None):
29
+ self.spec,self.filename=spec,filename;self.row=int((state or {}).get('row',0));self.opener=opener
30
+ def state_dict(self):return {'format':'jsonl-row-v1','row':self.row}
31
+ def __iter__(self):
32
+ handle=self.opener() if self.opener else HfFileSystem(token=get_token()).open(f'datasets/{self.spec["repo"]}@{self.spec["revision"]}/{self.filename}','rb',block_size=256*1024,cache_type='readahead')
33
+ with handle:
34
+ reader=handle
35
+ if self.filename.endswith('.zst'):
36
+ import zstandard
37
+ reader=zstandard.ZstdDecompressor().stream_reader(handle)
38
+ elif self.filename.endswith('.gz'):reader=gzip.GzipFile(fileobj=handle)
39
+ with io.TextIOWrapper(reader,encoding='utf-8') as lines:
40
+ for i,line in enumerate(lines):
41
+ if i<self.row:continue
42
+ if not line.strip():self.row=i+1;continue
43
+ data=json.loads(line);self.row=i+1;yield data
44
+
45
+ class Source:
46
+ def __init__(self,name,spec,state=None,limit=1024,loader=None):
47
+ self.name,self.spec,self.limit,self.loader=name,spec,limit,loader
48
+ self.s=copy.deepcopy(state) if state else dict(shard=0,epoch=0,cursor=None,rows=0,pass_rows=0,pending=[],held=[],held_keys=[],initialized=False,rejected=0,accepted=0)
49
+ self.reader=self.it=None
50
+ def close(self):
51
+ if self.it is not None and hasattr(self.it,'close'):self.it.close()
52
+ self.it=self.reader=None
53
+ def open(self):
54
+ self.close();f=self.spec['files'][self.s['shard']]
55
+ if self.loader:self.reader=self.loader(f,self.s['cursor'])
56
+ elif f.endswith('.parquet'):
57
+ self.reader=ParquetStream(self.spec['repo'],f,self.spec['revision'],list(self.spec['columns']),batch_size=1 if self.spec['kind'] in ('image','audio') else 64)
58
+ if self.s['cursor']:self.reader.load_state_dict(self.s['cursor'])
59
+ else:self.reader=JSONRows(self.spec,f,self.s['cursor'])
60
+ self.it=iter(self.reader)
61
+ def raw(self):
62
+ attempts=0
63
+ while True:
64
+ if self.it is None:retry(self.open)
65
+ try:
66
+ row=next(self.it);self.s['cursor']=copy.deepcopy(self.reader.state_dict());self.s['rows']+=1;self.s['pass_rows']+=1;return row
67
+ except StopIteration:
68
+ self.close();self.s['cursor']=None;self.s['shard']+=1
69
+ if self.s['shard']==len(self.spec['files']):
70
+ if not self.s['pass_rows']:raise RuntimeError(self.name+': empty dataset')
71
+ self.s['shard']=0;self.s['epoch']+=1;self.s['pass_rows']=0
72
+ except (OSError,TimeoutError,ConnectionError,httpx.HTTPError,requests.RequestException) as e:
73
+ # Reopen at the last committed row, never move past a failed fetch.
74
+ attempts+=1
75
+ if attempts>=5:raise RuntimeError(self.name+': stream retry limit reached; resume from the durable checkpoint') from e
76
+ print(self.name+': retrying current shard at the saved row cursor',flush=True)
77
+ time.sleep(min(16,2**attempts));self.close()
78
+ def transform(self,row,key):
79
+ kind=self.spec['kind']
80
+ if kind=='image':return visual_records(self.name,row,self.limit)
81
+ if kind=='audio':return audio_records(self.name,row,self.limit,self.s['epoch'],key)
82
+ if kind in ('rewrite','openhermes'):
83
+ instruction=row.get('instruction');text=row.get('text') if kind=='rewrite' else row.get('input')
84
+ if kind=='rewrite' or (isinstance(text,str) and text.strip()):
85
+ output=row.get('output')
86
+ if not all(isinstance(v,str) for v in [instruction,text,output]):raise InvalidRecord(self.name+': expected instruction/text/output strings')
87
+ if kind=='rewrite':
88
+ # rewrite-plus format: no labels, exactly one blank line between fields.
89
+ # Train causally on every byte so rewrite6 behaves like corpus text.
90
+ return plain_chunks(f'{instruction}\n\n{text}\n\n{output}',self.name,self.limit)
91
+ return [record(f'Instruction:\n{instruction}\n\nText:\n{text}\n\nRewrite:\n',output,self.name,self.limit,supervise_all=True)]
92
+ text=row.get(self.spec['column'])
93
+ if not isinstance(text,str):raise InvalidRecord(self.name+': missing text column '+self.spec['column'])
94
+ return plain_chunks(text,self.name,self.limit)
95
+ def initialize(self):
96
+ if self.s['initialized']:return
97
+ for _ in range(128):
98
+ row=self.raw();key=row_key(self.spec,row)
99
+ try:records=self.transform(row,key)
100
+ except (Oversize,InvalidRecord,ValueError):self.s['rejected']+=1;continue
101
+ self.s['held_keys'].append(key)
102
+ # One complete record per held-out source row; fixed across restarts.
103
+ self.s['held'].append(records[0])
104
+ if self.spec['kind']=='audio':
105
+ self.s['held'].extend(r for r in records[1:] if r['meta'].get('task')=='generation')
106
+ if len(self.s['held_keys'])==HOLDOUT:break
107
+ if len(self.s['held_keys'])<HOLDOUT:raise RuntimeError(self.name+': unable to construct validation records')
108
+ self.s['initialized']=True
109
+ def next_record(self,excluded):
110
+ if self.s['pending']:return self.s['pending'].pop(0)
111
+ for _ in range(256):
112
+ row=self.raw();key=row_key(self.spec,row)
113
+ if key in excluded:continue
114
+ try:rs=self.transform(row,key)
115
+ except Oversize:self.s['rejected']+=1;continue
116
+ except (InvalidRecord,ValueError) as e:
117
+ self.s['rejected']+=1
118
+ if self.s['rejected']<=3:print(f'{self.name}: rejected {type(e).__name__}: {e}',flush=True)
119
+ continue
120
+ self.s['accepted']+=1;self.s['pending']=rs[1:];return validate(rs[0],self.limit)
121
+ raise RuntimeError(self.name+': 256 consecutive unusable/held-out rows; fix the schema or byte budget')
122
+ def state_dict(self):return copy.deepcopy(self.s)
123
+
124
+ class Mixer:
125
+ """Source weights choose blocks, not rows. Complete structured records never split.
126
+
127
+ Text chunks carry the boundary byte. Packing is ordinary causal document packing
128
+ with BOS/EOS boundaries; there is no quadratic block-diagonal attention mask.
129
+ """
130
+ def __init__(self,manifest,limit=1024,state=None,loaders=None,cortex=True,mix_weights=None):
131
+ state=state or {};self.limit=limit;self.manifest=manifest
132
+ self.sources={n:Source(n,spec,state.get('sources',{}).get(n),limit,(loaders or {}).get(n)) for n,spec in manifest.items()}
133
+ self.cortex=CortexSource(state.get('cortex'),limit) if cortex else None
134
+ self.rng=random.Random(20260911)
135
+ if 'rng' in state:self.rng.setstate(state['rng'])
136
+ defaults={n:int(spec['weight']) for n,spec in manifest.items()}
137
+ if self.cortex:defaults['cortex']=int(CORTEX_WEIGHT)
138
+ requested=dict(state.get('mix_weights',{}));requested.update(mix_weights or {})
139
+ self.mix_weights={n:int(requested.get(n,defaults[n])) for n in defaults}
140
+ if any(v<=0 for v in self.mix_weights.values()):raise ValueError('All active mix weights must be positive integers')
141
+ self.mix_queue=list(state.get('mix_queue',[]))
142
+ if any(n not in self.mix_weights for n in self.mix_queue):self.mix_queue=[]
143
+ self.pending=copy.deepcopy(state.get('pending',{}));self.stats=Counter(state.get('stats',{}))
144
+ self.excluded=set(state.get('excluded',[]))
145
+ def initialize(self):
146
+ for s in self.sources.values():
147
+ s.initialize();self.excluded.update(s.s['held_keys'])
148
+ # Validate one real training record per source before GPU allocation.
149
+ for name,s in self.sources.items():
150
+ if name not in self.pending:self.pending[name]=s.next_record(self.excluded)
151
+ return self
152
+ def next_for(self,name):
153
+ if name in self.pending:return self.pending.pop(name)
154
+ return self.cortex.record() if name=='cortex' else self.sources[name].next_record(self.excluded)
155
+ def _refill_mix_queue(self):
156
+ # Low-discrepancy weighted window: exact long-run quotas, randomized phase,
157
+ # and roughly even spacing. This keeps small rewrite6/Cortex shares present
158
+ # across the entire run without random multi-thousand-block droughts.
159
+ slots=[]
160
+ for name,weight in self.mix_weights.items():
161
+ phase=self.rng.random()
162
+ for k in range(weight):
163
+ position=(k+phase)/weight
164
+ jitter=(self.rng.random()-.5)*(.30/weight)
165
+ slots.append((position+jitter,self.rng.random(),name))
166
+ slots.sort();self.mix_queue=[name for _,__,name in slots]
167
+ self.stats['mix/windows']+=1
168
+ def block(self):
169
+ if not self.mix_queue:self._refill_mix_queue()
170
+ name=self.mix_queue.pop(0);tokens=[];mask=[]
171
+ while True:
172
+ r=self.next_for(name);validate(r,self.limit)
173
+ if tokens and len(tokens)+len(r['ids'])>self.limit+1:self.pending[name]=r;break
174
+ tokens.extend(r['ids']);mask.extend(r['weights'])
175
+ if len(tokens)==self.limit+1 or len(tokens)>=int(self.limit*.90):break
176
+ # Large language chunks are already context-sized; small rows pack.
177
+ used=len(tokens);n=self.limit+1-used
178
+ tokens.extend([PAD]*n);mask.extend([0]*n)
179
+ self.stats[name+'/blocks']+=1;self.stats[name+'/targets']+=sum(mask[1:]);self.stats[name+'/tokens']+=used-1
180
+ return tokens,mask,name,used
181
+ def batch(self,count):
182
+ blocks=[self.block() for _ in range(count)]
183
+ # All ranks receive the same trimmed width, bounded by the full 1024 limit.
184
+ width=min(self.limit+1,1+math.ceil(max(b[3]-1 for b in blocks)/128)*128)
185
+ return {'ids':np.array([b[0][:width] for b in blocks],dtype=np.int64),'weights':np.array([b[1][:width] for b in blocks],dtype=np.float32),'sources':[b[2] for b in blocks]}
186
+ def validation(self):
187
+ return [r for s in self.sources.values() for r in s.s['held']]+(self.cortex.validation() if self.cortex else [])
188
+ def state_dict(self):
189
+ return dict(sources={n:s.state_dict() for n,s in self.sources.items()},cortex=self.cortex.state_dict() if self.cortex else None,
190
+ rng=self.rng.getstate(),mix_weights=dict(self.mix_weights),mix_queue=list(self.mix_queue),
191
+ pending=copy.deepcopy(self.pending),stats=dict(self.stats),excluded=sorted(self.excluded))
192
+ def close(self):
193
+ for s in self.sources.values():s.close()
unified/ablations/unified-v1-blt-none-s42/source/dataset_registry.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Enabled language-training sources for the current SparkBET run."""
2
+ from dataclasses import dataclass,asdict
3
+ import fnmatch,time
4
+ from huggingface_hub import HfApi
5
+
6
+ @dataclass(frozen=True)
7
+ class Spec:
8
+ repo:str
9
+ patterns:tuple
10
+ columns:tuple
11
+ weight:float
12
+ kind:str='text'
13
+ column:str='text'
14
+
15
+ # Keep the full project data framework while the current run uses language-only
16
+ # repositories. Non-language dataset entries are intentionally inactive.
17
+ SOURCES={
18
+ 'climbmix':Spec('karpathy/climbmix-400b-shuffle',('*.parquet','**/*.parquet'),('text',),50,column='text'),
19
+ 'rewrite6':Spec('appvoid/rewrite6',('**/rewrite.jsonl','rewrite.jsonl','**/*.jsonl'),('instruction','text','output'),10,'rewrite'),
20
+ 'ultra_style':Spec('openbmb/Ultra-FineWeb-L3',('data/ultrafineweb_en_l3/multi_style/*.parquet',),('content',),20,column='content'),
21
+ 'ultra_qa':Spec('openbmb/Ultra-FineWeb-L3',('data/ultrafineweb_en_l3/qa/*.parquet',),('content',),20,column='content'),
22
+ }
23
+ CORTEX_WEIGHT=15
24
+
25
+
26
+ def retry(fn,attempts=5):
27
+ for i in range(attempts):
28
+ try:return fn()
29
+ except Exception as e:
30
+ status=getattr(getattr(e,'response',None),'status_code',None)
31
+ if status in (401,403,404):raise
32
+ if i==attempts-1:raise
33
+ print(f'Network retry {i+1}/{attempts}: {type(e).__name__}',flush=True);time.sleep(min(16,2**i))
34
+
35
+
36
+ def make_manifest(overrides=None,disabled=()):
37
+ api=HfApi();cache={};manifest={};overrides=overrides or {}
38
+ for name,spec in SOURCES.items():
39
+ if name in disabled:continue
40
+ if spec.repo not in cache:cache[spec.repo]=retry(lambda:api.dataset_info(spec.repo))
41
+ info=cache[spec.repo];patterns=overrides.get(name,spec.patterns)
42
+ files=sorted({f.rfilename for f in info.siblings if any(fnmatch.fnmatch(f.rfilename,p) for p in patterns)})
43
+ if not files:raise RuntimeError(f'{name}: no training files match {patterns} in {spec.repo}. Set DATA_FILE_OVERRIDES after inspecting the repository.')
44
+ manifest[name]={**asdict(spec),'revision':info.sha,'files':files}
45
+ print(f'{name}: pinned {len(files)} shards at {info.sha[:12]}',flush=True)
46
+ return manifest
unified/ablations/unified-v1-blt-none-s42/source/inference.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Small text-generation helper for an exported SparkBET repository."""
2
+ from pathlib import Path
3
+ import torch
4
+ from safetensors.torch import load_file
5
+ from bet_model import SparkBET,BETConfig,uniform_steps
6
+
7
+
8
+ class Cortex:
9
+ def __init__(self,model,device=None):
10
+ self.model=model
11
+ self.device=torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
12
+ self.model.to(self.device).eval()
13
+
14
+ @classmethod
15
+ def from_export(cls,folder,device=None):
16
+ folder=Path(folder);model=SparkBET(BETConfig())
17
+ state=load_file(str(folder/"model.safetensors"),device="cpu")
18
+ if state and all(k.startswith("core.") for k in state):state={k[5:]:v for k,v in state.items()}
19
+ model.load_state_dict(state,strict=True)
20
+ return cls(model,device)
21
+
22
+ def generate_ids(self,ids,max_new_tokens=128,loops=8,temperature=0.0,top_k=None):
23
+ out=list(map(int,ids))
24
+ for _ in range(int(max_new_tokens)):
25
+ current=out[-self.model.c.max_seq_len:]
26
+ x=torch.tensor([current],device=self.device,dtype=torch.long)
27
+ with torch.inference_mode(),torch.autocast(self.device.type,dtype=torch.float16,enabled=self.device.type=="cuda"):
28
+ logits=self.model(x,uniform_steps(loops))[0,-1].float()
29
+ if temperature and temperature>0:
30
+ logits=logits/float(temperature)
31
+ if top_k:
32
+ values,_=torch.topk(logits,min(int(top_k),logits.numel()));logits[logits<values[-1]]=-float("inf")
33
+ nxt=int(torch.multinomial(torch.softmax(logits,-1),1))
34
+ else:nxt=int(logits.argmax())
35
+ out.append(nxt)
36
+ if nxt==258:break
37
+ return out
38
+
39
+ def generate(self,text,max_new_tokens=128,loops=8,temperature=0.0,top_k=None):
40
+ ids=[257]+list(text.encode("utf-8"))
41
+ out=self.generate_ids(ids,max_new_tokens,loops,temperature,top_k)
42
+ body=bytes(i for i in out[1:] if 0<=i<=255)
43
+ return body.decode("utf-8",errors="replace")
unified/ablations/unified-v1-blt-none-s42/source/media_data.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Complete caption/chat and audio records with strict byte budgets."""
2
+ import io,json,hashlib,math
3
+ from PIL import Image,ImageOps
4
+ from records import record,Oversize,InvalidRecord
5
+ from braille_encoder import encode_image,fit_box_to_square,local_contrast,floyd_steinberg,binary_to_braille
6
+ from audio_codec import canonical_audio,overview,encode_tile,CHUNK,RATE
7
+
8
+ def load_image(value):
9
+ if isinstance(value,Image.Image):im=value.copy()
10
+ elif isinstance(value,dict):
11
+ src=io.BytesIO(value['bytes']) if value.get('bytes') is not None else value.get('path')
12
+ if src is None:raise InvalidRecord('Image has no embedded data or local path')
13
+ with Image.open(src) as source:im=source.copy()
14
+ else:
15
+ with Image.open(value) as source:im=source.copy()
16
+ im=ImageOps.exif_transpose(im).convert('RGBA');im.thumbnail((1024,1024))
17
+ return im
18
+
19
+ def image_text(value,grid=80):
20
+ if grid>80 or grid<8 or grid%4:raise ValueError('Grid must be a multiple of four, at most 80')
21
+ im=load_image(value)
22
+ # Full-frame fit uses the supplied contrast/dither/dot layout. Saliency crops
23
+ # can discard the object asked about, so dataset examples preserve the frame.
24
+ small=fit_box_to_square(im,(0,0,*im.size),grid)
25
+ gray,alpha=local_contrast(small)
26
+ return '<image>\n'+binary_to_braille(floyd_steinberg(gray,alpha),alpha)+'\n</image>'
27
+
28
+ def content_text(content):
29
+ if isinstance(content,str):return content.replace('<image>','').strip()
30
+ if not isinstance(content,list):raise InvalidRecord('Unknown chat content')
31
+ return '\n'.join(p['text'] for p in content if p.get('type')=='text' and p.get('text')).strip()
32
+
33
+ def visual_records(name,row,limit=8192):
34
+ pictures=row['images'] if name=='llava' else [row['image']]
35
+ if not pictures:raise InvalidRecord('No image')
36
+ # Prefer full 80x80 resolution; reduce only to fit multiple complete images.
37
+ for grid in [80,64,48,32,16,8]:
38
+ image='\n'.join(image_text(v,grid) for v in pictures)
39
+ if len(image.encode())<limit-1024:break
40
+ else:raise Oversize('Too many images')
41
+ out=[]
42
+ def add(prefix,answer):
43
+ if not isinstance(answer,str) or not answer.strip():raise InvalidRecord('Empty visual answer')
44
+ try:out.append(record(prefix,answer,name,limit,meta={'grid':grid}))
45
+ except Oversize:pass # Whole target rejected; no fabricated answer fragments.
46
+ if name=='flickr8k':
47
+ for i in range(5):add(image+'\n\nCaption:\n',row[f'caption_{i}'])
48
+ elif name=='coco_captions':
49
+ values=row['caption'] if isinstance(row['caption'],list) else [row['caption']]
50
+ for caption in values:add(image+'\n\nCaption:\n',caption)
51
+ elif name=='chartqa':
52
+ values=row['label'] if isinstance(row['label'],list) else [row['label']]
53
+ for answer in values:add(image+'\n\nUser:\n'+row['query']+'\n\nAssistant:\n',answer)
54
+ else:
55
+ messages=row['messages'] if name=='llava' else [{'role':{'human':'user','gpt':'assistant'}[m['from']],'content':m['value']} for m in row['conversations']]
56
+ history=image;last=None
57
+ for m in messages:
58
+ role=m['role'];text=content_text(m['content'])
59
+ if role not in ('user','assistant','system'):raise InvalidRecord('Unknown role')
60
+ if role=='assistant':
61
+ if last!='user':raise InvalidRecord('Assistant without user context')
62
+ prefix=history+'\n\nAssistant:\n';add(prefix,text)
63
+ # Preserve full history for follow-ups; never reset to question only.
64
+ history+='\n\n'+role.title()+':\n'+text;last=role
65
+ if not out:raise Oversize('No complete visual turn fits the context')
66
+ return out
67
+
68
+ def audio_records(name,row,limit=8192,epoch=0,identity=''):
69
+ wave=canonical_audio(row['audio']);caption=row['text'] if name=='librispeech' else row['caption']
70
+ if not isinstance(caption,str) or not caption.strip():raise InvalidRecord('Missing audio text')
71
+ if len(wave)>RATE*30:raise Oversize('Whole-clip overview restricted to 30 seconds; no partial audio/full transcript pairs')
72
+ length=len(wave)/RATE;out=[]
73
+ kind='Transcript' if name=='librispeech' else 'Caption'
74
+ prefix=f'<audio_overview sr={RATE} seconds={length:.6f} freq_low=bottom time=right>\n'+overview(wave)+'\n</audio_overview>\n\n'+kind+':\n'
75
+ try:out.append(record(prefix,caption,name,limit,meta={'task':'understanding','seconds':length}))
76
+ except Oversize:pass
77
+ total=math.ceil(len(wave)/CHUNK)
78
+ # Each source-row pass advances its selected tile. Every tile is eventually
79
+ # visited without storing thousands of expanded samples in checkpoint state.
80
+ offset=int.from_bytes(hashlib.blake2b(identity.encode(),digest_size=8).digest(),'big')
81
+ tile=(offset+epoch)%total
82
+ target=encode_tile(wave[tile*CHUNK:(tile+1)*CHUNK])
83
+ previous=encode_tile(wave[(tile-1)*CHUNK:tile*CHUNK]) if tile else '<audio_start>'
84
+ prefix=f'{kind}:\n{caption}\n\nGenerate audio tile {tile+1}/{total}; start_sample={tile*CHUNK}; sample_rate={RATE}.\nPrevious:\n{previous}\nNext:\n'
85
+ try:out.append(record(prefix,target,name,limit,meta={'task':'generation','tile':tile,'tiles':total}))
86
+ except Oversize:pass
87
+ if not out:raise Oversize('No complete audio record fits')
88
+ return out
unified/ablations/unified-v1-blt-none-s42/source/modeling_bet.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn.functional as F
4
+ from transformers import PreTrainedModel, GenerationMixin
5
+ from transformers.modeling_outputs import CausalLMOutput
6
+
7
+ from .configuration_bet import BETConfig
8
+ from .bet_model import BETConfig as CoreConfig, SparkBET, uniform_steps
9
+
10
+
11
+ class BETPreTrainedModel(PreTrainedModel):
12
+ config_class=BETConfig
13
+ base_model_prefix="core"
14
+ supports_gradient_checkpointing=False
15
+ _no_split_modules=["PlainBlock","LoopedBlock"]
16
+
17
+
18
+ class BETForCausalLM(BETPreTrainedModel,GenerationMixin):
19
+ def __init__(self,config):
20
+ super().__init__(config)
21
+ core_cfg=CoreConfig(
22
+ vocab_size=config.vocab_size,
23
+ hidden_size=config.hidden_size,
24
+ intermediate_size=config.intermediate_size,
25
+ prelude_layers=config.prelude_layers,
26
+ body_blocks=config.body_blocks,
27
+ coda_layers=config.coda_layers,
28
+ num_heads=config.num_attention_heads,
29
+ num_kv_heads=config.num_key_value_heads,
30
+ head_dim=config.head_dim,
31
+ lora_rank=config.lora_rank,
32
+ hyper_lanes=config.hyper_lanes,
33
+ max_seq_len=config.max_position_embeddings,
34
+ max_loops=config.max_loops,
35
+ rope_theta=config.rope_theta,
36
+ rms_eps=config.rms_norm_eps,
37
+ ddl_beta_init=config.ddl_beta_init,
38
+ ddl_k_eps=config.ddl_k_eps,
39
+ ddl_v_sigmoid_scale=config.ddl_v_sigmoid_scale,
40
+ )
41
+ self.core=SparkBET(core_cfg)
42
+
43
+ def get_input_embeddings(self):return self.core.embed
44
+ def set_input_embeddings(self,value):self.core.embed=value
45
+ def get_output_embeddings(self):return None
46
+ def set_output_embeddings(self,value):
47
+ if value is not None:raise ValueError("SparkBET uses tied input/output embeddings")
48
+
49
+ def _cycles(self,cycles=None):
50
+ if cycles is None:
51
+ cycles=int(os.environ.get("BET_EVAL_CYCLES",self.config.refinement_cycles))
52
+ cycles=int(cycles)
53
+ if not 1<=cycles<=self.config.max_loops:
54
+ raise ValueError(f"refinement cycles must be in [1,{self.config.max_loops}]")
55
+ return cycles
56
+
57
+ def forward(
58
+ self,input_ids=None,attention_mask=None,labels=None,cycles=None,
59
+ past_key_values=None,use_cache=None,return_dict=True,**kwargs,
60
+ ):
61
+ if input_ids is None:raise ValueError("input_ids is required")
62
+ if past_key_values is not None:raise ValueError("SparkBET does not implement a KV cache")
63
+ logits=self.core(input_ids,uniform_steps(self._cycles(cycles)),attention_mask=attention_mask)
64
+ loss=None
65
+ if labels is not None:
66
+ shift_logits=logits[:,:-1].contiguous().float();shift_labels=labels[:,1:].contiguous()
67
+ loss=F.cross_entropy(shift_logits.view(-1,shift_logits.size(-1)),shift_labels.view(-1),ignore_index=-100)
68
+ if not return_dict:return tuple(v for v in (loss,logits) if v is not None)
69
+ return CausalLMOutput(loss=loss,logits=logits)
70
+
71
+ def prepare_inputs_for_generation(self,input_ids,attention_mask=None,**kwargs):
72
+ max_len=self.config.max_position_embeddings
73
+ if input_ids.shape[1]>max_len:
74
+ input_ids=input_ids[:,-max_len:]
75
+ if attention_mask is not None:attention_mask=attention_mask[:,-max_len:]
76
+ return {"input_ids":input_ids,"attention_mask":attention_mask,"use_cache":False}
unified/ablations/unified-v1-blt-none-s42/source/parquet_stream.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Synchronous Parquet row-group streaming with an explicit resumable row cursor.
2
+
3
+ Avoids background Arrow scanner callbacks into Python file objects during shutdown.
4
+ Only selected columns and the current batch are materialized; no local corpus cache.
5
+ """
6
+ import copy
7
+ import pyarrow.parquet as pq
8
+ from huggingface_hub import HfFileSystem,get_token
9
+ class ParquetStream:
10
+ def __init__(self,repo,filename,revision,columns,batch_size=128,opener=None):
11
+ self.columns,self.batch_size=columns,batch_size
12
+ self.cursor={'format':'parquet-row-v1','row_group':0,'row':0}
13
+ self.opener=opener or (lambda:HfFileSystem(token=get_token()).open(
14
+ f'datasets/{repo}@{revision}/{filename}','rb',block_size=65536,cache_type='bytes'))
15
+ def state_dict(self):return copy.deepcopy(self.cursor)
16
+ def load_state_dict(self,state):
17
+ if state.get('format')!='parquet-row-v1':
18
+ raise ValueError('Unsupported Parquet cursor; preserve the previous trainer for older runs')
19
+ self.cursor=copy.deepcopy(state)
20
+ def __iter__(self):
21
+ with self.opener() as handle:
22
+ reader=pq.ParquetFile(handle,pre_buffer=False,buffer_size=65536)
23
+ try:
24
+ for group in range(self.cursor['row_group'],reader.num_row_groups):
25
+ skip=self.cursor['row'] if group==self.cursor['row_group'] else 0
26
+ position=0
27
+ for batch in reader.iter_batches(batch_size=self.batch_size,row_groups=[group],
28
+ columns=self.columns,use_threads=False):
29
+ if position+batch.num_rows<=skip:
30
+ position+=batch.num_rows;continue
31
+ for row in batch.to_pylist():
32
+ position+=1
33
+ if position<=skip:continue
34
+ self.cursor={'format':'parquet-row-v1','row_group':group,'row':position}
35
+ yield row
36
+ self.cursor={'format':'parquet-row-v1','row_group':group+1,'row':0}
37
+ finally:reader.close()
unified/ablations/unified-v1-blt-none-s42/source/prepared_data.py ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """rewrite7: immutable prepared records, epoch reuse and bounded disk prefetch.
2
+
3
+ Preparation and observation are distinct. Only a successful optimizer update may
4
+ advance the consumer cursor or enter the observation ledger. Dataset uploads
5
+ precede consumption; the model checkpoint is the durable recovery authority.
6
+ """
7
+ from collections import OrderedDict
8
+ import bisect,copy,hashlib,json,math,os,queue,threading,time,traceback
9
+ from pathlib import Path
10
+ import numpy as np
11
+ import pyarrow as pa
12
+ import pyarrow.parquet as pq
13
+ import torch
14
+ from huggingface_hub import HfApi,hf_hub_download,CommitOperationAdd
15
+ from checkpoints import atomic_json,digest
16
+ from dataset_registry import retry
17
+ from unified_data import CoverageSource,CoverageMixer,CodecCortex
18
+ from cortex_source import CortexSource
19
+ from records import PAD,validate
20
+
21
+ SCHEMA=1
22
+
23
+ def sha(value):return hashlib.sha256(json.dumps(value,sort_keys=True,default=str).encode()).hexdigest()
24
+
25
+ def recipe_hash():
26
+ root=Path(__file__).parent;h=hashlib.sha256()
27
+ for name in ('prepared_data.py','unified_data.py','records.py','cortex_source.py','cortex_data.py','cortex_tasks.py','cortex_curriculum_v16.py','canvas_flow.py'):
28
+ h.update(name.encode());h.update((root/name).read_bytes())
29
+ return h.hexdigest()
30
+
31
+ def cursor_key(cursor):
32
+ if cursor is None:return (-1,-1)
33
+ if 'row_group' in cursor:return (int(cursor['row_group']),int(cursor['row']))
34
+ return (0,int(cursor.get('row',cursor.get('i',0))))
35
+
36
+ class EndFile(Exception):pass
37
+
38
+ class OneFile(CoverageSource):
39
+ def raw(self):
40
+ if self.it is None:retry(self.open)
41
+ try:row=next(self.it)
42
+ except StopIteration:self.close();raise EndFile
43
+ self.s['cursor']=copy.deepcopy(self.reader.state_dict());self.s['rows']+=1;self.s['pass_rows']+=1
44
+ return row
45
+
46
+ def _worker_init():
47
+ torch.set_num_threads(1)
48
+
49
+ def _cortex_record(args):
50
+ kind,index,limit,codec=args
51
+ if kind=='kernel':return CortexSource({'index':index},limit).record()
52
+ return CodecCortex({'visual_index':index,'until_visual':0},limit,codec).record()
53
+
54
+ class Generated:
55
+ def __init__(self,kind,start,count,limit,codec,state=None):
56
+ self.kind,self.start,self.count,self.limit,self.codec=kind,start,count,limit,codec
57
+ self.s=copy.deepcopy(state) if state else dict(index=0,cursor=None,rows=0,pending_document=None)
58
+ def initialize(self):pass
59
+ def next_record(self):
60
+ i=self.s['index']
61
+ if i>=self.count:raise EndFile
62
+ if self.kind=='kernel':r=CortexSource({'index':self.start+i},self.limit).record()
63
+ else:r=CodecCortex({'visual_index':self.start+i,'until_visual':0},self.limit,self.codec).record()
64
+ self.s.update(index=i+1,rows=i+1,cursor={'row':i+1})
65
+ return r
66
+ def records(self,count,pool=None):
67
+ n=min(count,self.count-self.s['index'])
68
+ if pool is None:
69
+ for _ in range(n):yield self.next_record()
70
+ else:
71
+ start=self.s['index'];args=[(self.kind,self.start+i,self.limit,self.codec) for i in range(start,start+n)]
72
+ for r in pool.map(_cortex_record,args,chunksize=8):
73
+ i=self.s['index']+1;self.s.update(index=i,rows=i,cursor={'row':i});yield r
74
+ def state_dict(self):return copy.deepcopy(self.s)
75
+ def close(self):pass
76
+
77
+ class Archive:
78
+ def __init__(self,cfg,api=None):
79
+ self.cfg=cfg;self.repo=cfg.get('prepared_repo','appvoid/rewrite7')
80
+ self.root=Path(cfg['work_dir'])/'rewrite7_cache';self.root.mkdir(parents=True,exist_ok=True)
81
+ self.online=bool(cfg.get('upload',False));self.read_remote=bool(cfg.get('hub_resume',False))
82
+ self.api=api or HfApi();self.head=None;self.remote=set();self.lock=threading.RLock();self.partitions={};self.tables=OrderedDict();self.pool=None
83
+ self.recipe=recipe_hash();self.prepared_records=0
84
+ if self.online:retry(lambda:self.api.create_repo(self.repo,repo_type='dataset',private=True,exist_ok=True))
85
+ if self.online or self.read_remote:
86
+ info=retry(lambda:self.api.dataset_info(self.repo));self.head=info.sha;self.remote={s.rfilename for s in info.siblings}
87
+ card=self.root/'README.md'
88
+ card.write_text('''---
89
+ pretty_name: rewrite7 prepared Cortex mixture
90
+ configs:
91
+ - config_name: default
92
+ data_files:
93
+ - split: train
94
+ path: data/**/*.parquet
95
+ ---
96
+ # rewrite7
97
+
98
+ Canonical prepared UTF-8 byte records from ClimbMix, Ultra-FineWeb-L3 **English QA/multi-style only**, rewrite6, and the deterministic Cortex generator. Original repository revisions, source filenames, row cursors, processing recipe and sample IDs are preserved. Upstream licenses and provenance remain applicable; this does not assign a new license to upstream content.
99
+
100
+ `data/<source>/<partition>/part-*.parquet` stores each source position once per processing recipe, **not once per epoch or training run**. Index manifests store the independent preparation cursor. `observed/<run>/<lineage>/...json` logs successful sample presentations and identifies the corresponding model checkpoint SHA. Repeated epochs reuse IDs and files. Prepared rows include a lookahead buffer; they must not all be counted as observed. Model checkpoints remain authoritative for durable recovery because the model and dataset repositories cannot commit atomically together.
101
+
102
+ Record fields: sample_id, source, ids (uint16), weights (uint8), prompt_len, metadata JSON, original cursor JSON, row ordinal, and last_chunk. The model's committed source/shard/record/epoch cursors provide the exact next training position. Canvas layout/image tensors are prepared in local dispatch shards; their deterministic recipe/seed/update are recorded in the observation ledger rather than duplicating corpus records for every view or epoch.
103
+ ''')
104
+ if self.online and 'README.md' not in self.remote:self.publish({'README.md':card},'Initialize rewrite7 prepared-data schema')
105
+ def fetch(self,relative):
106
+ p=self.root/relative
107
+ if p.exists():os.utime(p,None);return p
108
+ if relative not in self.remote:raise FileNotFoundError(relative)
109
+ # local_dir avoids a second full copy in the global Hugging Face cache.
110
+ retry(lambda:hf_hub_download(self.repo,relative,repo_type='dataset',revision=self.head,local_dir=str(self.root)))
111
+ return p
112
+ def publish(self,files,message):
113
+ if not self.online:return
114
+ with self.lock:
115
+ operations=[CommitOperationAdd(path_in_repo=n,path_or_fileobj=str(p)) for n,p in files.items()]
116
+ result=self.api.create_commit(repo_id=self.repo,repo_type='dataset',operations=operations,
117
+ parent_commit=self.head,commit_message=message)
118
+ self.head=result.oid;self.remote.update(files)
119
+ def cortex_pool(self):
120
+ workers=self.cfg.get('prepared_workers',1)
121
+ if workers<=1:return None
122
+ if self.pool is None:
123
+ import multiprocessing as mp
124
+ from concurrent.futures import ProcessPoolExecutor
125
+ self.pool=ProcessPoolExecutor(max_workers=workers,mp_context=mp.get_context('spawn'),initializer=_worker_init)
126
+ return self.pool
127
+ def touch_table(self,part):
128
+ self.tables.pop(part.prefix,None);self.tables[part.prefix]=part
129
+ while len(self.tables)>self.cfg.get('prepared_memory_shards',6):
130
+ _,old=self.tables.popitem(last=False);old.table=None;old.table_number=None
131
+ def evict_disk(self):
132
+ if not self.online:return
133
+ budget=int(self.cfg.get('prepared_cache_gb',40)*1024**3)
134
+ files=list(self.root.glob('data/**/*.parquet'))+list(self.root.glob('data/**/cursor-*.pt'))
135
+ total=sum(p.stat().st_size for p in files)
136
+ for p in sorted(files,key=lambda p:p.stat().st_mtime):
137
+ if total<=budget:break
138
+ if p.relative_to(self.root).as_posix() in self.remote:
139
+ total-=p.stat().st_size;p.unlink()
140
+ def partition(self,name,spec,filename,limit,loader=None):
141
+ descriptor=dict(schema=SCHEMA,recipe=self.recipe,source=name,repo=spec['repo'],revision=spec['revision'],file=filename,context=limit)
142
+ key=sha(descriptor)
143
+ if key not in self.partitions:
144
+ single={**spec,'files':[filename]}
145
+ factory=lambda state:OneFile(name,single,state,limit,loader)
146
+ self.partitions[key]=Partition(self,'data/'+name+'/'+key,descriptor,factory,self.cfg.get('prepared_records_per_shard',32768))
147
+ return self.partitions[key]
148
+ def generated(self,kind,index,limit,codec):
149
+ count=65536;start=index//count*count # Canonical span is independent of physical shard tuning.
150
+ descriptor=dict(schema=SCHEMA,recipe=self.recipe,source='cortex',kind=kind,start=start,count=count,context=limit,codec=codec if kind=='visual' else 'none')
151
+ key=sha(descriptor)
152
+ if key not in self.partitions:
153
+ factory=lambda state:Generated(kind,start,count,limit,codec,state)
154
+ self.partitions[key]=Partition(self,'data/cortex/'+key,descriptor,factory,self.cfg.get('cortex_records_per_shard',512))
155
+ return self.partitions[key].get(index-start)
156
+ def observe(self,run_id,lineage,step,path,events):
157
+ if not events:return
158
+ # Immutable, checkpoint-identified presentation log. The same update's
159
+ # retry/resume reuses the same ledger path and never appends corpus rows.
160
+ relative=f'observed/{run_id}/{lineage}/step-{step:012d}.json';dest=self.root/relative
161
+ value=dict(schema=SCHEMA,run_id=run_id,lineage=lineage,through_step=step,
162
+ model_checkpoint_sha256=digest(Path(path) if Path(path).is_file() else Path(path)/'training.pt'),recipe=self.recipe,
163
+ prepared_revision=self.head,events=events,
164
+ presentations=sum(len(e['sample_ids']) for e in events),
165
+ unique_ids_in_segment=len({s for e in events for s in e['sample_ids']}))
166
+ atomic_json(dest,value);self.publish({relative:dest},f'{run_id}: observed sample references through update {step}')
167
+ def close(self):
168
+ for p in self.partitions.values():p.close()
169
+ if self.pool is not None:self.pool.shutdown(wait=True,cancel_futures=True);self.pool=None
170
+
171
+ class Partition:
172
+ def __init__(self,archive,prefix,descriptor,factory,chunk):
173
+ self.a,self.prefix,self.descriptor,self.factory,self.chunk=archive,prefix,descriptor,factory,chunk
174
+ self.root=archive.root/prefix;self.root.mkdir(parents=True,exist_ok=True)
175
+ self.index=self.root/'index.json';self.builder=None;self.table=None;self.table_number=None
176
+ rel=prefix+'/index.json'
177
+ if rel in archive.remote:
178
+ # A fresh run reads the remote catalog; existing local progress may
179
+ # be newer after an upload failure and is republished before use.
180
+ remote_path=hf_hub_download(archive.repo,rel,repo_type='dataset',revision=archive.head,cache_dir=str(archive.root/'index_cache'))
181
+ remote=json.loads(Path(remote_path).read_text())
182
+ local=json.loads(self.index.read_text()) if self.index.exists() else None
183
+ if local is None or remote['count']>local['count']:atomic_json(self.index,remote)
184
+ self.meta=json.loads(self.index.read_text()) if self.index.exists() else dict(descriptor=descriptor,count=0,complete=False,shards=[])
185
+ if self.meta['descriptor']!=descriptor:raise RuntimeError('Prepared-data recipe mismatch')
186
+ def _builder(self):
187
+ if self.builder is None:
188
+ state=None
189
+ if self.meta['shards']:
190
+ tail=self.meta['shards'][-1];p=self.a.fetch(self.prefix+'/'+tail['state'])
191
+ if digest(p)!=tail['state_sha']:raise ValueError('Prepared source cursor checksum mismatch')
192
+ state=torch.load(p,map_location='cpu',weights_only=True)
193
+ self.builder=self.factory(state);self.builder.initialize()
194
+ return self.builder
195
+ def prepare(self):
196
+ if self.meta['complete']:return
197
+ builder=self._builder();rows=[];finished=False
198
+ # Read a large bounded block once. Cortex CPU work can run in spawned
199
+ # workers without forking an initialized CUDA runtime.
200
+ generated=iter(builder.records(self.chunk,self.a.cortex_pool())) if isinstance(builder,Generated) else None
201
+ for _ in range(self.chunk):
202
+ try:r=next(generated) if generated is not None else builder.next_record()
203
+ except (EndFile,StopIteration):finished=True;break
204
+ cursor=copy.deepcopy(builder.s['cursor']);offset=r.get('meta',{}).get('chunk_offset',0)
205
+ identity=sha(dict(partition=self.descriptor,cursor=cursor,offset=offset))
206
+ meta={**r.get('meta',{}),'sample_id':identity,
207
+ 'prepared_ref':self.prefix+'#'+str(self.meta['count']+len(rows)),
208
+ 'origin':self.descriptor}
209
+ rows.append(dict(sample_id=identity,source=r['source'],ids=r['ids'],weights=r['weights'],prompt_len=r['prompt_len'],
210
+ metadata=json.dumps(meta,sort_keys=True),cursor=json.dumps(cursor,sort_keys=True),row_ordinal=builder.s['rows'],last_chunk=builder.s.get('pending_document') is None))
211
+ if isinstance(builder,Generated) and builder.s['index']>=builder.count:finished=True
212
+ number=len(self.meta['shards']);files={}
213
+ if rows:
214
+ name=f'part-{number:06d}.parquet';dest=self.root/name
215
+ schema=pa.schema([('sample_id',pa.string()),('source',pa.string()),('ids',pa.list_(pa.uint16())),('weights',pa.list_(pa.uint8())),
216
+ ('prompt_len',pa.int32()),('metadata',pa.string()),('cursor',pa.string()),('row_ordinal',pa.int64()),('last_chunk',pa.bool_())])
217
+ temp=dest.with_suffix('.tmp');pq.write_table(pa.Table.from_pylist(rows,schema=schema),temp,compression='zstd',row_group_size=1024);os.replace(temp,dest)
218
+ state_name=f'cursor-{number:06d}.pt';state_path=self.root/state_name
219
+ temp=state_path.with_suffix('.tmp');torch.save(builder.state_dict(),temp);os.replace(temp,state_path)
220
+ self.meta['shards'].append(dict(file=name,start=self.meta['count'],count=len(rows),sha256=digest(dest),
221
+ state=state_name,state_sha=digest(state_path),first_cursor=json.loads(rows[0]['cursor']),last_cursor=json.loads(rows[-1]['cursor'])))
222
+ self.meta['count']+=len(rows);self.a.prepared_records+=len(rows)
223
+ files.update({self.prefix+'/'+name:dest,self.prefix+'/'+state_name:state_path})
224
+ self.meta['complete']=finished;atomic_json(self.index,self.meta);files[self.prefix+'/index.json']=self.index
225
+ self.a.publish(files,f'Prepare {self.descriptor["source"]}: {self.meta["count"]} unique records')
226
+ self.a.evict_disk()
227
+ def _load(self,number):
228
+ shard=self.meta['shards'][number]
229
+ if self.table_number!=number:
230
+ p=self.a.fetch(self.prefix+'/'+shard['file'])
231
+ if digest(p)!=shard['sha256']:raise ValueError('Prepared record shard checksum mismatch')
232
+ self.table=pq.read_table(p);self.table_number=number
233
+ # Recover a locally completed but not yet remotely committed shard.
234
+ if self.a.online and self.prefix+'/'+shard['file'] not in self.a.remote:
235
+ names=[shard['file'],shard['state'],'index.json']
236
+ self.a.publish({self.prefix+'/'+n:self.root/n for n in names},'Recover prepared shard upload')
237
+ self.a.touch_table(self)
238
+ return self.table
239
+ def get(self,index):
240
+ while index>=self.meta['count'] and not self.meta['complete']:self.prepare()
241
+ if index>=self.meta['count']:raise EndFile
242
+ number=bisect.bisect_right([s['start'] for s in self.meta['shards']],index)-1
243
+ table=self._load(number);row=table.slice(index-self.meta['shards'][number]['start'],1).to_pylist()[0]
244
+ return dict(ids=row['ids'],weights=row['weights'],source=row['source'],prompt_len=row['prompt_len'],meta={**json.loads(row['metadata']),'prepared_ref':self.prefix+'#'+str(index)},
245
+ prepared_cursor=json.loads(row['cursor']),prepared_last=row['last_chunk'])
246
+ def after_cursor(self,cursor):
247
+ if cursor is None:return 0
248
+ i=0
249
+ while True:
250
+ try:r=self.get(i)
251
+ except EndFile:return self.meta['count']
252
+ if cursor_key(r['prepared_cursor'])>cursor_key(cursor):return i
253
+ i+=1
254
+ def row_records(self,cursor):
255
+ out=[];i=0;target=cursor_key(cursor)
256
+ while True:
257
+ try:r=self.get(i)
258
+ except EndFile:break
259
+ current=cursor_key(r['prepared_cursor'])
260
+ if current>target:break
261
+ if current==target:out.append(r)
262
+ i+=1
263
+ if not out:raise ValueError('Legacy cursor not found in its pinned prepared shard')
264
+ return out
265
+ def close(self):
266
+ if self.builder:self.builder.close()
267
+ self.builder=None;self.table=None;self.table_number=None
268
+
269
+ class PreparedSource(CoverageSource):
270
+ def __init__(self,*args,archive,**kwargs):
271
+ super().__init__(*args,**kwargs);self.archive=archive
272
+ def initialize(self):
273
+ super().initialize();self.s.setdefault('prepared_position',None)
274
+ def partition(self):return self.archive.partition(self.name,self.spec,self.spec['files'][self.s['shard']],self.limit,self.loader)
275
+ def identify_borrowed(self,r):
276
+ if r.get('meta',{}).get('sample_id'):return r
277
+ rows=self.partition().row_records(self.s['cursor'])
278
+ doc=self.s.get('pending_document')
279
+ idx=(doc['offset']//self.limit-1) if doc else len(rows)-len(self.s['pending'])-1
280
+ candidate=rows[idx]
281
+ if candidate['ids']!=r['ids'] or candidate['weights']!=r['weights']:raise ValueError('Legacy buffered record differs from canonical prepared record')
282
+ return candidate
283
+ def next_record(self,excluded=()):
284
+ if self.s['pending']:
285
+ rows=self.partition().row_records(self.s['cursor']);r=rows[-len(self.s['pending'])];old=self.s['pending'].pop(0)
286
+ if r['ids']!=old['ids']:raise ValueError('Legacy pending chunk mismatch')
287
+ return r
288
+ if self.s.get('pending_document'):
289
+ rows=self.partition().row_records(self.s['cursor']);r=rows[self.s['pending_document']['offset']//self.limit]
290
+ old=super()._chunk()
291
+ if old['ids']!=r['ids']:raise ValueError('Legacy partial-document mismatch')
292
+ return r
293
+ visited=0
294
+ while True:
295
+ part=self.partition()
296
+ if self.s['prepared_position'] is None:self.s['prepared_position']=part.after_cursor(self.s['cursor'])
297
+ try:r=part.get(self.s['prepared_position'])
298
+ except EndFile:
299
+ visited+=1
300
+ if visited>len(self.spec['files']):raise RuntimeError(self.name+': empty prepared corpus')
301
+ self.s['shard']+=1;self.s['cursor']=None;self.s['prepared_position']=0
302
+ if self.s['shard']==len(self.spec['files']):self.s['shard']=0;self.s['epoch']+=1;self.s['pass_rows']=0
303
+ continue
304
+ if r['prepared_cursor']!=self.s['cursor']:
305
+ self.s['rows']+=1;self.s['pass_rows']+=1;self.s['accepted']+=1
306
+ self.s['cursor']=r['prepared_cursor'];self.s['prepared_position']+=1
307
+ self.s['documents_emitted']+=int(r['prepared_last'])
308
+ return r
309
+
310
+ class PreparedCortex(CodecCortex):
311
+ def __init__(self,*args,archive,**kwargs):super().__init__(*args,**kwargs);self.archive=archive
312
+ def record(self):
313
+ if self.codec=='none' or self.until_visual:
314
+ r=self.archive.generated('kernel',self.base.s['index'],self.limit,'none')
315
+ self.base.s['index']+=1;self.until_visual=max(0,self.until_visual-1);return r
316
+ r=self.archive.generated('visual',self.visual_index,self.limit,self.codec)
317
+ self.visual_index+=1;self.until_visual=3;return r
318
+
319
+ class PreparedMixer(CoverageMixer):
320
+ def __init__(self,manifest,limit=1024,state=None,loaders=None,group_weights=None,codec='none',archive=None):
321
+ super().__init__(manifest,limit,state,loaders,group_weights,codec);state=state or {};self.archive=archive
322
+ self.sources={n:PreparedSource(n,s,state.get('sources',{}).get(n),limit,(loaders or {}).get(n),archive=archive) for n,s in manifest.items()}
323
+ self.cortex=PreparedCortex(state.get('cortex'),limit,codec,archive=archive);self.batch_refs=[];self.batch_positions=[]
324
+ def initialize(self):
325
+ super().initialize()
326
+ for name,r in list(self.pending.items()):
327
+ if name in self.sources:self.pending[name]=self.sources[name].identify_borrowed(r)
328
+ elif name=='cortex' and not r.get('meta',{}).get('sample_id'):
329
+ # The already borrowed legacy Cortex record is the immediately
330
+ # preceding generator item. Match against both possible kinds.
331
+ candidates=[]
332
+ if self.cortex.base.s['index']>0:candidates.append(self.archive.generated('kernel',self.cortex.base.s['index']-1,self.limit,'none'))
333
+ if self.cortex.codec!='none' and self.cortex.visual_index>0:candidates.append(self.archive.generated('visual',self.cortex.visual_index-1,self.limit,self.cortex.codec))
334
+ matches=[x for x in candidates if x['ids']==r['ids'] and x['weights']==r['weights']]
335
+ if not matches:raise ValueError('Legacy Cortex buffer could not be identified')
336
+ self.pending[name]=matches[0]
337
+ return self
338
+ def block(self):
339
+ if not self.mix_queue:self._refill_mix_queue()
340
+ name=self.mix_queue.pop(0);tokens=[];weights=[];refs=[];positions=[]
341
+ while True:
342
+ r=self.next_for(name);validate(r,self.limit)
343
+ if tokens and len(tokens)+len(r['ids'])>self.limit+1:self.pending[name]=r;break
344
+ tokens.extend(r['ids']);weights.extend(r['weights']);refs.append(r['meta']['sample_id']);positions.append(r['meta']['prepared_ref'])
345
+ if len(tokens)==self.limit+1 or len(tokens)>=int(self.limit*.90):break
346
+ used=len(tokens);padding=self.limit+1-used;tokens.extend([PAD]*padding);weights.extend([0]*padding)
347
+ self.stats[name+'/blocks']+=1;self.stats[name+'/targets']+=sum(weights[1:]);self.stats[name+'/tokens']+=used-1
348
+ self.batch_refs.extend(refs);self.batch_positions.extend(positions)
349
+ return tokens,weights,name,used
350
+ def batch(self,count):
351
+ self.batch_refs=[];self.batch_positions=[];data=super().batch(count);data['sample_ids']=list(self.batch_refs);data['sample_refs']=list(self.batch_positions);return data
352
+
353
+ class ShardPrefetcher:
354
+ """One CPU producer, bounded disk dispatch shards, exact consumer snapshots."""
355
+ def __init__(self,mixer,cfg,run_id,start_step,remaining=None,variant='sparkbet',codec='none',mapper=None,observations=None):
356
+ self.mixer,self.cfg,self.archive=mixer,cfg,mixer.archive;self.manifest=mixer.manifest
357
+ self.delivered=mixer.state_dict();self.initial_step=start_step;self.variant=variant;self.codec=codec;self.mapper=mapper
358
+ self.root=Path(cfg['work_dir'])/run_id/'dispatch';self.root.mkdir(parents=True,exist_ok=True)
359
+ self.depth=cfg.get('prefetch_shards',2);self.per=cfg.get('dispatch_batches_per_shard',16)
360
+ self.queue=queue.Queue(maxsize=self.depth);self.stop_event=threading.Event();self.ready=threading.Condition()
361
+ self.made=0;self.error=None;self.done=False;self.current=[];self.cursor=0;self.current_path=None;self.pending_event=None;self.events=[]
362
+ self.remaining=remaining;self.panel=mixer.validation()
363
+ self.observations=copy.deepcopy(observations or dict(presentations=0,unique_samples=0,ranges={}))
364
+ for old in self.root.glob('dispatch-*.pt'):old.unlink()
365
+ self.worker=threading.Thread(target=self._produce,name='rewrite7-shard-producer',daemon=True);self.worker.start()
366
+ def _produce(self):
367
+ produced=0;number=0
368
+ try:
369
+ while not self.stop_event.is_set() and (self.remaining is None or produced<self.remaining):
370
+ # Backpressure BEFORE construction bounds the extra in-flight shard.
371
+ while self.queue.full() and not self.stop_event.wait(.1):pass
372
+ if self.stop_event.is_set():break
373
+ entries=[]
374
+ for _ in range(self.per):
375
+ if self.stop_event.is_set() or (self.remaining is not None and produced>=self.remaining):break
376
+ data=self.mixer.batch(self.cfg['global_batch']);step=self.initial_step+produced
377
+ if self.mapper:data['canvas']=self.mapper(data,step)
378
+ # Torch serialization contains tensors/primitive state only.
379
+ data['ids']=torch.from_numpy(data['ids']);data['weights']=torch.from_numpy(data['weights'])
380
+ entries.append(dict(data=data,after=self.mixer.state_dict(),step=step));produced+=1
381
+ if not entries:break
382
+ path=self.root/f'dispatch-{self.initial_step+produced-len(entries):012d}.pt';tmp=path.with_suffix('.tmp')
383
+ torch.save(entries,tmp);os.replace(tmp,path)
384
+ while not self.stop_event.is_set():
385
+ try:self.queue.put(path,timeout=.1);break
386
+ except queue.Full:pass
387
+ number+=1
388
+ with self.ready:self.made=number;self.ready.notify_all()
389
+ with self.ready:self.done=True;self.ready.notify_all()
390
+ except BaseException as e:
391
+ self.error=(e,traceback.format_exc())
392
+ with self.ready:self.done=True;self.ready.notify_all()
393
+ finally:self.mixer.close()
394
+ def prewarm(self):
395
+ target=self.depth if self.remaining is None else min(self.depth,math.ceil(self.remaining/self.per))
396
+ began=time.monotonic();last=began
397
+ with self.ready:
398
+ while self.made<target and not self.done:
399
+ self.ready.wait(timeout=5)
400
+ if time.monotonic()-last>=30:print(f'Preparing rewrite7 startup buffer: {self.made}/{target} dispatch shards ready',flush=True);last=time.monotonic()
401
+ self._raise();self.preparation_seconds=time.monotonic()-began
402
+ print(f'rewrite7 startup buffer ready: {self.made} shards, {self.preparation_seconds:.1f}s',flush=True)
403
+ def _raise(self):
404
+ if self.error:raise RuntimeError('Background shard preparation failed; committed model cursor is unchanged.\n'+self.error[1]) from self.error[0]
405
+ def batch(self,count):
406
+ if count!=self.cfg['global_batch']:raise ValueError('Prefetch logical batch changed')
407
+ if self.pending_event is not None:raise RuntimeError('Commit the previous successful batch before advancing')
408
+ if self.cursor==len(self.current):
409
+ if self.current_path:self.current_path.unlink(missing_ok=True)
410
+ while True:
411
+ self._raise()
412
+ try:self.current_path=self.queue.get(timeout=.5);break
413
+ except queue.Empty:
414
+ if self.done:raise StopIteration('Prepared finite run exhausted')
415
+ self.current=torch.load(self.current_path,map_location='cpu',weights_only=True);self.cursor=0
416
+ entry=self.current[self.cursor];self.cursor+=1;data=entry['data']
417
+ data={**data,'ids':data['ids'].numpy(),'weights':data['weights'].numpy()}
418
+ self.pending_after=entry['after'];self.pending_event=dict(update=entry['step']+1,sample_ids=data['sample_ids'],sample_refs=data['sample_refs'],
419
+ canvas_seed=self.cfg.get('seed'),canvas_step=entry['step'] if self.mapper else None,variant=self.variant,codec=self.codec)
420
+ return data
421
+ def commit_batch(self):
422
+ if self.pending_event is None:raise RuntimeError('No prepared batch to commit')
423
+ self.delivered=self.pending_after
424
+ for ref in self.pending_event['sample_refs']:
425
+ prefix,position=ref.rsplit('#',1);position=int(position)
426
+ ranges=self.observations['ranges'].setdefault(prefix,[])
427
+ if not any(a<=position<b for a,b in ranges):
428
+ ranges.append([position,position+1]);ranges.sort();merged=[]
429
+ for a,b in ranges:
430
+ if merged and a<=merged[-1][1]:merged[-1][1]=max(merged[-1][1],b)
431
+ else:merged.append([a,b])
432
+ self.observations['ranges'][prefix]=merged;self.observations['unique_samples']+=1
433
+ self.observations['presentations']+=1
434
+ self.events.append(self.pending_event);self.pending_event=None
435
+ def state_dict(self):return copy.deepcopy(self.delivered)
436
+ def validation(self):return self.panel
437
+ def coverage(self):
438
+ return {n:dict(epoch=s['epoch'],shard=s['shard'],shards=len(self.manifest[n]['files']),rows_read=s['rows'],
439
+ prepared_position=s.get('prepared_position'),documents_emitted=s.get('documents_emitted',0)) for n,s in self.delivered['sources'].items()}
440
+ def flush_observed(self,run_id,lineage,step,path):
441
+ self.archive.observe(run_id,lineage,step,path,self.events);self.events=[]
442
+ def close(self):
443
+ self.stop_event.set();self.worker.join(timeout=30)
444
+ if self.worker.is_alive():raise RuntimeError('Background preparation has not stopped; retry after its current network operation ends')
445
+ self.archive.close()
unified/ablations/unified-v1-blt-none-s42/source/records.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Direct UTF-8 bytes. Special IDs match BET, not the old Cortex tokenizer."""
2
+ from dataclasses import dataclass
3
+ PAD,BOS,EOS=256,257,258
4
+
5
+ class Oversize(ValueError):pass
6
+ class InvalidRecord(ValueError):pass
7
+
8
+ def ids(text):return list(text.encode('utf-8'))
9
+ def decode(tokens):return bytes(t for t in tokens if 0<=t<256).decode('utf-8',errors='replace')
10
+
11
+ def record(prefix,answer,source,limit=1024,supervise_all=False,meta=None):
12
+ p,a=ids(prefix),ids(answer)
13
+ if not a:raise InvalidRecord('Empty target: '+source)
14
+ tokens=[BOS]+p+a+[EOS]
15
+ if len(tokens)>limit+1:raise Oversize(f'{source}: {len(tokens)} IDs exceeds {limit+1}; no truncation')
16
+ weights=[0]+([1]*len(p) if supervise_all else [0]*len(p))+[1]*(len(a)+1)
17
+ return dict(ids=tokens,weights=weights,source=source,prompt_len=1+len(p),meta=meta or {})
18
+
19
+ def plain_chunks(text,source,limit=1024):
20
+ # Lossless bytes, including split UTF-8 sequences: decoder assembles the byte stream.
21
+ # No false EOS at chunk boundaries. One-token overlap predicts each byte once.
22
+ if not isinstance(text,str) or not text.strip():raise InvalidRecord('Empty/non-string text: '+source)
23
+ raw_bytes=text.encode('utf-8')
24
+ if len(raw_bytes)>1024*1024:raise Oversize('Document exceeds the 1 MiB bounded-buffer limit; rejected intact')
25
+ raw=[BOS]+list(raw_bytes)+[EOS];out=[]
26
+ for offset in range(0,len(raw)-1,limit):
27
+ chunk=raw[offset:offset+limit+1]
28
+ out.append(dict(ids=chunk,weights=[0]+[1]*(len(chunk)-1),source=source,prompt_len=1,meta={}))
29
+ return out
30
+
31
+ def validate(r,limit=1024):
32
+ assert 2<=len(r['ids'])<=limit+1
33
+ assert len(r['ids'])==len(r['weights'])
34
+ assert all(type(x)==int and 0<=x<259 for x in r['ids'])
35
+ assert all(x in (0,1) for x in r['weights']) and sum(r['weights'][1:])>0
36
+ assert r['weights'][0]==0
37
+ return r
unified/ablations/unified-v1-blt-none-s42/source/tokenization_bet.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from transformers import PreTrainedTokenizer
4
+
5
+
6
+ BYTE_PREFIX = "<0x"
7
+ PAD_TOKEN = "<pad>"
8
+ BOS_TOKEN = "<bos>"
9
+ EOS_TOKEN = "<eos>"
10
+
11
+
12
+ class BETByteTokenizer(PreTrainedTokenizer):
13
+ """Lossless UTF-8 byte tokenizer used by BET.
14
+
15
+ IDs:
16
+ 0..255 -> raw byte values
17
+ 256 -> PAD
18
+ 257 -> BOS
19
+ 258 -> EOS
20
+
21
+ No UNK token is required because every UTF-8 string is representable as bytes.
22
+ """
23
+
24
+ vocab_files_names = {"vocab_file": "byte_vocab.json"}
25
+ model_input_names = ["input_ids", "attention_mask"]
26
+
27
+ def __init__(
28
+ self,
29
+ vocab_file=None,
30
+ pad_token=PAD_TOKEN,
31
+ bos_token=BOS_TOKEN,
32
+ eos_token=EOS_TOKEN,
33
+ unk_token=None,
34
+ model_max_length=1024,
35
+ padding_side="left",
36
+ clean_up_tokenization_spaces=False,
37
+ **kwargs,
38
+ ):
39
+ # Transformers v5 loads values from tokenizer_config.json into this
40
+ # constructor. Make every value that we also forward to PythonBackend
41
+ # an explicit argument so it is consumed exactly once instead of being
42
+ # duplicated inside **kwargs.
43
+ self.vocab_file = vocab_file
44
+ kwargs.setdefault("split_special_tokens",True)
45
+ super().__init__(
46
+ pad_token=pad_token,
47
+ bos_token=bos_token,
48
+ eos_token=eos_token,
49
+ unk_token=unk_token,
50
+ model_max_length=model_max_length,
51
+ padding_side=padding_side,
52
+ clean_up_tokenization_spaces=clean_up_tokenization_spaces,
53
+ **kwargs,
54
+ )
55
+
56
+ @property
57
+ def vocab_size(self):
58
+ return 259
59
+
60
+ def get_vocab(self):
61
+ vocab = {f"<0x{i:02X}>": i for i in range(256)}
62
+ vocab[PAD_TOKEN] = 256
63
+ vocab[BOS_TOKEN] = 257
64
+ vocab[EOS_TOKEN] = 258
65
+ return vocab
66
+
67
+ def _tokenize(self, text, **kwargs):
68
+ return [f"<0x{b:02X}>" for b in text.encode("utf-8", errors="replace")]
69
+
70
+ def _convert_token_to_id(self, token):
71
+ if token == PAD_TOKEN:
72
+ return 256
73
+ if token == BOS_TOKEN:
74
+ return 257
75
+ if token == EOS_TOKEN:
76
+ return 258
77
+ if isinstance(token, str) and token.startswith(BYTE_PREFIX) and token.endswith(">"):
78
+ try:
79
+ value = int(token[3:-1], 16)
80
+ if 0 <= value <= 255:
81
+ return value
82
+ except ValueError:
83
+ pass
84
+ # This branch should be unreachable for text encoded by this tokenizer.
85
+ return 0
86
+
87
+ def _convert_id_to_token(self, index):
88
+ index = int(index)
89
+ if 0 <= index <= 255:
90
+ return f"<0x{index:02X}>"
91
+ if index == 256:
92
+ return PAD_TOKEN
93
+ if index == 257:
94
+ return BOS_TOKEN
95
+ if index == 258:
96
+ return EOS_TOKEN
97
+ return "<0x00>"
98
+
99
+ def convert_tokens_to_string(self, tokens):
100
+ out = []
101
+ buf = bytearray()
102
+
103
+ def flush():
104
+ nonlocal buf
105
+ if buf:
106
+ out.append(bytes(buf).decode("utf-8", errors="replace"))
107
+ buf = bytearray()
108
+
109
+ for token in tokens:
110
+ idx = self._convert_token_to_id(token)
111
+ if isinstance(token, str) and 0 <= idx <= 255 and token.startswith(BYTE_PREFIX):
112
+ buf.append(idx)
113
+ else:
114
+ flush()
115
+ out.append(str(token))
116
+ flush()
117
+ return "".join(out)
118
+
119
+ def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
120
+ # BET pretraining did not automatically insert BOS/EOS around ordinary text.
121
+ if token_ids_1 is None:
122
+ return list(token_ids_0)
123
+ return list(token_ids_0) + list(token_ids_1)
124
+
125
+ def create_token_type_ids_from_sequences(self, token_ids_0, token_ids_1=None):
126
+ n = len(token_ids_0) + (len(token_ids_1) if token_ids_1 is not None else 0)
127
+ return [0] * n
128
+
129
+ def save_vocabulary(self, save_directory, filename_prefix=None):
130
+ os.makedirs(save_directory, exist_ok=True)
131
+ name = "byte_vocab.json" if filename_prefix is None else f"{filename_prefix}-byte_vocab.json"
132
+ path = os.path.join(save_directory, name)
133
+ vocab = {f"<0x{i:02X}>": i for i in range(256)}
134
+ vocab.update({PAD_TOKEN: 256, BOS_TOKEN: 257, EOS_TOKEN: 258})
135
+ with open(path, "w", encoding="utf-8") as f:
136
+ json.dump(vocab, f, indent=2, sort_keys=True)
137
+ return (path,)
unified/ablations/unified-v1-blt-none-s42/source/trainer.py ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Continuous SparkBET training on one GPU or torchrun DDP, always FP16 autocast."""
2
+ import os,json,time,math,random,signal,gc,uuid,shutil,fcntl,subprocess,sys,traceback
3
+ from pathlib import Path
4
+ from datetime import timedelta
5
+ from contextlib import nullcontext
6
+ import numpy as np
7
+ import torch
8
+ import torch.distributed as dist
9
+ from torch.nn.parallel import DistributedDataParallel as DDP
10
+ from bet_model import (
11
+ BETFog,BETConfig,uniform_steps,verify_architecture,
12
+ set_gradient_checkpointing,set_state_noise_sigma,
13
+ )
14
+ from records import decode
15
+ from data_stream import Mixer
16
+ from checkpoints import FORMAT,save,resume,legacy_import,pipeline_hash,atomic_json,Publisher
17
+ from dataset_registry import make_manifest
18
+
19
+ ROOT=Path(__file__).resolve().parent
20
+
21
+
22
+ def lr_at(step,cfg):
23
+ """Warm up once, then keep learning indefinitely; no terminal update exists."""
24
+ warm=cfg['warmup_steps']
25
+ return cfg['lr']*min(1.0,(step+1)/max(1,warm))
26
+
27
+
28
+ def loss_sum(logits,target,mask):
29
+ # FP32 cross entropy even when matmuls use FP16.
30
+ losses=torch.nn.functional.cross_entropy(
31
+ logits.float().flatten(0,1),target.reshape(-1),reduction='none'
32
+ ).view_as(mask)
33
+ return (losses*mask).sum()
34
+
35
+
36
+ def scaler_new(enabled=True):
37
+ # FP16 + GradScaler is deliberately the one CUDA precision contract on T4,
38
+ # A10/A10G and RTX PRO 6000 so hardware changes do not change numerics by policy.
39
+ return torch.amp.GradScaler('cuda',enabled=enabled and torch.cuda.is_available(),init_scale=1024,growth_interval=2000)
40
+
41
+
42
+ def optimizer_new(model,cfg):
43
+ return torch.optim.AdamW(model.parameters(),lr=cfg['lr'],betas=(.9,.95),eps=1e-8,weight_decay=.1)
44
+
45
+
46
+ def broadcast_object(value,rank,world):
47
+ if world==1:return value
48
+ box=[value if rank==0 else None];dist.broadcast_object_list(box,src=0);return box[0]
49
+
50
+
51
+ def coordinator_call(fn,rank,world):
52
+ """Broadcast rank-zero errors before peers enter the next collective."""
53
+ result=None;error=None
54
+ if rank==0:
55
+ try:result=fn()
56
+ except Exception as exc:error=f'{type(exc).__name__}: {exc}'
57
+ error=broadcast_object(error,rank,world)
58
+ if error:raise RuntimeError('Coordinator operation failed: '+error)
59
+ return result
60
+
61
+
62
+ def all_true(flag,device,world):
63
+ value=torch.tensor(int(flag),device=device,dtype=torch.int32)
64
+ if world>1:dist.all_reduce(value,op=dist.ReduceOp.MIN)
65
+ return bool(value.item())
66
+
67
+
68
+ def amp(device):
69
+ return torch.autocast(device.type,dtype=torch.float16,enabled=device.type=='cuda')
70
+
71
+
72
+ def rng_state():
73
+ return dict(
74
+ python=random.getstate(),torch=torch.get_rng_state(),
75
+ cuda=torch.cuda.get_rng_state().cpu() if torch.cuda.is_available() else None,
76
+ )
77
+
78
+
79
+ def restore_rng(s):
80
+ random.setstate(s['python']);torch.set_rng_state(s['torch'])
81
+ if s['cuda'] is not None and torch.cuda.is_available():torch.cuda.set_rng_state(s['cuda'])
82
+
83
+
84
+ def auxiliary_stage(update_step,cfg):
85
+ """Sustained fresh-data stages: L8+Lr for base_updates*r optimizer updates.
86
+
87
+ One optimizer update fetches exactly one *new batch draw* from the mixer.
88
+ Batches may revisit finite data across epochs, but do not repeat a cached
89
+ mini-batch merely to inflate the number of stage steps. The selected depth
90
+ is independent of microbatch, DDP partitions, and FP16 overflow retries.
91
+ The origin anchors the new curriculum at an explicitly migrated checkpoint.
92
+ """
93
+ lo=int(cfg.get('aux_loop_min',1));hi=int(cfg.get('aux_loop_max',7))
94
+ if not (1<=lo<=hi<8):raise ValueError('auxiliary loop range must stay inside L1..L7')
95
+ if cfg.get('aux_loop_schedule','progressive_data_stages_v2')!='progressive_data_stages_v2':
96
+ raise ValueError('Unsupported auxiliary schedule: expected progressive_data_stages_v2')
97
+ base=int(cfg.get('aux_stage_base_updates',128))
98
+ if base<1:raise ValueError('aux_stage_base_updates must be positive')
99
+ step=int(update_step);origin=int(cfg.get('aux_curriculum_origin_step',0))
100
+ if step<0 or origin<0 or step<origin:
101
+ raise ValueError('Curriculum step must be >= its nonnegative origin')
102
+ cycle_updates=base*sum(range(lo,hi+1))
103
+ cycle,phase=divmod(step-origin,cycle_updates)
104
+ for depth in range(lo,hi+1):
105
+ stage_updates=base*depth
106
+ if phase<stage_updates:
107
+ return dict(depth=depth,cycle=cycle,cycle_updates=cycle_updates,
108
+ stage_updates=stage_updates,stage_update=phase,
109
+ stage_remaining=stage_updates-phase-1)
110
+ phase-=stage_updates
111
+ raise AssertionError('Invalid progressive curriculum phase')
112
+
113
+
114
+ def auxiliary_loop(update_step,micro_index,cfg):
115
+ """One exact auxiliary budget per committed update; micro_index unused."""
116
+ return auxiliary_stage(update_step,cfg)['depth']
117
+
118
+
119
+ def prepare(cfg, keep_datasets=False):
120
+ """Runs once in the coordinator before CUDA/DDP is launched."""
121
+ run=Path(cfg['run_dir']);run.mkdir(parents=True,exist_ok=True)
122
+ lock=open(run/'trainer.lock','w')
123
+ try:fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
124
+ except BlockingIOError:raise RuntimeError('A trainer is already running in this output directory')
125
+ publisher=Publisher(cfg['repo'],run,checkpoint_prefix=cfg.get('hub_checkpoint_prefix','checkpoints/sparkbet9m'))
126
+ if cfg['upload']:publisher.authenticate()
127
+ sha=pipeline_hash(ROOT)
128
+ if cfg.get('aux_loop_schedule')!='progressive_data_stages_v2':
129
+ raise ValueError('This notebook requires progressive_data_stages_v2 auxiliary scheduling')
130
+ if int(cfg.get('aux_stage_base_updates',0))<1:
131
+ raise ValueError('aux_stage_base_updates must be a positive integer')
132
+ # Allow only two precisely verified prior code fingerprints; fail closed otherwise.
133
+ previous=("273f8513fadd50aeb20907fce71d2352fbb12a965519e7810950caedf36bd207", "a306e734261104d381edf90097254fcb59b298deec64314f48fe85764e5159df", "78638dd200d2834060ffb746ee680feb15b95586b432d37e25606a2575b131aa") if cfg.get('allow_verified_schedule_migration',True) else ()
134
+ state=resume(
135
+ cfg['resume_roots'],cfg['repo'] if cfg['hub_resume'] else None,run,sha,
136
+ checkpoint_prefix=cfg.get('hub_checkpoint_prefix','checkpoints/sparkbet9m'),
137
+ allowed_previous_pipelines=previous,
138
+ migration_schedule='progressive_data_stages_v2',
139
+ aux_stage_base_updates=int(cfg['aux_stage_base_updates']),
140
+ )
141
+ old=None
142
+ if state is None:old=legacy_import(cfg['resume_roots'],cfg.get('legacy_checkpoint'))
143
+ if state:
144
+ # Optimization semantics follow the checkpoint. Hardware-only choices
145
+ # (microbatch and checkpointing) are re-probed on every launch.
146
+ for k in ['global_batch','context','lr','warmup_steps','primary_loops','aux_loop_weight','aux_loop_min','aux_loop_max','aux_loop_schedule','aux_stage_base_updates','aux_curriculum_origin_step','mix_weights']:
147
+ if k in state['config'] and cfg.get(k)!=state['config'][k]:
148
+ print(f'Resuming saved {k}={state["config"][k]}',flush=True);cfg[k]=state['config'][k]
149
+ # When crossing into the rewrite-plus live mix, keep the learned model,
150
+ # optimizer, scaler and Cortex generator position but intentionally reset
151
+ # language source cursors. The source set itself changed, so reusing the old
152
+ # manifest/cursors would silently keep training on the previous corpus.
153
+ migration=state.get('schedule_migrations',[])
154
+ just_migrated=bool(migration and migration[-1].get('new_pipeline')==sha and migration[-1].get('step')==state['step'])
155
+ if just_migrated and cfg.get('migrate_to_rewrite_plus_mix',True):
156
+ manifest=make_manifest(cfg.get('data_file_overrides'),cfg.get('disabled_sources',[]))
157
+ old_mix=state.get('mixer') or {}
158
+ mix_state=dict(cortex=old_mix.get('cortex'),rng=old_mix.get('rng'),stats=old_mix.get('stats',{}))
159
+ mixer=Mixer(manifest,cfg['context'],mix_state,mix_weights=cfg.get('mix_weights')).initialize()
160
+ state.setdefault('data_migrations',[]).append(dict(step=state['step'],kind='rewrite_plus_live_mix',language_cursors_reset=True))
161
+ print('Migrated to live rewrite-plus mix: model/optimizer/scaler/Cortex preserved; language streams restart under the new fair interleaver.',flush=True)
162
+ else:
163
+ manifest=state['manifest'];mixer=Mixer(manifest,cfg['context'],state['mixer'],mix_weights=cfg.get('mix_weights')).initialize()
164
+ else:
165
+ manifest=make_manifest(cfg.get('data_file_overrides'),cfg.get('disabled_sources',[]));mixer=Mixer(manifest,cfg['context'],mix_weights=cfg.get('mix_weights')).initialize()
166
+ state=dict(
167
+ format=FORMAT,step=int(old['completed_steps']) if old else 0,lineage=uuid.uuid4().hex,
168
+ pipeline=sha,config=cfg,manifest=manifest,mixer=mixer.state_dict(),rng_by_rank=None,
169
+ tokens=0,targets=0,model=old['model'] if old else None,optimizer=old['optimizer'] if old else None,
170
+ scaler=old['scaler'] if old else None,migration=old.get('migration') if old else None,
171
+ )
172
+ # Keep the checkpoint's stage origin and budget; never restart a stage
173
+ # accidentally when the notebook reruns after a checkpoint recovery.
174
+ auxiliary_stage(state['step'],cfg)
175
+ panel=mixer.validation();state['manifest']=manifest;state['mixer']=mixer.state_dict();state['config']=cfg
176
+ staging=run/'launch_state.pt';tmp=run/'launch_state.tmp.pt';torch.save(state,tmp);os.replace(tmp,staging)
177
+ torch.save(panel,run/'validation.pt');atomic_json(run/'config.json',cfg);atomic_json(run/'dataset_manifest.json',manifest)
178
+ print(f'Preflight: {len(manifest)} language sources + Cortex; {len(panel)} fixed validation records. Target {cfg["repo"]}.',flush=True)
179
+ print('CPU data validation complete. GPU memory/gradient preflight runs in the training process.',flush=True)
180
+ if keep_datasets:return lock,mixer
181
+ mixer.close();return lock
182
+
183
+
184
+ def _preflight_candidate(model,opt,sc,device,cfg,mb,use_checkpointing):
185
+ set_gradient_checkpointing(use_checkpointing)
186
+ torch.cuda.empty_cache();torch.cuda.reset_peak_memory_stats();opt.zero_grad(set_to_none=True)
187
+ # Use full 1024 input length. Training slices a 1025-token record to 1024 inputs.
188
+ x=torch.randint(0,256,(mb,cfg['context']),device=device)
189
+ y=torch.roll(x,-1,1);mask=torch.ones_like(x,dtype=torch.float32)
190
+ paths=[(int(cfg.get('primary_loops',8)),1.0),(int(cfg.get('aux_loop_max',7)),float(cfg.get('aux_loop_weight',.20)))]
191
+ for loops,weight in paths:
192
+ with amp(device):
193
+ logits=model(x,uniform_steps(loops));loss=loss_sum(logits,y,mask)*weight/mask.sum()
194
+ sc.scale(loss).backward();del logits,loss
195
+ sc.unscale_(opt)
196
+ finite=all(p.grad is None or bool(torch.isfinite(p.grad).all()) for p in model.parameters())
197
+ if not finite:raise RuntimeError('Non-finite FP16 preflight gradients')
198
+ torch.nn.utils.clip_grad_norm_(model.parameters(),1.)
199
+ sc.step(opt);sc.update();torch.cuda.synchronize()
200
+ used=torch.cuda.max_memory_allocated()
201
+ del x,y,mask;opt.zero_grad(set_to_none=True)
202
+ return used
203
+
204
+
205
+ def memory_preflight(device,cfg):
206
+ """Full-context L8+L7 probe. Prefer physical batch before checkpointing."""
207
+ saved=rng_state();torch.manual_seed(42)
208
+ model=BETFog(BETConfig()).to(device);verify_architecture(model,1)
209
+ memory=torch.cuda.get_device_properties(device).total_memory
210
+ local_batch=cfg['global_batch']//cfg['world_size']
211
+ requested=[local_batch,32,16,12,8,6,4,3,2,1]+list(cfg.get('microbatch_candidates') or [])
212
+ candidates=[]
213
+ for v in requested:
214
+ v=int(v)
215
+ if v>=1 and v<=local_batch and local_batch%v==0 and v not in candidates:candidates.append(v)
216
+ if 1 not in candidates:candidates.append(1)
217
+ candidates=sorted(candidates,reverse=True)
218
+ report=[];selected=None;selected_checkpointing=None
219
+ # First spend available memory on real batch. Checkpoint only if no uncheckpointed
220
+ # candidate has enough safety headroom.
221
+ for use_checkpointing in (False,True):
222
+ for mb in candidates:
223
+ opt=optimizer_new(model,cfg);sc=scaler_new()
224
+ try:
225
+ used=_preflight_candidate(model,opt,sc,device,cfg,mb,use_checkpointing)
226
+ headroom=used<=memory*float(cfg.get('preflight_memory_fraction',.86))
227
+ report.append(dict(microbatch=mb,gradient_checkpointing=use_checkpointing,peak_gib=used/2**30,headroom=headroom))
228
+ if headroom:
229
+ selected=mb;selected_checkpointing=use_checkpointing
230
+ del opt,sc;break
231
+ except torch.cuda.OutOfMemoryError:
232
+ report.append(dict(microbatch=mb,gradient_checkpointing=use_checkpointing,oom=True))
233
+ torch.cuda.empty_cache()
234
+ finally:
235
+ if 'opt' in locals():
236
+ try:opt.zero_grad(set_to_none=True)
237
+ except Exception:pass
238
+ del opt
239
+ if 'sc' in locals():del sc
240
+ gc.collect();torch.cuda.empty_cache()
241
+ if selected is not None:break
242
+ del model;gc.collect();torch.cuda.empty_cache();restore_rng(saved)
243
+ if selected is None:
244
+ raise RuntimeError('No full-context L8+L7 microbatch fits with memory headroom. Context/architecture were not silently reduced.')
245
+ set_gradient_checkpointing(selected_checkpointing)
246
+ print('GPU preflight:',json.dumps(report),'selected',selected,'checkpointing',selected_checkpointing,flush=True)
247
+ return selected,selected_checkpointing,report
248
+
249
+
250
+ def train_update(engine,model,opt,sc,batch,cfg,device,rank=0,world=1,update_step=0):
251
+ """L8 + sustained fresh-data stage auxiliary; overflow retries are schedule-stable."""
252
+ all_ids,all_w=batch;denom=all_w[:,1:].sum()
253
+ if denom<=0:raise RuntimeError('No supervised targets')
254
+ ids=all_ids.chunk(world,0)[rank];weights=all_w.chunk(world,0)[rank]
255
+ mb=cfg['microbatch'];total=ids.shape[0];aux_weight=float(cfg.get('aux_loop_weight',.20))
256
+ # One exact auxiliary budget per optimizer update, independent of physical
257
+ # microbatch partitioning. Every successful update consumes another global
258
+ # batch; longer Lr stages therefore truly receive more data and updates.
259
+ aux_loops_for_update=auxiliary_loop(update_step,0,cfg)
260
+ for attempt in range(9):
261
+ opt.zero_grad(set_to_none=True);losses=torch.zeros(2,device=device);aux_stats=torch.zeros(2,device=device)
262
+ for micro_index,offset in enumerate(range(0,total,mb)):
263
+ x=ids[offset:offset+mb,:-1];y=ids[offset:offset+mb,1:];m=weights[offset:offset+mb,1:]
264
+ aux_loops=aux_loops_for_update
265
+ for j,(loops,weight) in enumerate([(int(cfg.get('primary_loops',8)),1.0),(aux_loops,aux_weight)]):
266
+ sync=world>1 and offset+mb>=total and j==1
267
+ ctx=engine.no_sync() if world>1 and not sync else nullcontext()
268
+ with ctx:
269
+ with amp(device):
270
+ logits=engine(x,uniform_steps(loops));raw=loss_sum(logits,y,m);loss=raw*(world*weight)/denom
271
+ if not all_true(torch.isfinite(raw).item(),device,world):
272
+ raise RuntimeError('Non-finite forward loss; checkpoint remains at last successful update')
273
+ sc.scale(loss).backward();losses[j]+=raw.detach();del logits,raw,loss
274
+ aux_stats[0]+=float(aux_loops);aux_stats[1]+=1
275
+ sc.unscale_(opt)
276
+ finite=all(p.grad is None or bool(torch.isfinite(p.grad).all()) for p in model.parameters());finite=all_true(finite,device,world)
277
+ if finite:
278
+ norm=torch.nn.utils.clip_grad_norm_(model.parameters(),1.);finite=all_true(torch.isfinite(norm).item(),device,world)
279
+ if finite:
280
+ sc.step(opt);sc.update()
281
+ if world>1:dist.all_reduce(losses);dist.all_reduce(aux_stats)
282
+ return (losses/denom).tolist(),float(norm),attempt,int(denom.item()),float(aux_stats[0]/aux_stats[1])
283
+ sc.update(new_scale=sc.get_scale()*.5)
284
+ if rank==0:print(f'FP16 overflow: retrying the same update at scale {sc.get_scale()}',flush=True)
285
+ raise RuntimeError('FP16 overflow persists after eight retries; no data/step advancement')
286
+
287
+
288
+ @torch.inference_mode()
289
+ def evaluate(model,panel,step,cfg,device):
290
+ model.eval();metrics={}
291
+ full=step%cfg['full_eval_every']==0
292
+ rows=panel if full else [panel[(step//cfg['eval_every']*8+i)%len(panel)] for i in range(min(8,len(panel)))]
293
+ groups={};budgets=range(1,9) if full else (3,6,8)
294
+ for loops in budgets:
295
+ for r in rows:
296
+ x=torch.tensor([r['ids'][:-1]],device=device);y=torch.tensor([r['ids'][1:]],device=device);w=torch.tensor([r['weights'][1:]],device=device,dtype=torch.float32)
297
+ with amp(device):logits=model(x,uniform_steps(loops));loss=loss_sum(logits,y,w)
298
+ name=r['source'];tag=f'eval/{name}/L{loops}'
299
+ if name=='cortex':tag+=f'/{r["meta"]["surface"]}/{r["meta"].get("split","train")}'
300
+ acc=((logits.argmax(-1)==y)*w).sum();bucket=groups.setdefault(tag,[0.,0.,0.]);bucket[0]+=float(loss);bucket[1]+=float(acc);bucket[2]+=float(w.sum())
301
+ chosen=[r for r in panel if r['source']=='cortex' and len(r['ids'])-r['prompt_len']<=128][:4]
302
+ correct=0
303
+ for r in chosen:
304
+ context=list(r['ids'][:r['prompt_len']]);expected=r['ids'][r['prompt_len']:];ok=True
305
+ for target in expected:
306
+ # Keep only the last context window on generated sequences.
307
+ current=context[-cfg['context']:]
308
+ with amp(device):next_id=int(model(torch.tensor([current],device=device),uniform_steps(loops))[0,-1].argmax())
309
+ if next_id!=target:ok=False;break
310
+ context.append(next_id)
311
+ correct+=ok
312
+ if chosen:metrics[f'eval/cortex/greedy_exact_L{loops}']=correct/len(chosen)
313
+ for tag,(loss,correct,total) in groups.items():
314
+ metrics[tag+'/loss']=loss/total;metrics[tag+'/byte_accuracy']=correct/total
315
+ model.train();return metrics
316
+
317
+
318
+ def main(cfg):
319
+ rank=int(os.environ.get('RANK','0'));local=int(os.environ.get('LOCAL_RANK','0'));world=int(os.environ.get('WORLD_SIZE','1'))
320
+ if not torch.cuda.is_available():raise RuntimeError('Training requires a CUDA GPU. CPU mode is only used by packaged tests.')
321
+ torch.cuda.set_device(local);device=torch.device('cuda',local)
322
+ if world>1:dist.init_process_group('nccl',timeout=timedelta(minutes=90))
323
+ cfg['world_size']=world;run=Path(cfg['run_dir']);lock=None
324
+ def acquire_lock():
325
+ nonlocal lock
326
+ lock=open(run/'trainer.lock','w')
327
+ try:fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
328
+ except BlockingIOError:raise RuntimeError('Another trainer is already running here')
329
+ coordinator_call(acquire_lock,rank,world)
330
+ state=torch.load(run/'launch_state.pt',map_location='cpu',weights_only=True)
331
+ random.seed(42);np.random.seed(42);torch.manual_seed(42);torch.cuda.manual_seed(42)
332
+ torch.backends.cuda.matmul.allow_tf32=False;torch.backends.cudnn.allow_tf32=False
333
+ # Keep the mathematically equivalent memory-efficient kernels available. Precision
334
+ # remains FP16 regardless of accelerator family.
335
+ print(f'Rank {rank}: measuring full {cfg["context"]}-byte FP16 L8+L7 backward/Adam memory on {torch.cuda.get_device_name(device)}...',flush=True)
336
+ mb,use_checkpointing,report=memory_preflight(device,cfg)
337
+ m=torch.tensor(mb,device=device,dtype=torch.int32);cp=torch.tensor(int(use_checkpointing),device=device,dtype=torch.int32)
338
+ if world>1:
339
+ dist.all_reduce(m,op=dist.ReduceOp.MIN);dist.all_reduce(cp,op=dist.ReduceOp.MAX)
340
+ cfg['microbatch']=int(m.item());cfg['gradient_checkpointing']=bool(cp.item());set_gradient_checkpointing(cfg['gradient_checkpointing'])
341
+ set_state_noise_sigma(float(cfg.get('state_noise_sigma',0.0)))
342
+ model=BETFog(BETConfig()).to(device);verify_architecture(model,rank)
343
+ opt=optimizer_new(model,cfg);sc=scaler_new()
344
+ if state['model'] is not None:model.load_state_dict(state['model'],strict=True)
345
+ if state['optimizer'] is not None:opt.load_state_dict(state['optimizer'])
346
+ if state['scaler'] is not None:sc.load_state_dict(state['scaler'])
347
+ for values in opt.state.values():
348
+ for k,v in values.items():
349
+ if torch.is_tensor(v) and k!='step':values[k]=v.to(device)
350
+ if state.get('rng_by_rank'):restore_rng(state['rng_by_rank'][rank%len(state['rng_by_rank'])])
351
+ step=state['step'];token_count=state['tokens'];target_count=state['targets'];lineage=state['lineage'];manifest=state['manifest'];committed_mixer=state['mixer'];sha=state['pipeline'];migration=state.get('migration');schedule_migrations=state.get('schedule_migrations',[])
352
+ del state;gc.collect()
353
+ if cfg.get('compile')=='auto':do_compile=torch.cuda.get_device_capability(device)[0]>=8 and not cfg['gradient_checkpointing']
354
+ else:do_compile=bool(cfg.get('compile'))
355
+ if do_compile:
356
+ torch._dynamo.config.suppress_errors=True
357
+ try:
358
+ modules=[*model.prelude,*model.body,*model.coda]
359
+ for layer in modules:layer.compile(dynamic=True)
360
+ print(f'Rank {rank}: physical-block compilation enabled (FP16 unchanged).',flush=True)
361
+ except Exception as e:print('Compile unavailable; eager blocks retained:',type(e).__name__,flush=True)
362
+ engine=DDP(model,device_ids=[local],broadcast_buffers=False,gradient_as_bucket_view=True) if world>1 else model
363
+ mixer=Mixer(manifest,cfg['context'],committed_mixer,mix_weights=cfg.get('mix_weights')).initialize() if rank==0 else None
364
+ panel=torch.load(run/'validation.pt',weights_only=True) if rank==0 else None
365
+ from torch.utils.tensorboard import SummaryWriter
366
+ publisher=Publisher(cfg['repo'],run,checkpoint_prefix=cfg.get('hub_checkpoint_prefix','checkpoints/sparkbet9m'));writer=None;segment=None
367
+ def new_writer():
368
+ nonlocal writer,segment
369
+ segment=run/'publish_logs'/lineage;segment.mkdir(parents=True,exist_ok=True)
370
+ writer=SummaryWriter(str(segment),purge_step=step+1,max_queue=10,flush_secs=30,filename_suffix=f'.step-{step:012d}')
371
+ if rank==0:
372
+ new_writer();atomic_json(run/'gpu_preflight.json',dict(report=report,world=world,microbatch=cfg['microbatch'],gradient_checkpointing=cfg['gradient_checkpointing'],precision='float16'))
373
+ stop=False
374
+ def stop_handler(*_):
375
+ nonlocal stop;stop=True
376
+ signal.signal(signal.SIGINT,stop_handler);signal.signal(signal.SIGTERM,stop_handler)
377
+ start=time.monotonic();last_save=start;last_upload=start;last_logs=start;last_export=start;saved_step=-1
378
+ def checkpoint_now(upload=False):
379
+ nonlocal saved_step,last_save,last_upload
380
+ rng=rng_state();rngs=[None]*world if rank==0 else None
381
+ if world>1:dist.gather_object(rng,rngs,dst=0)
382
+ else:rngs=[rng]
383
+ def do_save():
384
+ nonlocal saved_step,last_save,last_upload
385
+ payload=dict(format=FORMAT,step=step,lineage=lineage,pipeline=sha,config=cfg,manifest=manifest,mixer=committed_mixer,
386
+ rng_by_rank=rngs,tokens=token_count,targets=target_count,model=model.state_dict(),optimizer=opt.state_dict(),scaler=sc.state_dict(),migration=migration,schedule_migrations=schedule_migrations)
387
+ path=save(run,payload,cfg['keep_local']);saved_step=step;last_save=time.monotonic();print(f'Saved full checkpoint: update {step:,}',flush=True)
388
+ if cfg.get('modal_volume_name'):
389
+ try:
390
+ import modal;modal.Volume.from_name(cfg['modal_volume_name']).commit()
391
+ except Exception as e:print('Volume commit failed; Hub upload still attempted:',type(e).__name__,flush=True)
392
+ if upload and cfg['upload']:
393
+ try:publisher.checkpoint(path);last_upload=time.monotonic();print('Full-state Hub upload complete.',flush=True)
394
+ except Exception as e:print('Hub upload failed; local checkpoint is intact:',type(e).__name__,str(e)[:200],flush=True)
395
+ coordinator_call(do_save,rank,world)
396
+ try:
397
+ while True:
398
+ stop_flag=stop or (run/'STOP').exists();should_stop=not all_true(not stop_flag,device,world)
399
+ if should_stop:break
400
+ stage=auxiliary_stage(step,cfg)
401
+ if rank==0 and stage['stage_update']==0:
402
+ examples=int(cfg['global_batch'])*stage['stage_updates']
403
+ print(f'Curriculum cycle {stage["cycle"]}: L8 + L{stage["depth"]} | '
404
+ f'{stage["stage_updates"]:,} fresh batch draws, '
405
+ f'{examples:,} example presentations allocated.',flush=True)
406
+ began=time.monotonic();data=coordinator_call(lambda:mixer.batch(cfg['global_batch']),rank,world)
407
+ shape=broadcast_object(data['ids'].shape if rank==0 else None,rank,world)
408
+ x=torch.as_tensor(data['ids'],device=device) if rank==0 else torch.empty(shape,dtype=torch.long,device=device)
409
+ w=torch.as_tensor(data['weights'],device=device) if rank==0 else torch.empty(shape,dtype=torch.float32,device=device)
410
+ if world>1:dist.broadcast(x,0);dist.broadcast(w,0)
411
+ current_lr=lr_at(step,cfg)
412
+ for pg in opt.param_groups:pg['lr']=current_lr
413
+ losses,norm,overflows,targets,aux_mean=train_update(engine,model,opt,sc,(x,w),cfg,device,rank,world,step)
414
+ step+=1;token_count+=int((x[:,1:]!=256).sum());target_count+=targets
415
+ if rank==0:committed_mixer=mixer.state_dict()
416
+ del x,w,data
417
+ if rank==0:
418
+ elapsed=time.monotonic()-began;aux_weight=float(cfg.get('aux_loop_weight',.20))
419
+ values={'train/loss_L8':losses[0],'train/loss_aux':losses[1],'train/aux_loop_mean':aux_mean,
420
+ 'train/objective':losses[0]+aux_weight*losses[1],'train/lr':current_lr,'train/grad_norm':norm,
421
+ 'train/scaler':sc.get_scale(),'train/overflow_retries':overflows,'train/supervised_tokens':target_count,
422
+ 'train/nonpad_tokens':token_count,'train/update_seconds':elapsed,'train/target_bytes_per_second':targets/elapsed,
423
+ 'train/aux_stage_depth':stage['depth'], 'train/aux_stage_cycle':stage['cycle'],
424
+ 'train/aux_stage_updates_completed':stage['stage_update']+1,
425
+ 'train/aux_stage_updates_budget':stage['stage_updates'],
426
+ 'train/aux_stage_examples_presented':(stage['stage_update']+1)*int(cfg['global_batch']),
427
+ 'train/aux_stage_updates_remaining':stage['stage_remaining']}
428
+ for k,v in values.items():writer.add_scalar(k,v,step)
429
+ if step%cfg['log_every']==0:
430
+ print(f'update {step:,} | L8 {losses[0]:.4f} | aux {losses[1]:.4f} @ L{stage["depth"]} '
431
+ f'({stage["stage_update"]+1}/{stage["stage_updates"]} stage updates) | '
432
+ f'lr {current_lr:.2e} | {elapsed:.1f}s | targets {target_count:,}',flush=True)
433
+ for n,s in mixer.sources.items():writer.add_scalar('data/'+n+'/epochs',s.s['epoch'],step);writer.add_scalar('data/'+n+'/rejected_rows',s.s['rejected'],step)
434
+ atomic_json(run/'metrics_latest.json',dict(step=step,**values))
435
+ if step%cfg['eval_every']==0:
436
+ def evaluate_and_log():
437
+ metrics=evaluate(model,panel,step,cfg,device)
438
+ for k,v in metrics.items():writer.add_scalar(k,v,step)
439
+ atomic_json(run/'eval_latest.json',dict(step=step,**metrics));print('Validation complete:',json.dumps(metrics),flush=True)
440
+ coordinator_call(evaluate_and_log,rank,world)
441
+ actions=None
442
+ if rank==0:
443
+ now=time.monotonic();actions=dict(
444
+ save=step%cfg['save_every']==0 or now-last_save>=cfg['save_seconds'],
445
+ upload=step%cfg['hub_checkpoint_steps']==0,
446
+ logs=now-last_logs>=cfg['hub_log_seconds'],export=now-last_export>=cfg['hub_export_seconds'])
447
+ actions=broadcast_object(actions,rank,world)
448
+ if actions['save'] or actions['upload']:checkpoint_now(actions['upload'])
449
+ if rank==0 and actions['logs']:
450
+ writer.flush();writer.close()
451
+ if cfg['upload']:
452
+ try:publisher.logs()
453
+ except Exception as e:print('Metric upload pending; retry next interval:',type(e).__name__,flush=True)
454
+ new_writer();last_logs=time.monotonic()
455
+ if rank==0 and actions['export'] and cfg['upload']:
456
+ try:publisher.export(model,step,manifest,ROOT);last_export=time.monotonic()
457
+ except Exception as e:print('Inference export pending:',type(e).__name__,str(e)[:200],flush=True)
458
+ except BaseException:
459
+ if rank==0:
460
+ print('Training failed. Recover from the last completed checkpoint; no partial update is published.',flush=True)
461
+ if writer:writer.flush();writer.close()
462
+ if mixer:mixer.close()
463
+ raise
464
+ else:
465
+ if rank==0:print('Final checkpoint and metric flush...',flush=True)
466
+ checkpoint_now(upload=True)
467
+ if rank==0:
468
+ writer.flush();writer.close()
469
+ if cfg['upload']:
470
+ try:publisher.logs();publisher.export(model,step,manifest,ROOT)
471
+ except Exception as e:print('Final publication incomplete; local state preserved:',type(e).__name__,str(e)[:200],flush=True)
472
+ mixer.close()
473
+ if world>1:dist.destroy_process_group()
474
+
475
+
476
+ if __name__=='__main__':
477
+ import argparse
478
+ parser=argparse.ArgumentParser();parser.add_argument('--config',required=True);parser.add_argument('--prepare',action='store_true');args=parser.parse_args()
479
+ cfg=json.loads(Path(args.config).read_text())
480
+ if args.prepare:prepare(cfg)
481
+ else:main(cfg)
unified/ablations/unified-v1-blt-none-s42/source/unified_data.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Coverage-preserving four-family data stream. No permanent train exclusions."""
2
+ import copy,fnmatch,hashlib,json,math
3
+ from collections import Counter
4
+ from functools import lru_cache
5
+ import numpy as np
6
+ from dataset_registry import SOURCES,retry
7
+ from data_stream import Source,Mixer,row_key
8
+ from cortex_source import CortexSource,episode_pair,FAMILIES
9
+ from records import BOS,EOS,PAD,validate,record,plain_chunks,InvalidRecord
10
+
11
+ GROUPS=('climbmix','ultrafine','cortex','rewrite6')
12
+ DEFAULT_WEIGHTS=dict(climbmix=50,ultrafine=30,cortex=15,rewrite6=5)
13
+
14
+ def group_of(name): return 'ultrafine' if name.startswith('ultra_') else name
15
+
16
+ def complete_manifest(old=None,api=None):
17
+ """Retain old pinned revisions/cursors; add missing partitions at that revision."""
18
+ from huggingface_hub import HfApi
19
+ from dataclasses import asdict
20
+ api=api or HfApi();old={k:v for k,v in (old or {}).items() if k in SOURCES};revisions={s['repo']:s['revision'] for s in old.values()}
21
+ info={};result={}
22
+ for name,spec in SOURCES.items():
23
+ if spec.repo not in info:
24
+ info[spec.repo]=retry(lambda:api.dataset_info(spec.repo,revision=revisions.get(spec.repo)))
25
+ entry=info[spec.repo]
26
+ files=sorted(f.rfilename for f in entry.siblings if any(fnmatch.fnmatch(f.rfilename,p) for p in spec.patterns))
27
+ if not files:raise RuntimeError(f'{name}: no matching shards at pinned revision {entry.sha}')
28
+ if name in old:
29
+ if set(old[name]['files'])-set(files):raise RuntimeError(f'{name}: saved shards missing at pinned revision')
30
+ result[name]=copy.deepcopy(old[name])
31
+ # Preserve the old shard-index meaning. Newly discovered shards append.
32
+ result[name]['files']=list(old[name]['files'])+[f for f in files if f not in old[name]['files']]
33
+ else: result[name]={**asdict(spec),'revision':entry.sha,'files':files}
34
+ return result
35
+
36
+ @lru_cache(maxsize=64)
37
+ def quota_tuple(count,weights):
38
+ """Closest integer allocation with strict priority and every family present."""
39
+ if count<10:raise ValueError('global_batch must be >=10 for strict climbmix > ultrafine > cortex > rewrite6 quotas')
40
+ target=np.array(weights,float)*count/sum(weights);best=None;score=float('inf')
41
+ for rewrite in range(1,count//4+1):
42
+ for cortex in range(rewrite+1,count//3+1):
43
+ for ultra in range(cortex+1,count//2+1):
44
+ climb=count-rewrite-cortex-ultra
45
+ if climb<=ultra:continue
46
+ q=(climb,ultra,cortex,rewrite);loss=float(((np.array(q)-target)**2).sum())
47
+ if loss<score:score=loss;best=q
48
+ if best is None:raise ValueError('Cannot satisfy per-batch source priority')
49
+ return best
50
+
51
+ class CoverageSource(Source):
52
+ def initialize(self):
53
+ # Legacy held-out rows rejoin the stream when their shard is visited again.
54
+ # Never reset a partially consumed shard merely to change the mixture.
55
+ self.s['initialized']=True
56
+ self.s.setdefault('pending_document',None)
57
+ self.s.setdefault('documents_emitted',0)
58
+ def _chunk(self):
59
+ doc=self.s['pending_document'];raw=doc['bytes'];offset=doc['offset'];total=len(raw)+2
60
+ stop=min(total,offset+self.limit+1)
61
+ ids=([BOS] if offset==0 else [])+list(raw[max(0,offset-1):min(len(raw),stop-1)])+([EOS] if stop==total else [])
62
+ r=dict(ids=ids,weights=[0]+[1]*(len(ids)-1),source=self.name,prompt_len=1,
63
+ meta={'row_key':doc['key'],'chunk_offset':offset,'coverage':'lossless'})
64
+ doc['offset']+=self.limit
65
+ if doc['offset']>=total-1:
66
+ self.s['pending_document']=None;self.s['documents_emitted']+=1
67
+ return validate(r,self.limit)
68
+ def next_record(self,excluded=()):
69
+ if self.s['pending']:return self.s['pending'].pop(0) # Preserve legacy buffered records.
70
+ if self.s.get('pending_document') is not None:return self._chunk()
71
+ row=self.raw();key=row_key(self.spec,row)
72
+ try:
73
+ if self.spec['kind']=='rewrite':
74
+ values=[row.get(k) for k in ('instruction','text','output')]
75
+ if not all(isinstance(v,str) for v in values):raise InvalidRecord('instruction/text/output must be strings')
76
+ text='\n\n'.join(values)
77
+ else:
78
+ text=row.get(self.spec['column'])
79
+ if not isinstance(text,str):raise InvalidRecord('missing string column '+self.spec['column'])
80
+ raw=text.encode('utf-8') # Empty/whitespace rows still contribute their EOS.
81
+ except (ValueError,TypeError,UnicodeError) as e:
82
+ raise RuntimeError(f'{self.name}: invalid row at shard={self.s["shard"]}, cursor={self.s["cursor"]}; no sample was silently skipped. Fix the schema, then resume the last complete checkpoint.') from e
83
+ self.s['accepted']+=1
84
+ self.s['pending_document']={'bytes':raw,'offset':0,'key':key}
85
+ return self._chunk()
86
+
87
+ class CodecCortex:
88
+ """Add deterministic graphics within Cortex's share; never skip a task index."""
89
+ def __init__(self,state=None,limit=1024,codec='none'):
90
+ state=state or {};self.codec=codec;self.limit=limit
91
+ self.base=CortexSource(state.get('kernel',state if 'index' in state else None),limit)
92
+ self.visual_index=state.get('visual_index',0);self.until_visual=state.get('until_visual',3)
93
+ def record(self):
94
+ if self.codec=='none' or self.until_visual:
95
+ self.until_visual=max(0,self.until_visual-1);return self.base.record()
96
+ from canvas_flow import draw_picture,FAMILIES as GRAPHICS,pack_braille
97
+ rng=np.random.default_rng(92000+self.visual_index);family=GRAPHICS[self.visual_index%len(GRAPHICS)]
98
+ bitmap=draw_picture(rng,family,48)
99
+ if self.codec=='braille':
100
+ cells=pack_braille(bitmap);art='\n'.join(''.join(chr(0x2800+int(v)) for v in row) for row in cells)
101
+ elif self.codec=='ascii':
102
+ density=bitmap.reshape(12,4,24,2).mean((1,3));ramp=' .:-=+*#%@'
103
+ art='\n'.join(''.join(ramp[int(round(v*9))] for v in row) for row in density)
104
+ else:raise ValueError(self.codec)
105
+ r=record(f'Image encoding={self.codec}; name the drawing.\n{art}\nAnswer: ',family,'cortex',self.limit,
106
+ meta={'family':'visual_classification','codec':self.codec,'bitmap_seed':self.visual_index})
107
+ self.visual_index+=1;self.until_visual=3;return r
108
+ def state_dict(self):return dict(kernel=self.base.state_dict(),visual_index=self.visual_index,until_visual=self.until_visual)
109
+ def validation(self):
110
+ rows=self.base.validation()
111
+ if self.codec!='none':
112
+ other=CodecCortex({'visual_index':100000,'until_visual':0},self.limit,self.codec)
113
+ for _ in range(10):other.until_visual=0;rows.append(other.record())
114
+ return rows
115
+
116
+ class CoverageMixer(Mixer):
117
+ def __init__(self,manifest,limit=1024,state=None,loaders=None,group_weights=None,codec='none'):
118
+ super().__init__(manifest,limit,state,loaders,cortex=False)
119
+ state=state or {}
120
+ self.sources={n:CoverageSource(n,s,state.get('sources',{}).get(n),limit,(loaders or {}).get(n)) for n,s in manifest.items()}
121
+ self.cortex=CodecCortex(state.get('cortex'),limit,codec)
122
+ self.group_weights=dict(group_weights or state.get('group_weights') or DEFAULT_WEIGHTS)
123
+ if set(self.group_weights)!=set(GROUPS) or not all(v>0 for v in self.group_weights.values()):raise ValueError('Exactly four positive source weights are required')
124
+ self.groups={g:[n for n in self.sources if group_of(n)==g] for g in GROUPS};self.groups['cortex']=['cortex']
125
+ if any(not n for n in self.groups.values()):raise ValueError('A required dataset family is missing')
126
+ self.group_cursor=dict(state.get('group_cursor',{}));self.codec=codec;self.excluded=set()
127
+ self.pending={n:r for n,r in self.pending.items() if n in manifest or n=='cortex'}
128
+ def initialize(self):
129
+ for s in self.sources.values():s.initialize()
130
+ # Do not consume a row merely to build a validation set.
131
+ return self
132
+ def batch(self,count):
133
+ quotas=quota_tuple(count,tuple(self.group_weights[g] for g in GROUPS))
134
+ schedule=sorted((i/q,g) for g,q in zip(GROUPS,quotas) for i in range(q))
135
+ blocks=[]
136
+ for _,group in schedule:
137
+ members=self.groups[group];cursor=self.group_cursor.get(group,0)
138
+ name=members[cursor%len(members)];self.group_cursor[group]=cursor+1
139
+ # One requested source per block; old buffered records stay attached to it.
140
+ self.mix_queue=[name];blocks.append(self.block())
141
+ width=min(self.limit+1,1+math.ceil(max(b[3]-1 for b in blocks)/128)*128)
142
+ return dict(ids=np.asarray([b[0][:width] for b in blocks],np.int64),
143
+ weights=np.asarray([b[1][:width] for b in blocks],np.float32),sources=[b[2] for b in blocks])
144
+ def state_dict(self):
145
+ s=super().state_dict();s.update(group_weights=self.group_weights,group_cursor=self.group_cursor,
146
+ coverage_version=1,codec=self.codec,excluded=[])
147
+ return copy.deepcopy(s)
148
+ def validation(self):
149
+ # Language probes are illustrative, external to the streamed corpora;
150
+ # do not call them a representative held-out web benchmark.
151
+ probes=[record('','The river flows past a quiet village.','language_probe',self.limit,supervise_all=True),
152
+ record('','A careful experiment changes one condition at a time.','language_probe',self.limit,supervise_all=True)]
153
+ return probes+self.cortex.validation()
154
+ def coverage(self):
155
+ return {n:dict(epoch=s.s['epoch'],shard=s.s['shard'],shards=len(s.spec['files']),rows_read=s.s['rows'],
156
+ documents_emitted=s.s.get('documents_emitted',0),pending_document=s.s.get('pending_document') is not None,
157
+ buffered_records=len(s.s['pending'])+int(n in self.pending)) for n,s in self.sources.items()}
unified/ablations/unified-v1-blt-none-s42/source/unified_models.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Explicit experiment registry; no recurrent Ouro candidate."""
2
+ from dataclasses import asdict
3
+ import numpy as np
4
+ import torch
5
+ from torch import nn
6
+ import torch.nn.functional as F
7
+ from bet_model import BETFog,BETConfig,uniform_steps,verify_architecture
8
+ from causal_arch import ModelCfg,build_model,forward_model,targets_and_mask
9
+
10
+ CAUSAL=('sparkbet','ouro_1pass','evabyte','blt','byteflow_causal')
11
+ FLOW=('canvas_full','canvas_no_flow','canvas_raster','canvas_patch8')
12
+ VARIANTS=CAUSAL+FLOW
13
+
14
+ def build(variant,context=1024,seed=42):
15
+ if variant not in VARIANTS:raise ValueError(f'Unsupported variant {variant}; recurrent ouro is removed')
16
+ torch.manual_seed(seed)
17
+ if variant=='sparkbet':
18
+ m=BETFog(BETConfig());verify_architecture(m,1)
19
+ return m,dict(variant=variant,config=asdict(m.c),fidelity='checkpoint-compatible SparkBET, one physical pass')
20
+ if variant in FLOW:
21
+ from canvas_flow import Config,CanvasModel
22
+ v=variant.removeprefix('canvas_');v='full' if v=='full' else v
23
+ c=Config(text_len=context+2,variant=v,patch=8 if v=='patch8' else 4)
24
+ m=nn.ModuleDict({'content':CanvasModel(c),'planner':CanvasModel(Config(**{**asdict(c),'dim':128,'depth':3,'heads':4}),True)})
25
+ return m,dict(variant=variant,config=asdict(c),fidelity='custom absorbing discrete flow; separate planner and content')
26
+ c=ModelCfg(dim=320,ff=864,heads=5,context=context,body_layers=6,loops=1,
27
+ local_dim=128,local_ff=384,local_heads=2,encoder_layers=1,decoder_layers=1,
28
+ local_window=128,eva_window=256,eva_chunk=16,hash_buckets=1024,
29
+ entropy_dim=64,entropy_ff=192,entropy_heads=1,entropy_layers=2,grad_checkpoint=False)
30
+ m,meta=build_model(variant,c,seed)
31
+ return m,dict(variant=variant,config=asdict(c),**meta)
32
+
33
+ def denominators(ids,weights,variant):
34
+ n=8 if variant=='evabyte' else 1
35
+ return torch.stack([(targets_and_mask(ids,j+1)[1]*weights[:,j+1:]).sum().clamp_min(1) for j in range(n)])
36
+
37
+ def causal_loss(model,variant,ids,weights,denom):
38
+ out={'logits':model(ids[:,:-1],uniform_steps(1))} if variant=='sparkbet' else forward_model(model,ids[:,:-1],variant)
39
+ heads=out.get('heads') or [out['logits']];loss=out['logits'].float().sum()*0
40
+ primary=None
41
+ for j,logits in enumerate(heads):
42
+ target,valid=targets_and_mask(ids,j+1);mask=valid*weights[:,j+1:]
43
+ ce=F.cross_entropy(logits[:,:target.shape[1]].float().flatten(0,1),target.flatten(),reduction='none').view_as(target)
44
+ raw=(ce*mask).sum();loss=loss+raw/denom[j]/len(heads)
45
+ if j==0:primary=raw.detach()/denom[0]
46
+ return loss,primary
47
+
48
+ def canvas_batch(data,step,context,codec='braille',seed=42,variant='canvas_full'):
49
+ """Every real input byte is retained; only Cortex contributes generated images.
50
+
51
+ Bytes (including UTF-8 continuation bytes) have individual flow positions.
52
+ Display code must UTF-8-decode the reconstructed linguistic lane. Spatial
53
+ byte slots are not Unicode glyph cells. Do not claim browser-grade shaping.
54
+ """
55
+ from canvas_flow import make_example,capacity_map,ARRAY_KEYS,TEXT,IMAGE,RESERVED,FREE,EOS,PAD,POLICIES
56
+ rows=[]
57
+ for j,(ids,source) in enumerate(zip(data['ids'],data['sources'])):
58
+ index=step*len(data['ids'])+j
59
+ # Non-Cortex content is text-only; preserve all corpus bytes, no filtering.
60
+ e=make_example(index,'train',128,context+2,192,seed,encoding_override=(codec=='ascii'))
61
+ # Preserve packed-document separation without introducing an early flow EOS.
62
+ lane=[int(x) if x<256 else 10 for x in ids if 0<=x<256 or x==258]
63
+ raw=np.asarray(lane,np.int16)
64
+ visual=source=='cortex' and index%3!=0
65
+ if not visual:e['image'].fill(0);e['layout'].fill(TEXT)
66
+ else:
67
+ e['layout'][e['layout']==FREE]=TEXT
68
+ # Image-only pages are additional Cortex presentations, never replace a
69
+ # corpus/Cortex task's bytes. The paired task bytes stay on this page.
70
+ if (e['layout']==TEXT).sum()<len(raw)+1:raise RuntimeError('Canvas capacity exhausted; no bytes truncated')
71
+ text=np.full(context+2,PAD,np.int16);text[:len(raw)]=raw;text[len(raw)]=EOS;e['text']=text
72
+ if variant=='canvas_raster':e['order']=np.int64(POLICIES.index('row'))
73
+ e['coords'],e['regions'],e['capacity']=capacity_map(e['layout'],context+2,POLICIES[int(e['order'])])
74
+ geometry='; '.join(f'{o["family"]} at {o["x"]},{o["y"]}' for o in e['meta']['objects']) if visual else ''
75
+ prompt=f'{source}; {"mixed" if visual else "text"}; {"ASCII" if e["encoding"] else "Braille"}; order {POLICIES[int(e["order"])]}; margin {e["meta"]["margin"]}; {geometry}'.encode()[:192]
76
+ e['prompt'].fill(PAD);e['prompt'][:len(prompt)]=list(prompt)
77
+ rows.append(e)
78
+ if visual and index%3==1:
79
+ # Additional image-only page: the original task bytes above are kept.
80
+ import copy
81
+ picture=copy.deepcopy(e);picture['layout'][picture['layout']==TEXT]=FREE
82
+ picture['text'].fill(PAD);picture['text'][0]=EOS
83
+ picture['coords'].fill(0);picture['regions'].fill(0);picture['capacity'].fill(False)
84
+ rows.append(picture)
85
+ return {k:torch.from_numpy(np.stack([e[k] for e in rows])).long() for k in ARRAY_KEYS}
86
+
87
+ def flow_loss(model,batch):
88
+ from canvas_flow import make_loss
89
+ content=make_loss(model['content'],batch)
90
+ planner=make_loss(model['planner'],batch,planner=True)
91
+ return content+planner,content.detach()
unified/ablations/unified-v1-blt-none-s42/source/unified_runner.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Single-GPU Run All entry point: bounded screens or continuous main training."""
2
+ import contextlib,copy,csv,fcntl,json,math,os,random,signal,time,uuid
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import torch
6
+ from checkpoints import pipeline_hash,atomic_json
7
+ from unified_data import CoverageMixer,complete_manifest,DEFAULT_WEIGHTS
8
+ from unified_models import build,CAUSAL,FLOW,denominators,causal_loss,canvas_batch,flow_loss
9
+ from unified_state import Store,save,ledger,fingerprint,FORMAT,LEGACY_FORMAT
10
+ from trainer import rng_state,restore_rng
11
+
12
+ ROOT=Path(__file__).parent
13
+
14
+ def lr_at(step,cfg,plan):
15
+ value=cfg['lr']*min(1.,(step+1)/max(1,cfg['warmup_steps']))
16
+ end=plan['end_step'];cool=plan['cooldown_steps']
17
+ if end is not None and cool and step>=end-cool:
18
+ # Last optimizer update has the requested floor; no off-by-one zero step.
19
+ fraction=(end-1-step)/max(1,cool-1)
20
+ value*=cfg['min_lr_ratio']+(1-cfg['min_lr_ratio'])*max(0.,fraction)
21
+ return value
22
+
23
+ def plan_for(start,steps,cooldown):
24
+ if cooldown<0:raise ValueError('Cooldown must be nonnegative')
25
+ if steps is not None and steps<1:raise ValueError('Finite steps must be positive; use None for infinite')
26
+ return dict(start_step=start,end_step=None if steps is None else start+steps,
27
+ cooldown_steps=0 if steps is None else min(steps,int(cooldown)))
28
+
29
+ def finite_optimizer(opt):
30
+ return all(not torch.is_tensor(v) or bool(torch.isfinite(v).all()) for s in opt.state.values() for v in s.values())
31
+
32
+ def update(model,opt,data,variant,cfg,device,step,codec):
33
+ """Retry SAME batch + RNG: BF16/FP16 scales -> FP32, OOM -> smaller microbatch.
34
+
35
+ No optimizer mutation on a failed backward. Invalid Adam updates restore the
36
+ pre-update parameters and moments, then retry at a lower learning rate.
37
+ """
38
+ saved_rng=rng_state();micro=min(cfg['microbatch'],len(data['ids']))
39
+ ids=torch.as_tensor(data['ids'],device=device);weights=torch.as_tensor(data['weights'],device=device)
40
+ flow=variant in FLOW
41
+ mapped=(data.get('canvas') or canvas_batch(data,step,cfg['context'],codec,cfg['seed'],variant)) if flow else None
42
+ batch_count=len(mapped['image']) if flow else len(ids)
43
+ denom=None if flow else denominators(ids,weights,variant)
44
+ modes=[('bf16',1.)] if device.type=='cuda' and torch.cuda.is_bf16_supported() else ([('fp16',s) for s in cfg['fallback_scales']] if device.type=='cuda' else [])
45
+ modes += [('fp32',1.),('fp32',1.)]
46
+ mode_index=0;attempt=0;lr_backoff=1.
47
+ initial_lrs=[p['lr'] for p in opt.param_groups]
48
+ while mode_index<len(modes):
49
+ precision,scale=modes[mode_index];restore_rng(saved_rng);opt.zero_grad(set_to_none=True)
50
+ total=0.;primary=0.;failed=False;oom=False
51
+ try:
52
+ for start in range(0,batch_count,micro):
53
+ end=min(batch_count,start+micro)
54
+ with torch.autocast(device.type,dtype=torch.bfloat16 if precision=='bf16' else torch.float16,enabled=precision!='fp32'):
55
+ if flow:
56
+ batch={k:v[start:end].to(device) for k,v in mapped.items()}
57
+ loss,one=flow_loss(model,batch);factor=(end-start)/batch_count;loss=loss*factor;one=one*factor
58
+ else:loss,one=causal_loss(model,variant,ids[start:end],weights[start:end],denom)
59
+ if not bool(torch.isfinite(loss)):failed=True;break
60
+ (loss*scale).backward();total+=float(loss.detach());primary+=float(one)
61
+ if not failed:
62
+ for p in model.parameters():
63
+ if p.grad is not None:p.grad.div_(scale)
64
+ failed=not all(p.grad is None or bool(torch.isfinite(p.grad).all()) for p in model.parameters())
65
+ if not failed:
66
+ norm=torch.nn.utils.clip_grad_norm_(model.parameters(),cfg['clip_grad'])
67
+ failed=not bool(torch.isfinite(norm))
68
+ except torch.cuda.OutOfMemoryError:
69
+ oom=True;failed=True
70
+ if not failed:
71
+ # ~9M parameter screen: CPU rollback copies are cheap relative to a
72
+ # corrupt long-running optimizer. Disable only with an explicit choice.
73
+ snapshot={k:v.detach().cpu().clone() for k,v in model.state_dict().items()}
74
+ moments=copy.deepcopy(opt.state_dict())
75
+ for group,lr in zip(opt.param_groups,initial_lrs):group['lr']=lr*lr_backoff
76
+ opt.step()
77
+ good=all(bool(torch.isfinite(p).all()) for p in model.parameters()) and finite_optimizer(opt)
78
+ if good:return dict(loss=primary,objective=total,grad_norm=float(norm),retry_count=attempt,precision=precision,
79
+ targets=int(weights[:,1:].sum()),microbatch=micro,lr_backoff=lr_backoff)
80
+ model.load_state_dict(snapshot);opt.load_state_dict(moments);lr_backoff*=.25
81
+ attempt+=1;opt.zero_grad(set_to_none=True)
82
+ if oom:
83
+ if device.type=='cuda':torch.cuda.empty_cache()
84
+ if micro==1:raise RuntimeError('OOM at microbatch 1; checkpoint is intact. Use canvas_patch8 or a smaller model.')
85
+ micro=max(1,micro//2)
86
+ else:mode_index+=1
87
+ print(f'Retrying unchanged batch: attempt={attempt}, microbatch={micro}, next precision={modes[min(mode_index,len(modes)-1)][0]}, lr multiplier={lr_backoff}',flush=True)
88
+ restore_rng(saved_rng)
89
+ raise RuntimeError('Numerical fallbacks exhausted; no data cursor committed. Resume the last complete checkpoint.')
90
+
91
+ @torch.no_grad()
92
+ def evaluate(model,variant,panel,cfg,device,step,codec):
93
+ """Fixed external Cortex/probe panel; not held-out web perplexity."""
94
+ was=model.training;model.eval();saved=rng_state();torch.manual_seed(97031)
95
+ losses=[]
96
+ try:
97
+ for r in panel:
98
+ data=dict(ids=np.asarray([r['ids']],np.int64),weights=np.asarray([r['weights']],np.float32),sources=[r['source']])
99
+ if variant in FLOW:
100
+ batch={k:v.to(device) for k,v in canvas_batch(data,0,cfg['context'],codec,seed=97031,variant=variant).items()}
101
+ loss,_=flow_loss(model,batch)
102
+ else:
103
+ x=torch.as_tensor(data['ids'],device=device);w=torch.as_tensor(data['weights'],device=device)
104
+ _,loss=causal_loss(model,variant,x,w,denominators(x,w,variant))
105
+ losses.append(float(loss))
106
+ return sum(losses)/len(losses)
107
+ finally:restore_rng(saved);model.train(was)
108
+
109
+ def patcher_stage(model,mixer,cfg,device,store):
110
+ """Train an independent entropy model; persist progress and train-only threshold.
111
+
112
+ Its extra data/compute are reported separately. Main ablations restart the
113
+ same data anchor afterward, so patcher preparation does not shift BLT's data.
114
+ """
115
+ stage_began=time.monotonic();previous_seconds=0.
116
+ path=store.run/'patcher.pt';patcher=model.patcher;patcher.requires_grad_(True)
117
+ opt=torch.optim.AdamW(patcher.parameters(),lr=cfg['lr'],betas=(.9,.95));start=0
118
+ if not path.exists() and store.cfg['hub_resume'] and store.prefix+'/patcher.pt' in store.remote_files:
119
+ from huggingface_hub import hf_hub_download
120
+ import shutil
121
+ f=hf_hub_download(store.repo,store.prefix+'/patcher.pt',revision=store.head)
122
+ shutil.copy2(f,path)
123
+ if path.exists():
124
+ state=torch.load(path,map_location='cpu',weights_only=True)
125
+ if state['budget']!=cfg['patcher_steps']:raise ValueError('Patcher budget differs')
126
+ if state.get('signature')!=store.signature or state.get('code_sha')!=pipeline_hash(ROOT):raise ValueError('Patcher configuration/code differs')
127
+ if state.get('manifest')!=mixer.manifest:raise ValueError('Patcher dataset revision differs; use its original data anchor')
128
+ patcher.load_state_dict(state['model']);opt.load_state_dict(state['optimizer']);start=state['step'];previous_seconds=state.get('patcher_seconds',0.)
129
+ if hasattr(mixer,'archive'):
130
+ from prepared_data import PreparedMixer
131
+ mixer=PreparedMixer(mixer.manifest,cfg['context'],state['mixer'],group_weights=cfg['group_weights'],archive=mixer.archive).initialize()
132
+ else:mixer=CoverageMixer(mixer.manifest,cfg['context'],state['mixer'],group_weights=cfg['group_weights']).initialize()
133
+ restore_rng(state['rng'])
134
+ if hasattr(mixer,'archive'):
135
+ from prepared_data import ShardPrefetcher
136
+ mixer=ShardPrefetcher(mixer,cfg,store.run_id+'-patcher',start,cfg['patcher_steps']-start+4,observations=state.get('prepared_observations') if path.exists() else None)
137
+ mixer.prewarm()
138
+ for i in range(start,cfg['patcher_steps']):
139
+ data=mixer.batch(cfg['global_batch']);update(patcher,opt,data,'entropy',cfg,device,i,'none')
140
+ if hasattr(mixer,'commit_batch'):mixer.commit_batch()
141
+ if (i+1)%cfg['save_every']==0 or i+1==cfg['patcher_steps']:
142
+ temp=path.with_suffix('.tmp');torch.save(dict(step=i+1,budget=cfg['patcher_steps'],patcher_seconds=previous_seconds+time.monotonic()-stage_began,signature=store.signature,code_sha=pipeline_hash(ROOT),manifest=mixer.manifest,model=patcher.state_dict(),optimizer=opt.state_dict(),mixer=mixer.state_dict(),prepared_observations=copy.deepcopy(getattr(mixer,'observations',{})),rng=rng_state()),temp);os.replace(temp,path)
143
+ if hasattr(mixer,'flush_observed'):mixer.flush_observed(store.run_id+'-patcher','entropy-pretraining',i+1,path)
144
+ if store.cfg['upload']:
145
+ from huggingface_hub import CommitOperationAdd
146
+ operations=[CommitOperationAdd(path_in_repo=store.prefix+'/patcher.pt',path_or_fileobj=str(path))]
147
+ anchor=store.root/(cfg['experiment_id']+'-anchor.pt')
148
+ if anchor.exists():operations.append(CommitOperationAdd(path_in_repo='unified/experiments/'+cfg['experiment_id']+'/anchor.pt',path_or_fileobj=str(anchor)))
149
+ result=store.api.create_commit(repo_id=store.repo,parent_commit=store.head,operations=operations,commit_message=f'{store.run_id}: entropy patcher {i+1}')
150
+ store.head=result.oid
151
+ patcher.eval();values=[]
152
+ for _ in range(4):
153
+ data=mixer.batch(cfg['global_batch']);x=torch.as_tensor(data['ids'],device=device)[:,:-1]
154
+ if hasattr(mixer,'commit_batch'):
155
+ mixer.pending_event['phase']='entropy_calibration';mixer.commit_batch()
156
+ for chunk in x.split(cfg['microbatch']):
157
+ ent=patcher.entropy(chunk);values.append(ent[chunk<256].cpu())
158
+ values=torch.cat(values)
159
+ if not len(values):raise RuntimeError('Empty entropy calibration')
160
+ patcher.threshold.fill_(float(torch.quantile(values,1-1/model.c.patch_size)))
161
+ patcher.requires_grad_(False)
162
+ if hasattr(mixer,'flush_observed'):mixer.flush_observed(store.run_id+'-calibration','entropy-calibration',cfg['patcher_steps'],path)
163
+ mixer.close()
164
+ return previous_seconds+time.monotonic()-stage_began
165
+
166
+ def run_one(cfg,variant,codec,seed,main=False,anchor=None):
167
+ if variant in FLOW and codec not in ('braille','ascii'):raise ValueError('Canvas codec must be braille or ascii')
168
+ if codec not in ('none','braille','ascii'):raise ValueError('Unknown codec')
169
+ cfg={**cfg,'seed':seed};run_id=f'main-{variant}-{codec}' if main else f'{cfg["experiment_id"]}-{variant}-{codec}-s{seed}'
170
+ # Hyperparameters that change a trial's meaning are immutable on resume.
171
+ semantic={k:cfg[k] for k in ('context','global_batch','lr','warmup_steps','group_weights','clip_grad','patcher_steps')}
172
+ signature=fingerprint(dict(variant=variant,codec=codec,seed=seed,semantic=semantic,version=1))
173
+ store=Store(cfg,run_id,signature,main);state=store.latest();store.restore_ledger()
174
+ promoted=False
175
+ if main and state is None and cfg.get('train_from_run_id'):
176
+ origin=Store(cfg,cfg['train_from_run_id'],signature,False);state=origin.latest()
177
+ if state is None:raise RuntimeError('Requested ablation checkpoint was not found')
178
+ state=copy.deepcopy(state);state['initialization']='promoted:'+cfg['train_from_run_id'];state['lineage']=uuid.uuid4().hex;promoted=True
179
+ random.seed(seed);np.random.seed(seed);torch.manual_seed(seed)
180
+ device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
181
+ if device.type!='cuda' and not cfg.get('allow_cpu_test'):raise RuntimeError('Select a Molab RTX PRO 6000 GPU runtime before Run All')
182
+ if device.type=='cuda':
183
+ print(torch.cuda.get_device_name(),flush=True)
184
+ torch.backends.cuda.matmul.allow_tf32=True;torch.backends.cudnn.allow_tf32=True
185
+ model,meta=build(variant,cfg['context'],seed);model=model.to(device)
186
+ legacy=state is not None and state['format']==LEGACY_FORMAT
187
+ if state and not legacy and state['code_sha']!=pipeline_hash(ROOT):raise RuntimeError('Code fingerprint differs; use a new experiment ID or explicitly migrate the implementation')
188
+ manifest=complete_manifest(state['manifest'] if state else (anchor or {}).get('manifest'))
189
+ mix_state=state['mixer'] if state else (anchor or {}).get('mixer')
190
+ if state and state.get('prepared_dataset') and not cfg.get('prepared_shards',False):raise ValueError('This checkpoint uses prepared record cursors; keep PREPARED_SHARDS enabled')
191
+ archive=None
192
+ if cfg.get('prepared_shards',False):
193
+ from prepared_data import Archive,PreparedMixer,ShardPrefetcher
194
+ archive=Archive(cfg)
195
+ mixer=PreparedMixer(manifest,cfg['context'],mix_state,group_weights=cfg['group_weights'],codec=codec,archive=archive).initialize()
196
+ else:mixer=CoverageMixer(manifest,cfg['context'],mix_state,group_weights=cfg['group_weights'],codec=codec).initialize()
197
+ start=state['step'] if state else 0;step=start
198
+ if state and state['config']['context']!=cfg['context']:raise ValueError('Resume context differs; buffered stream records must keep their original context')
199
+ if state:model.load_state_dict(state['model'],strict=True)
200
+ initialization=state.get('initialization','migrated_attached_sparkbet') if state else 'fresh_matched_seed'
201
+ print(f'{run_id}: {initialization}; {sum(p.numel() for p in model.parameters()):,} parameters; one pass',flush=True)
202
+ patcher_seconds=state.get('patcher_seconds',0.) if state else 0.
203
+ if variant=='blt' and not state:
204
+ if cfg['patcher_steps']<1:raise ValueError('BLT requires a positive entropy-patcher training budget')
205
+ pm=PreparedMixer(manifest,cfg['context'],mix_state,group_weights=cfg['group_weights'],archive=archive).initialize() if archive else CoverageMixer(manifest,cfg['context'],mix_state,group_weights=cfg['group_weights']).initialize()
206
+ patcher_seconds=patcher_stage(model,pm,cfg,device,store)
207
+ # Restore the trial's initialization RNG after preparation (patcher saved).
208
+ random.seed(seed);torch.manual_seed(seed)
209
+ opt=torch.optim.AdamW([p for p in model.parameters() if p.requires_grad],lr=cfg['lr'],betas=(.9,.95),eps=1e-8,weight_decay=.1,
210
+ fused=(device.type=='cuda'))
211
+ if state:opt.load_state_dict(state['optimizer'])
212
+ if state:
213
+ rng=state.get('rng') or (state.get('rng_by_rank') or [None])[0]
214
+ if rng:restore_rng(rng)
215
+ steps=cfg['train_steps'] if main else cfg['ablation_steps']
216
+ plan=state['plan'] if state and not legacy and not promoted else plan_for(start,steps,cfg['cooldown_steps'])
217
+ if state and not legacy and not promoted:
218
+ if main and ((steps is None)!=(plan['end_step'] is None)):
219
+ print('Explicit main schedule change: keeping weights, optimizer and all data cursors',flush=True)
220
+ plan=plan_for(start,steps,cfg['cooldown_steps'])
221
+ expected=None if steps is None else steps
222
+ saved_budget=None if plan['end_step'] is None else plan['end_step']-plan['start_step']
223
+ if expected!=saved_budget:raise ValueError('Saved run budget differs. Resume with its original budget; use a new experiment ID for a new trial.')
224
+ lineage=state['lineage'] if state else uuid.uuid4().hex
225
+ targets=state.get('targets',0) if state else 0;seconds=state.get('seconds',0.) if state else 0.
226
+ measured_targets=state.get('measured_targets',0) if state else 0
227
+ if archive and (plan['end_step'] is None or step<plan['end_step']):
228
+ mapper=(lambda data,i:canvas_batch(data,i,cfg['context'],codec,seed,variant)) if variant in FLOW else None
229
+ mixer=ShardPrefetcher(mixer,cfg,run_id,step,None if plan['end_step'] is None else plan['end_step']-step,variant,codec,mapper,
230
+ observations=state.get('prepared_observations') if state else None)
231
+ mixer.prewarm()
232
+ preparation_seconds=(state.get('preparation_seconds',0.) if state else 0.)+getattr(mixer,'preparation_seconds',0.)
233
+ code_sha=pipeline_hash(ROOT);committed=mixer.state_dict();stop=False
234
+ panel=mixer.validation();last={};probe='';csvpath=Path(cfg['work_dir'])/'experiments.csv'
235
+ def handler(*_):
236
+ nonlocal stop;stop=True
237
+ old_handlers={s:signal.getsignal(s) for s in (signal.SIGINT,signal.SIGTERM)}
238
+ for s in old_handlers:signal.signal(s,handler)
239
+ def checkpoint(status,publish=True):
240
+ payload=dict(format=FORMAT,step=step,lineage=lineage,signature=signature,code_sha=code_sha,config=cfg,meta=meta,
241
+ manifest=manifest,mixer=committed,model=model.state_dict(),optimizer=opt.state_dict(),rng=rng_state(),
242
+ plan=plan,targets=targets,measured_targets=measured_targets,seconds=seconds,preparation_seconds=preparation_seconds,patcher_seconds=patcher_seconds,initialization=initialization)
243
+ if archive:
244
+ payload['prepared_dataset']=dict(repo=archive.repo,revision=archive.head,recipe=archive.recipe)
245
+ payload['prepared_observations']=copy.deepcopy(getattr(mixer,'observations',{}))
246
+ path=save(store.run,payload,cfg['keep_local'])
247
+ if hasattr(mixer,'flush_observed'):mixer.flush_observed(run_id,lineage,step,path)
248
+ row=dict(run_id=run_id,mode='train' if main else 'ablation',cohort='canvas_flow' if variant in FLOW else ('codec' if codec!='none' else 'causal'),
249
+ variant=variant,codec=codec,seed=seed,step=step,start_step=plan['start_step'],status=status,initialization=initialization,
250
+ params=sum(p.numel() for p in model.parameters()),context=cfg['context'],loss=last.get('loss',''),probe_loss=probe,
251
+ targets=targets,seconds=seconds,targets_per_second=measured_targets/max(1,seconds),grad_norm=last.get('grad_norm',''),retry_count=last.get('retry_count',0),
252
+ precision=last.get('precision',''),patcher_steps=cfg['patcher_steps'] if variant=='blt' else 0,
253
+ dataset_sha=fingerprint(manifest),code_sha=code_sha,coverage_json=json.dumps(mixer.coverage(),sort_keys=True))
254
+ observations=getattr(mixer,'observations',{})
255
+ row.update(sample_presentations=observations.get('presentations',''),unique_samples_seen=observations.get('unique_samples',''),preparation_seconds=preparation_seconds,patcher_seconds=patcher_seconds)
256
+ ledger(csvpath,row)
257
+ atomic_json(store.run/'coverage.json',mixer.coverage())
258
+ progress=dict(step=step,prepared_dataset=payload.get('prepared_dataset'),observations=observations,coverage=mixer.coverage())
259
+ if publish:store.publish(path,model,manifest,{**meta,'data_progress':progress})
260
+ try:
261
+ if not state or promoted:checkpoint('initialized')
262
+ # Completed finite trials are immutable; Run All proceeds to the next one.
263
+ if plan['end_step'] is not None and step>=plan['end_step']:
264
+ print(f'{run_id}: already complete',flush=True);return True
265
+ while not stop and not (Path(cfg['work_dir'])/'STOP').exists():
266
+ if plan['end_step'] is not None and step>=plan['end_step']:break
267
+ began=time.monotonic();data=mixer.batch(cfg['global_batch'])
268
+ for g in opt.param_groups:g['lr']=lr_at(step,cfg,plan)
269
+ last=update(model,opt,data,variant,cfg,device,step,codec)
270
+ cfg['microbatch']=last['microbatch'];step+=1;targets+=last['targets'];measured_targets+=last['targets'];seconds+=time.monotonic()-began
271
+ if hasattr(mixer,'commit_batch'):mixer.commit_batch()
272
+ committed=mixer.state_dict()
273
+ if step%cfg['log_every']==0:print(f'{run_id}: update={step} loss={last["loss"]:.4f} targets/s={measured_targets/max(1,seconds):.0f}',flush=True)
274
+ if step%cfg['eval_every']==0:probe=evaluate(model,variant,panel,cfg,device,step,codec)
275
+ if step%cfg['save_every']==0:checkpoint('running',publish=step%cfg['hub_every']==0)
276
+ elif step%cfg['hub_every']==0:checkpoint('running')
277
+ complete=plan['end_step'] is not None and step>=plan['end_step']
278
+ probe=evaluate(model,variant,panel,cfg,device,step,codec)
279
+ checkpoint('complete' if complete else 'paused')
280
+ return complete
281
+ except BaseException:
282
+ print('Run failed: no partial update is published. Resume the last complete local/Hub checkpoint.',flush=True)
283
+ raise
284
+ finally:
285
+ mixer.close()
286
+ for s,h in old_handlers.items():signal.signal(s,h)
287
+ del model,opt
288
+ if device.type=='cuda':torch.cuda.empty_cache()
289
+
290
+ def main(cfg):
291
+ if cfg['mode'] not in ('train','ablation'):raise ValueError('MODE must be train or ablation')
292
+ for key in ('context','global_batch','microbatch','ablation_steps','patcher_steps','hub_every','save_every','eval_every','log_every'):
293
+ if not isinstance(cfg[key],int) or cfg[key]<1:raise ValueError(key+' must be a positive integer')
294
+ if cfg.get('prepared_shards'):
295
+ for key in ('prepared_records_per_shard','cortex_records_per_shard','prefetch_shards','dispatch_batches_per_shard','prepared_memory_shards','prepared_workers'):
296
+ if cfg.get(key,1)<1:raise ValueError(key+' must be positive')
297
+ if cfg['global_batch']<10:raise ValueError('GLOBAL_BATCH must be at least 10')
298
+ if not cfg['seeds'] or len(set(cfg['seeds']))!=len(cfg['seeds']):raise ValueError('Supply distinct seeds')
299
+ if cfg['lr']<=0 or cfg['clip_grad']<=0 or not 0<=cfg['min_lr_ratio']<=1:raise ValueError('Invalid optimizer settings')
300
+ root=Path(cfg['work_dir']);root.mkdir(parents=True,exist_ok=True)
301
+ lock=(root/'runner.lock').open('w')
302
+ try:fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
303
+ except BlockingIOError:raise RuntimeError('This experiment already has an active trainer')
304
+ if (root/'STOP').exists():
305
+ print('Removing the previous clean-stop request; Run All explicitly resumes training.',flush=True);(root/'STOP').unlink()
306
+ if cfg['mode']=='train':run_one(cfg,cfg['train_variant'],cfg['train_codec'],cfg['seeds'][0],True)
307
+ else:
308
+ # One immutable starting stream for all fresh architecture/codec trials.
309
+ anchor_path=root/(cfg['experiment_id']+'-anchor.pt')
310
+ if anchor_path.exists():anchor=torch.load(anchor_path,map_location='cpu',weights_only=True)
311
+ else:
312
+ from huggingface_hub import HfApi,hf_hub_download
313
+ from dataset_registry import retry
314
+ remote_anchor='unified/experiments/'+cfg['experiment_id']+'/anchor.pt'
315
+ info=retry(lambda:HfApi().model_info(cfg['repo'])) if cfg['hub_resume'] else None
316
+ if info and remote_anchor in [f.rfilename for f in info.siblings]:
317
+ import shutil
318
+ f=retry(lambda:hf_hub_download(cfg['repo'],remote_anchor,revision=info.sha))
319
+ shutil.copy2(f,anchor_path)
320
+ manifest=complete_manifest();m=CoverageMixer(manifest,cfg['context'],group_weights=cfg['group_weights']).initialize()
321
+ anchor=torch.load(anchor_path,map_location='cpu',weights_only=True) if anchor_path.exists() else dict(manifest=manifest,mixer=m.state_dict());m.close();tmp=anchor_path.with_suffix('.tmp');torch.save(anchor,tmp);os.replace(tmp,anchor_path)
322
+ for seed in cfg['seeds']:
323
+ for variant,codec in cfg['trials']:
324
+ if not run_one(cfg,variant,codec,seed,anchor=anchor):return
325
+ lock.close()
326
+
327
+ if __name__=='__main__':
328
+ import argparse
329
+ parser=argparse.ArgumentParser();parser.add_argument('--config',required=True);args=parser.parse_args()
330
+ main(json.loads(Path(args.config).read_text()))
unified/ablations/unified-v1-blt-none-s42/source/unified_state.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verified resume and single-commit state + inference + experiment ledger."""
2
+ import csv,hashlib,json,os,shutil,time,uuid,copy
3
+ from pathlib import Path
4
+ import torch
5
+ from huggingface_hub import HfApi,hf_hub_download,CommitOperationAdd,CommitOperationDelete
6
+ from checkpoints import digest,atomic_json,verified_load,FORMAT as LEGACY_FORMAT,Publisher
7
+ from dataset_registry import retry
8
+
9
+ FORMAT='cortex-unified-v1'
10
+ LEGACY_PIPELINE='d99360d8f48a2cc017360a4d48b462b9fbee20902d3b58bcc936f054c46fc07c'
11
+ FIELDS=['run_id','mode','cohort','variant','codec','seed','step','start_step','status','initialization',
12
+ 'sample_presentations','unique_samples_seen','preparation_seconds','patcher_seconds','params','context','loss','probe_loss','targets','seconds','targets_per_second','grad_norm',
13
+ 'retry_count','precision','patcher_steps','dataset_sha','code_sha','coverage_json']
14
+
15
+ def fingerprint(value):return hashlib.sha256(json.dumps(value,sort_keys=True,default=str).encode()).hexdigest()
16
+
17
+ def ledger(path,row):
18
+ """One ledger; idempotent (run_id, step) upsert across retries/resumes."""
19
+ path=Path(path);path.parent.mkdir(parents=True,exist_ok=True)
20
+ rows=list(csv.DictReader(path.open())) if path.exists() else []
21
+ key=(str(row['run_id']),str(row['step']))
22
+ rows=[r for r in rows if (r['run_id'],r['step'])!=key];rows.append({k:row.get(k,'') for k in FIELDS})
23
+ tmp=path.with_suffix('.tmp')
24
+ with tmp.open('w',newline='') as f:
25
+ w=csv.DictWriter(f,fieldnames=FIELDS);w.writeheader();w.writerows(rows);f.flush();os.fsync(f.fileno())
26
+ os.replace(tmp,path)
27
+
28
+ def save(run,state,keep=3):
29
+ root=Path(run)/'checkpoints';root.mkdir(parents=True,exist_ok=True)
30
+ dest=root/f'checkpoint-{state["step"]:012d}'
31
+ temp=root/('.partial-'+uuid.uuid4().hex);temp.mkdir()
32
+ try:
33
+ with (temp/'training.pt').open('wb') as f:torch.save(state,f);f.flush();os.fsync(f.fileno())
34
+ meta=dict(format=FORMAT,step=state['step'],lineage=state['lineage'],signature=state['signature'],
35
+ sha256=digest(temp/'training.pt'),bytes=(temp/'training.pt').stat().st_size,saved_at=time.time())
36
+ atomic_json(temp/'metadata.json',meta);(temp/'COMPLETE').write_text('complete\n')
37
+ if dest.exists():
38
+ # Completed update is immutable; status/ledger can still be republished.
39
+ shutil.rmtree(temp);return dest
40
+ os.replace(temp,dest)
41
+ for p in sorted(root.glob('checkpoint-*'))[:-keep]:shutil.rmtree(p)
42
+ return dest
43
+ except BaseException:
44
+ shutil.rmtree(temp,ignore_errors=True);raise
45
+
46
+ def load(path):
47
+ path=Path(path);meta=json.loads((path/'metadata.json').read_text())
48
+ if meta['format']==LEGACY_FORMAT:return verified_load(path)
49
+ f=path/'training.pt'
50
+ if meta['format']!=FORMAT or not (path/'COMPLETE').exists():raise ValueError('Unrecognized/incomplete checkpoint')
51
+ if f.stat().st_size!=meta['bytes'] or digest(f)!=meta['sha256']:raise ValueError('Checkpoint checksum mismatch')
52
+ s=torch.load(f,map_location='cpu',weights_only=True)
53
+ if any(s[k]!=meta[k] for k in ('format','step','lineage','signature')):raise ValueError('State/metadata mismatch')
54
+ return s
55
+
56
+ class Store:
57
+ def __init__(self,cfg,run_id,signature,main=False,api=None):
58
+ self.cfg=cfg;self.run_id=run_id;self.signature=signature;self.main=main
59
+ self.repo=cfg['repo'];self.root=Path(cfg['work_dir']);self.run=self.root/run_id
60
+ self.run.mkdir(parents=True,exist_ok=True);self.api=api or HfApi()
61
+ self.prefix='unified/main/'+run_id if main else 'unified/ablations/'+run_id
62
+ self.head=None;self.remote_files=[]
63
+ def inventory(self):
64
+ if not self.cfg['hub_resume'] and not self.cfg['upload']:return
65
+ # Authentication/repository errors must fail, never silently start fresh.
66
+ info=retry(lambda:self.api.model_info(self.repo));self.head=info.sha
67
+ self.remote_files=[f.rfilename for f in info.siblings]
68
+ def latest(self):
69
+ self.inventory();items=[]
70
+ for p in (self.run/'checkpoints').glob('checkpoint-*/metadata.json'):
71
+ if (p.parent/'COMPLETE').exists():items.append((json.loads(p.read_text()),p.parent,None))
72
+ if self.cfg['hub_resume']:
73
+ for f in self.remote_files:
74
+ eligible=f.startswith(self.prefix+'/checkpoint-') or (self.main and self.cfg.get('train_variant','sparkbet')=='sparkbet' and not self.cfg.get('train_from_run_id') and f.startswith('checkpoints/sparkbet9m/checkpoint-'))
75
+ if eligible and f.endswith('/metadata.json') and f.rsplit('/',1)[0]+'/COMPLETE' in self.remote_files:
76
+ local=retry(lambda f=f:hf_hub_download(self.repo,f,revision=self.head,cache_dir=str(self.root/'hub_cache')))
77
+ items.append((json.loads(Path(local).read_text()),None,f.rsplit('/',1)[0]))
78
+ unified=[x for x in items if x[0]['format']==FORMAT]
79
+ # The unified lineage succeeds the old pipeline. Never prefer a legacy
80
+ # writer's higher step over a deliberately migrated unified lineage.
81
+ if unified:items=unified
82
+ if len({x[0]['lineage'] for x in items})>1:raise RuntimeError('Conflicting lineages; select a distinct work directory/repository')
83
+ for meta,path,remote in sorted(items,key=lambda x:(x[0]['step'],x[0]['saved_at']),reverse=True):
84
+ if meta['format']==FORMAT and meta['signature']!=self.signature:raise RuntimeError('Saved experiment configuration differs; change EXPERIMENT_ID for a new trial')
85
+ try:
86
+ if remote:
87
+ path=self.run/'download'/remote.replace('/','_');path.mkdir(parents=True,exist_ok=True)
88
+ for n in ('training.pt','metadata.json','COMPLETE'):
89
+ f=retry(lambda n=n:hf_hub_download(self.repo,remote+'/'+n,revision=self.head,cache_dir=str(self.root/'hub_cache')))
90
+ shutil.copy2(f,path/n)
91
+ s=load(path)
92
+ except (ValueError,EOFError,OSError) as e:
93
+ print(f'Invalid checkpoint at {meta["step"]}: {type(e).__name__}; trying older complete state',flush=True);continue
94
+ if s['format']==LEGACY_FORMAT:
95
+ if not self.main or s['pipeline']!=LEGACY_PIPELINE:raise RuntimeError('Legacy pipeline was not the audited attachment; refusing implicit migration')
96
+ print('Migrating attached SparkBET weights, optimizer and data cursors. New objective: one pass, no auxiliary recurrence. Missing dataset partitions append at the saved revision.',flush=True)
97
+ print(f'Resuming {self.run_id}, update {s["step"]}, {"Hub" if remote else "local"}',flush=True)
98
+ return s
99
+ if items:raise RuntimeError('No checkpoint passed verification; fresh initialization refused')
100
+ if self.main and self.cfg['hub_resume'] and 'model.safetensors' in self.remote_files and not self.cfg.get('train_from_run_id'):
101
+ raise RuntimeError('Root weights exist but no compatible full-state checkpoint was found. Select TRAIN_FROM_RUN_ID to promote a compatible ablation; a silent fresh main run is refused.')
102
+ return None
103
+ def restore_ledger(self):
104
+ path=self.root/'experiments.csv'
105
+ if self.cfg['hub_resume'] and 'experiments.csv' in self.remote_files:
106
+ f=retry(lambda:hf_hub_download(self.repo,'experiments.csv',revision=self.head,cache_dir=str(self.root/'hub_cache')))
107
+ remote=list(csv.DictReader(open(f)));local=list(csv.DictReader(path.open())) if path.exists() else []
108
+ for row in remote+local:ledger(path,row)
109
+ def publish(self,path,model,manifest,meta):
110
+ if not self.cfg['upload']:return
111
+ # Fetch a head before building an optimistic atomic commit. A concurrent
112
+ # writer causes a clear conflict; never overwrite its root or ledger.
113
+ previous_head=self.head
114
+ self.inventory()
115
+ if previous_head is not None and self.head!=previous_head:
116
+ self.head=previous_head
117
+ raise RuntimeError('Hub changed since this run last read/published it; resume to merge state instead of overwriting another writer')
118
+ dest=self.run/'export';dest.mkdir(exist_ok=True)
119
+ if self.main and meta['variant']=='sparkbet':
120
+ export=Publisher(self.repo,self.run)
121
+ class LocalOnly:
122
+ def upload_folder(self,**kwargs):pass
123
+ export.api=LocalOnly();export.export(model,int(Path(path).name.split('-')[-1]),manifest,Path(__file__).parent)
124
+ config=json.loads((dest/'config.json').read_text());config.update(refinement_cycles=1,precision='bf16 autocast where supported; fp32 master')
125
+ atomic_json(dest/'config.json',config)
126
+ shutil.copy2(Path(__file__).parent/'UNIFIED_README.md',dest/'README.md')
127
+ else:
128
+ from safetensors.torch import save_file
129
+ save_file({k:v.detach().cpu().contiguous().clone() for k,v in model.state_dict().items()},str(dest/'model.safetensors'))
130
+ atomic_json(dest/'experiment_config.json',meta)
131
+ if self.main:
132
+ atomic_json(dest/'config.json',dict(architecture=meta['variant'],custom_loader='unified_models.build',config=meta['config'],step=int(Path(path).name.split('-')[-1])))
133
+ (dest/'README.md').write_text('# Unified canvas / byte experiment\n\nCustom scaled architecture. Fetch the matching `unified/main` source modules and use `unified_models.build(variant, context, seed)`, then load `model.safetensors` strictly. This export does not advertise Transformers AutoModel compatibility. Full optimizer/data/RNG resume is in the same commit.\n')
134
+ if 'data_progress' in meta:atomic_json(dest/'data_progress.json',meta['data_progress'])
135
+ operations=[];remote_prefix=self.prefix+'/'+Path(path).name
136
+ for p in Path(path).iterdir():
137
+ if p.is_file():operations.append(CommitOperationAdd(path_in_repo=remote_prefix+'/'+p.name,path_or_fileobj=str(p)))
138
+ for p in dest.iterdir():
139
+ if p.is_file():operations.append(CommitOperationAdd(path_in_repo=p.name if self.main else self.prefix+'/inference/'+p.name,path_or_fileobj=str(p)))
140
+ # Save the exact implementation beside every exported configuration.
141
+ for p in Path(__file__).parent.glob('*.py'):
142
+ operations.append(CommitOperationAdd(path_in_repo=self.prefix+'/source/'+p.name,path_or_fileobj=str(p)))
143
+ anchor=self.root/(self.cfg['experiment_id']+'-anchor.pt')
144
+ if anchor.exists() and not self.main:operations.append(CommitOperationAdd(path_in_repo='unified/experiments/'+self.cfg['experiment_id']+'/anchor.pt',path_or_fileobj=str(anchor)))
145
+ csvpath=self.root/'experiments.csv'
146
+ if csvpath.exists():operations.append(CommitOperationAdd(path_in_repo='experiments.csv',path_or_fileobj=str(csvpath)))
147
+ old=sorted({f.rsplit('/',1)[0] for f in self.remote_files if f.startswith(self.prefix+'/checkpoint-') and f.endswith('/COMPLETE')})
148
+ keep=set(sorted(set(old+[remote_prefix]))[-self.cfg.get('keep_hub',2):])
149
+ for f in self.remote_files:
150
+ if f.startswith(self.prefix+'/checkpoint-') and f.rsplit('/',1)[0] not in keep:operations.append(CommitOperationDelete(path_in_repo=f))
151
+ result=self.api.create_commit(repo_id=self.repo,operations=operations,parent_commit=self.head,
152
+ commit_message=f'{self.run_id}: state and inference synchronized at {Path(path).name}')
153
+ self.head=result.oid