alexwengg commited on
Commit
5e32a69
·
verified ·
1 Parent(s): 6874837

d3 Core ML: lite (vllm-sr Decision 3.0 v3.1.0)

Browse files
Files changed (32) hide show
  1. .gitattributes +4 -0
  2. lite/README.md +50 -0
  3. lite/config.json +116 -0
  4. lite/coreml_config.json +312 -0
  5. lite/d3_E1536.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  6. lite/d3_E1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  7. lite/d3_E1536.mlpackage/Manifest.json +18 -0
  8. lite/d3_E256.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  9. lite/d3_E256.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  10. lite/d3_E256.mlpackage/Manifest.json +18 -0
  11. lite/d3_E640.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  12. lite/d3_E640.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  13. lite/d3_E640.mlpackage/Manifest.json +18 -0
  14. lite/embeddings.f16 +3 -0
  15. lite/pos_embed.f32 +3 -0
  16. lite/python/d3_coreml_image.py +128 -0
  17. lite/python/d3_coreml_text.py +80 -0
  18. lite/python/d3_coreml_video.py +122 -0
  19. lite/python/d3_graph.py +120 -0
  20. lite/python/qwen35_export.py +333 -0
  21. lite/python/vision_export.py +230 -0
  22. lite/readout.f32 +3 -0
  23. lite/tokenizer.json +3 -0
  24. lite/vision_P1536.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  25. lite/vision_P1536.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  26. lite/vision_P1536.mlpackage/Manifest.json +18 -0
  27. lite/vision_P3072.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  28. lite/vision_P3072.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  29. lite/vision_P3072.mlpackage/Manifest.json +18 -0
  30. lite/vision_P320.mlpackage/Data/com.apple.CoreML/model.mlmodel +3 -0
  31. lite/vision_P320.mlpackage/Data/com.apple.CoreML/weights/weight.bin +3 -0
  32. lite/vision_P320.mlpackage/Manifest.json +18 -0
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* 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
 
 
 
 
 
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
+ lite/embeddings.f16 filter=lfs diff=lfs merge=lfs -text
37
+ lite/pos_embed.f32 filter=lfs diff=lfs merge=lfs -text
38
+ lite/readout.f32 filter=lfs diff=lfs merge=lfs -text
39
+ lite/tokenizer.json filter=lfs diff=lfs merge=lfs -text
lite/README.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: vllm-sr/d3-lite
4
+ library_name: coreml
5
+ pipeline_tag: zero-shot-classification
6
+ tags:
7
+ - coreml
8
+ - apple-silicon
9
+ - decision-model
10
+ - vision
11
+ - video
12
+ ---
13
+
14
+ # d3-lite — Core ML
15
+
16
+ [vllm-sr/d3-lite](https://huggingface.co/vllm-sr/d3-lite) (0.85B, part of [Decision 3.0](https://huggingface.co/collections/vllm-sr/decision-30) by the vLLM Semantic Router team) converted to Core ML for Apple silicon: text, image and video requests on device. Same contract as upstream: one forward pass per question, the 255-way FP32 readout at the last prompt token, a probability for every option, no text generated.
17
+
18
+ ## Files
19
+
20
+ | File | What |
21
+ |---|---|
22
+ | `d3_E256` / `d3_E640` / `d3_E1536.mlpackage` | text graph (fp16 compute, int8 weights) per prompt-length bucket; input = token embeddings with image/video tokens spliced in |
23
+ | `vision_P320` / `vision_P1536` / `vision_P3072.mlpackage` | vision tower (fp32) per patch bucket; video frames go through it one frame pair at a time (block mask) |
24
+ | `embeddings.f16`, `pos_embed.f32`, `readout.f32`, `tokenizer.json`, `config.json`, `coreml_config.json` | host-side tables and config |
25
+ | `python/` | Python host (coremltools): `d3_coreml_text.py`, `d3_coreml_image.py`, `d3_coreml_video.py` |
26
+
27
+ Swift: `D3VisionManager` in [FluidUse](https://github.com/FluidInference/FluidUse) loads this folder.
28
+
29
+ ## Checked against upstream
30
+
31
+ Same weights as upstream v3.1.0. Answers that differ from the upstream PyTorch runtime (bf16, MPS), same requests:
32
+
33
+ | | flips |
34
+ |---|---|
35
+ | Text (typed-decisions TEST, 20 requests) | 2 / 100 |
36
+ | Images (receipt checks) | 1 / 24 |
37
+ | Video (4-second street clips, 2 fps) | 0 / 28 |
38
+
39
+ Speed on an M5 Pro (24 GB), `CPU_AND_GPU`: ~46 ms per text question, 0.32 s per 4-second video look.
40
+
41
+ ## Python
42
+
43
+ ```python
44
+ from d3_coreml_text import D3CoreMLText # d3_coreml_image.D3CoreMLImage, d3_coreml_video.D3CoreMLVideo
45
+ m = D3CoreMLText("<vllm-sr/d3-lite snapshot>", "<this repo>")
46
+ m.system_one(state="...", questions={"route": {"type": "choice", "criteria": {...}}})
47
+ ```
48
+ The Python host builds prompts with upstream's own runtime (`d3_runtime.py` in the upstream snapshot, run without weights), so prompts match upstream exactly.
49
+
50
+ License: Apache-2.0, as upstream.
lite/config.json ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3_5Model"
4
+ ],
5
+ "dtype": "bfloat16",
6
+ "image_token_id": 248056,
7
+ "model_type": "qwen3_5",
8
+ "text_config": {
9
+ "attention_bias": false,
10
+ "attention_dropout": 0.0,
11
+ "attn_output_gate": true,
12
+ "bos_token_id": null,
13
+ "dtype": "bfloat16",
14
+ "eos_token_id": 248044,
15
+ "full_attention_interval": 4,
16
+ "head_dim": 256,
17
+ "hidden_act": "silu",
18
+ "hidden_size": 1024,
19
+ "initializer_range": 0.02,
20
+ "intermediate_size": 3584,
21
+ "layer_types": [
22
+ "linear_attention",
23
+ "linear_attention",
24
+ "linear_attention",
25
+ "full_attention",
26
+ "linear_attention",
27
+ "linear_attention",
28
+ "linear_attention",
29
+ "full_attention",
30
+ "linear_attention",
31
+ "linear_attention",
32
+ "linear_attention",
33
+ "full_attention",
34
+ "linear_attention",
35
+ "linear_attention",
36
+ "linear_attention",
37
+ "full_attention",
38
+ "linear_attention",
39
+ "linear_attention",
40
+ "linear_attention",
41
+ "full_attention",
42
+ "linear_attention",
43
+ "linear_attention",
44
+ "linear_attention",
45
+ "full_attention"
46
+ ],
47
+ "linear_conv_kernel_dim": 4,
48
+ "linear_key_head_dim": 128,
49
+ "linear_num_key_heads": 16,
50
+ "linear_num_value_heads": 16,
51
+ "linear_value_head_dim": 128,
52
+ "mamba_ssm_dtype": "float32",
53
+ "max_position_embeddings": 262144,
54
+ "mlp_only_layers": [],
55
+ "model_type": "qwen3_5_text",
56
+ "mtp_num_hidden_layers": 1,
57
+ "mtp_use_dedicated_embeddings": false,
58
+ "num_attention_heads": 8,
59
+ "num_hidden_layers": 24,
60
+ "num_key_value_heads": 2,
61
+ "pad_token_id": null,
62
+ "partial_rotary_factor": 0.25,
63
+ "rms_norm_eps": 1e-06,
64
+ "rope_parameters": {
65
+ "mrope_interleaved": true,
66
+ "mrope_section": [
67
+ 11,
68
+ 11,
69
+ 10
70
+ ],
71
+ "partial_rotary_factor": 0.25,
72
+ "rope_theta": 10000000,
73
+ "rope_type": "default"
74
+ },
75
+ "tie_word_embeddings": true,
76
+ "use_cache": true,
77
+ "vocab_size": 248320
78
+ },
79
+ "tie_word_embeddings": true,
80
+ "transformers_version": "5.17.0",
81
+ "video_token_id": 248057,
82
+ "vision_config": {
83
+ "deepstack_visual_indexes": [],
84
+ "depth": 12,
85
+ "hidden_act": "gelu_pytorch_tanh",
86
+ "hidden_size": 768,
87
+ "in_channels": 3,
88
+ "initializer_range": 0.02,
89
+ "intermediate_size": 3072,
90
+ "model_type": "qwen3_5_vision",
91
+ "num_heads": 12,
92
+ "num_position_embeddings": 2304,
93
+ "out_hidden_size": 1024,
94
+ "patch_size": 16,
95
+ "rope_parameters": {
96
+ "rope_theta": 10000.0,
97
+ "rope_type": "axial"
98
+ },
99
+ "spatial_merge_size": 2,
100
+ "temporal_patch_size": 2
101
+ },
102
+ "vision_end_token_id": 248054,
103
+ "vision_start_token_id": 248053,
104
+ "auto_map": {
105
+ "AutoModel": "modeling_d3.D3Model"
106
+ },
107
+ "custom_pipelines": {
108
+ "decision": {
109
+ "impl": "pipeline_d3.D3Pipeline",
110
+ "pt": [
111
+ "AutoModel"
112
+ ],
113
+ "type": "text"
114
+ }
115
+ }
116
+ }
lite/coreml_config.json ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_name": "lite",
3
+ "source": "vllm-sr/lite",
4
+ "codes": [
5
+ "A",
6
+ "B",
7
+ "C",
8
+ "D",
9
+ "E",
10
+ "F",
11
+ "G",
12
+ "H",
13
+ "I",
14
+ "J",
15
+ "K",
16
+ "L",
17
+ "M",
18
+ "N",
19
+ "O",
20
+ "P",
21
+ "Q",
22
+ "R",
23
+ "S",
24
+ "T",
25
+ "U",
26
+ "V",
27
+ "W",
28
+ "X",
29
+ "Y",
30
+ "Z",
31
+ "AA",
32
+ "AB",
33
+ "AC",
34
+ "AD",
35
+ "AE",
36
+ "AF",
37
+ "AG",
38
+ "AH",
39
+ "AI",
40
+ "AJ",
41
+ "AK",
42
+ "AL",
43
+ "AM",
44
+ "AN",
45
+ "AO",
46
+ "AP",
47
+ "AQ",
48
+ "AR",
49
+ "AS",
50
+ "AT",
51
+ "AU",
52
+ "AV",
53
+ "AW",
54
+ "AX",
55
+ "AY",
56
+ "AZ",
57
+ "BA",
58
+ "BB",
59
+ "BC",
60
+ "BD",
61
+ "BE",
62
+ "BF",
63
+ "BG",
64
+ "BH",
65
+ "BI",
66
+ "BJ",
67
+ "BK",
68
+ "BL",
69
+ "BM",
70
+ "BN",
71
+ "BO",
72
+ "BP",
73
+ "BR",
74
+ "BS",
75
+ "BT",
76
+ "BU",
77
+ "BV",
78
+ "BW",
79
+ "BX",
80
+ "BY",
81
+ "CA",
82
+ "CB",
83
+ "CC",
84
+ "CD",
85
+ "CE",
86
+ "CF",
87
+ "CG",
88
+ "CH",
89
+ "CI",
90
+ "CK",
91
+ "CL",
92
+ "CM",
93
+ "CN",
94
+ "CO",
95
+ "CP",
96
+ "CR",
97
+ "CS",
98
+ "CT",
99
+ "CU",
100
+ "CV",
101
+ "CW",
102
+ "CX",
103
+ "CY",
104
+ "DA",
105
+ "DB",
106
+ "DC",
107
+ "DD",
108
+ "DE",
109
+ "DF",
110
+ "DG",
111
+ "DH",
112
+ "DI",
113
+ "DJ",
114
+ "DK",
115
+ "DL",
116
+ "DM",
117
+ "DN",
118
+ "DO",
119
+ "DP",
120
+ "DR",
121
+ "DS",
122
+ "DT",
123
+ "DU",
124
+ "DV",
125
+ "DW",
126
+ "DX",
127
+ "DY",
128
+ "EA",
129
+ "EB",
130
+ "EC",
131
+ "ED",
132
+ "EE",
133
+ "EF",
134
+ "EG",
135
+ "EH",
136
+ "EI",
137
+ "EK",
138
+ "EL",
139
+ "EM",
140
+ "EN",
141
+ "EO",
142
+ "EP",
143
+ "EQ",
144
+ "ER",
145
+ "ES",
146
+ "ET",
147
+ "EU",
148
+ "EV",
149
+ "EW",
150
+ "EX",
151
+ "EZ",
152
+ "FA",
153
+ "FB",
154
+ "FC",
155
+ "FD",
156
+ "FE",
157
+ "FF",
158
+ "FG",
159
+ "FH",
160
+ "FI",
161
+ "FK",
162
+ "FL",
163
+ "FM",
164
+ "FN",
165
+ "FO",
166
+ "FP",
167
+ "FR",
168
+ "FS",
169
+ "FT",
170
+ "FU",
171
+ "FW",
172
+ "FX",
173
+ "FY",
174
+ "GA",
175
+ "GB",
176
+ "GC",
177
+ "GD",
178
+ "GE",
179
+ "GF",
180
+ "GG",
181
+ "GH",
182
+ "GI",
183
+ "GL",
184
+ "GM",
185
+ "GN",
186
+ "GO",
187
+ "GP",
188
+ "GR",
189
+ "GS",
190
+ "GT",
191
+ "GU",
192
+ "GV",
193
+ "GW",
194
+ "GX",
195
+ "GY",
196
+ "HA",
197
+ "HB",
198
+ "HC",
199
+ "HD",
200
+ "HE",
201
+ "HF",
202
+ "HG",
203
+ "HH",
204
+ "HI",
205
+ "HK",
206
+ "HL",
207
+ "HM",
208
+ "HN",
209
+ "HO",
210
+ "HP",
211
+ "HQ",
212
+ "HR",
213
+ "HS",
214
+ "HT",
215
+ "HU",
216
+ "HV",
217
+ "HW",
218
+ "HX",
219
+ "HY",
220
+ "HZ",
221
+ "IA",
222
+ "IB",
223
+ "IC",
224
+ "ID",
225
+ "IE",
226
+ "IF",
227
+ "IG",
228
+ "IH",
229
+ "II",
230
+ "IJ",
231
+ "IK",
232
+ "IL",
233
+ "IM",
234
+ "IN",
235
+ "IO",
236
+ "IP",
237
+ "IQ",
238
+ "IR",
239
+ "IS",
240
+ "IT",
241
+ "IU",
242
+ "IV",
243
+ "IW",
244
+ "IX",
245
+ "IZ",
246
+ "JA",
247
+ "JB",
248
+ "JC",
249
+ "JD",
250
+ "JE",
251
+ "JI",
252
+ "JJ",
253
+ "JK",
254
+ "JM",
255
+ "JO",
256
+ "JP",
257
+ "JR",
258
+ "JS",
259
+ "JT"
260
+ ],
261
+ "temperature": 1.0,
262
+ "pad_token_id": 248044,
263
+ "image_token_id": 248056,
264
+ "vision_start_token_id": 248053,
265
+ "vision_end_token_id": 248054,
266
+ "hidden_size": 1024,
267
+ "vocab_size": 248320,
268
+ "rope": {
269
+ "theta": 10000000,
270
+ "rotary_dim": 64,
271
+ "mrope_section": [
272
+ 11,
273
+ 11,
274
+ 10
275
+ ]
276
+ },
277
+ "vision": {
278
+ "patch_size": 16,
279
+ "merge_size": 2,
280
+ "temporal_patch_size": 2,
281
+ "hidden_size": 768,
282
+ "num_heads": 12,
283
+ "out_hidden_size": 1024,
284
+ "num_position_embeddings": 2304,
285
+ "rope_theta": 10000.0
286
+ },
287
+ "image": {
288
+ "min_pixels": 65536,
289
+ "max_pixels": 1638400,
290
+ "mean": [
291
+ 0.5,
292
+ 0.5,
293
+ 0.5
294
+ ],
295
+ "std": [
296
+ 0.5,
297
+ 0.5,
298
+ 0.5
299
+ ],
300
+ "rescale": 0.00392156862745098
301
+ },
302
+ "vision_buckets": [
303
+ 320,
304
+ 1536,
305
+ 3072
306
+ ],
307
+ "text_buckets": [
308
+ 256,
309
+ 640,
310
+ 1536
311
+ ]
312
+ }
lite/d3_E1536.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06625e4407aba711b4d13c0caaa85ea5d11d573d6f7e4245b62b52c49a960534
3
+ size 4539378
lite/d3_E1536.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:1570efcc188e884763f440b4f4780383b2b27b7286ff113a0c7b2f8dcdb83ddf
3
+ size 500897664
lite/d3_E1536.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "0F786AA6-669F-4BB1-9F0F-9BE9E09F20EF": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "78F6A973-8C96-4F9B-B548-288DFCBD0406": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "0F786AA6-669F-4BB1-9F0F-9BE9E09F20EF"
18
+ }
lite/d3_E256.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9886980c329516c7732b7c07f21cd480bc6b953a2091876743b5b39799848489
3
+ size 1944915
lite/d3_E256.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:a5565253ea689f841b849230ed43061d12f993c0d6c293627cef497cf7d64787
3
+ size 500221312
lite/d3_E256.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "5A657C48-D6B0-4EC9-A96E-CE6DDEFEC813": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "AFC1813F-EB9F-47B9-A90B-AAF702E6BD57": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "5A657C48-D6B0-4EC9-A96E-CE6DDEFEC813"
18
+ }
lite/d3_E640.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4c1ef6632dc8144243c270a18f1a4a27c519dd21a98d0f25f443300a325484c8
3
+ size 2722606
lite/d3_E640.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:7c18d2ca33683162515a27f22c2ce7cec5832eef740a7eb165a6ffcdb020a7e7
3
+ size 500423680
lite/d3_E640.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "176FF80A-94E3-4B6C-82DB-0642EF7BE052": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "F528A27F-912C-4218-ACEE-AE0622862F5D": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "176FF80A-94E3-4B6C-82DB-0642EF7BE052"
18
+ }
lite/embeddings.f16 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dd7ed88d1e676ff4a3dc46d25d4751b1e465540a31522aba620a4948878e14e9
3
+ size 508559360
lite/pos_embed.f32 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9a883241c3d1f55bbb10b1aca1202f463dc0ebe0efba8d11c4d00aaf1e95a4db
3
+ size 7077888
lite/python/d3_coreml_image.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """d3 image requests on Core ML (Python host): upstream prompt, processor and answer code; Core ML vision tower
2
+ (fp32) + text graph (fp16, host-spliced embeddings, host M-RoPE); FP32 readout on the host.
3
+
4
+ m = D3CoreMLImage("<d3 checkpoint dir>", "<coreml dir: Vision_P*, d3_E*.mlpackage, embeddings.f16>")
5
+ m.system_one(state=..., images=[...], questions={...}) # same answers dict as upstream
6
+
7
+ Positions come from transformers' Qwen3_5Model.compute_3d_position_ids on a weightless (meta) model."""
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import coremltools as ct
13
+ import numpy as np
14
+ import torch
15
+
16
+ HERE = Path(__file__).parent
17
+
18
+
19
+ class D3CoreMLImage:
20
+ def __init__(self, root, coreml_dir, compute_units=ct.ComputeUnit.CPU_AND_GPU):
21
+ root, cdir = Path(root), Path(coreml_dir)
22
+ sys.path.insert(0, str(root))
23
+ sys.path.insert(0, str(HERE.parent / "vision"))
24
+ sys.path.insert(0, str(HERE.parent))
25
+ import d3_runtime as up
26
+ from qwen35_export import rope_cos_sin
27
+ from transformers import AutoConfig
28
+ from transformers.models.qwen3_5.modeling_qwen3_5 import Qwen3_5Model
29
+ from vision_export import VisionConfig
30
+ from d3_graph import load_text_config
31
+
32
+ self.up, self.rope_cos_sin = up, rope_cos_sin
33
+ config = json.loads((root / "decision_config.json").read_text())
34
+ self.codes, self.temperature = config["codes"], float(config.get("temperature", 1.0))
35
+ self.processor = up.load_processor(root)
36
+ self.vcfg = VisionConfig(json.loads((root / "config.json").read_text())["vision_config"])
37
+ self.tcfg = load_text_config(str(root))
38
+ with torch.device("meta"):
39
+ self.positions = Qwen3_5Model(AutoConfig.from_pretrained(str(root)))
40
+ from safetensors import safe_open
41
+ self.pos_table = None
42
+ for path in sorted(root.glob("model*.safetensors")):
43
+ with safe_open(str(path), "pt") as f:
44
+ if "visual.pos_embed.weight" in f.keys():
45
+ self.pos_table = f.get_tensor("visual.pos_embed.weight").float()
46
+ from safetensors.torch import load_file
47
+ self.readout = load_file(str(root / "readout.safetensors"))["weight"].float().numpy()
48
+ H = self.tcfg.hidden_size
49
+ self.embed = np.memmap(cdir / "embeddings.f16", dtype=np.float16, mode="r").reshape(-1, H)
50
+ self.image_token = AutoConfig.from_pretrained(str(root)).image_token_id
51
+ self.vision = {int(p.name[8:]): ct.models.MLModel(str(p / "VisionTower_fp32.mlpackage"), compute_units=compute_units)
52
+ for p in sorted(cdir.glob("Vision_P*"))}
53
+ multi = cdir / "d3_text.mlpackage" # one package, functions E{L} sharing the weights
54
+ if multi.exists():
55
+ names = json.loads((cdir / "d3_text.functions.json").read_text())
56
+ self.text = {int(n[1:]): ct.models.MLModel(str(multi), compute_units=compute_units, function_name=n)
57
+ for n in names}
58
+ else:
59
+ self.text = {int(p.stem[4:]): ct.models.MLModel(str(p), compute_units=compute_units)
60
+ for p in sorted(cdir.glob("d3_E*.mlpackage"))}
61
+ self.model_name = root.name + "-coreml"
62
+
63
+ def warm(self):
64
+ """Run every vision and text bucket once: the first call per bucket pays the GPU setup (seconds)."""
65
+ for N, model in self.vision.items():
66
+ model.predict({"patches": np.zeros((N, self.vcfg.patch_dim), np.float32),
67
+ "pos_embeds": np.zeros((N, self.vcfg.hidden), np.float32),
68
+ "cos": np.zeros((N, self.vcfg.head_dim), np.float32),
69
+ "sin": np.zeros((N, self.vcfg.head_dim), np.float32),
70
+ "mask": np.zeros((1, 1, N, N), np.float32)})
71
+ H, R = self.tcfg.hidden_size, self.tcfg.rotary_dim
72
+ for L, model in self.text.items():
73
+ model.predict({"hidden": np.zeros((1, L, H), np.float16), "cos": np.ones((L, R), np.float16),
74
+ "sin": np.zeros((L, R), np.float16), "key_valid": np.ones(L, np.float16),
75
+ "last_onehot": np.eye(1, L, L - 1, dtype=np.float16)})
76
+
77
+ def _tokens(self, pv, grid):
78
+ from vision_export import prepare_inputs
79
+ N = next(b for b in sorted(self.vision) if b >= pv.shape[0])
80
+ patches, pe, cs, sn, mask, n = prepare_inputs(self.vcfg, self.pos_table, pv, grid, N)
81
+ out = self.vision[N].predict({"patches": patches.numpy(), "pos_embeds": pe.numpy(), "cos": cs.numpy(),
82
+ "sin": sn.numpy(), "mask": mask.numpy()})["tokens"]
83
+ return out[: n // 4]
84
+
85
+ def _probs(self, text, images, count, image_tokens):
86
+ enc = self.processor(text=[text], images=images, return_tensors="pt")
87
+ ids = enc["input_ids"][0].numpy()
88
+ hidden = self.embed[ids].astype(np.float32)
89
+ rows = np.where(ids == self.image_token)[0]
90
+ hidden[rows] = np.concatenate(image_tokens)
91
+ with torch.no_grad():
92
+ pos = self.positions.compute_3d_position_ids(
93
+ input_ids=enc["input_ids"], inputs_embeds=None, image_grid_thw=enc["image_grid_thw"],
94
+ attention_mask=enc["attention_mask"], mm_token_type_ids=enc["mm_token_type_ids"])[:, 0]
95
+ n = len(ids)
96
+ L = next((b for b in sorted(self.text) if b >= n), None)
97
+ if L is None:
98
+ raise ValueError(f"{n} tokens exceed the largest bucket {max(self.text)}")
99
+ cos, sin = self.rope_cos_sin(self.tcfg, torch.cat([pos, pos[:, -1:].expand(3, L - n)], dim=1))
100
+ x = np.zeros((1, L, hidden.shape[1]), dtype=np.float16); x[0, :n] = hidden
101
+ valid = np.zeros(L, dtype=np.float16); valid[:n] = 1
102
+ last = np.zeros((1, L), dtype=np.float16); last[0, n - 1] = 1
103
+ h = self.text[L].predict({"hidden": x, "cos": cos.numpy().astype(np.float16), "sin": sin.numpy().astype(np.float16),
104
+ "key_valid": valid, "last_onehot": last})["final"].reshape(-1)
105
+ logits = self.readout[:count].astype(np.float64) @ h.astype(np.float64) / self.temperature
106
+ p = np.exp(logits - logits.max())
107
+ return (p / p.sum()).tolist(), n
108
+
109
+ def system_one(self, *, state, questions, images):
110
+ up = self.up
111
+ decoded = [up.load_image(v) for v in images]
112
+ enc = self.processor.image_processor(images=decoded, return_tensors="pt")
113
+ # one vision call per image; every question reuses the image tokens
114
+ tokens, start = [], 0
115
+ for k, grid in enumerate(enc["image_grid_thw"]):
116
+ n = int(grid.prod())
117
+ tokens.append(self._tokens(enc["pixel_values"][start:start + n], grid[None]))
118
+ start += n
119
+ answers, used = {}, 0
120
+ for key, q in questions.items():
121
+ question = up.normalize_question(q)
122
+ text = self.processor.apply_chat_template(
123
+ up.image_messages("d3", state, question.rendered, self.codes, len(decoded)),
124
+ tokenize=False, add_generation_prompt=True, enable_thinking=False)
125
+ probs, n = self._probs(text, decoded, len(question.keys), tokens)
126
+ used += n
127
+ answers[key] = up.product_answer(question, probs)
128
+ return {"model": self.model_name, "answers": answers, "usage": {"input_tokens": used, "output_tokens": 0}}
lite/python/d3_coreml_text.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """d3 text requests on Core ML (Python host), for the checkpoints whose text graph takes embeddings (d3-mini).
2
+
3
+ Upstream's own request preparation runs on a weightless (meta-device) d3, so prompts and token ids are exactly
4
+ upstream's; Core ML runs the text graph per question (host embedding gather, text positions on all three M-RoPE axes);
5
+ FP32 readout on the host.
6
+
7
+ m = D3CoreMLText("<d3 checkpoint>", "<coreml dir>", text_buckets=[256, 640])
8
+ m.system_one(state=..., questions={...})"""
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+
13
+ import coremltools as ct
14
+ import numpy as np
15
+ import torch
16
+
17
+ HERE = Path(__file__).parent
18
+
19
+
20
+ class D3CoreMLText:
21
+ def __init__(self, root, coreml_dir, text_buckets=None, compute_units=ct.ComputeUnit.CPU_AND_GPU):
22
+ root, cdir = Path(root), Path(coreml_dir)
23
+ sys.path.insert(0, str(root))
24
+ sys.path.insert(0, str(HERE.parent))
25
+ import d3_runtime as up
26
+ from d3_graph import load_text_config
27
+ from qwen35_export import rope_cos_sin
28
+ from safetensors.torch import load_file
29
+
30
+ self.up, self.rope_cos_sin = up, rope_cos_sin
31
+ self.prep = up.D3.from_pretrained(str(root), device="meta", verify="none") # no weights: prompts only
32
+ self.temperature = self.prep.temperature
33
+ self.tcfg = load_text_config(str(root))
34
+ self.readout = load_file(str(root / "readout.safetensors"))["weight"].float().numpy()
35
+ self.embed = np.memmap(cdir / "embeddings.f16", dtype=np.float16, mode="r").reshape(-1, self.tcfg.hidden_size)
36
+ self.text = {int(p.stem[4:]): ct.models.MLModel(str(p), compute_units=compute_units)
37
+ for p in sorted(cdir.glob("d3_E*.mlpackage")) if not text_buckets or int(p.stem[4:]) in text_buckets}
38
+ self.pad = self.prep.pad_id
39
+ self.model_name = root.name + "-coreml"
40
+ self.timing = []
41
+
42
+ def warm(self):
43
+ for L, model in self.text.items():
44
+ for _ in range(2):
45
+ self._final(list(range(10)), L)
46
+
47
+ def _final(self, ids, L):
48
+ n = len(ids)
49
+ x = np.zeros((1, L, self.tcfg.hidden_size), dtype=np.float16)
50
+ x[0, :n] = self.embed[np.asarray(ids)]
51
+ x[0, n:] = self.embed[self.pad]
52
+ pos = torch.arange(L).clamp(max=n - 1)[None].expand(3, -1)
53
+ cos, sin = self.rope_cos_sin(self.tcfg, pos)
54
+ valid = np.zeros(L, dtype=np.float16); valid[:n] = 1
55
+ last = np.zeros((1, L), dtype=np.float16); last[0, n - 1] = 1
56
+ return self.text[L].predict({"hidden": x, "cos": cos.numpy().astype(np.float16),
57
+ "sin": sin.numpy().astype(np.float16), "key_valid": valid,
58
+ "last_onehot": last})["final"].reshape(-1)
59
+
60
+ def system_one(self, *, state, questions):
61
+ prepared = self.prep.prepare(state, questions)
62
+ answers, used, self.timing = {}, 0, []
63
+ for key in prepared.keys:
64
+ if key in prepared.errors:
65
+ answers[key] = prepared.errors[key]
66
+ continue
67
+ ids, question = prepared.sequences[key], prepared.questions[key]
68
+ L = next((b for b in sorted(self.text) if b >= len(ids)), None)
69
+ if L is None:
70
+ answers[key] = {"type": question.kind, "error": "max_length_exceeded",
71
+ "message": f"{len(ids)} tokens exceed the largest Core ML bucket {max(self.text)}"}
72
+ continue
73
+ t = time.perf_counter()
74
+ h = self._final(ids, L)
75
+ self.timing.append((len(ids), L, round(time.perf_counter() - t, 3)))
76
+ logits = self.readout[:len(question.keys)].astype(np.float64) @ h.astype(np.float64) / self.temperature
77
+ p = np.exp(logits - logits.max())
78
+ answers[key] = self.up.product_answer(question, (p / p.sum()).tolist())
79
+ used += len(ids)
80
+ return {"model": self.model_name, "answers": answers, "usage": {"input_tokens": used, "output_tokens": 0}}
lite/python/d3_coreml_video.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """d3 video requests on Core ML (Python host).
2
+
3
+ Upstream's own request preparation runs on a weightless (meta-device) d3, so frame sampling, the video processor,
4
+ the timestamped placeholders and the 3-D positions are exactly upstream's. Core ML runs the compute: the fp32 vision
5
+ tower once per video (attention within each frame pair, as upstream: one block per temporal group), then the text
6
+ graph per question with the video tokens spliced in; FP32 readout on the host.
7
+
8
+ m = D3CoreMLVideo("<d3 checkpoint>", "<coreml dir>", max_pixels=100_352)
9
+ m.system_one(state=..., questions={...}, videos=[frames]) # frames: uint8 [n, h, w, 3] at 2 fps"""
10
+ import json
11
+ import sys
12
+ import time
13
+ from pathlib import Path
14
+
15
+ import coremltools as ct
16
+ import numpy as np
17
+ import torch
18
+
19
+ HERE = Path(__file__).parent
20
+
21
+
22
+ class D3CoreMLVideo:
23
+ def __init__(self, root, coreml_dir, max_pixels=None, vision_buckets=None, text_buckets=None,
24
+ compute_units=ct.ComputeUnit.CPU_AND_GPU):
25
+ """`vision_buckets` / `text_buckets`: load only these sizes (each loaded model costs memory)."""
26
+ root, cdir = Path(root), Path(coreml_dir)
27
+ sys.path.insert(0, str(root))
28
+ sys.path.insert(0, str(HERE.parent / "vision"))
29
+ sys.path.insert(0, str(HERE.parent))
30
+ import d3_runtime as up
31
+ if max_pixels:
32
+ up.VIDEO_MAX_PIXELS = max_pixels
33
+ from d3_graph import load_text_config
34
+ from qwen35_export import rope_cos_sin
35
+ from safetensors import safe_open
36
+ from safetensors.torch import load_file
37
+ from vision_export import VisionConfig
38
+
39
+ self.up, self.rope_cos_sin = up, rope_cos_sin
40
+ self.prep = up.D3.from_pretrained(str(root), device="meta", verify="none") # no weights: prompts only
41
+ self.processor, self.temperature = self.prep.processor, self.prep.temperature
42
+ self.backbone = self.prep.backbone
43
+ self.vcfg = VisionConfig(json.loads((root / "config.json").read_text())["vision_config"])
44
+ self.tcfg = load_text_config(str(root))
45
+ for path in sorted(root.glob("model*.safetensors")):
46
+ with safe_open(str(path), "pt") as f:
47
+ if "visual.pos_embed.weight" in f.keys():
48
+ self.pos_table = f.get_tensor("visual.pos_embed.weight").float()
49
+ self.readout = load_file(str(root / "readout.safetensors"))["weight"].float().numpy()
50
+ H = self.tcfg.hidden_size
51
+ self.embed = np.memmap(cdir / "embeddings.f16", dtype=np.float16, mode="r").reshape(-1, H)
52
+ self.video_token = self.backbone.config.video_token_id
53
+ self.vision = {int(p.name[8:]): ct.models.MLModel(str(p / "VisionTower_fp32.mlpackage"), compute_units=compute_units)
54
+ for p in sorted(cdir.glob("Vision_P*")) if not vision_buckets or int(p.name[8:]) in vision_buckets}
55
+ self.text = {int(p.stem[4:]): ct.models.MLModel(str(p), compute_units=compute_units)
56
+ for p in sorted(cdir.glob("d3_E*.mlpackage")) if not text_buckets or int(p.stem[4:]) in text_buckets}
57
+ self.model_name = root.name + "-coreml"
58
+ self.timing = {}
59
+
60
+ def warm(self, frames, state, questions):
61
+ for _ in range(2):
62
+ self.system_one(state=state, questions=questions, videos=[frames])
63
+
64
+ def _video_tokens(self, pixel_values, grid):
65
+ """Vision tower for one video: attention within each temporal group (frame pair), padding masked."""
66
+ from vision_export import prepare_inputs
67
+ n = pixel_values.shape[0]
68
+ N = next(b for b in sorted(self.vision) if b >= n)
69
+ patches, pe, cs, sn, mask, n = prepare_inputs(self.vcfg, self.pos_table, pixel_values, grid, N)
70
+ t, h, w = (int(x) for x in grid.reshape(-1))
71
+ block = np.full((N, N), -1e4, dtype=np.float32)
72
+ for g in range(t): # rows are t-major: each frame pair is h*w consecutive patches
73
+ s = g * h * w
74
+ block[s:s + h * w, s:s + h * w] = 0
75
+ out = self.vision[N].predict({"patches": patches.numpy(), "pos_embeds": pe.numpy(), "cos": cs.numpy(),
76
+ "sin": sn.numpy(), "mask": block[None, None]})["tokens"]
77
+ return out[: n // 4]
78
+
79
+ def system_one(self, *, state, questions, videos):
80
+ up, proc = self.up, self.processor
81
+ t0 = time.perf_counter()
82
+ prepared = self.prep.prepare(state, questions, videos=videos)
83
+ media = prepared.media
84
+ grids = media["video_grid_thw"]
85
+ tokens, start = [], 0
86
+ for grid in grids:
87
+ n = int(grid.prod())
88
+ tokens.append(self._video_tokens(media["pixel_values_videos"][start:start + n], grid[None]))
89
+ start += n
90
+ tokens = np.concatenate(tokens)
91
+ t1 = time.perf_counter()
92
+ answers, used = {}, 0
93
+ for key in prepared.runnable:
94
+ question = prepared.questions[key]
95
+ expanded = proc.get_text_with_replacements([prepared.texts[key]], [], prepared.video_texts)[0][0]
96
+ enc = proc.tokenizer([expanded], return_token_type_ids=False, return_tensors="pt")
97
+ ids = enc["input_ids"]
98
+ types = torch.as_tensor(proc.create_mm_token_type_ids(ids))
99
+ row = ids[0].numpy()
100
+ hidden = self.embed[row].astype(np.float32)
101
+ hidden[np.where(row == self.video_token)[0]] = tokens
102
+ with torch.no_grad():
103
+ pos = self.backbone.compute_3d_position_ids(
104
+ input_ids=ids, inputs_embeds=None, image_grid_thw=None, video_grid_thw=grids,
105
+ attention_mask=enc["attention_mask"], past_key_values=None, mm_token_type_ids=types)[:, 0]
106
+ n = len(row)
107
+ L = next((b for b in sorted(self.text) if b >= n), None)
108
+ if L is None:
109
+ raise ValueError(f"{n} tokens exceed the largest text bucket {max(self.text)}")
110
+ cos, sin = self.rope_cos_sin(self.tcfg, torch.cat([pos, pos[:, -1:].expand(3, L - n)], dim=1))
111
+ x = np.zeros((1, L, hidden.shape[1]), dtype=np.float16); x[0, :n] = hidden
112
+ valid = np.zeros(L, dtype=np.float16); valid[:n] = 1
113
+ last = np.zeros((1, L), dtype=np.float16); last[0, n - 1] = 1
114
+ h = self.text[L].predict({"hidden": x, "cos": cos.numpy().astype(np.float16),
115
+ "sin": sin.numpy().astype(np.float16), "key_valid": valid,
116
+ "last_onehot": last})["final"].reshape(-1)
117
+ logits = self.readout[:len(question.keys)].astype(np.float64) @ h.astype(np.float64) / self.temperature
118
+ p = np.exp(logits - logits.max())
119
+ answers[key] = up.product_answer(question, (p / p.sum()).tolist())
120
+ used += n
121
+ self.timing = {"prepare+vision": round(t1 - t0, 3), "text": round(time.perf_counter() - t1, 3)}
122
+ return {"model": self.model_name, "answers": answers, "usage": {"input_tokens": used, "output_tokens": 0}}
lite/python/d3_graph.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """d3-lite (Qwen3.5-0.8B, code readout v1) as one Core ML row graph.
2
+
3
+ One question per call, as upstream: the row is right-padded to the bucket length L. The 6 full-attention layers are
4
+ noncausal (decision_config `noncausal_full_attention`): they see every real token and mask only the padding keys. The
5
+ Gated DeltaNet layers are causal scans, so padding after the last real token cannot reach it. Output is the final-normed
6
+ hidden state at the last real token; the 255 x 1024 FP32 readout runs on the host, as upstream.
7
+ """
8
+
9
+ import json
10
+
11
+ import numpy as np
12
+ import torch
13
+ from safetensors.torch import load_file
14
+ from torch import nn
15
+
16
+ from qwen35_export import DecoderLayer, RMSNorm, TextConfig, rope_cos_sin
17
+
18
+ MASK_VALUE = -1e4
19
+
20
+
21
+ class D3Row(nn.Module):
22
+ def __init__(self, cfg: TextConfig, seq_len: int, chunk: int = 64):
23
+ super().__init__()
24
+ self.cfg, self.L = cfg, seq_len
25
+ self.embed_tokens = nn.Embedding(1, cfg.hidden_size) # replaced on load
26
+ self.layers = nn.ModuleList([DecoderLayer(cfg, i, seq_len, chunk) for i in range(cfg.num_layers)])
27
+ self.norm = RMSNorm(cfg.hidden_size, cfg.eps)
28
+
29
+ def forward(self, input_ids, cos, sin, key_valid, last_onehot):
30
+ """input_ids [1, L] int32; cos/sin [L, R]; key_valid [L] (1 real, 0 pad); last_onehot [1, L] -> hidden [1, H]."""
31
+ mask = ((1.0 - key_valid) * MASK_VALUE)[None, None, None, :]
32
+ x = self.embed_tokens(input_ids)
33
+ for layer in self.layers:
34
+ x = layer(x, cos, sin, mask)
35
+ return self.norm(torch.matmul(last_onehot, x[0]))
36
+
37
+
38
+ class D3RowEmbeds(nn.Module):
39
+ """D3Row for image requests: the host gathers token embeddings and splices the vision tower's tokens in at the
40
+ image-pad positions, and builds cos/sin from the 3-D M-RoPE positions (text and image tokens differ)."""
41
+
42
+ def __init__(self, cfg: TextConfig, seq_len: int, chunk: int = 64):
43
+ super().__init__()
44
+ self.cfg, self.L = cfg, seq_len
45
+ self.layers = nn.ModuleList([DecoderLayer(cfg, i, seq_len, chunk) for i in range(cfg.num_layers)])
46
+ self.norm = RMSNorm(cfg.hidden_size, cfg.eps)
47
+
48
+ def forward(self, hidden, cos, sin, key_valid, last_onehot):
49
+ """hidden [1, L, H]; cos/sin [L, R]; key_valid [L]; last_onehot [1, L] -> final hidden [1, H]."""
50
+ mask = ((1.0 - key_valid) * MASK_VALUE)[None, None, None, :]
51
+ x = hidden
52
+ for layer in self.layers:
53
+ x = layer(x, cos, sin, mask)
54
+ return self.norm(torch.matmul(last_onehot, x[0]))
55
+
56
+
57
+ def load_row_embeds(root: str, seq_len: int, chunk: int = 64, dtype=torch.float32) -> tuple[D3RowEmbeds, torch.Tensor]:
58
+ """(graph, embedding table [V, H]). dtype=float16 halves peak memory for tracing the larger checkpoints (d3-mini:
59
+ fp32 weights plus their copy do not fit in 24 GB); weights are assigned in place, not copied."""
60
+ cfg = load_text_config(root)
61
+ prefix = "language_model."
62
+ state = {}
63
+ from safetensors import safe_open
64
+ files = sorted(__import__("glob").glob(f"{root}/model*.safetensors"))
65
+ for path in files:
66
+ with safe_open(path, "pt") as f:
67
+ for k in f.keys():
68
+ if k.startswith(prefix):
69
+ state[k[len(prefix):]] = f.get_tensor(k).to(dtype)
70
+ embed = state.pop("embed_tokens.weight")
71
+ model = D3RowEmbeds(cfg, seq_len, chunk).to(dtype)
72
+ missing, unexpected = model.load_state_dict(state, strict=False, assign=True)
73
+ if missing or unexpected:
74
+ raise RuntimeError(f"weight mismatch: missing={missing[:8]} unexpected={unexpected[:8]}")
75
+ return model.eval(), embed
76
+
77
+
78
+ def load_text_config(root: str) -> TextConfig:
79
+ return TextConfig(json.load(open(f"{root}/config.json"))["text_config"])
80
+
81
+
82
+ def load_row(root: str, seq_len: int, chunk: int = 64) -> D3Row:
83
+ cfg = load_text_config(root)
84
+ weights = load_file(f"{root}/model.safetensors")
85
+ prefix = "language_model."
86
+ state = {k[len(prefix):]: v.float() for k, v in weights.items() if k.startswith(prefix)}
87
+ model = D3Row(cfg, seq_len, chunk)
88
+ model.embed_tokens = nn.Embedding.from_pretrained(state.pop("embed_tokens.weight"), freeze=True)
89
+ missing, unexpected = model.load_state_dict(state, strict=False)
90
+ missing = [m for m in missing if not m.startswith("embed_tokens")]
91
+ if missing or unexpected:
92
+ raise RuntimeError(f"weight mismatch: missing={missing[:8]} unexpected={unexpected[:8]}")
93
+ return model.eval()
94
+
95
+
96
+ def load_readout(root: str) -> np.ndarray:
97
+ return load_file(f"{root}/readout.safetensors")["weight"].float().numpy()
98
+
99
+
100
+ def row_inputs(cfg: TextConfig, ids, L: int, pad_id: int) -> dict[str, np.ndarray]:
101
+ n = len(ids)
102
+ if not 1 <= n <= L:
103
+ raise ValueError(f"{n} tokens do not fit L{L}")
104
+ input_ids = np.full((1, L), pad_id, dtype=np.int32)
105
+ input_ids[0, :n] = ids
106
+ pos = torch.arange(L)
107
+ pos[n:] = n - 1
108
+ cos, sin = rope_cos_sin(cfg, pos[None].expand(3, -1))
109
+ valid = np.zeros(L, dtype=np.float32)
110
+ valid[:n] = 1
111
+ last = np.zeros((1, L), dtype=np.float32)
112
+ last[0, n - 1] = 1
113
+ return {"input_ids": input_ids, "cos": cos.numpy(), "sin": sin.numpy(), "key_valid": valid, "last_onehot": last}
114
+
115
+
116
+ def code_probabilities(hidden: np.ndarray, readout: np.ndarray, count: int, temperature: float = 1.0) -> np.ndarray:
117
+ logits = hidden.astype(np.float64).reshape(-1) @ readout[:count].astype(np.float64).T / temperature
118
+ logits -= logits.max()
119
+ p = np.exp(logits)
120
+ return p / p.sum()
lite/python/qwen35_export.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Trace-friendly, prefill-only Qwen3.5 text decoder (copied from models/computer-use/cua-s1-4b/coreml, mobius PR #104).
2
+
3
+ Cua-S1-4B reads one forward pass: the logits of the answer letters A..Z at the
4
+ last prompt position. Nothing is generated, so this graph has no KV cache, no
5
+ recurrent-state I/O and no full vocabulary head:
6
+
7
+ - inputs are `inputs_embeds` (host-side embedding gather, so a multimodal
8
+ caller can splice vision features in) plus host-computed M-RoPE `cos`/`sin`;
9
+ - prompts are right-padded to a fixed bucket; the model is causal and the gated
10
+ delta rule is a forward scan, so padding after the last real token cannot
11
+ change anything before it;
12
+ - the last chunk gathers the hidden state at `last_index` (one-hot matmul),
13
+ applies the final norm and multiplies by the 26 tied-embedding rows of the
14
+ letter tokens.
15
+
16
+ The gated delta rule uses the chunked (WY/UT) form. The unit-lower-triangular
17
+ solve is a recursive 2x2 block inverse (log2(chunk) batched matmul levels). The
18
+ nilpotent power series (I + N)(I + N^2)... is exact algebraically but its terms
19
+ grow combinatorially before cancelling and overflow on real Qwen3.5 layers.
20
+ Within-chunk decay differences are computed as masked sums of the per-step
21
+ decays instead of differences of cumulative sums, which keeps them accurate in
22
+ fp16 when the cumulative decay is large.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import math
28
+
29
+ import torch
30
+ import torch.nn.functional as F
31
+ from torch import nn
32
+
33
+
34
+ class TextConfig:
35
+ def __init__(self, cfg: dict):
36
+ self.hidden_size = cfg["hidden_size"]
37
+ self.intermediate_size = cfg["intermediate_size"]
38
+ self.num_layers = cfg["num_hidden_layers"]
39
+ self.layer_types = cfg["layer_types"]
40
+ self.eps = cfg["rms_norm_eps"]
41
+ self.num_heads = cfg["num_attention_heads"]
42
+ self.num_kv_heads = cfg["num_key_value_heads"]
43
+ self.head_dim = cfg["head_dim"]
44
+ self.lin_k_heads = cfg["linear_num_key_heads"]
45
+ self.lin_v_heads = cfg["linear_num_value_heads"]
46
+ self.lin_k_dim = cfg["linear_key_head_dim"]
47
+ self.lin_v_dim = cfg["linear_value_head_dim"]
48
+ self.conv_kernel = cfg["linear_conv_kernel_dim"]
49
+ rope = cfg["rope_parameters"]
50
+ self.rope_theta = rope["rope_theta"]
51
+ self.rotary_dim = int(self.head_dim * rope.get("partial_rotary_factor", 1.0))
52
+ self.mrope_section = rope.get("mrope_section", [11, 11, 10])
53
+
54
+
55
+ def rope_cos_sin(cfg: TextConfig, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
56
+ """Interleaved M-RoPE tables. `position_ids` is [3, L] (t, h, w); returns [L, rotary_dim] fp32 each.
57
+
58
+ Mirrors `Qwen3_5TextRotaryEmbedding` (computed in float64 then cast, host side)."""
59
+ dim = cfg.rotary_dim
60
+ inv_freq = 1.0 / (cfg.rope_theta ** (torch.arange(0, dim, 2, dtype=torch.float64) / dim))
61
+ freqs = position_ids.to(torch.float64)[:, :, None] * inv_freq[None, None, :] # [3, L, dim/2]
62
+ thw = freqs[0].clone()
63
+ for axis, offset in ((1, 1), (2, 2)):
64
+ length = cfg.mrope_section[axis] * 3
65
+ thw[:, offset:length:3] = freqs[axis][:, offset:length:3]
66
+ emb = torch.cat([thw, thw], dim=-1)
67
+ return emb.cos().float(), emb.sin().float()
68
+
69
+
70
+ class RMSNorm(nn.Module):
71
+ """Qwen3.5 zero-centred RMSNorm: x * rsqrt(mean(x^2)+eps) * (1 + w)."""
72
+
73
+ def __init__(self, dim: int, eps: float):
74
+ super().__init__()
75
+ self.eps = eps
76
+ self.weight = nn.Parameter(torch.zeros(dim))
77
+
78
+ def forward(self, x):
79
+ x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
80
+ return x * (1.0 + self.weight)
81
+
82
+
83
+ class MLP(nn.Module):
84
+ def __init__(self, cfg: TextConfig):
85
+ super().__init__()
86
+ self.gate_proj = nn.Linear(cfg.hidden_size, cfg.intermediate_size, bias=False)
87
+ self.up_proj = nn.Linear(cfg.hidden_size, cfg.intermediate_size, bias=False)
88
+ self.down_proj = nn.Linear(cfg.intermediate_size, cfg.hidden_size, bias=False)
89
+
90
+ def forward(self, x):
91
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
92
+
93
+
94
+ def rotate_half(x):
95
+ half = x.shape[-1] // 2
96
+ return torch.cat([-x[..., half:], x[..., :half]], dim=-1)
97
+
98
+
99
+ class FullAttention(nn.Module):
100
+ def __init__(self, cfg: TextConfig):
101
+ super().__init__()
102
+ self.cfg = cfg
103
+ h, kv, d = cfg.num_heads, cfg.num_kv_heads, cfg.head_dim
104
+ self.q_proj = nn.Linear(cfg.hidden_size, h * d * 2, bias=False)
105
+ self.k_proj = nn.Linear(cfg.hidden_size, kv * d, bias=False)
106
+ self.v_proj = nn.Linear(cfg.hidden_size, kv * d, bias=False)
107
+ self.o_proj = nn.Linear(h * d, cfg.hidden_size, bias=False)
108
+ self.q_norm = RMSNorm(d, cfg.eps)
109
+ self.k_norm = RMSNorm(d, cfg.eps)
110
+
111
+ def forward(self, x, cos, sin, mask):
112
+ cfg = self.cfg
113
+ L = x.shape[1]
114
+ h, kv, d, r = cfg.num_heads, cfg.num_kv_heads, cfg.head_dim, cfg.rotary_dim
115
+ qg = self.q_proj(x).view(1, L, h, 2 * d)
116
+ q, gate = qg[..., :d], qg[..., d:]
117
+ gate = gate.reshape(1, L, h * d)
118
+ q = self.q_norm(q).transpose(1, 2) # [1, h, L, d]
119
+ k = self.k_norm(self.k_proj(x).view(1, L, kv, d)).transpose(1, 2)
120
+ v = self.v_proj(x).view(1, L, kv, d).transpose(1, 2)
121
+
122
+ c, s = cos[None, None], sin[None, None] # [1, 1, L, r]
123
+ q = torch.cat([q[..., :r] * c + rotate_half(q[..., :r]) * s, q[..., r:]], dim=-1)
124
+ k = torch.cat([k[..., :r] * c + rotate_half(k[..., :r]) * s, k[..., r:]], dim=-1)
125
+
126
+ rep = h // kv
127
+ k = k[:, :, None].expand(1, kv, rep, L, d).reshape(1, h, L, d)
128
+ v = v[:, :, None].expand(1, kv, rep, L, d).reshape(1, h, L, d)
129
+ scores = torch.matmul(q, k.transpose(-1, -2)) * (d**-0.5) + mask
130
+ out = torch.matmul(torch.softmax(scores, dim=-1), v) # [1, h, L, d]
131
+ out = out.transpose(1, 2).reshape(1, L, h * d)
132
+ return self.o_proj(out * torch.sigmoid(gate))
133
+
134
+
135
+ class GatedRMSNorm(nn.Module):
136
+ def __init__(self, dim: int, eps: float):
137
+ super().__init__()
138
+ self.eps = eps
139
+ self.weight = nn.Parameter(torch.ones(dim))
140
+
141
+ def forward(self, x, gate):
142
+ x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
143
+ return self.weight * x * F.silu(gate)
144
+
145
+
146
+ class GatedDeltaNet(nn.Module):
147
+ def __init__(self, cfg: TextConfig, seq_len: int, chunk_size: int):
148
+ super().__init__()
149
+ self.cfg = cfg
150
+ self.chunk = chunk_size
151
+ assert seq_len % chunk_size == 0, "bucket length must be a multiple of the delta-rule chunk size"
152
+ self.num_chunks = seq_len // chunk_size
153
+ kd = cfg.lin_k_heads * cfg.lin_k_dim
154
+ vd = cfg.lin_v_heads * cfg.lin_v_dim
155
+ self.key_dim, self.value_dim = kd, vd
156
+ self.conv_dim = 2 * kd + vd
157
+ self.in_proj_qkv = nn.Linear(cfg.hidden_size, self.conv_dim, bias=False)
158
+ self.in_proj_z = nn.Linear(cfg.hidden_size, vd, bias=False)
159
+ self.in_proj_b = nn.Linear(cfg.hidden_size, cfg.lin_v_heads, bias=False)
160
+ self.in_proj_a = nn.Linear(cfg.hidden_size, cfg.lin_v_heads, bias=False)
161
+ self.conv1d = nn.Conv1d(self.conv_dim, self.conv_dim, cfg.conv_kernel, groups=self.conv_dim, bias=False)
162
+ self.dt_bias = nn.Parameter(torch.ones(cfg.lin_v_heads))
163
+ self.A_log = nn.Parameter(torch.zeros(cfg.lin_v_heads))
164
+ self.norm = GatedRMSNorm(cfg.lin_v_dim, cfg.eps)
165
+ self.out_proj = nn.Linear(vd, cfg.hidden_size, bias=False)
166
+
167
+ C = chunk_size
168
+ idx = torch.arange(C)
169
+ # incl[i, j, k] = 1 if j < k <= i : sum_k g_k = cum_i - cum_j (lower triangle incl. diagonal is 0 on i == j)
170
+ incl = ((idx[None, None, :] > idx[None, :, None]) & (idx[None, None, :] <= idx[:, None, None])).float()
171
+ self.register_buffer("pair_sum_t", incl.reshape(C * C, C), persistent=False) # [C*C, C]
172
+ self.register_buffer("prefix_sum_t", (idx[None, :] <= idx[:, None]).float(), persistent=False) # [C, C]
173
+ self.register_buffer("suffix_sum_t", (idx[None, :] > idx[:, None]).float(), persistent=False)
174
+ self.register_buffer("lower_incl", (idx[None, :] <= idx[:, None]).float(), persistent=False)
175
+ self.register_buffer("strict_lower", (idx[None, :] < idx[:, None]).float(), persistent=False)
176
+ self.register_buffer("eye", torch.eye(C), persistent=False)
177
+ assert C & (C - 1) == 0, "delta-rule chunk size must be a power of two"
178
+ for n in (C // (2 * s) for s in (2**i for i in range(int(math.log2(C))))):
179
+ self.register_buffer(f"block_eye_{n}", torch.eye(n), persistent=False)
180
+
181
+ def unit_lower_inverse(self, t: torch.Tensor) -> torch.Tensor:
182
+ """Inverse of unit-lower-triangular [..., C, C] by recursive 2x2 blocking:
183
+ [[A, 0], [X, B]]^-1 = [[A^-1, 0], [-B^-1 X A^-1, B^-1]].
184
+
185
+ Stable like forward substitution (it only forms the true inverse's sub-blocks),
186
+ but log2(C) batched levels instead of C sequential row updates."""
187
+ C = self.chunk
188
+ lead = t.shape[:-2]
189
+ t = t.reshape(-1, C, C) # Core ML tensors are rank <= 5
190
+ b = t.shape[0]
191
+ inv = torch.ones(b, C, 1, 1, dtype=t.dtype, device=t.device) # 1x1 diagonal blocks of a unit triangle
192
+ s = 1
193
+ while s < C:
194
+ n2 = C // (2 * s)
195
+ # diagonal 2s x 2s blocks of t, then their lower-left s x s part
196
+ blocks = (t.reshape(b, n2, 2 * s, n2, 2 * s) * getattr(self, f"block_eye_{n2}")[:, None, :, None]).sum(-2)
197
+ x = blocks[..., s:, :s] # [b, n2, s, s]
198
+ pair = inv.reshape(b, n2, 2, s, s)
199
+ a_inv, b_inv = pair[..., 0, :, :], pair[..., 1, :, :]
200
+ lower = -torch.matmul(b_inv, torch.matmul(x, a_inv))
201
+ top = torch.cat([a_inv, torch.zeros_like(a_inv)], dim=-1)
202
+ bottom = torch.cat([lower, b_inv], dim=-1)
203
+ inv = torch.cat([top, bottom], dim=-2) # [b, n2, 2s, 2s]
204
+ s *= 2
205
+ return inv.reshape(*lead, C, C)
206
+
207
+ def forward(self, x):
208
+ cfg = self.cfg
209
+ L = x.shape[1]
210
+ C, N = self.chunk, self.num_chunks
211
+ H, Dk, Dv = cfg.lin_v_heads, cfg.lin_k_dim, cfg.lin_v_dim
212
+ rep = cfg.lin_v_heads // cfg.lin_k_heads
213
+
214
+ qkv = self.in_proj_qkv(x).transpose(1, 2) # [1, conv_dim, L]
215
+ qkv = F.silu(self.conv1d(F.pad(qkv, (cfg.conv_kernel - 1, 0)))).transpose(1, 2)
216
+ q = qkv[..., : self.key_dim].reshape(1, L, cfg.lin_k_heads, Dk)
217
+ k = qkv[..., self.key_dim : 2 * self.key_dim].reshape(1, L, cfg.lin_k_heads, Dk)
218
+ v = qkv[..., 2 * self.key_dim :].reshape(1, L, H, Dv)
219
+ z = self.in_proj_z(x).reshape(1, L, H, Dv)
220
+ beta = torch.sigmoid(self.in_proj_b(x)) # [1, L, H]
221
+ g = -torch.exp(self.A_log) * F.softplus(self.in_proj_a(x) + self.dt_bias) # [1, L, H], <= 0
222
+
223
+ # l2norm (FLA convention) then repeat k heads up to v heads
224
+ q = q * torch.rsqrt((q * q).sum(-1, keepdim=True) + 1e-6)
225
+ k = k * torch.rsqrt((k * k).sum(-1, keepdim=True) + 1e-6)
226
+ q = q[:, :, :, None].expand(1, L, cfg.lin_k_heads, rep, Dk).reshape(1, L, H, Dk)
227
+ k = k[:, :, :, None].expand(1, L, cfg.lin_k_heads, rep, Dk).reshape(1, L, H, Dk)
228
+ q = q * (Dk**-0.5)
229
+
230
+ # -> [H, N, C, D]
231
+ q = q[0].permute(1, 0, 2).reshape(H, N, C, Dk)
232
+ k = k[0].permute(1, 0, 2).reshape(H, N, C, Dk)
233
+ v = v[0].permute(1, 0, 2).reshape(H, N, C, Dv)
234
+ beta = beta[0].T.reshape(H, N, C, 1)
235
+ g = g[0].T.reshape(H, N, C)
236
+
237
+ # constant-first matmuls: `x @ const` would lower to Core ML `linear` and be picked up by weight compression
238
+ g_col = g.unsqueeze(-1) # [H, N, C, 1]
239
+ cum = torch.matmul(self.prefix_sum_t, g_col).squeeze(-1) # cum_i = sum_{k<=i} g_k
240
+ to_end = torch.matmul(self.suffix_sum_t, g_col).squeeze(-1) # sum_{k>i} g_k = cum_last - cum_i
241
+ pair = torch.matmul(self.pair_sum_t, g_col).reshape(H, N, C, C) # cum_i - cum_j (0 above diagonal)
242
+ pair_decay = torch.exp(pair) * self.lower_incl
243
+
244
+ k_beta = k * beta
245
+ v_beta = v * beta
246
+ kkt = torch.matmul(k_beta, k.transpose(-1, -2)) * pair_decay
247
+ attn_intra = torch.matmul(q, k.transpose(-1, -2)) * pair_decay
248
+
249
+ inv = self.unit_lower_inverse(self.eye + kkt * self.strict_lower)
250
+ new_v = torch.matmul(inv, v_beta) # [H, N, C, Dv]
251
+ k_cumdecay = torch.matmul(inv, k_beta * torch.exp(cum)[..., None]) # [H, N, C, Dk]
252
+
253
+ q_dec = q * torch.exp(cum)[..., None]
254
+ k_dec = k * torch.exp(to_end)[..., None]
255
+ chunk_decay = torch.exp(cum[..., -1:])[..., None] # [H, N, 1, 1]
256
+
257
+ state = torch.zeros(H, Dk, Dv, dtype=x.dtype, device=x.device)
258
+ outs = []
259
+ for i in range(N):
260
+ v_new = new_v[:, i] - torch.matmul(k_cumdecay[:, i], state)
261
+ outs.append(torch.matmul(q_dec[:, i], state) + torch.matmul(attn_intra[:, i], v_new))
262
+ if i + 1 < N:
263
+ state = state * chunk_decay[:, i] + torch.matmul(k_dec[:, i].transpose(-1, -2), v_new)
264
+ core = torch.stack(outs, dim=1).reshape(H, L, Dv).permute(1, 0, 2) # [L, H, Dv]
265
+
266
+ core = self.norm(core, z[0])
267
+ return self.out_proj(core.reshape(1, L, H * Dv))
268
+
269
+
270
+ class DecoderLayer(nn.Module):
271
+ def __init__(self, cfg: TextConfig, layer_idx: int, seq_len: int, chunk_size: int):
272
+ super().__init__()
273
+ self.is_linear = cfg.layer_types[layer_idx] == "linear_attention"
274
+ if self.is_linear:
275
+ self.linear_attn = GatedDeltaNet(cfg, seq_len, chunk_size)
276
+ else:
277
+ self.self_attn = FullAttention(cfg)
278
+ self.mlp = MLP(cfg)
279
+ self.input_layernorm = RMSNorm(cfg.hidden_size, cfg.eps)
280
+ self.post_attention_layernorm = RMSNorm(cfg.hidden_size, cfg.eps)
281
+
282
+ def forward(self, x, cos, sin, mask):
283
+ h = self.input_layernorm(x)
284
+ h = self.linear_attn(h) if self.is_linear else self.self_attn(h, cos, sin, mask)
285
+ x = x + h
286
+ return x + self.mlp(self.post_attention_layernorm(x))
287
+
288
+
289
+ class DecoderChunk(nn.Module):
290
+ """Layers [start, end). The last chunk (with_head) returns letter logits [1, n_letters]."""
291
+
292
+ def __init__(
293
+ self,
294
+ cfg: TextConfig,
295
+ start: int,
296
+ end: int,
297
+ seq_len: int,
298
+ chunk_size: int = 64,
299
+ with_head: bool = False,
300
+ n_letters: int = 26,
301
+ ):
302
+ super().__init__()
303
+ self.start, self.end, self.with_head = start, end, with_head
304
+ self.layers = nn.ModuleList([DecoderLayer(cfg, i, seq_len, chunk_size) for i in range(start, end)])
305
+ mask = torch.full((seq_len, seq_len), -1e4).triu(1)
306
+ self.register_buffer("mask", mask[None, None], persistent=False)
307
+ if with_head:
308
+ self.norm = RMSNorm(cfg.hidden_size, cfg.eps)
309
+ self.letter_head = nn.Linear(cfg.hidden_size, n_letters, bias=False)
310
+
311
+ def forward(self, hidden, cos, sin, last_onehot=None):
312
+ for layer in self.layers:
313
+ hidden = layer(hidden, cos, sin, self.mask)
314
+ if not self.with_head:
315
+ return hidden
316
+ last = torch.matmul(last_onehot, hidden[0]) # [1, hidden]
317
+ return self.letter_head(self.norm(last))
318
+
319
+ def load_merged(self, state: dict[str, torch.Tensor], letter_rows: torch.Tensor | None = None):
320
+ """`state` uses Qwen3_5TextModel names relative to the language model (`layers.N...`, `norm.weight`)."""
321
+ own = {}
322
+ for local, global_idx in enumerate(range(self.start, self.end)):
323
+ prefix = f"layers.{global_idx}."
324
+ for key, value in state.items():
325
+ if key.startswith(prefix):
326
+ own[f"layers.{local}.{key[len(prefix):]}"] = value
327
+ if self.with_head:
328
+ own["norm.weight"] = state["norm.weight"]
329
+ own["letter_head.weight"] = letter_rows
330
+ missing, unexpected = self.load_state_dict({k: v.float() for k, v in own.items()}, strict=False)
331
+ missing = [m for m in missing if not m.endswith(("mask",))]
332
+ if missing or unexpected:
333
+ raise RuntimeError(f"weight mismatch: missing={missing[:8]} unexpected={unexpected[:8]}")
lite/python/vision_export.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Trace-friendly Qwen3.5 vision tower for Core ML (from clef-vision-0.8b; d3 weight names): one image per call, patches right-padded to a fixed count.
2
+
3
+ Inputs (all fp32, host-prepared from the HF processor's ``pixel_values`` / ``image_grid_thw``):
4
+ patches [N, 1536] flattened (C, T, P, P) patches, zero padded after the image's n patches
5
+ pos_embeds [N, 768] bilinear-resampled learned position grid (host: HF interpolation indices/weights)
6
+ cos, sin [N, 64] axial 2D rope tables (host)
7
+ mask [1, 1, N, N] additive key mask: 0 for real patches, -1e4 for padding
8
+ Output: merged tokens [N/4, 1024]; rows [0, n/4) are the image's tokens in the order the language model expects.
9
+
10
+ Padding is appended after the image, so with the key mask real patches never attend to it; the merger groups four
11
+ consecutive patches, and n is always a multiple of 4 (grid h, w are multiples of the merge size), so no group mixes
12
+ real and padded patches. Parity vs ``Qwen3_5VisionModel`` is checked in ``vision_parity``.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import json
18
+ import math
19
+ import time
20
+ from pathlib import Path
21
+
22
+ import numpy as np
23
+ import torch
24
+ import torch.nn.functional as F
25
+ from torch import nn
26
+
27
+ VISION_PREFIX = "visual."
28
+
29
+
30
+ class VisionConfig:
31
+ def __init__(self, cfg: dict):
32
+ self.depth = cfg["depth"]
33
+ self.hidden = cfg["hidden_size"]
34
+ self.heads = cfg["num_heads"]
35
+ self.head_dim = self.hidden // self.heads
36
+ self.intermediate = cfg["intermediate_size"]
37
+ self.out_hidden = cfg["out_hidden_size"]
38
+ self.patch = cfg["patch_size"]
39
+ self.temporal = cfg["temporal_patch_size"]
40
+ self.channels = cfg["in_channels"]
41
+ self.merge = cfg["spatial_merge_size"]
42
+ self.num_pos = cfg["num_position_embeddings"]
43
+ self.act = cfg["hidden_act"] # gelu_pytorch_tanh for the blocks; the merger uses exact GELU
44
+ self.rope_theta = cfg.get("rope_parameters", {}).get("rope_theta", 10000.0)
45
+ self.patch_dim = self.channels * self.temporal * self.patch * self.patch
46
+
47
+
48
+ def rotate_half(x: torch.Tensor) -> torch.Tensor:
49
+ x1, x2 = x.chunk(2, dim=-1)
50
+ return torch.cat([-x2, x1], dim=-1)
51
+
52
+
53
+ class Block(nn.Module):
54
+ def __init__(self, cfg: VisionConfig):
55
+ super().__init__()
56
+ self.cfg = cfg
57
+ self.norm1 = nn.LayerNorm(cfg.hidden, eps=1e-6)
58
+ self.norm2 = nn.LayerNorm(cfg.hidden, eps=1e-6)
59
+ self.qkv = nn.Linear(cfg.hidden, cfg.hidden * 3)
60
+ self.proj = nn.Linear(cfg.hidden, cfg.hidden)
61
+ self.fc1 = nn.Linear(cfg.hidden, cfg.intermediate)
62
+ self.fc2 = nn.Linear(cfg.intermediate, cfg.hidden)
63
+ self.scale = cfg.head_dim**-0.5
64
+
65
+ def forward(self, x, cos, sin, mask):
66
+ n = x.shape[0]
67
+ h = self.norm1(x)
68
+ qkv = self.qkv(h).reshape(n, 3, self.cfg.heads, self.cfg.head_dim)
69
+ q, k, v = qkv[:, 0], qkv[:, 1], qkv[:, 2] # [N, H, d]
70
+ c, s = cos[:, None, :], sin[:, None, :]
71
+ q = q * c + rotate_half(q) * s
72
+ k = k * c + rotate_half(k) * s
73
+ q = q.permute(1, 0, 2)[None] # [1, H, N, d]
74
+ k = k.permute(1, 0, 2)[None]
75
+ v = v.permute(1, 0, 2)[None]
76
+ scores = torch.matmul(q, k.transpose(-1, -2)) * self.scale + mask
77
+ attn = torch.matmul(torch.softmax(scores, dim=-1), v) # [1, H, N, d]
78
+ attn = attn[0].permute(1, 0, 2).reshape(n, self.cfg.hidden)
79
+ x = x + self.proj(attn)
80
+ h = self.norm2(x)
81
+ act = F.gelu(self.fc1(h), approximate="tanh" if self.cfg.act == "gelu_pytorch_tanh" else "none")
82
+ return x + self.fc2(act)
83
+
84
+
85
+ class VisionTower(nn.Module):
86
+ def __init__(self, cfg: VisionConfig, n_patches: int):
87
+ super().__init__()
88
+ assert n_patches % (cfg.merge**2) == 0
89
+ self.cfg = cfg
90
+ self.n_patches = n_patches
91
+ self.patch_embed = nn.Linear(cfg.patch_dim, cfg.hidden) # Conv3d with kernel == stride == patch
92
+ self.blocks = nn.ModuleList([Block(cfg) for _ in range(cfg.depth)])
93
+ merged = cfg.hidden * cfg.merge**2
94
+ self.merger_norm = nn.LayerNorm(cfg.hidden, eps=1e-6)
95
+ self.merger_fc1 = nn.Linear(merged, merged)
96
+ self.merger_fc2 = nn.Linear(merged, cfg.out_hidden)
97
+
98
+ def forward(self, patches, pos_embeds, cos, sin, mask):
99
+ x = self.patch_embed(patches) + pos_embeds
100
+ for block in self.blocks:
101
+ x = block(x, cos, sin, mask)
102
+ x = self.merger_norm(x).reshape(self.n_patches // self.cfg.merge**2, -1)
103
+ return self.merger_fc2(F.gelu(self.merger_fc1(x)))
104
+
105
+ def load_hf(self, state: dict[str, torch.Tensor]) -> None:
106
+ """``state`` holds the HF vision weights with the ``visual.`` prefix stripped."""
107
+ own = {
108
+ "patch_embed.weight": state["patch_embed.proj.weight"].reshape(self.cfg.hidden, -1),
109
+ "patch_embed.bias": state["patch_embed.proj.bias"],
110
+ "merger_norm.weight": state["merger.norm.weight"],
111
+ "merger_norm.bias": state["merger.norm.bias"],
112
+ "merger_fc1.weight": state["merger.linear_fc1.weight"],
113
+ "merger_fc1.bias": state["merger.linear_fc1.bias"],
114
+ "merger_fc2.weight": state["merger.linear_fc2.weight"],
115
+ "merger_fc2.bias": state["merger.linear_fc2.bias"],
116
+ }
117
+ for i in range(self.cfg.depth):
118
+ src, dst = f"blocks.{i}.", f"blocks.{i}."
119
+ for a, b in (("norm1", "norm1"), ("norm2", "norm2"), ("attn.qkv", "qkv"), ("attn.proj", "proj"),
120
+ ("mlp.linear_fc1", "fc1"), ("mlp.linear_fc2", "fc2")):
121
+ own[f"{dst}{b}.weight"] = state[f"{src}{a}.weight"]
122
+ own[f"{dst}{b}.bias"] = state[f"{src}{a}.bias"]
123
+ missing, unexpected = self.load_state_dict({k: v.float() for k, v in own.items()}, strict=False)
124
+ if missing or unexpected:
125
+ raise RuntimeError(f"vision weight mismatch: missing={missing[:6]} unexpected={unexpected[:6]}")
126
+
127
+
128
+ # ----------------------------------------------------------------------------------------------------------------------
129
+ # Host-side preparation (numpy/torch; mirrors transformers.vision_utils + Qwen3_5VisionRotaryEmbedding)
130
+
131
+ def axial_rope(cfg: VisionConfig, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
132
+ """``position_ids`` [n, 2] (h, w) -> cos/sin [n, head_dim]: freqs over head_dim//4 per axis, concat (h, w), x2."""
133
+ spatial = cfg.head_dim // 2
134
+ inv_freq = 1.0 / (cfg.rope_theta ** (torch.arange(0, spatial, 2, dtype=torch.float32) / spatial))
135
+ freqs = position_ids[..., None].float() * inv_freq # [n, 2, spatial/2]
136
+ hw = torch.cat([freqs[:, 0], freqs[:, 1]], dim=-1)
137
+ emb = torch.cat([hw, hw], dim=-1)
138
+ return emb.cos(), emb.sin()
139
+
140
+
141
+ def prepare_inputs(cfg: VisionConfig, pos_table: torch.Tensor, pixel_values: torch.Tensor, grid_thw: torch.Tensor,
142
+ n_patches: int):
143
+ """One image. Uses the HF helpers for position ids and pos-embed interpolation so the host matches the reference."""
144
+ from transformers.vision_utils import get_vision_interpolation_indices_and_weights, get_vision_position_ids
145
+
146
+ n = int(pixel_values.shape[0])
147
+ if n > n_patches:
148
+ raise ValueError(f"image has {n} patches; bucket holds {n_patches}")
149
+ grid = grid_thw.reshape(1, 3)
150
+ idx, w = get_vision_interpolation_indices_and_weights(
151
+ grid, num_grid_per_side=int(math.isqrt(cfg.num_pos)), mode="bilinear", align_corners=True,
152
+ spatial_merge_size=cfg.merge, kwargs={})
153
+ pos_embeds = (pos_table[idx] * w[:, :, None]).sum(1) # [n, hidden]
154
+ position_ids = get_vision_position_ids(grid, cfg.merge, kwargs={}) # [n, 2]
155
+ cos, sin = axial_rope(cfg, position_ids)
156
+ patches = torch.zeros(n_patches, cfg.patch_dim)
157
+ patches[:n] = pixel_values.float()
158
+ pe = torch.zeros(n_patches, cfg.hidden)
159
+ pe[:n] = pos_embeds.float()
160
+ cs = torch.zeros(n_patches, cfg.head_dim)
161
+ sn = torch.zeros(n_patches, cfg.head_dim)
162
+ cs[:n], sn[:n] = cos, sin
163
+ mask = torch.zeros(1, 1, n_patches, n_patches)
164
+ mask[..., n:] = -1e4
165
+ return patches, pe, cs, sn, mask, n
166
+
167
+
168
+ def load_vision(merged_backbone: Path, n_patches: int):
169
+ from safetensors.torch import load_file
170
+
171
+ config = json.loads((merged_backbone / "config.json").read_text())
172
+ cfg = VisionConfig(config["vision_config"])
173
+ state = {}
174
+ for path in sorted(merged_backbone.glob("model*.safetensors")): # one file or shards
175
+ state.update({k[len(VISION_PREFIX):]: v for k, v in load_file(str(path)).items() if k.startswith(VISION_PREFIX)})
176
+ tower = VisionTower(cfg, n_patches)
177
+ tower.load_hf(state)
178
+ return tower.eval(), cfg, state["pos_embed.weight"].float()
179
+
180
+
181
+ def main() -> None:
182
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
183
+ ap.add_argument("--merged", type=Path, required=True, help="d3 checkpoint dir")
184
+ ap.add_argument("--patches", type=int, default=784, help="patch bucket (784 = 448x448 cap -> 196 tokens)")
185
+ ap.add_argument("--precision", choices=("fp16", "fp32", "mixed"), default="fp32",
186
+ help="fp32 is the only exact option: fp16 (0.27 max err) and mixed with fp32 norms/softmax "
187
+ "(0.33) both break on this ViT's activation range; fp32 runs 35 ms on the M5 Pro GPU")
188
+ ap.add_argument("--out", type=Path, default=Path("build/coreml"))
189
+ args = ap.parse_args()
190
+ import coremltools as ct
191
+
192
+ tower, cfg, pos_table = load_vision(args.merged, args.patches)
193
+ N = args.patches
194
+ example = (torch.zeros(N, cfg.patch_dim), torch.zeros(N, cfg.hidden), torch.zeros(N, cfg.head_dim),
195
+ torch.zeros(N, cfg.head_dim), torch.zeros(1, 1, N, N))
196
+ started = time.time()
197
+ with torch.no_grad():
198
+ traced = torch.jit.trace(tower, example, check_trace=False)
199
+ names = ["patches", "pos_embeds", "cos", "sin", "mask"]
200
+ if args.precision == "fp32":
201
+ precision = ct.precision.FLOAT32
202
+ elif args.precision == "fp16":
203
+ precision = ct.precision.FLOAT16
204
+ else:
205
+ keep_fp32 = {"layer_norm", "softmax", "reduce_mean", "reduce_sum", "pow", "sqrt", "rsqrt", "real_div"}
206
+ precision = ct.transform.FP16ComputePrecision(op_selector=lambda op: op.op_type not in keep_fp32)
207
+ model = ct.convert(
208
+ traced, convert_to="mlprogram", minimum_deployment_target=ct.target.iOS17,
209
+ compute_precision=precision,
210
+ compute_units=ct.ComputeUnit.CPU_ONLY,
211
+ inputs=[ct.TensorType(name=n, shape=tuple(t.shape), dtype=np.float32) for n, t in zip(names, example)],
212
+ outputs=[ct.TensorType(name="tokens", dtype=np.float32)],
213
+ )
214
+ model.short_description = "d3 vision tower (vLLM-SR Decision 3.0): one image, fixed patch bucket"
215
+ model.license = "Apache-2.0"
216
+ model.user_defined_metadata.update({"patches": str(N), "tokens": str(N // cfg.merge**2)})
217
+ out = args.out / f"Vision_P{N}"
218
+ out.mkdir(parents=True, exist_ok=True)
219
+ package = out / f"VisionTower_{args.precision}.mlpackage"
220
+ model.save(str(package))
221
+ pos_table.numpy().astype(np.float32).tofile(out / "pos_embed.f32") # the host resamples this per image
222
+ (out / "config.json").write_text(json.dumps({"patches": N, "tokens": N // cfg.merge**2, "patch_dim": cfg.patch_dim,
223
+ "hidden": cfg.hidden, "head_dim": cfg.head_dim,
224
+ "out_hidden": cfg.out_hidden, "precision": args.precision},
225
+ indent=2) + "\n")
226
+ print(f"saved {package} in {time.time() - started:.0f} s")
227
+
228
+
229
+ if __name__ == "__main__":
230
+ main()
lite/readout.f32 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7772300f2e211f3c2028a16b9f493e941c2343bd14a9131145eb34da39f36448
3
+ size 1044480
lite/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5f9e4d4901a92b997e463c1f46055088b6cca5ca61a6522d1b9f64c4bb81cb42
3
+ size 12807982
lite/vision_P1536.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eec6afaa226859a3d717d2ced945947b3663d1b641076d0abc5e96af19b61d57
3
+ size 153343
lite/vision_P1536.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:3d571710dd88a5f2e34d0b51b815a839bc0b53425eca17a45f965d474218ec6e
3
+ size 395303488
lite/vision_P1536.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "ABBDA214-38F9-44F0-A089-B636CD88D5C9": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "E4544D1C-B3E4-4D3A-A03E-147542E6B2CD": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "ABBDA214-38F9-44F0-A089-B636CD88D5C9"
18
+ }
lite/vision_P3072.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d6a1ec684714a1aeb552cb699b9e5303a342db6c0bec6ab481abe1581df1810
3
+ size 153343
lite/vision_P3072.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:3d571710dd88a5f2e34d0b51b815a839bc0b53425eca17a45f965d474218ec6e
3
+ size 395303488
lite/vision_P3072.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "77FBF011-9CB1-4E8C-8C83-A7CA7E3C7916": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Specification",
7
+ "name": "model.mlmodel",
8
+ "path": "com.apple.CoreML/model.mlmodel"
9
+ },
10
+ "A8B8926F-B70F-4353-AE83-938EC417390B": {
11
+ "author": "com.apple.CoreML",
12
+ "description": "CoreML Model Weights",
13
+ "name": "weights",
14
+ "path": "com.apple.CoreML/weights"
15
+ }
16
+ },
17
+ "rootModelIdentifier": "77FBF011-9CB1-4E8C-8C83-A7CA7E3C7916"
18
+ }
lite/vision_P320.mlpackage/Data/com.apple.CoreML/model.mlmodel ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9c6e604d1d2e1c2b63d5cd01a0263e0245c10b7a41c16bb177953c9208166b8b
3
+ size 153335
lite/vision_P320.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:3d571710dd88a5f2e34d0b51b815a839bc0b53425eca17a45f965d474218ec6e
3
+ size 395303488
lite/vision_P320.mlpackage/Manifest.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fileFormatVersion": "1.0.0",
3
+ "itemInfoEntries": {
4
+ "02E1986B-12A5-4C92-8F54-8B6A3CADE0DF": {
5
+ "author": "com.apple.CoreML",
6
+ "description": "CoreML Model Weights",
7
+ "name": "weights",
8
+ "path": "com.apple.CoreML/weights"
9
+ },
10
+ "AB4963EB-278E-43AA-B7F3-BAC7CACD9427": {
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": "AB4963EB-278E-43AA-B7F3-BAC7CACD9427"
18
+ }