JohnP1 commited on
Commit
c735e00
·
0 Parent(s):

D1A-E2B v0.6 Core ML: text model, 5 int8 chunks, L=512, with the reference runner (#224)

Browse files
.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: google/gemma-4-E2B
4
+ tags: [coreml, apple-neural-engine, decision-model, d1a]
5
+ ---
6
+ # D1A-E2B v0.6 · Core ML (text, ≤ 512 tokens)
7
+
8
+ D1A-E2B v0.6 for the Apple Neural Engine. D1A is a small decision model: a document and typed questions go in, and a
9
+ calibrated probability for every option comes out, in one pass, with no generated text ([jonpol01/d1a](https://github.com/jonpol01/d1a)).
10
+ This repository is the text model converted to Core ML; the conversion is described in jonpol01/d1a#224.
11
+
12
+ ## Status
13
+
14
+ - **Correct at ≤ 512 tokens.** On 28 real requests (42 questions), no clear-cut answer changes against the fp32 PyTorch
15
+ rebuild of the same model, on an M1 Max and an M4.
16
+ - **Speed (median request):** M4 Neural Engine 0.96 s (MLX on the GPU: 0.20 s); M1 Max 9.7 s (MLX: 0.17 s). The M1's Neural Engine does not take these chunks, so on M1-class Macs they run on the CPU; the M4 runs them on its Neural Engine.
17
+ - **Use it** where the GPU should stay free or power matters (iPhone, iPad). On a Mac, the MLX build is faster.
18
+ - **Not included yet:** photo, voice and video (their encoders are separate models, still to convert), and inputs longer than
19
+ 512 tokens (2,048-token programs give wrong answers on the M4 Neural Engine).
20
+
21
+ ## Run it
22
+
23
+ ```bash
24
+ pip install coremltools numpy torch transformers safetensors "d1a @ git+https://github.com/jonpol01/d1a"
25
+ hf download JohnP1/d1a-e2b-coreml --revision v0.6 --local-dir d1a-e2b-coreml
26
+ python d1a-e2b-coreml/reference/run.py d1a-e2b-coreml request.json
27
+ ```
28
+
29
+ `request.json` is a System One request, for example:
30
+
31
+ ```json
32
+ {"state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges on my card.",
33
+ "questions": {"team": {"type": "choice", "instructions": "Which team should handle this?", "criteria": {"returns": null, "shipping": null, "billing": null}},
34
+ "urgent": {"type": "noul", "instructions": "Does this need a reply today?"}}}
35
+ ```
36
+
37
+ The first run compiles the chunks into `coreml/.compiled/` (a few minutes); later runs load them.
38
+
39
+ ## Files
40
+
41
+ | Path | What |
42
+ |---|---|
43
+ | `coreml/L512_<a>-<b>.mlpackage` | 5 chunks of the 35-layer text model, int8 weights (per block of 64). Chunk 7-15 outputs the shared keys and values that chunks 15-35 read. |
44
+ | `embeddings.safetensors` | the token and per-layer embedding tables (MLX affine-quantized, from JohnP1/d1a-e2b-mlx-q8@v0.6), read row by row on the host |
45
+ | `head.safetensors`, `d1a_config.json` | the pointer head, the temperature and the pad and delimiter ids |
46
+ | `config.json`, `tokenizer.json`, `tokenizer_config.json` | the Gemma 4 model config and tokenizer |
47
+ | `io.json` | each chunk's input names |
48
+ | `reference/` | Python reference: `run.py` (one request), `workers.py` (one process per chunk), `d1a_coreml.py` (host-side embeddings and masks) |
49
+
50
+ ## How a request runs
51
+
52
+ 1. Encode it with D1A's layout (`d1a.core.encoding.encode`) and right-pad it to 512 tokens with the pad token.
53
+ 2. Look up the token embeddings (scaled by √hidden) and the per-layer embeddings (scaled by √256) on the host.
54
+ 3. Build the block-causal masks (full and sliding), clamped to −1e4.
55
+ 4. Run the chunks in order, each in its own process. Several large Neural Engine programs in one process can crash the
56
+ runtime.
57
+ 5. Read the hidden states at each question's `<decide>` and `</opt>` positions, score them with the head, divide by the
58
+ temperature, and take the softmax.
59
+
60
+ macOS 15 / iOS 18 or later.
61
+
62
+ ## D1A builds compared
63
+
64
+ Measured on 2026-10-11 on the same 28 real requests (42 questions, all ≤ 512 tokens), on an Apple M1 Max and a Mac mini M4.
65
+
66
+ | | E2B · MLX | E2B · Core ML | E4B · MLX | E4B · Core ML |
67
+ |---|---|---|---|---|
68
+ | Version | v0.6 | v0.6 | v0.6 | v0.6 |
69
+ | Repository | JohnP1/d1a-e2b-mlx-q8 | JohnP1/d1a-e2b-coreml | JohnP1/d1a-e4b-mlx-q8 | JohnP1/d1a-e4b-coreml |
70
+ | Runs on | Mac GPU | Neural Engine (iPhone, iPad, M4-class Macs) | Mac GPU | Neural Engine |
71
+ | Text | yes | yes | yes | yes |
72
+ | Photo, voice, video (16 frames, no sound) | yes | not yet (next) | yes | not yet |
73
+ | Input length | long documents | ≤ 512 tokens | long documents | ≤ 512 tokens |
74
+ | Questions | yes/no, choice, score, with a calibrated probability for each option | same | same | same |
75
+ | Languages | questions in English; documents in English or Japanese | same | same | same |
76
+ | PR labels, 487 newest PRs: type / severity | 87.5% / 77.2%; 0 of 30 P0/P1 found | same model | 90.6% / 79.1%; 13 of 30 P0/P1 | same model |
77
+ | Size | 3.8 GB + 1.0 GB media | 3.5 GB (1.8 GB chunks + 1.6 GB embeddings) | 6.6 GB + 1.0 GB media | 6.1 GB (3.9 GB chunks + 2.1 GB embeddings) |
78
+ | Median request, M4 (p95) | **0.20 s** (0.26) | 0.96 s (0.97) | 0.63 s (0.89) | 1.80 s (1.91) |
79
+ | Median request, M1 Max (p95) | **0.17 s** (0.23) | 9.7 s (10.0) ² | 0.81 s (1.33) | 2.93 s (2.95) |
80
+ | Load, M4 / M1 Max | 2 s / 9 s | 40 s / 247 s | 3 s / – | 52 s / 106 s |
81
+ | Answer changes vs fp32 PyTorch ¹ | 0 of 37 clear-cut (M4: 1 near-tie of 42) | 0 of 37 clear-cut (M4: 1 near-tie of 42) | 0 of 38 clear-cut | 0 of 38 clear-cut |
82
+ | Photo / voice / video, median (M1 Max) | 0.83 s over the 14 demo requests | – | – | – |
83
+
84
+ ¹ Each build is compared with the fp32 PyTorch rebuild of the same v0.6 model. "Clear-cut" means the reference's top
85
+ answer leads by more than 0.05; a near-tie can flip either way.
86
+ ² On the M1 Max's Neural Engine, the E2B chunks fall back to the CPU: each chunk takes the same time with or without the
87
+ Neural Engine. The M4 runs the same files on its Neural Engine.
88
+
89
+ ## License
90
+
91
+ Apache-2.0. Base model: Gemma 4 by Google (Apache-2.0). D1A is built on Kev by Jared Palmer (Apache-2.0).
config.json ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Gemma4ForConditionalGeneration"
4
+ ],
5
+ "audio_config": {
6
+ "_name_or_path": "",
7
+ "architectures": null,
8
+ "attention_chunk_size": 12,
9
+ "attention_context_left": 13,
10
+ "attention_context_right": 0,
11
+ "attention_invalid_logits_value": -1000000000.0,
12
+ "attention_logit_cap": 50.0,
13
+ "chunk_size_feed_forward": 0,
14
+ "conv_kernel_size": 5,
15
+ "dtype": "bfloat16",
16
+ "gradient_clipping": 10000000000.0,
17
+ "hidden_act": "silu",
18
+ "hidden_size": 1024,
19
+ "id2label": {
20
+ "0": "LABEL_0",
21
+ "1": "LABEL_1"
22
+ },
23
+ "initializer_range": 0.02,
24
+ "is_encoder_decoder": false,
25
+ "label2id": {
26
+ "LABEL_0": 0,
27
+ "LABEL_1": 1
28
+ },
29
+ "model_type": "gemma4_audio",
30
+ "num_attention_heads": 8,
31
+ "num_hidden_layers": 12,
32
+ "output_attentions": false,
33
+ "output_hidden_states": false,
34
+ "output_proj_dims": 1536,
35
+ "problem_type": null,
36
+ "residual_weight": 0.5,
37
+ "return_dict": true,
38
+ "rms_norm_eps": 1e-06,
39
+ "subsampling_conv_channels": [
40
+ 128,
41
+ 32
42
+ ],
43
+ "use_clipped_linears": true
44
+ },
45
+ "audio_token_id": 258881,
46
+ "boa_token_id": 256000,
47
+ "boi_token_id": 255999,
48
+ "dtype": "bfloat16",
49
+ "eoa_token_id": 258883,
50
+ "eoa_token_index": 258883,
51
+ "eoi_token_id": 258882,
52
+ "eos_token_id": 1,
53
+ "image_token_id": 258880,
54
+ "initializer_range": 0.02,
55
+ "model_type": "gemma4",
56
+ "quantization": {
57
+ "group_size": 64,
58
+ "bits": 8,
59
+ "mode": "affine",
60
+ "language_model.model.embed_tokens_per_layer": {
61
+ "group_size": 64,
62
+ "bits": 4,
63
+ "mode": "affine"
64
+ }
65
+ },
66
+ "quantization_config": {
67
+ "group_size": 64,
68
+ "bits": 8,
69
+ "mode": "affine",
70
+ "language_model.model.embed_tokens_per_layer": {
71
+ "group_size": 64,
72
+ "bits": 4,
73
+ "mode": "affine"
74
+ }
75
+ },
76
+ "text_config": {
77
+ "attention_bias": false,
78
+ "attention_dropout": 0.0,
79
+ "attention_k_eq_v": false,
80
+ "bos_token_id": 2,
81
+ "dtype": "bfloat16",
82
+ "enable_moe_block": false,
83
+ "eos_token_id": 1,
84
+ "expert_intermediate_size": null,
85
+ "final_logit_softcapping": 30.0,
86
+ "global_head_dim": 512,
87
+ "head_dim": 256,
88
+ "hidden_activation": "gelu_pytorch_tanh",
89
+ "hidden_size": 1536,
90
+ "hidden_size_per_layer_input": 256,
91
+ "initializer_range": 0.02,
92
+ "intermediate_size": 6144,
93
+ "layer_types": [
94
+ "sliding_attention",
95
+ "sliding_attention",
96
+ "sliding_attention",
97
+ "sliding_attention",
98
+ "full_attention",
99
+ "sliding_attention",
100
+ "sliding_attention",
101
+ "sliding_attention",
102
+ "sliding_attention",
103
+ "full_attention",
104
+ "sliding_attention",
105
+ "sliding_attention",
106
+ "sliding_attention",
107
+ "sliding_attention",
108
+ "full_attention",
109
+ "sliding_attention",
110
+ "sliding_attention",
111
+ "sliding_attention",
112
+ "sliding_attention",
113
+ "full_attention",
114
+ "sliding_attention",
115
+ "sliding_attention",
116
+ "sliding_attention",
117
+ "sliding_attention",
118
+ "full_attention",
119
+ "sliding_attention",
120
+ "sliding_attention",
121
+ "sliding_attention",
122
+ "sliding_attention",
123
+ "full_attention",
124
+ "sliding_attention",
125
+ "sliding_attention",
126
+ "sliding_attention",
127
+ "sliding_attention",
128
+ "full_attention"
129
+ ],
130
+ "max_position_embeddings": 131072,
131
+ "model_type": "gemma4_text",
132
+ "num_attention_heads": 8,
133
+ "num_experts": null,
134
+ "num_global_key_value_heads": null,
135
+ "num_hidden_layers": 35,
136
+ "num_key_value_heads": 1,
137
+ "num_kv_shared_layers": 20,
138
+ "pad_token_id": 0,
139
+ "rms_norm_eps": 1e-06,
140
+ "rope_parameters": {
141
+ "full_attention": {
142
+ "partial_rotary_factor": 0.25,
143
+ "rope_theta": 1000000.0,
144
+ "rope_type": "proportional"
145
+ },
146
+ "sliding_attention": {
147
+ "rope_theta": 10000.0,
148
+ "rope_type": "default"
149
+ }
150
+ },
151
+ "sliding_window": 512,
152
+ "tie_word_embeddings": true,
153
+ "top_k_experts": null,
154
+ "use_bidirectional_attention": null,
155
+ "use_cache": true,
156
+ "use_double_wide_mlp": true,
157
+ "vocab_size": 262144,
158
+ "vocab_size_per_layer_input": 262144
159
+ },
160
+ "tie_word_embeddings": true,
161
+ "transformers_version": "5.5.0.dev0",
162
+ "video_token_id": 258884,
163
+ "vision_soft_tokens_per_image": 280
164
+ }
coreml/L512_00-07.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63cdc56003526dc6bc1922b1f5245e14fda32e472ff1c22f0b1bace80ba61235
3
+ size 254034
coreml/L512_00-07.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4932ca078e73e04eb9bacb44aafee7356ff9b418068c7a0d2bb3325dbcadfe34
3
+ size 287097152
coreml/L512_00-07.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "15E6ABF0-3B7A-45EB-AEFE-575087ED5B8B": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "186F9637-EC03-4170-A083-0E8F08B64E70": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "15E6ABF0-3B7A-45EB-AEFE-575087ED5B8B"
18
+ }
coreml/L512_07-15.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9f64f99c757d484f0d57e5746f3ede35e2d44d377de494e72f8e9d532b9afb29
3
+ size 286085
coreml/L512_07-15.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a52d7199ef53e97e89fe64fc5b729742b3db3d4c90fa129372602b7fd3dd2cea
3
+ size 317972736
coreml/L512_07-15.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "C34EF27C-FE7F-43D9-B3CB-EF07BA6ED05E": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "E1CCD0C2-4CB7-4016-8EA6-D0C9512C59E7": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "C34EF27C-FE7F-43D9-B3CB-EF07BA6ED05E"
18
+ }
coreml/L512_15-22.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:98899d9bcfbe1941ae4533d9b2db80647f205ebe4d14a9a039f0850be19db094
3
+ size 183109
coreml/L512_15-22.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ffb4791b587ae08b55f9030c8ba40f8dd2cb660d3cdd5e4416246396041cc120
3
+ size 473557760
coreml/L512_15-22.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "8BF3EE60-129B-43DC-ADBC-76FCC2AE0F22": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "D588AC66-1980-4F48-A80E-D22E2DC5FC04": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "8BF3EE60-129B-43DC-ADBC-76FCC2AE0F22"
18
+ }
coreml/L512_22-29.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5d99163080dcb2cdf8fd583083db5e1b11a165a6ae47856186e5a45e455e6586
3
+ size 183113
coreml/L512_22-29.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:99d4cc60a0b5d645595238fd279e7d2eb0c939de902e7f80bc9eb5f005ed3503
3
+ size 473557760
coreml/L512_22-29.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "5CBC619A-F5EC-4532-AE9A-E730428289EC": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "E85695E6-50B8-4D34-AFD3-8C9EAF6EF828": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "E85695E6-50B8-4D34-AFD3-8C9EAF6EF828"
18
+ }
coreml/L512_29-35.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c72de76354fb0154b891dd2ef7819eabfd556d4c54c59a243a8d8ff17dbdf1b9
3
+ size 163816
coreml/L512_29-35.mlpackage/Data/com.apple.CoreML/weights/weight.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:81a752d4ccecd24523c1e5bc5c5859c07d44233a75d229fe846912e44ef90cea
3
+ size 413443264
coreml/L512_29-35.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "4D7E2B93-F4E3-4337-A629-BB3697573F32": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "64D6F739-0E0A-4E79-97A2-DCFB02714BFA": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Specification",
13
+ "name": "model.mlmodel",
14
+ "path": "com.apple.CoreML/model.mlmodel"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "64D6F739-0E0A-4E79-97A2-DCFB02714BFA"
18
+ }
d1a_config.json ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "d1a-mlx",
3
+ "format_version": 1,
4
+ "base": "google/gemma-4-E2B",
5
+ "base_revision": "d29ff6b45f081a49ee2733a859c9c9c2d95d1a6f",
6
+ "source": "JohnP1/d1a-e2b",
7
+ "source_revision": "v0.6",
8
+ "adapter_sha256": "247f222314c8ceee158140d38a0be1c304da81719034ef6e71ee02506e334c1b",
9
+ "lora": 16,
10
+ "head_dim": 256,
11
+ "hidden_size": 1536,
12
+ "temperature": 1.447269237440378,
13
+ "leading_ids": [
14
+ 2
15
+ ],
16
+ "bos_id": 2,
17
+ "delimiter_ids": [
18
+ 6,
19
+ 7,
20
+ 8,
21
+ 9,
22
+ 10
23
+ ],
24
+ "pad_id": 0,
25
+ "dtype": "bfloat16",
26
+ "quantization": {
27
+ "group_size": 64,
28
+ "bits": 8,
29
+ "mode": "affine",
30
+ "language_model.model.embed_tokens_per_layer": {
31
+ "group_size": 64,
32
+ "bits": 4,
33
+ "mode": "affine"
34
+ },
35
+ "embeddings": true
36
+ },
37
+ "head": "head.safetensors",
38
+ "temperature_fit": {
39
+ "rows": [
40
+ "cal/rows.json",
41
+ "calpr/rows.json"
42
+ ],
43
+ "n": 3178,
44
+ "method": "min micro mean NLL over a 121-point log grid 0.25..4",
45
+ "cross_validation": {
46
+ "raw": {
47
+ "n": 3178,
48
+ "ece": 0.0505561988092814,
49
+ "brier": 0.2952905648331223,
50
+ "nll": 0.5648973549274169,
51
+ "confident_error_rate": 0.02957835116425425,
52
+ "coverage_at_5pct_error": 0.5613593455003146
53
+ },
54
+ "out_of_fold": {
55
+ "n": 3178,
56
+ "ece": 0.023704548579686884,
57
+ "brier": 0.2896952220920997,
58
+ "nll": 0.5324486179012022,
59
+ "confident_error_rate": 0.017935808684707365,
60
+ "coverage_at_5pct_error": 0.5556954059156702
61
+ },
62
+ "ece_ci95": {
63
+ "raw": [
64
+ 0.04102750015575881,
65
+ 0.06377306530700594
66
+ ],
67
+ "out_of_fold": [
68
+ 0.016511069955034523,
69
+ 0.03860363764230206
70
+ ],
71
+ "delta": [
72
+ -0.04016350379193325,
73
+ -0.010727297381358629
74
+ ]
75
+ },
76
+ "separated": true,
77
+ "temperatures": [
78
+ 1.447269237440378,
79
+ 1.447269237440378,
80
+ 1.4142135623730947,
81
+ 1.4810975522865641,
82
+ 1.447269237440378
83
+ ],
84
+ "folds": 5,
85
+ "seed": 0,
86
+ "samples": 1000,
87
+ "groups": 1670,
88
+ "unit": "source-stratified (source, group); sibling questions and variants stay together"
89
+ },
90
+ "interval": {
91
+ "level": 0.9,
92
+ "samples": 2000,
93
+ "seed": 0,
94
+ "lower": 1.381912879967776,
95
+ "upper": 1.5157165665103978,
96
+ "questions": 3178
97
+ },
98
+ "fit_rows": [
99
+ {
100
+ "rows": "cal/rows.json",
101
+ "suite": "evals/v7/decision-v7",
102
+ "split": "calibration",
103
+ "questions": 1148
104
+ },
105
+ {
106
+ "rows": "calpr/rows.json",
107
+ "suite": "evals/d1a/pr-labels",
108
+ "split": "development",
109
+ "questions": 2030
110
+ }
111
+ ],
112
+ "training_suites": [
113
+ "evals/devtools-v1",
114
+ "evals/documents-v1",
115
+ "evals/hard-v1",
116
+ "evals/v7/decision-v7"
117
+ ],
118
+ "training_partitions": [
119
+ "evals/d1a/ja-jglue:train",
120
+ "evals/d1a/pr-labels:train",
121
+ "evals/d1a/pr-labels:train-blast",
122
+ "evals/d1a/pr-labels:train-ja",
123
+ "evals/d1a/routing:factory-train",
124
+ "evals/d1a/routing:generic-train"
125
+ ],
126
+ "same_corpus": [
127
+ "calpr/rows.json: SAME CORPUS: evals/d1a/pr-labels:development is an eval partition of evals/d1a/pr-labels, whose train partitions ['evals/d1a/pr-labels:train', 'evals/d1a/pr-labels:train-blast', 'evals/d1a/pr-labels:train-ja'] the checkpoint trained on"
128
+ ],
129
+ "in_distribution": {
130
+ "allowed": true,
131
+ "problems": [
132
+ "cal/rows.json: evals/v7/decision-v7 is training data of the checkpoint",
133
+ "cal/rows.json: it pools 12 training source(s) ['agnews', 'amazon', 'banking77', 'boolq', 'compositional', 'dbpedia14', 'imdb', 'legacy_policy'] ...",
134
+ "cal/rows.json: it reads the calibration partition of evals/v7/decision-v7, a training corpus",
135
+ "calpr/rows.json: SAME CORPUS: evals/d1a/pr-labels:development is an eval partition of evals/d1a/pr-labels, whose train partitions ['evals/d1a/pr-labels:train', 'evals/d1a/pr-labels:train-blast', 'evals/d1a/pr-labels:train-ja'] the checkpoint trained on",
136
+ "cannot list the sources of --data /work/data/train-e2b-v06.jsonl (outside evals/, no sidecar train-e2b-v06.jsonl.json naming its sources), training data of d1"
137
+ ]
138
+ }
139
+ },
140
+ "use_case_temperatures": {},
141
+ "use_case_temperature_fits": {}
142
+ }
embeddings.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d6e0735da110bf28359f69f4aa48db25dd8385c629e2c411b1fa6bb8c61a1694
3
+ size 1749025704
head.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5b56c4f9da06b75a359da342e6002c6c73896033925b88e9e13c3757878e7447
3
+ size 3148080
io.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"chunks": ["00-07", "07-15", "15-22", "22-29", "29-35"], "inputs": {"00-07": ["emb", "ple_tok", "pos", "mask_full", "mask_slide"], "07-15": ["h_in", "ple", "pos", "mask_full", "mask_slide"], "15-22": ["h_in", "ple", "pos", "mask_full", "mask_slide", "k_full_attention", "v_full_attention", "k_sliding_attention", "v_sliding_attention"], "22-29": ["h_in", "ple", "pos", "mask_full", "mask_slide", "k_full_attention", "v_full_attention", "k_sliding_attention", "v_sliding_attention"], "29-35": ["h_in", "ple", "pos", "mask_full", "mask_slide", "k_full_attention", "v_full_attention", "k_sliding_attention", "v_sliding_attention", "picks"]}}
reference/d1a_coreml.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """D1A (Gemma 4 text) -> Core ML pilot (jonpol01/d1a#224): rebuild a D1A MLX export as a torch module, split into
2
+ - host side: the two embedding tables (token embedding, per-layer embedding: 2.75B of E2B's 4.6B parameters) read
3
+ row by row from the export's safetensors (dequantized per request, never held whole), and
4
+ - Core: everything that computes (per-layer projection, 35 decoder layers, final norm, pointer head), which is what
5
+ gets converted to Core ML.
6
+ Inputs follow D1A's packed encoding (d1a.core.encoding.encode): one record, block-causal mask, positions restarting per
7
+ question after the state, readout at each question's <decide> and </opt> tokens."""
8
+ import json
9
+ import math
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ import torch
14
+ from safetensors import safe_open
15
+ from torch import nn
16
+ from transformers import AutoConfig
17
+ from transformers.models.gemma4 import modeling_gemma4 as G
18
+
19
+ PREFIX = "language_model.model."
20
+
21
+
22
+ def dequant(packed, scales, biases, bits, group=64):
23
+ """MLX affine dequantization: packed uint32 [out, in*bits/32] (lowest bits first) -> float32 [out, in]."""
24
+ p = packed.view(torch.int32).to(torch.int64) & 0xFFFFFFFF
25
+ per = 32 // bits
26
+ shifts = torch.arange(per, dtype=torch.int64) * bits
27
+ q = ((p[..., None] >> shifts) & ((1 << bits) - 1)).reshape(p.shape[0], -1).to(torch.float32)
28
+ s, b = scales.to(torch.float32), biases.to(torch.float32)
29
+ return (q.view(q.shape[0], -1, group) * s[..., None] + b[..., None]).reshape(q.shape[0], -1)
30
+
31
+
32
+ class _Shards:
33
+ """model.safetensors or model-0000N-of-0000M.safetensors (via model.safetensors.index.json), read like one safe_open."""
34
+
35
+ def __init__(self, path):
36
+ idx = Path(path) / "model.safetensors.index.json"
37
+ if idx.exists():
38
+ wm = json.loads(idx.read_text())["weight_map"]
39
+ files = {f: safe_open(str(Path(path) / f), "pt") for f in sorted(set(wm.values()))}
40
+ self.where = {k: files[f] for k, f in wm.items()}
41
+ else: # a released Core ML bundle ships only the embedding tables, as embeddings.safetensors
42
+ name = "model.safetensors" if (Path(path) / "model.safetensors").exists() else "embeddings.safetensors"
43
+ f = safe_open(str(Path(path) / name), "pt")
44
+ self.where = {k: f for k in f.keys()}
45
+
46
+ def keys(self):
47
+ return self.where.keys()
48
+
49
+ def get_tensor(self, k):
50
+ return self.where[k].get_tensor(k)
51
+
52
+ def get_slice(self, k):
53
+ return self.where[k].get_slice(k)
54
+
55
+
56
+ class Export:
57
+ """A D1A MLX export directory (config.json, d1a_config.json, model.safetensors, head.safetensors)."""
58
+
59
+ def __init__(self, path):
60
+ self.path = Path(path)
61
+ self.cfg = AutoConfig.from_pretrained(self.path)
62
+ self.tc = self.cfg.text_config
63
+ self.tc._attn_implementation = "eager"
64
+ self.d1a = json.loads((self.path / "d1a_config.json").read_text())
65
+ q = self.d1a["quantization"]
66
+ self.bits = {"default": (q["bits"], q["group_size"]), "embed_tokens_per_layer": (q[PREFIX.replace("model.", "") + "model.embed_tokens_per_layer"]["bits"] if False else q["language_model.model.embed_tokens_per_layer"]["bits"], q["language_model.model.embed_tokens_per_layer"]["group_size"])}
67
+ self.f = _Shards(self.path)
68
+
69
+ def bits_of(self, name):
70
+ return self.bits["embed_tokens_per_layer"] if name.endswith("embed_tokens_per_layer") else self.bits["default"]
71
+
72
+ def tensor(self, name):
73
+ """A float32 tensor for `name` (without PREFIX), dequantized if it is stored quantized."""
74
+ full = PREFIX + name
75
+ keys = set(self.f.keys())
76
+ if full + ".weight" in keys and full + ".scales" in keys:
77
+ bits, group = self.bits_of(name)
78
+ return dequant(self.f.get_tensor(full + ".weight"), self.f.get_tensor(full + ".scales"), self.f.get_tensor(full + ".biases"), bits, group)
79
+ return self.f.get_tensor(full).to(torch.float32)
80
+
81
+ def text_model(self, dtype=torch.float32):
82
+ """The Gemma 4 text trunk with every non-embedding weight loaded (embedding tables stay empty: host side)."""
83
+ with torch.device("meta"):
84
+ m = G.Gemma4TextModel(self.tc)
85
+ m = m.to_empty(device="cpu")
86
+ state = {}
87
+ keys = set(self.f.keys())
88
+ for name, p in m.named_parameters():
89
+ if name.startswith("embed_tokens"):
90
+ continue
91
+ base = name[: -len(".weight")] if name.endswith(".weight") else name
92
+ if PREFIX + base + ".scales" in keys:
93
+ state[name] = self.tensor(base)
94
+ else:
95
+ state[name] = self.f.get_tensor(PREFIX + name).to(torch.float32)
96
+ assert state[name].shape == p.shape, (name, state[name].shape, p.shape)
97
+ for name, b in m.named_buffers():
98
+ if PREFIX + name in keys:
99
+ state[name] = self.f.get_tensor(PREFIX + name).to(torch.float32)
100
+ missing, unexpected = m.load_state_dict(state, strict=False)
101
+ missing = [k for k in missing if not k.startswith("embed_tokens")]
102
+ assert not missing and not unexpected, (missing, unexpected)
103
+ # buffers transformers computes itself (rotary inv_freq, embed_scale): re-run their init on CPU
104
+ m.rotary_emb = G.Gemma4TextRotaryEmbedding(self.tc)
105
+ return m.to(dtype).eval()
106
+
107
+ def head(self):
108
+ h = safe_open(str(self.path / "head.safetensors"), "pt")
109
+ dp, d = h.get_tensor("q.weight").shape # pointer width, hidden size (E2B 1536, E4B 2560)
110
+ q, k = nn.Linear(d, dp), nn.Linear(d, dp)
111
+ q.weight.data, q.bias.data = h.get_tensor("q.weight").float(), h.get_tensor("q.bias").float()
112
+ k.weight.data, k.bias.data = h.get_tensor("k.weight").float(), h.get_tensor("k.bias").float()
113
+ return q, k
114
+
115
+
116
+ class HostEmbed:
117
+ """Row lookups of the two embedding tables, dequantized per request: emb = E[ids] * sqrt(hidden) and
118
+ ple = E_pl[ids] * sqrt(ple_dim), reshaped [L, layers, ple_dim] (transformers' get_per_layer_inputs)."""
119
+
120
+ def __init__(self, ex):
121
+ self.ex, self.tc = ex, ex.tc
122
+ self.f = ex.f
123
+
124
+ def _rows(self, name, ids):
125
+ full = PREFIX + name
126
+ bits, group = self.ex.bits_of(name)
127
+ w, s, b = (self.f.get_slice(full + suf) for suf in (".weight", ".scales", ".biases"))
128
+ uniq, inv = np.unique(np.asarray(ids), return_inverse=True)
129
+ rows = [dequant(w[int(i): int(i) + 1], s[int(i): int(i) + 1], b[int(i): int(i) + 1], bits, group) for i in uniq]
130
+ return torch.cat(rows)[torch.as_tensor(inv)]
131
+
132
+ def __call__(self, ids):
133
+ emb = self._rows("embed_tokens", ids) * (self.tc.hidden_size ** 0.5)
134
+ ple = self._rows("embed_tokens_per_layer", ids) * (self.tc.hidden_size_per_layer_input ** 0.5)
135
+ return emb[None], ple.view(1, len(ids), self.tc.num_hidden_layers, self.tc.hidden_size_per_layer_input)
136
+
137
+
138
+ def masks_for(enc, window, L=None, dtype=torch.float32):
139
+ """D1A's packed masks (d1a.backends.torch.branch_masks) for one record, padded to L: additive [1, 1, L, L]."""
140
+ seg, pos = enc["seg"], enc["pos"]
141
+ n = len(seg)
142
+ L = L or n
143
+ s = torch.full((L,), -1)
144
+ s[:n] = torch.tensor(seg)
145
+ p = torch.zeros(L, dtype=torch.long)
146
+ p[:n] = torch.tensor(pos)
147
+ causal = torch.tril(torch.ones(L, L, dtype=torch.bool))
148
+ same = (s[None, :] == s[:, None]) | (s[None, :] == 0)
149
+ allow = (causal & same & (s != -1)[None, :]) | torch.eye(L, dtype=torch.bool)
150
+ neg = torch.finfo(dtype).min
151
+ full = torch.zeros(L, L, dtype=dtype).masked_fill(~allow, neg)
152
+ far = (p[:, None] - p[None, :] >= window) & ~torch.eye(L, dtype=torch.bool)
153
+ slide = full.masked_fill(far, neg)
154
+ return full[None, None], slide[None, None], p[None]
155
+
156
+
157
+ class Core(nn.Module):
158
+ """Everything that computes: per-layer projection, decoder layers, final norm, pointer head.
159
+ forward(emb [1,L,d], ple_tok [1,L,layers,256], pos [1,L] int, mask_full, mask_slide [1,1,L,L],
160
+ decide [Q] int, opts [Q,K] int, opt_valid [Q,K] float (1 used, 0 padding)) -> logits [Q,K]
161
+ (raw, temperature not applied; padded options get a large negative)."""
162
+
163
+ def __init__(self, text, head_q, head_k, silu_tanh=True):
164
+ super().__init__()
165
+ self.text, self.hq, self.hk = text, head_q, head_k
166
+ self.types = text.config.layer_types
167
+ self.scale = 1 / math.sqrt(head_q.out_features)
168
+
169
+ def forward(self, emb, ple_tok, pos, mask_full, mask_slide, decide, opts, opt_valid):
170
+ t = self.text
171
+ ple = t.project_per_layer_inputs(emb, ple_tok)
172
+ rope = {lt: t.rotary_emb(emb, pos, lt) for lt in t.unique_layer_types}
173
+ masks = {"full_attention": mask_full, "sliding_attention": mask_slide}
174
+ from collections import UserDict
175
+ kv = UserDict()
176
+ h = emb
177
+ for i, layer in enumerate(t.layers):
178
+ h = layer(h, ple[:, :, i, :], shared_kv_states=kv, position_embeddings=rope[self.types[i]],
179
+ attention_mask=masks[self.types[i]], position_ids=pos)
180
+ h = t.norm(h)[0] # [L, d]
181
+ q = self.hq(h[decide]) # [Q, dp]
182
+ k = self.hk(h[opts.reshape(-1)]).view(*opts.shape, -1) # [Q, K, dp]
183
+ z = (k * q[:, None, :]).sum(-1) * self.scale
184
+ return z + (opt_valid - 1.0) * 1e4
reference/pipeline.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run a chunked D1A Core ML trunk end to end: host embeddings -> chunk 0 .. chunk N (h, per-layer inputs and the shared
2
+ keys/values passed along) -> hidden states at the picks -> pointer head on the host.
3
+ Usage as a library: Pipeline(export_dir, chunk_dir, L, units).probs(enc) -> [per-question probabilities]."""
4
+ import glob
5
+ import os
6
+ import re
7
+ import sys
8
+ import time
9
+
10
+ import coremltools as ct
11
+ import numpy as np
12
+ import torch
13
+
14
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # d1a_coreml.py sits next to this file
15
+ from d1a_coreml import Export, HostEmbed, masks_for # noqa: E402
16
+
17
+ P_PICKS = 128
18
+
19
+
20
+ class Pipeline:
21
+ def __init__(self, export_dir, chunk_dir, L, units="CPU_AND_NE"):
22
+ self.ex = Export(export_dir)
23
+ self.host = HostEmbed(self.ex)
24
+ self.hq, self.hk = self.ex.head()
25
+ self.T = self.ex.d1a["temperature"]
26
+ self.L = L
27
+ cu = getattr(ct.ComputeUnit, units)
28
+ self.chunks = []
29
+ if chunk_dir.endswith(".mlpackage"): # one multifunction package: functions c00_07, c07_15, ...
30
+ names = ["c00_07", "c07_15", "c15_22", "c22_29", "c29_35"]
31
+ for name in names:
32
+ a, b = map(int, name[1:].split("_"))
33
+ m = ct.models.MLModel(chunk_dir, compute_units=cu, function_name=name)
34
+ fn = next(f for f in m.get_spec().description.functions if f.name == name)
35
+ self.chunks.append((a, b, m, [i.name for i in fn.input], [o.name for o in fn.output]))
36
+ return
37
+ files = sorted(glob.glob(f"{chunk_dir}/L{L}_*.mlpackage"))
38
+ for f in files:
39
+ a, b = map(int, re.search(r"_(\d+)-(\d+)\.mlpackage$", f).groups())
40
+ m = ct.models.MLModel(f, compute_units=cu)
41
+ spec = m.get_spec()
42
+ self.chunks.append((a, b, m, [i.name for i in spec.description.input], [o.name for o in spec.description.output]))
43
+
44
+ def hidden(self, enc):
45
+ L, n = self.L, len(enc["ids"])
46
+ # right-pad with the pad token's own embeddings (as d1a's torch backend pads ids), never zero rows: an all-zero row
47
+ # makes every RMSNorm compute 0 / sqrt(eps), and eps (1e-6) is subnormal in fp16, which the Neural Engine flushes
48
+ ids = list(enc["ids"]) + [self.ex.d1a.get("pad_id", 0)] * (L - n)
49
+ emb, ple = self.host(ids)
50
+ e = emb.numpy().astype(np.float16)
51
+ p = ple.numpy().astype(np.float16)
52
+ mf, ms, pos = masks_for(enc, self.ex.tc.sliding_window, L=L)
53
+ picks = list(enc["decide_idx"]) + [o for oo in enc["opt_idx"] for o in oo]
54
+ pk = np.zeros(P_PICKS, np.int32); pk[: len(picks)] = picks
55
+ common = {"pos": pos.numpy().astype(np.int32), "mask_full": mf.clamp(min=-1e4).numpy().astype(np.float16),
56
+ "mask_slide": ms.clamp(min=-1e4).numpy().astype(np.float16)}
57
+ state = {"emb": e, "ple_tok": p, "picks": pk}
58
+ for a, b, m, ins, outs in self.chunks:
59
+ feed = {k: np.ascontiguousarray(common[k] if k in common else state[k]) for k in ins}
60
+ feed = {k: (v.astype(np.float16) if v.dtype == np.float32 else v) for k, v in feed.items()} # chunk outputs come back fp32
61
+ out = {k: np.array(v, copy=True) for k, v in m.predict(feed).items()} # Core ML may reuse its output buffers on the next call
62
+ if "hidden" in out:
63
+ return torch.tensor(out["hidden"][: len(picks)], dtype=torch.float32)
64
+ state["h_in"] = out["h"]
65
+ if "ple_out" in out:
66
+ state["ple"] = out["ple_out"]
67
+ for k, v in out.items():
68
+ if k.endswith("_out") and k != "ple_out":
69
+ state[k[: -len("_out")]] = v
70
+ raise RuntimeError("no chunk returned hidden")
71
+
72
+ def probs(self, enc):
73
+ h = self.hidden(enc)
74
+ Q = len(enc["decide_idx"])
75
+ out, j = [], Q
76
+ with torch.no_grad():
77
+ for qi, oo in enumerate(enc["opt_idx"]):
78
+ z = (self.hk(h[j: j + len(oo)]) @ self.hq(h[qi])) / (self.hq.out_features ** 0.5)
79
+ out.append(torch.softmax(z / self.T, -1).numpy()); j += len(oo)
80
+ return out
reference/run.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Answer one System One request with a D1A Core ML bundle (this repository, downloaded), on the Neural Engine.
2
+
3
+ pip install coremltools numpy torch transformers safetensors "d1a @ git+https://github.com/jonpol01/d1a"
4
+ python reference/run.py <bundle dir> request.json
5
+
6
+ request.json is a /v1/systemone request: {"state": "...", "questions": {"id": {"type": "noul" | "choice" | "score", ...}}}.
7
+ The request must encode to 512 tokens or fewer. The first run compiles the chunks into coreml/.compiled/ (a few minutes);
8
+ later runs load them. Prints each question's options with their probabilities."""
9
+ import json
10
+ import os
11
+ import sys
12
+
13
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # workers.py and d1a_coreml.py sit next to this file
14
+
15
+
16
+ def main():
17
+ from transformers import AutoTokenizer
18
+
19
+ from d1a.core.api import SystemOneRequest, to_record
20
+ from d1a.core.encoding import encode
21
+ from workers import WorkerPipeline
22
+
23
+ bundle, request = sys.argv[1], sys.argv[2]
24
+ req = SystemOneRequest.model_validate({"model": "d1a-latest", **json.load(open(request))})
25
+ rec, meta = to_record(req)
26
+ enc = encode(AutoTokenizer.from_pretrained(bundle), rec)
27
+ if len(enc["ids"]) > 512:
28
+ raise SystemExit(f"the request encodes to {len(enc['ids'])} tokens; this build takes 512 or fewer")
29
+ pipe = WorkerPipeline(bundle, os.path.join(bundle, "coreml"), 512)
30
+ try:
31
+ for m, p in zip(meta, pipe.probs(enc)):
32
+ print(m["id"], {k: round(float(v), 4) for k, v in zip(m["keys"], p)})
33
+ finally:
34
+ pipe.close()
35
+
36
+
37
+ if __name__ == "__main__":
38
+ main()
reference/workers.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """One process per Core ML chunk (workaround: several long-context chunks in one process crash the Neural Engine runtime
2
+ on the second request on this Mac, while each chunk alone, or all on the GPU, runs fine). The parent runs the host side
3
+ (embeddings, masks, pointer head) and passes tensors along the chain through pipes.
4
+ WorkerPipeline(export_dir, chunk_dir, L, units).probs(enc)"""
5
+ import glob
6
+ import os
7
+ import multiprocessing as mp
8
+ import re
9
+ import shutil
10
+ import sys
11
+
12
+ import numpy as np
13
+ import torch
14
+
15
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # d1a_coreml.py sits next to this file
16
+
17
+
18
+ BUCKETS = tuple(int(x) for x in os.environ.get("D1A_COREML_BUCKETS", "512").split(",")) # each length is its own ANE program; fewer keeps the ANE within its memory
19
+
20
+
21
+ def compiled_chunks(chunk_dir):
22
+ """-> [(first layer, end layer, .mlmodelc, its .mlpackage)]. A released bundle's coreml/L512_<a>-<b>.mlpackage are
23
+ compiled once, each as a one-function package (L512), into coreml/.compiled/; a build tree's compiled/*.mlmodelc
24
+ (multifunction, one function per length) are used as they are."""
25
+ import coremltools as ct
26
+ out = []
27
+ for f in sorted(glob.glob(f"{chunk_dir}/*.mlmodelc")):
28
+ a, b = map(int, re.search(r"(\d+)-(\d+)\.mlmodelc$", f).groups())
29
+ out.append((a, b, f, f.replace("compiled/", "buckets/").replace(".mlmodelc", ".mlpackage")))
30
+ if out:
31
+ return out
32
+ os.makedirs(f"{chunk_dir}/.compiled", exist_ok=True)
33
+ for src in sorted(glob.glob(f"{chunk_dir}/L*_*.mlpackage")):
34
+ L, a, b = map(int, re.search(r"L(\d+)_(\d+)-(\d+)\.mlpackage$", src).groups())
35
+ dst = f"{chunk_dir}/.compiled/{a:02d}-{b:02d}.mlmodelc"
36
+ if not os.path.exists(dst):
37
+ d = ct.utils.MultiFunctionDescriptor(); d.add_function(src, src_function_name="main", target_function_name=f"L{L}")
38
+ d.default_function_name = f"L{L}"; tmp = f"{chunk_dir}/.compiled/{a:02d}-{b:02d}.mlpackage"
39
+ ct.utils.save_multifunction(d, tmp); shutil.move(ct.models.utils.compile_model(tmp), dst); shutil.rmtree(tmp)
40
+ out.append((a, b, dst, src))
41
+ return out
42
+
43
+
44
+ def _worker(path, spec_path, units, conn):
45
+ import coremltools as ct
46
+ cu = getattr(ct.ComputeUnit, units)
47
+ # a precompiled .mlmodelc (compiled once by coremltools.models.utils.compile_model): loading each length function
48
+ # from it never recompiles, where MLModel(.mlpackage) compiles the whole package again for every function
49
+ models = {L: ct.models.CompiledMLModel(path, compute_units=cu, function_name=f"L{L}") for L in BUCKETS}
50
+ spec = ct.utils.load_spec(spec_path)
51
+ fns = {f.name: f for f in spec.description.functions}
52
+ fn = fns.get(f"L{BUCKETS[0]}") or spec.description # a one-function package keeps its inputs in the description
53
+ conn.send(([i.name for i in fn.input], [o.name for o in fn.output]))
54
+ while True:
55
+ msg = conn.recv()
56
+ if msg is None:
57
+ break
58
+ L, feed = msg
59
+ import time as _t
60
+ t0 = _t.perf_counter()
61
+ out = models[L].predict(feed)
62
+ t1 = _t.perf_counter()
63
+ res = {k: np.array(v, dtype=np.float16 if v.dtype == np.float32 else v.dtype, copy=True) for k, v in out.items()}
64
+ res["_predict_ms"] = np.array((t1 - t0) * 1000)
65
+ conn.send(res)
66
+
67
+
68
+ class WorkerPipeline:
69
+ def __init__(self, export_dir, chunk_dir, L=None, units="CPU_AND_NE"):
70
+ from d1a_coreml import Export, HostEmbed
71
+ self.ex = Export(export_dir)
72
+ self.host = HostEmbed(self.ex)
73
+ self.hq, self.hk = self.ex.head()
74
+ self.T, self.L = self.ex.d1a["temperature"], L
75
+ ctx = mp.get_context("spawn")
76
+ self.chunks = []
77
+ for a, b, f, spec_path in compiled_chunks(chunk_dir):
78
+ parent, child = ctx.Pipe()
79
+ p = ctx.Process(target=_worker, args=(f, spec_path, units, child), daemon=True)
80
+ p.start()
81
+ self.chunks.append((a, b, p, parent))
82
+ self.io = getattr(self, "io", []) + [parent.recv()] # one at a time: concurrent first loads race in Core ML's compiler
83
+
84
+ def close(self):
85
+ for _, _, p, conn in self.chunks:
86
+ conn.send(None); p.join(5)
87
+
88
+ def hidden(self, enc):
89
+ from d1a_coreml import masks_for
90
+ self.last_predict_ms = []
91
+ n = len(enc["ids"])
92
+ L = next(b for b in BUCKETS if b >= n) # the smallest compiled length that fits
93
+ ids = list(enc["ids"]) + [self.ex.d1a.get("pad_id", 0)] * (L - n)
94
+ emb, ple = self.host(ids)
95
+ mf, ms, pos = masks_for(enc, self.ex.tc.sliding_window, L=L)
96
+ picks = list(enc["decide_idx"]) + [o for oo in enc["opt_idx"] for o in oo]
97
+ pk = np.zeros(128, np.int32); pk[: len(picks)] = picks
98
+ common = {"pos": pos.numpy().astype(np.int32), "mask_full": mf.clamp(min=-1e4).numpy().astype(np.float16),
99
+ "mask_slide": ms.clamp(min=-1e4).numpy().astype(np.float16)}
100
+ state = {"emb": emb.numpy().astype(np.float16), "ple_tok": ple.numpy().astype(np.float16), "picks": pk}
101
+ for (a, b, p, conn), (ins, outs) in zip(self.chunks, self.io):
102
+ conn.send((L, {k: (common[k] if k in common else state[k]) for k in ins}))
103
+ out = conn.recv()
104
+ self.last_predict_ms = getattr(self, "last_predict_ms", []) + [float(out.pop("_predict_ms"))]
105
+ if "hidden" in out:
106
+ return torch.tensor(out["hidden"][: len(picks)].astype(np.float32))
107
+ state["h_in"] = out["h"]
108
+ if "ple_out" in out:
109
+ state["ple"] = out["ple_out"]
110
+ for k, v in out.items():
111
+ if k.endswith("_out") and k != "ple_out":
112
+ state[k[: -len("_out")]] = v
113
+ raise RuntimeError("no chunk returned hidden")
114
+
115
+ def probs(self, enc):
116
+ h = self.hidden(enc)
117
+ Q = len(enc["decide_idx"])
118
+ out, j = [], Q
119
+ with torch.no_grad():
120
+ for qi, oo in enumerate(enc["opt_idx"]):
121
+ z = (self.hk(h[j: j + len(oo)]) @ self.hq(h[qi])) / (self.hq.out_features ** 0.5)
122
+ out.append(torch.softmax(z / self.T, -1).numpy()); j += len(oo)
123
+ return out
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:12bac982b793c44b03d52a250a9f0d0b666813da566b910c24a6da0695fd11e6
3
+ size 32170070
tokenizer_config.json ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_token": "<|audio|>",
3
+ "backend": "tokenizers",
4
+ "boa_token": "<|audio>",
5
+ "boi_token": "<|image>",
6
+ "bos_token": "<bos>",
7
+ "eoa_token": "<audio|>",
8
+ "eoc_token": "<channel|>",
9
+ "eoi_token": "<image|>",
10
+ "eos_token": "<eos>",
11
+ "eot_token": "<turn|>",
12
+ "escape_token": "<|\"|>",
13
+ "etc_token": "<tool_call|>",
14
+ "etd_token": "<tool|>",
15
+ "etr_token": "<tool_response|>",
16
+ "extra_special_tokens": [
17
+ "<|video|>"
18
+ ],
19
+ "image_token": "<|image|>",
20
+ "is_local": false,
21
+ "local_files_only": false,
22
+ "mask_token": "<mask>",
23
+ "model_max_length": 1000000000000000019884624838656,
24
+ "model_specific_special_tokens": {
25
+ "audio_token": "<|audio|>",
26
+ "boa_token": "<|audio>",
27
+ "boi_token": "<|image>",
28
+ "eoa_token": "<audio|>",
29
+ "eoc_token": "<channel|>",
30
+ "eoi_token": "<image|>",
31
+ "eot_token": "<turn|>",
32
+ "escape_token": "<|\"|>",
33
+ "etc_token": "<tool_call|>",
34
+ "etd_token": "<tool|>",
35
+ "etr_token": "<tool_response|>",
36
+ "image_token": "<|image|>",
37
+ "soc_token": "<|channel>",
38
+ "sot_token": "<|turn>",
39
+ "stc_token": "<|tool_call>",
40
+ "std_token": "<|tool>",
41
+ "str_token": "<|tool_response>",
42
+ "think_token": "<|think|>"
43
+ },
44
+ "pad_token": "<pad>",
45
+ "padding_side": "left",
46
+ "processor_class": "Gemma4Processor",
47
+ "soc_token": "<|channel>",
48
+ "sot_token": "<|turn>",
49
+ "stc_token": "<|tool_call>",
50
+ "std_token": "<|tool>",
51
+ "str_token": "<|tool_response>",
52
+ "think_token": "<|think|>",
53
+ "tokenizer_class": "GemmaTokenizer",
54
+ "unk_token": "<unk>"
55
+ }