botp
/

orz99 ArcherHume commited on
Commit
1d2de8a
·
0 Parent(s):

Duplicate from DoccyHealth/Solomon

Browse files

Co-authored-by: Archer Hume <ArcherHume@users.noreply.huggingface.co>

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +5 -0
  2. LICENSE +202 -0
  3. MANIFEST.json +829 -0
  4. MODIFICATIONS.md +118 -0
  5. NOTICE +98 -0
  6. README.md +661 -0
  7. adapter/adapter.safetensors +3 -0
  8. adapter/config.json +45 -0
  9. adapter/heads.npz +3 -0
  10. licenses/Qwen-Apache-2.0.txt +202 -0
  11. mlx/.gitignore +12 -0
  12. mlx/CONVERTER-SOURCE.json +21 -0
  13. mlx/LICENSE +202 -0
  14. mlx/MODIFICATIONS.md +25 -0
  15. mlx/NOTICE +78 -0
  16. mlx/README.md +146 -0
  17. mlx/bf16/LICENSE +202 -0
  18. mlx/bf16/MODIFICATIONS.md +75 -0
  19. mlx/bf16/NOTICE +78 -0
  20. mlx/bf16/README.md +51 -0
  21. mlx/bf16/base-manifest.json +197 -0
  22. mlx/bf16/binding.json +141 -0
  23. mlx/bf16/conversion.json +203 -0
  24. mlx/docs/UPSTREAM-MODIFICATIONS.md +75 -0
  25. mlx/docs/VALIDATION-20260921.md +46 -0
  26. mlx/examples/decide.py +21 -0
  27. mlx/pyproject.toml +35 -0
  28. mlx/requirements.cloud.lock +1072 -0
  29. mlx/requirements.lock +79 -0
  30. mlx/scripts/benchmark.py +122 -0
  31. mlx/scripts/benchmark_chunks.py +50 -0
  32. mlx/scripts/check_cuda_parity.py +172 -0
  33. mlx/scripts/score_parity.py +19 -0
  34. mlx/scripts/validate_api.py +128 -0
  35. mlx/src/solomon_mlx/__init__.py +5 -0
  36. mlx/src/solomon_mlx/_vendor/__init__.py +2 -0
  37. mlx/src/solomon_mlx/_vendor/contract.py +111 -0
  38. mlx/src/solomon_mlx/_vendor/evidence.py +156 -0
  39. mlx/src/solomon_mlx/_vendor/evidence_v3.py +425 -0
  40. mlx/src/solomon_mlx/_vendor/prompts.py +43 -0
  41. mlx/src/solomon_mlx/_vendor/retrieval.py +152 -0
  42. mlx/src/solomon_mlx/_vendor/semantics.py +71 -0
  43. mlx/src/solomon_mlx/api.py +321 -0
  44. mlx/src/solomon_mlx/artifacts.py +77 -0
  45. mlx/src/solomon_mlx/budget.py +71 -0
  46. mlx/src/solomon_mlx/cli.py +76 -0
  47. mlx/src/solomon_mlx/engine.py +333 -0
  48. mlx/src/solomon_mlx/evaluation.py +324 -0
  49. mlx/src/solomon_mlx/prepare.py +121 -0
  50. mlx/src/solomon_mlx_hub/__init__.py +5 -0
.gitattributes ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ *.bin filter=lfs diff=lfs merge=lfs -text
3
+ *.npz filter=lfs diff=lfs merge=lfs -text
4
+ *.pt filter=lfs diff=lfs merge=lfs -text
5
+ *.tar filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Doccy Pty Ltd
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
MANIFEST.json ADDED
@@ -0,0 +1,829 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0",
3
+ "approved": true,
4
+ "base_weights_referenced_not_redistributed": [
5
+ {
6
+ "copyright": "2026 Alibaba Cloud",
7
+ "license": "Apache-2.0",
8
+ "purpose": "the operator downloads these themselves; this repository ships only an adapter",
9
+ "repo": "Qwen/Qwen3.8-27B",
10
+ "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0"
11
+ }
12
+ ],
13
+ "blockers": [],
14
+ "calibration_file_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9",
15
+ "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b",
16
+ "contract": "solomon-v1",
17
+ "descriptor": {
18
+ "path": "release/solomon-release.json",
19
+ "sha256": "7a8b02bc00465fb3a06e59604af4ab07e5b290d45b293e7ef72d4c5c7bf4a596"
20
+ },
21
+ "excluded_categories": [
22
+ "panels and document corpora (data/**, **/authoring/**, **/*.jsonl)",
23
+ "datasets and training inputs of every kind",
24
+ "caches, states, rendered images, score archives, annotation outputs and logs",
25
+ "test files, evaluation-panel builders and qualification machinery",
26
+ "acceptance fixture documents and any padded development document",
27
+ "base model weights (referenced by pinned revision, never redistributed)",
28
+ "GPU launch scripts, ops/ tooling, internal plans and internal cards"
29
+ ],
30
+ "files": [
31
+ {
32
+ "destination": ".gitattributes",
33
+ "generated_by": "huggingface-cli / already committed in the clone",
34
+ "kind": "git_lfs_config",
35
+ "note": "tracks *.safetensors, *.bin, *.npz, *.pt, *.tar through git-lfs; the adapter (.safetensors) and the heads (.npz) are both covered, so no new LFS pattern is needed",
36
+ "staged_bytes": 217,
37
+ "staged_sha256": "8baff4be52225e26dd13dffb05fed3d0db821911c36d7ffd6f6d00d02ca421c7"
38
+ },
39
+ {
40
+ "destination": "README.md",
41
+ "generated_by": "ops/solomon_package.py (authored for an external audience; every figure read from a measured artifact at generation time)",
42
+ "kind": "model_card",
43
+ "staged_bytes": 43920,
44
+ "staged_sha256": "09c96e67b59006ca0eb53113b71c08c5ab830781f86291b8721b173dcb861bc0"
45
+ },
46
+ {
47
+ "destination": "LICENSE",
48
+ "generated_by": "ops/solomon_package.py (Apache-2.0 body from the archived upstream text, our copyright in the appendix)",
49
+ "kind": "project_license",
50
+ "staged_bytes": 11344,
51
+ "staged_sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457"
52
+ },
53
+ {
54
+ "destination": "NOTICE",
55
+ "generated_by": "ops/solomon_package.py",
56
+ "kind": "attribution_notice",
57
+ "staged_bytes": 5125,
58
+ "staged_sha256": "e0ff128e3b74f42d2f8de164da975d192192d9cb010330d6cf1a0fd73685bb74"
59
+ },
60
+ {
61
+ "destination": "MODIFICATIONS.md",
62
+ "generated_by": "ops/solomon_package.py",
63
+ "kind": "change_notice_apache_4b",
64
+ "staged_bytes": 7207,
65
+ "staged_sha256": "c51ba45105ebd0a41181ca7b35f41795a3e73c86b1cd41d8d7e9e3cadac3737a"
66
+ },
67
+ {
68
+ "destination": "requirements.lock",
69
+ "generated_by": "ops/solomon_package.py",
70
+ "kind": "dependency_pins",
71
+ "staged_bytes": 3943,
72
+ "staged_sha256": "56144aef9d15db53f7b33fd1c80e7a8d078b70800fa24d3ecc72d86e14cc2898"
73
+ },
74
+ {
75
+ "destination": "MANIFEST.json",
76
+ "generated_by": "ops/solomon_package.py",
77
+ "kind": "manifest"
78
+ },
79
+ {
80
+ "destination": "licenses/Qwen-Apache-2.0.txt",
81
+ "expected_bytes": 11544,
82
+ "expected_sha256": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a",
83
+ "kind": "upstream_model_license",
84
+ "staged_bytes": 11544,
85
+ "staged_sha256": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a"
86
+ },
87
+ {
88
+ "destination": "adapter/adapter.safetensors",
89
+ "expected_bytes": 870363376,
90
+ "expected_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0",
91
+ "git_lfs": true,
92
+ "kind": "lora_adapter",
93
+ "staged_bytes": 870363376,
94
+ "staged_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0"
95
+ },
96
+ {
97
+ "destination": "adapter/heads.npz",
98
+ "expected_bytes": 1643162,
99
+ "expected_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab",
100
+ "git_lfs": true,
101
+ "kind": "answer_heads",
102
+ "staged_bytes": 1643162,
103
+ "staged_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab"
104
+ },
105
+ {
106
+ "destination": "adapter/config.json",
107
+ "generated_by": "ops/solomon_package.py",
108
+ "kind": "adapter_config",
109
+ "staged_bytes": 2240,
110
+ "staged_sha256": "7e78a6017d4400843dabf33acc9e1d43255f0f90021518eb36e991c492544436"
111
+ },
112
+ {
113
+ "destination": "serving/serving-binding.json",
114
+ "kind": "serving_binding",
115
+ "note": "default serving binding (bf16)",
116
+ "staged_bytes": 6249,
117
+ "staged_sha256": "11b9352ab72b9e05a3d5ddbecc5a17f462c33cb6cc09dd401e062fc44dfb0831"
118
+ },
119
+ {
120
+ "destination": "serving/serving-binding-fp32.json",
121
+ "expected_sha256": "eed6de24fafcbdd542858f2687cb1ca6f5e524cd9780390f4148330a0863a193",
122
+ "kind": "serving_binding",
123
+ "note": "serving binding for precision=fp32",
124
+ "staged_bytes": 6203,
125
+ "staged_sha256": "eed6de24fafcbdd542858f2687cb1ca6f5e524cd9780390f4148330a0863a193"
126
+ },
127
+ {
128
+ "destination": "serving/serving-binding-int8.json",
129
+ "expected_sha256": "a711eca4c70bdcb22d41127af49ae8b90c1e23b54db4fb5308d9cb0e05fe40ea",
130
+ "kind": "serving_binding",
131
+ "note": "serving binding for precision=int8",
132
+ "staged_bytes": 6353,
133
+ "staged_sha256": "a711eca4c70bdcb22d41127af49ae8b90c1e23b54db4fb5308d9cb0e05fe40ea"
134
+ },
135
+ {
136
+ "destination": "serving/readout-temperature-v3.json",
137
+ "expected_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9",
138
+ "kind": "calibration_artifact",
139
+ "staged_bytes": 3370,
140
+ "staged_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9"
141
+ },
142
+ {
143
+ "destination": "serving/evidence-head.safetensors",
144
+ "expected_bytes": 31461804,
145
+ "expected_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c",
146
+ "git_lfs": true,
147
+ "kind": "evidence_head_weights",
148
+ "note": "relevance head weights (float32)",
149
+ "staged_bytes": 31461804,
150
+ "staged_sha256": "5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c"
151
+ },
152
+ {
153
+ "destination": "serving/evidence-head.json",
154
+ "expected_bytes": 3106,
155
+ "expected_sha256": "b8bf1d642af9f3a830f1ee47c49bda7336d65b56b97fe3625ea1741f133fa244",
156
+ "kind": "evidence_head_config",
157
+ "note": "relevance head config incl. lexical_residual alpha",
158
+ "staged_bytes": 3106,
159
+ "staged_sha256": "b8bf1d642af9f3a830f1ee47c49bda7336d65b56b97fe3625ea1741f133fa244"
160
+ },
161
+ {
162
+ "destination": "serving/evidence-policy.json",
163
+ "expected_bytes": 969,
164
+ "expected_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84",
165
+ "kind": "evidence_policy",
166
+ "note": "ranked-pointer serving policy (top 3, suppressed when not stated)",
167
+ "staged_bytes": 969,
168
+ "staged_sha256": "866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84"
169
+ },
170
+ {
171
+ "destination": "serving/selection.json",
172
+ "generated_by": "ops/solomon_package.py",
173
+ "kind": "readout_selection",
174
+ "staged_bytes": 796,
175
+ "staged_sha256": "7f58b2e65e37df6409f2ac5fb774c7792fa93899269fdf76dc3446aacfae127f"
176
+ },
177
+ {
178
+ "destination": "serving/service-export.json",
179
+ "generated_by": "ops/solomon_package.py",
180
+ "kind": "qualification_envelope",
181
+ "staged_bytes": 607,
182
+ "staged_sha256": "40ba3d4d2252554ff4e12a8f761097558af653be3ff23829e4c935bb3b0df4a8"
183
+ },
184
+ {
185
+ "destination": "src/solomon/__init__.py",
186
+ "kind": "serving_code",
187
+ "staged_bytes": 1157,
188
+ "staged_sha256": "1e9e7993cd182b5ccac3eab801277cb00c802c75281bee81571890c6fdaed2c6"
189
+ },
190
+ {
191
+ "destination": "src/solomon/api.py",
192
+ "kind": "serving_code",
193
+ "staged_bytes": 4284,
194
+ "staged_sha256": "21836850e511062c34c9a1c23cd78ae5ab00ea4b23c7bc1f79dc8838f1b41655"
195
+ },
196
+ {
197
+ "destination": "src/solomon/binding.py",
198
+ "kind": "serving_code",
199
+ "staged_bytes": 13767,
200
+ "staged_sha256": "09aaede5bc00b9cd77af024c92b53e7b020b7b38724a88aafa52ef8d61958f5c"
201
+ },
202
+ {
203
+ "destination": "src/solomon/calibration.py",
204
+ "kind": "serving_code",
205
+ "staged_bytes": 11273,
206
+ "staged_sha256": "4e29eb430e33c7cbf074c5be153fe858210c6d4def6462c2bf9761251e91dee6"
207
+ },
208
+ {
209
+ "destination": "src/solomon/engine.py",
210
+ "kind": "serving_code",
211
+ "staged_bytes": 7404,
212
+ "staged_sha256": "839442be642ac6449b32492b2ca0a4bce65de2d6095f097b053ed667304ec9fb"
213
+ },
214
+ {
215
+ "destination": "src/solomon/engine_contract.py",
216
+ "kind": "serving_code",
217
+ "staged_bytes": 18263,
218
+ "staged_sha256": "3df6f2e515b765cbb07d5a281328989443795d7fdc920dc21756b4230ddf1fd5"
219
+ },
220
+ {
221
+ "destination": "src/solomon/engine_cuda.py",
222
+ "kind": "serving_code",
223
+ "staged_bytes": 12700,
224
+ "staged_sha256": "28572bc9bafae9c9bd9ad42d4de2dc267ad083f126379beaf486bd4b1795641a"
225
+ },
226
+ {
227
+ "destination": "src/solomon/engine_numerics.py",
228
+ "kind": "serving_code",
229
+ "staged_bytes": 3558,
230
+ "staged_sha256": "ff6779232b56469d2273cc548f4de7f7a0f6a15731c7c0986e453a2dd1180e5f"
231
+ },
232
+ {
233
+ "destination": "src/solomon/engine_reasoning.py",
234
+ "kind": "serving_code",
235
+ "staged_bytes": 9209,
236
+ "staged_sha256": "87f417015f45293283bd3a2738c5745e5a2aac7a805ae89403fdc1fb7b226bb4"
237
+ },
238
+ {
239
+ "destination": "src/solomon/engine_reference.py",
240
+ "kind": "serving_code",
241
+ "staged_bytes": 13037,
242
+ "staged_sha256": "5487a5b0536e481a8285ccd82dff7ff389340d4f59c36653d358088b6c4050c8"
243
+ },
244
+ {
245
+ "destination": "src/solomon/evidence.py",
246
+ "kind": "serving_code",
247
+ "staged_bytes": 7617,
248
+ "staged_sha256": "a0ece458b5c0546c1f80b48cea87ebc1c3732e83f2c0f8e58969645bc8bde2f7"
249
+ },
250
+ {
251
+ "destination": "src/solomon/evidence_head.py",
252
+ "kind": "serving_code",
253
+ "staged_bytes": 19646,
254
+ "staged_sha256": "b68b93af8d956fc503c1a04bb188fc9f370a27aada4cef0a026f298c2f42dbf3"
255
+ },
256
+ {
257
+ "destination": "src/solomon/evidence_packages.py",
258
+ "kind": "serving_code",
259
+ "staged_bytes": 24688,
260
+ "staged_sha256": "8e555f09c7fa96e1e135d5bb49d388b66ffbd4fc40fc1659b4cb7396ca3728c5"
261
+ },
262
+ {
263
+ "destination": "src/solomon/evidence_selector.py",
264
+ "kind": "serving_code",
265
+ "staged_bytes": 18296,
266
+ "staged_sha256": "3cb6c6637c665dcb6ab401228a399ca8d5714bc9f91a2635d14b334abdf8b8de"
267
+ },
268
+ {
269
+ "destination": "src/solomon/heads.py",
270
+ "kind": "serving_code",
271
+ "staged_bytes": 13978,
272
+ "staged_sha256": "1934d3c50c57622fa2159211b5c9da024ab888ad6ba49d8b7e82365539a785a4"
273
+ },
274
+ {
275
+ "destination": "src/solomon/prompts_two_letter.py",
276
+ "kind": "serving_code",
277
+ "staged_bytes": 4169,
278
+ "staged_sha256": "12e19c094e48be2881cbad79f5c1ea0ffefffdcb308013d1b45925d32b6aae6f"
279
+ },
280
+ {
281
+ "destination": "src/solomon/readout.py",
282
+ "kind": "serving_code",
283
+ "staged_bytes": 14094,
284
+ "staged_sha256": "59f7f941262eba4f912dbadf4c0140f7cbb5997736f9dc3c7bae5f856763fc77"
285
+ },
286
+ {
287
+ "destination": "src/solomon/reliability.py",
288
+ "kind": "serving_code",
289
+ "staged_bytes": 1523,
290
+ "staged_sha256": "08cc3b22371a314b06bc89a7807c7c0581063f8a70a86ad92b62eae442bad42a"
291
+ },
292
+ {
293
+ "destination": "src/solomon/retrieval.py",
294
+ "kind": "serving_code",
295
+ "staged_bytes": 9155,
296
+ "staged_sha256": "2b7bd6603cb6df3bfe63496dd475206ff17655d48916cf974860adf335e9b155"
297
+ },
298
+ {
299
+ "destination": "src/solomon/routing.py",
300
+ "kind": "serving_code",
301
+ "staged_bytes": 3038,
302
+ "staged_sha256": "9901da2c3d3a0e8dc453dae91f6c68c7a32b609418fb6e8fc41e95e3601b3f77"
303
+ },
304
+ {
305
+ "destination": "src/solomon/semantics.py",
306
+ "kind": "serving_code",
307
+ "staged_bytes": 2910,
308
+ "staged_sha256": "86e1833b7f693e914cc55744ed5565ae0d436424c262f8773bb944a9f5ac65da"
309
+ },
310
+ {
311
+ "destination": "src/solomon/service.py",
312
+ "kind": "serving_code",
313
+ "staged_bytes": 39483,
314
+ "staged_sha256": "8096810e0bd318255568817d0f022be8aed8e0999b7d01cf5fd2bbafb5dcc351"
315
+ },
316
+ {
317
+ "destination": "src/solomon/service_answers.py",
318
+ "kind": "serving_code",
319
+ "staged_bytes": 20064,
320
+ "staged_sha256": "9075e9b9e7a713a9f843267dc5aebe8d6b9fc4dcaadbd9800c964d966e83fb06"
321
+ },
322
+ {
323
+ "destination": "src/solomon/service_checked.py",
324
+ "kind": "serving_code",
325
+ "staged_bytes": 7596,
326
+ "staged_sha256": "cff9d85ea8dcb3f431dd2497a636f4d02cdc2c5d2add6f12aab9889bb29e9082"
327
+ },
328
+ {
329
+ "destination": "src/solomon/service_evidence.py",
330
+ "kind": "serving_code",
331
+ "staged_bytes": 7758,
332
+ "staged_sha256": "a2021700c36d9844f607497b7ab0d098e1f51fd775c6eb2a77940fa590335603"
333
+ },
334
+ {
335
+ "destination": "src/solomon/service_heads.py",
336
+ "kind": "serving_code",
337
+ "staged_bytes": 9364,
338
+ "staged_sha256": "6dc2a0da037aea32c9bba0c3e5924c8f33779eed9b6f02c66465b3a9b960fb9c"
339
+ },
340
+ {
341
+ "destination": "src/solomon/service_packages.py",
342
+ "kind": "serving_code",
343
+ "staged_bytes": 13181,
344
+ "staged_sha256": "5f30eaaac5daa80f1fc8399828ddd1c7128a34e79fe88a3440656e76629ac8d8"
345
+ },
346
+ {
347
+ "destination": "src/solomon/service_states.py",
348
+ "kind": "serving_code",
349
+ "staged_bytes": 10657,
350
+ "staged_sha256": "329ecca03d06a53c3aa15568784b70cdde7c4ea63c3283e4aaec3f9018defdda"
351
+ },
352
+ {
353
+ "destination": "src/solomon/serving.py",
354
+ "kind": "serving_code",
355
+ "staged_bytes": 4009,
356
+ "staged_sha256": "978a6f1d1193dd535a5fc03fa393b6421a2cd7c85bb77b1dfa796b7fcfb92836"
357
+ },
358
+ {
359
+ "destination": "src/solomon/units.py",
360
+ "kind": "serving_code",
361
+ "staged_bytes": 11852,
362
+ "staged_sha256": "0b2fbf586cf73a9f54b4fed5a7e996cc8b97196ac8ee2980888a7d8cb5713d79"
363
+ },
364
+ {
365
+ "destination": "mlx/.gitignore",
366
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
367
+ "kind": "mlx_package",
368
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
369
+ "staged_bytes": 126,
370
+ "staged_sha256": "dde5ff0f5e7a2c741e6ec76777be6a2e1d3679815bdf8f44d8ca2cee2ce0e351"
371
+ },
372
+ {
373
+ "destination": "mlx/CONVERTER-SOURCE.json",
374
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
375
+ "kind": "mlx_package",
376
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
377
+ "staged_bytes": 1515,
378
+ "staged_sha256": "60bc159ff656411b5d7fbec93fc696fc616bac88f9ad1c0e87390a003111c9c0"
379
+ },
380
+ {
381
+ "destination": "mlx/LICENSE",
382
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
383
+ "kind": "mlx_package",
384
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
385
+ "staged_bytes": 11344,
386
+ "staged_sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457"
387
+ },
388
+ {
389
+ "destination": "mlx/MODIFICATIONS.md",
390
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
391
+ "kind": "mlx_package",
392
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
393
+ "staged_bytes": 1616,
394
+ "staged_sha256": "ff60b513fd97c5cbaa06af65972b234a6479216e94546f4852dd70f0dbf278ca"
395
+ },
396
+ {
397
+ "destination": "mlx/NOTICE",
398
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
399
+ "kind": "mlx_package",
400
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
401
+ "staged_bytes": 3897,
402
+ "staged_sha256": "7c62f7b3fde4f2f80f869be0bf592facd5e4a4d38dec826c1f6953bede6255ca"
403
+ },
404
+ {
405
+ "destination": "mlx/README.md",
406
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
407
+ "kind": "mlx_package",
408
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
409
+ "staged_bytes": 7417,
410
+ "staged_sha256": "586f53fd31ea723be1cbf0a2b5f35ed196960a231e132d8fd27e5842cc2b46a8"
411
+ },
412
+ {
413
+ "destination": "mlx/bf16/LICENSE",
414
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
415
+ "kind": "mlx_package",
416
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
417
+ "staged_bytes": 11344,
418
+ "staged_sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457"
419
+ },
420
+ {
421
+ "destination": "mlx/bf16/MODIFICATIONS.md",
422
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
423
+ "kind": "mlx_package",
424
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
425
+ "staged_bytes": 4195,
426
+ "staged_sha256": "eda88d63f04f03cb169579d527ceea4eb71bf44eae5c305bc688fc0e2a3fdc78"
427
+ },
428
+ {
429
+ "destination": "mlx/bf16/NOTICE",
430
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
431
+ "kind": "mlx_package",
432
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
433
+ "staged_bytes": 3897,
434
+ "staged_sha256": "7c62f7b3fde4f2f80f869be0bf592facd5e4a4d38dec826c1f6953bede6255ca"
435
+ },
436
+ {
437
+ "destination": "mlx/bf16/README.md",
438
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
439
+ "kind": "mlx_package",
440
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
441
+ "staged_bytes": 3063,
442
+ "staged_sha256": "1e3aa92ff4687d6e46520473bcc9c207b492d9239f1f882bff185c716ecee320"
443
+ },
444
+ {
445
+ "destination": "mlx/bf16/base-manifest.json",
446
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
447
+ "kind": "mlx_package",
448
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
449
+ "staged_bytes": 6434,
450
+ "staged_sha256": "e62a6942465b43f7f47616e36a16d6f32a1bd9daa5124dd26bf61b8eab0c3b6d"
451
+ },
452
+ {
453
+ "destination": "mlx/bf16/binding.json",
454
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
455
+ "kind": "mlx_package",
456
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
457
+ "staged_bytes": 12306,
458
+ "staged_sha256": "6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530"
459
+ },
460
+ {
461
+ "destination": "mlx/bf16/conversion.json",
462
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
463
+ "kind": "mlx_package",
464
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
465
+ "staged_bytes": 6934,
466
+ "staged_sha256": "fb3db797acdf96c9eca2b6dfef2d6d385705aeade387011d45ea16b91b510c66"
467
+ },
468
+ {
469
+ "destination": "mlx/docs/UPSTREAM-MODIFICATIONS.md",
470
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
471
+ "kind": "mlx_package",
472
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
473
+ "staged_bytes": 4195,
474
+ "staged_sha256": "eda88d63f04f03cb169579d527ceea4eb71bf44eae5c305bc688fc0e2a3fdc78"
475
+ },
476
+ {
477
+ "destination": "mlx/docs/VALIDATION-20260921.md",
478
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
479
+ "kind": "mlx_package",
480
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
481
+ "staged_bytes": 2928,
482
+ "staged_sha256": "86afe1be02ed55a75c2b2a1663898f20cc536371f5dcd2df0f701f7a30ae9a47"
483
+ },
484
+ {
485
+ "destination": "mlx/examples/decide.py",
486
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
487
+ "kind": "mlx_package",
488
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
489
+ "staged_bytes": 763,
490
+ "staged_sha256": "1a004e8a474f0458a4b84024098dfcb44ec5a24a795d742d266e7557ac9fd170"
491
+ },
492
+ {
493
+ "destination": "mlx/pyproject.toml",
494
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
495
+ "kind": "mlx_package",
496
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
497
+ "staged_bytes": 1115,
498
+ "staged_sha256": "e7822f6addbb909f4424084e3cca5eb11a7f5cb929b4b5742b1690a2bed756b5"
499
+ },
500
+ {
501
+ "destination": "mlx/requirements.cloud.lock",
502
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
503
+ "kind": "mlx_package",
504
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
505
+ "staged_bytes": 74492,
506
+ "staged_sha256": "4b60f433f1f67289db1db68630d011aea1565b50cf518c1e7461d7c2b0e2599b"
507
+ },
508
+ {
509
+ "destination": "mlx/requirements.lock",
510
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
511
+ "kind": "mlx_package",
512
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
513
+ "staged_bytes": 1338,
514
+ "staged_sha256": "cb2a81cc73252bc54570e035483d68e24253c98223e0e65373df5995bd99dc5a"
515
+ },
516
+ {
517
+ "destination": "mlx/scripts/benchmark.py",
518
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
519
+ "kind": "mlx_package",
520
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
521
+ "staged_bytes": 5074,
522
+ "staged_sha256": "7a9ee62cae81bf4d05d5ad17760bfa510886812a017eb0bc8d9cab68dcd1cab6"
523
+ },
524
+ {
525
+ "destination": "mlx/scripts/benchmark_chunks.py",
526
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
527
+ "kind": "mlx_package",
528
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
529
+ "staged_bytes": 1897,
530
+ "staged_sha256": "216e10981bdff42d04269082ae2317ab25a161c80504813aa74ee1a55b7ca167"
531
+ },
532
+ {
533
+ "destination": "mlx/scripts/check_cuda_parity.py",
534
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
535
+ "kind": "mlx_package",
536
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
537
+ "staged_bytes": 7614,
538
+ "staged_sha256": "082ae6c535f52d6b386d3348222515ef564b38efaf59fd8d6b262d00867b0a24"
539
+ },
540
+ {
541
+ "destination": "mlx/scripts/score_parity.py",
542
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
543
+ "kind": "mlx_package",
544
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
545
+ "staged_bytes": 542,
546
+ "staged_sha256": "ddc84f869bccd5d2c270f6abf2a0862c59ec7c0ebdc3497959ba35c16bb11b7a"
547
+ },
548
+ {
549
+ "destination": "mlx/scripts/validate_api.py",
550
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
551
+ "kind": "mlx_package",
552
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
553
+ "staged_bytes": 5852,
554
+ "staged_sha256": "f29c49ac600314491c36c43b780b92958dea56253377b4b64a2da7ecb4b57a28"
555
+ },
556
+ {
557
+ "destination": "mlx/src/solomon_mlx/__init__.py",
558
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
559
+ "kind": "mlx_package",
560
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
561
+ "staged_bytes": 132,
562
+ "staged_sha256": "714a5439b33780d250c404938cd062c0d6809f83f21b59f89997bd168469925a"
563
+ },
564
+ {
565
+ "destination": "mlx/src/solomon_mlx/_vendor/__init__.py",
566
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
567
+ "kind": "mlx_package",
568
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
569
+ "staged_bytes": 113,
570
+ "staged_sha256": "77defd15cc47661e0e9a31275e7fd59e979f3f9a6429671d8199f6002dca6c2c"
571
+ },
572
+ {
573
+ "destination": "mlx/src/solomon_mlx/_vendor/contract.py",
574
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
575
+ "kind": "mlx_package",
576
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
577
+ "staged_bytes": 6474,
578
+ "staged_sha256": "7c6607179a028f30b462349a86f9cf7d0ec2652e0a1c7c8c24649b6ce71df697"
579
+ },
580
+ {
581
+ "destination": "mlx/src/solomon_mlx/_vendor/evidence.py",
582
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
583
+ "kind": "mlx_package",
584
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
585
+ "staged_bytes": 7730,
586
+ "staged_sha256": "18a4978a27d6bbd3f7daaa34836d1820303c4ff581b0dfda877aab586392293b"
587
+ },
588
+ {
589
+ "destination": "mlx/src/solomon_mlx/_vendor/evidence_v3.py",
590
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
591
+ "kind": "mlx_package",
592
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
593
+ "staged_bytes": 24783,
594
+ "staged_sha256": "6ff3da1e920c082e9b3629ca0230007262f2ce69fd921b89dd4b3e67511b3a64"
595
+ },
596
+ {
597
+ "destination": "mlx/src/solomon_mlx/_vendor/prompts.py",
598
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
599
+ "kind": "mlx_package",
600
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
601
+ "staged_bytes": 4693,
602
+ "staged_sha256": "8bfb5a12d625664c8830f5cf243aac04d1e973bfe4f5ecfc8221021f09c318df"
603
+ },
604
+ {
605
+ "destination": "mlx/src/solomon_mlx/_vendor/retrieval.py",
606
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
607
+ "kind": "mlx_package",
608
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
609
+ "staged_bytes": 9261,
610
+ "staged_sha256": "11e7bbb1a84ba837eac3768d6e882f44a557be25d62729c8a39dd5cb870c0932"
611
+ },
612
+ {
613
+ "destination": "mlx/src/solomon_mlx/_vendor/semantics.py",
614
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
615
+ "kind": "mlx_package",
616
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
617
+ "staged_bytes": 3051,
618
+ "staged_sha256": "b580d3c114536a6faf58bcd92d2c61028f80891a19d78d6a17ed585948abfc15"
619
+ },
620
+ {
621
+ "destination": "mlx/src/solomon_mlx/api.py",
622
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
623
+ "kind": "mlx_package",
624
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
625
+ "staged_bytes": 13772,
626
+ "staged_sha256": "bae9fdd3ef6b395142ec5c099635cc9ba90f7f33c7423f99caf6009f51d4fd3c"
627
+ },
628
+ {
629
+ "destination": "mlx/src/solomon_mlx/artifacts.py",
630
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
631
+ "kind": "mlx_package",
632
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
633
+ "staged_bytes": 2614,
634
+ "staged_sha256": "0b3d102a2a04d88b2a1f3eb8a22506f626191615d779bfd53a4377824e6c239b"
635
+ },
636
+ {
637
+ "destination": "mlx/src/solomon_mlx/budget.py",
638
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
639
+ "kind": "mlx_package",
640
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
641
+ "staged_bytes": 2875,
642
+ "staged_sha256": "f72614686c19e1d923e6bc23d4353e8b1ac150863565939ae4a3831bc4ecd211"
643
+ },
644
+ {
645
+ "destination": "mlx/src/solomon_mlx/cli.py",
646
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
647
+ "kind": "mlx_package",
648
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
649
+ "staged_bytes": 2990,
650
+ "staged_sha256": "0a6be4cd03621963ec5f73a8af71224cb8b40ae88232477d8370b7d3b9b75021"
651
+ },
652
+ {
653
+ "destination": "mlx/src/solomon_mlx/engine.py",
654
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
655
+ "kind": "mlx_package",
656
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
657
+ "staged_bytes": 15359,
658
+ "staged_sha256": "13c27c3fbca576ae0d70262c2a75c9382df3b4fcfd8a11996d125750883b648a"
659
+ },
660
+ {
661
+ "destination": "mlx/src/solomon_mlx/evaluation.py",
662
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
663
+ "kind": "mlx_package",
664
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
665
+ "staged_bytes": 13906,
666
+ "staged_sha256": "bb9f50d184527e21139b9ffb5e9d1613bc30822ccd7cf06b7b7f8b1cc8b50674"
667
+ },
668
+ {
669
+ "destination": "mlx/src/solomon_mlx/prepare.py",
670
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
671
+ "kind": "mlx_package",
672
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
673
+ "staged_bytes": 5513,
674
+ "staged_sha256": "dda9879a9ddc1c3f6dcf0df52b3a244b2ca7920946bbd29ddc8e1c0f9944b887"
675
+ },
676
+ {
677
+ "destination": "mlx/src/solomon_mlx_hub/__init__.py",
678
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
679
+ "kind": "mlx_package",
680
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
681
+ "staged_bytes": 248,
682
+ "staged_sha256": "9132679f7a5826d1f472cef6a42068ead04b9c4737d2c5fa667d8cb8437212e2"
683
+ },
684
+ {
685
+ "destination": "mlx/src/solomon_mlx_hub/__main__.py",
686
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
687
+ "kind": "mlx_package",
688
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
689
+ "staged_bytes": 1852,
690
+ "staged_sha256": "86cf93d7c2a2342e1f9babc4981f4dbd1255db08d79406ec0c3faaff91762f1c"
691
+ },
692
+ {
693
+ "destination": "mlx/src/solomon_mlx_hub/data/base-manifest.json",
694
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
695
+ "kind": "mlx_package",
696
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
697
+ "staged_bytes": 6434,
698
+ "staged_sha256": "e62a6942465b43f7f47616e36a16d6f32a1bd9daa5124dd26bf61b8eab0c3b6d"
699
+ },
700
+ {
701
+ "destination": "mlx/src/solomon_mlx_hub/data/release.json",
702
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
703
+ "kind": "mlx_package",
704
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
705
+ "staged_bytes": 8582,
706
+ "staged_sha256": "3b07f07cdb19897ecec0694cfe2eb745a8d8e99c573ec74dc2411b965715f70a"
707
+ },
708
+ {
709
+ "destination": "mlx/src/solomon_mlx_hub/prepare.py",
710
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
711
+ "kind": "mlx_package",
712
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
713
+ "staged_bytes": 8707,
714
+ "staged_sha256": "9d433eb7b242aaa83e1f3c3f4a2b9f99cf410299f99c133732f6ff5559a9b202"
715
+ },
716
+ {
717
+ "destination": "mlx/tests/test_artifacts_and_evaluation.py",
718
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
719
+ "kind": "mlx_package",
720
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
721
+ "staged_bytes": 2745,
722
+ "staged_sha256": "10bbfb9b9e7d66c7dae6fd1e2ca2980572e4a81f93f847c3a0f49525d8053a23"
723
+ },
724
+ {
725
+ "destination": "mlx/tests/test_budget.py",
726
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
727
+ "kind": "mlx_package",
728
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
729
+ "staged_bytes": 780,
730
+ "staged_sha256": "4faef6c3ad8aa65df057babb86d4092943a4a262bb859445ee1dde1e29a826d5"
731
+ },
732
+ {
733
+ "destination": "mlx/tests/test_contract.py",
734
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
735
+ "kind": "mlx_package",
736
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
737
+ "staged_bytes": 3393,
738
+ "staged_sha256": "b4ac6dba4ac70f1f8c8c145fba62d276d3af9dc7ab833652c92f85a3048e5f26"
739
+ },
740
+ {
741
+ "destination": "mlx/tests/test_hub.py",
742
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
743
+ "kind": "mlx_package",
744
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
745
+ "staged_bytes": 7227,
746
+ "staged_sha256": "5926ceb8e970f1cc3c7a0b9fda2d35638f31eb12009d7232fabde4308247c633"
747
+ },
748
+ {
749
+ "destination": "mlx/tests/test_metal_primitives.py",
750
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
751
+ "kind": "mlx_package",
752
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
753
+ "staged_bytes": 1704,
754
+ "staged_sha256": "2d88596fba6cb6b16acb9af958add22e9b21d367dbd4fcc277878973a5e3c30a"
755
+ },
756
+ {
757
+ "destination": "mlx/tests/test_parity.py",
758
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
759
+ "kind": "mlx_package",
760
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
761
+ "staged_bytes": 763,
762
+ "staged_sha256": "77487c307fe272a99e1f17b9e26e30dc987127a7c47fc7c2c8a545bca174b881"
763
+ },
764
+ {
765
+ "destination": "mlx/tests/test_processor.py",
766
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
767
+ "kind": "mlx_package",
768
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
769
+ "staged_bytes": 853,
770
+ "staged_sha256": "521586bf405dced3dc8ebe890c2ad3f42dd99792c08fd84e9d0a4398642b3727"
771
+ },
772
+ {
773
+ "destination": "mlx/tests/test_tiny_decoder.py",
774
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
775
+ "kind": "mlx_package",
776
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
777
+ "staged_bytes": 2369,
778
+ "staged_sha256": "e2a7496551da6d6690ee55c2d299a4759122bcfa77d48f0424bac2892a555dcf"
779
+ },
780
+ {
781
+ "destination": "mlx/tests/test_tiny_library.py",
782
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
783
+ "kind": "mlx_package",
784
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
785
+ "staged_bytes": 6658,
786
+ "staged_sha256": "03a2887be6713671ce1307d24c1e41e5d5025423c24230c4d7e99743a9b50bc8"
787
+ },
788
+ {
789
+ "destination": "mlx/uv.lock",
790
+ "generated_by": "the MLX packaging step (separate owner); staged in place",
791
+ "kind": "mlx_package",
792
+ "note": "part of the optional Apple-silicon package under mlx/; not loaded by the serving layer under src/",
793
+ "staged_bytes": 285374,
794
+ "staged_sha256": "e413d49af059f183f7fd4a39fccae8cc2c3908a4e7562926ea8de248880ddbc7"
795
+ }
796
+ ],
797
+ "hash_or_size_mismatch": [],
798
+ "heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab",
799
+ "license": "apache-2.0",
800
+ "live_acceptance_passed_for_this_binding": true,
801
+ "missing_sources": [],
802
+ "model": "Solomon",
803
+ "not_shipped_though_statically_reachable": [
804
+ "a smoke test carrying an embedded synthetic document",
805
+ "the answer-head training loop",
806
+ "the benchmark-panel builder (panel authoring and validation, not a serving file)",
807
+ "the evaluation harness",
808
+ "the superseded training-side confidence layer",
809
+ "the training-side calibration fit",
810
+ "the training/evaluation data module"
811
+ ],
812
+ "not_staged": [],
813
+ "owner_release_decision_recorded": true,
814
+ "policy": "DEFAULT DENY. Only the destinations listed in `files` may exist in the repository. `ops/solomon_package.py --enforce` walks the working tree and fails on anything else.",
815
+ "publishing_performed": false,
816
+ "readout": "four_collapsed",
817
+ "repo_id": "DoccyHealth/Solomon",
818
+ "runtime_fingerprint": "7d529382321e0e14131a643920d7522b85d1b521d249db89fba839b3fd0f8bea",
819
+ "schema": "solomon.distribution-manifest.v1",
820
+ "serving_binding_sha256": "0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8",
821
+ "staged_count": 104,
822
+ "staged_into": "release/Solomon",
823
+ "totals": {
824
+ "bytes_from_disk": 903843866,
825
+ "entries": 104,
826
+ "ready": 104
827
+ },
828
+ "version": "1.1.0"
829
+ }
MODIFICATIONS.md ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Statement of changes
2
+
3
+ Apache License 2.0, section 4(b): prominent notice that files carry modifications.
4
+
5
+ ## What is modified
6
+
7
+ **No upstream source file is modified.** No file from `Qwen/Qwen3.8-27B` is edited,
8
+ renamed, patched or redistributed in this repository, with one exception: the
9
+ upstream licence text is reproduced byte-for-byte at `licenses/Qwen-Apache-2.0.txt`
10
+ (sha256 `bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`) because section 4(a) requires it. That file is
11
+ unmodified and carries the upstream copyright.
12
+
13
+ The modification this work carries is not an edit to a source file. It is a set
14
+ of **trained parameters applied to the base model at inference time**, plus an
15
+ original serving layer that reads the model's logits. Concretely:
16
+
17
+ | Change | Artifact | sha256 |
18
+ |---|---|---|
19
+ | LoRA adapter, rank 64, question-side placement, float32 | `adapter/adapter.safetensors` | `d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0` |
20
+ | Trained linear answer heads | `adapter/heads.npz` | `f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab` |
21
+ | Readout calibration, one positive scalar per answer type | `serving/readout-temperature-v3.json` | file `1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9` / payload `945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b` |
22
+ | Runtime identity binding (bf16, default) | `serving/serving-binding.json` | payload `0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8` |
23
+ | Runtime identity binding (fp32) | `serving/serving-binding-fp32.json` | payload `517f263000cf65457751c4fba519221d48ac550e060b241f198b007ae88c59db` |
24
+ | Runtime identity binding (int8) | `serving/serving-binding-int8.json` | payload `b550254777ceb3f53e7e10e15f7dc9f80f620ddd9c1ed69592190da5f2107f16` |
25
+ | Experimental evidence head weights | `serving/evidence-head.safetensors` | `5088019adb67e523ef7411cf75e354f1070e5752c132e50f5f3acc4feb1c0f6c` |
26
+ | Experimental evidence head config | `serving/evidence-head.json` | `b8bf1d642af9f3a830f1ee47c49bda7336d65b56b97fe3625ea1741f133fa244` |
27
+ | Experimental evidence policy | `serving/evidence-policy.json` | `866888d26a5da9c91ffb1b418334e174a80320ea5f8d2216c2d459f2e25f3c84` |
28
+ | Serving layer (original work, not derived from upstream code) | `src/` | see `MANIFEST.json` |
29
+
30
+ Base model, unmodified and not redistributed: `Qwen/Qwen3.8-27B` at revision
31
+ `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, Copyright 2026 Alibaba Cloud, Apache-2.0.
32
+
33
+ The v1.1 adapter and heads were trained on 21 September 2026 (v1.0: 16 to 20 September 2026). The shipped artifacts are
34
+ identified by the checksums in the table above and in `MANIFEST.json`.
35
+
36
+ ## Which files carry a change notice
37
+
38
+ | File | Why |
39
+ |---|---|
40
+ | `NOTICE` | Section 4(b) and 4(c) statement, carried with every copy |
41
+ | `MODIFICATIONS.md` | this file |
42
+ | `README.md` | names the base model, the pinned revision and the derivative relationship in the front matter and in the body |
43
+ | `adapter/config.json` | machine-readable record of the base repo, the pinned revision and both weight checksums |
44
+ | `serving/serving-binding.json` | pins the exact runtime the weights were measured on and refuses to load against a different one |
45
+
46
+ No file under `src/` carries an upstream change notice, because no file under
47
+ `src/` contains upstream code. Every file there is original work, written for
48
+ this project, and is covered by the repository's own Apache-2.0 `LICENSE` and by
49
+ the copyright line in `NOTICE`.
50
+
51
+ All of the changes described above — the adapter, the heads, the calibration, the
52
+ serving binding and the serving layer — are Copyright 2026
53
+ Doccy Pty Ltd and licensed under Apache-2.0.
54
+
55
+ ## Third-party text scan
56
+
57
+ Before release, **every file staged into this repository was scanned for text
58
+ originating in third-party source documents.** The scan compared normalised
59
+ 6-gram and 8-gram shingles of every staged text file against:
60
+
61
+ 1. the 42 third-party source records the training and evaluation panels were
62
+ built from (Apache-2.0, MIT, BSD-3-Clause, CC BY 4.0, CC BY-SA 4.0, CC0,
63
+ OGL v3.0 and US-government public-domain assertions); and
64
+ 2. every generated panel and document corpus on disk.
65
+
66
+ **Result: no third-party document text is present in any shipped file.** The
67
+ only matches were:
68
+
69
+ * the reproduced Apache-2.0 licence text at `licenses/Qwen-Apache-2.0.txt`, which
70
+ matches an Apache-2.0 licence document held in the evaluation corpus and is
71
+ required to be here verbatim; and
72
+ * the phrase *"A missing fact is not a negative fact"*, which is **our own
73
+ prompt-template wording** appearing in our own evaluation panels, not
74
+ third-party text entering our prompts.
75
+
76
+ Acceptance fixtures are excluded from this repository entirely. The fixture
77
+ documents used in live acceptance are original synthetic text authored for this
78
+ project and held in tooling that is not distributed.
79
+
80
+ Re-run the scan with `ops/solomon_package.py --scan` in the source project.
81
+
82
+ ## Maintainer notes
83
+
84
+ These warnings used to live in a `PUSH.md` that carried its own instruction to be
85
+ deleted before the repository was made public. The repository is public now, so
86
+ that file is gone — from the manifest and from the tree — and the parts of it that
87
+ are still true are kept here.
88
+
89
+ **Never mutate a published revision.** The serving binding pins by hash and
90
+ consumers pin by revision. If something is wrong with a published revision, push a
91
+ **new** revision and a **new** tag. Do not force-push over a revision that has
92
+ already been fetched: a consumer who pinned it would silently get different weights
93
+ under a hash they already trusted.
94
+
95
+ **Do not trust a fetch tool's exit code.** The remote-volume fetch used to retrieve the adapter PRINTS "No such file or directory" AND EXITS 0 on a wrong path: a failed download is indistinguishable from a successful one by return code. Never trust the exit status; check that the file exists and that its sha256 matches before treating a fetch as done.
96
+ The 870 MB adapter in this repository was fetched that way, and the only thing that
97
+ made the fetch trustworthy was re-hashing the file afterwards. Verify what you
98
+ downloaded from here the same way:
99
+
100
+ ```sh
101
+ shasum -a 256 adapter/adapter.safetensors # d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0
102
+ shasum -a 256 adapter/heads.npz # f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab
103
+ ```
104
+
105
+ `MANIFEST.json` carries the size and sha256 of every file here.
106
+
107
+ **Both weight files must go through git-lfs.** `.gitattributes` tracks
108
+ `*.safetensors` and `*.npz`; confirm with `git lfs ls-files` before committing. An
109
+ 870 MB blob committed outside LFS has to be undone by rewriting history.
110
+
111
+ **Every generated file here comes from `release/solomon-release.json`.** Editing a
112
+ generated file by hand breaks the manifest hash and is caught by
113
+ `ops/solomon_package.py --enforce` as `DRIFTED_SINCE_STAGING`. Change the descriptor
114
+ or the generator and re-stage.
115
+
116
+ **A calibration may not be carried onto a different adapter.** The temperatures were
117
+ fitted on this model's logits. Refit and register them for a new adapter, or ship no
118
+ calibration and serve at T = 1.0, which is always permitted.
NOTICE ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Solomon v1.1.0
2
+ Copyright 2026 Doccy Pty Ltd
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
5
+ this work except in compliance with the License. You may obtain a copy of the
6
+ License in the LICENSE file distributed with this work, or at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ --------------------------------------------------------------------------------
11
+ ATTRIBUTION FOR THE BASE MODEL (Apache License 2.0, section 4(c))
12
+ --------------------------------------------------------------------------------
13
+
14
+ This work is a DERIVATIVE WORK of:
15
+
16
+ Qwen/Qwen3.8-27B
17
+ Copyright 2026 Alibaba Cloud
18
+ Licensed under the Apache License, Version 2.0
19
+ Pinned revision: 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0
20
+ Licence text: licenses/Qwen-Apache-2.0.txt (sha256 bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a, 11544 bytes,
21
+ the exact bytes served at the pinned revision)
22
+
23
+ The base model weights are NOT redistributed in this repository. They are
24
+ referenced by the pinned revision above and downloaded by the operator directly
25
+ from the upstream repository under the upstream licence.
26
+
27
+ Upstream NOTICE file: NONE. A NOTICE file is absent from Qwen/Qwen3.8-27B at the
28
+ pinned revision (HTTP 404, retrieved 2026-09-18T03:49Z without credentials).
29
+ Apache-2.0 section 4(d) therefore imposes no carry-forward obligation here. No
30
+ upstream attribution text has been invented or paraphrased.
31
+
32
+ "Qwen" and "Alibaba Cloud" are used nominatively, to identify the base model
33
+ this adapter was trained against. Apache-2.0 section 6 grants no trademark
34
+ rights and none are claimed or implied. Nothing here states or implies any
35
+ endorsement, sponsorship or affiliation.
36
+
37
+ --------------------------------------------------------------------------------
38
+ STATEMENT OF CHANGES (Apache License 2.0, section 4(b))
39
+ --------------------------------------------------------------------------------
40
+
41
+ No upstream source file is modified, and no upstream file is redistributed
42
+ except the unmodified licence text at licenses/Qwen-Apache-2.0.txt.
43
+
44
+ The modification this work carries is a trained LoRA adapter and a set of
45
+ trained linear answer heads, applied to the base model at inference time:
46
+
47
+ * LoRA adapter, rank 64, question-side placement, float32
48
+ adapter/adapter.safetensors
49
+ sha256 d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0
50
+ 870363376 bytes
51
+ * Trained linear answer heads
52
+ adapter/heads.npz
53
+ sha256 f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab
54
+ 1643162 bytes
55
+ * Readout calibration (one positive scalar per task)
56
+ serving/readout-temperature-v3.json
57
+ file sha256 1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9
58
+ payload sha256 945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b
59
+
60
+ The v1.1 adapter and heads were trained on 21 September 2026 (v1.0: 16 to 20 September 2026). The adapter and the heads
61
+ are identified by the checksums above. Full change detail is in MODIFICATIONS.md.
62
+
63
+ --------------------------------------------------------------------------------
64
+ THIRD-PARTY CONTENT IN THIS REPOSITORY
65
+ --------------------------------------------------------------------------------
66
+
67
+ The only third-party content distributed here is the unmodified Apache License
68
+ 2.0 text at licenses/Qwen-Apache-2.0.txt, reproduced to satisfy section 4(a).
69
+
70
+ No training document, evaluation panel, dataset, corpus, rendered page, cached
71
+ state, score archive or acceptance fixture is distributed. Every file in this
72
+ repository was scanned for text originating in third-party source documents
73
+ before release; see MODIFICATIONS.md, "Third-party text scan".
74
+
75
+ Everything else in this repository -- the serving code under src/, the adapter
76
+ and head weights, the calibration artifact, the serving binding and the
77
+ documentation -- is original work of Doccy Pty Ltd, licensed under
78
+ Apache-2.0.
79
+
80
+ --------------------------------------------------------------------------------
81
+ TRAINING-DATA PROVENANCE
82
+ --------------------------------------------------------------------------------
83
+
84
+ The v1.1 adapter was trained on real public documents (Australian government
85
+ pages under CC BY 4.0, UK Crown copyright under the Open Government Licence v3.0,
86
+ and US federal government works) and on synthetic documents, some of which were
87
+ produced by editing third-party natural texts. None of those texts, and no
88
+ document, panel or dataset built from them, is distributed here. The real-document
89
+ counts and licences are summarised in README.md, "Training data (v1.1)"; the
90
+ synthetic-document sources are listed with their titles, URLs and recorded
91
+ licences in README.md, "Training-data provenance", including the three recorded
92
+ as CC BY-SA 4.0.
93
+
94
+ That listing is provenance disclosure and attribution as good practice. It is not
95
+ a statement that the trained weights are a derivative work or an adaptation of
96
+ those texts. Whether share-alike terms reach model weights is unsettled; this
97
+ package takes no position on it and the owner accepted the residual risk on
98
+ 21 September 2026 rather than resolving it.
README.md ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: Qwen/Qwen3.8-27B
4
+ base_model_relation: adapter
5
+ library_name: peft
6
+ pipeline_tag: text-classification
7
+ tags:
8
+ - lora
9
+ - document-question-answering
10
+ - structured-decisions
11
+ - calibration
12
+ - synthetic-evaluation
13
+ ---
14
+
15
+ <!-- Generated by the release packager from release/solomon-release.json and the measured artifacts.
16
+ Do not edit this file by hand: it is hashed in MANIFEST.json and an edit trips enforcement. -->
17
+
18
+ # Solomon
19
+
20
+ A LoRA adapter and trained answer heads for **Qwen/Qwen3.8-27B** that turn a document plus a set of
21
+ structured questions into one probability per decision, each with a retrieval pointer to where the
22
+ support for it plausibly sits in the source. It does not generate text.
23
+
24
+ > ### Read this before you trust a number on this page
25
+ >
26
+ > **v1.1 was measured on real documents, and the headline gain is not statistically established.** On a
27
+ > held-out panel of 802 questions over
28
+ > 54 real documents it answers
29
+ > **706** whole
30
+ > questions right against 679
31
+ > for v1.0: +3.4 points, 95% document-bootstrap interval
32
+ > [-1.3, +7.4].
33
+ > The interval includes zero.
34
+ >
35
+ > **The evaluation labels are AI-generated and have not been checked by a human.** See **Evaluation labels**.
36
+ >
37
+ > Nothing on this page is a certified error rate and nothing is guaranteed.
38
+
39
+ ## What it is, and what it is for
40
+
41
+ Give it a document once and ask structured questions against it. Each answer comes back as a
42
+ probability. On request it also returns **ranked pointers** — the three sentences an experimental relevance
43
+ head scores highest — as a place to start reading, not as the reason for the answer (**Evidence**, below).
44
+ There is no chat, no reasoning trace and no sampling: every answer is read from letter logits at a fixed
45
+ position through trained linear heads, so the same document and the same question return the same
46
+ numbers every time.
47
+
48
+ Four answer types:
49
+
50
+ | Type | Question shape | What comes back |
51
+ |---|---|---|
52
+ | Yes / no | does the document establish X? | one probability |
53
+ | Single choice | which of these does it state? | one probability over the listed options |
54
+ | Ordered choice | which threshold does it state? | one probability over the ordered options |
55
+ | Multi-label | which of these apply? | **one probability per candidate** |
56
+
57
+ **v1.1 removed the entity answer type.** "Which of these parties is the X?" is a yes/no question with the
58
+ party written in: ask one yes/no question per candidate, or send the parties as multi-label candidates. An
59
+ old entity request (`candidate_kind: "entity"`, or a `{candidate}` placeholder) is answered with a 400 that
60
+ says exactly this.
61
+
62
+ **It is for** turning documents into structured, machine-readable answers where you need a number
63
+ attached to each one, and where determinism and a refusal to drift matter more than fluency.
64
+
65
+ **It is not for** general knowledge question-answering, chat, generation, or summarisation. It is not
66
+ for any setting where a wrong answer is costly and cannot be checked: see **Limitations**.
67
+
68
+ ## How it works
69
+
70
+ | | |
71
+ |---|---|
72
+ | Base model | `Qwen/Qwen3.8-27B`, Apache-2.0, pinned revision `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`. **Not redistributed here** |
73
+ | Adaptation | LoRA, rank 64, **question-side** placement, float32 |
74
+ | Answer projection | trained linear heads (`adapter/heads.npz`), not the language-model head |
75
+ | Readout | structured letter logits; yes/no-shaped units collapse to a binary log-odds before temperature |
76
+ | Calibration | one scalar per answer type; **all 1.0 (unscaled) in v1.1** |
77
+ | Runtime identity | a 21-key binding (BF16; one binding per precision) that refuses to load if the engine is not the one measured |
78
+
79
+ **The document is prefilled once; the questions branch off it.** The document goes through the model a
80
+ single time and becomes a reusable state (`POST /states`). Every question is then answered as an
81
+ isolated branch off that prefix. Two consequences are worth stating because they are tested on real
82
+ hardware and not merely intended: asking the same questions in a different order returns the same
83
+ answers, and answering from the cached document state matches a full forward pass with the same decision and
84
+ within 0.05 in probability (the v1.1 acceptance runs: BF16 full, int8 subset; largest difference
85
+ 0.0108 in BF16, and 0.0097 in an earlier BF16
86
+ run). That limit is looser than the 1e-3 used for fp32, because BF16 differences of about 0.01 are expected; the
87
+ fp32 configuration was not re-run through acceptance for v1.1.
88
+
89
+ **The adapter is off while the document is read, and on from the question onward.** That is what
90
+ question-side placement means. Applying it across the whole sequence gives a different model to
91
+ the one that was measured, and the identity binding exists partly to stop that happening by accident.
92
+
93
+ **The readout is structured, not generated.** Rather than sampling an answer and parsing it, the model
94
+ is asked to commit at a fixed position and the letter logits at that position are read through trained
95
+ heads (readout mode `four_collapsed`). Yes/no-shaped units — a yes/no question, and each individual candidate
96
+ inside a multi-label answer — are read through **one merged yes/no head** and collapsed to a single binary
97
+ log-odds, `p = sigmoid(z / T)` with `z = log P(yes)/P(no)`, before the temperature is applied. The head is
98
+ shared, and results are reported per type. Choice questions apply the temperature to the listed slice,
99
+ `softmax(logits[:n] / T)`. **v1.1 serves every type at T = 1.0** (boolean 1.0 · multilabel 1.0 · single 1.0 · ordered 1.0): per-type temperatures were fitted on
100
+ the real dev panel and did not improve held-out calibration, so none is applied (see **Calibration**).
101
+
102
+ A branch whose `head_key` is not in the calibration artifact's map is **refused, not served at an
103
+ assumed 1.0**.
104
+
105
+ **The temperatures live inside the runtime binding**, covered by its checksum. A calibration fitted on
106
+ one model may not be served on another: the loader refuses by name rather than serving scalars that
107
+ mean nothing. Serving at temperature 1.0 everywhere is always permitted, on any model.
108
+
109
+ ### What the returned score means
110
+
111
+ `ordering_score` means two different things depending on the question, and the difference matters.
112
+
113
+ - **Single-unit** — a yes/no question, a single or ordered choice, and **every per-candidate value**
114
+ inside a multi-label answer. Here the score *is* the readout probability (unscaled in v1.1); how well that
115
+ magnitude holds on real documents is measured under **Reliability on real documents**, below.
116
+ - **Multi-unit** — the rolled-up question-level score for a multi-label question with more
117
+ than one candidate. It is the **product** of the per-candidate probabilities, which assumes those
118
+ candidates are independent. **That assumption has never been validated as a joint probability.** It
119
+ orders such questions well; it is a heuristic ordering, not a calibrated joint. If you need a
120
+ magnitude for one of these, read the per-candidate numbers.
121
+
122
+ The field is not called `probability` because that would be accurate for the first case and an
123
+ overclaim for the second.
124
+
125
+ There is **no abstention**. Every question is answered. The service will not emit a field named
126
+ `abstain`, `confidence`, `threshold` or `certified_error_rate`; it raises rather than return one. If
127
+ you want to decline low-confidence answers, that is your policy, made on your population, and this
128
+ release makes no claim about where to put the line.
129
+
130
+ ## How to run it
131
+
132
+ The package is a library, not a daemon: you build the engine, wrap it in the serving layer and start the
133
+ HTTP surface in four lines. **The reference CUDA configuration is BF16**: base weights in bfloat16 with the
134
+ linear-attention recurrence promoted to float32 (`precision='bf16'`, the configuration the model was trained
135
+ in). The v1.1 BF16 and fp32 measurements on this card were run on NVIDIA B200 GPUs. Two alternatives are selectable, each with
136
+ its own measured numbers below and its own identity: `precision='fp32'` (float32 weights and attention,
137
+ float64 recurrence; roughly twice the memory) and `precision='int8'` (weight-only 8-bit decoder linears via
138
+ torchao, compute in bf16; the smallest footprint, not faster: on an NVIDIA RTX A6000
139
+ (48 GB) it held 29.2 GiB after load and peaked at
140
+ 32.8 GiB on the real test text documents and
141
+ 40.4 GiB on page images). The binding pins the precision, so a
142
+ configuration can only be served against numbers measured on it. **The Apple-silicon MLX package in `mlx/` has
143
+ not been updated for v1.1**: it is pinned to the v1.0 revision of this repository and loads the v1.0 adapter,
144
+ heads and calibration, not the files described on this card. It is experimental, and none of the numbers on this
145
+ card describe it.
146
+
147
+ ```sh
148
+ pip install -r requirements.lock
149
+
150
+ huggingface-cli download DoccyHealth/Solomon --local-dir ./solomon
151
+ huggingface-cli download Qwen/Qwen3.8-27B --revision 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0 --local-dir ./solomon/base
152
+
153
+ cd solomon
154
+ export PYTHONPATH=$PWD/src
155
+ ```
156
+
157
+ Run from inside that directory: the engine looks for the base model in `base/`, and the serving layer
158
+ reads `serving/selection.json` and the binding it names, both relative to the package root.
159
+
160
+ ```python
161
+ from solomon import api, service
162
+ from solomon.serving import ServiceEngine
163
+
164
+ engine = ServiceEngine('adapter/adapter.safetensors', 'adapter/heads.npz') # precision='bf16' (default) | 'fp32' | 'int8'
165
+ layer = service.service('./store', engine) # './store' holds cached document states
166
+ # fp32 / int8: build the engine with that precision AND pass its binding, e.g.
167
+ # engine = ServiceEngine(..., precision='int8')
168
+ # layer = service.service('./store', engine, binding='serving/serving-binding-int8.json')
169
+ server = api.serve(layer) # GET /health, POST /states, POST /v1/decide
170
+ print('http://127.0.0.1:%d' % server.server_port)
171
+ server.serve_forever() # or skip the server and call layer.decide(...) directly
172
+ ```
173
+
174
+ `ServiceEngine` also takes `expected_adapter=` and `expected_heads=`, the two sha256 values printed
175
+ below; pass them and the engine refuses to start on a file that is not the one this card describes.
176
+
177
+ ### Asking questions
178
+
179
+ One request carries a document (or a saved `state_id`) and any number of questions. The four shapes:
180
+
181
+ ```jsonc
182
+ POST /v1/decide
183
+ {
184
+ "state": "…the document text…",
185
+ "evidence": "support", // none | support | sufficiency | removal
186
+ "questions": {
187
+ "certified": "Is Rookwood Ltd certified to supply produce?", // yes / no
188
+ "growers": {"type": "noul", "instructions": "Which growers may supply produce?",
189
+ "candidates": ["Rookwood Ltd", "Ostler Ltd"]}, // multi-label
190
+ "scheme": {"type": "choice", "instructions": "Which certification scheme applies?",
191
+ "options": ["Freshcare", "SQF", "GlobalG.A.P."]}, // single choice
192
+ "risk": {"type": "choice", "instructions": "What supply risk rating is recorded?",
193
+ "options": ["low", "medium", "high"], "ordered": true} // ordered choice
194
+ }
195
+ }
196
+ ```
197
+
198
+ ```jsonc
199
+ {
200
+ "answers": {
201
+ "certified": {"type": "noul", "noul": 0.97, "ordering_score": 0.97, "temperature": 1.0,
202
+ "evidence_method": "trained_relevance_head_ranked", "evidence": [{"start": 212, "end": 256, "text": "…", "score": 0.83, "rank": 1}, …], "evidence_suppressed": false},
203
+ "growers": {"type": "noul", "candidate_kind": "label",
204
+ "candidates": {"Rookwood Ltd": 0.96, "Ostler Ltd": 0.04},
205
+ "candidate_ordering_scores": {"Rookwood Ltd": 0.96, "Ostler Ltd": 0.96},
206
+ "candidate_evidence": {"Rookwood Ltd": [{"start": 212, "end": 256, "text": "…", "score": 0.91, "rank": 1}, …],
207
+ "Ostler Ltd": [{"start": 257, "end": 309, "text": "…", "score": 0.77, "rank": 1}, …]},
208
+ "candidate_evidence_suppressed": {"Rookwood Ltd": false, "Ostler Ltd": false}},
209
+ "scheme": {"type": "choice", "answer": "Freshcare", "probabilities": {"Freshcare": 0.91, "SQF": 0.06, "GlobalG.A.P.": 0.03}},
210
+ "risk": {"type": "choice", "ordered": true, "answer": "medium", "probabilities": {"low": 0.12, "medium": 0.81, "high": 0.07}}
211
+ }
212
+ }
213
+ ```
214
+
215
+ (Illustrative numbers, not measurements.) The entity form `{"instructions": "Is {candidate} a grower?",
216
+ "candidates": [...]}` was removed in v1.1: write `"growers"` above, or one yes/no question per party.
217
+
218
+ **Evidence.** At `evidence: "support"` the response carries up to three ranked sentence pointers with scores
219
+ from an experimental relevance head, per answer branch. `evidence_method` names the selector:
220
+ `trained_relevance_head_ranked`, or `lexical_overlap_fallback` (word overlap, used for page images and whenever
221
+ the head is not configured, with `evidence_fallback_reason` saying why). Neither establishes that the
222
+ answer was caused by the span (`evidence_faithfulness_established: false`). See **Evidence (experimental)**.
223
+
224
+ `GET /health` reports the contract, the readout mode and the runtime identity. If the engine you built
225
+ differs from the one the numbers were measured on — a different torch build, a different adapter, a
226
+ different arithmetic mode — **the binding refuses to load rather than quietly serving different
227
+ numbers**. That is intended behaviour; do not work around it.
228
+
229
+ ### What is in this repository
230
+
231
+ ```
232
+ README.md this file
233
+ LICENSE Apache-2.0
234
+ NOTICE attribution and the Apache-2.0 4(b)/4(c) notices
235
+ MODIFICATIONS.md statement of changes, third-party text scan, maintainer notes
236
+ MANIFEST.json every file, its size and its sha256
237
+ requirements.lock the pins of the image this was qualified on, and dependency licences
238
+ licenses/Qwen-Apache-2.0.txt the upstream licence, verbatim
239
+ adapter/adapter.safetensors the LoRA adapter
240
+ adapter/heads.npz the trained answer heads
241
+ adapter/config.json base repo, pinned revision, checksums, placement
242
+ serving/serving-binding.json runtime identity binding (bf16, default) and the frozen temperatures
243
+ serving/serving-binding-fp32.json runtime identity binding for precision=fp32
244
+ serving/serving-binding-int8.json runtime identity binding for precision=int8
245
+ serving/evidence-head.safetensors relevance head weights (float32)
246
+ serving/evidence-head.json relevance head config incl. lexical_residual alpha
247
+ serving/evidence-policy.json ranked-pointer serving policy (top 3, suppressed when not stated)
248
+ serving/readout-temperature-v3.json the calibration artifact, standalone
249
+ serving/selection.json readout mode and model identity
250
+ serving/service-export.json qualification envelope hashes
251
+ src/solomon/__init__.py solomon: a document plus structured questions in, one probability per decision out
252
+ src/solomon/api.py HTTP surface for the Solomon layer (solomon/service.py)
253
+ src/solomon/binding.py serving identity: which readout is served, and proof that it is the one that was measured
254
+ src/solomon/calibration.py the readout calibration: one positive scalar per answer type, and nothing else
255
+ src/solomon/engine.py question-only CUDA runtime for immutable semantic-head checkpoints
256
+ src/solomon/engine_contract.py the answer contract engine: contract v3 on the reference engine's float32 cached path
257
+ src/solomon/engine_cuda.py CUDA contract-v3 engine
258
+ src/solomon/engine_numerics.py versioned CUDA repair: bounded FP32 attention, FP64 recurrent accumulation
259
+ src/solomon/engine_reasoning.py the confidence layer text reasoning on immutable answer-contract base-prefix states
260
+ src/solomon/engine_reference.py the reference engine: task-agnostic document prefix, float32 arithmetic, chunked prefill
261
+ src/solomon/evidence.py deterministic source references and explicit evidence interventions
262
+ src/solomon/evidence_head.py v1.1 evidence head: the ONE forward shared by the trainer (the training tooling via
263
+ src/solomon/evidence_packages.py evidence packages (v3): selected spans plus source-derived governing context, per question unit
264
+ src/solomon/evidence_selector.py evidence selection for the Solomon layer (v1.1): the trained relevance head, with word overlap as a labelled fallback
265
+ src/solomon/heads.py final normalized feature extraction, preserving the qualified CUDA engine
266
+ src/solomon/prompts_two_letter.py two-letter (Noul) prompts and block conversions
267
+ src/solomon/readout.py contract v3 readouts: branch jobs for every answer type, predictions from letter logits, and metrics
268
+ src/solomon/reliability.py what the readout says about its own answer, with nothing fitted behind it
269
+ src/solomon/retrieval.py inference-only source candidates and explicitly labelled retrieval baselines
270
+ src/solomon/routing.py real callback-driven escalation
271
+ src/solomon/semantics.py answer semantics (design note, not distributed)
272
+ src/solomon/service.py decision layer: the Solomon serving contract over the pinned readout chain
273
+ src/solomon/service_answers.py confidence-aware five-task service, reusing immutable answer-contract input persistence
274
+ src/solomon/service_checked.py backend-neutral contract-v3 service; restart replays immutable inputs, not tensors
275
+ src/solomon/service_evidence.py optional source-grounded evidence around the existing confidence service
276
+ src/solomon/service_heads.py trained-head fast/views service; unchanged decoder and separate stage confidence
277
+ src/solomon/service_packages.py evidence packages (v3) in the answer service: per-unit, source-grounded, page-referenced
278
+ src/solomon/service_states.py local contract-v3 prototype: task-neutral text/image states and five answer types
279
+ src/solomon/serving.py preserve five-task trained-head confidence/routing with optional evidence
280
+ src/solomon/units.py v1.1 shared sentence/list-item splitter
281
+ mlx/ the optional Apple-silicon package: its own library, tests and notices
282
+ ```
283
+
284
+ **What is deliberately not here:** no training data, no evaluation panels, no document corpora, no
285
+ datasets, no cached states, no rendered images, no score archives, no test fixtures, no logs and no
286
+ base model weights. The package is default-deny: a manifest names every permitted file and the build
287
+ fails if anything else is present.
288
+
289
+ The files under `serving/` carry identity only: hashes, the readout mode, the served design and the
290
+ per-type temperatures. The loader verifies the binding's checksum and every runtime key it carries. The
291
+ measured provenance behind those hashes (which panels, which fit, which qualification run) is held in
292
+ the maintainer's records and is not distributed.
293
+
294
+ ## Evidence (experimental)
295
+
296
+ **Evidence here is a set of ranked pointers, not an explanation.** At `evidence: "support"` the service returns
297
+ the **top 3 sentences** of the document with a relevance score each (`evidence_method:
298
+ "trained_relevance_head_ranked"`). The scores come from a small relevance head fitted after training, on the
299
+ model's layer-42 states plus a word-overlap term. It reads states the answer already computed; it
300
+ makes no extra model call and **cannot change an answer**: with the head on and off, all 3,230 real
301
+ test answer branches were identical (maximum logit difference 0.0).
302
+
303
+ - `evidence_faithfulness_established` is `false`. Nothing shows the pointed-to sentence caused the answer.
304
+ - **No pointers are returned when the answer is "not stated"** (the answer's collapsed state is not-stated, or a
305
+ choice answer resolved to the reserved not-stated option). That state is the absence signal; the head itself
306
+ has no reliable "no evidence" signal.
307
+ - On the development panel the head put a labelled supporting sentence in its top 3 more often than plain word
308
+ overlap: hit@3 0.817 against 0.669
309
+ over 753 labelled rows. That comparison was used to choose the head, so it is
310
+ optimistic, and **no test-panel measurement exists**. The precision and recall targets for evidence were not established.
311
+ - **Word overlap is the labelled fallback** (`lexical_overlap_fallback`, with `evidence_fallback_reason`), used for
312
+ page-image documents and whenever the head is not configured.
313
+
314
+ Treat pointers as a place to start reading and verify them yourself.
315
+
316
+ ## What it scores
317
+
318
+ Every figure in this section was measured on the adapter this repository ships
319
+ (`d122466d430a…`) in the BF16 reference configuration unless stated. The scoring runs
320
+ loaded a heads file (`96ea51416bbe…`) that is the shipped `adapter/heads.npz`
321
+ (`f766d752d776…`) plus two legacy slots that were never read; every array the two files share is
322
+ byte-identical, so the served logits are the measured logits. The packager checks those hashes against the measurement records and refuses to
323
+ build if they differ. Figures for v1.0 are that model re-scored on the same panels, for comparison only.
324
+
325
+ ### Real documents
326
+
327
+ Real public documents (government notices, policies, agreements, correspondence, minutes and similar), split by
328
+ document into training, dev and test. Test documents were never trained on and never used for any fit.
329
+
330
+ | Panel | Questions / documents | This model | v1.0 | External reference¹ | v1.1 − v1.0, points [95% CI] |
331
+ |---|---|---|---|---|---|
332
+ | Test | 802 / 54 | **706 (88.0%)** | 679 (84.7%) | 690 (86.0%) | +3.4 [-1.3, +7.4] |
333
+ | Dev | 624 / 15 | **555 (88.9%)** | 529 (84.8%) | 553 (88.6%) | +4.2 [+1.0, +7.5] |
334
+
335
+ ¹ a commercial structured-decision API (external reference), scored on the same questions and labels.
336
+
337
+ Intervals are document-cluster bootstrap (2,000 resamples). **On test the interval includes zero**: the release
338
+ rule asked for a lower bound of −1.0 points and the measured bound is
339
+ -1.3. The owner accepted this miss for v1.1; see **Release decisions**.
340
+
341
+ Per answer type, real test:
342
+
343
+ | Answer type | Questions | This model | v1.0 | External reference | v1.1 − v1.0, points [95% CI] |
344
+ |---|---|---|---|---|---|
345
+ | Yes / no | 149 | 132 / 149 | 132 / 149 | 135 / 149 | +0.0 [-10.1, +7.2] |
346
+ | Party-role questions (formerly entity; now asked as yes/no per party) | 213 | 185 / 213 | 165 / 213 | 180 / 213 | +9.4 [+0.0, +19.2] |
347
+ | Multi-label | 226 | 191 / 226 | 190 / 226 | 184 / 226 | +0.4 [-4.8, +5.9] |
348
+ | Ordered choice | 109 | 95 / 109 | 90 / 109 | 91 / 109 | +4.6 [-1.9, +10.7] |
349
+ | Single choice | 105 | 103 / 105 | 102 / 105 | 100 / 105 | +1.0 [+0.0, +3.0] |
350
+ | Multi-label, per candidate (slots) | 1,204 | 96.5% | 95.8% | not tallied | +0.7 [-1.0, +2.3] |
351
+ | Party-role, per party (slots) | 1,329 | 97.2% | 94.9% | not tallied | +2.3 [+0.4, +4.1] |
352
+
353
+ Counts are whole questions right, with the same question definition applied to all three models.
354
+
355
+ ### Page images against text
356
+
357
+ The same 802 real test questions, asked from rendered page images instead
358
+ of extracted text: 713
359
+ whole questions right from images against 706
360
+ from text, with 98.5% of 2,936
361
+ answer branches agreeing between the two.
362
+
363
+ ### Natural images
364
+
365
+ A panel of photographs and pictures with structured questions (no document text). Two populations are reported
366
+ and they are different numbers:
367
+
368
+ - **Per answer unit** (731 units): this model 97.3%,
369
+ v1.0 96.2%, base Qwen 95.5%.
370
+ Answers stated at 0.99 or above that were wrong: this model 0.0%
371
+ of 293, base Qwen 1.5% of
372
+ 401.
373
+ - **Whole questions** (415): this model 95.2%, v1.0
374
+ 93.3%, base Qwen 92.0%.
375
+
376
+ ### General knowledge, with no document (out of domain)
377
+
378
+ 800 multiple-choice items, 400 from MMLU and 400 from MMLU-Pro, every model on the same items and prompt.
379
+
380
+ | Model | Accuracy (800) | Answers stated ≥ 0.99 | Of those, wrong | ECE (top label, 15 bins) |
381
+ |---|---|---|---|---|
382
+ | **This model (v1.1)** | 72.9% | 24 | 0 (0.0%) | 0.052 |
383
+ | v1.0 | 72.8% | 401 | 20 (5.0%) | 0.146 |
384
+ | External reference¹ | 87.1% | 314 | 5 (1.6%) | 0.040 |
385
+ | Base Qwen, same prompt | 71.8% | 187 | 4 (2.1%) | 0.049 |
386
+
387
+ All four rows come from one computation on the same 800 items. Probabilities are unscaled (T = 1) for every row,
388
+ which is how v1.1 serves them.
389
+
390
+ v1.0 stated half of its answers at 0.99 or above and was wrong on 1 in 20 of them. This model almost never
391
+ claims 0.99 on general knowledge. MMLU moved +1.5 points and
392
+ MMLU-Pro -1.25 points against v1.0; the release rule allowed at
393
+ most 1 point either way, so **MMLU-Pro missed it**. The owner accepted this miss for v1.1.
394
+
395
+ ### Reliability on real documents
396
+
397
+ Real test, text, every answer unit (each option or candidate scored against its label): of the probabilities
398
+ stated in each band, the share that were actually right. A calibrated model's column would track the band.
399
+
400
+ | Stated P(yes) | this model | external reference |
401
+ |---|---|---|
402
+ | 0.00–0.01 | 0.0% (n=2,095) | 0.1% (n=2,619) |
403
+ | 0.01–0.02 | 0.8% (n=1,038) | 0.7% (n=305) |
404
+ | 0.02–0.05 | 3.2% (n=569) | 1.8% (n=325) |
405
+ | 0.05–0.10 | 14.4% (n=132) | 3.0% (n=202) |
406
+ | 0.10–0.20 | 46.0% (n=87) | 7.5% (n=213) |
407
+ | 0.20–0.30 | 51.6% (n=31) | 18.1% (n=116) |
408
+ | 0.30–0.40 | 18.2% (n=22) | 28.6% (n=63) |
409
+ | 0.40–0.50 | 66.7% (n=18) | 30.4% (n=56) |
410
+ | 0.50–0.60 | 47.1% (n=17) | 39.5% (n=43) |
411
+ | 0.60–0.70 | 61.5% (n=13) | 55.6% (n=63) |
412
+ | 0.70–0.80 | 53.1% (n=32) | 68.6% (n=70) |
413
+ | 0.80–0.90 | 83.7% (n=43) | 82.9% (n=111) |
414
+ | 0.90–0.95 | 89.3% (n=84) | 91.0% (n=89) |
415
+ | 0.95–0.98 | 97.3% (n=295) | 92.5% (n=106) |
416
+ | 0.98–0.99 | 99.6% (n=485) | 95.6% (n=90) |
417
+ | 0.99–1.00 | 100.0% (n=126) | 99.5% (n=616) |
418
+
419
+ The top end is at or above the external reference. **The low end under-calls**: answers stated at 5–20% are yes
420
+ more often than stated. (This table is the unscaled readout, which is what v1.1 serves.)
421
+
422
+ ### Calibration
423
+
424
+ Per-type temperatures were fitted on the real dev panel (boolean 0.8175 · multilabel 0.8423 · single 1.1077 · ordered 1.2562) and checked on held-out real test.
425
+ **They did not improve held-out calibration**: test ECE got worse in 8 of 10 answer-type × modality
426
+ cells, and the question-weighted ECE across all cells was 0.0212 with the fitted temperatures against
427
+ 0.0199 unscaled. Only multi-label improved. **v1.1 therefore ships unscaled probabilities (T = 1.0 for
428
+ every type).** Real test ECE per cell (10 bins):
429
+
430
+ | Answer type | Input | Units | ECE, unscaled (served) | ECE, dev-fitted temperature | Meets 0.03 target |
431
+ |---|---|---|---|---|---|
432
+ | yes/no | image | 149 | **0.055** | 0.068 | **no** |
433
+ | yes/no | text | 149 | **0.067** | 0.081 | **no** |
434
+ | party-role (per party) | image | 1,329 | **0.009** | 0.020 | yes |
435
+ | party-role (per party) | text | 1,329 | **0.011** | 0.013 | yes |
436
+ | multi-label (per candidate) | image | 1,204 | **0.020** | 0.012 | yes |
437
+ | multi-label (per candidate) | text | 1,204 | **0.024** | 0.013 | yes |
438
+ | ordered | image | 109 | **0.073** | 0.086 | **no** |
439
+ | ordered | text | 109 | **0.054** | 0.077 | **no** |
440
+ | single | image | 105 | **0.022** | 0.053 | yes |
441
+ | single | text | 105 | **0.014** | 0.040 | yes |
442
+
443
+ The 0.03 target is **missed for yes/no** (0.067 text,
444
+ 0.055 image) **and ordered choice** (0.054 text,
445
+ 0.073 image). Those cells have only
446
+ 105–149 questions each (yes/no, single and ordered). Party-role rows are
447
+ the former entity questions, now asked as one yes/no question per party.
448
+
449
+ ### Precision configurations
450
+
451
+ Real test, text, 3,230 answer branches. Accuracy is whole
452
+ questions; flips are served decisions that differ from BF16.
453
+
454
+ | Configuration | Status | Whole-question accuracy | vs BF16, points [95% CI] | Decisions flipped vs BF16 | ECE (unscaled) |
455
+ |---|---|---|---|---|---|
456
+ | `bf16` | **default, reference** | 88.03% | — | — | 0.018 |
457
+ | `fp32` | comparison | 87.66% | -0.37 [-0.77, +0.00] | 0.10% | 0.016 |
458
+ | `int8` | option | 87.53% | -0.50 [-1.02, +0.00] | 0.27% | 0.016 |
459
+
460
+ `int8` is weight-only 8-bit (torchao) with bf16 compute. **`int8` page-image accuracy has not been scored against
461
+ the labels**; a decision-agreement run against fp32 on page images agreed on
462
+ 99.71% of served decisions. On page
463
+ images `fp32` scored 88.78% against BF16
464
+ 88.90%. No speed claim is made for any configuration.
465
+
466
+ ### Failure modes (synthetic probes)
467
+
468
+ 6,000 generated questions across 38 targeted failure modes, paired
469
+ against v1.0. Most modes are flat. Modes whose interval excludes zero:
470
+
471
+ | Mode | Questions | v1.0 | This model | Difference, points [95% CI] |
472
+ |---|---|---|---|---|
473
+ | indirect reference | 160 | 80.6% | 72.5% | -8.1 [-13.1, -3.8] |
474
+ | opposite polarity question | 135 | 94.1% | 97.0% | +3.0 [+0.7, +5.9] |
475
+
476
+ - **Paraphrase agreement** on yes/no questions: 0.951 (v1.0
477
+ 0.946); the target was 0.98 and is not met.
478
+ - **Adversarial confident flips** (answer changed at ≥ 0.9 by an injected instruction, false summary or
479
+ self-classifying text): 2.0% (v1.0 2.1%);
480
+ the target was 1% and is not met.
481
+ - **Per-type calibration on real test** misses the 0.03 ECE target for yes/no and ordered questions (see
482
+ **Calibration**, below; reported, not blocking).
483
+
484
+ ## Limitations
485
+
486
+ - **Evaluation labels are not human-verified.** Every real-document reference label was produced by AI
487
+ labellers: two blind passes plus adjudication, with 99% agreement on binary slots between the passes. The owner
488
+ decided to release v1.1 without a human label review. Some measured errors may be label errors, and some
489
+ measured successes may share a labeller's mistake.
490
+ - **The headline improvement is not significant** (interval includes zero) and the real panels are small:
491
+ 54 test documents.
492
+ - **The low end of the probability scale under-calls** on real documents (see the reliability table).
493
+ - **Indirect references regressed** on the synthetic probes (table above).
494
+ - **The multi-candidate roll-up is an ordering, not a joint probability.** Read per-candidate values if you
495
+ need a magnitude.
496
+ - **Nothing here is a certified error rate.** No threshold is enforced anywhere on the serving path.
497
+ - **Page images:** measured on the real test panel in BF16 and fp32 only.
498
+
499
+ ## Release decisions
500
+
501
+ Two release rules were missed and **both were accepted by the owner for v1.1**:
502
+
503
+ 1. Real test, whole questions: lower bound of the 95% interval -1.3
504
+ points against a rule of −1.0.
505
+ 2. General knowledge: MMLU-Pro -1.25 points against a limit
506
+ of 1 point (MMLU +1.5).
507
+
508
+ ## Evaluation labels
509
+
510
+ Reference labels on the real panels are AI-generated (two blind passes plus adjudication) and **have not been
511
+ reviewed by a human**. No Claude or GPT output is used anywhere as training input.
512
+
513
+ ## Training data (v1.1)
514
+
515
+ **Real documents.** 200 real public documents: 160 collected for this release plus
516
+ 40 from an earlier evaluation panel (those 40 are test-only). Split by document: 113 train,
517
+ 24 dev, 63 test (the evaluation panels above use the labelled subset). Licences of the
518
+ 160 collected documents, as recorded at collection: 59 Australian government pages under CC BY 4.0,
519
+ 76 UK Crown copyright under the Open Government Licence v3.0, 25 US federal government works (public domain).
520
+ No document is distributed here.
521
+
522
+ **Where the training labels came from.**
523
+
524
+ - Real-document training labels: Qwen3.8 2.4T (open weights), called through OpenRouter and routed to
525
+ third-party hosts serving full-precision weights, not the Alibaba API.
526
+ - Anchor targets from the unmodified base Qwen model, so general behaviour does not drift.
527
+ - Code generators for the synthetic documents and targeted failure-mode questions.
528
+ - Replay of the v1.0 training data (whose third-party sources are listed below).
529
+
530
+ **No Claude or GPT output is ever training input or a training label.** The build enforces this with an
531
+ allow-list of row producers.
532
+
533
+ ## Training-data provenance (third-party texts in the synthetic documents)
534
+
535
+ **No training document, panel, corpus or source text is distributed in this repository.**
536
+ The adapter was trained on synthetic documents, and some of those documents were produced by **editing
537
+ third-party natural texts**. Those texts are listed here so that their provenance is on the record, and
538
+ so that a reviewer doing lawful-sourcing diligence can see what was used without having to ask.
539
+
540
+ The model card discloses the provenance of the third-party texts the training panel was edited from, and attributes them. This is provenance disclosure and attribution as good practice, and it supports documented-lawful-sourcing procurement review. It is NOT a concession that the trained weights are a derivative work or an adaptation of those texts; that question is open and nothing in this package answers it.
541
+
542
+ 13 of the 42 reviewed sources were used in the training panel:
543
+
544
+ | | Source | URL | Licence, as recorded |
545
+ |---|---|---|---|
546
+ | | Django's security policies | [https://docs.djangoproject.com/en/dev/internals/security/](https://docs.djangoproject.com/en/dev/internals/security/) | BSD 3-Clause (Django project LICENSE, which covers the documentation in the django/django repository) |
547
+ | **CC BY-SA** | Wikipedia:Arbitration/Policy (English Wikipedia arbitration policy) | [https://en.wikipedia.org/wiki/Wikipedia:Arbitration/Policy](https://en.wikipedia.org/wiki/Wikipedia:Arbitration/Policy) | CC BY-SA 4.0 (Wikipedia text; attribution: English Wikipedia contributors, 'Wikipedia:Arbitration/Policy') |
548
+ | | Common Paper Mutual Non-Disclosure Agreement, Version 1.0 – Standard Terms | [https://commonpaper.com/standards/mutual-nda/1.0/](https://commonpaper.com/standards/mutual-nda/1.0/) | CC BY 4.0 (stated in the agreement footer and in the CommonPaper/Mutual-NDA repository README: 'free to use and modify under CC BY 4.0') |
549
+ | | 36 CFR Part 2 (National Park Service) - Resource Protection, Public Use and Recreation: sections 2.10, 2.13, 2.14, 2.15, 2.16, 2.21 and 2.22 | [https://www.ecfr.gov/current/title-36/chapter-I/part-2](https://www.ecfr.gov/current/title-36/chapter-I/part-2) | US Government work (public domain): Code of Federal Regulations text, not subject to copyright (17 U.S.C. 105) |
550
+ | | NSF Proposal & Award Policies & Procedures Guide (PAPPG, NSF 24-1), Chapter IV: Non-Award Decisions and Transactions | [https://www.nsf.gov/policies/pappg/24-1/ch-4-non-award-decisions-transactions](https://www.nsf.gov/policies/pappg/24-1/ch-4-non-award-decisions-transactions) | US Government work (public domain): U.S. National Science Foundation policy guide, not subject to copyright (17 U.S.C. 105) |
551
+ | | GOV.UK: Make a court claim for money | [https://www.gov.uk/api/content/make-court-claim-for-money](https://www.gov.uk/api/content/make-court-claim-for-money) | Open Government Licence v3.0 |
552
+ | | 42 CFR 68: NIH Loan Repayment Programs (2025 edition) | [https://www.govinfo.gov/content/pkg/CFR-2025-title42-vol1/xml/CFR-2025-title42-vol1-part68.xml](https://www.govinfo.gov/content/pkg/CFR-2025-title42-vol1/xml/CFR-2025-title42-vol1-part68.xml) | US Government work (public domain) |
553
+ | **CC BY-SA** | WordPress.com (Automattic) Terms of Service, last updated April 10, 2026 | [https://github.com/Automattic/legalmattic/blob/master/Terms%20of%20Service/WordPress.com/EN-Terms-of-Service.md](https://github.com/Automattic/legalmattic/blob/master/Terms%20of%20Service/WordPress.com/EN-Terms-of-Service.md) | CC BY-SA 4.0 (Automattic/legalmattic LICENSE.txt and README; the Terms themselves state they are available under a Creative Commons Sharealike license) |
554
+ | **CC BY-SA** | GitLab Handbook: Global Travel and Expense Policy (sections 1 to 3) | [https://handbook.gitlab.com/handbook/finance/expenses/](https://handbook.gitlab.com/handbook/finance/expenses/) | CC BY-SA 4.0 (GitLab handbook content; licence badge in the footer of handbook.gitlab.com pages) |
555
+ | | Sourcegraph Handbook: Spending company money | [https://github.com/sourcegraph/handbook/blob/main/content/benefits-pay-perks/benefits-perks/spending-company-money.md](https://github.com/sourcegraph/handbook/blob/main/content/benefits-pay-perks/benefits-perks/spending-company-money.md) | Apache License 2.0 (LICENSE of the public sourcegraph/handbook repository) |
556
+ | | Project Jupyter Governance: Executive Council | [https://raw.githubusercontent.com/jupyter/governance/main/docs/executive_council.md](https://raw.githubusercontent.com/jupyter/governance/main/docs/executive_council.md) | CC0 1.0 Universal (jupyter/governance repository LICENSE.md) |
557
+ | | 29 CFR 1904.30–1904.34: establishment records and annual summaries (2025 edition) | [https://www.govinfo.gov/content/pkg/CFR-2025-title29-vol5/xml/CFR-2025-title29-vol5-part1904.xml](https://www.govinfo.gov/content/pkg/CFR-2025-title29-vol5/xml/CFR-2025-title29-vol5-part1904.xml) | US Government work (public domain) |
558
+ | | 31 CFR 1.2, 1.4 and 1.6: Treasury FOIA requests and appeals (2025 edition) | [https://www.govinfo.gov/content/pkg/CFR-2025-title31-vol1/xml/CFR-2025-title31-vol1-part1.xml](https://www.govinfo.gov/content/pkg/CFR-2025-title31-vol1/xml/CFR-2025-title31-vol1-part1.xml) | US Government work (public domain) |
559
+
560
+ **3 of these are recorded as CC BY-SA 4.0** — *Wikipedia:Arbitration/Policy (English Wikipedia arbitration policy)*, *WordPress.com (Automattic) Terms of Service, last updated April 10, 2026*, *GitLab Handbook: Global Travel and Expense Policy (sections 1 to 3)*. Share-alike is the one term
561
+ attribution cannot cure. Whether a share-alike obligation can propagate through training into model
562
+ weights **is legally unsettled**; there is no authority settling it in either direction, and the
563
+ project's own licence review explicitly declines to infer one. The owner of this release
564
+ **accepted that residual risk on 2026-09-21 rather than resolving
565
+ it**, and kept this package under Apache-2.0. A reader should treat the question as open, not answered.
566
+
567
+ **On the strength of this evidence.** The licences above are **as recorded by the person who collected
568
+ each source**, from the source's own stated terms at the time of collection. The review records
569
+ `evidence_level: "authoring metadata assertion, not archived governing licence text"` and
570
+ `upstream_terms_independently_verified: false` for every row. No governing licence text was archived
571
+ alongside most of these sources, and this listing should not be read as a licence audit.
572
+
573
+ Listing these sources is **provenance disclosure and attribution as good practice**. It is not a
574
+ statement that the trained weights are a derivative work, an adaptation, or a copy of any of these
575
+ texts.
576
+
577
+
578
+ ## Third-party dependency licences
579
+
580
+ `requirements.lock` names the packages the serving layer needs. **None of them is redistributed in
581
+ this repository** — you install them yourself from their own publishers — so Apache-2.0 section 4(a)
582
+ imposes no bundled-notice obligation here and no dependency licence text is packaged. This summary
583
+ exists because a reviewer will ask for one.
584
+
585
+ Each licence in the table is the one declared in that distribution's OWN package metadata -- the `METADATA` file of an installed wheel -- read from a copy on the maintainer's machine. Where no copy existed, the row says NOT VERIFIED instead of guessing.
586
+
587
+ | Package | Pinned as | Licence, as declared by the distribution itself |
588
+ |---|---|---|
589
+ | `torch` | 2.13.0 | BSD-3-Clause — read from version 2.8.0 |
590
+ | `torchvision` | 0.28.0 | NOT VERIFIED |
591
+ | `transformers` | 5.17.0 | Apache 2.0 License |
592
+ | `flash-linear-attention` | 0.5.2 | NOT VERIFIED |
593
+ | `safetensors` | unpinned in the qualified image | Apache Software License — read from version 0.8.0 |
594
+ | `accelerate` | unpinned in the qualified image | Apache (Apache Software License) — read from version 1.15.0 |
595
+ | `numpy` | unpinned in the qualified image | BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0 — read from version 2.5.3 |
596
+ | `scipy` | unpinned in the qualified image | BSD License (classifier); the METADATA `License` field carries the Enthought / SciPy Developers copyright line rather than an SPDX identifier — read from version 1.18.1 |
597
+ | `pillow` | unpinned in the qualified image | MIT-CMU — read from version 12.3.0 |
598
+
599
+ **Two rows say NOT VERIFIED and mean it.** A licence read from one installed version is evidence about that version only. Where the table names a version different from the pin, that is the version whose metadata was actually read, and the pinned version's own metadata could differ. Treat this table as a starting point for
600
+ your own review, not as a legal opinion, and re-check the distributions you actually install.
601
+
602
+ Full evidence paths for each row are in the release descriptor
603
+ (`release/solomon-release.json` → `dependency_licences`), which is not distributed; the same
604
+ information is repeated in the comments of `requirements.lock`.
605
+
606
+
607
+ ## Licence and attribution
608
+
609
+ Copyright 2026 Doccy Pty Ltd.
610
+
611
+ This repository is licensed **Apache-2.0** — the adapter and head weights, the calibration artifact,
612
+ the serving code and the documentation alike. See `LICENSE` and `NOTICE`.
613
+
614
+ It is a **derivative work** of `Qwen/Qwen3.8-27B`, Copyright 2026 Alibaba Cloud, licensed under Apache-2.0.
615
+ The upstream licence text is reproduced verbatim at `licenses/Qwen-Apache-2.0.txt` (sha256
616
+ `bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`). `NOTICE` carries the attribution required by section 4(c) and
617
+ `MODIFICATIONS.md` the statement of changes required by section 4(b).
618
+
619
+ No `NOTICE` file exists in the upstream repository at the pinned revision (HTTP 404, checked
620
+ 2026-09-18), so section 4(d) carries nothing forward and no upstream attribution text has been
621
+ invented.
622
+
623
+ "Qwen" and "Alibaba Cloud" are used nominatively to identify the base model. Apache-2.0 section 6
624
+ grants no trademark rights and none are claimed. No endorsement or affiliation is implied.
625
+
626
+ ## Verify what you downloaded
627
+
628
+ ```sh
629
+ shasum -a 256 adapter/adapter.safetensors # d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0
630
+ shasum -a 256 adapter/heads.npz # f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab
631
+ shasum -a 256 serving/readout-temperature-v3.json
632
+ # -> 1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9
633
+ ```
634
+
635
+ `MANIFEST.json` carries the size and sha256 of every file in this repository.
636
+
637
+ The calibration artifact has **two legitimate and different hashes**, and confusing them makes a sound
638
+ provenance chain look tampered with. `1a2285d8…` is the *file* hash, what `shasum`
639
+ returns. `945bad44…` is the artifact's own internal `sha256` field, computed over its
640
+ contents with that field removed — a self-referential field cannot hash the file containing it. The
641
+ loader verifies the payload hash; use the file hash to check the file you were given. Both are recorded
642
+ in the serving binding's `provenance`, under those names.
643
+
644
+ ## Release record
645
+
646
+ Machine-readable identity for citation and pinning. The model identity, the calibration and the runtime
647
+ binding move together; pin by revision.
648
+
649
+ | | |
650
+ |---|---|
651
+ | Repository | `DoccyHealth/Solomon` |
652
+ | Release | `1.1.0`, 2026-09-21 |
653
+ | Serving contract | `solomon-v1` |
654
+ | Adapter sha256 | `d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0` |
655
+ | Heads sha256 | `f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab` |
656
+ | Base model | `Qwen/Qwen3.8-27B` at `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0` |
657
+ | Runtime binding sha256 (payload) | `0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8` |
658
+ | Calibration sha256 (file / payload) | `1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9` / `945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b` |
659
+ | Runtime fingerprint | `7d529382321e0e14131a643920d7522b85d1b521d249db89fba839b3fd0f8bea` |
660
+ | Readout | `four_collapsed` |
661
+ | Served temperatures | boolean 1.0 · multilabel 1.0 · single 1.0 · ordered 1.0 |
adapter/adapter.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0
3
+ size 870363376
adapter/config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_bytes": 870363376,
3
+ "adapter_file": "adapter/adapter.safetensors",
4
+ "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0",
5
+ "answer_projection": "trained-semantic-head-float32",
6
+ "base_model_copyright": "2026 Alibaba Cloud",
7
+ "base_model_license": "Apache-2.0",
8
+ "base_model_name_or_path": "Qwen/Qwen3.8-27B",
9
+ "base_model_redistributed": false,
10
+ "base_model_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
11
+ "calibration_artifact": "serving/readout-temperature-v3.json",
12
+ "calibration_file_sha256": "1a2285d8fd56d17ee1d06a1e9fce866cc0d3b0263730754babb11deea5f1f7c9",
13
+ "calibration_fitted_on": {
14
+ "adapter_sha256": "d122466d430a058bb6457d919f811160e97fbd20149f4f24ca455c5d83e360a0",
15
+ "trained_heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab"
16
+ },
17
+ "calibration_note": "a temperature is fitted on ONE model's logits. The loader refuses to serve these scalars on any adapter other than the one named in calibration_fitted_on. Serving at T = 1.0 everywhere is always permitted on any model.",
18
+ "calibration_payload_sha256": "945bad449b7f5ffc88e597277d632fbab81c3c8729e22c8babd3f4a45fe1378b",
19
+ "contract": "solomon-v1",
20
+ "copyright": "2026 Doccy Pty Ltd",
21
+ "design": {
22
+ "ordered": "S",
23
+ "single_choice": "R"
24
+ },
25
+ "dtype": "float32",
26
+ "heads_bytes": 1643162,
27
+ "heads_file": "adapter/heads.npz",
28
+ "heads_sha256": "f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab",
29
+ "license": "apache-2.0",
30
+ "lora_alpha": 64,
31
+ "measured_arithmetic": "fp32",
32
+ "measured_backend": "cuda",
33
+ "model_name": "Solomon",
34
+ "peft_type": "LORA",
35
+ "placement": "question",
36
+ "placement_note": "question-side: the adapter is OFF while the document prefix is prefilled and ON from the question branch onward. Applying it to the whole sequence gives a different model to the one that was measured.",
37
+ "r": 64,
38
+ "readout": "four_collapsed",
39
+ "runtime_fingerprint": "7d529382321e0e14131a643920d7522b85d1b521d249db89fba839b3fd0f8bea",
40
+ "schema": "solomon-adapter-config-v1",
41
+ "serving_binding": "serving/serving-binding.json",
42
+ "serving_binding_sha256": "0add0efda28902180db757a12160953e0f5d8dc303decc27b2cfda84db900da8",
43
+ "task_type": "FEATURE_EXTRACTION",
44
+ "version": "1.1.0"
45
+ }
adapter/heads.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f766d752d7768a419a9657155cf27f042834d9de29392cf7470d8725130e67ab
3
+ size 1643162
licenses/Qwen-Apache-2.0.txt ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Alibaba Cloud
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
mlx/.gitignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ *.egg-info/
6
+ snapshots/
7
+ models/
8
+ evaluations/
9
+ *.log
10
+
11
+ document-replay.json
12
+ dist/
mlx/CONVERTER-SOURCE.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "converter_code_sha256": "810ec77e6a1e4972a942bc5bcea502183b97e048c9b317a6cc38c2cab45fcaeb",
3
+ "files": {
4
+ "__init__.py": "714a5439b33780d250c404938cd062c0d6809f83f21b59f89997bd168469925a",
5
+ "_vendor/__init__.py": "77defd15cc47661e0e9a31275e7fd59e979f3f9a6429671d8199f6002dca6c2c",
6
+ "_vendor/contract.py": "7c6607179a028f30b462349a86f9cf7d0ec2652e0a1c7c8c24649b6ce71df697",
7
+ "_vendor/evidence.py": "18a4978a27d6bbd3f7daaa34836d1820303c4ff581b0dfda877aab586392293b",
8
+ "_vendor/evidence_v3.py": "6ff3da1e920c082e9b3629ca0230007262f2ce69fd921b89dd4b3e67511b3a64",
9
+ "_vendor/prompts.py": "8bfb5a12d625664c8830f5cf243aac04d1e973bfe4f5ecfc8221021f09c318df",
10
+ "_vendor/retrieval.py": "11e7bbb1a84ba837eac3768d6e882f44a557be25d62729c8a39dd5cb870c0932",
11
+ "_vendor/semantics.py": "b580d3c114536a6faf58bcd92d2c61028f80891a19d78d6a17ed585948abfc15",
12
+ "api.py": "bae9fdd3ef6b395142ec5c099635cc9ba90f7f33c7423f99caf6009f51d4fd3c",
13
+ "artifacts.py": "0b3d102a2a04d88b2a1f3eb8a22506f626191615d779bfd53a4377824e6c239b",
14
+ "budget.py": "f72614686c19e1d923e6bc23d4353e8b1ac150863565939ae4a3831bc4ecd211",
15
+ "cli.py": "0a6be4cd03621963ec5f73a8af71224cb8b40ae88232477d8370b7d3b9b75021",
16
+ "engine.py": "13c27c3fbca576ae0d70262c2a75c9382df3b4fcfd8a11996d125750883b648a",
17
+ "evaluation.py": "bb9f50d184527e21139b9ffb5e9d1613bc30822ccd7cf06b7b7f8b1cc8b50674",
18
+ "prepare.py": "dda9879a9ddc1c3f6dcf0df52b3a244b2ca7920946bbd29ddc8e1c0f9944b887"
19
+ },
20
+ "schema": "solomon-mlx-converter-source-v1"
21
+ }
mlx/LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Doccy Pty Ltd
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
mlx/MODIFICATIONS.md ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Solomon MLX modifications
2
+
3
+ This port adapts Doccy Pty Ltd’s Apache-2.0 Solomon source at revision
4
+ `5c0a4a82ddaeca6da2e3013f7045a8196c86957d`. Original modification notices are preserved in
5
+ `docs/UPSTREAM-MODIFICATIONS.md`.
6
+
7
+ Changes:
8
+
9
+ - Replaced CUDA execution with MLX-VLM Qwen3.5 execution for the declared Qwen3.8 architecture.
10
+ - Added BF16 shard conversion with FP32 normalization parameters, adapter, trained heads and recurrent states.
11
+ - Replaced global adapter state with instance-owned state and isolated question cache containers.
12
+ - Removed vocabulary projection from decision inference.
13
+ - Added a document-state API, replay recipes, artifact checksums and MLX-specific runtime identities.
14
+ - Extracted the original prompts, question contract, semantics and evidence utilities into `_vendor`.
15
+ - Implemented the documented ordering-score product locally because the release omits `scope9.reliability`.
16
+ - Added download, CUDA parity comparison, benchmark and test tools. New temperature fitting is outside the current scope.
17
+
18
+ No claim is made that this port inherits CUDA calibration or qualification. See
19
+ `docs/VALIDATION-20260921.md` for measured results and outstanding validation.
20
+
21
+ Adapter-only distribution update (21 September 2026): added a separate verified Hub
22
+ loader, atomic local assembly and cache reuse; retained the exact historical converter
23
+ source and FP32 normalization handling. Base weights are downloaded from the pinned
24
+ Qwen repository. Duplicate adapter/head files under `mlx/bf16` are replaced by the
25
+ root `adapter/` copies. CUDA parity status is reported separately from conversion.
mlx/NOTICE ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Solomon v1.1.0
2
+ Copyright 2026 Doccy Pty Ltd
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
5
+ this work except in compliance with the License. You may obtain a copy of the
6
+ License in the LICENSE file distributed with this work, or at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ --------------------------------------------------------------------------------
11
+ ATTRIBUTION FOR THE BASE MODEL (Apache License 2.0, section 4(c))
12
+ --------------------------------------------------------------------------------
13
+
14
+ This work is a DERIVATIVE WORK of:
15
+
16
+ Qwen/Qwen3.8-27B
17
+ Copyright 2026 Alibaba Cloud
18
+ Licensed under the Apache License, Version 2.0
19
+ Pinned revision: 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0
20
+ Licence text: licenses/Qwen-Apache-2.0.txt (sha256 bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a, 11544 bytes,
21
+ the exact bytes served at the pinned revision)
22
+
23
+ The base model weights are NOT redistributed in this repository. They are
24
+ referenced by the pinned revision above and downloaded by the operator directly
25
+ from the upstream repository under the upstream licence.
26
+
27
+ Upstream NOTICE file: NONE. A NOTICE file is absent from Qwen/Qwen3.8-27B at the
28
+ pinned revision (HTTP 404, retrieved 2026-09-18T03:49Z without credentials).
29
+ Apache-2.0 section 4(d) therefore imposes no carry-forward obligation here. No
30
+ upstream attribution text has been invented or paraphrased.
31
+
32
+ "Qwen" and "Alibaba Cloud" are used nominatively, to identify the base model
33
+ this adapter was trained against. Apache-2.0 section 6 grants no trademark
34
+ rights and none are claimed or implied. Nothing here states or implies any
35
+ endorsement, sponsorship or affiliation.
36
+
37
+ --------------------------------------------------------------------------------
38
+ STATEMENT OF CHANGES (Apache License 2.0, section 4(b))
39
+ --------------------------------------------------------------------------------
40
+
41
+ No upstream source file is modified, and no upstream file is redistributed
42
+ except the unmodified licence text at licenses/Qwen-Apache-2.0.txt.
43
+
44
+ The modification this work carries is a trained LoRA adapter and a set of
45
+ trained linear answer heads, applied to the base model at inference time:
46
+
47
+ * LoRA adapter, rank 64, question-side placement, float32
48
+ adapter/adapter.safetensors
49
+ sha256 2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca
50
+ 870363376 bytes
51
+ * Trained linear answer heads
52
+ adapter/heads.npz
53
+ sha256 126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f
54
+ 2053938 bytes
55
+ * Readout calibration (one positive scalar per task)
56
+ serving/scope9-readout-temperature-v2.json
57
+ file sha256 baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118
58
+ payload sha256 682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278
59
+
60
+ Training took place between 16 and 20 September 2026. The adapter and the heads
61
+ are identified by the checksums above. Full change detail is in MODIFICATIONS.md.
62
+
63
+ --------------------------------------------------------------------------------
64
+ THIRD-PARTY CONTENT IN THIS REPOSITORY
65
+ --------------------------------------------------------------------------------
66
+
67
+ The only third-party content distributed here is the unmodified Apache License
68
+ 2.0 text at licenses/Qwen-Apache-2.0.txt, reproduced to satisfy section 4(a).
69
+
70
+ No training document, evaluation panel, dataset, corpus, rendered page, cached
71
+ state, score archive or acceptance fixture is distributed. Every file in this
72
+ repository was scanned for text originating in third-party source documents
73
+ before release; see MODIFICATIONS.md, "Third-party text scan".
74
+
75
+ Everything else in this repository -- the serving code under src/, the adapter
76
+ and head weights, the calibration artifact, the serving binding and the
77
+ documentation -- is original work of Doccy Pty Ltd, licensed under
78
+ Apache-2.0.
mlx/README.md ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Solomon MLX
2
+
3
+ Full BF16 Solomon v1.1 inference on Apple Silicon. The repository distributes
4
+ adapters and source code; the Qwen backbone is downloaded separately from its
5
+ pinned upstream revision and converted locally. No quantization or LoRA merging
6
+ is performed. Full-model measurements use an M5 Max with 128 GB memory, with
7
+ about 59.9 GB peak Metal allocation on the focused fixtures. Longer documents
8
+ and more images require additional memory; 24–32 GB Macs cannot run this profile.
9
+
10
+ **Status: experimental.** Focused text and image decisions match CUDA. Complete
11
+ held-out parity remains pending. Qualification is CUDA parity only: no new
12
+ calibration or temperature fitting. See [validation results](docs/VALIDATION-20260921.md).
13
+
14
+ ## Install from this repository
15
+
16
+ Use Python 3.12 or 3.13 on Apple Silicon. Pin the full repository commit shown on
17
+ Hugging Face, including after any repository history rewrite. The historical
18
+ Solomon source revision remains provenance; it is not required to be downloadable.
19
+
20
+ ```sh
21
+ # From a source checkout, enter its mlx/ directory first.
22
+ uv sync --frozen --extra dev
23
+
24
+ # Obtain the current commit once, then retain it for repeatable downloads.
25
+ SOLOMON_COMMIT=$(uv run python -c 'from huggingface_hub import HfApi; print(HfApi().model_info("DoccyHealth/Solomon").sha)')
26
+ uv run solomon-mlx-hub prepare --revision "$SOLOMON_COMMIT" --output models/quality
27
+ ```
28
+
29
+ Authenticate with `hf auth login` first if the repository requires access. This
30
+ package is supplied here as source; it is not claimed to be published on PyPI.
31
+ For a checkout without downloading model files through Git LFS:
32
+
33
+ ```sh
34
+ GIT_LFS_SKIP_SMUDGE=1 git clone https://huggingface.co/DoccyHealth/Solomon
35
+ cd Solomon/mlx
36
+ ```
37
+
38
+ The setup tool explicitly fetches `adapter/**` and the retained MLX licensing
39
+ metadata. It downloads `Qwen/Qwen3.8-27B` at
40
+ `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, verifies every input against the
41
+ bundled manifest, and converts one shard at a time. Budget about 112 GB of disk
42
+ for original and converted weights, plus temporary space and caches. Existing
43
+ original base files can be reused with `--base /path/to/original-qwen`.
44
+
45
+ The output contains `backbone/`, the unmerged adapter, trained heads, notices
46
+ and a fresh `binding.json`. Existing valid outputs are verified and reused;
47
+ corrupt or incompatible outputs fail without being overwritten. Conversion is
48
+ atomic and concurrent preparations into the same output are rejected. Interrupted
49
+ conversions may leave a hidden temporary directory; the final output is never
50
+ marked ready before verification completes.
51
+
52
+ For fully offline preparation, provide both downloaded inputs:
53
+
54
+ ```sh
55
+ uv run solomon-mlx-hub prepare --revision "$SOLOMON_COMMIT" \
56
+ --snapshot /path/to/solomon-snapshot --base /path/to/original-qwen \
57
+ --output models/quality
58
+ uv run solomon-mlx-hub verify models/quality
59
+ ```
60
+
61
+ CPU conversion is the default. `--device gpu` selects Metal conversion on a Mac.
62
+ Linux CPU conversion is also supported by the existing converter and can use
63
+ `uv sync --frozen --extra cloud` with MLX's CPU backend. Linux conversion does
64
+ not run Apple Metal inference or establish CUDA parity.
65
+
66
+ ## Converter and provenance
67
+
68
+ The exact converter is [src/solomon_mlx/prepare.py](src/solomon_mlx/prepare.py).
69
+ The runtime Python sources are unchanged in behaviour. The only edits made for
70
+ this release rename the runtime identity's `source_contract` field to `solomon-v1`
71
+ and adjust comments, docstrings and one error message, so their combined
72
+ `converter_code_sha256` is
73
+ `bbcae17fc1c35db80a79d5865133a42ef9a1b0cf342fff71949972e69cce43ec`.
74
+ It is computed by `solomon_mlx.artifacts.code_identity()` from the sorted mapping
75
+ of relative Python paths to SHA-256 values. The new download/assembly wrapper is
76
+ in the separate `solomon_mlx_hub` package.
77
+
78
+ The converter retains large weights in BF16 and promotes normalization weights,
79
+ `A_log` and `dt_bias` to FP32 **before** applying upstream normalization offsets.
80
+ It validates tensor names and shapes with MLX-VLM's Qwen3.5 implementation. The
81
+ original FP32 adapter and ten trained heads are copied without changes. The
82
+ runtime applies the adapter only to question tokens, with its explicit 2.0 scale.
83
+
84
+ `bf16/conversion.json` and `bf16/binding.json` describe the historical cloud
85
+ conversion; paths inside them describe the original local model layout. They
86
+ are provenance, not a manifest of files currently present on the Hub, and they
87
+ record the converter hash of that historical conversion, `648e440cface0838f2dcc8d89b3ab172d97f4ffc1f88fe0b7cbbe3ca73b8a575`,
88
+ which predates the documentation edits described above. The root
89
+ adapter files have the same hashes as their removed duplicates. Newly converted
90
+ safetensors may serialize differently across CPU and Metal, so new outputs get
91
+ actual output checksums and their own runtime binding. Historical CUDA or MLX
92
+ qualification identities are never reused for a different artifact binding.
93
+
94
+ ## Python API
95
+
96
+ ```python
97
+ from solomon_mlx import Solomon
98
+
99
+ model = Solomon.load("models/quality", profile="quality")
100
+ with model.prefill("Rookwood Ltd holds a current certification.") as state:
101
+ result = model.decide(
102
+ state=state,
103
+ questions={"certified": {
104
+ "type": "noul",
105
+ "instructions": "Does Rookwood Ltd hold a current certification?",
106
+ }},
107
+ evidence="support",
108
+ )
109
+ print(result["answers"]["certified"])
110
+ ```
111
+
112
+ Or use `solomon_mlx_hub.load("models/quality", revision=COMMIT)` to prepare and
113
+ load in one call. `revision` must be a full 40-character commit SHA. Cached valid
114
+ outputs are reused without network access and retain their original binding.
115
+
116
+ Documents accept text, structured JSON objects, or ordered `{"text": ...}` and
117
+ `{"image": local_path}` parts. Image features are computed once per document.
118
+ There is no PDF renderer or OCR. Question forms preserve Boolean, entity and
119
+ multilabel `noul`, single `choice`, and ordered `score` semantics; candidate order
120
+ is retained. Missing/conflicting facts collapse before temperature application.
121
+ The API defaults to T=1. No new temperatures are fitted by setup or parity checks.
122
+
123
+ Evidence levels are `none`, `support`, `sufficiency`, and `removal`. Text spans use
124
+ exact code-point offsets. Sufficiency and removal re-encode the relevant source;
125
+ they do not establish causal faithfulness. Image evidence requires `page_selector=`;
126
+ otherwise the result reports `unsupported_page_selector`. States belong to one
127
+ model instance and support `close()`, `save(path)`, and `model.replay(path)`.
128
+ Replay persists a checksummed source recipe and recomputes caches.
129
+
130
+ ## Tests
131
+
132
+ ```sh
133
+ uv run pytest -q
134
+ uv run ruff check src tests scripts
135
+ ```
136
+
137
+ Tests use synthetic small models to cover conversion precision, CPU/Metal tensor
138
+ agreement, cache isolation, text/image processing, answer semantics, evidence,
139
+ artifact corruption, and adapter-only setup. These tests do not replace trained
140
+ model parity measurements. The included parity checker compares saved CUDA and
141
+ MLX scores using the same frozen temperatures exactly once, reports probability
142
+ drift separately, and cannot pass a complete-panel gate from partial results.
143
+ Evaluation inputs and private infrastructure configuration are not distributed.
144
+
145
+ Preserve `LICENSE`, `NOTICE`, `MODIFICATIONS.md`, and upstream model notices with
146
+ permitted copies. Model downloads and conversion do not alter repository visibility.
mlx/bf16/LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Doccy Pty Ltd
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
mlx/bf16/MODIFICATIONS.md ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Statement of changes
2
+
3
+ Apache License 2.0, section 4(b): prominent notice that files carry modifications.
4
+
5
+ ## What is modified
6
+
7
+ **No upstream source file is modified.** No file from `Qwen/Qwen3.8-27B` is edited,
8
+ renamed, patched or redistributed in this repository, with one exception: the
9
+ upstream licence text is reproduced byte-for-byte at `licenses/Qwen-Apache-2.0.txt`
10
+ (sha256 `bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`) because section 4(a) requires it. That file is
11
+ unmodified and carries the upstream copyright.
12
+
13
+ The modification this work carries is not an edit to a source file. It is a set
14
+ of **trained parameters applied to the base model at inference time**, plus an
15
+ original serving layer that reads the model's logits. Concretely:
16
+
17
+ | Change | Artifact | sha256 |
18
+ |---|---|---|
19
+ | LoRA adapter, rank 64, question-side placement, float32 | `adapter/adapter.safetensors` | `2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca` |
20
+ | Trained linear answer heads | `adapter/heads.npz` | `126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f` |
21
+ | Readout calibration, one positive scalar per task | `serving/scope9-readout-temperature-v2.json` | file `baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118` / payload `682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278` |
22
+ | Runtime identity binding | `serving/serving-binding.json` | payload `95683c1f87ec3f71b7657669dc311918ff53b7d41a981eaa8d827047e72cb505` |
23
+ | Serving layer (original work, not derived from upstream code) | `src/` | see `MANIFEST.json` |
24
+
25
+ Base model, unmodified and not redistributed: `Qwen/Qwen3.8-27B` at revision
26
+ `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, Copyright 2026 Alibaba Cloud, Apache-2.0.
27
+
28
+ Training took place between 16 and 20 September 2026. The shipped artifacts are
29
+ identified by the checksums in the table above and in `MANIFEST.json`.
30
+
31
+ ## Which files carry a change notice
32
+
33
+ | File | Why |
34
+ |---|---|
35
+ | `NOTICE` | Section 4(b) and 4(c) statement, carried with every copy |
36
+ | `MODIFICATIONS.md` | this file |
37
+ | `README.md` | names the base model, the pinned revision and the derivative relationship in the front matter and in the body |
38
+ | `adapter/config.json` | machine-readable record of the base repo, the pinned revision and both weight checksums |
39
+ | `serving/serving-binding.json` | pins the exact runtime the weights were measured on and refuses to load against a different one |
40
+
41
+ No file under `src/` carries an upstream change notice, because no file under
42
+ `src/` contains upstream code. Every file there is original work, written for
43
+ this project, and is covered by the repository's own Apache-2.0 `LICENSE` and by
44
+ the copyright line in `NOTICE`.
45
+
46
+ All of the changes described above — the adapter, the heads, the calibration, the
47
+ serving binding and the serving layer — are Copyright 2026
48
+ Doccy Pty Ltd and licensed under Apache-2.0.
49
+
50
+ ## Third-party text scan
51
+
52
+ Before release, **every file staged into this repository was scanned for text
53
+ originating in third-party source documents.** The scan compared normalised
54
+ 6-gram and 8-gram shingles of every staged text file against:
55
+
56
+ 1. the 42 third-party source records the training and evaluation panels were
57
+ built from (Apache-2.0, MIT, BSD-3-Clause, CC BY 4.0, CC BY-SA 4.0, CC0,
58
+ OGL v3.0 and US-government public-domain assertions); and
59
+ 2. every generated panel and document corpus on disk.
60
+
61
+ **Result: no third-party document text is present in any shipped file.** The
62
+ only matches were:
63
+
64
+ * the reproduced Apache-2.0 licence text at `licenses/Qwen-Apache-2.0.txt`, which
65
+ matches an Apache-2.0 licence document held in the evaluation corpus and is
66
+ required to be here verbatim; and
67
+ * the phrase *"A missing fact is not a negative fact"*, which is **our own
68
+ prompt-template wording** appearing in our own evaluation panels, not
69
+ third-party text entering our prompts.
70
+
71
+ Acceptance fixtures are excluded from this repository entirely. The fixture
72
+ documents used in live acceptance are original synthetic text authored for this
73
+ project and held in tooling that is not distributed.
74
+
75
+ Re-run the scan with `ops/solomon_package.py --scan` in the source project.
mlx/bf16/NOTICE ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Solomon v1.1.0
2
+ Copyright 2026 Doccy Pty Ltd
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
5
+ this work except in compliance with the License. You may obtain a copy of the
6
+ License in the LICENSE file distributed with this work, or at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ --------------------------------------------------------------------------------
11
+ ATTRIBUTION FOR THE BASE MODEL (Apache License 2.0, section 4(c))
12
+ --------------------------------------------------------------------------------
13
+
14
+ This work is a DERIVATIVE WORK of:
15
+
16
+ Qwen/Qwen3.8-27B
17
+ Copyright 2026 Alibaba Cloud
18
+ Licensed under the Apache License, Version 2.0
19
+ Pinned revision: 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0
20
+ Licence text: licenses/Qwen-Apache-2.0.txt (sha256 bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a, 11544 bytes,
21
+ the exact bytes served at the pinned revision)
22
+
23
+ The base model weights are NOT redistributed in this repository. They are
24
+ referenced by the pinned revision above and downloaded by the operator directly
25
+ from the upstream repository under the upstream licence.
26
+
27
+ Upstream NOTICE file: NONE. A NOTICE file is absent from Qwen/Qwen3.8-27B at the
28
+ pinned revision (HTTP 404, retrieved 2026-09-18T03:49Z without credentials).
29
+ Apache-2.0 section 4(d) therefore imposes no carry-forward obligation here. No
30
+ upstream attribution text has been invented or paraphrased.
31
+
32
+ "Qwen" and "Alibaba Cloud" are used nominatively, to identify the base model
33
+ this adapter was trained against. Apache-2.0 section 6 grants no trademark
34
+ rights and none are claimed or implied. Nothing here states or implies any
35
+ endorsement, sponsorship or affiliation.
36
+
37
+ --------------------------------------------------------------------------------
38
+ STATEMENT OF CHANGES (Apache License 2.0, section 4(b))
39
+ --------------------------------------------------------------------------------
40
+
41
+ No upstream source file is modified, and no upstream file is redistributed
42
+ except the unmodified licence text at licenses/Qwen-Apache-2.0.txt.
43
+
44
+ The modification this work carries is a trained LoRA adapter and a set of
45
+ trained linear answer heads, applied to the base model at inference time:
46
+
47
+ * LoRA adapter, rank 64, question-side placement, float32
48
+ adapter/adapter.safetensors
49
+ sha256 2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca
50
+ 870363376 bytes
51
+ * Trained linear answer heads
52
+ adapter/heads.npz
53
+ sha256 126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f
54
+ 2053938 bytes
55
+ * Readout calibration (one positive scalar per task)
56
+ serving/scope9-readout-temperature-v2.json
57
+ file sha256 baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118
58
+ payload sha256 682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278
59
+
60
+ Training took place between 16 and 20 September 2026. The adapter and the heads
61
+ are identified by the checksums above. Full change detail is in MODIFICATIONS.md.
62
+
63
+ --------------------------------------------------------------------------------
64
+ THIRD-PARTY CONTENT IN THIS REPOSITORY
65
+ --------------------------------------------------------------------------------
66
+
67
+ The only third-party content distributed here is the unmodified Apache License
68
+ 2.0 text at licenses/Qwen-Apache-2.0.txt, reproduced to satisfy section 4(a).
69
+
70
+ No training document, evaluation panel, dataset, corpus, rendered page, cached
71
+ state, score archive or acceptance fixture is distributed. Every file in this
72
+ repository was scanned for text originating in third-party source documents
73
+ before release; see MODIFICATIONS.md, "Third-party text scan".
74
+
75
+ Everything else in this repository -- the serving code under src/, the adapter
76
+ and head weights, the calibration artifact, the serving binding and the
77
+ documentation -- is original work of Doccy Pty Ltd, licensed under
78
+ Apache-2.0.
mlx/bf16/README.md ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Solomon BF16 conversion provenance
2
+
3
+ This directory retains metadata for the unquantized BF16 conversion. **Backbone
4
+ weights are downloaded separately from Qwen; adapter and head weights live at
5
+ `adapter/adapter.safetensors` and `adapter/heads.npz` in the repository root.**
6
+ This directory is no longer a self-contained loadable model.
7
+
8
+ Install the source package in [`../`](../) and follow its README. From that
9
+ package directory:
10
+
11
+ ```sh
12
+ uv sync --frozen --extra dev
13
+ SOLOMON_COMMIT=$(uv run python -c 'from huggingface_hub import HfApi; print(HfApi().model_info("DoccyHealth/Solomon").sha)')
14
+ uv run solomon-mlx-hub prepare --revision "$SOLOMON_COMMIT" --output models/quality
15
+ ```
16
+
17
+ The loader fetches `adapter/**` plus MLX licensing metadata at the pinned Solomon
18
+ commit. It obtains the original base from `Qwen/Qwen3.8-27B` at
19
+ `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, verifies every base file, and invokes
20
+ the included exact converter. Existing original weights can be supplied with
21
+ `--base`. Existing valid converted outputs are verified and reused.
22
+
23
+ The original converter source is `../src/solomon_mlx/prepare.py`. The runtime
24
+ source hash recorded in `conversion.json` for this historical conversion is
25
+ `648e440cface0838f2dcc8d89b3ab172d97f4ffc1f88fe0b7cbbe3ca73b8a575`. The
26
+ current runtime source hash is
27
+ `bbcae17fc1c35db80a79d5865133a42ef9a1b0cf342fff71949972e69cce43ec`;
28
+ the two differ only by the release documentation edits described in `../README.md`,
29
+ which changed no converter behaviour. The new Hub loader is packaged separately so the original
30
+ converter remains reproducible. Dependency versions are locked in `../uv.lock`.
31
+ Large matrices stay BF16; normalization parameters, `A_log`, and `dt_bias` stay
32
+ FP32, with normalization offsets applied after promotion. LoRA is never merged.
33
+
34
+ `conversion.json` and `binding.json` retain the historical output checksums and
35
+ paths, including removed backbone and duplicate adapter paths. The root adapter
36
+ and heads are byte-identical to those historical records. The old binding SHA-256
37
+ is `6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530`.
38
+ New local conversions receive a fresh binding with actual output checksums;
39
+ safetensors serialization may differ across CPU and Metal. The historical source
40
+ revision `2ec506902269e4636285c8811f6c0f52c9300c0c` is provenance, not a required
41
+ future download URL. After a history rewrite, pin the new repository commit.
42
+
43
+ **Experimental: complete CUDA parity remains pending.** Focused text/image
44
+ fixtures and a partial text panel match CUDA decisions. See the dated results in
45
+ `../docs/VALIDATION-20260921.md`. No new calibration or temperature fitting is
46
+ required or performed. Existing CUDA temperatures are applied identically to both
47
+ backends for comparison; CUDA qualification is not inherited.
48
+
49
+ The historical conversion produced 55,610,774,146 bytes. Focused inference used
50
+ about 59.9 GB peak Metal allocation on an M5 Max / 128 GB Mac; larger inputs need
51
+ more memory. This is full BF16, without quantization, and does not fit 24–32 GB Macs.
mlx/bf16/base-manifest.json ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
3
+ "files": [
4
+ {
5
+ "name": ".gitattributes",
6
+ "size": 1570,
7
+ "blob_id": "52373fe24473b1aa44333d318f578ae6bf04b49b",
8
+ "sha256": null
9
+ },
10
+ {
11
+ "name": "LICENSE",
12
+ "size": 11544,
13
+ "blob_id": "f938136e3adacfd92be087f6e113b5d6d97f678f",
14
+ "sha256": null
15
+ },
16
+ {
17
+ "name": "README.md",
18
+ "size": 65012,
19
+ "blob_id": "bc8aa0e396cd029c21cd773cca21830d0ded28ec",
20
+ "sha256": null
21
+ },
22
+ {
23
+ "name": "chat_template.jinja",
24
+ "size": 8952,
25
+ "blob_id": "c0c686f9c38d70d179fb7b5f5aa7530bc913dda3",
26
+ "sha256": null
27
+ },
28
+ {
29
+ "name": "config.json",
30
+ "size": 4312,
31
+ "blob_id": "706cebd746c4b6f2b1d1f892630867acfdfd3df8",
32
+ "sha256": null
33
+ },
34
+ {
35
+ "name": "crc32.txt",
36
+ "size": 238,
37
+ "blob_id": "6de5ee6a0c6596744baee911af0a5cdcb8d99a1e",
38
+ "sha256": null
39
+ },
40
+ {
41
+ "name": "generation_config.json",
42
+ "size": 202,
43
+ "blob_id": "023756cfadf88e5bf69eefeee3e172f38c448d64",
44
+ "sha256": null
45
+ },
46
+ {
47
+ "name": "merges.txt",
48
+ "size": 3353259,
49
+ "blob_id": "a494e019ca1502219fd0128658b979e5f05ae8e8",
50
+ "sha256": null
51
+ },
52
+ {
53
+ "name": "model-00001-of-00018.safetensors",
54
+ "size": 3966730552,
55
+ "blob_id": "79b73cdd69afeca931d181f5d1e21ad2d73bb7b7",
56
+ "sha256": "ba0ce20aae489ad196733da5064bcdf159a1fe84f53336648196e1ebb7751b1c"
57
+ },
58
+ {
59
+ "name": "model-00002-of-00018.safetensors",
60
+ "size": 3043080328,
61
+ "blob_id": "81cf43545884a227a01351942dfbc72577812316",
62
+ "sha256": "06a148c01bfbe3faa14a5f184a7ff29a706f7ae1c8b2705d2058e26d17a001fb"
63
+ },
64
+ {
65
+ "name": "model-00003-of-00018.safetensors",
66
+ "size": 2542796952,
67
+ "blob_id": "a2da5e6cd6307a6cae13d04820ab0d31b47b48bd",
68
+ "sha256": "2e1bf62cbcd406eaa64b60d10353e1f0ef4039d0976e56f05cabe953454f9968"
69
+ },
70
+ {
71
+ "name": "model-00004-of-00018.safetensors",
72
+ "size": 3988973152,
73
+ "blob_id": "175ddab3f2488ad181f73d789d2d778cf6d4fca6",
74
+ "sha256": "511e34063187882659753c4d93f3859f93c019fd438d8813071921c81d9a3f1a"
75
+ },
76
+ {
77
+ "name": "model-00005-of-00018.safetensors",
78
+ "size": 2099339864,
79
+ "blob_id": "64890208fdd563c3aac0c2f55f69948bcaa1a492",
80
+ "sha256": "635cb53446dc74f219740fc59e18b774f877b803b9722e289ca62575a6efa701"
81
+ },
82
+ {
83
+ "name": "model-00006-of-00018.safetensors",
84
+ "size": 3979553696,
85
+ "blob_id": "3ae29b86e4c773babfe17124dbd10d54d37819ec",
86
+ "sha256": "0bc5214fac607f0e6cc92eec3789d4b8559410ef9fce66621ba8158e8410dae0"
87
+ },
88
+ {
89
+ "name": "model-00007-of-00018.safetensors",
90
+ "size": 2108759344,
91
+ "blob_id": "b7e9c0f038884839f3dad38fef7e75ee3362c24f",
92
+ "sha256": "80b0c49033e9a0d5762562aa12f4acdb7f54da586f3d0110f28c48d91cf07892"
93
+ },
94
+ {
95
+ "name": "model-00008-of-00018.safetensors",
96
+ "size": 3979553696,
97
+ "blob_id": "0b58cbc52714e1c683711be87e7c2e33fb10e7a0",
98
+ "sha256": "7192c5b66185d3592927daabee1cc19e6f6e0ce75988ee20e824b624765fda79"
99
+ },
100
+ {
101
+ "name": "model-00009-of-00018.safetensors",
102
+ "size": 2108759344,
103
+ "blob_id": "4fc0c6b28004d809225d0e115f30e9e11c2345dc",
104
+ "sha256": "af3c48cc37af44f3db6ae0579baf019180d48d9c527caa0a1f03ff85813a56d8"
105
+ },
106
+ {
107
+ "name": "model-00010-of-00018.safetensors",
108
+ "size": 3979553696,
109
+ "blob_id": "9ec693a17c788084116c51b4f34632670df77612",
110
+ "sha256": "163490a76f3bea3a40855b7efc04ce6d27afaf1a34f0bbde495b9491f76457c9"
111
+ },
112
+ {
113
+ "name": "model-00011-of-00018.safetensors",
114
+ "size": 2108759344,
115
+ "blob_id": "3c63baaed16d833432a919647af53ac76bb6b007",
116
+ "sha256": "5f3ae1b948aeee39da77aec558e8236cd65fe4d7cb7686a76bb007acc563c6d8"
117
+ },
118
+ {
119
+ "name": "model-00012-of-00018.safetensors",
120
+ "size": 3979553696,
121
+ "blob_id": "8568b440a4666ed1b4cda0b5d21496378304f326",
122
+ "sha256": "a3de1c7114677a8f5ac5c4892c90e8238ea5c1e2038c80e757dfc87c3902ca55"
123
+ },
124
+ {
125
+ "name": "model-00013-of-00018.safetensors",
126
+ "size": 2108759344,
127
+ "blob_id": "724083995a2257194321a3d00b36fd7d4bb7348d",
128
+ "sha256": "06ab79a41f74c9c5cb734816feb0c7fc364104b227165ee7391231e1155aa02a"
129
+ },
130
+ {
131
+ "name": "model-00014-of-00018.safetensors",
132
+ "size": 3979553696,
133
+ "blob_id": "a60b80b6510d7938d951fbddc76aaadd121c6268",
134
+ "sha256": "4138ed94603065ba884bbcadedb04d7718bb40117e85e6f5c6fc5b9c05b7a85b"
135
+ },
136
+ {
137
+ "name": "model-00015-of-00018.safetensors",
138
+ "size": 2108759344,
139
+ "blob_id": "7d4c34f3d716e1a47acff2f576b65b41a3ec4f3f",
140
+ "sha256": "69224e27b9de4e7dbf6fc936c6eaae08447bda3b80a6c31a871ab451173afd22"
141
+ },
142
+ {
143
+ "name": "model-00016-of-00018.safetensors",
144
+ "size": 3979564040,
145
+ "blob_id": "c67e3e42c9715298fa7f68c6b378bfb1d74290d0",
146
+ "sha256": "73cb9a1089fb6155cb648609478d6633be8a5c7d9ca5a05bc8925ce8a553cefe"
147
+ },
148
+ {
149
+ "name": "model-00017-of-00018.safetensors",
150
+ "size": 2108759344,
151
+ "blob_id": "23dd899179729a4370c655e2c2241cfdbe4ef4ba",
152
+ "sha256": "beb51f01056142ac4984bd800507b0dd0fd18de57f8e9ef6ea41d1a3598983a8"
153
+ },
154
+ {
155
+ "name": "model-00018-of-00018.safetensors",
156
+ "size": 3392197344,
157
+ "blob_id": "a92b4073fc6a58ffc9334ac63360fc8d9cc50d33",
158
+ "sha256": "1d3479509e21494658f9b64d317f5ea8e55c4025d28c702d6c4d0b356ce8ea06"
159
+ },
160
+ {
161
+ "name": "model.safetensors.index.json",
162
+ "size": 112216,
163
+ "blob_id": "da35e3c564457dface7d138f0b6cac284ff8958c",
164
+ "sha256": null
165
+ },
166
+ {
167
+ "name": "preprocessor_config.json",
168
+ "size": 390,
169
+ "blob_id": "2ea84a437d448ff71b08df68fdd949d5cc4ebb64",
170
+ "sha256": null
171
+ },
172
+ {
173
+ "name": "tokenizer.json",
174
+ "size": 12809320,
175
+ "blob_id": "9328ce9c41e80f6dd7bc2c66d8ae1fc93bf87440",
176
+ "sha256": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3"
177
+ },
178
+ {
179
+ "name": "tokenizer_config.json",
180
+ "size": 17928,
181
+ "blob_id": "5de744b3fca2129d7186979ae47c06be33903243",
182
+ "sha256": null
183
+ },
184
+ {
185
+ "name": "video_preprocessor_config.json",
186
+ "size": 385,
187
+ "blob_id": "3ba673a5ad7d4d13f54155ecd38b2a94a6dac8fe",
188
+ "sha256": null
189
+ },
190
+ {
191
+ "name": "vocab.json",
192
+ "size": 6722759,
193
+ "blob_id": "0aa0ce0658d60ac4a5d609f4eadb0e8e43514176",
194
+ "sha256": null
195
+ }
196
+ ]
197
+ }
mlx/bf16/binding.json ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_dtype": "float32",
3
+ "adapter_placement": "question",
4
+ "adapter_scale": 2.0,
5
+ "base_hashes": {
6
+ ".gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930",
7
+ "LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a",
8
+ "README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641",
9
+ "chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
10
+ "config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab",
11
+ "crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83",
12
+ "generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e",
13
+ "merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d",
14
+ "model-00001-of-00018.safetensors": "ba0ce20aae489ad196733da5064bcdf159a1fe84f53336648196e1ebb7751b1c",
15
+ "model-00002-of-00018.safetensors": "06a148c01bfbe3faa14a5f184a7ff29a706f7ae1c8b2705d2058e26d17a001fb",
16
+ "model-00003-of-00018.safetensors": "2e1bf62cbcd406eaa64b60d10353e1f0ef4039d0976e56f05cabe953454f9968",
17
+ "model-00004-of-00018.safetensors": "511e34063187882659753c4d93f3859f93c019fd438d8813071921c81d9a3f1a",
18
+ "model-00005-of-00018.safetensors": "635cb53446dc74f219740fc59e18b774f877b803b9722e289ca62575a6efa701",
19
+ "model-00006-of-00018.safetensors": "0bc5214fac607f0e6cc92eec3789d4b8559410ef9fce66621ba8158e8410dae0",
20
+ "model-00007-of-00018.safetensors": "80b0c49033e9a0d5762562aa12f4acdb7f54da586f3d0110f28c48d91cf07892",
21
+ "model-00008-of-00018.safetensors": "7192c5b66185d3592927daabee1cc19e6f6e0ce75988ee20e824b624765fda79",
22
+ "model-00009-of-00018.safetensors": "af3c48cc37af44f3db6ae0579baf019180d48d9c527caa0a1f03ff85813a56d8",
23
+ "model-00010-of-00018.safetensors": "163490a76f3bea3a40855b7efc04ce6d27afaf1a34f0bbde495b9491f76457c9",
24
+ "model-00011-of-00018.safetensors": "5f3ae1b948aeee39da77aec558e8236cd65fe4d7cb7686a76bb007acc563c6d8",
25
+ "model-00012-of-00018.safetensors": "a3de1c7114677a8f5ac5c4892c90e8238ea5c1e2038c80e757dfc87c3902ca55",
26
+ "model-00013-of-00018.safetensors": "06ab79a41f74c9c5cb734816feb0c7fc364104b227165ee7391231e1155aa02a",
27
+ "model-00014-of-00018.safetensors": "4138ed94603065ba884bbcadedb04d7718bb40117e85e6f5c6fc5b9c05b7a85b",
28
+ "model-00015-of-00018.safetensors": "69224e27b9de4e7dbf6fc936c6eaae08447bda3b80a6c31a871ab451173afd22",
29
+ "model-00016-of-00018.safetensors": "73cb9a1089fb6155cb648609478d6633be8a5c7d9ca5a05bc8925ce8a553cefe",
30
+ "model-00017-of-00018.safetensors": "beb51f01056142ac4984bd800507b0dd0fd18de57f8e9ef6ea41d1a3598983a8",
31
+ "model-00018-of-00018.safetensors": "1d3479509e21494658f9b64d317f5ea8e55c4025d28c702d6c4d0b356ce8ea06",
32
+ "model.safetensors.index.json": "77042094076611b69791a610065f28b7013b8c621795fa86ddccc8bac7d1b9df",
33
+ "preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516",
34
+ "tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3",
35
+ "tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27",
36
+ "video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13",
37
+ "vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003"
38
+ },
39
+ "base_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
40
+ "conversion": "upstream Qwen3.5 sanitization, norms promoted before unit offset",
41
+ "dependencies": {
42
+ "mlx": "0.32.2",
43
+ "mlx-vlm": "0.7.1",
44
+ "numpy": "2.5.3",
45
+ "transformers": "5.17.0"
46
+ },
47
+ "dtype": "bfloat16",
48
+ "files": {
49
+ "LICENSE": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457",
50
+ "MODIFICATIONS.md": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7",
51
+ "NOTICE": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1",
52
+ "adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca",
53
+ "backbone/.gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930",
54
+ "backbone/LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a",
55
+ "backbone/README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641",
56
+ "backbone/chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
57
+ "backbone/config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab",
58
+ "backbone/crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83",
59
+ "backbone/generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e",
60
+ "backbone/merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d",
61
+ "backbone/model-00001-of-00018.safetensors": "6c54d794f08d8a479e8a123b6ab3473b8a8f304f0da59b043aabe7bdb5f05a7a",
62
+ "backbone/model-00002-of-00018.safetensors": "3de19e7456388362e7dbe1455362f6b2661d8d7084a7a346e7d3f77053ff0f78",
63
+ "backbone/model-00003-of-00018.safetensors": "103e7f75798c312e696d2547889732b110a634725f6233b7b0bc654bf3365528",
64
+ "backbone/model-00004-of-00018.safetensors": "9b6ff88af2b387c943366b263d1ebef06090cf2a94e637895a74eb10300bf5da",
65
+ "backbone/model-00005-of-00018.safetensors": "f2e95aa5371257fadde49c6d735b716aab00868ae689befd54c091df7a6c57b4",
66
+ "backbone/model-00006-of-00018.safetensors": "7c196564fbb98f683573ae02d3a90bd74ccb7289d9103f09c76d9feffcc64b24",
67
+ "backbone/model-00007-of-00018.safetensors": "1a657de97643d5283547047925453e0bbec151b5f293f0a48ee8fbffb82c83cb",
68
+ "backbone/model-00008-of-00018.safetensors": "e26030a4ee68739f4092696d685b9ef5b4a7052f4843e949cf4419ee7d086b81",
69
+ "backbone/model-00009-of-00018.safetensors": "d73885d7e8995d58cd722b6fd4976035f30e3464189a71d88bb8e504a4612c53",
70
+ "backbone/model-00010-of-00018.safetensors": "ee367c91f6faf3a2d5245e28ee20dbe4e51a1ab08f8e27029c67e7c47a77bd89",
71
+ "backbone/model-00011-of-00018.safetensors": "01ea6406c8ee3f3bd0cbe96fb1ff120bdafc20bdeb4eb5dc8fc08894fef9221f",
72
+ "backbone/model-00012-of-00018.safetensors": "59da9b95e4d818c1189f681044df6bb9a5c7375ea0a01b8d6a6a682451fc4a8e",
73
+ "backbone/model-00013-of-00018.safetensors": "c2f63ec1a168d0f328da8ef74caee72d4e67d6dccfd9ccb595abcaedaeae86c4",
74
+ "backbone/model-00014-of-00018.safetensors": "d8a6453ec4fbe9763644dd9127c3a5ad5fec5c8a5cde6f1414f1101ee9a1250c",
75
+ "backbone/model-00015-of-00018.safetensors": "95db368ab83b8f60d1b9205de6ffa97adbe665d7e61e9adcfb237d25dabf9f8a",
76
+ "backbone/model-00016-of-00018.safetensors": "482d3d948c8cb31068323a85b7c2d445271a28a08699bbed48c13e465894715a",
77
+ "backbone/model-00017-of-00018.safetensors": "7615db84bee928608723f0e5e887f98e11091ccd86583453efcf9745050741f2",
78
+ "backbone/model-00018-of-00018.safetensors": "1219c9ea7951f2bb93c0ccafa2e6e65ac9111fda00961e3fbe614e30050c95e9",
79
+ "backbone/model.safetensors.index.json": "5aec933c5ef08d6c1d411b1b1f225b97d7c55f8eeed4b74535849ce9361e595f",
80
+ "backbone/preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516",
81
+ "backbone/tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3",
82
+ "backbone/tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27",
83
+ "backbone/video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13",
84
+ "backbone/vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003",
85
+ "heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f"
86
+ },
87
+ "head_dtype": "float32",
88
+ "profile": "quality",
89
+ "quantization": null,
90
+ "recurrent_state_dtype": "float32",
91
+ "schema": "solomon-mlx-binding-v1",
92
+ "sensitive_parameters": "FP32 normalization weights, A_log and dt_bias",
93
+ "solomon_revision": "2ec506902269e4636285c8811f6c0f52c9300c0c",
94
+ "source_hashes": {
95
+ ".gitattributes": "8baff4be52225e26dd13dffb05fed3d0db821911c36d7ffd6f6d00d02ca421c7",
96
+ "LICENSE": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457",
97
+ "MANIFEST.json": "a473433af0c2de6a97a0d99e9e47216e91ed67c4269e07ca8d678d8c068c317b",
98
+ "MODIFICATIONS.md": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7",
99
+ "NOTICE": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1",
100
+ "PUSH.md": "cc8e0ea0a0dec3db020f8245eb90b04571b77d8ac9f3efcd64d0ab0bef5bac16",
101
+ "README.md": "51a2389ef504652f0b0a292cfaf28bb5e7635bde1aede0289f42696ad5818afc",
102
+ "adapter/adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca",
103
+ "adapter/config.json": "cafc99ce8299eeca3fc2b2e1a2b08dcf659b66467d6675b6f8731a616693dbc4",
104
+ "adapter/heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f",
105
+ "licenses/Qwen-Apache-2.0.txt": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a",
106
+ "requirements.lock": "27675e999e5bcfc175ed768711856a975c2286d2ba807471cc7b0213d6d13f89",
107
+ "serving/scope9-readout-temperature-v2.json": "baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118",
108
+ "serving/selection.json": "14fa9c88a913d61a60d13b65f0974cff3985f40d50f204a1b83a9426db21b362",
109
+ "serving/service-export.json": "ccd7c0ca37cd900912631ab98b72703a4a4aebf2fadd6208675781aabd1e5ac8",
110
+ "serving/serving-binding.json": "1849df4bd4897d048a45a2f4cbc63beac4ded6afc0d43ff41e53a8a6dc8c49e4",
111
+ "src/scope35/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
112
+ "src/scope35/engine.py": "61f91be0dabc680795e5d4b83f9d80f02cbed2502b06e3809fdc09fbeeb99202",
113
+ "src/scope4/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
114
+ "src/scope4/cuda_engine.py": "f41dd19a248193bbda6aa81705317c702e0ec07976c7520a513b14260edddbf2",
115
+ "src/scope4/cuda_engine_v2.py": "d536117abd403811bba62129c2ba30d2e890c9058741baff65f3e7414f829bce",
116
+ "src/scope4/cuda_service.py": "c1c2267401b88dc5d4ad3d18b08c547c9b2e5d0a01c922d8ba88a1756075315a",
117
+ "src/scope4/engine.py": "b1ef3525011a8ec66c308f461e525d73a0a2b927bbfa605c76bef960d25fb5ff",
118
+ "src/scope4/readout.py": "cafcadbfc6ab3e77eac7bcd092d2254049c987913e2f744318ae7e2f36c24587",
119
+ "src/scope4/service.py": "2dfbfbadf92bce3f001063f9d94e5bf19a8d0ff9d143dd68b20d5032dd2e5e73",
120
+ "src/scope5/__init__.py": "6c079cc4864d55e798e243a6cbdc6e2b3fd963ff31fd28cf9a8122819a8c65cf",
121
+ "src/scope5/cuda_engine.py": "2d7d64d7e01cccefdb7afd504b80c54443ca271d0bcd3cf352b6bfd99dc491e3",
122
+ "src/scope5/heads_runtime.py": "48c0c29c79a8b781cddf22034b9ffee3df9a8d61b8709b1dd808b8df47024fe7",
123
+ "src/scope5/heads_service.py": "5c775e26bcb2de1c32156ce0cc404d06de8fbedff6c67fcb5606ce3713b88342",
124
+ "src/scope5/routing.py": "0d88a7f0433dd448b81bd04c48044c98e453038d9ffcd903c9479c20ec6651c1",
125
+ "src/scope5/service.py": "d056ae3e4ebda5d89ba4ebe0c93ae225a31e33a8f543632a0976703c25501019",
126
+ "src/scope6/__init__.py": "e72ad8e334509eb002496207c4e43598d222ba2953162344e43ee122e3832df0",
127
+ "src/scope6/evidence.py": "a0ece458b5c0546c1f80b48cea87ebc1c3732e83f2c0f8e58969645bc8bde2f7",
128
+ "src/scope6/evidence_v3.py": "9b2b9e9c319242ca9c4a96a2567d1c1adb3134332c95dd6dfd60114c54d09426",
129
+ "src/scope6/retrieval.py": "4e5094aacc2ee3bbb0663de402198cd418933c4682f9d5a9006d4058e15f9b4f",
130
+ "src/scope6/runtime.py": "2d154d8ba39e61a10b23bba286e1ce87f57dbdc21d8f6e73430c0929a1990fe7",
131
+ "src/scope6/service.py": "5e7b6f9e163be35ab17ed76355ecee9faa541f27ca76e969ff49f2076dfbd9b8",
132
+ "src/scope6/service_v3.py": "b92cfdee8ca1c039645a97746e47fc99965f9c5f5ecd98e31aedf71bca29b611",
133
+ "src/scope6/serving.py": "aab481eb4b1d34c13caaf8c3628f3702f2956bb413f288c7a30979d5c2281356",
134
+ "src/scope9/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
135
+ "src/scope9/api.py": "db677e569a3f7d15e6398d4bfb9ac0b00f286502fe4d97e9811388ccfb95dcae",
136
+ "src/scope9/live.py": "62f40568c8c0bc7ea4d436325eb54d49fe06ea8f51c304b384dcffb605bf3e3f",
137
+ "src/scope9/prompts.py": "53e22b99c19f5af588fea2167594c6331877a8ce71d0a0424bce10000bc22b53",
138
+ "src/scope9/semantics.py": "9fd9041c50f8925608bf6a2f351abab745648137400475bdf46f66885bef67dc",
139
+ "src/scope9/service.py": "9deed4e6acae4b26619a95f6f2ef19ced6bb42d6d6a4e1710c26accff4bc198b"
140
+ }
141
+ }
mlx/bf16/conversion.json ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "run_id": "bf16-cpu-79517a5cd7aa",
3
+ "base_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
4
+ "solomon_revision": "2ec506902269e4636285c8811f6c0f52c9300c0c",
5
+ "backend": "mlx-linux-cpu",
6
+ "binding_sha256": "6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530",
7
+ "converter_code_sha256": "648e440cface0838f2dcc8d89b3ab172d97f4ffc1f88fe0b7cbbe3ca73b8a575",
8
+ "total_output_bytes": 55610774146,
9
+ "seconds": 220.91851106299998,
10
+ "apple_silicon_validation": "pending; conversion is not inference qualification",
11
+ "files": [
12
+ {
13
+ "name": "LICENSE",
14
+ "size": 11344,
15
+ "sha256": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457"
16
+ },
17
+ {
18
+ "name": "MODIFICATIONS.md",
19
+ "size": 4197,
20
+ "sha256": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7"
21
+ },
22
+ {
23
+ "name": "NOTICE",
24
+ "size": 3891,
25
+ "sha256": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1"
26
+ },
27
+ {
28
+ "name": "adapter.safetensors",
29
+ "size": 870363376,
30
+ "sha256": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca"
31
+ },
32
+ {
33
+ "name": "backbone/.gitattributes",
34
+ "size": 1570,
35
+ "sha256": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930"
36
+ },
37
+ {
38
+ "name": "backbone/LICENSE",
39
+ "size": 11544,
40
+ "sha256": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a"
41
+ },
42
+ {
43
+ "name": "backbone/README.md",
44
+ "size": 65012,
45
+ "sha256": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641"
46
+ },
47
+ {
48
+ "name": "backbone/chat_template.jinja",
49
+ "size": 8952,
50
+ "sha256": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041"
51
+ },
52
+ {
53
+ "name": "backbone/config.json",
54
+ "size": 4312,
55
+ "sha256": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab"
56
+ },
57
+ {
58
+ "name": "backbone/crc32.txt",
59
+ "size": 238,
60
+ "sha256": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83"
61
+ },
62
+ {
63
+ "name": "backbone/generation_config.json",
64
+ "size": 202,
65
+ "sha256": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e"
66
+ },
67
+ {
68
+ "name": "backbone/merges.txt",
69
+ "size": 3353259,
70
+ "sha256": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d"
71
+ },
72
+ {
73
+ "name": "backbone/model-00001-of-00018.safetensors",
74
+ "size": 3967078356,
75
+ "sha256": "6c54d794f08d8a479e8a123b6ab3473b8a8f304f0da59b043aabe7bdb5f05a7a"
76
+ },
77
+ {
78
+ "name": "backbone/model-00002-of-00018.safetensors",
79
+ "size": 3043154160,
80
+ "sha256": "3de19e7456388362e7dbe1455362f6b2661d8d7084a7a346e7d3f77053ff0f78"
81
+ },
82
+ {
83
+ "name": "backbone/model-00003-of-00018.safetensors",
84
+ "size": 2542796953,
85
+ "sha256": "103e7f75798c312e696d2547889732b110a634725f6233b7b0bc654bf3365528"
86
+ },
87
+ {
88
+ "name": "backbone/model-00004-of-00018.safetensors",
89
+ "size": 3989088609,
90
+ "sha256": "9b6ff88af2b387c943366b263d1ebef06090cf2a94e637895a74eb10300bf5da"
91
+ },
92
+ {
93
+ "name": "backbone/model-00005-of-00018.safetensors",
94
+ "size": 2099392965,
95
+ "sha256": "f2e95aa5371257fadde49c6d735b716aab00868ae689befd54c091df7a6c57b4"
96
+ },
97
+ {
98
+ "name": "backbone/model-00006-of-00018.safetensors",
99
+ "size": 3979669611,
100
+ "sha256": "7c196564fbb98f683573ae02d3a90bd74ccb7289d9103f09c76d9feffcc64b24"
101
+ },
102
+ {
103
+ "name": "backbone/model-00007-of-00018.safetensors",
104
+ "size": 2108811989,
105
+ "sha256": "1a657de97643d5283547047925453e0bbec151b5f293f0a48ee8fbffb82c83cb"
106
+ },
107
+ {
108
+ "name": "backbone/model-00008-of-00018.safetensors",
109
+ "size": 3979669575,
110
+ "sha256": "e26030a4ee68739f4092696d685b9ef5b4a7052f4843e949cf4419ee7d086b81"
111
+ },
112
+ {
113
+ "name": "backbone/model-00009-of-00018.safetensors",
114
+ "size": 2108812007,
115
+ "sha256": "d73885d7e8995d58cd722b6fd4976035f30e3464189a71d88bb8e504a4612c53"
116
+ },
117
+ {
118
+ "name": "backbone/model-00010-of-00018.safetensors",
119
+ "size": 3979669601,
120
+ "sha256": "ee367c91f6faf3a2d5245e28ee20dbe4e51a1ab08f8e27029c67e7c47a77bd89"
121
+ },
122
+ {
123
+ "name": "backbone/model-00011-of-00018.safetensors",
124
+ "size": 2108812003,
125
+ "sha256": "01ea6406c8ee3f3bd0cbe96fb1ff120bdafc20bdeb4eb5dc8fc08894fef9221f"
126
+ },
127
+ {
128
+ "name": "backbone/model-00012-of-00018.safetensors",
129
+ "size": 3979669597,
130
+ "sha256": "59da9b95e4d818c1189f681044df6bb9a5c7375ea0a01b8d6a6a682451fc4a8e"
131
+ },
132
+ {
133
+ "name": "backbone/model-00013-of-00018.safetensors",
134
+ "size": 2108811987,
135
+ "sha256": "c2f63ec1a168d0f328da8ef74caee72d4e67d6dccfd9ccb595abcaedaeae86c4"
136
+ },
137
+ {
138
+ "name": "backbone/model-00014-of-00018.safetensors",
139
+ "size": 3979669605,
140
+ "sha256": "d8a6453ec4fbe9763644dd9127c3a5ad5fec5c8a5cde6f1414f1101ee9a1250c"
141
+ },
142
+ {
143
+ "name": "backbone/model-00015-of-00018.safetensors",
144
+ "size": 2108812005,
145
+ "sha256": "95db368ab83b8f60d1b9205de6ffa97adbe665d7e61e9adcfb237d25dabf9f8a"
146
+ },
147
+ {
148
+ "name": "backbone/model-00016-of-00018.safetensors",
149
+ "size": 3979690166,
150
+ "sha256": "482d3d948c8cb31068323a85b7c2d445271a28a08699bbed48c13e465894715a"
151
+ },
152
+ {
153
+ "name": "backbone/model-00017-of-00018.safetensors",
154
+ "size": 2108812005,
155
+ "sha256": "7615db84bee928608723f0e5e887f98e11091ccd86583453efcf9745050741f2"
156
+ },
157
+ {
158
+ "name": "backbone/model-00018-of-00018.safetensors",
159
+ "size": 2542796942,
160
+ "sha256": "1219c9ea7951f2bb93c0ccafa2e6e65ac9111fda00961e3fbe614e30050c95e9"
161
+ },
162
+ {
163
+ "name": "backbone/model.safetensors.index.json",
164
+ "size": 111087,
165
+ "sha256": "5aec933c5ef08d6c1d411b1b1f225b97d7c55f8eeed4b74535849ce9361e595f"
166
+ },
167
+ {
168
+ "name": "backbone/preprocessor_config.json",
169
+ "size": 390,
170
+ "sha256": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516"
171
+ },
172
+ {
173
+ "name": "backbone/tokenizer.json",
174
+ "size": 12809320,
175
+ "sha256": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3"
176
+ },
177
+ {
178
+ "name": "backbone/tokenizer_config.json",
179
+ "size": 17928,
180
+ "sha256": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27"
181
+ },
182
+ {
183
+ "name": "backbone/video_preprocessor_config.json",
184
+ "size": 385,
185
+ "sha256": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13"
186
+ },
187
+ {
188
+ "name": "backbone/vocab.json",
189
+ "size": 6722759,
190
+ "sha256": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003"
191
+ },
192
+ {
193
+ "name": "heads.npz",
194
+ "size": 2053938,
195
+ "sha256": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f"
196
+ },
197
+ {
198
+ "name": "binding.json",
199
+ "size": 12306,
200
+ "sha256": "6d45715aa040fad98b061ff2cbafb35b047143ef2475646cee3df7a334f4d530"
201
+ }
202
+ ]
203
+ }
mlx/docs/UPSTREAM-MODIFICATIONS.md ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Statement of changes
2
+
3
+ Apache License 2.0, section 4(b): prominent notice that files carry modifications.
4
+
5
+ ## What is modified
6
+
7
+ **No upstream source file is modified.** No file from `Qwen/Qwen3.8-27B` is edited,
8
+ renamed, patched or redistributed in this repository, with one exception: the
9
+ upstream licence text is reproduced byte-for-byte at `licenses/Qwen-Apache-2.0.txt`
10
+ (sha256 `bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a`) because section 4(a) requires it. That file is
11
+ unmodified and carries the upstream copyright.
12
+
13
+ The modification this work carries is not an edit to a source file. It is a set
14
+ of **trained parameters applied to the base model at inference time**, plus an
15
+ original serving layer that reads the model's logits. Concretely:
16
+
17
+ | Change | Artifact | sha256 |
18
+ |---|---|---|
19
+ | LoRA adapter, rank 64, question-side placement, float32 | `adapter/adapter.safetensors` | `2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca` |
20
+ | Trained linear answer heads | `adapter/heads.npz` | `126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f` |
21
+ | Readout calibration, one positive scalar per task | `serving/scope9-readout-temperature-v2.json` | file `baa7263ca9e865dda230617563e6a615de5050adc620c28924dffef68d527118` / payload `682d611ec53c3322905c7e265bf9b9b35a1cd581f6d8b7ea924df864a3d74278` |
22
+ | Runtime identity binding | `serving/serving-binding.json` | payload `95683c1f87ec3f71b7657669dc311918ff53b7d41a981eaa8d827047e72cb505` |
23
+ | Serving layer (original work, not derived from upstream code) | `src/` | see `MANIFEST.json` |
24
+
25
+ Base model, unmodified and not redistributed: `Qwen/Qwen3.8-27B` at revision
26
+ `1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0`, Copyright 2026 Alibaba Cloud, Apache-2.0.
27
+
28
+ Training took place between 16 and 20 September 2026. The shipped artifacts are
29
+ identified by the checksums in the table above and in `MANIFEST.json`.
30
+
31
+ ## Which files carry a change notice
32
+
33
+ | File | Why |
34
+ |---|---|
35
+ | `NOTICE` | Section 4(b) and 4(c) statement, carried with every copy |
36
+ | `MODIFICATIONS.md` | this file |
37
+ | `README.md` | names the base model, the pinned revision and the derivative relationship in the front matter and in the body |
38
+ | `adapter/config.json` | machine-readable record of the base repo, the pinned revision and both weight checksums |
39
+ | `serving/serving-binding.json` | pins the exact runtime the weights were measured on and refuses to load against a different one |
40
+
41
+ No file under `src/` carries an upstream change notice, because no file under
42
+ `src/` contains upstream code. Every file there is original work, written for
43
+ this project, and is covered by the repository's own Apache-2.0 `LICENSE` and by
44
+ the copyright line in `NOTICE`.
45
+
46
+ All of the changes described above — the adapter, the heads, the calibration, the
47
+ serving binding and the serving layer — are Copyright 2026
48
+ Doccy Pty Ltd and licensed under Apache-2.0.
49
+
50
+ ## Third-party text scan
51
+
52
+ Before release, **every file staged into this repository was scanned for text
53
+ originating in third-party source documents.** The scan compared normalised
54
+ 6-gram and 8-gram shingles of every staged text file against:
55
+
56
+ 1. the 42 third-party source records the training and evaluation panels were
57
+ built from (Apache-2.0, MIT, BSD-3-Clause, CC BY 4.0, CC BY-SA 4.0, CC0,
58
+ OGL v3.0 and US-government public-domain assertions); and
59
+ 2. every generated panel and document corpus on disk.
60
+
61
+ **Result: no third-party document text is present in any shipped file.** The
62
+ only matches were:
63
+
64
+ * the reproduced Apache-2.0 licence text at `licenses/Qwen-Apache-2.0.txt`, which
65
+ matches an Apache-2.0 licence document held in the evaluation corpus and is
66
+ required to be here verbatim; and
67
+ * the phrase *"A missing fact is not a negative fact"*, which is **our own
68
+ prompt-template wording** appearing in our own evaluation panels, not
69
+ third-party text entering our prompts.
70
+
71
+ Acceptance fixtures are excluded from this repository entirely. The fixture
72
+ documents used in live acceptance are original synthetic text authored for this
73
+ project and held in tooling that is not distributed.
74
+
75
+ Re-run the scan with `ops/solomon_package.py --scan` in the source project.
mlx/docs/VALIDATION-20260921.md ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Solomon BF16 CUDA parity validation — 21 September 2026
2
+
3
+ Full BF16 inference was exercised on the Apple M5 Max / 128 GB Mac. No weights or temperatures were changed. New temperature fitting is out of scope.
4
+
5
+ ## Confirmed results
6
+
7
+ | Check | Result |
8
+ |---|---|
9
+ | Fresh text fixtures, all five answer types | 22/22 decisions match CUDA |
10
+ | Fresh page-image fixtures | 4/4 decisions match CUDA |
11
+ | Saved text subset | 645/645 branch decisions and 327/327 whole-question decisions match CUDA |
12
+ | Prefix tokens and captured full token sequences | Exact match with CUDA |
13
+ | Replay and fresh process versus previous run | Zero logit drift on this Mac |
14
+ | Public API cache isolation, evidence and invalid-input checks | Passed |
15
+ | Existing package tests | 21 passed |
16
+ | Parity workflow and reserved-answer regression tests | 2 passed |
17
+ | Lint | Passed |
18
+
19
+ The saved subset covers 196 documents and all five tasks. It comes from previously scored calibration-fit inputs and is a diagnostic subset, not a complete held-out qualification. It was used only for comparison; nothing was fitted.
20
+
21
+ ## Numerical differences
22
+
23
+ With the same frozen CUDA serving temperatures applied once on both backends, maximum probability drift on the saved subset is 2.5871 percentage points; mean drift is 0.0126 points. At identical T=1, maximum drift is 4.1508 points and mean drift is 0.0078 points. All compared decisions still agree.
24
+
25
+ CUDA and MLX logits are not bitwise equal. The report preserves the largest probability differences for inspection. No claim is made that the CUDA confidence calibration has been independently validated for MLX.
26
+
27
+ The focused runs used 59.90 GB peak Metal allocation. This is not a measurement of the maximum supported context or many-page memory use.
28
+
29
+ ## Remaining work
30
+
31
+ The untouched certification panel is now scored directly: 1,200 documents / 21,718 branches. It skips fit/development scoring and temperature fitting. The complete agreement gate is at least 99.9% for both branches and whole questions, at T=1 and at the frozen CUDA temperatures. Probability drift and task accuracy are reported separately.
32
+
33
+ Image fixtures and API evidence checks are limited tests; broad image accuracy and CUDA evidence-selection equivalence are not established by them.
34
+
35
+ Runtime fingerprint: `33c9b63f036c5c039a2db4ba944f5b0042ae8b8e485e68c2aeddb57c407f8908`.
36
+
37
+ Detailed evaluation inputs and outputs remain private; the source release includes aggregate results only.
38
+
39
+ ## Adapter-only packaging checks
40
+
41
+ The 21 September source update passed 24 portable package tests, including seven
42
+ Hub-loader tests. The local development suite passed 30 tests. The new verifier
43
+ also checked all 37 files of the existing full BF16 conversion. The built wheel
44
+ contains the original converter with its exact recorded source hash and both
45
+ pinned-input manifests. These checks cover packaging and integrity, not additional
46
+ trained-model parity.
mlx/examples/decide.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from solomon_mlx import Solomon
2
+
3
+ model = Solomon.load("models/quality")
4
+ questions = {
5
+ "certified": {"type": "noul", "instructions": "Is Rookwood Ltd certified?"},
6
+ "auditor": {
7
+ "type": "choice",
8
+ "instructions": "Who performs the audit?",
9
+ "options": ["The Buyer", "The grower", "An independent auditor"],
10
+ },
11
+ "severity": {
12
+ "type": "score",
13
+ "instructions": "What is the breach severity?",
14
+ "levels": ["none recorded", "minor", "material"],
15
+ },
16
+ }
17
+ with model.prefill(
18
+ "Rookwood Ltd is certified. An independent auditor performs the audit. One minor breach is recorded."
19
+ ) as state:
20
+ print(model.decide(state=state, questions=questions, evidence="support"))
21
+ state.save("document-replay.json")
mlx/pyproject.toml ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["hatchling==1.32.3"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "solomon-mlx"
7
+ version = "0.1.0"
8
+ description = "Solomon v1.1 BF16 inference on Apple Silicon"
9
+ license = "Apache-2.0"
10
+ license-files = ["LICENSE", "NOTICE", "MODIFICATIONS.md", "docs/UPSTREAM-MODIFICATIONS.md"]
11
+ requires-python = ">=3.12,<3.14"
12
+ dependencies = ["mlx==0.32.2", "mlx-vlm==0.7.1", "transformers==5.17.0", "numpy==2.5.3", "pillow==12.3.0", "safetensors==0.8.0", "huggingface-hub==1.32.0", "scipy==1.18.1"]
13
+
14
+ [project.optional-dependencies]
15
+ dev = ["pytest==9.1.1", "ruff==0.16.8", "modal==1.5.5"]
16
+ cloud = [
17
+ "boto3==1.43.98",
18
+ "mlx[cpu]==0.32.2; sys_platform == 'linux'",
19
+ "modal==1.5.5",
20
+ ]
21
+
22
+ [project.scripts]
23
+ solomon-mlx = "solomon_mlx.cli:main"
24
+ solomon-mlx-hub = "solomon_mlx_hub.__main__:main"
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+ markers = ["model: requires the full pinned model"]
29
+
30
+ [tool.ruff]
31
+ line-length = 110
32
+ extend-exclude = ["src/solomon_mlx/_vendor", "snapshots", "models", "evaluations"]
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ packages = ["src/solomon_mlx", "src/solomon_mlx_hub"]
mlx/requirements.cloud.lock ADDED
@@ -0,0 +1,1072 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv export --frozen --no-dev --extra cloud --no-emit-project --format requirements-txt --output-file requirements.cloud.lock
3
+ aiohappyeyeballs==2.7.1 \
4
+ --hash=sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d \
5
+ --hash=sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472
6
+ # via aiohttp
7
+ aiohttp==3.14.3 \
8
+ --hash=sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043 \
9
+ --hash=sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b \
10
+ --hash=sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf \
11
+ --hash=sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559 \
12
+ --hash=sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86 \
13
+ --hash=sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a \
14
+ --hash=sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c \
15
+ --hash=sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5 \
16
+ --hash=sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d \
17
+ --hash=sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71 \
18
+ --hash=sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1 \
19
+ --hash=sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427 \
20
+ --hash=sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42 \
21
+ --hash=sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd \
22
+ --hash=sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8 \
23
+ --hash=sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228 \
24
+ --hash=sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0 \
25
+ --hash=sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee \
26
+ --hash=sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f \
27
+ --hash=sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c \
28
+ --hash=sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371 \
29
+ --hash=sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0 \
30
+ --hash=sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529 \
31
+ --hash=sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc \
32
+ --hash=sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b \
33
+ --hash=sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80 \
34
+ --hash=sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e \
35
+ --hash=sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807 \
36
+ --hash=sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d \
37
+ --hash=sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82 \
38
+ --hash=sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19 \
39
+ --hash=sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0 \
40
+ --hash=sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5 \
41
+ --hash=sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4 \
42
+ --hash=sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2 \
43
+ --hash=sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9 \
44
+ --hash=sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883 \
45
+ --hash=sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde \
46
+ --hash=sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787 \
47
+ --hash=sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627 \
48
+ --hash=sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a \
49
+ --hash=sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24
50
+ # via modal
51
+ aiosignal==1.4.0 \
52
+ --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \
53
+ --hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7
54
+ # via aiohttp
55
+ annotated-doc==0.0.5 \
56
+ --hash=sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101 \
57
+ --hash=sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb
58
+ # via
59
+ # fastapi
60
+ # typer
61
+ annotated-types==0.8.0 \
62
+ --hash=sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7 \
63
+ --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0
64
+ # via pydantic
65
+ anyio==4.15.1 \
66
+ --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \
67
+ --hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94
68
+ # via
69
+ # httpx
70
+ # starlette
71
+ # watchfiles
72
+ attrs==26.1.0 \
73
+ --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 \
74
+ --hash=sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32
75
+ # via aiohttp
76
+ boto3==1.43.98 \
77
+ --hash=sha256:1ec732e023fb29c12dc8520f925b5bbbed29eeb36b5764b5a5c26052d7c721f7 \
78
+ --hash=sha256:7454f666a9e852a56db0a7fa23be33a899f64e27828930eac43c3edba7b34e17
79
+ # via solomon-mlx
80
+ botocore==1.43.98 \
81
+ --hash=sha256:6135dd639ea6d1b3b49381bc253c8a139d61f7d44cb5f7dae8e7cd1791758572 \
82
+ --hash=sha256:84b35b10402c2fc0c265f634fbf86336eecc6489ee23f55074b329924b2cfd6f
83
+ # via
84
+ # boto3
85
+ # s3transfer
86
+ cbor2==6.1.4 \
87
+ --hash=sha256:01ecc79a28f33d17331943ce508fc1e21f4b06553c73f874f4c77120d72b2ef9 \
88
+ --hash=sha256:1fc15061553e4494dc10883237501e3402c645fe509248dd698e1faf2460d68b \
89
+ --hash=sha256:2310f07db3f9ba26f2a623774ff9f3dc7185af54f732ea119785a6b1bf7e1e7e \
90
+ --hash=sha256:310f3dfb296ba48fe9b63c5cf26e691e3548a1eae6901d2f0c18e941d151f220 \
91
+ --hash=sha256:32a4663425fbca4a4a7aa918eb5789d844c406439e58424cf34511f79f559242 \
92
+ --hash=sha256:36ae16d64b1f7b620c1af748e7b6947e20069ef80eee56871c5fbb84cc635905 \
93
+ --hash=sha256:4bd29f21529e279d50fc14f1a811f7b05b4d8e66a7969163cce98983b6817245 \
94
+ --hash=sha256:553a46bda7d09552631a714e22b91e6ff2c867ecd91511596ce290d8879b8d5b \
95
+ --hash=sha256:598710183daae69cbdeb177a870ec64aa601de8138a61491fd256826d15a860f \
96
+ --hash=sha256:5e6c76004d674ad1c620660cb0bc5a8a0b72a5d8c7b70926d8e09e6d7e87332f \
97
+ --hash=sha256:69978901302ecbc8cda57b520487c5c5240ed217de783eb7728fceb258311d76 \
98
+ --hash=sha256:ad4efa23fee6447e56a269191044e06eb39e809458bcd674e164fe9445feafd0 \
99
+ --hash=sha256:c08b9c7d2ea013e24a0cb819b872b0119dde404f64a1182c0b24095b7bba781f \
100
+ --hash=sha256:c48a7c938fc5fa5300ff82b5df09068dcb4838685ae8556b5ee8279d74f97ab4 \
101
+ --hash=sha256:cc8cd300e236e9797b2e1ce306109dc481fcccf78bfa2682bf36d99e6eab1ec6 \
102
+ --hash=sha256:d2560c2ba6a95904ba2a0ca257af878c4344409d9b46d8e646d8ebb617b1e0dd \
103
+ --hash=sha256:d9ada5a6ccfbb8ea7a3aa2aeb028421b52d8e0cd9323f0a2aeaa9c09d25fbce2
104
+ # via modal
105
+ certifi==2026.7.22 \
106
+ --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \
107
+ --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55
108
+ # via
109
+ # httpcore
110
+ # httpx
111
+ # modal
112
+ # requests
113
+ cffi==2.1.1 \
114
+ --hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \
115
+ --hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \
116
+ --hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \
117
+ --hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \
118
+ --hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \
119
+ --hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \
120
+ --hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \
121
+ --hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \
122
+ --hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \
123
+ --hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \
124
+ --hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \
125
+ --hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \
126
+ --hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \
127
+ --hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \
128
+ --hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \
129
+ --hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \
130
+ --hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \
131
+ --hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \
132
+ --hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \
133
+ --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \
134
+ --hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \
135
+ --hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \
136
+ --hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \
137
+ --hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \
138
+ --hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \
139
+ --hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \
140
+ --hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264
141
+ # via
142
+ # miniaudio
143
+ # sounddevice
144
+ charset-normalizer==3.5.1 \
145
+ --hash=sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac \
146
+ --hash=sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369 \
147
+ --hash=sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031 \
148
+ --hash=sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235 \
149
+ --hash=sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072 \
150
+ --hash=sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c \
151
+ --hash=sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950 \
152
+ --hash=sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2 \
153
+ --hash=sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e \
154
+ --hash=sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2 \
155
+ --hash=sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa \
156
+ --hash=sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2 \
157
+ --hash=sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71 \
158
+ --hash=sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96 \
159
+ --hash=sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8 \
160
+ --hash=sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9 \
161
+ --hash=sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f \
162
+ --hash=sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591 \
163
+ --hash=sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e \
164
+ --hash=sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3 \
165
+ --hash=sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3 \
166
+ --hash=sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6 \
167
+ --hash=sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959 \
168
+ --hash=sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf \
169
+ --hash=sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0 \
170
+ --hash=sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e \
171
+ --hash=sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491 \
172
+ --hash=sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a \
173
+ --hash=sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c \
174
+ --hash=sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7 \
175
+ --hash=sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a \
176
+ --hash=sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9 \
177
+ --hash=sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e \
178
+ --hash=sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4 \
179
+ --hash=sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3 \
180
+ --hash=sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99 \
181
+ --hash=sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6 \
182
+ --hash=sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598 \
183
+ --hash=sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6 \
184
+ --hash=sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962 \
185
+ --hash=sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08 \
186
+ --hash=sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573 \
187
+ --hash=sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6 \
188
+ --hash=sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2 \
189
+ --hash=sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2 \
190
+ --hash=sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe \
191
+ --hash=sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3 \
192
+ --hash=sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc \
193
+ --hash=sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d \
194
+ --hash=sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893 \
195
+ --hash=sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d \
196
+ --hash=sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada \
197
+ --hash=sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876 \
198
+ --hash=sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f \
199
+ --hash=sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0
200
+ # via requests
201
+ click==8.5.0 \
202
+ --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360 \
203
+ --hash=sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34
204
+ # via
205
+ # huggingface-hub
206
+ # modal
207
+ # uvicorn
208
+ colorama==0.4.6 ; sys_platform == 'win32' \
209
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
210
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
211
+ # via
212
+ # tqdm
213
+ # typer
214
+ fastapi==0.141.1 \
215
+ --hash=sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3 \
216
+ --hash=sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1
217
+ # via mlx-vlm
218
+ filelock==4.0.1 \
219
+ --hash=sha256:481a321a27bef441e23c53371c6abc8d7d16e26b97090074ba44f7538a3fd55a \
220
+ --hash=sha256:fdefc3f3e87716d855ae2b732c1cfd521dd99799ef2b4d00e8c0d4dcdc7cc94b
221
+ # via huggingface-hub
222
+ frozenlist==1.8.0 \
223
+ --hash=sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0 \
224
+ --hash=sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121 \
225
+ --hash=sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c \
226
+ --hash=sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d \
227
+ --hash=sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b \
228
+ --hash=sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed \
229
+ --hash=sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b \
230
+ --hash=sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143 \
231
+ --hash=sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e \
232
+ --hash=sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746 \
233
+ --hash=sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8 \
234
+ --hash=sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad \
235
+ --hash=sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82 \
236
+ --hash=sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29 \
237
+ --hash=sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf \
238
+ --hash=sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62 \
239
+ --hash=sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383 \
240
+ --hash=sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52 \
241
+ --hash=sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d \
242
+ --hash=sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1 \
243
+ --hash=sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714 \
244
+ --hash=sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65 \
245
+ --hash=sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506 \
246
+ --hash=sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888 \
247
+ --hash=sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41 \
248
+ --hash=sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608 \
249
+ --hash=sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa \
250
+ --hash=sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1 \
251
+ --hash=sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231 \
252
+ --hash=sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496 \
253
+ --hash=sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a \
254
+ --hash=sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3 \
255
+ --hash=sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7 \
256
+ --hash=sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4 \
257
+ --hash=sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51 \
258
+ --hash=sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8 \
259
+ --hash=sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042 \
260
+ --hash=sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b \
261
+ --hash=sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e \
262
+ --hash=sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52 \
263
+ --hash=sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1 \
264
+ --hash=sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94 \
265
+ --hash=sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822 \
266
+ --hash=sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11 \
267
+ --hash=sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51 \
268
+ --hash=sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40 \
269
+ --hash=sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5 \
270
+ --hash=sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4 \
271
+ --hash=sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027 \
272
+ --hash=sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd
273
+ # via
274
+ # aiohttp
275
+ # aiosignal
276
+ fsspec==2026.9.0 \
277
+ --hash=sha256:0f08147951c8cb31d844c3547d631053b127863b60be04cf06e121333ee0e2fe \
278
+ --hash=sha256:8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f
279
+ # via huggingface-hub
280
+ grpclib==0.4.9 \
281
+ --hash=sha256:7762ec1c8ed94dfad597475152dd35cbd11aecaaca2f243e29702435ca24cf0e \
282
+ --hash=sha256:cc589c330fa81004c6400a52a566407574498cb5b055fa927013361e21466c46
283
+ # via modal
284
+ h11==0.16.0 \
285
+ --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
286
+ --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
287
+ # via
288
+ # httpcore
289
+ # uvicorn
290
+ h2==4.4.1 \
291
+ --hash=sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6 \
292
+ --hash=sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516
293
+ # via grpclib
294
+ hf-xet==1.6.0 ; platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' \
295
+ --hash=sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7 \
296
+ --hash=sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef \
297
+ --hash=sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3 \
298
+ --hash=sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb \
299
+ --hash=sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d \
300
+ --hash=sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c \
301
+ --hash=sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f \
302
+ --hash=sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a \
303
+ --hash=sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b
304
+ # via huggingface-hub
305
+ hpack==4.2.0 \
306
+ --hash=sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0 \
307
+ --hash=sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986
308
+ # via h2
309
+ httpcore==1.0.9 \
310
+ --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
311
+ --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
312
+ # via httpx
313
+ httpx==0.28.1 \
314
+ --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
315
+ --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
316
+ # via huggingface-hub
317
+ huggingface-hub==1.32.0 \
318
+ --hash=sha256:b0c7c80561969d9cdacdd55fce67ba9584cca0b9d4ea80957a3a5c1445fac5c8 \
319
+ --hash=sha256:ed70a45498abe86039df7c2f4e5f7575de524be908d3840e8f828d5525eafd6a
320
+ # via
321
+ # mlx-audio
322
+ # solomon-mlx
323
+ # tokenizers
324
+ # transformers
325
+ hyperframe==6.1.0 \
326
+ --hash=sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5 \
327
+ --hash=sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08
328
+ # via h2
329
+ idna==3.20 \
330
+ --hash=sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44 \
331
+ --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
332
+ # via
333
+ # anyio
334
+ # httpx
335
+ # requests
336
+ # yarl
337
+ jinja2==3.1.6 \
338
+ --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \
339
+ --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67
340
+ # via mlx-vlm
341
+ jmespath==1.1.0 \
342
+ --hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \
343
+ --hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64
344
+ # via
345
+ # boto3
346
+ # botocore
347
+ llguidance==1.8.0 \
348
+ --hash=sha256:020b4ec2254a20555e69095c7d488907f17d7e65ab5a840b267530f2cb369f70 \
349
+ --hash=sha256:0eb7be70bf822e54cd4021bb200cfe2b86e4f3379d251067dc9f7da327f3ceab \
350
+ --hash=sha256:18d1579eabb040e65c870d50c6df19a7bef140c5260d12ad35b7f0dc446312e0 \
351
+ --hash=sha256:39668c11396896e5f05f59b70c81e4afd060b3408f02c8518b7a6943bfbb8a5d \
352
+ --hash=sha256:6ae4343bd40b88d1dd824a17edcee11b5e5a000b16b6fedb9fcf7f58d019177c \
353
+ --hash=sha256:6bf3953d06e7f5e24bd02fa6a89a5b2b88f7e811c0fa0228487d8267ef7cec54 \
354
+ --hash=sha256:79b0576991b8fc7534456b65c41d43c3183c8ca974a17799359af969c1489c07 \
355
+ --hash=sha256:a8837ac2b3bf4c46e1b6363012a22de043b7f8ef013b04d8689d471eb573b766 \
356
+ --hash=sha256:b5e866d8a896e255f30ec952f5280c61a3d6f391a9dce575ce976dd58f0b7000 \
357
+ --hash=sha256:bb9a89e8cdd7c8b5cf4f84e45b04177e79acdcc4d5116fbc775e511f7314df44
358
+ # via mlx-vlm
359
+ markdown-it-py==4.2.0 \
360
+ --hash=sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49 \
361
+ --hash=sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a
362
+ # via rich
363
+ markupsafe==3.0.3 \
364
+ --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \
365
+ --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \
366
+ --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \
367
+ --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \
368
+ --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \
369
+ --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \
370
+ --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \
371
+ --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \
372
+ --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \
373
+ --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \
374
+ --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \
375
+ --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \
376
+ --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \
377
+ --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \
378
+ --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \
379
+ --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \
380
+ --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \
381
+ --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \
382
+ --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \
383
+ --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \
384
+ --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \
385
+ --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \
386
+ --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \
387
+ --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \
388
+ --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \
389
+ --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \
390
+ --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \
391
+ --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \
392
+ --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \
393
+ --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \
394
+ --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \
395
+ --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \
396
+ --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \
397
+ --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795
398
+ # via jinja2
399
+ mdurl==0.1.2 \
400
+ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
401
+ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
402
+ # via markdown-it-py
403
+ miniaudio==1.71 \
404
+ --hash=sha256:12bc33e7e61072b4b541c14e10ef76119d5643e6bbb98e2dec0c0738889438fb \
405
+ --hash=sha256:19be6f0a1e601c2237433e579734cfaf6469191b224c20c9e5f73c32ef9ee2b9 \
406
+ --hash=sha256:1bf93aeede652926f27f430f0fd69ef0cf8a949c07b537d6a2f295602c747037 \
407
+ --hash=sha256:4c849ccb1349f7b3553a77a66fe7e972315185f5c4c44a0bbda7ebcdd224db37 \
408
+ --hash=sha256:61b86f26d653040db32d9d15b05446321dd10e45beba25b44f841e26935213d5 \
409
+ --hash=sha256:62db602651bc20a2698f36a0d356d7217ed6f4f917550c7ffb3705c8e8be90cf \
410
+ --hash=sha256:70fa2ea5353e6919aca59b8c5768144af009d18c3bca251749d66fb497424563 \
411
+ --hash=sha256:8fc1a4f084cc1b4b25c567d22f54d1e46bfa505c17ed777c8b198e5c53d0f785 \
412
+ --hash=sha256:ab100e5240b104b5326e4ec1be07b6ae461f7d3d4d7a694857fd2f0493d210f9 \
413
+ --hash=sha256:d9dc15eff711bcfc62a9d05e0c78e4bc34821a455595e049629f2fea7491a523 \
414
+ --hash=sha256:e6287f15caa808a88aad0700a182bec1ff6d98769717425adf9ebf41259d1936 \
415
+ --hash=sha256:f4a44b70b66628b0c307e40ae0ae857695978cae18462179b806d8edc807d416 \
416
+ --hash=sha256:ff51e2887bb673e2e757752b586b3dc924d59aa5fbcae9bbc45f4a111bd3262b
417
+ # via
418
+ # mlx-audio
419
+ # mlx-vlm
420
+ mlx==0.32.2 \
421
+ --hash=sha256:45857fadb381fea3db57d9681b04c987813fa883f3182ec9fb05bc56f468978a \
422
+ --hash=sha256:48e8738b078eeb7bfde74931fc02d8aa9c32f05d3c0dbf97b5c3cbe0470fbddf \
423
+ --hash=sha256:50ced716f4ab860cbda9d65adf74877923fe26036c4e28d8f572ecec57621cfc \
424
+ --hash=sha256:583111ec13fedf63ddbfefda77dabd57168978f06474f5e5d2637180d154fcc4 \
425
+ --hash=sha256:65beb9ce75153072808ef18913ecca3d929b87be5d7c1f57d9df391c04b6f957 \
426
+ --hash=sha256:65d3d29b66045ed8dd2d8e437c8770de325843c364f7b7c38cd8ae90a7eec854 \
427
+ --hash=sha256:68560fd648c5bb900aa6f6765cd74c5a8abaf092d97d73584a57b7545966c227 \
428
+ --hash=sha256:6c615ad1c6877d7d38affe8526446955cd48d6796e871150f59145cf0d1a265d \
429
+ --hash=sha256:77217798a2b036bae9f213b851d4cde4581893787c9964458b7d471f86036bd6 \
430
+ --hash=sha256:8d270ade1e48e006383a6b5f33a3a4ec5c265389299a60a60bf55165af1770d9 \
431
+ --hash=sha256:9d21abe340403b6bf445e8494590a7341b655739088c92f178d7bd4241ba0110 \
432
+ --hash=sha256:bc69bd1062b97028ae6b79522ed0bd635a2b133fe3c398c03b66b060c20d9240 \
433
+ --hash=sha256:c7670ffb854c11e6776349a3797fa076d1a2a00290ebe2e9cfed8b60ca4a5db0 \
434
+ --hash=sha256:c95a384de1a0c0ba18425344cad8ac87180e3c5c1921a42e2621475be5966bcf \
435
+ --hash=sha256:cf63fd5c32258ab07523b06401c20ee2280bf56e26e991d05bf6fd8a4d42d1f6 \
436
+ --hash=sha256:daebf84dfb857d70e87b1b98189a057e691e99a4a2b9f6f64cadebad917f8964 \
437
+ --hash=sha256:dc5eb3cc30d4285f2734c368442f717599e97294663441cb42959e3b856d6898 \
438
+ --hash=sha256:df8c75e509de868fca148dfeb38d92ce956eed386569c87caeb72bd16d2d6962 \
439
+ --hash=sha256:f6071e4973927966c12b3894deb75f4fcfc33300e0705312a80fdbbaa92a9c4f \
440
+ --hash=sha256:f77e47e6c1e176a61ec1bb1f9e74d874482eea6eb86ffb162a1f3e789c806453 \
441
+ --hash=sha256:fe813a4dee2daa6d5ac496ef25117034728c77fe26f713983a3b406eb6f4e8f0
442
+ # via
443
+ # mlx-audio
444
+ # mlx-vlm
445
+ # solomon-mlx
446
+ mlx-audio==0.5.4 \
447
+ --hash=sha256:3e1895860d9a636360a9377b5651239f5197e649d56fe25f1382cac6f2ed1e55 \
448
+ --hash=sha256:d350ecc43a65b94b1578381370be64229948f79aef9ff599d20adb65e38351b3
449
+ # via mlx-vlm
450
+ mlx-cpu==0.32.2 ; sys_platform == 'linux' \
451
+ --hash=sha256:d0f94625588b51a878786dd51cec5617894ce586cf3c94645ce1b64e24c27c0b \
452
+ --hash=sha256:fc5d31b90fc4f457b2f9614ed45651a567d22a6a11a03d114a90a4e10bdd8878
453
+ # via mlx
454
+ mlx-metal==0.32.2 ; sys_platform == 'darwin' \
455
+ --hash=sha256:3825fff379dbc107dd3413e564a06caeaa24819910ec49c0439e454c06a1b9b8 \
456
+ --hash=sha256:55a369250d220b2cf10213a87a2ac1b1a420608c5b35b1df4e7147ac8e32f121 \
457
+ --hash=sha256:e6abeac9ac5265830c9c1541b6f96e9be37a85c2446763a46ad466c63a3837ab
458
+ # via mlx
459
+ mlx-vlm==0.7.1 \
460
+ --hash=sha256:b8abd3cc7e3513d9915bf9f6903833c697dc4c3084c2acdc880029c33e331a03 \
461
+ --hash=sha256:d9696bc3a2e961f43b5948a101dc0c966b5989d382e6910c37e2f3d555bc3408
462
+ # via solomon-mlx
463
+ modal==1.5.5 \
464
+ --hash=sha256:30df363ed1898cc3d91a09ff3f95c38ab043f6b6294011b01085312c6a0ac777 \
465
+ --hash=sha256:8d10d3ee09818aaba1973b73ce2521ab8961b63a29b5b52e3ff0d25e7a74808e
466
+ # via solomon-mlx
467
+ multidict==6.9.0 \
468
+ --hash=sha256:0db5bf96ec2ce45a8bc7fbbe8a486089969bb2791a66b6789ee3aed0d5dd562e \
469
+ --hash=sha256:1126782e3c3b1a7ccd990be3d3221709348e4b07d6ecf8b50965a93ae2624145 \
470
+ --hash=sha256:11e32ccf23cdbfcf8299a6a825930a858ec9a6aa05d6752d0f90f2bdf19489e1 \
471
+ --hash=sha256:1eb7939025bd9289d9dfe3a399102b1642f4fbb105a6af82f284052ee89e97a9 \
472
+ --hash=sha256:22067e88ff266e6a5dc59114529332692a01cc04b10dbdc2c14ab91217dee819 \
473
+ --hash=sha256:254e53be2ec70518bb82dfa9b0c7166baaf2bd0e918bd17b65caaf489d5a5522 \
474
+ --hash=sha256:2ea72901860ccbe94517421681c60b13533ce03ba2f7bd96293c3a4d16ac4ccb \
475
+ --hash=sha256:3e78870909e9f9e3d672ba99f1eb75130d7e01c42e642e6a70f434df32ca0ee1 \
476
+ --hash=sha256:3ec1e387b1f8a85ae5b94aa8c4e0576912ffa4d31bd0578f24c950d4f05ee476 \
477
+ --hash=sha256:408fac672931f2458be3bc8c89d9facd16dac2aad17c7cee2ca1693eee99f07e \
478
+ --hash=sha256:4bb769ccc72e15d7d441e1a08f169d418376be77cdc387e813129c26b357fe50 \
479
+ --hash=sha256:4be612f23990a261060ccd8f15e89dad1f9b7bb0c1021c5c3987f5fc57959391 \
480
+ --hash=sha256:4bef8cb5edea9c9daeb8396a75eeb97f912c3fb3fa408fe659edfecf91e47424 \
481
+ --hash=sha256:516fa4817cd070088f616a901380db56189f86daee9da79aada3c9b653f49ed7 \
482
+ --hash=sha256:51a08dceed4b42ef25755ee2cbf50df325e90e6b415d00955ffdea2c394ad6c0 \
483
+ --hash=sha256:57c2445049f7d8e66306f712868219da7ff7168ef42263dc032401211bf1205c \
484
+ --hash=sha256:640113258c5925a9eed2c12523410b25565ac5df2fa6735fbae88fb09bcdd212 \
485
+ --hash=sha256:662315f8621b3559268813b11134c1f9633edd4ab5f7b713688a243c13f4026d \
486
+ --hash=sha256:67bcff396d2ad62197c95488a716a88462792b384b5ea6e44bf7c1070eddb8fc \
487
+ --hash=sha256:67bed23e9803945b0760650ec3e903af772c49abe869f3bc03c66b2e7649d5ef \
488
+ --hash=sha256:6a5111a2bd824c821a3dd09da29680391b0caaa18fea7761358f4001e6898d1c \
489
+ --hash=sha256:75f7fc21ffce9a792cb919f0e0b0b52117bd672f2a55d1929d573b6fc437f374 \
490
+ --hash=sha256:7aaa14f0b9ffa2780c5d3b21da0e9b58778ed47af3369df72e5fd6dc10ff8041 \
491
+ --hash=sha256:85ed0f3c3b01174aea5a8a8fcd456f64c2e9719c0f42c612c2694ecd35974a65 \
492
+ --hash=sha256:8bb6be697065cbf31051465f9939d65624573d2219c3da8316db9e7cf5e0f5a7 \
493
+ --hash=sha256:8e991677c4bdc5d9f2e71c74717a4e32cfe98930ca05becdf722b5eae1329d6a \
494
+ --hash=sha256:8f06c4da5315a6f709b13408c3e13f3b475f8c559ec7608c3c67062512871235 \
495
+ --hash=sha256:91092d597fcf0940cd6a59e64ba7156ee22d6899993fd7c5b1897fda71482f8f \
496
+ --hash=sha256:95d339c3b75b4a50c665bdcf8417428cd71c3e5cd48e194cb1d336fcb856beac \
497
+ --hash=sha256:97555ad30ad20a8eb90fa86522088eebbbd68aba03e56d53f4850b3535c65e61 \
498
+ --hash=sha256:9869105ab61db13004f9ed610cd29ce4237b2c609f5f7b8bb8f2339fb9dad17d \
499
+ --hash=sha256:b33e499a7b1f722547d57b4865ed68c03161643b9afe49fe097a0832078d183d \
500
+ --hash=sha256:bb69b724c345420ba49187a17a894f146099f5b2e501df42ddb4452ac8be37fa \
501
+ --hash=sha256:beda95a6bd0a2e2265b941b90ff72543afbeaebb6a450e3a6a010d10d4a2ffca \
502
+ --hash=sha256:c51fd8d59e72e45c64907bf7f81fbd94f4c8dcd1c8b2f23f4c2d7ede788fcfc3 \
503
+ --hash=sha256:c7ab60b91e11b25e7682c5cd8763fdd17929ea83f234ba441091f1492e631ea3 \
504
+ --hash=sha256:cb847cb4002e725f88ffe29883da8290fd0754fa0461ed8988735e20964a9f65 \
505
+ --hash=sha256:cff3cff5a725bdb8359962de8d7429aea592d7693dbd197eeabbdfab9b6300e9 \
506
+ --hash=sha256:d7d32c0543494efbc9394e2b571725071d08e295993486bc9a43f6f89375ee01 \
507
+ --hash=sha256:e231de8ce43d4fd10bec4f67f71238ed88a974de30a9f20be7a4ab16c970a988 \
508
+ --hash=sha256:e4826f6b56456fb1e98111d7bc20cbdc7fa0a41b1f9ad80ff2dd3f2f5b226fe1 \
509
+ --hash=sha256:e71a072c52c78b7f97cd4611df6cef10977e4f2367cd0654a7626192f931adde \
510
+ --hash=sha256:f93c9058a0eceac0df2ce9d4c8823b84786ee598194753c2ca0c100224405e47 \
511
+ --hash=sha256:fca5b74b5909c29041f857c40d51d9273636fb4221cf020e4c452deb1c448a40 \
512
+ --hash=sha256:fdd484b84d3394e805689c56be3ab1f877ae7ff0eb9ff90a3ee7a755cbebab4f
513
+ # via
514
+ # aiohttp
515
+ # grpclib
516
+ # yarl
517
+ numpy==2.5.3 \
518
+ --hash=sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def \
519
+ --hash=sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0 \
520
+ --hash=sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f \
521
+ --hash=sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff \
522
+ --hash=sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034 \
523
+ --hash=sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec \
524
+ --hash=sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58 \
525
+ --hash=sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204 \
526
+ --hash=sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e \
527
+ --hash=sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321 \
528
+ --hash=sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0 \
529
+ --hash=sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034 \
530
+ --hash=sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3 \
531
+ --hash=sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231 \
532
+ --hash=sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4 \
533
+ --hash=sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b \
534
+ --hash=sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9 \
535
+ --hash=sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034 \
536
+ --hash=sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958 \
537
+ --hash=sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563 \
538
+ --hash=sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c \
539
+ --hash=sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09 \
540
+ --hash=sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e
541
+ # via
542
+ # mlx-audio
543
+ # mlx-vlm
544
+ # opencv-python
545
+ # scipy
546
+ # solomon-mlx
547
+ # transformers
548
+ opencv-python==5.0.0.93 \
549
+ --hash=sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2 \
550
+ --hash=sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898 \
551
+ --hash=sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157 \
552
+ --hash=sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2 \
553
+ --hash=sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b \
554
+ --hash=sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039 \
555
+ --hash=sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac \
556
+ --hash=sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881 \
557
+ --hash=sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2
558
+ # via mlx-vlm
559
+ packaging==26.3 \
560
+ --hash=sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79 \
561
+ --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
562
+ # via
563
+ # huggingface-hub
564
+ # transformers
565
+ pillow==12.3.0 \
566
+ --hash=sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 \
567
+ --hash=sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df \
568
+ --hash=sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8 \
569
+ --hash=sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89 \
570
+ --hash=sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510 \
571
+ --hash=sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce \
572
+ --hash=sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace \
573
+ --hash=sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a \
574
+ --hash=sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e \
575
+ --hash=sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91 \
576
+ --hash=sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66 \
577
+ --hash=sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09 \
578
+ --hash=sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f \
579
+ --hash=sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec \
580
+ --hash=sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b \
581
+ --hash=sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965 \
582
+ --hash=sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35 \
583
+ --hash=sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9 \
584
+ --hash=sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f \
585
+ --hash=sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c \
586
+ --hash=sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65 \
587
+ --hash=sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7
588
+ # via
589
+ # mlx-vlm
590
+ # solomon-mlx
591
+ propcache==0.5.4 \
592
+ --hash=sha256:0c889f6fa84957bc7e8b4eab71fd16a0455068d5045e3aa40c733071d2b2fd77 \
593
+ --hash=sha256:2814ecd8e818f487bee4b0f921bc4d1c176cc5fc71ac0f072d0fa67eda4ac14b \
594
+ --hash=sha256:36c0d9db44b523ef93d03341b1c42d69ff01d673c053d1b1c6c3a363bcaa39ba \
595
+ --hash=sha256:3e413d7a4a9b4866b7a761d6060d434b64d23cd35122eda3b026a0bbe8196b25 \
596
+ --hash=sha256:425f8cc86ab5018b4b8d4a23bc8e74d964bd3d757c3702e301aa79be76c53f6c \
597
+ --hash=sha256:44149f46500a0a41b95b4d99c2e586a77319539730607b9892974a092788b111 \
598
+ --hash=sha256:4fbc1a15dc8cd1689508758d626b372b1f09d28d9577667feaf9e6bfcd8efcbc \
599
+ --hash=sha256:60a64cbccaa11b7760ce705a14ada17ba459e7ca9f23ba587eb013821032d7ef \
600
+ --hash=sha256:62c60aec739ed00124573cce1178138fd690c7676352d67a37328c1cf51d7468 \
601
+ --hash=sha256:69fc35c0779522da366c563e5faf203ffc1f8ff0021d5b1337fa4efa5be73177 \
602
+ --hash=sha256:6af4693716bfb03f1752ef1b30faa593db2c01d5272e9b8564a1549452a979ab \
603
+ --hash=sha256:7cc528e760a8af06f2b13e9b9f362cd90c7c718ea61228a96dbd31ba16ed7f47 \
604
+ --hash=sha256:7ffafcbfc7b549ab940047e505c831eabac5e67de53e1bc174adbc5285c55944 \
605
+ --hash=sha256:87a3caecf8095e48dc72f84bfa42e23a848cf410cc9cc13031fba4869b706a21 \
606
+ --hash=sha256:8876b39961e33d912afe3c1bee18ee564fdad0206f873cc15d522756b7f50737 \
607
+ --hash=sha256:8a235f73d6e020855dc29dff012d920c02ee0feab8d73a24185a7569f4be1161 \
608
+ --hash=sha256:96f7c5c15656040ddcbc51e56dc59b58aa25999d743c126abd425b9766ab43e9 \
609
+ --hash=sha256:98914de2c4d7f0f9f4a8c6ea4bf05841f4175796941e3ef7d47eb718f22311fb \
610
+ --hash=sha256:9a2a8a50a93dee0268a860a07fa3b4bd968f8ce4dbd794957da772f395368526 \
611
+ --hash=sha256:a4d7a54719b67338a305dca2ce6aafe366817df94ddfd4b5514374356f5ca546 \
612
+ --hash=sha256:a5793c7698a53f56f4a1889a4737c7eeb1b7ad0842fa6b1abca22913ff79c8c1 \
613
+ --hash=sha256:a74bfa37147cc08fb29df10bd9c16f40fa7f860cd3a6d2fff853323a94f6e17f \
614
+ --hash=sha256:ae58f361bd5dae942717c65d3413b478c70aea9c462599e7b9adad3731db3894 \
615
+ --hash=sha256:b28f41fa3b8c6900457f858ec5b03998f3a6d535fbc1bb2edec5961ea05ec429 \
616
+ --hash=sha256:b3083bfe87f95c756e610bd8025f26cbd1cd4aaa03a422f2d65efb7a97cd53d8 \
617
+ --hash=sha256:c02c0e570c5c7e077b0181a9f3cdb7d4c3617d1cda6b5c95bd5d34022923d82c \
618
+ --hash=sha256:c2ba30a89035b57b73e00475de948521602f543d79ce01db10b04b36c4c76fc8 \
619
+ --hash=sha256:c3e98c55bde2bcf7db3c70d1aed7ae9aa8aebbf19a250c66645cde44cdb8b867 \
620
+ --hash=sha256:cdee8205a44d0be91bbac4c41b95d86641b72dfc7aef1279400e4fda3f26a937 \
621
+ --hash=sha256:d1f5a500bfcbb2c0ab85e98a0dcd70f5899d34efe365a0187700369a79603031 \
622
+ --hash=sha256:db3ae52ccc150dbc84704e9d642743897f3e1c54742ff34cacb661e52e3818a9 \
623
+ --hash=sha256:dbab5f5ff6897c81f355d079010cdae85b02e5a0b518b5251523b8ad8ae9ac3c \
624
+ --hash=sha256:dcbf346a318a5e30063f547630b02bb787ce2f45b6368d5da143660b6a3835d8 \
625
+ --hash=sha256:e1d52a05dc417279f7e5c7618c5dfbbc29923aaf9bc0a5c1802ddcebf54c61a0 \
626
+ --hash=sha256:f85915e00dcb1cd9f2f890ead064ed40a27df06f0db65be427b29482ae357572 \
627
+ --hash=sha256:ff6b113f50bc066a698db5d944d2c6dc7507168dd3341e255a8892fd0715a558
628
+ # via
629
+ # aiohttp
630
+ # yarl
631
+ protobuf==6.33.6 \
632
+ --hash=sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326 \
633
+ --hash=sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901 \
634
+ --hash=sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3 \
635
+ --hash=sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a \
636
+ --hash=sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135 \
637
+ --hash=sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3 \
638
+ --hash=sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2 \
639
+ --hash=sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593
640
+ # via modal
641
+ pycparser==3.0 ; implementation_name != 'PyPy' \
642
+ --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
643
+ --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
644
+ # via cffi
645
+ pydantic==2.13.5 \
646
+ --hash=sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73 \
647
+ --hash=sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08
648
+ # via fastapi
649
+ pydantic-core==2.46.5 \
650
+ --hash=sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942 \
651
+ --hash=sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2 \
652
+ --hash=sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc \
653
+ --hash=sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5 \
654
+ --hash=sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0 \
655
+ --hash=sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931 \
656
+ --hash=sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038 \
657
+ --hash=sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b \
658
+ --hash=sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433 \
659
+ --hash=sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29 \
660
+ --hash=sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b \
661
+ --hash=sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8 \
662
+ --hash=sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e \
663
+ --hash=sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1 \
664
+ --hash=sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f \
665
+ --hash=sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761 \
666
+ --hash=sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869 \
667
+ --hash=sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5 \
668
+ --hash=sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c \
669
+ --hash=sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea \
670
+ --hash=sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519 \
671
+ --hash=sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e \
672
+ --hash=sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0 \
673
+ --hash=sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4 \
674
+ --hash=sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d \
675
+ --hash=sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f \
676
+ --hash=sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5 \
677
+ --hash=sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575 \
678
+ --hash=sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62 \
679
+ --hash=sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a \
680
+ --hash=sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed \
681
+ --hash=sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f \
682
+ --hash=sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3 \
683
+ --hash=sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff \
684
+ --hash=sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084
685
+ # via pydantic
686
+ pygments==2.21.0 \
687
+ --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9 \
688
+ --hash=sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c
689
+ # via rich
690
+ python-dateutil==2.9.0.post0 \
691
+ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
692
+ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
693
+ # via botocore
694
+ python-multipart==0.0.32 \
695
+ --hash=sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e \
696
+ --hash=sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23
697
+ # via mlx-vlm
698
+ pyyaml==6.0.3 \
699
+ --hash=sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6 \
700
+ --hash=sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1 \
701
+ --hash=sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea \
702
+ --hash=sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb \
703
+ --hash=sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b \
704
+ --hash=sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c \
705
+ --hash=sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd \
706
+ --hash=sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c \
707
+ --hash=sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196 \
708
+ --hash=sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8 \
709
+ --hash=sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e \
710
+ --hash=sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28 \
711
+ --hash=sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5 \
712
+ --hash=sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5 \
713
+ --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc \
714
+ --hash=sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26 \
715
+ --hash=sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f \
716
+ --hash=sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be \
717
+ --hash=sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c \
718
+ --hash=sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6 \
719
+ --hash=sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0
720
+ # via
721
+ # huggingface-hub
722
+ # transformers
723
+ regex==2026.9.10 \
724
+ --hash=sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4 \
725
+ --hash=sha256:048a89ee797db10160bd2bd519286577a6b43a100279bd4b7d8456a3d69c80a0 \
726
+ --hash=sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8 \
727
+ --hash=sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0 \
728
+ --hash=sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d \
729
+ --hash=sha256:20e8bfb07ad79a282f8b95b56fe67f9750b1b7f775724e4ba1f23cb296115ce4 \
730
+ --hash=sha256:239620b0e0681669367c0e218c8eb2551d9f8fe3b9fccfc8d0003377804e8348 \
731
+ --hash=sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd \
732
+ --hash=sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1 \
733
+ --hash=sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0 \
734
+ --hash=sha256:3bdeed3318a8eb2bbadc9c56347e0ff651639e934a47e168d05a3b12929fd0e7 \
735
+ --hash=sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1 \
736
+ --hash=sha256:4db7d00c4afbfbb55b8e17b1e371da11418ea9389b030acec63c1fa4c7ad4b86 \
737
+ --hash=sha256:5847e22bbf959764d776937d791d034cc2d19b787e361c88d97e859e8dc68502 \
738
+ --hash=sha256:6aebdd9a946de328b3f6f61dbf48dd064a36eb6dddf96e34ae6651d37f6e9383 \
739
+ --hash=sha256:6b34a778c695d24e77c140e3b4c95da69282e34f2f6b02b55656aa4a0379f643 \
740
+ --hash=sha256:79e9432995e14c749d34209413de5e621ec8e67789bf4f46dbfabea9d06a2406 \
741
+ --hash=sha256:7abb38b8c40f3a235235a44da452c64b7b5c1d650ec6351027db0e090804f2e5 \
742
+ --hash=sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb \
743
+ --hash=sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db \
744
+ --hash=sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a \
745
+ --hash=sha256:bafa41b0dd63669e5c0f8adf3d24819efeb73c847f492eb011212eb352e69041 \
746
+ --hash=sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3 \
747
+ --hash=sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944 \
748
+ --hash=sha256:c103b3b14e011774af4fb7e4617ad4d72b9171905cd3b231a70a4efd76e477d7 \
749
+ --hash=sha256:c25a754bb81a2edcfc3b65eda50f017d736f818112ed43e8aafd595cb00678ae \
750
+ --hash=sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23 \
751
+ --hash=sha256:d8c668af8f7bdb1d18739c27d30cd9f4b371495a883f75a002fb7a39d740fecd \
752
+ --hash=sha256:dce932f8e3ba936475ea3d0d8b59f7b050a9e206e994f53f8fd80299871e87da \
753
+ --hash=sha256:e0dc78251154b66dc60211563fc115345da332eaa881e4e2523fb1edae3772f4 \
754
+ --hash=sha256:ebb2ba68e4641a994061f70bf44ed448fba0b9b1d18c94ffb9efc1cca805b39b \
755
+ --hash=sha256:ef5a059ea1c6ee5d1c7e99a2484e628608d010921efe876c6f0e2029d2f35eca \
756
+ --hash=sha256:f2374c27deb189b282ec7e16106752c22ad39b056bbd8018960b1e4cc95d67a1
757
+ # via transformers
758
+ requests==2.34.2 \
759
+ --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0 \
760
+ --hash=sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed
761
+ # via mlx-vlm
762
+ rich==15.0.0 \
763
+ --hash=sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb \
764
+ --hash=sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36
765
+ # via
766
+ # modal
767
+ # typer
768
+ s3transfer==0.19.2 \
769
+ --hash=sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993 \
770
+ --hash=sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25
771
+ # via boto3
772
+ safetensors==0.8.0 \
773
+ --hash=sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358 \
774
+ --hash=sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f \
775
+ --hash=sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d \
776
+ --hash=sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d \
777
+ --hash=sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0 \
778
+ --hash=sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc \
779
+ --hash=sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235 \
780
+ --hash=sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98 \
781
+ --hash=sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4 \
782
+ --hash=sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846 \
783
+ --hash=sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca \
784
+ --hash=sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0 \
785
+ --hash=sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25 \
786
+ --hash=sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452 \
787
+ --hash=sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d \
788
+ --hash=sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78 \
789
+ --hash=sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774
790
+ # via
791
+ # solomon-mlx
792
+ # transformers
793
+ scipy==1.18.1 \
794
+ --hash=sha256:3ab3523da44749156e1f68b464dc56af11ae4cbc5c739a49d05f32b982eca9f3 \
795
+ --hash=sha256:3c085faa2cfa879c5141df483f836f4d691045a078224a670fa570fa01612d89 \
796
+ --hash=sha256:457fd7a2a8edeb044ab6ffbc0aa03ff6cd18491356e5e0c834d76ce621b916d1 \
797
+ --hash=sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307 \
798
+ --hash=sha256:559ed65f60c1af5a03f3912605a1b5114f522c7c32fb23c3376ae8f03219fe28 \
799
+ --hash=sha256:5e4d44984abc0020154ea81b247adeddcc3ac5527b975ff798bd1ba0adc513c2 \
800
+ --hash=sha256:75b00eb8fb802090aa903f4ea1c7f5a584779f967361e68b7e98e531cc2d7174 \
801
+ --hash=sha256:78c0665edead396b1abb4897c41a5c1d9bf090c8a637a4c20a61678e0a264e66 \
802
+ --hash=sha256:7bbf207c4453ce1ad2e00b17313852b33310b83090c2311bdaf97f93c0380d12 \
803
+ --hash=sha256:c35d74ce0e193ff740c2f2be2ac913ddc232fe6c1ff40b26cfecb9c670c63314 \
804
+ --hash=sha256:c825cef2f49e46753726a7181a8e199804a912b29519ada542c6ebc654951899 \
805
+ --hash=sha256:cd479fc04dd9401e3b4f49e76518768ef99c4f517a98c284eb091fd725719adf \
806
+ --hash=sha256:d2924a03db38dc2e848bca2fe9f077dafb891480b91a00a0963a8cf86dfc31c1 \
807
+ --hash=sha256:d416b16cccfd70fbf62400e84d0bb2f4e6af519a45557f1692c749b37f14b315 \
808
+ --hash=sha256:d65d448389b8436493abcf629cc94ad0cf32aecaf06e1acca1de53cc795f2f12 \
809
+ --hash=sha256:e3b417bf8c2c7c16e8f58ad91db17783ec911ac16e7b50eb6eab6e809b4f5b07 \
810
+ --hash=sha256:e6fb6a55cc0ba97b59a1f288fb86dc6fce8bdfc0fffcbfd015e3a954bf2a2d93 \
811
+ --hash=sha256:e708533e8b2ae2497d65346538a7dcc92814410b25b81432eac66de0f2af8265 \
812
+ --hash=sha256:ea324d9dd34c38bfb9bec8ca4d1b407db97dbb74029f566b8e322b1b6fe56fe6 \
813
+ --hash=sha256:f55fa87b6c612ecd6b058f167c53231b1d14e412efe361d3d6e38b3631c73218 \
814
+ --hash=sha256:fdaf5ea890a6183d0565f51a61799d67081bd5b1cf03c5f4b3fd3732108625c9
815
+ # via
816
+ # mlx-audio
817
+ # solomon-mlx
818
+ sentencepiece==0.2.2 \
819
+ --hash=sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b \
820
+ --hash=sha256:201a8e0f55501a76e08dbf2c54bc45f4642b379271e89c667d517bfbc2191f2a \
821
+ --hash=sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838 \
822
+ --hash=sha256:3ab3f1ae98970b5590e2209341522718900ba19bcc2c207ffaa6bd417ad960c5 \
823
+ --hash=sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6 \
824
+ --hash=sha256:3ec27c152a1f1b24bc9168b55a5880f3c16e2334e697da6f55a1046a22405a3d \
825
+ --hash=sha256:4f0603267cd15b92b68c2c0e852a441507614b70dc7773659baa6b8c214a91fd \
826
+ --hash=sha256:59d6588712101ccfcae9b03692be3aaae1514c2078666d7b05f15ba3a702e41b \
827
+ --hash=sha256:64b656f025355cf8c51abe9fbe3848540756c6d7ca5e6791b1afa664bc24c7cb \
828
+ --hash=sha256:72b7825b331b1b7e7c45be2e674b3e3c65af608fa376bad2d851b20aaf0cdc78 \
829
+ --hash=sha256:74f0ee601047c0c12a783088b51be4e6214a62ecd9e02278c477433cd16e0ed9 \
830
+ --hash=sha256:76ff5814db72e7462dece042d7593cdf102b8ec82c2b1cc201a2add34ee3050d \
831
+ --hash=sha256:77c3ce990b23441e5ecfa5bce181fd6f408b564aeb6d7e1d1e7de9c5612501c8 \
832
+ --hash=sha256:7c6e7bf684dc12145bfa685d3060beaea55139134ba848289bee514ed42e7383 \
833
+ --hash=sha256:89625fb43765cccaa1443b9adb61f283e5fe4cb1536728205d06bada730caa53 \
834
+ --hash=sha256:8eed98514bffe5ecac37f493f91869c351fbb05629328bfdbc08502c6c094dc0 \
835
+ --hash=sha256:b23fe17779834d3c27aaf2edac9486d04cca1a7deb8f5facda35150ac6263a91 \
836
+ --hash=sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da \
837
+ --hash=sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a \
838
+ --hash=sha256:d795c4ac689a57f9d4ba2288126ec7901d389ad5827d2f8b8533c883974fe563 \
839
+ --hash=sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820 \
840
+ --hash=sha256:fd523c4992041faa5c2b3cde62253d11a96c30d73a34afe48a486e8e2254cd1c
841
+ # via mlx-vlm
842
+ shellingham==1.5.4 \
843
+ --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \
844
+ --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de
845
+ # via typer
846
+ six==1.17.0 \
847
+ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
848
+ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
849
+ # via python-dateutil
850
+ sounddevice==0.5.6 \
851
+ --hash=sha256:7f4162f514f007b0bf25a3ccfed3f1705bc2ec311888a90232729eec4f57a4f4 \
852
+ --hash=sha256:8ec9fbfde2e32f020b167e348f3ab3bac6625a5f15af524d790108ac7147a410 \
853
+ --hash=sha256:b36b807eb02abd257198bf84b2af05e4fea199a9d2f0019014169c7136d45e9c \
854
+ --hash=sha256:c8ae19173e5f27f8c12d4b5eee2dbfe542cee125d591e663e0fb4dfb75246d45 \
855
+ --hash=sha256:de099612311ad81e55d31ccbd83f43ea6bf4d87b48f9b6ea55a1fbcde0eee4e0 \
856
+ --hash=sha256:e3aef00ad8b1d1740eb66d9a7671eab88a4d2b8fa4ab33498d742e63b65c309c
857
+ # via mlx-audio
858
+ starlette==1.6.0 \
859
+ --hash=sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c \
860
+ --hash=sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b
861
+ # via
862
+ # fastapi
863
+ # mlx-vlm
864
+ synchronicity==0.12.5 \
865
+ --hash=sha256:94d96b1d85698e3056b96a793b8c0949af6584e4a7d877fabdeb5385efe230aa \
866
+ --hash=sha256:fdbbb10d437bc08a6b0f814fc66fddd1b58ffed314533d42f1ab555801e781af
867
+ # via modal
868
+ tokenizers==0.23.2 \
869
+ --hash=sha256:12f0835dc2ee694746a76adf7b1567d4346a4a502ebe93fb1f5f80ea49799b78 \
870
+ --hash=sha256:2e96f5699d5249c9c64aa8412e044f727aae3a4098cf830f9901ec1afc361cde \
871
+ --hash=sha256:325fee2e0418a9dc6c9ecf736a5f5f0db7875183ace9549ae339da76f7a1fbb7 \
872
+ --hash=sha256:41c2f84d172449b4dadb9cdc508e3e364076613c35b16e76ecfe47a60d1e3305 \
873
+ --hash=sha256:43e4f2071e3cc8d5d86421c874aebc82659bb51a68bcdef5a0da75ee89511ccb \
874
+ --hash=sha256:5c56bda1511921587789163e524d196ed8284174ac23abd7685d5ea8da6c4718 \
875
+ --hash=sha256:7b7e37ba198f24150f523e1242e83c4970de4a525480586be5dcc24d9add32c5 \
876
+ --hash=sha256:7f0f085686b9de0d0079e6f874ae053600db64c5d13049e0bbc0119926d25aac \
877
+ --hash=sha256:85a9a357a3764aecc904ee76bdaf8cf1ad8e5a67a1b929a487c4a39b49ed0e90 \
878
+ --hash=sha256:950d7c9426fa72406a0ffeacdbc0bb9985f5db20eb8b263f29c79aaf83105703 \
879
+ --hash=sha256:986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf \
880
+ --hash=sha256:a37039b5dfc4af84eb3ef0a92f4307e28936c8f9adccba2629d36f652e9bf7a2 \
881
+ --hash=sha256:bef235815a067b2648caf6dcc7a71091b0b0fff9ee8057f6451eb9335fae52ef \
882
+ --hash=sha256:debf978920d93ba9c219bd67cc4bbfaf912c9039e41e7a28b91ec15e3728c95a \
883
+ --hash=sha256:e49c394456dd9985787fec76132438ba3fb8911f857b1bf3d40119f9292d41aa \
884
+ --hash=sha256:eb2f9c8a24da020ea8c11a01a19c1c2547912d92121ae4a01cfbca46125dee40 \
885
+ --hash=sha256:f486f402f6f9abee5bb032553736813af0c710a86b2e0ca592634c55cea1f835
886
+ # via transformers
887
+ toml==0.10.2 \
888
+ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
889
+ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
890
+ # via modal
891
+ tqdm==4.70.1 \
892
+ --hash=sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73 \
893
+ --hash=sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4
894
+ # via
895
+ # huggingface-hub
896
+ # mlx-audio
897
+ # mlx-vlm
898
+ # transformers
899
+ transformers==5.17.0 \
900
+ --hash=sha256:78ec1ce21579b38dfb83950a0658cd119f87212a2fcfdff478096ce9d6c03801 \
901
+ --hash=sha256:a153be279169b55b92d8000bf4af294aed684503d091cca7804da2dd8a9de000
902
+ # via
903
+ # mlx-audio
904
+ # mlx-vlm
905
+ # solomon-mlx
906
+ typer==0.27.2 \
907
+ --hash=sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945 \
908
+ --hash=sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d
909
+ # via transformers
910
+ types-certifi==2021.10.8.3 \
911
+ --hash=sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f \
912
+ --hash=sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a
913
+ # via modal
914
+ types-toml==0.10.8.20260518 \
915
+ --hash=sha256:0e564ab05f6fde62a315b3b5a9b6624fda569399795d30a37e64705a70459303 \
916
+ --hash=sha256:80e10facd24fdeda9d5c672187d72be3ac284843788d67f5aae59e3e016db6fe
917
+ # via modal
918
+ typing-extensions==4.16.0 \
919
+ --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \
920
+ --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5
921
+ # via
922
+ # aiohttp
923
+ # aiosignal
924
+ # anyio
925
+ # fastapi
926
+ # huggingface-hub
927
+ # modal
928
+ # pydantic
929
+ # pydantic-core
930
+ # starlette
931
+ # synchronicity
932
+ # typing-inspection
933
+ typing-inspection==0.4.4 \
934
+ --hash=sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47 \
935
+ --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147
936
+ # via
937
+ # fastapi
938
+ # pydantic
939
+ urllib3==2.8.0 \
940
+ --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \
941
+ --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63
942
+ # via
943
+ # botocore
944
+ # requests
945
+ uvicorn==0.53.0 \
946
+ --hash=sha256:a9356f0cb89b3b8621529c5d5eebd69bfe154f4c3f68b4cf2de47e45fa855c2e \
947
+ --hash=sha256:e8dca71ec86dce5f04e333f0d56cdedf942446e6643b9cea1af0d6d3a02cb03e
948
+ # via mlx-vlm
949
+ watchfiles==1.2.0 \
950
+ --hash=sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9 \
951
+ --hash=sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98 \
952
+ --hash=sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f \
953
+ --hash=sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427 \
954
+ --hash=sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4 \
955
+ --hash=sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa \
956
+ --hash=sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c \
957
+ --hash=sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906 \
958
+ --hash=sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c \
959
+ --hash=sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c \
960
+ --hash=sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077 \
961
+ --hash=sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01 \
962
+ --hash=sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9 \
963
+ --hash=sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658 \
964
+ --hash=sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0 \
965
+ --hash=sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8 \
966
+ --hash=sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898 \
967
+ --hash=sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44 \
968
+ --hash=sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5 \
969
+ --hash=sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc \
970
+ --hash=sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5 \
971
+ --hash=sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9 \
972
+ --hash=sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0 \
973
+ --hash=sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55 \
974
+ --hash=sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb \
975
+ --hash=sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0 \
976
+ --hash=sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26 \
977
+ --hash=sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3 \
978
+ --hash=sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838 \
979
+ --hash=sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71 \
980
+ --hash=sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d \
981
+ --hash=sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44 \
982
+ --hash=sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2 \
983
+ --hash=sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5 \
984
+ --hash=sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799 \
985
+ --hash=sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379 \
986
+ --hash=sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925 \
987
+ --hash=sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72 \
988
+ --hash=sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08 \
989
+ --hash=sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4
990
+ # via modal
991
+ websockets==17.1 \
992
+ --hash=sha256:00bf34b64501e3477e81fc281532ff3cbf4da26633c10b63979d5085d46602d3 \
993
+ --hash=sha256:0340bbef6bfbe16da888b3983d666a4db4954ac3253c38f13bc7aba0c7db5a2f \
994
+ --hash=sha256:073c5c3f7e127041fa9d34a9e29ceefee8c3cafbd267ed2927318f425144380d \
995
+ --hash=sha256:0c863507ada5805517ca6dff1c524dcd42942efe6304dacf06700878398d21a6 \
996
+ --hash=sha256:0de501b7f2db11e83739ac20e2d33d46da4604b829f506c24be80e7def069391 \
997
+ --hash=sha256:1fce0f43e0d41422e0b2cad6561e1970df22f212f4c7e884967df7cf591b031c \
998
+ --hash=sha256:29176d8b429cfa0fa443c473878d37a5c06cfd0cb36b71ba4314accc71e05906 \
999
+ --hash=sha256:2a0162a6372110a5601cb5c9fd826635cedf69f3e110c545dd19774e040b970e \
1000
+ --hash=sha256:2afb58c7ba48b329d56769f8dfd89f394efe587b65ef806bae810a484d6d3608 \
1001
+ --hash=sha256:3709a1ab30b4b922027d22f68d2b61a0656a91680ac894a537624e6be7dd7f7c \
1002
+ --hash=sha256:4031152769179ab8dcdeafc7b0e58052a49117560a28671700b47b2c7b717aad \
1003
+ --hash=sha256:43bd0c1ceb924d67f5c1a5254d8361dd9d94246e6331a726064dfa2917880780 \
1004
+ --hash=sha256:581fa678ef46f4277cc8491312468e582f8ad609dbab907ba6096a08c6a0ff98 \
1005
+ --hash=sha256:5aefe78e6a3077fe22b5e64b04666a85a3eb8b934d40e8595a693adcbceb6f11 \
1006
+ --hash=sha256:5f051f8030a51815dc00e24bd2e5f1435af095c1cc111d747ac6e2a3620d7641 \
1007
+ --hash=sha256:617243e19a0992095956f406ee9cd3bc4ba92862d83cb1d83bb59ce574412bec \
1008
+ --hash=sha256:655a8e28010f09fd6fa317e857afab3af7647f33e41dee88fa421e92086d1090 \
1009
+ --hash=sha256:677014a073bcb1fbaa7e21144786864f16c08f856d66834f611eceb9006cbab8 \
1010
+ --hash=sha256:76dd004f59115087c7b700474cb18f01325e37250032e19396c08ae41448e4b3 \
1011
+ --hash=sha256:77b37cceca17291897c3c73bd30a7c7c7909593554b5da574ec852af83c1742a \
1012
+ --hash=sha256:7a72efa3bf4fa3a6669a54420a472ad056da3973d827f10e3a536da463f926c2 \
1013
+ --hash=sha256:7e724f843fa6a0614aece65a7c73e51d0f4412ca41dccac13c3caf98e69536bb \
1014
+ --hash=sha256:829dba1bc049779de9b332088c1a6a9858e96bd67e50b6b644a95e02b67836bc \
1015
+ --hash=sha256:87f0d5e77548b0c40c8464cdb6108792e7e53f487c6400028a4ec28a8afbe5ab \
1016
+ --hash=sha256:882af300d2c6a092b93767d5de03c7bb56dfb06314140c8e872d3f48e09f7b74 \
1017
+ --hash=sha256:9f4a08ff7cb68c27b18e09223cc6304e01d0f82d5a240d251266dfd2e6e44729 \
1018
+ --hash=sha256:9f4c0377a83e163a303514fdfab501dbe379bdc13e5b9312a91d112658b29dce \
1019
+ --hash=sha256:a06f3b5085176763182449559e20391d7ce616a8972a9f7a33deda87ea6d4f3c \
1020
+ --hash=sha256:acfea4c20bf54384883ea33b1240fc1db4f52e190823a4e2b334bc3e8bfca96a \
1021
+ --hash=sha256:c3241d684a76eaaef8b2dc789afde4343cd3aad55ea81e4e8ab3605b529bae51 \
1022
+ --hash=sha256:ccbf3f4a9890d50b3a08ee04029fde30a03bfdeffaa19977628bf17251764e60 \
1023
+ --hash=sha256:ce0305b702b20d1e1d60a9aaace6bc89970e1753565543f310d549eab22c2435 \
1024
+ --hash=sha256:d41ef69d5416fbc1d98cf96c37be6192d10fd101c3e0f8b3ddc36e09432b3c08 \
1025
+ --hash=sha256:d8e83333385cac6030a5167fd18bf96cc6c58b914c308e683f05b0cf94bc8dd0 \
1026
+ --hash=sha256:dc2b79afc074d2f3e64b26539350f697fe1b85ea1c49ea24eb588f247b053ce1 \
1027
+ --hash=sha256:e4bd7eacb87d8cf3ed70d6392c770a0d92441f05d7d2a3efafb5bc171d5e3067 \
1028
+ --hash=sha256:e5f5c7a893507d0e83a80b88aefd6522f7e882cd53f9722c6f23f5a020c9557c \
1029
+ --hash=sha256:eec113a5b41d124ef42ff56b0d74a6da3fd986400038eab9e58ee42a4024e837 \
1030
+ --hash=sha256:f221081107b8c48184d99f7019604486376e7ef826037e70aad6b02540732c23 \
1031
+ --hash=sha256:f62114a54117e4948a1e414e89521f7fe1e3c2f83f2a571a06a4fc6718b0900a \
1032
+ --hash=sha256:f64e001bb7fa89b9f32cfa600bf8e9ac8ca26759d9b92ae01453ee303d9cd7b4 \
1033
+ --hash=sha256:fd8f47dbf2e8adb15c847215f83436de3fdb120b51fdae0fbbdf69fd97a3ad80
1034
+ # via mlx-vlm
1035
+ yarl==1.25.1 \
1036
+ --hash=sha256:03dd38de09bc213e9a8b29761eec33ee1d5318dac0e49d8af36e4d27830e23a7 \
1037
+ --hash=sha256:0a66db89ea473abeac4b70523cafd94db3772380e565f9d28af7a179b7af71fa \
1038
+ --hash=sha256:0f12afda4eea8c8994a76d4df1875c765194f5fbe8a9d197929ea303caee29ec \
1039
+ --hash=sha256:10b2fd95332f0d716d5eee3c9fb2ce8eada19082de7fee83d32e37992fd75c26 \
1040
+ --hash=sha256:126a2533570c554719ca40a1288fdee1700b6bc82e7131aa69fa85252d92e651 \
1041
+ --hash=sha256:14b79a30a93a3ce2e8832603fd0ab780ada281b0ba5110b519a634f2d7d7d1fc \
1042
+ --hash=sha256:1f51020b2eb8a003c84925638ec63c21a750a4bddd3a22ec8eac6a742dadf1b9 \
1043
+ --hash=sha256:25868beca8b6765f8f7d0e11fe6dd7c66dd4b0793b9500286d20cc92352126a5 \
1044
+ --hash=sha256:2b49375d22299b0a834c2bca72f39aaecc270d96fb24c30424899676f487b22a \
1045
+ --hash=sha256:3feb99222553a8cbedfa52c2f59dd84c3f50d5b582c728d522caf8d72769a54b \
1046
+ --hash=sha256:419f392a1da624877975709e3864dfe833af6cc7671b39318086d456e288380c \
1047
+ --hash=sha256:4bd6340d20ae2c7ca719b87b426e808e90743b676d05d4c26c4fb5ca71f41184 \
1048
+ --hash=sha256:4d781294bb815ecb5ea57ff6bbf8038e0a31a95fdf3e1788f66e0dc100d64b58 \
1049
+ --hash=sha256:681c758b0490f9e96b78e5fa8e8dc6e648e9185bb6eaebe73183c33ea0c445f3 \
1050
+ --hash=sha256:68782fdb4027b8d1eee25ec35e9a6db05e863b899eb0310b3a33b6c3fef55707 \
1051
+ --hash=sha256:7d575b54cb3863ef9bc290ea4b009999d55dc237326131e4853cf33e888fee03 \
1052
+ --hash=sha256:7e4de3ac4adbad3d0bc7c6f4360a7dbff5de2f15e3b723be3198074e17fd9c40 \
1053
+ --hash=sha256:80e47012e730da131c9f059c80936783f9659aae22dc31c03c0595590d11ed54 \
1054
+ --hash=sha256:83d4a37e4b95da4d8bda930d6d35b75b4cdadbacbb4980cae290ea3100b5d51d \
1055
+ --hash=sha256:94d7aa6debf92a1dd14cb5280b083a764169a13cfb23a452111160274ed989f4 \
1056
+ --hash=sha256:a2ed0ba415ccdf08f14bf544cb78346d0f76086707ffee24921a2c84dbf1305a \
1057
+ --hash=sha256:a3faadac7d812ddac258feb57b9846b60c1b437c4f4b9ad42595c6f6fe4390df \
1058
+ --hash=sha256:af4ea5b37403ef4e30f3927eaed540db942bde01d8d3ff083527c0704d1c9c68 \
1059
+ --hash=sha256:b10dd0557ba422715b5206b3743192135a6022acca8baec51aa127d0a75db8fe \
1060
+ --hash=sha256:bc3ac7bf569f6b64dad04dd7808c7872dae8a97df657856eac05e9b7e3614a85 \
1061
+ --hash=sha256:be80550d9bfe83d9b62398a37081a90434e6df2d978ec345c3d2820de6beddab \
1062
+ --hash=sha256:c6f117789d22dce188e5754e8bc65b7e6ebf8cb73963b9fa761f672a5883769d \
1063
+ --hash=sha256:cce0727fd5ac04d372fa9bbfde9febc2bcf209aadfcf0468e45dec72719895d1 \
1064
+ --hash=sha256:e029648f9c951db30e98a7d7ec90835db88ec4b32820efe2a9bdc2287e032eb6 \
1065
+ --hash=sha256:e07595c7d6f4db270ceede356a1bd1c07a34f1c26f958d1ed0cd7b48e0d2bba3 \
1066
+ --hash=sha256:e12c538e00e7c1b286a07061046b90e8124e6a9793efae2c70db6a4aad07faad \
1067
+ --hash=sha256:e546fe1d4a93ebc2910f0d768baff19faa09843ab3f2036a67ed6e69fae4419d \
1068
+ --hash=sha256:e80f557716fd765439577131e526b8942ffc2c07bdbc5e39fa62f660ba1e963f \
1069
+ --hash=sha256:eb96ed1ae6c7d072d60840c0434aef07a2df611812810807fbc54263a6053e9a \
1070
+ --hash=sha256:ef74070ac553c59eb4f04258722066d6c6135b7baa03b2e9f2da65c096e96d98 \
1071
+ --hash=sha256:f61964f235a43738bfac50da46fc4254943a7eea3051aeb0b6fc7c992c29fadc
1072
+ # via aiohttp
mlx/requirements.lock ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiohappyeyeballs==2.7.1
2
+ aiohttp==3.14.3
3
+ aiosignal==1.4.0
4
+ annotated-doc==0.0.5
5
+ annotated-types==0.8.0
6
+ anyio==4.15.1
7
+ attrs==26.1.0
8
+ cbor2==6.1.4
9
+ certifi==2026.7.22
10
+ cffi==2.1.1
11
+ charset-normalizer==3.5.1
12
+ click==8.5.0
13
+ fastapi==0.141.1
14
+ filelock==4.0.1
15
+ frozenlist==1.8.0
16
+ fsspec==2026.9.0
17
+ grpclib==0.4.9
18
+ h11==0.16.0
19
+ h2==4.4.1
20
+ hf-xet==1.6.0
21
+ hpack==4.2.0
22
+ httpcore==1.0.9
23
+ httpx==0.28.1
24
+ huggingface-hub==1.32.0
25
+ hyperframe==6.1.0
26
+ idna==3.20
27
+ iniconfig==2.3.0
28
+ jinja2==3.1.6
29
+ llguidance==1.8.0
30
+ markdown-it-py==4.2.0
31
+ markupsafe==3.0.3
32
+ mdurl==0.1.2
33
+ miniaudio==1.71
34
+ mlx==0.32.2
35
+ mlx-audio==0.5.4
36
+ mlx-metal==0.32.2
37
+ mlx-vlm==0.7.1
38
+ modal==1.5.5
39
+ multidict==6.9.0
40
+ numpy==2.5.3
41
+ opencv-python==5.0.0.93
42
+ packaging==26.3
43
+ pillow==12.3.0
44
+ pluggy==1.6.0
45
+ propcache==0.5.4
46
+ protobuf==6.33.6
47
+ pycparser==3.0
48
+ pydantic==2.13.5
49
+ pydantic-core==2.46.5
50
+ pygments==2.21.0
51
+ pytest==9.1.1
52
+ python-multipart==0.0.32
53
+ pyyaml==6.0.3
54
+ regex==2026.9.10
55
+ requests==2.34.2
56
+ rich==15.0.0
57
+ ruff==0.16.8
58
+ safetensors==0.8.0
59
+ scipy==1.18.1
60
+ sentencepiece==0.2.2
61
+ shellingham==1.5.4
62
+ -e .
63
+ sounddevice==0.5.6
64
+ starlette==1.6.0
65
+ synchronicity==0.12.5
66
+ tokenizers==0.23.2
67
+ toml==0.10.2
68
+ tqdm==4.70.1
69
+ transformers==5.17.0
70
+ typer==0.27.2
71
+ types-certifi==2021.10.8.3
72
+ types-toml==0.10.8.20260518
73
+ typing-extensions==4.16.0
74
+ typing-inspection==0.4.4
75
+ urllib3==2.8.0
76
+ uvicorn==0.53.0
77
+ watchfiles==1.2.0
78
+ websockets==17.1
79
+ yarl==1.25.1
mlx/scripts/benchmark.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Full-model parity and measurements on this Mac; emits no simulated hardware results."""
2
+
3
+ import argparse
4
+ import json
5
+ import time
6
+ from pathlib import Path
7
+
8
+ import mlx.core as mx
9
+ import numpy as np
10
+
11
+ from solomon_mlx import Solomon
12
+ from solomon_mlx._vendor.semantics import listed_probs, p_yes
13
+ from solomon_mlx.artifacts import digest, sha256
14
+
15
+
16
+ def probabilities(job, row):
17
+ if job["task"] in ("boolean", "multilabel", "entity"):
18
+ p = p_yes(row["letter_logits"])
19
+ return np.array([p, 1 - p])
20
+ n = job["n"] - 2 if job["head_key"].endswith("choiceR") else job["n"]
21
+ return listed_probs(row["letter_logits"], n)
22
+
23
+
24
+ def run(model_dir, jobs_path, reference_path, output):
25
+ output = Path(output)
26
+ if output.exists():
27
+ raise FileExistsError("Benchmark outputs are immutable")
28
+ jobs = json.loads(Path(jobs_path).read_text())
29
+ reference = json.loads(Path(reference_path).read_text())
30
+ ref = {r["id"]: r for r in reference["rows"]}
31
+ if set(ref) != {j["id"] for j in jobs}:
32
+ raise ValueError("Benchmark and reference jobs differ")
33
+ mx.reset_peak_memory()
34
+ started = time.perf_counter()
35
+ model = Solomon.load(model_dir)
36
+ load_seconds = time.perf_counter() - started
37
+ states = {}
38
+ rows = []
39
+ prefills = []
40
+ try:
41
+ for job in jobs:
42
+ key = digest(job["parts"])
43
+ if key not in states:
44
+ state = model.prefill(job["parts"])
45
+ states[key] = state
46
+ prefills.append(
47
+ {
48
+ "document": key,
49
+ "tokens": state.prefix_tokens,
50
+ "seconds": state._data["prefill_seconds"],
51
+ "vision_seconds": state._data["vision_seconds"],
52
+ "cache_bytes": sum(c.nbytes for c in state._data["cache"]),
53
+ }
54
+ )
55
+ state = states[key]
56
+ row = model.engine.ask(
57
+ state._data,
58
+ job["block"],
59
+ job["n"],
60
+ job["head_key"],
61
+ execution=job.get("execution", "cached"),
62
+ taps=job.get("taps", []),
63
+ )
64
+ p, q = probabilities(job, row), probabilities(job, ref[job["id"]])
65
+ row.update(
66
+ id=job["id"],
67
+ decision_agrees=bool(p.argmax() == q.argmax()),
68
+ max_probability_drift=float(np.max(np.abs(p - q))),
69
+ max_logit_drift=float(
70
+ np.max(np.abs(np.array(row["letter_logits"]) - ref[job["id"]]["letter_logits"]))
71
+ ),
72
+ prefix_ids_exact=state._data["prefix_ids"] == ref[job["id"]]["prefix_ids"],
73
+ )
74
+ if "token_ids" in row:
75
+ row["token_ids_exact"] = row["token_ids"] == ref[job["id"]]["token_ids"]
76
+ if row.get("taps"):
77
+ row["layer_max_hidden_drift"] = {
78
+ k: float(np.max(np.abs(np.array(v) - ref[job["id"]]["taps"][k])))
79
+ for k, v in row["taps"].items()
80
+ }
81
+ rows.append(row)
82
+ print(job["id"], row["seconds"], row["decision_agrees"], flush=True)
83
+ # Replay checks real prefill and repeated question semantics on the same binding.
84
+ first = states[digest(jobs[0]["parts"])]
85
+ recipe = output.with_suffix(".replay.json")
86
+ first.save(recipe)
87
+ with model.replay(recipe) as restored:
88
+ b, n, h = jobs[0]["block"], jobs[0]["n"], jobs[0]["head_key"]
89
+ replay = model.engine.ask(restored._data, b, n, h)
90
+ replay_drift = float(np.max(np.abs(np.array(replay["letter_logits"]) - rows[0]["letter_logits"])))
91
+ finally:
92
+ for state in states.values():
93
+ state.close()
94
+ warm = [r["seconds"] for r in rows if r["reused_prefix_tokens"]]
95
+ report = {
96
+ "runtime": model.identity,
97
+ "device": mx.device_info(),
98
+ "jobs_sha256": sha256(jobs_path),
99
+ "reference_sha256": sha256(reference_path),
100
+ "load_seconds": load_seconds,
101
+ "prefills": prefills,
102
+ "rows": rows,
103
+ "warm_question_latency_median_seconds": float(np.median(warm)),
104
+ "questions_per_second": len(warm) / sum(warm),
105
+ "peak_metal_bytes": mx.get_peak_memory(),
106
+ "replay_max_logit_drift": replay_drift,
107
+ "decision_agreement": float(np.mean([r["decision_agrees"] for r in rows])),
108
+ "scope": "development parity fixtures; not held-out task accuracy or release qualification",
109
+ "calibration_status": "uncalibrated",
110
+ }
111
+ output.write_text(json.dumps(report, indent=2))
112
+ return report
113
+
114
+
115
+ if __name__ == "__main__":
116
+ p = argparse.ArgumentParser()
117
+ p.add_argument("--model", default="models/quality")
118
+ p.add_argument("--jobs", default="evaluations/golden-jobs.json")
119
+ p.add_argument("--reference", default="evaluations/bf16-reference-1789901869/report.json")
120
+ p.add_argument("--output", default="evaluations/bf16-text-benchmark.json")
121
+ a = p.parse_args()
122
+ run(a.model, a.jobs, a.reference, a.output)
mlx/scripts/benchmark_chunks.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Measure 512/1024/2048-token prefill on frozen original acceptance documents."""
2
+
3
+ import argparse
4
+ import json
5
+ import time
6
+ from pathlib import Path
7
+
8
+ import mlx.core as mx
9
+
10
+ from solomon_mlx import Solomon
11
+
12
+ p = argparse.ArgumentParser()
13
+ p.add_argument("--model", default="models/quality")
14
+ p.add_argument("--output", default="evaluations/chunk-benchmark.json")
15
+ a = p.parse_args()
16
+ out = Path(a.output)
17
+ if out.exists():
18
+ raise FileExistsError("Use a new immutable benchmark output")
19
+ model = Solomon.load(a.model)
20
+ fixtures = json.loads(Path("evaluations/cuda-acceptance/input/documents.json").read_text())
21
+ results = []
22
+ for document, parts in fixtures["documents"].items():
23
+ for target in (1242, 2048):
24
+ text = "".join(p["text"] for p in parts)
25
+ while True:
26
+ rendered = model.engine.render([{"text": text}], "X")
27
+ end = rendered.rfind("\n\nX")
28
+ length = len(model.engine.t.encode(rendered[:end], add_special_tokens=False)) - 1
29
+ if length >= target:
30
+ break
31
+ text += fixtures["filler"]
32
+ for chunk in (512, 1024, 2048):
33
+ model.engine.chunk_size = chunk
34
+ mx.reset_peak_memory()
35
+ started = time.perf_counter()
36
+ with model.prefill(text) as state:
37
+ results.append(
38
+ {
39
+ "document": document,
40
+ "chunk": chunk,
41
+ "tokens": state.prefix_tokens,
42
+ "seconds": time.perf_counter() - started,
43
+ "peak_metal_bytes": mx.get_peak_memory(),
44
+ "cache_bytes": sum(c.nbytes for c in state._data["cache"]),
45
+ }
46
+ )
47
+ print(results[-1], flush=True)
48
+ out.write_text(
49
+ json.dumps({"runtime": model.identity, "measurements": results, "hardware_simulation": False}, indent=2)
50
+ )
mlx/scripts/check_cuda_parity.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compare saved MLX scores with CUDA using identical, frozen temperatures.
2
+
3
+ No fitting, parameter selection, or changes to inference weights take place.
4
+ Partial panels are diagnostic only and can never qualify a release.
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ from collections import defaultdict
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+
14
+ from solomon_mlx._vendor.semantics import listed_probs, p_yes
15
+ from solomon_mlx.api import TASKS
16
+ from solomon_mlx.artifacts import digest, runtime_identity, sha256
17
+ from solomon_mlx.evaluation import compare_rows, load_panel, read_cuda_scores
18
+
19
+
20
+ def decision_probabilities(row, temperature):
21
+ """Parity includes every branch, even when its gold label is not a listed option."""
22
+ if row["task"] in ("boolean", "entity", "multilabel"):
23
+ p = p_yes(row["letter_logits"], temperature)
24
+ return np.array([1 - p, p])
25
+ width = row["n"] - 2 if row["head_key"].endswith("choiceR") else row["n"]
26
+ return listed_probs(row["letter_logits"], width, temperature)
27
+
28
+
29
+ def compare(panel, scores, cuda_directory, reference, output, *, allow_partial=False):
30
+ panel, scores, output = Path(panel), Path(scores), Path(output)
31
+ if output.exists():
32
+ raise FileExistsError("Parity reports are immutable")
33
+ jobs, manifest = load_panel(panel)
34
+ identity = json.loads((scores / "identity.json").read_text())
35
+ model_binding = json.loads(Path("models/quality/binding.json").read_text())
36
+ if identity["runtime"] != runtime_identity(model_binding):
37
+ raise ValueError("Scores belong to another MLX runtime")
38
+ if identity["panel_sha256"] != manifest["jobs_sha256"]:
39
+ raise ValueError("Scores belong to another panel")
40
+ groups = defaultdict(list)
41
+ for job in jobs:
42
+ groups[job["document_key"]].append(job)
43
+ rows, files = [], {}
44
+ for key, group in groups.items():
45
+ path = scores / (key + ".json")
46
+ if not path.exists() and allow_partial:
47
+ continue
48
+ record = json.loads(path.read_text())
49
+ body = {k: v for k, v in record.items() if k != "sha256"}
50
+ if (
51
+ record["sha256"] != digest(body)
52
+ or record["identity"] != digest(identity)
53
+ or [r["id"] for r in record["rows"]] != [r["id"] for r in group]
54
+ ):
55
+ raise ValueError("Corrupt or mismatched score document")
56
+ rows.extend(record["rows"])
57
+ files[path.name] = sha256(path)
58
+ complete = len(files) == len(groups)
59
+ if not allow_partial:
60
+ marker = json.loads((scores / "complete.json").read_text())
61
+ if marker != {
62
+ "identity": digest(identity),
63
+ "documents": len(groups),
64
+ "branches": len(jobs),
65
+ "files": files,
66
+ }:
67
+ raise ValueError("Incomplete or mismatched completion manifest")
68
+ ref = json.loads(Path(reference).read_text())
69
+ cuda = read_cuda_scores(cuda_directory, panel, ref["identity"])
70
+ selected = {r["id"] for r in rows}
71
+ cuda = [r for r in cuda if r["id"] in selected]
72
+ binding_path = Path("evaluations/cuda-acceptance/input/serving-binding.json")
73
+ source_manifest = json.loads((binding_path.parent / "manifest.json").read_text())
74
+ if sha256(binding_path) != source_manifest["files"][binding_path.name]:
75
+ raise ValueError("CUDA acceptance binding checksum mismatch")
76
+ binding = json.loads(binding_path.read_text())
77
+ for key in (
78
+ "adapter_sha256",
79
+ "trained_heads_sha256",
80
+ "model_sha256",
81
+ "numerics",
82
+ "placement",
83
+ "arithmetic",
84
+ ):
85
+ if binding["runtime"][key] != ref["identity"][key]:
86
+ raise ValueError("CUDA temperatures belong to another reference")
87
+ temperatures = {task: binding["temperatures"]["models"][task]["temperature"] for task in TASKS}
88
+ comparisons = {}
89
+ cuda_by_id = {r["id"]: r for r in cuda}
90
+ for name, temps in (("temperature_one", dict.fromkeys(TASKS, 1.0)), ("cuda_serving", temperatures)):
91
+ result = compare_rows(rows, cuda, temperatures=temps, reference_temperatures=temps)
92
+ result.pop("quality_gate_passed")
93
+ result["accuracy_units"] = result["units"]
94
+ result["accuracy_questions"] = result["questions"]
95
+ worst, questions = [], defaultdict(list)
96
+ for row in rows:
97
+ other = {**row, "letter_logits": cuda_by_id[row["id"]]["letter_logits"]}
98
+ p = decision_probabilities(row, temps[row["task"]])
99
+ q = decision_probabilities(other, temps[row["task"]])
100
+ if not np.isfinite(p).all() or not np.isfinite(q).all():
101
+ raise ValueError("Nonfinite parity probability")
102
+ agrees = int(np.argmax(p)) == int(np.argmax(q))
103
+ questions[row["question_id"]].append(agrees)
104
+ worst.append(
105
+ {
106
+ "id": row["id"],
107
+ "task": row["task"],
108
+ "max_probability_drift": float(np.max(np.abs(p - q))),
109
+ "decision_agrees": agrees,
110
+ }
111
+ )
112
+ result.update(
113
+ units=len(rows),
114
+ questions=len(questions),
115
+ unit_decision_agreement=float(np.mean([r["decision_agrees"] for r in worst])),
116
+ question_decision_agreement=float(np.mean([all(v) for v in questions.values()])),
117
+ max_probability_drift=max(r["max_probability_drift"] for r in worst),
118
+ mean_probability_drift=float(np.mean([r["max_probability_drift"] for r in worst])),
119
+ )
120
+ result["agreement_gate_passed"] = (
121
+ result["unit_decision_agreement"] >= 0.999 and result["question_decision_agreement"] >= 0.999
122
+ )
123
+ result["largest_probability_differences"] = sorted(
124
+ worst, key=lambda r: r["max_probability_drift"], reverse=True
125
+ )[:10]
126
+ comparisons[name] = result
127
+ report = {
128
+ "scope": "complete text parity panel" if complete else "partial text parity diagnostic",
129
+ "complete": complete,
130
+ "documents": len(files),
131
+ "total_documents": len(groups),
132
+ "branches": len(rows),
133
+ "total_branches": len(jobs),
134
+ "tasks": sorted({r["task"] for r in rows}),
135
+ "runtime": identity["runtime"],
136
+ "cuda_runtime": ref["identity"],
137
+ "panel_sha256": identity["panel_sha256"],
138
+ "score_files_sha256": digest(files),
139
+ "cuda_serving_binding_sha256": sha256(binding_path),
140
+ "temperature_fitting_performed": False,
141
+ "temperature_policy": "identical settings on both backends; not MLX calibration",
142
+ "comparisons": comparisons,
143
+ "parity_gate_passed": complete and all(r["agreement_gate_passed"] for r in comparisons.values()),
144
+ "bitwise_equality_claimed": False,
145
+ "image_qualification": False,
146
+ }
147
+ output.parent.mkdir(parents=True, exist_ok=True)
148
+ output.write_text(json.dumps(report, indent=2))
149
+ print(
150
+ json.dumps(
151
+ {k: report[k] for k in ("scope", "documents", "branches", "tasks", "parity_gate_passed")},
152
+ indent=2,
153
+ )
154
+ )
155
+ return report
156
+
157
+
158
+ if __name__ == "__main__":
159
+ parser = argparse.ArgumentParser()
160
+ for field in ("panel", "scores", "cuda-directory", "output"):
161
+ parser.add_argument("--" + field, required=True)
162
+ parser.add_argument("--reference", default="evaluations/bf16-reference-1789901869/report.json")
163
+ parser.add_argument("--allow-partial", action="store_true")
164
+ args = parser.parse_args()
165
+ compare(
166
+ args.panel,
167
+ args.scores,
168
+ args.cuda_directory,
169
+ args.reference,
170
+ args.output,
171
+ allow_partial=args.allow_partial,
172
+ )
mlx/scripts/score_parity.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Score a frozen panel for CUDA parity without fitting temperatures."""
2
+
3
+ import argparse
4
+
5
+ from solomon_mlx import Solomon
6
+ from solomon_mlx.evaluation import score_panel
7
+
8
+
9
+ def main():
10
+ parser = argparse.ArgumentParser(description=__doc__)
11
+ parser.add_argument("--model", default="models/quality")
12
+ parser.add_argument("--panel", required=True)
13
+ parser.add_argument("--output", required=True)
14
+ args = parser.parse_args()
15
+ score_panel(Solomon.load(args.model), args.panel, args.output)
16
+
17
+
18
+ if __name__ == "__main__":
19
+ main()
mlx/scripts/validate_api.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Focused public API checks using the real BF16 model, without calibration."""
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+
9
+ from solomon_mlx import Solomon
10
+
11
+
12
+ def validate(output):
13
+ output = Path(output)
14
+ if output.exists():
15
+ raise FileExistsError("Validation outputs are immutable")
16
+ model = Solomon.load("models/quality")
17
+ document = "Alice is certified. Bob is not certified. The current priority is high."
18
+ questions = {
19
+ "boolean": "Is Alice certified?",
20
+ "single": {"type": "choice", "instructions": "Who is certified?", "options": ["Alice", "Bob"]},
21
+ "ordered": {"type": "score", "instructions": "What is the priority?", "levels": ["low", "high"]},
22
+ "entity": {"instructions": "Is {candidate} certified?", "candidates": ["Alice", "Bob"]},
23
+ "multilabel": {
24
+ "instructions": "Which facts apply?",
25
+ "candidates": ["Alice is certified", "Bob is certified"],
26
+ },
27
+ }
28
+ checks = {}
29
+ with model.prefill(document) as state:
30
+ first = model.decide(state=state, questions=questions, evidence="none", diagnostics=True)
31
+ assert set(first["answers"]) == set(questions)
32
+ checks["all_five_answer_types"] = True
33
+ repeated = model.decide(
34
+ state=state, questions={"boolean": questions["boolean"]}, evidence="none", diagnostics=True
35
+ )
36
+ a = first["answers"]["boolean"]["branches"][0]["letter_logits"]
37
+ b = repeated["answers"]["boolean"]["branches"][0]["letter_logits"]
38
+ np.testing.assert_array_equal(a, b)
39
+ checks["repeated_question_logits_exact"] = True
40
+ full = model.decide(
41
+ state=state,
42
+ questions={"boolean": questions["boolean"]},
43
+ evidence="none",
44
+ execution="full",
45
+ diagnostics=True,
46
+ )
47
+ c = full["answers"]["boolean"]["branches"][0]["letter_logits"]
48
+ checks["cached_full_max_logit_drift"] = float(np.max(np.abs(np.array(a) - c)))
49
+ assert (first["answers"]["boolean"]["noul"] >= 0.5) == (full["answers"]["boolean"]["noul"] >= 0.5)
50
+ reverse = model.decide(
51
+ state=state,
52
+ questions={"entity": {**questions["entity"], "candidates": ["Bob", "Alice"]}},
53
+ evidence="none",
54
+ )
55
+ assert reverse["answers"]["entity"]["candidates"] == first["answers"]["entity"]["candidates"]
56
+ assert list(reverse["answers"]["entity"]["candidates"]) == ["Bob", "Alice"]
57
+ checks["candidate_order_and_cache_isolation"] = True
58
+ evidence = model.decide(state=state, questions={"boolean": questions["boolean"]}, evidence="removal")
59
+ answer = evidence["answers"]["boolean"]
60
+ assert answer["evidence"]
61
+ for span in answer["evidence"]:
62
+ assert document[span["start"] : span["end"]] == span["text"]
63
+ assert answer["evidence_detail"]["verification"] == "fresh_source_reencoding"
64
+ assert answer["evidence_detail"]["calls"] == 2
65
+ checks["evidence_spans_and_fresh_verification"] = True
66
+ exhausted = model.decide(
67
+ state=state, questions={"boolean": questions["boolean"]}, evidence="removal", evidence_max_calls=0
68
+ )
69
+ assert exhausted["answers"]["boolean"]["evidence_status"] == "budget_exhausted"
70
+ assert exhausted["usage"]["evidence_calls"] == 0
71
+ checks["evidence_budget_enforced"] = True
72
+ replay = output.with_suffix(".replay.json")
73
+ state.save(replay)
74
+ try:
75
+ model.decide(state=state, questions={"q": "Fact?"})
76
+ except ValueError:
77
+ checks["closed_state_rejected"] = True
78
+ else:
79
+ raise AssertionError("Closed state accepted")
80
+ with model.replay(replay) as restored:
81
+ result = model.decide(
82
+ state=restored, questions={"boolean": questions["boolean"]}, evidence="none", diagnostics=True
83
+ )
84
+ np.testing.assert_array_equal(a, result["answers"]["boolean"]["branches"][0]["letter_logits"])
85
+ checks["public_api_replay_exact"] = True
86
+ corrupt = json.loads(replay.read_text())
87
+ corrupt["parts"][0]["text"] += " changed"
88
+ bad_path = output.with_suffix(".corrupt-replay.json")
89
+ bad_path.write_text(json.dumps(corrupt))
90
+ try:
91
+ model.replay(bad_path)
92
+ except ValueError:
93
+ checks["corrupt_replay_rejected"] = True
94
+ else:
95
+ raise AssertionError("Corrupt replay accepted")
96
+ image_parts = json.loads(Path("evaluations/image-jobs.json").read_text())[0]["parts"]
97
+ with model.prefill(image_parts) as images:
98
+ result = model.decide(state=images, questions={"q": "Is Alice certified?"}, evidence="support")
99
+ assert result["answers"]["q"]["evidence_status"] == "unsupported_page_selector"
100
+ checks["missing_page_selector_reported"] = True
101
+ with model.prefill({"subject": "Alice", "certified": True}) as structured:
102
+ assert structured.prefix_tokens > 0
103
+ checks["structured_document_accepted"] = True
104
+ try:
105
+ model.engine.admit(40961)
106
+ except ValueError:
107
+ checks["context_ceiling_enforced"] = True
108
+ else:
109
+ raise AssertionError("Context limit not enforced")
110
+ assert model.engine.context["start"] is None
111
+ assert len(model.engine.heads) == 10
112
+ checks["adapter_state_reset_and_ten_heads_loaded"] = True
113
+ report = {
114
+ "runtime": model.identity,
115
+ "checks": checks,
116
+ "passed": True,
117
+ "scope": "real-weight API behavior; these checks do not establish held-out CUDA parity",
118
+ "answers": first["answers"],
119
+ "evidence": answer,
120
+ }
121
+ output.write_text(json.dumps(report, indent=2))
122
+ print(json.dumps({"passed": True, "checks": checks}, indent=2))
123
+
124
+
125
+ if __name__ == "__main__":
126
+ parser = argparse.ArgumentParser()
127
+ parser.add_argument("--output", required=True)
128
+ validate(parser.parse_args().output)
mlx/src/solomon_mlx/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Solomon semantic decisions on Apple Silicon."""
2
+
3
+ from .api import DocumentState, Solomon
4
+
5
+ __all__ = ["DocumentState", "Solomon"]
mlx/src/solomon_mlx/_vendor/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Copyright 2026 Doccy Pty Ltd. Apache-2.0.
2
+ # Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md.
mlx/src/solomon_mlx/_vendor/contract.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Doccy Pty Ltd. Apache-2.0.
2
+ # Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md.
3
+ import json
4
+ import numpy as np
5
+ MAX_CANDIDATES = 64
6
+
7
+ def _text(value, name):
8
+ if not isinstance(value, str) or not value.strip():
9
+ raise ValueError(name + ' must be a nonempty string')
10
+ return value
11
+
12
+ def parse_questions(questions):
13
+ """Questions {id: spec} -> ordered list of normalised specs.
14
+
15
+ noul: {"type": "noul", "instructions": str} -> task boolean
16
+ {"type": "noul", "instructions": "Is {candidate} ...?", "candidates": [...]} -> task entity (one Noul per candidate)
17
+ {"type": "noul", "instructions": str, "candidates": [...], "candidate_kind": "label"} -> task multilabel
18
+ (candidate_kind defaults to 'entity' when instructions contain {candidate}, else 'label')
19
+ choice: {"type": "choice", "instructions": str, "options": [str, ...] | {key: text}, "ordered": bool}
20
+ ('criteria' is accepted as a compatibility alias of 'options')
21
+ score: {"type": "score", "instructions": str, "levels": [str, ...]} (alias 'criteria'); an ordered choice keyed
22
+ "0".."K-1" with a legend and score = sum_i i * p_i.
23
+ """
24
+ if not isinstance(questions, dict) or not questions:
25
+ raise ValueError('questions must be a nonempty mapping of id -> question')
26
+ out = []
27
+ for qid, spec in questions.items():
28
+ if not isinstance(qid, str) or not qid:
29
+ raise ValueError('question ids must be nonempty strings')
30
+ if isinstance(spec, str):
31
+ spec = {'type': 'noul', 'instructions': spec}
32
+ if not isinstance(spec, dict):
33
+ raise ValueError(f'question {qid}: spec must be an object')
34
+ kind = str(spec.get('type', 'noul')).lower()
35
+ instructions = _text(spec.get('instructions', spec.get('question')), f'question {qid}: instructions')
36
+ if kind == 'noul':
37
+ if 'candidates' not in spec:
38
+ out.append({'id': qid, 'type': 'noul', 'task': 'boolean', 'request': {'question': instructions}})
39
+ continue
40
+ candidates = spec['candidates']
41
+ if not isinstance(candidates, list) or not 1 <= len(candidates) <= MAX_CANDIDATES or len(set(candidates)) != len(candidates) or any((not isinstance(c, str) or not c.strip() for c in candidates)):
42
+ raise ValueError(f'question {qid}: candidates must be 1 to {MAX_CANDIDATES} distinct nonempty strings')
43
+ ckind = spec.get('candidate_kind', 'entity' if '{candidate}' in instructions else 'label')
44
+ if ckind == 'entity':
45
+ if instructions.count('{candidate}') != 1 or '{entity}' in instructions:
46
+ raise ValueError(f'question {qid}: entity instructions need exactly one {{candidate}} placeholder')
47
+ request = {'template': instructions.replace('{candidate}', '{entity}'), 'entities': list(candidates)}
48
+ task = 'entity'
49
+ elif ckind == 'label':
50
+ if '{candidate}' in instructions:
51
+ raise ValueError(f'question {qid}: label candidates take no {{candidate}} placeholder')
52
+ request = {'question': instructions, 'labels': list(candidates)}
53
+ task = 'multilabel'
54
+ else:
55
+ raise ValueError(f'question {qid}: candidate_kind must be entity or label')
56
+ out.append({'id': qid, 'type': 'noul', 'task': task, 'candidates': list(candidates), 'request': request})
57
+ elif kind in ('choice', 'score'):
58
+ raw = spec.get('levels', spec.get('options', spec.get('criteria'))) if kind == 'score' else spec.get('options', spec.get('criteria'))
59
+ if isinstance(raw, dict):
60
+ keys, texts = ([str(k) for k in raw], [raw[k] if isinstance(raw[k], str) and raw[k].strip() else str(k) for k in raw])
61
+ elif isinstance(raw, list):
62
+ texts = list(raw)
63
+ keys = [str(i) for i in range(len(raw))] if kind == 'score' else list(raw)
64
+ else:
65
+ raise ValueError(f'question {qid}: options must be a list or a mapping')
66
+ if not 2 <= len(texts) <= 8 or any((not isinstance(t, str) or not t.strip() for t in texts)) or len(set(texts)) != len(texts):
67
+ raise ValueError(f'question {qid}: provide 2 to 8 distinct nonempty options')
68
+ ordered = kind == 'score' or bool(spec.get('ordered', False))
69
+ if not isinstance(spec.get('ordered', False), bool):
70
+ raise ValueError(f'question {qid}: ordered must be Boolean')
71
+ out.append({'id': qid, 'type': kind, 'task': 'ordered' if ordered else 'single', 'keys': keys, 'texts': texts, 'request': {'question': instructions, 'options': texts}})
72
+ else:
73
+ raise ValueError(f'question {qid}: type must be noul, choice or score')
74
+ return out
75
+
76
+ def _state_parts(state):
77
+ if isinstance(state, str):
78
+ return [{'text': state}]
79
+ if isinstance(state, list):
80
+ return state
81
+ if isinstance(state, dict):
82
+ return [{'text': json.dumps(state, ensure_ascii=False, indent=2, sort_keys=False)}]
83
+ raise ValueError('state must be text, an object, or a list of document parts')
84
+ def empty(spec):
85
+ if spec['type'] == 'noul':
86
+ return {'candidates': None} if 'candidates' in spec else {'noul': None}
87
+ return {'probabilities': None, 'answer': None}
88
+
89
+ def present(spec, dists):
90
+ if spec['type'] == 'noul':
91
+ if 'candidates' in spec:
92
+ return {'candidate_kind': 'entity' if spec['task'] == 'entity' else 'label', 'candidates': {c: float(d[0]) for c, d in zip(spec['candidates'], dists)}, 'candidate_ordering_scores': {c: float(max(d[0], 1 - d[0])) for c, d in zip(spec['candidates'], dists)}}
93
+ return {'noul': float(dists[0][0])}
94
+ p = dists[0]
95
+ keys = spec['keys']
96
+ out = {'probabilities': {k: float(v) for k, v in zip(keys, p)}, 'answer': keys[int(np.argmax(p))]}
97
+ out['choice'] = out['answer']
98
+ if spec['type'] == 'score':
99
+ out['score'] = float(np.dot(np.arange(len(p)), p))
100
+ out['legend'] = dict(zip(keys, spec['texts']))
101
+ elif spec['task'] == 'ordered':
102
+ out['ordered'] = True
103
+ return out
104
+
105
+ def decision(spec, shown):
106
+ if spec['type'] == 'noul':
107
+ if 'candidates' in spec:
108
+ return {c: p >= 0.5 for c, p in shown['candidates'].items()}
109
+ return shown['noul'] >= 0.5
110
+ return shown['answer']
111
+
mlx/src/solomon_mlx/_vendor/evidence.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Doccy Pty Ltd. Apache-2.0.
2
+ # Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md.
3
+ """Deterministic source references and explicit evidence interventions.
4
+
5
+ Offsets count Python Unicode code points, never bytes. Candidate ranking is lexical
6
+ and is labelled as such; only an independent model callback supplies support scores.
7
+ Interventions re-encode edited source; an existing KV state cannot prove removal.
8
+ """
9
+ import hashlib
10
+ import re
11
+ from pathlib import Path
12
+
13
+
14
+ def digest(text):
15
+ return hashlib.sha256(text.encode()).hexdigest()
16
+
17
+
18
+ def passages(text, max_chars=1200):
19
+ if not isinstance(text,str) or max_chars < 1:
20
+ raise ValueError('text and positive passage size required')
21
+ result=[]
22
+ # Cover every character, including whitespace; splitting does not normalize text.
23
+ start=0
24
+ while start < len(text):
25
+ limit=min(len(text),start+max_chars)
26
+ end=limit
27
+ if limit < len(text):
28
+ candidates=[m.end() for m in re.finditer(r'\n\s*\n|(?<=[.!?])\s+',text[start:limit])]
29
+ if candidates and candidates[-1] >= max_chars//2:end=start+candidates[-1]
30
+ result.append({'id':f'text:{start}:{end}','kind':'text','start':start,'end':end,
31
+ 'text':text[start:end],'source_sha256':digest(text)})
32
+ start=end
33
+ return result
34
+
35
+
36
+ def image_pages(paths):
37
+ return [{'id':f'page:{i+1}','kind':'image','page':i+1,'path':str(p),
38
+ 'source_sha256':hashlib.sha256(Path(p).read_bytes()).hexdigest()} for i,p in enumerate(paths)]
39
+
40
+
41
+ def validate_spans(text, spans):
42
+ ordered=sorted(spans,key=lambda s:(s['start'],s['end']))
43
+ last=0
44
+ for span in ordered:
45
+ start,end=span['start'],span['end']
46
+ if type(start) is not int or type(end) is not int or not 0<=start<end<=len(text) or start<last:
47
+ raise ValueError('invalid or overlapping evidence range')
48
+ if span.get('source_sha256',digest(text))!=digest(text) or span.get('text',text[start:end])!=text[start:end]:
49
+ raise ValueError('evidence no longer matches source')
50
+ last=end
51
+ return ordered
52
+
53
+
54
+ def rank_candidates(question, candidates, limit=8):
55
+ if limit<1:raise ValueError('positive candidate limit required')
56
+ tokens=set(re.findall(r'\w+',question.casefold()))
57
+ ranked=[]
58
+ for c in candidates:
59
+ words=set(re.findall(r'\w+',c.get('text','').casefold()))
60
+ ranked.append({**c,'candidate_score':len(words&tokens)/max(1,len(tokens)),
61
+ 'candidate_method':'lexical_overlap' if c['kind']=='text' else 'page_order'})
62
+ # Stable ties retain source order (page 2 must precede page 10).
63
+ return sorted(ranked,key=lambda c:-c['candidate_score'])[:limit]
64
+
65
+
66
+ def select(question, candidates, support_score, limit=8, minimum_support=.5):
67
+ """Callback receives only question+candidate, never benchmark gold."""
68
+ if not 0<=minimum_support<=1:raise ValueError('invalid support threshold')
69
+ results=[]
70
+ for candidate in rank_candidates(question,candidates,limit):
71
+ score=float(support_score(question,candidate))
72
+ if not 0<=score<=1:raise ValueError('finite support probability required')
73
+ results.append({**candidate,'support_score':score})
74
+ chosen=[r for r in results if r['support_score']>=minimum_support]
75
+ return {'evidence':chosen,'candidates':results,'verification':'model_support_only',
76
+ 'faithfulness_established':False,'no_support_found':not chosen}
77
+
78
+
79
+ def intervene(text, spans, question, decide):
80
+ """decide(document,question) must prefill each supplied document afresh.
81
+
82
+ All three actual calls are returned; agreement/disagreement is evidence, not a
83
+ guarantee that a passage is the unique cause of an answer.
84
+ """
85
+ spans=validate_spans(text,spans)
86
+ evidence='\n\n'.join(text[s['start']:s['end']] for s in spans)
87
+ pieces=[];start=0
88
+ for span in spans:
89
+ pieces.append(text[start:span['start']]);start=span['end']
90
+ pieces.append(text[start:]);removed=''.join(pieces)
91
+ full=decide(text,question)
92
+ only=decide(evidence,question)
93
+ removal=decide(removed,question)
94
+ return {'full':full,'evidence_only':only,'evidence_removed':removal,
95
+ 'evidence_only_agrees':only==full,'removal_changes_answer':removal!=full,
96
+ 'verification':'fresh_source_reencoding','source_sha256':digest(text),
97
+ 'evidence_sha256':digest(evidence),'removed_sha256':digest(removed),
98
+ 'calls':3,'input_characters':len(text)+len(evidence)+len(removed)}
99
+
100
+
101
+ def validate_pages(pages, selected):
102
+ """Validate ordered page manifests and selected immutable references."""
103
+ indexed = {}
104
+ for expected, page in enumerate(pages, 1):
105
+ if page.get('kind') != 'image' or type(page.get('page')) is not int or page['page'] != expected:
106
+ raise ValueError('image manifest must use consecutive source page IDs')
107
+ if page.get('id') != f'page:{expected}':
108
+ raise ValueError('image page ID does not match source position')
109
+ actual = hashlib.sha256(Path(page['path']).read_bytes()).hexdigest()
110
+ if page.get('source_sha256') != actual:
111
+ raise ValueError('image no longer matches source')
112
+ indexed[expected] = page
113
+ ids = []
114
+ for reference in selected:
115
+ number = reference.get('page')
116
+ if type(number) is not int or number not in indexed or number in ids:
117
+ raise ValueError('invalid or duplicate evidence page')
118
+ source = indexed[number]
119
+ if any(reference.get(key) != source[key] for key in ('id', 'kind', 'path', 'source_sha256')):
120
+ raise ValueError('evidence page no longer matches source manifest')
121
+ ids.append(number)
122
+ return [indexed[number] for number in sorted(ids)]
123
+
124
+
125
+ def intervene_pages(pages, selected, question, decide, text=''):
126
+ """Re-encode full, evidence-only and page-removed multimodal documents.
127
+
128
+ decide(text, image_paths, question) must create a fresh state each time and
129
+ accept an empty image list. Evidence-only has no accompanying source text;
130
+ removal retains all source text and unselected pages. This isolates page
131
+ evidence and makes text-only sufficiency a visible competing explanation.
132
+ Original page IDs are recorded because subset images are renumbered on input.
133
+ """
134
+ if not isinstance(text, str):
135
+ raise ValueError('source text must be a string')
136
+ selected = validate_pages(pages, selected)
137
+ selected_ids = {p['page'] for p in selected}
138
+ removed = [p for p in pages if p['page'] not in selected_ids]
139
+ calls = [(text, pages), ('', selected), (text, removed)]
140
+ answers = []
141
+ for source_text, source_pages in calls:
142
+ # Recheck all originals before each call: never silently mix revisions.
143
+ validate_pages(pages, selected)
144
+ answers.append(decide(source_text, [p['path'] for p in source_pages], question))
145
+ validate_pages(pages, selected)
146
+ full, only, removal = answers
147
+ return {'full': full, 'evidence_only': only, 'evidence_removed': removal,
148
+ 'evidence_only_agrees': only == full, 'removal_changes_answer': removal != full,
149
+ 'verification': 'fresh_source_reencoding', 'faithfulness_established': False,
150
+ 'source_text_sha256': digest(text),
151
+ 'source_pages': [{k: p[k] for k in ('id', 'page', 'source_sha256')} for p in pages],
152
+ 'evidence_page_ids': [p['page'] for p in selected],
153
+ 'removed_input_page_ids': [p['page'] for p in removed],
154
+ 'calls': 3, 'input_images': sum(len(p) for _, p in calls),
155
+ 'input_image_bytes': sum(Path(p['path']).stat().st_size for _, ps in calls for p in ps),
156
+ 'input_characters': 2 * len(text)}
mlx/src/solomon_mlx/_vendor/evidence_v3.py ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Doccy Pty Ltd. Apache-2.0.
2
+ # Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md.
3
+ """Evidence packages (v3): selected spans plus source-derived governing context, per question unit.
4
+
5
+ A package is a genuine subset of one source document: every range is an exact, validated slice
6
+ of the source (Python code-point offsets), rendered in source order with whitespace-only
7
+ separators. Nothing is paraphrased, summarised or generated. Context is attached by
8
+ question-independent document structure plus the unit's own selected spans and question text;
9
+ gold/authoring data is never an input.
10
+
11
+ Roles
12
+ evidence spans chosen by the selector (or, in oracle diagnostics, gold anchors). Span
13
+ precision/recall gates are computed on these only, exactly as before.
14
+ context ranges attached automatically, each with one or more reasons:
15
+ header document title block (identity of the source, author/compiler, date)
16
+ interpretation generic reading rules (scope6.retrieval.governing_context, unchanged regex)
17
+ locator container of an included span: message header line (date, author ->
18
+ recipient), minute number + heading, schedule/section heading, entry number
19
+ correction a withdrawal/rescission/deletion elsewhere that refers to the container of an
20
+ included statement and names the same subject and topic (or the exact entry)
21
+ withdrawn the statement(s) that an included withdrawal refers to, so the chain is
22
+ readable (the withdrawn text is marked by the withdrawal, not by us)
23
+ rule a general rule / band scale whose operative topic matches the unit question
24
+ (attached only when the unit has evidence)
25
+ exception the exception clause governing an included rule, or the rule an included
26
+ exception limits
27
+ definition the sentence defining a capitalised class term used by an included rule,
28
+ when it names the unit's subject
29
+ condition a statement about the unit's subject that bears on an included rule's
30
+ conditions or exception (>=2 shared content words beyond the question topic,
31
+ or one document-rare shared word)
32
+ convention the document's own reading conventions (sentences of the paragraphs that hold
33
+ generic reading rules) that the package needs: silence always; conflict and vocabulary when
34
+ there is evidence; removal when a withdrawal/deletion chain is present;
35
+ condition when a rule is present; exception when an exception is present
36
+
37
+ Size is measured as covered source characters / source characters and reported per package. A
38
+ package above the registered cap (default 0.30) sheds context in TRIM_ORDER (never evidence, rule,
39
+ exception, condition, correction or withdrawn context) and records what was trimmed.
40
+ """
41
+ import re
42
+ from .evidence import digest, validate_spans
43
+ from .retrieval import candidates, governing_context
44
+
45
+ SCHEMA = 'scope6-evidence-package-v3.1'
46
+ SIZE_CAP = .30
47
+ # Context dropped first when a package exceeds the size cap (evidence is never dropped).
48
+ TRIM_ORDER = ('interpretation', 'convention/silence', 'convention/vocabulary', 'convention/conflict', 'header', 'definition',
49
+ 'convention/condition', 'convention/exception', 'convention/removal', 'locator')
50
+ MONTHS = 'January|February|March|April|May|June|July|August|September|October|November|December'
51
+ _WORD = re.compile(r'\w+')
52
+ _CAP = re.compile(r"\b[A-Z][\w&'-]*")
53
+ _WITHDRAWAL = re.compile(r"withdr[ae]w|withdrawn|take back|disregard|rescind|retract|should not be relied|"
54
+ r"substituted|\bis deleted|\bdelete[sd]?\b|struck out|replaced by|no further effect", re.I)
55
+ _REF_DATE = re.compile(r'(?:message|wrote|written|letter|note)\D{0,14}?(\d{1,2} (?:' + MONTHS + r'))')
56
+ _REF_MINUTE = re.compile(r'\bminute (\d+)\b', re.I)
57
+ _REF_ENTRY = re.compile(r'\b[Ee]ntry (\d+) of [Ss]chedule (\d+)')
58
+ _MSG_HEAD = re.compile(r'Message \d+\. (\d{1,2} (?:' + MONTHS + r'))\.')
59
+ _MINUTE_HEAD = re.compile(r'(\d+)\.\s+[^.\n]{1,80}\.')
60
+ _SCHEDULE_HEAD = re.compile(r'SCHEDULE (\d+)\b')
61
+ _ENTRY_PREFIX = re.compile(r'[A-Z]?\d+(?:\.\d+)*\.?\s*')
62
+ _PERMISSION = re.compile(r'\bmay\b|permitted|authorised|entitled|leave to|allowed|cleared|confers?', re.I)
63
+ _RULE = re.compile(r"(?i:\b(?:any|each|every)\s+[a-z]+(?:\s+[a-z]+)?\s+(?:who|that)\b|\ban?\s+[a-z]+\s+who\b|general rule|standing rule|"
64
+ r"office rule|rule governing|\bif that\b|where one and the same|\b[a-z]+s who\b|\bwhere (?:an?|any|one)\b)|\b[A-Z][a-z]+ [A-Z][a-z]+s? (?:that|who)\b")
65
+ _SCALE = re.compile(r'\bbands?\b|\bscale\b|from lowest to highest|order of the levels|worked out from|fixed by that number|\bfewer than \d+', re.I)
66
+ _EXCEPTION = re.compile(r'does not apply|confers nothing on|is outside (?:clause|the rule)|not engaged|does not reach|switched off|'
67
+ r'disapplied|subject to (?:one|the) exception|nothing in the rule|save that|except (?:where|that|for)\b', re.I)
68
+ CONVENTIONS = {
69
+ 'removal': re.compile(r"withdr[ae]w|rescind|delet|substitut|struck out|take back|displace|disregard|\bspent\b|express(?:ly)? (?:withdrawal|rescission)", re.I),
70
+ 'condition': re.compile(r"\bconditions?\b|\blimbs?\b|requirements?\b|only part|part of the way|whole of it|each of its|all of its", re.I),
71
+ 'exception': re.compile(r"\bexcept", re.I),
72
+ 'conflict': re.compile(r"both (?:stand|remain|hold|are in force|left standing)|opposite (?:ways|things)|inconsistent|contradict|"
73
+ r"point opposite|not say which|not chosen between|this office does not say", re.I),
74
+ 'vocabulary': re.compile(r"interchangeabl|one and the same (?:permission|refusal)|are one (?:grant|refusal)|mean the same|same thing|words to like effect", re.I),
75
+ 'silence': re.compile(r"silen|absence of|unminuted|nothing has been decided|has not been said|undecided|left open", re.I)}
76
+ _DEFINES = re.compile(r'\bare\b|\bmeans\b|\bidentified\b|\binclude', re.I)
77
+ _STOP = {'this', 'that', 'these', 'those', 'with', 'under', 'which', 'what', 'does', 'file', 'correspondence', 'record', 'records',
78
+ 'recorded', 'minutes', 'agreement', 'bundle', 'messages', 'message', 'stand', 'stands', 'taking', 'reading', 'whole',
79
+ 'strength', 'position', 'open', 'given', 'have', 'been', 'applies', 'apply', 'entitled', 'allowed', 'liberty', 'free',
80
+ 'from', 'there', 'their', 'they', 'decisions', 'here', 'schedules', 'schedule', 'papers', 'office', 'shown', 'show',
81
+ 'shows', 'case', 'matters', 'terms', 'place', 'placed', 'about', 'question', 'whether', 'should', 'relied', 'either',
82
+ 'wrote', 'withdraw', 'withdrawn', 'passage', 'treat', 'nothing', 'follows', 'please', 'disregard', 'concerns', 'deals',
83
+ 'resolved', 'rescinded', 'much', 'decision', 'minute', 'further', 'effect', 'permission', 'permitted', 'refused',
84
+ 'barred', 'prohibited', 'authorised', 'cleared', 'leave', 'allows', 'allow', 'bars', 'refuses', 'grant', 'refusal',
85
+ 'entry', 'deleted', 'substituted', 'following', 'replaced', 'struck', 'committee', 'secretary', 'chair', 'principal'}
86
+ ROLES = ('evidence', 'context')
87
+ # Capitalised words that are never subject names (sentence openers, document furniture).
88
+ _NOT_NAMES = {'the', 'this', 'that', 'these', 'those', 'it', 'its', 'i', 'we', 'our', 'my', 'on', 'for', 'so', 'as', 'at', 'in', 'of',
89
+ 'please', 'treat', 'where', 'when', 'what', 'which', 'who', 'whether', 'any', 'each', 'every', 'no', 'nothing', 'there',
90
+ 'message', 'messages', 'entry', 'schedule', 'clause', 'minute', 'minutes', 'agreement', 'principal', 'resolved', 'note',
91
+ 'committee', 'secretary', 'chair', 'treasurer', 'register', 'records', 'record', 'permission', 'an', 'a', 'if', 'all',
92
+ 'both', 'neither', 'either', 'one', 'two', 'words', 'dates', 'decisions', 'rules', 'only', 'once', 'part', 'to', 'by',
93
+ 'read', 'do', 'what', 'should', 'with', 'from', 'under', 'after', 'before', 'reading', 'taking', 'is', 'has', 'have'}
94
+
95
+
96
+ def _words(s):
97
+ return set(_WORD.findall(s.casefold()))
98
+
99
+
100
+ def _topic(s, names=()):
101
+ low = {w for n in names for w in _words(n)}
102
+ return {w for w in _words(s) if len(w) >= 4 and w not in _STOP and w not in low and not w.isdigit()
103
+ and not re.fullmatch(MONTHS.casefold(), w)}
104
+
105
+
106
+ class Structure:
107
+ """Question-independent source structure: sentences, paragraphs, lines and containers."""
108
+
109
+ def __init__(self, text):
110
+ self.text = text
111
+ self.sentences = candidates(text)
112
+ self.paragraphs = []
113
+ start = 0
114
+ for m in list(re.finditer(r'\n\s*\n', text)) + [None]:
115
+ end = m.start() if m else len(text)
116
+ if text[start:end].strip():
117
+ self.paragraphs.append((start, end))
118
+ start = m.end() if m else len(text)
119
+ self.lines = [(m.start(), m.end()) for m in re.finditer(r'[^\n]+', text)]
120
+ df = {}
121
+ for s in self.sentences:
122
+ for w in _words(s['text']):
123
+ df[w] = df.get(w, 0) + 1
124
+ self.df = df
125
+ self.interpretation = [(s['start'], s['end']) for s in governing_context(text, self.sentences)]
126
+ # containers: message date -> paragraph; minute number -> paragraph; (schedule, entry) -> line
127
+ self.messages, self.minutes, self.entries = {}, {}, {}
128
+ for a, b in self.paragraphs:
129
+ first = text[a:b].split('\n', 1)[0]
130
+ m = _MSG_HEAD.match(first)
131
+ if m:
132
+ self.messages.setdefault(m.group(1), (a, b))
133
+ m = re.match(r'(\d+)\. ', text[a:b])
134
+ if m and '\n' not in text[a:b].strip():
135
+ self.minutes.setdefault(m.group(1), (a, b))
136
+ m = _SCHEDULE_HEAD.match(first)
137
+ if m:
138
+ for la, lb in self.lines:
139
+ if a <= la and lb <= b:
140
+ e = re.match(r'(\d+)\. ', text[la:lb])
141
+ if e:
142
+ self.entries[(m.group(1), e.group(1))] = (la, lb)
143
+ self.rules = [s for s in self.sentences if (_RULE.search(s['text']) and _PERMISSION.search(s['text'])) or _SCALE.search(s['text'])]
144
+ self.exceptions = [s for s in self.sentences if _EXCEPTION.search(s['text'])]
145
+ self.withdrawals = [s for s in self.sentences if _WITHDRAWAL.search(s['text'])]
146
+ # Reading conventions: sentences of the interpretation paragraphs (those holding a generic reading rule).
147
+ blocks = {self.paragraph_of(a) for a, _ in self.interpretation} - {None}
148
+ general = [s for s in self.sentences if self.paragraph_of(s['start']) in blocks and s not in self.rules]
149
+ self.conventions = {k: [(s['start'], s['end']) for s in general if rx.search(s['text'])] for k, rx in CONVENTIONS.items()}
150
+
151
+ def governed_rule(self, exception):
152
+ """The rule an exception limits: nearest preceding rule in the same paragraph (or the same sentence)."""
153
+ para = self.paragraph_of(exception['start'])
154
+ prior = [r for r in self.rules if r['start'] <= exception['start'] and para and para[0] <= r['start'] < para[1]
155
+ and not _SCALE.search(r['text'])]
156
+ return prior[-1] if prior else None
157
+
158
+ def names(self, s, limit=16):
159
+ """Capitalised tokens that are rare in this source (subject names); months/number words excluded."""
160
+ out = set()
161
+ for tok in _CAP.findall(s):
162
+ tok = re.sub(r"'s$", '', tok)
163
+ w = tok.casefold()
164
+ if re.fullmatch(MONTHS, tok) or len(w) < 3 or w in _NOT_NAMES or (tok.isupper() and len(tok) > 1):
165
+ continue
166
+ if self.df.get(w, 0) <= limit:
167
+ out.add(tok)
168
+ return out
169
+
170
+ def paragraph_of(self, pos):
171
+ for a, b in self.paragraphs:
172
+ if a <= pos < b:
173
+ return a, b
174
+ return None
175
+
176
+ def line_of(self, pos):
177
+ for a, b in self.lines:
178
+ if a <= pos < b:
179
+ return a, b
180
+ return None
181
+
182
+ def container(self, span):
183
+ """(kind, key) of the message/minute/entry holding a span, else None."""
184
+ for key, (a, b) in self.entries.items():
185
+ if a <= span['start'] < b:
186
+ return ('entry', key)
187
+ para = self.paragraph_of(span['start'])
188
+ if para is None:
189
+ return None
190
+ for key, rng in self.messages.items():
191
+ if rng == para:
192
+ return ('message', key)
193
+ for key, rng in self.minutes.items():
194
+ if rng == para:
195
+ return ('minute', key)
196
+ return None
197
+
198
+ def references(self, sentence):
199
+ """Containers a withdrawal-type sentence refers to."""
200
+ out = []
201
+ for m in _REF_ENTRY.finditer(sentence):
202
+ out.append(('entry', (m.group(2), m.group(1))))
203
+ for m in _REF_DATE.finditer(sentence):
204
+ out.append(('message', m.group(1)))
205
+ for m in _REF_MINUTE.finditer(sentence):
206
+ out.append(('minute', m.group(1)))
207
+ return out
208
+
209
+ def container_range(self, ref):
210
+ kind, key = ref
211
+ return {'entry': self.entries, 'message': self.messages, 'minute': self.minutes}[kind].get(key)
212
+
213
+ def locators(self, span):
214
+ """Header ranges that place a span in its container (never the span's own text)."""
215
+ text, out = self.text, []
216
+ para = self.paragraph_of(span['start'])
217
+ if para is None:
218
+ return out
219
+ a, b = para
220
+ nl = text.find('\n', a, b)
221
+ if nl >= 0 and span['start'] > nl:
222
+ out.append((a, nl)) # first line of a multi-line block: message/section/schedule heading
223
+ else:
224
+ m = _MINUTE_HEAD.match(text, a)
225
+ if m and m.end() <= span['start']:
226
+ out.append((a, m.end())) # numbered minute and its heading
227
+ line = self.line_of(span['start'])
228
+ if line and line[0] < span['start']:
229
+ prefix = text[line[0]:span['start']]
230
+ if len(prefix) <= 16 and _ENTRY_PREFIX.fullmatch(prefix):
231
+ out.append((line[0], span['start'])) # entry / clause number
232
+ return out
233
+
234
+ def sentence_ranges_in(self, rng):
235
+ a, b = rng
236
+ return [s for s in self.sentences if a <= s['start'] < b]
237
+
238
+
239
+ def _strip(text, a, b):
240
+ while a < b and text[a].isspace():
241
+ a += 1
242
+ while b > a and text[b - 1].isspace():
243
+ b -= 1
244
+ return a, b
245
+
246
+
247
+ def _merge(text, items):
248
+ """items: (start, end, role, reason) -> merged validated ranges; evidence role wins on overlap."""
249
+ cleaned = []
250
+ for a, b, role, reason in items:
251
+ a, b = _strip(text, a, b)
252
+ if a < b:
253
+ cleaned.append((a, b, role, reason))
254
+ cleaned.sort()
255
+ merged = []
256
+ for a, b, role, reason in cleaned:
257
+ if merged and a <= merged[-1]['end']:
258
+ m = merged[-1]
259
+ m['end'] = max(m['end'], b)
260
+ m['roles'].add(role)
261
+ m['reasons'].add(reason)
262
+ else:
263
+ merged.append({'start': a, 'end': b, 'roles': {role}, 'reasons': {reason}})
264
+ h = digest(text)
265
+ out = [{'id': f"text:{m['start']}:{m['end']}", 'kind': 'text', 'start': m['start'], 'end': m['end'],
266
+ 'text': text[m['start']:m['end']], 'source_sha256': h,
267
+ 'role': 'evidence' if 'evidence' in m['roles'] else 'context', 'reasons': sorted(m['reasons'])} for m in merged]
268
+ validate_spans(text, out)
269
+ return out
270
+
271
+
272
+ def render(text, spans):
273
+ """Source-order rendering; separators are whitespace only and mirror the source layout."""
274
+ parts, last = [], None
275
+ for s in sorted(spans, key=lambda s: s['start']):
276
+ if last is not None:
277
+ gap = text[last:s['start']]
278
+ parts.append('\n\n' if '\n\n' in gap or re.search(r'\n\s*\n', gap) else ('\n' if '\n' in gap else ' '))
279
+ parts.append(text[s['start']:s['end']])
280
+ last = s['end']
281
+ return ''.join(parts)
282
+
283
+
284
+ def build(text, question, evidence, *, subject=None, structure=None, options=None, cap=SIZE_CAP):
285
+ """One unit's evidence package.
286
+
287
+ evidence: selected (or gold, for oracle diagnostics) source spans for this unit.
288
+ subject: optional explicit subject string (entity name); otherwise rare capitalised question tokens.
289
+ """
290
+ opts = {'header': True, 'interpretation': True, 'locator': True, 'correction': True, 'withdrawn': True,
291
+ 'rule': True, 'exception': True, 'definition': True, 'condition': True, 'convention': True, **(options or {})}
292
+ st = structure or Structure(text)
293
+ evidence = validate_spans(text, [{k: s[k] for k in ('start', 'end')} for s in evidence]) if evidence else []
294
+ items = [(s['start'], s['end'], 'evidence', 'selected') for s in evidence]
295
+ subj = st.names(subject if subject else question, limit=10 ** 9 if subject else 16)
296
+ if subject:
297
+ subj |= {subject}
298
+ qtopic = _topic(question, subj)
299
+ rare = st.names(question)
300
+
301
+ def mentions(t):
302
+ return (subject in t) if subject else bool(st.names(t) & rare)
303
+ if opts['header'] and st.paragraphs:
304
+ a, b = st.paragraphs[0]
305
+ items.append((a, min(b, a + 400), 'context', 'header'))
306
+ if opts['interpretation']:
307
+ items += [(a, b, 'context', 'interpretation') for a, b in st.interpretation]
308
+ if evidence:
309
+ included = [dict(s) for s in evidence]
310
+ if opts['rule']:
311
+ for r in st.rules:
312
+ if len(_topic(r['text'], subj) & qtopic) >= (3 if 'Attribute:' in question else 2):
313
+ items.append((r['start'], r['end'], 'context', 'rule'))
314
+ included.append(r)
315
+ # Iterate twice: attached statements can themselves need locators/corrections.
316
+ for _ in range(3):
317
+ current = [{'start': a, 'end': b} for a, b, role, reason in items
318
+ if role == 'evidence' or reason in ('rule', 'exception', 'withdrawn', 'correction', 'definition', 'condition')]
319
+ spans = [s for s in st.sentences if any(max(s['start'], c['start']) < min(s['end'], c['end']) for c in current)]
320
+ for s in spans:
321
+ s_text = s['text']
322
+ if opts['exception'] and s in st.rules:
323
+ for e in st.exceptions:
324
+ g = st.governed_rule(e)
325
+ if g is not None and g['start'] == s['start']:
326
+ items.append((e['start'], e['end'], 'context', 'exception'))
327
+ if opts['exception'] and s in st.exceptions:
328
+ g = st.governed_rule(s)
329
+ if g is not None:
330
+ items.append((g['start'], g['end'], 'context', 'exception'))
331
+ if opts['definition'] and s in st.rules:
332
+ for term in set(re.findall(r'(?<=[a-z,;] )([A-Z][a-z]+ [A-Z][a-z]+?)s?\b', s_text)):
333
+ for d in st.sentences:
334
+ if term in d['text'] and _DEFINES.search(d['text']) and d['start'] < s['start'] and mentions(d['text']):
335
+ items.append((d['start'], d['end'], 'context', 'definition'))
336
+ if opts['condition'] and s in st.rules:
337
+ # Facts about this unit's subject that bear on the rule's conditions or its exception.
338
+ governing = [s] + [e for e in st.exceptions if (st.governed_rule(e) or {}).get('start') == s['start']]
339
+ words = set().union(*(_topic(g['text'], subj) for g in governing)) - qtopic
340
+ for t in st.sentences:
341
+ shared = _topic(t['text'], subj) & words
342
+ if t['start'] != s['start'] and mentions(t['text']) and (len(shared) >= 2 or any(st.df.get(w, 0) <= 3 for w in shared)):
343
+ items.append((t['start'], t['end'], 'context', 'condition'))
344
+ if opts['locator']:
345
+ items += [(a, b, 'context', 'locator') for a, b in st.locators(s)]
346
+ is_withdrawal = bool(_WITHDRAWAL.search(s_text))
347
+ if opts['withdrawn'] and is_withdrawal:
348
+ wnames = st.names(s_text)
349
+ for ref in st.references(s_text):
350
+ rng = st.container_range(ref)
351
+ if rng is None:
352
+ continue
353
+ if ref[0] == 'entry':
354
+ items.append((rng[0], rng[1], 'context', 'withdrawn'))
355
+ continue
356
+ for t in st.sentence_ranges_in(rng):
357
+ if t['start'] == s['start']:
358
+ continue
359
+ if st.names(t['text']) & wnames and _topic(t['text'], wnames) & _topic(s_text, wnames):
360
+ items.append((t['start'], t['end'], 'context', 'withdrawn'))
361
+ if opts['correction'] and not is_withdrawal:
362
+ where = st.container(s)
363
+ if where is None:
364
+ continue
365
+ snames = st.names(s_text)
366
+ for w in st.withdrawals:
367
+ if w['start'] == s['start'] or where not in st.references(w['text']):
368
+ continue
369
+ if where[0] == 'entry' or (st.names(w['text']) & snames and _topic(w['text'], snames) & _topic(s_text, snames)):
370
+ items.append((w['start'], w['end'], 'context', 'correction'))
371
+ if opts['convention']:
372
+ reasons = {r for _, _, _, r in items}
373
+ ev_text = ' '.join(text[a:b] for a, b, role, _ in items if role == 'evidence')
374
+ wanted = {'silence'}
375
+ if evidence:
376
+ wanted |= {'conflict', 'vocabulary'}
377
+ if reasons & {'correction', 'withdrawn'} or _WITHDRAWAL.search(ev_text):
378
+ wanted.add('removal')
379
+ if 'rule' in reasons or any(r['start'] < b and a < r['end'] for r in st.rules if not _SCALE.search(r['text'])
380
+ for a, b, role, _ in items if role == 'evidence'):
381
+ wanted.add('condition')
382
+ if 'exception' in reasons:
383
+ wanted.add('exception')
384
+ for kind in sorted(wanted):
385
+ items += [(a, b, 'context', 'convention/'+kind) for a, b in st.conventions[kind]]
386
+ spans = _merge(text, items);trimmed = []
387
+ def size(sp):return sum(x['end'] - x['start'] for x in sp) / len(text) if text else 0.
388
+ for reason in (TRIM_ORDER if cap is not None else ()):
389
+ if size(spans) <= cap:
390
+ break
391
+ keep = [it for it in items if it[3] != reason]
392
+ if len(keep) != len(items):
393
+ items = keep;trimmed.append(reason);spans = _merge(text, items)
394
+ rendered = render(text, spans)
395
+ covered = sum(s['end'] - s['start'] for s in spans)
396
+ return {'schema': SCHEMA, 'question': question, 'source_sha256': digest(text), 'text': rendered, 'spans': spans,
397
+ 'size_cap': cap, 'within_size_cap': cap is None or covered <= cap * len(text), 'trimmed_for_cap': trimmed,
398
+ 'evidence_ranges': [{'start': s['start'], 'end': s['end']} for s in evidence],
399
+ 'no_support_found': not evidence, 'source_characters': len(text), 'covered_characters': covered,
400
+ 'rendered_characters': len(rendered), 'source_fraction': covered / len(text) if text else 0.,
401
+ 'context_reasons': sorted({r for s in spans for r in s['reasons'] if r != 'selected'}),
402
+ 'faithfulness_established': False}
403
+
404
+
405
+ def union(text, packages):
406
+ """One document-level package: union of unit packages (used for whole-question rendering)."""
407
+ items = [(s['start'], s['end'], s['role'], r) for p in packages for s in p['spans'] for r in s['reasons']]
408
+ if not items:
409
+ return {'schema': SCHEMA, 'text': '', 'spans': [], 'source_fraction': 0., 'covered_characters': 0,
410
+ 'source_characters': len(text), 'rendered_characters': 0, 'source_sha256': digest(text)}
411
+ spans = _merge(text, items)
412
+ rendered = render(text, spans)
413
+ covered = sum(s['end'] - s['start'] for s in spans)
414
+ return {'schema': SCHEMA, 'text': rendered, 'spans': spans, 'source_fraction': covered / len(text),
415
+ 'covered_characters': covered, 'source_characters': len(text), 'rendered_characters': len(rendered),
416
+ 'source_sha256': digest(text)}
417
+
418
+
419
+ def with_pages(package, text, page_map):
420
+ """Attach renderer page IDs to every package span (scope6.sources.evidence_pages)."""
421
+ from scope6.sources import evidence_pages
422
+ out = dict(package)
423
+ out['spans'] = [{**s, 'pages': evidence_pages(text, [s], page_map)} for s in package['spans']]
424
+ out['pages'] = sorted({p for s in out['spans'] for p in s['pages']})
425
+ return out
mlx/src/solomon_mlx/_vendor/prompts.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Doccy Pty Ltd. Apache-2.0.
2
+ # Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md.
3
+ SYSTEM = 'You answer questions about the supplied document. Use only the document. Task instructions follow the document; follow them exactly.'
4
+
5
+ BOOLEAN_TASK = 'Task: classify the evidence for the question using only the document and its explicit rules. A = Yes only. B = No only. C = neither Yes nor No is established. D = both Yes and No are established. A missing fact is not a negative fact. Evidence about another person or subject does not contradict the queried one. Apply explicit time and replacement rules before deciding. Respond with exactly one letter: A, B, C, or D. Do not explain.'
6
+
7
+ CHOICE_TASK = 'Task: choose the single option that the document best supports. Respond with exactly one letter. Do not explain.'
8
+
9
+ def boolean_block(question):
10
+ return BOOLEAN_TASK + '\nQuestion: ' + question + '\nAnswer (one letter):'
11
+ LETTERS = 'ABCDEFGHIJ'
12
+
13
+ PAGE = '<|vision_start|><|image_pad|><|vision_end|>'
14
+
15
+ RESERVED = ['The document does not state this', 'The document gives conflicting answers']
16
+
17
+ SINGLE_R = 'Task: choose the single option that the document establishes as the answer. If the document does not establish any of the other listed answers, choose the option that says it does not state this. If the document establishes two different listed answers and does not say which prevails, choose the option that says it gives conflicting answers. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter. Do not explain.'
18
+
19
+ ORDERED_R = 'Task: the options form an ordered scale, lowest first, followed by two special options. Choose the single level that the document establishes. If the document does not establish any level, choose the option that says it does not state this. If it establishes two different levels and does not say which prevails, choose the option that says it gives conflicting answers. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter. Do not explain.'
20
+
21
+ SINGLE_S = CHOICE_TASK
22
+
23
+ ORDERED_S = 'Task: the options form an ordered scale, lowest first. Choose the single level that the document best supports. Respond with exactly one letter. Do not explain.'
24
+
25
+ SUFFICIENCY = 'Task: classify what the document establishes about the answer to the question, using only the document. A = it establishes exactly one of the listed answers. B = it does not establish any of the listed answers. C = it establishes two or more different listed answers and does not say which prevails. A missing fact is not a negative fact. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter: A, B, or C. Do not explain.'
26
+
27
+ LABEL = 'Task: decide whether the label applies, using only the document. A = the document establishes that it applies, and nothing in it establishes that it does not. B = the document establishes that it does not apply, and nothing in it establishes that it does. C = the document establishes neither. D = the document establishes both. A missing fact is not a negative fact. Evidence about another person or subject does not count. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter: A, B, C, or D. Do not explain.'
28
+
29
+ OPTION = 'Task: decide whether the proposed answer is correct, using only the document. A = the document establishes this answer, and nothing in it establishes a different one. B = the document establishes a different answer, or establishes that this one is wrong, and nothing in it establishes this one. C = the document establishes neither. D = the document establishes both this answer and a different one. A missing fact is not a negative fact. A replaced or withdrawn statement establishes nothing. Respond with exactly one letter: A, B, C, or D. Do not explain.'
30
+
31
+ def _lettered(options):
32
+ return '\n'.join((f'{LETTERS[i]}. {text}' for i, text in enumerate(options)))
33
+
34
+ def listwise_block(question, options, ordered=False, reserved=True):
35
+ """Caller options in caller order; with `reserved` the service appends the two reserved outcomes."""
36
+ if not 2 <= len(options) <= 8 or len(set(options)) != len(options):
37
+ raise ValueError('needs 2 to 8 distinct options')
38
+ shown = list(options) + (RESERVED if reserved else [])
39
+ task = (ORDERED_R if ordered else SINGLE_R) if reserved else ORDERED_S if ordered else SINGLE_S
40
+ return (task + '\nQuestion: ' + question + '\nOptions:\n' + _lettered(shown) + '\nAnswer (one letter):', len(shown))
41
+
42
+ def label_block(question, label):
43
+ return (LABEL + '\nQuestion: ' + question + '\nLabel: ' + label + '\nAnswer (one letter):', 4)
mlx/src/solomon_mlx/_vendor/retrieval.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Doccy Pty Ltd. Apache-2.0.
2
+ # Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md.
3
+ """Inference-only source candidates and explicitly labelled retrieval baselines."""
4
+ import re
5
+ from .evidence import digest,rank_candidates,validate_spans
6
+
7
+ def candidates(text):
8
+ """Source sentences retaining exact character ranges; no authoring labels."""
9
+ ends=[m.end() for m in re.finditer(r'(?<=[.!?])(?:[ \t]+|\n+)|\n\s*\n',text)]+[len(text)]
10
+ result=[];start=0
11
+ for end in ends:
12
+ if end>start and text[start:end].strip():
13
+ result.append({'id':f'text:{start}:{end}','kind':'text','start':start,'end':end,
14
+ 'text':text[start:end],'source_sha256':digest(text)})
15
+ start=end
16
+ return result
17
+
18
+ def governing_context(text,spans=None):
19
+ """Generic rule/retraction interpretation context, independent of question gold."""
20
+ spans=candidates(text) if spans is None else spans
21
+ pattern=re.compile(r'general rule|both limbs|same (?:person|courier|member|officer)|neither limb|missing requirement|silence|withdrawn passage|dates and seniority|a withdrawn|outside these|bands are|order of the levels|no.*precedence|each passage|a grant in force|an exception to a rule',re.I)
22
+ return [s for s in spans if pattern.search(s['text'])]
23
+
24
+ def merge(text,spans):
25
+ intervals=[]
26
+ for span in sorted(spans,key=lambda s:(s['start'],s['end'])):
27
+ a,b=span['start'],span['end']
28
+ if intervals and a<=intervals[-1][1]:intervals[-1]=(intervals[-1][0],max(b,intervals[-1][1]))
29
+ else:intervals.append((a,b))
30
+ result=[{'id':f'text:{a}:{b}','kind':'text','start':a,'end':b,'text':text[a:b],'source_sha256':digest(text)} for a,b in intervals]
31
+ return validate_spans(text,result)
32
+
33
+ def render_subset(text,spans,*,include_context=True):
34
+ selected=merge(text,list(spans)+(governing_context(text) if include_context else []))
35
+ return '\n\n'.join(s['text'] for s in selected),selected
36
+
37
+ _WORD=re.compile(r'\w+')
38
+ _SCALE=re.compile(r'\bbands?\b|\bscale\b|from lowest to highest|order of the levels',re.I)
39
+ POOL_V2={'name':'idf-rare-rule-v2','idf_limit':24,'rare_df':16,'rules':True}
40
+
41
+ def _words(s):return set(_WORD.findall(s.casefold()))
42
+
43
+ def rule_candidates(text,spans=None):
44
+ """Question-independent rule, exception, withdrawal-convention and scale passages."""
45
+ spans=candidates(text) if spans is None else spans
46
+ rules={s['id'] for s in governing_context(text,spans)}
47
+ return [s for s in spans if s['id'] in rules or _SCALE.search(s['text'])]
48
+
49
+ def candidate_pool(question,spans,text=None,*,idf_limit=24,rare_df=16,rules=True,**_):
50
+ """Bounded source-only pool: document-IDF top-k, every passage sharing a rare
51
+ question term (typically the subject's name), plus question-independent rules.
52
+
53
+ Candidate order is source order. candidate_score stays the plain lexical-overlap
54
+ fraction used by the frozen relevance-head feature, so heads remain comparable.
55
+ Gold is never an input.
56
+ """
57
+ import math
58
+ if idf_limit<1 or rare_df<0:raise ValueError('invalid candidate pool policy')
59
+ ranked=rank_candidates(question,spans,len(spans)) if spans else []
60
+ words=[_words(c['text']) for c in ranked];q=_words(question);n=len(ranked)
61
+ df={}
62
+ for ws in words:
63
+ for w in ws:df[w]=df.get(w,0)+1
64
+ idf=[sum(math.log((n+1)/(df[w]+.5)) for w in ws&q) for ws in words]
65
+ order=sorted(range(n),key=lambda i:(-idf[i],ranked[i]['start']))
66
+ reason={}
67
+ for i in order[:idf_limit]:reason.setdefault(ranked[i]['id'],'idf')
68
+ rare={w for w in q if df.get(w,0)<=rare_df}
69
+ for c,ws in zip(ranked,words):
70
+ if ws&rare:reason.setdefault(c['id'],'rare_term')
71
+ if rules:
72
+ for c in rule_candidates(text or '',ranked):reason.setdefault(c['id'],'rule')
73
+ return sorted(({**c,'pool_reason':reason[c['id']]} for c in ranked if c['id'] in reason),key=lambda c:c['start'])
74
+
75
+ _WITHDRAW=re.compile(r'withdr[ae]w|withdrawn|take back|disregard|rescind|retract|should not be relied|there is substituted|is deleted|expressly delete',re.I)
76
+ _REFERENCE=re.compile(r'(?:message of|wrote on|message dated) (\d{1,2} [A-Z][a-z]+)|minute (\d+)|[Ee]ntry (\d+) of [Ss]chedule (\d+)|clause ([\d.]+)|passage in my message')
77
+ _EXCEPTION=re.compile(r'does not apply to|subject to the exception|is an exception|except (?:where|that|for)\b',re.I)
78
+ _STOP={'this','that','these','those','with','under','which','what','does','file','correspondence','record','records','recorded',
79
+ 'minutes','agreement','bundle','messages','message','stand','stands','taking','reading','whole','strength','position',
80
+ 'open','given','have','been','applies','apply','entitled','allowed','liberty','free','from','there','their','they',
81
+ 'decisions','here','schedules','schedule','papers','office','shown','show','shows','case','matters','terms','place','placed'}
82
+ DEPENDENCIES_V1={'name':'withdrawal-exception-v1','withdrawals':True,'exceptions':True,'prune_withdrawn':False,'rare_df':16}
83
+
84
+ def _topic(words,exclude):
85
+ return {w for w in words if len(w)>=4 and w not in _STOP and w not in exclude and not w.isdigit()}
86
+
87
+ def _withdrawn_block(text,passage):
88
+ """Source range that a withdrawal refers to (dated message or numbered minute); None if not resolvable."""
89
+ m=_REFERENCE.search(passage)
90
+ if not m:return None
91
+ if m.group(1):head=re.search(r'Message \d+\. '+re.escape(m.group(1))+r'\.',text)
92
+ elif m.group(2):head=re.search(r'(?:^|\n)'+m.group(2)+r'\. ',text)
93
+ else:return None
94
+ if not head:return None
95
+ end=text.find('\n\n',head.end());return head.start(),(len(text) if end<0 else end)
96
+
97
+ def dependency_expand(question,spans,selected_ids,text,*,withdrawals=True,exceptions=True,prune_withdrawn=False,rare_df=16,**_):
98
+ """Complete a unit's selected evidence with its withdrawal/exception dependencies.
99
+
100
+ Source text and the already-selected passages only; never gold. Units with no
101
+ selection are unchanged, so no-positive-support decisions are preserved.
102
+ - withdrawal: a passage with a withdrawal verb AND an explicit reference (message/minute/
103
+ entry/clause) that names the question's subject (a rare capitalised name, also present in a
104
+ selected passage) and its most specific in-source topic word;
105
+ - exception: an exception clause immediately following a selected passage;
106
+ - prune_withdrawn (optional): drop selected passages inside the dated message / numbered
107
+ minute a kept withdrawal refers to, when they share its subject and a topic word.
108
+ Returns {'keep','added','pruned'} as candidate id lists in source order.
109
+ """
110
+ selected=[s for s in spans if s['id'] in set(selected_ids)]
111
+ if not selected:return {'keep':[],'added':[],'pruned':[]}
112
+ words=[_words(s['text']) for s in spans];df={}
113
+ for ws in words:
114
+ for w in ws:df[w]=df.get(w,0)+1
115
+ # Subject: capitalised non-initial question tokens (names) that are rare in the source.
116
+ names=re.findall(r'(?<!^)(?<![.?!] )\b[A-Z][\w&\'-]*',question.strip())
117
+ subject={w for n in names for w in _words(n) if len(w)>=3 and df.get(w,0)<=rare_df}
118
+ q=_words(question);present=[w for w in _topic(q,subject) if df.get(w,0)]
119
+ # The withdrawal must name the question's most specific in-source topic word (e.g. the attribute).
120
+ topic={min(present,key=lambda w:(df[w],w))} if present else set()
121
+ chosen={s['id'] for s in selected};sel_words=set().union(*(_words(s['text']) for s in selected));added=[]
122
+ for i,(s,ws) in enumerate(zip(spans,words)):
123
+ if s['id'] in chosen:continue
124
+ if (withdrawals and _WITHDRAW.search(s['text']) and _REFERENCE.search(s['text'])
125
+ and ws&subject&sel_words and ws&topic):added.append(s['id']);continue
126
+ if exceptions and i>0 and spans[i-1]['id'] in chosen and _EXCEPTION.search(s['text']):added.append(s['id'])
127
+ keep=chosen|set(added);pruned=[]
128
+ if prune_withdrawn:
129
+ for s,ws in zip(spans,words):
130
+ if s['id'] not in keep or not _WITHDRAW.search(s['text']):continue
131
+ block=_withdrawn_block(text,s['text'])
132
+ if block is None:continue
133
+ key=ws&subject;about=_topic(ws,subject)
134
+ for t,tw in zip(spans,words):
135
+ if (t['id'] in keep and t['id']!=s['id'] and block[0]<=t['start'] and t['end']<=block[1]+2
136
+ and tw&key and tw&about and not _WITHDRAW.search(t['text'])):pruned.append(t['id'])
137
+ order=[s['id'] for s in spans];pruned=set(pruned)
138
+ return {'keep':[i for i in order if i in keep and i not in pruned],'added':[i for i in order if i in set(added)],
139
+ 'pruned':[i for i in order if i in pruned]}
140
+
141
+ def lexical_select(text,questions,limit=4):
142
+ source=candidates(text);chosen={}
143
+ for q in questions:
144
+ for candidate in rank_candidates(q,source,limit):
145
+ if candidate['candidate_score']>0:chosen[candidate['id']]=candidate
146
+ return {'evidence':sorted(chosen.values(),key=lambda c:c['start']),
147
+ 'method':'lexical_overlap','verification':'retrieval_only','faithfulness_established':False}
148
+
149
+ def remove(text,spans):
150
+ spans=merge(text,spans);cursor=0;parts=[]
151
+ for s in spans:parts.append(text[cursor:s['start']]);cursor=s['end']
152
+ parts.append(text[cursor:]);return ''.join(parts)
mlx/src/solomon_mlx/_vendor/semantics.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Doccy Pty Ltd. Apache-2.0.
2
+ # Adapted from pinned Solomon v1.1; see NOTICE and MODIFICATIONS.md.
3
+ """Scope 9 answer semantics (docs/plans/2026-09-20-scope9-noul-final-refinement.md §1).
4
+
5
+ Yes/no, entity and multi-label candidates are Nouls: one probability P(yes). Gold yes only when the
6
+ document clearly establishes it; not stated and conflicting are no. Choice (single, ordered) is a
7
+ distribution over the listed options only; reserved-gold items have no Scope 9 target.
8
+
9
+ Works for both readouts: four_collapsed (four-state letter logits A/B/C/D, collapsed) and two_letter (A/B).
10
+ """
11
+ import numpy as np
12
+
13
+ YES, NO, NOT_STATED, CONFLICTING = 0, 1, 2, 3
14
+
15
+
16
+ def softmax(x, t=1.0):
17
+ z = np.asarray(x, np.float64) / t
18
+ z = z - z.max()
19
+ e = np.exp(z)
20
+ return e / e.sum()
21
+
22
+
23
+ def noul_gold(gold4):
24
+ """Four-state (or two-state) gold -> 1 for yes, 0 for no."""
25
+ return int(int(gold4) == YES)
26
+
27
+
28
+ def noul_logit(letter_logits):
29
+ """Binary log-odds z = log P(yes)/P(no) of a Noul branch: letter A against everything else."""
30
+ logits = np.asarray(letter_logits, np.float64)
31
+ if len(logits) not in (2, 4):
32
+ raise ValueError(f'Noul branch must have 2 or 4 letters, got {len(logits)}')
33
+ rest = logits[1:] - logits[1:].max()
34
+ return float(logits[YES] - (logits[1:].max() + np.log(np.exp(rest).sum())))
35
+
36
+
37
+ def p_yes(letter_logits, t=1.0):
38
+ """P(yes) from a Noul branch: letter A of a 2-letter (two_letter) or 4-state (four_collapsed) readout.
39
+
40
+ Temperature applies to the COLLAPSED binary logit, not to the letters: a Noul is a binary unit whose
41
+ 'no' mass may be spread over several reserved letters, so p_yes(t) = sigmoid(z/t) with z = noul_logit.
42
+ At t = 1 this is exactly softmax over the letters at A (the two forms only differ once t != 1, where the
43
+ letterwise form would decay toward 1/len(letters) instead of toward 1/2). scope9.qualification.p_yes and
44
+ abstention_refit/readout.py fit and evaluate the collapsed form, so the serving path must match it.
45
+ """
46
+ logits = np.asarray(letter_logits, np.float64)
47
+ if len(logits) not in (2, 4):
48
+ raise ValueError(f'Noul branch must have 2 or 4 letters, got {len(logits)}')
49
+ if t == 1.0:
50
+ return float(softmax(logits)[YES])
51
+ z = noul_logit(logits) / float(t)
52
+ return float(1.0 / (1.0 + np.exp(-z))) if z > -700 else 0.0
53
+
54
+
55
+ def noul_confidence(p):
56
+ return max(p, 1.0 - p)
57
+
58
+
59
+ def listed_gold(gold, n_options):
60
+ """Listed option index, or None when the old gold was a reserved slot (not stated / none-of-listed / conflicting)."""
61
+ return int(gold) if isinstance(gold, (int, np.integer)) and 0 <= int(gold) < n_options else None
62
+
63
+
64
+ def listed_probs(letter_logits, n_options, t=1.0):
65
+ """Choice distribution over the listed options only (reserved slots, if present, are discarded)."""
66
+ return softmax(np.asarray(letter_logits, np.float64)[:n_options], t)
67
+
68
+
69
+ def complement_deviation(p, p_negated):
70
+ """G3a under Scope 9: a statement and its negation should sum to 1."""
71
+ return abs(p - (1.0 - p_negated))
mlx/src/solomon_mlx/api.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Public document-state API and four-state answer semantics."""
2
+
3
+ import copy
4
+ import json
5
+ import math
6
+ from pathlib import Path
7
+
8
+ from ._vendor.contract import _state_parts, decision, parse_questions, present
9
+ from ._vendor.prompts import boolean_block, label_block, listwise_block
10
+ from ._vendor.semantics import listed_probs, p_yes
11
+ from .artifacts import digest, sha256
12
+
13
+ TASKS = ("boolean", "single", "ordered", "multilabel", "entity")
14
+
15
+
16
+ def branches(spec):
17
+ task, req = spec["task"], spec["request"]
18
+ if task == "boolean":
19
+ return [(boolean_block(req["question"]), 4, "boolean/state4")]
20
+ if task in ("single", "ordered"):
21
+ block, width = listwise_block(
22
+ req["question"], req["options"], ordered=task == "ordered", reserved=task == "single"
23
+ )
24
+ return [(block, width, task + ("/choiceR" if task == "single" else "/choiceS"))]
25
+ if task == "entity":
26
+ return [
27
+ (boolean_block(req["template"].replace("{entity}", c)), 4, "entity/state4")
28
+ for c in req["entities"]
29
+ ]
30
+ return [(*label_block(req["question"], c), "multilabel/state4") for c in req["labels"]]
31
+
32
+
33
+ def distributions(spec, rows, temperature):
34
+ if spec["task"] in ("boolean", "entity", "multilabel"):
35
+ values = [p_yes(r["letter_logits"], temperature) for r in rows]
36
+ return [[p, 1 - p] for p in values]
37
+ return [listed_probs(rows[0]["letter_logits"], len(spec["texts"]), temperature).tolist()]
38
+
39
+
40
+ def ordering_score(values):
41
+ """Product of per-unit top probabilities; not a calibrated joint probability."""
42
+ if not values:
43
+ raise ValueError("At least one answer unit is required")
44
+ for p in values:
45
+ if len(p) < 2 or not all(math.isfinite(v) and v >= 0 for v in p) or abs(sum(p) - 1) > 1e-6:
46
+ raise ValueError("Invalid answer distribution")
47
+ return math.prod(max(p) for p in values)
48
+
49
+
50
+ class DocumentState:
51
+ def __init__(self, owner, data):
52
+ self._owner, self._data, self.closed = owner, data, False
53
+ self.image_hashes = {p["image"]: sha256(p["image"]) for p in data["parts"] if "image" in p}
54
+
55
+ @property
56
+ def prefix_tokens(self):
57
+ self._check()
58
+ return len(self._data["prefix_ids"])
59
+
60
+ def _check(self):
61
+ if self.closed:
62
+ raise ValueError("Document state is closed")
63
+ if any(sha256(path) != value for path, value in self.image_hashes.items()):
64
+ raise ValueError("Document image changed after prefill")
65
+
66
+ def save(self, path):
67
+ """Save a source-bound replay recipe, never pickle executable cache objects."""
68
+ self._check()
69
+ body = {
70
+ "format": "solomon-mlx-replay-v1",
71
+ "runtime": self._owner.identity["fingerprint"],
72
+ "parts": self._data["parts"],
73
+ "image_hashes": self.image_hashes,
74
+ "prefix_ids_sha256": digest(self._data["prefix_ids"]),
75
+ }
76
+ Path(path).write_text(json.dumps({**body, "sha256": digest(body)}, indent=2))
77
+
78
+ def close(self):
79
+ with self._owner.engine.lock:
80
+ self._data.clear()
81
+ self.closed = True
82
+
83
+ def __enter__(self):
84
+ self._check()
85
+ return self
86
+
87
+ def __exit__(self, *args):
88
+ self.close()
89
+
90
+
91
+ class Solomon:
92
+ @classmethod
93
+ def load(
94
+ cls,
95
+ model_dir,
96
+ profile="quality",
97
+ *,
98
+ chunk_size=2048,
99
+ max_tokens=40960,
100
+ page_selector=None,
101
+ calibration=None,
102
+ ):
103
+ if profile != "quality":
104
+ raise ValueError("Only full BF16 quality is implemented; quantization is secondary")
105
+ from .engine import Engine
106
+
107
+ return cls(
108
+ Engine(model_dir, chunk_size=chunk_size, max_tokens=max_tokens),
109
+ page_selector=page_selector,
110
+ calibration=calibration,
111
+ )
112
+
113
+ def __init__(self, engine, *, page_selector=None, calibration=None):
114
+ self.engine, self.identity, self.page_selector = engine, engine.identity, page_selector
115
+ self.temperatures = dict.fromkeys(TASKS, 1.0)
116
+ self.calibration_status = "uncalibrated"
117
+ if calibration is not None:
118
+ artifact = json.loads(Path(calibration).read_text())
119
+ payload = {k: v for k, v in artifact.items() if k != "sha256"}
120
+ if (
121
+ artifact.get("sha256") != digest(payload)
122
+ or artifact["runtime"] != self.identity["fingerprint"]
123
+ ):
124
+ raise ValueError("Calibration checksum or MLX runtime identity mismatch")
125
+ temps = artifact["temperatures"]
126
+ if set(temps) != set(TASKS) or any(
127
+ isinstance(v, bool)
128
+ or not isinstance(v, (int, float))
129
+ or not math.isfinite(v)
130
+ or not 0 < v <= 20
131
+ for v in temps.values()
132
+ ):
133
+ raise ValueError("Invalid temperatures")
134
+ self.temperatures, self.calibration_status = temps, "profile_fitted"
135
+
136
+ def prefill(self, document):
137
+ parts = copy.deepcopy(_state_parts(document))
138
+ if not parts:
139
+ parts = [{"text": ""}]
140
+ for p in parts:
141
+ if not isinstance(p, dict) or set(p) not in ({"text"}, {"image"}):
142
+ raise ValueError("Each document part must contain only text or image")
143
+ if "text" in p and not isinstance(p["text"], str):
144
+ raise ValueError("Text parts must be strings")
145
+ if "image" in p:
146
+ p["image"] = str(Path(p["image"]).resolve(strict=True))
147
+ hashes = {p["image"]: sha256(p["image"]) for p in parts if "image" in p}
148
+ state = DocumentState(self, self.engine.prefill(parts))
149
+ if state.image_hashes != hashes:
150
+ state.close()
151
+ raise ValueError("Image changed while document was being prefilled")
152
+ return state
153
+
154
+ def replay(self, path):
155
+ body = json.loads(Path(path).read_text())
156
+ expected = body.pop("sha256")
157
+ if (
158
+ digest(body) != expected
159
+ or body["format"] != "solomon-mlx-replay-v1"
160
+ or body["runtime"] != self.identity["fingerprint"]
161
+ ):
162
+ raise ValueError("Replay checksum or runtime mismatch")
163
+ if any(sha256(p) != h for p, h in body["image_hashes"].items()):
164
+ raise ValueError("Replay image changed")
165
+ state = self.prefill(body["parts"])
166
+ if digest(state._data["prefix_ids"]) != body["prefix_ids_sha256"]:
167
+ state.close()
168
+ raise ValueError("Replay tokenization differs")
169
+ return state
170
+
171
+ def _answer(self, state, spec, execution="cached"):
172
+ rows = [self.engine.ask(state._data, b, n, h, execution=execution) for b, n, h in branches(spec)]
173
+ dists = distributions(spec, rows, self.temperatures[spec["task"]])
174
+ return {
175
+ **present(spec, dists),
176
+ "ordering_score": ordering_score(dists),
177
+ "temperature": self.temperatures[spec["task"]],
178
+ }, rows
179
+
180
+ def decide(
181
+ self,
182
+ *,
183
+ state,
184
+ questions,
185
+ evidence="support",
186
+ evidence_max_calls=64,
187
+ execution="cached",
188
+ diagnostics=False,
189
+ ):
190
+ if not isinstance(state, DocumentState) or state._owner is not self:
191
+ raise ValueError("State belongs to a different model instance")
192
+ if evidence not in ("none", "support", "sufficiency", "removal"):
193
+ raise ValueError("Invalid evidence level")
194
+ if type(evidence_max_calls) is not int or not 0 <= evidence_max_calls <= 512:
195
+ raise ValueError("Invalid evidence call budget")
196
+ specs = parse_questions(questions)
197
+ with self.engine.lock:
198
+ state._check()
199
+ answers, usage = {}, {"branches": 0, "input_tokens": 0, "evidence_calls": 0}
200
+ for spec in specs:
201
+ answer, rows = self._answer(state, spec, execution)
202
+ body = self._evidence(state, spec, answer, evidence, evidence_max_calls)
203
+ answer.update(
204
+ evidence=body["references"], evidence_status=body["status"], evidence_detail=body
205
+ )
206
+ if diagnostics:
207
+ answer["branches"] = rows
208
+ answers[spec["id"]] = answer
209
+ usage["branches"] += len(rows)
210
+ usage["input_tokens"] += sum(
211
+ r["branch_tokens"] if execution == "cached" else r["prompt_tokens"] for r in rows
212
+ )
213
+ usage["evidence_calls"] += body.get("calls", 0)
214
+ return {
215
+ "answers": answers,
216
+ "usage": usage,
217
+ "runtime": self.identity,
218
+ "calibration_status": self.calibration_status,
219
+ "answer_policy": "always_answers",
220
+ }
221
+
222
+ def _fresh(self, document, spec):
223
+ with self.prefill(document) as state:
224
+ answer, _ = self._answer(state, spec)
225
+ return decision(spec, answer)
226
+
227
+ def _evidence(self, state, spec, answer, level, budget):
228
+ from ._vendor import evidence_v3 as v3
229
+ from ._vendor.evidence import image_pages, validate_pages, validate_spans
230
+ from ._vendor.retrieval import lexical_select, remove
231
+
232
+ body = {
233
+ "references": [],
234
+ "status": "not_requested",
235
+ "calls": 0,
236
+ "verification": "none",
237
+ "faithfulness_established": False,
238
+ }
239
+ if level == "none":
240
+ return body
241
+ parts, req = state._data["parts"], spec["request"]
242
+ task = spec["task"]
243
+ if task == "entity":
244
+ questions = [req["template"].replace("{entity}", c) for c in req["entities"]]
245
+ elif task == "multilabel":
246
+ questions = [req["question"] + " Label: " + c for c in req["labels"]]
247
+ else:
248
+ questions = [req["question"] + (" " + " ".join(req["options"]) if "options" in req else "")]
249
+ images = [p["image"] for p in parts if "image" in p]
250
+ needed = (1 if images else len(questions)) if level in ("sufficiency", "removal") else 0
251
+ needed += int(level == "removal")
252
+ if needed > budget:
253
+ return {**body, "status": "budget_exhausted", "required_calls": needed}
254
+ baseline = decision(spec, answer)
255
+ if images:
256
+ if self.page_selector is None:
257
+ return {**body, "status": "unsupported_page_selector", "pages_available": len(images)}
258
+ pages = image_pages(images)
259
+ selector = self.page_selector
260
+ plan = None
261
+ if hasattr(selector, "plan"):
262
+ plan = selector.plan(
263
+ pages, questions, **({"task": task} if getattr(selector, "task_aware", False) else {})
264
+ )
265
+ if type(plan.get("calls")) is not int or plan["calls"] < 0:
266
+ raise ValueError("Invalid page selector call estimate")
267
+ if needed + plan["calls"] > budget:
268
+ return {**body, "status": "budget_exhausted", "required_calls": needed + plan["calls"]}
269
+ selection = (
270
+ selector.execute(plan) if plan is not None else selector(copy.deepcopy(pages), questions)
271
+ )
272
+ calls = selection.get("cost", {}).get("calls", 0)
273
+ if calls != (plan["calls"] if plan is not None else 0):
274
+ raise ValueError("Page selector exceeded its declared call budget")
275
+ refs = validate_pages(pages, selection["evidence"])
276
+ body["calls"] = calls
277
+ selected = {r["page"] for r in refs}
278
+ page, remainder = 0, []
279
+ for part in parts:
280
+ if "image" in part:
281
+ page += 1
282
+ if page in selected:
283
+ continue
284
+ remainder.append(part)
285
+ subsets = [([{"image": r["path"]} for r in refs], spec)]
286
+ else:
287
+ text = "".join(p["text"] for p in parts)
288
+ selection = lexical_select(text, questions)
289
+ refs = validate_spans(text, selection["evidence"])
290
+ structure = v3.Structure(text)
291
+ packages, subsets = [], []
292
+ for i, q in enumerate(questions):
293
+ subject = req["entities"][i] if task == "entity" else None
294
+ package = v3.build(text, q, refs, subject=subject, structure=structure)
295
+ unit = copy.deepcopy(spec)
296
+ if "candidates" in spec:
297
+ candidate = spec["candidates"][i]
298
+ unit["candidates"] = [candidate]
299
+ unit["request"]["entities" if task == "entity" else "labels"] = [candidate]
300
+ subsets.append((package["text"], unit))
301
+ packages.append({k: v for k, v in package.items() if k != "text"})
302
+ body["packages"] = packages
303
+ remainder = remove(text, refs)
304
+ body.update(
305
+ references=refs, status="found" if refs else "no_support_found", verification="retrieval_only"
306
+ )
307
+ if level in ("sufficiency", "removal"):
308
+ predictions = [self._fresh(doc, unit) for doc, unit in subsets]
309
+ assembled = (
310
+ {k: v for d in predictions for k, v in d.items()}
311
+ if "candidates" in spec and not images
312
+ else predictions[0]
313
+ )
314
+ body["evidence_only"] = {"prediction": assembled, "agrees_with_full": assembled == baseline}
315
+ body["calls"] += len(subsets)
316
+ body["verification"] = "fresh_source_reencoding"
317
+ if level == "removal":
318
+ removed = self._fresh(remainder, spec)
319
+ body["evidence_removed"] = {"prediction": removed, "agrees_with_full": removed == baseline}
320
+ body["calls"] += 1
321
+ return body
mlx/src/solomon_mlx/artifacts.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Artifact verification and distinct MLX runtime identities."""
2
+
3
+ import hashlib
4
+ import json
5
+ from importlib.metadata import version
6
+ from pathlib import Path
7
+
8
+ SOLOMON_REVISION = "5c0a4a82ddaeca6da2e3013f7045a8196c86957d"
9
+ BASE_REVISION = "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0"
10
+ ADAPTER_SHA = "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca"
11
+ HEADS_SHA = "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f"
12
+
13
+
14
+ def sha256(path):
15
+ h = hashlib.sha256()
16
+ with Path(path).open("rb") as f:
17
+ for block in iter(lambda: f.read(8 << 20), b""):
18
+ h.update(block)
19
+ return h.hexdigest()
20
+
21
+
22
+ def digest(value):
23
+ return hashlib.sha256(
24
+ json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
25
+ ).hexdigest()
26
+
27
+
28
+ def verify_release(root):
29
+ root = Path(root)
30
+ manifest = json.loads((root / "MANIFEST.json").read_text())
31
+ checked = {}
32
+ for row in manifest["files"]:
33
+ rel = row["destination"]
34
+ path = (root / rel).resolve()
35
+ if not path.is_relative_to(root.resolve()):
36
+ raise ValueError("Manifest path escapes source directory")
37
+ expected = row.get("staged_sha256")
38
+ size = row.get("staged_bytes")
39
+ actual = sha256(path)
40
+ if expected and actual != expected:
41
+ raise ValueError(f"Source checksum mismatch: {rel}")
42
+ if size is not None and path.stat().st_size != size:
43
+ raise ValueError(f"Source size mismatch: {rel}")
44
+ checked[rel] = actual
45
+ return checked
46
+
47
+
48
+ def code_identity():
49
+ root = Path(__file__).parent
50
+ return digest({str(p.relative_to(root)): sha256(p) for p in sorted(root.rglob("*.py"))})
51
+
52
+
53
+ def runtime_identity(binding, *, chunk_size=2048, max_tokens=40960):
54
+ import platform
55
+
56
+ import mlx.core as mx
57
+
58
+ value = {
59
+ "backend": "mlx-metal",
60
+ "contract": "solomon-mlx-v1",
61
+ "source_contract": "solomon-v1",
62
+ "profile": binding["profile"],
63
+ "chunk_size": chunk_size,
64
+ "max_tokens": max_tokens,
65
+ "metal_device": mx.device_info(),
66
+ "macos_version": platform.mac_ver()[0],
67
+ "model_binding": digest(binding),
68
+ "code_sha256": code_identity(),
69
+ "versions": {p: version(p) for p in ("mlx", "mlx-vlm", "transformers", "numpy", "pillow")},
70
+ "placement": "question",
71
+ "lora_scale": 2.0,
72
+ "answer_projection": "trained-semantic-head-float32",
73
+ "recurrence": "mlx-float32",
74
+ "solomon_revision": SOLOMON_REVISION,
75
+ "base_revision": BASE_REVISION,
76
+ }
77
+ return {**value, "fingerprint": digest(value)}
mlx/src/solomon_mlx/budget.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fail-closed local cost reservations for serialized Modal reference jobs."""
2
+
3
+ import fcntl
4
+ import json
5
+ import math
6
+ import os
7
+ import time
8
+ import uuid
9
+ from contextlib import contextmanager
10
+ from pathlib import Path
11
+
12
+
13
+ class Budget:
14
+ def __init__(self, path, cap=250.0):
15
+ self.path, self.cap = Path(path), cap
16
+ if not math.isfinite(cap) or not 0 < cap <= 250:
17
+ raise ValueError("The authorized cap is at most US$250")
18
+
19
+ @contextmanager
20
+ def reserve(self, amount, label):
21
+ if not math.isfinite(amount) or amount <= 0:
22
+ raise ValueError("Positive finite reservation required")
23
+ self.path.parent.mkdir(parents=True, exist_ok=True)
24
+ with self.path.with_suffix(".lock").open("a") as lock:
25
+ # Held until the synchronous remote call finishes. Refuse concurrent jobs.
26
+ try:
27
+ fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
28
+ except BlockingIOError:
29
+ raise RuntimeError("Another reference job already owns the GPU budget") from None
30
+ ledger = (
31
+ json.loads(self.path.read_text()) if self.path.exists() else {"cap": self.cap, "entries": []}
32
+ )
33
+ if ledger["cap"] != self.cap:
34
+ raise ValueError("Budget cap changed")
35
+ if any(e["status"] == "reserved" for e in ledger["entries"]):
36
+ raise RuntimeError(
37
+ "Unresolved prior dispatch: reconcile its remote status before another GPU job"
38
+ )
39
+ if any(
40
+ type(e.get("reserved_usd")) not in (int, float)
41
+ or not math.isfinite(e["reserved_usd"])
42
+ or e["reserved_usd"] <= 0
43
+ or e.get("status") not in ("reserved", "completed_conservative_charge")
44
+ for e in ledger["entries"]
45
+ ):
46
+ raise ValueError("Corrupt budget ledger")
47
+ used = sum(e["reserved_usd"] for e in ledger["entries"])
48
+ if used + amount > self.cap:
49
+ raise RuntimeError("Modal budget exhausted before dispatch")
50
+ entry = {
51
+ "id": uuid.uuid4().hex,
52
+ "label": label,
53
+ "reserved_usd": amount,
54
+ "status": "reserved",
55
+ "time": time.time(),
56
+ }
57
+ ledger["entries"].append(entry)
58
+ self._write(ledger)
59
+ # If interrupted, leave the reservation unresolved.
60
+ yield entry
61
+ entry["status"] = "completed_conservative_charge"
62
+ entry["completed"] = time.time()
63
+ self._write(ledger)
64
+
65
+ def _write(self, value):
66
+ tmp = self.path.with_suffix(".tmp")
67
+ with tmp.open("w") as file:
68
+ file.write(json.dumps(value, indent=2))
69
+ file.flush()
70
+ os.fsync(file.fileno())
71
+ tmp.replace(self.path)
mlx/src/solomon_mlx/cli.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ from pathlib import Path
4
+
5
+
6
+ def main():
7
+ parser = argparse.ArgumentParser(description="Private Solomon BF16 MLX tooling")
8
+ commands = parser.add_subparsers(dest="command", required=True)
9
+ verify = commands.add_parser("verify-source")
10
+ verify.add_argument("directory")
11
+ download = commands.add_parser("download-base")
12
+ download.add_argument("--output", default="snapshots/base")
13
+ convert = commands.add_parser("prepare")
14
+ convert.add_argument("--base", default="snapshots/base")
15
+ convert.add_argument("--solomon", default="snapshots/solomon")
16
+ convert.add_argument("--manifest", default="snapshots/base-manifest.json")
17
+ convert.add_argument("--output", default="models/quality")
18
+ decide = commands.add_parser("decide")
19
+ decide.add_argument("--model", default="models/quality")
20
+ decide.add_argument("--document", required=True)
21
+ decide.add_argument("--questions", required=True)
22
+ decide.add_argument(
23
+ "--evidence", choices=["none", "support", "sufficiency", "removal"], default="support"
24
+ )
25
+ args = parser.parse_args()
26
+ if args.command == "verify-source":
27
+ from .artifacts import verify_release
28
+
29
+ print(json.dumps(verify_release(args.directory), indent=2))
30
+ elif args.command == "download-base":
31
+ from huggingface_hub import HfApi, snapshot_download
32
+
33
+ from .artifacts import BASE_REVISION
34
+ from .prepare import verify_base
35
+
36
+ model = HfApi().model_info("Qwen/Qwen3.8-27B", revision=BASE_REVISION, files_metadata=True)
37
+ manifest = {
38
+ "revision": model.sha,
39
+ "files": [
40
+ {
41
+ "name": f.rfilename,
42
+ "size": f.size,
43
+ "blob_id": f.blob_id,
44
+ "sha256": f.lfs.sha256 if f.lfs else None,
45
+ }
46
+ for f in model.siblings
47
+ ],
48
+ }
49
+ Path(args.output).parent.mkdir(parents=True, exist_ok=True)
50
+ Path(args.output + "-manifest.json").write_text(json.dumps(manifest, indent=2))
51
+ snapshot_download("Qwen/Qwen3.8-27B", revision=BASE_REVISION, local_dir=args.output, max_workers=18)
52
+ verified = verify_base(args.output, manifest)
53
+ Path(args.output + "-verified.json").write_text(json.dumps(verified, indent=2))
54
+ elif args.command == "prepare":
55
+ from .prepare import prepare
56
+
57
+ prepare(args.base, args.solomon, args.output, args.manifest)
58
+ else:
59
+ from .api import Solomon
60
+
61
+ model = Solomon.load(args.model)
62
+ with model.prefill(Path(args.document).read_text()) as state:
63
+ print(
64
+ json.dumps(
65
+ model.decide(
66
+ state=state,
67
+ questions=json.loads(Path(args.questions).read_text()),
68
+ evidence=args.evidence,
69
+ ),
70
+ indent=2,
71
+ )
72
+ )
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
mlx/src/solomon_mlx/engine.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """BF16 Metal execution with instance-owned adaptation and isolated question caches."""
2
+
3
+ import copy
4
+ import json
5
+ import threading
6
+ import time
7
+ from pathlib import Path
8
+
9
+ import mlx.core as mx
10
+ import numpy as np
11
+ from mlx import nn
12
+ from PIL import Image
13
+
14
+ from ._vendor.prompts import PAGE, SYSTEM
15
+ from .artifacts import ADAPTER_SHA, BASE_REVISION, HEADS_SHA, SOLOMON_REVISION, runtime_identity, sha256
16
+
17
+
18
+ class SwitchLoRA(nn.Module):
19
+ def __init__(self, linear, a, b, context):
20
+ super().__init__()
21
+ self.linear, self.lora_a, self.lora_b = linear, a, b
22
+ self._context = context
23
+
24
+ def __call__(self, x):
25
+ y = self.linear(x)
26
+ start = self._context["start"]
27
+ if start is None or start >= x.shape[1]:
28
+ return y
29
+ delta = (2.0 * ((x[:, start:].astype(mx.float32) @ self.lora_a) @ self.lora_b)).astype(y.dtype)
30
+ return y + delta if start == 0 else mx.concatenate([y[:, :start], y[:, start:] + delta], axis=1)
31
+
32
+
33
+ def fork_cache(caches):
34
+ """New cache containers and array handles; MLX owns copy-on-write storage.
35
+
36
+ mx.array creates a distinct handle, so slice updates cannot change a prefix's
37
+ Python array. Recurrent/window updates replace the branch's private slots.
38
+ """
39
+ from mlx_vlm.models.cache import ArraysCache, KVCache
40
+
41
+ result = []
42
+ for original in caches:
43
+ if isinstance(original, ArraysCache):
44
+ branch = ArraysCache(len(original.cache))
45
+ branch.cache = [None if x is None else mx.array(x) for x in original.cache]
46
+ elif isinstance(original, KVCache):
47
+ branch = KVCache()
48
+ branch.state = tuple(None if x is None else mx.array(x) for x in original.state)
49
+ else:
50
+ raise TypeError(f"Unsupported prefix cache: {type(original).__name__}")
51
+ result.append(branch)
52
+ return result
53
+
54
+
55
+ class Engine:
56
+ def __init__(self, directory, *, chunk_size=2048, max_tokens=40960):
57
+ from mlx_vlm.models.qwen3_vl.processing_qwen3_vl import Qwen3VLProcessor
58
+ from mlx_vlm.utils import load_model
59
+
60
+ self.directory = Path(directory).resolve()
61
+ self.binding = json.loads((self.directory / "binding.json").read_text())
62
+ if (
63
+ self.binding.get("schema") != "solomon-mlx-binding-v1"
64
+ or self.binding.get("base_revision") != BASE_REVISION
65
+ or self.binding.get("solomon_revision") != SOLOMON_REVISION
66
+ ):
67
+ raise ValueError("Unrecognized or unpinned Solomon MLX binding")
68
+ if self.binding["profile"] != "quality" or self.binding["dtype"] != "bfloat16":
69
+ raise ValueError("This runtime currently accepts only the BF16 quality profile")
70
+ for name, expected in self.binding["files"].items():
71
+ path = (self.directory / name).resolve()
72
+ if not path.is_relative_to(self.directory) or sha256(path) != expected:
73
+ raise ValueError(f"Model artifact checksum mismatch: {name}")
74
+ adapter, heads = self.directory / "adapter.safetensors", self.directory / "heads.npz"
75
+ if sha256(adapter) != ADAPTER_SHA or sha256(heads) != HEADS_SHA:
76
+ raise ValueError("Solomon checkpoint identity mismatch")
77
+ if not 1 <= chunk_size <= 2048 or not 1 <= max_tokens <= 40960:
78
+ raise ValueError("Invalid chunk size or context ceiling")
79
+ weight_bytes = sum(
80
+ (self.directory / name).stat().st_size
81
+ for name in self.binding["files"]
82
+ if name.endswith((".safetensors", ".npz"))
83
+ )
84
+ if weight_bytes + 4 * 2**30 > mx.device_info()["max_recommended_working_set_size"]:
85
+ raise MemoryError(
86
+ "Full BF16 weights and minimum workspace exceed this Mac’s recommended Metal working set"
87
+ )
88
+ self.chunk_size, self.max_tokens = chunk_size, max_tokens
89
+ self.lock = threading.RLock()
90
+ self.context = {"start": None}
91
+ self.model = load_model(self.directory / "backbone", lazy=True, strict=True)
92
+ self.processor = Qwen3VLProcessor.from_pretrained(
93
+ str(self.directory / "backbone"), trust_remote_code=False
94
+ )
95
+ self.lm, self.t = self.model.language_model, self.processor.tokenizer
96
+ self.pad = self.t.convert_tokens_to_ids("<|image_pad|>")
97
+ weights = mx.load(str(adapter))
98
+ for name in sorted({key.rsplit(".", 1)[0] for key in weights}):
99
+ parts = name.split(".")
100
+ if parts[:2] != ["model", "layers"]:
101
+ raise ValueError(f"Unexpected adapter target: {name}")
102
+ owner = self.lm.model.layers[int(parts[2])]
103
+ for part in parts[3:-1]:
104
+ owner = getattr(owner, part)
105
+ linear = getattr(owner, parts[-1])
106
+ a, b = weights[name + ".lora_a"].astype(mx.float32), weights[name + ".lora_b"].astype(mx.float32)
107
+ if a.shape != (linear.weight.shape[1], 64) or b.shape != (64, linear.weight.shape[0]):
108
+ raise ValueError(f"Adapter orientation/shape mismatch: {name}")
109
+ setattr(owner, parts[-1], SwitchLoRA(linear, a, b, self.context))
110
+ with np.load(heads, allow_pickle=False) as archive:
111
+ keys = {k[:-7] for k in archive.files if k.endswith("/weight")}
112
+ required_heads = {
113
+ "boolean/state4",
114
+ "entity/state4",
115
+ "multilabel/state4",
116
+ "ordered/threshold4",
117
+ "single/choiceR",
118
+ "single/choiceS",
119
+ "single/sufficiency3",
120
+ "ordered/choiceR",
121
+ "ordered/choiceS",
122
+ "ordered/sufficiency3",
123
+ }
124
+ if keys != required_heads:
125
+ raise ValueError("All ten semantic heads are required")
126
+ self.heads = {}
127
+ for key in keys:
128
+ w, b = archive[key + "/weight"], archive[key + "/bias"]
129
+ if (
130
+ w.shape != (10, 5120)
131
+ or b.shape != (10,)
132
+ or not np.isfinite(w).all()
133
+ or not np.isfinite(b).all()
134
+ ):
135
+ raise ValueError("Invalid semantic head")
136
+ self.heads[key] = (mx.array(w, mx.float32), mx.array(b, mx.float32))
137
+ self.model.freeze()
138
+ self.model.eval()
139
+ mx.eval(self.model.parameters(), self.heads)
140
+ self.identity = runtime_identity(self.binding, chunk_size=self.chunk_size, max_tokens=self.max_tokens)
141
+
142
+ def render(self, parts, block):
143
+ content = ""
144
+ for i, p in enumerate(parts):
145
+ if "text" in p:
146
+ content += ("\n" if i and "image" in parts[i - 1] else "") + p["text"]
147
+ else:
148
+ content += ("\n" if i and "text" in parts[i - 1] else "") + PAGE
149
+ return self.t.apply_chat_template(
150
+ [
151
+ {"role": "system", "content": SYSTEM},
152
+ {"role": "user", "content": "Document:\n" + content + "\n\n" + block},
153
+ ],
154
+ tokenize=False,
155
+ add_generation_prompt=True,
156
+ enable_thinking=False,
157
+ )
158
+
159
+ def expand(self, ids, counts):
160
+ out, index = [], 0
161
+ for token in ids:
162
+ if token == self.pad:
163
+ if index >= len(counts):
164
+ raise ValueError("Unexpected image placeholder in document text")
165
+ out.extend([token] * counts[index])
166
+ index += 1
167
+ else:
168
+ out.append(token)
169
+ if index != len(counts):
170
+ raise ValueError("Image placeholder count mismatch")
171
+ return out
172
+
173
+ def positions(self, start, count):
174
+ return mx.broadcast_to(mx.arange(start, start + count)[None, None, :], (3, 1, count))
175
+
176
+ def admit(self, count):
177
+ if count < 1 or count > self.max_tokens:
178
+ raise ValueError(f"{count} tokens exceeds the {self.max_tokens}-token scope ceiling")
179
+ # Conservative allowance: BF16 attention KV + FP32 recurrent states and
180
+ # chunk intermediates. This supplements the token ceiling, not a promise
181
+ # of availability in the presence of other processes.
182
+ temporary = 4 * 2**30 + count * 16 * 2 * 4 * 256 * 2
183
+ limit = mx.device_info()["max_recommended_working_set_size"]
184
+ if mx.get_active_memory() + temporary > limit:
185
+ raise MemoryError("Insufficient recommended Metal working set for this request")
186
+
187
+ def forward(self, ids, positions, cache, *, embeds=None, adapter_from=None, taps=()):
188
+ hidden, captured = None, {}
189
+ try:
190
+ for start in range(0, len(ids), self.chunk_size):
191
+ end = min(start + self.chunk_size, len(ids))
192
+ self.context["start"] = None if adapter_from is None else max(0, adapter_from - start)
193
+ last = end == len(ids)
194
+ out = self.lm(
195
+ mx.array([ids[start:end]]),
196
+ cache=cache,
197
+ position_ids=positions[:, :, start:end],
198
+ inputs_embeds=None if embeds is None else embeds[:, start:end],
199
+ skip_logits=True,
200
+ return_hidden=last,
201
+ capture_layer_ids=list(taps) if last else None,
202
+ )
203
+ if last:
204
+ hidden = out.hidden_states[-1][0, -1].astype(mx.float32)
205
+ captured = {
206
+ str(i): self.lm.model.norm(h[:, -1:])[0, -1].astype(mx.float32)
207
+ for i, h in zip(sorted(set(taps)), out.hidden_states[:-1])
208
+ }
209
+ mx.eval(hidden, captured)
210
+ mx.eval([c.state for c in cache])
211
+ return hidden, captured
212
+ finally:
213
+ self.context["start"] = None
214
+
215
+ def prefill(self, parts):
216
+ with self.lock:
217
+ prefill_started = time.perf_counter()
218
+ text = self.render(parts, "X")
219
+ boundary = text.rfind("\n\nX")
220
+ if boundary < 0:
221
+ raise ValueError("Missing document boundary")
222
+ raw = self.t.encode(text[:boundary], add_special_tokens=False)
223
+ if "text" in parts[-1]:
224
+ raw = raw[:-1]
225
+ vision_started = time.perf_counter()
226
+ counts, grids, features = [], [], []
227
+ for part in parts:
228
+ if "image" not in part:
229
+ continue
230
+ with Image.open(part["image"]) as image:
231
+ processed = self.processor.image_processor(images=[image.convert("RGB")])
232
+ grid_np = np.asarray(processed["image_grid_thw"])
233
+ count = int(grid_np.prod()) // self.model.config.vision_config.spatial_merge_size**2
234
+ self.admit(len(raw) + sum(counts) + count - len(counts) - 1)
235
+ grid = mx.array(grid_np)
236
+ pixels = mx.array(np.asarray(processed["pixel_values"])).astype(
237
+ self.model.vision_tower.patch_embed.proj.weight.dtype
238
+ )
239
+ feature, _ = self.model.vision_tower(pixels, grid)
240
+ mx.eval(feature)
241
+ counts.append(count)
242
+ grids.append(grid)
243
+ features.append(feature)
244
+ vision_seconds = time.perf_counter() - vision_started if counts else 0.0
245
+ ids = self.expand(raw, counts)
246
+ self.admit(len(ids))
247
+ embeds, delta, feats, grid = None, 0, None, None
248
+ if counts:
249
+ feats, grid = mx.concatenate(features), mx.concatenate(grids)
250
+ f = self.model.get_input_embeddings(
251
+ mx.array([ids]), mx.zeros((1,)), image_grid_thw=grid, cached_image_features=feats
252
+ )
253
+ embeds, positions = f.inputs_embeds, f.position_ids
254
+ delta = int(np.asarray(f.rope_deltas).reshape(-1)[0])
255
+ if delta != int(mx.max(positions).item()) + 1 - len(ids):
256
+ raise ValueError("Multimodal RoPE offset mismatch")
257
+ else:
258
+ positions = self.positions(0, len(ids))
259
+ cache = self.lm.make_cache()
260
+ started = time.perf_counter()
261
+ self.forward(ids, positions, cache, embeds=embeds)
262
+ return {
263
+ "parts": copy.deepcopy(parts),
264
+ "prefix_ids": ids,
265
+ "cache": cache,
266
+ "counts": counts,
267
+ "rope_delta": delta,
268
+ "features": feats,
269
+ "grid": grid,
270
+ "positions": positions,
271
+ "prefill_seconds": time.perf_counter() - prefill_started,
272
+ "language_prefill_seconds": time.perf_counter() - started,
273
+ "vision_seconds": vision_seconds,
274
+ }
275
+
276
+ def ask(self, state, block, width, head, *, execution="cached", taps=()):
277
+ with self.lock:
278
+ if head not in self.heads or not 2 <= width <= 10:
279
+ raise ValueError("Unknown semantic head or invalid width")
280
+ ids = self.expand(
281
+ self.t.encode(self.render(state["parts"], block), add_special_tokens=False), state["counts"]
282
+ )
283
+ self.admit(len(ids))
284
+ p = len(state["prefix_ids"])
285
+ if ids[:p] != state["prefix_ids"]:
286
+ raise ValueError("Question token prefix differs from cached document")
287
+ started = time.perf_counter()
288
+ if execution == "cached":
289
+ hidden, captured = self.forward(
290
+ ids[p:],
291
+ self.positions(p + state["rope_delta"], len(ids) - p),
292
+ fork_cache(state["cache"]),
293
+ adapter_from=0,
294
+ taps=taps,
295
+ )
296
+ elif execution == "full":
297
+ embeds = None
298
+ if state["counts"]:
299
+ f = self.model.get_input_embeddings(
300
+ mx.array([ids]),
301
+ mx.zeros((1,)),
302
+ image_grid_thw=state["grid"],
303
+ cached_image_features=state["features"],
304
+ )
305
+ embeds, positions = f.inputs_embeds, f.position_ids
306
+ else:
307
+ positions = self.positions(0, len(ids))
308
+ hidden, captured = self.forward(
309
+ ids, positions, self.lm.make_cache(), embeds=embeds, adapter_from=p, taps=taps
310
+ )
311
+ else:
312
+ raise ValueError("Execution must be cached or full")
313
+ w, b = self.heads[head]
314
+ logits = (w @ hidden + b)[:width]
315
+ mx.eval(logits)
316
+ values = np.asarray(logits)
317
+ if not np.isfinite(values).all():
318
+ raise ValueError("Nonfinite trained-head output")
319
+ result = {
320
+ "letter_logits": values.tolist(),
321
+ "head_key": head,
322
+ "prompt_tokens": len(ids),
323
+ "branch_tokens": len(ids) - p,
324
+ "reused_prefix_tokens": p if execution == "cached" else 0,
325
+ "seconds": time.perf_counter() - started,
326
+ }
327
+ if taps:
328
+ result.update(
329
+ hidden=np.asarray(hidden).tolist(),
330
+ taps={k: np.asarray(v).tolist() for k, v in captured.items()},
331
+ token_ids=ids,
332
+ )
333
+ return result
mlx/src/solomon_mlx/evaluation.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Resumable panel scoring, fit-only calibration and one-shot held-out reports."""
2
+
3
+ import gzip
4
+ import hashlib
5
+ import json
6
+ from collections import defaultdict
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ from scipy.optimize import minimize_scalar
11
+
12
+ from ._vendor.semantics import listed_probs, p_yes
13
+ from .api import TASKS
14
+ from .artifacts import ADAPTER_SHA, HEADS_SHA, digest, sha256
15
+
16
+
17
+ def load_panel(directory):
18
+ directory = Path(directory)
19
+ manifest = json.loads((directory / "manifest.json").read_text())
20
+ if (
21
+ manifest["adapter_sha256"] != ADAPTER_SHA
22
+ or manifest["heads_sha256"] != HEADS_SHA
23
+ or manifest["readout_mode"] != "four_collapsed"
24
+ ):
25
+ raise ValueError("Panel belongs to a different checkpoint or answer semantics")
26
+ raw = gzip.decompress((directory / "jobs.json.gz").read_bytes())
27
+ if hashlib.sha256(raw).hexdigest() != manifest["jobs_sha256"]:
28
+ raise ValueError("Panel jobs checksum mismatch")
29
+ jobs = json.loads(raw)
30
+ if len({r["id"] for r in jobs}) != len(jobs):
31
+ raise ValueError("Duplicate panel branch IDs")
32
+ return jobs, manifest
33
+
34
+
35
+ def score_panel(model, panel, output):
36
+ """Atomically persist each document so interruption never requires rescoring it."""
37
+ jobs, manifest = load_panel(panel)
38
+ output = Path(output)
39
+ output.mkdir(parents=True, exist_ok=True)
40
+ identity = {
41
+ "runtime": model.identity,
42
+ "panel_sha256": manifest["jobs_sha256"],
43
+ "panel_role": Path(panel).name.split("-")[0],
44
+ }
45
+ meta = output / "identity.json"
46
+ if meta.exists() and json.loads(meta.read_text()) != identity:
47
+ raise ValueError("Cannot resume with different model code, weights or panel")
48
+ meta.write_text(json.dumps(identity, indent=2))
49
+ documents = defaultdict(list)
50
+ for row in jobs:
51
+ documents[row["document_key"]].append(row)
52
+ for key, group in documents.items():
53
+ path = output / (key + ".json")
54
+ if path.exists():
55
+ record = json.loads(path.read_text())
56
+ body = {k: v for k, v in record.items() if k != "sha256"}
57
+ if (
58
+ record["sha256"] != digest(body)
59
+ or record["identity"] != digest(identity)
60
+ or [r["id"] for r in record["rows"]] != [r["id"] for r in group]
61
+ ):
62
+ raise ValueError("Corrupt or mismatched resumed document")
63
+ continue
64
+ parts = group[0].get("parts") or [{"text": group[0]["doc"]}]
65
+ if any((r.get("parts") or [{"text": r["doc"]}]) != parts for r in group):
66
+ raise ValueError("Document key aliases different sources")
67
+ with model.prefill(parts) as state:
68
+ rows = []
69
+ for job in group:
70
+ result = model.engine.ask(state._data, job["block"], job["n"], job["head_key"])
71
+ rows.append({**result, **{k: job[k] for k in ("id", "task", "gold", "n", "question_id")}})
72
+ body = {
73
+ "identity": digest(identity),
74
+ "rows": rows,
75
+ "prefix_tokens": state.prefix_tokens,
76
+ "prefill_seconds": state._data["prefill_seconds"],
77
+ }
78
+ temp = path.with_suffix(".tmp")
79
+ temp.write_text(json.dumps({**body, "sha256": digest(body)}))
80
+ temp.replace(path)
81
+ print("Scored " + key + " " + str(len(rows)) + " branches", flush=True)
82
+ completed = {
83
+ "identity": digest(identity),
84
+ "documents": len(documents),
85
+ "branches": len(jobs),
86
+ "files": {key + ".json": sha256(output / (key + ".json")) for key in documents},
87
+ }
88
+ (output / "complete.json").write_text(json.dumps(completed, indent=2))
89
+
90
+
91
+ def read_scores(directory):
92
+ directory = Path(directory)
93
+ identity = json.loads((directory / "identity.json").read_text())
94
+ completed = json.loads((directory / "complete.json").read_text())
95
+ if completed["identity"] != digest(identity):
96
+ raise ValueError("Score identity mismatch")
97
+ rows = []
98
+ for name, checksum in completed["files"].items():
99
+ p = directory / name
100
+ if not p.resolve().is_relative_to(directory.resolve()) or sha256(p) != checksum:
101
+ raise ValueError("Score checksum mismatch")
102
+ record = json.loads(p.read_text())
103
+ rows.extend(record["rows"])
104
+ if len(rows) != completed["branches"]:
105
+ raise ValueError("Incomplete score set")
106
+ return rows, identity
107
+
108
+
109
+ def unit(row, temperature=1.0):
110
+ logits = row["letter_logits"]
111
+ task = row["task"]
112
+ gold = row["gold"]
113
+ if task in ("boolean", "entity", "multilabel"):
114
+ p = p_yes(logits, temperature)
115
+ return [1 - p, p], int(gold == 0)
116
+ width = row["n"] - 2 if row["head_key"].endswith("choiceR") else row["n"]
117
+ if not isinstance(gold, int) or not 0 <= gold < width:
118
+ return None, None
119
+ return listed_probs(logits, width, temperature).tolist(), gold
120
+
121
+
122
+ def fit_calibration(scores, output, *, panel_role):
123
+ if panel_role != "fit":
124
+ raise ValueError("Temperature fitting accepts fit panels only")
125
+ rows, identity = read_scores(scores)
126
+ if identity["panel_role"] != "fit":
127
+ raise ValueError("Scores were not generated from a fit panel")
128
+ output = Path(output)
129
+ if output.exists():
130
+ raise FileExistsError("Calibration artifacts are immutable")
131
+ temperatures, losses = {}, {}
132
+ for task in TASKS:
133
+ selected = [r for r in rows if r["task"] == task and unit(r)[0] is not None]
134
+ if not selected:
135
+ raise ValueError("No fit examples for " + task)
136
+
137
+ def loss(log_t, selected=selected):
138
+ t = float(np.exp(log_t))
139
+ return float(np.mean([-np.log(max(unit(r, t)[0][unit(r, t)[1]], 1e-300)) for r in selected]))
140
+
141
+ fit = minimize_scalar(loss, bounds=(np.log(0.05), np.log(20)), method="bounded")
142
+ temperatures[task] = float(np.exp(fit.x))
143
+ losses[task] = {"before": loss(0.0), "after": float(fit.fun), "units": len(selected)}
144
+ payload = {
145
+ "schema": "solomon-mlx-temperature-v1",
146
+ "runtime": identity["runtime"]["fingerprint"],
147
+ "temperatures": temperatures,
148
+ "fit_panel_sha256": identity["panel_sha256"],
149
+ "losses": losses,
150
+ "selection_role": "fit",
151
+ "heldout_used": False,
152
+ }
153
+ output.write_text(json.dumps({**payload, "sha256": digest(payload)}, indent=2))
154
+ return payload
155
+
156
+
157
+ def compare_rows(mlx_rows, cuda_rows, *, temperatures=None, reference_temperatures=None):
158
+ temperatures = temperatures or dict.fromkeys(TASKS, 1.0)
159
+ reference_temperatures = reference_temperatures or dict.fromkeys(TASKS, 1.0)
160
+ reference = {r["id"]: r for r in cuda_rows}
161
+ if len(reference) != len(cuda_rows) or set(reference) != {r["id"] for r in mlx_rows}:
162
+ raise ValueError("Comparison panels have different or duplicate branch IDs")
163
+ units = []
164
+ questions = defaultdict(list)
165
+ for row in mlx_rows:
166
+ other = {**row, "letter_logits": reference[row["id"]]["letter_logits"]}
167
+ p, gold = unit(row, temperatures[row["task"]])
168
+ q, _ = unit(other, reference_temperatures[row["task"]])
169
+ if p is None:
170
+ continue
171
+ left, right = int(np.argmax(p)), int(np.argmax(q))
172
+ item = {
173
+ "agreement": left == right,
174
+ "mlx_correct": left == gold,
175
+ "cuda_correct": right == gold,
176
+ "probability_drift": float(np.max(np.abs(np.asarray(p) - q))),
177
+ }
178
+ units.append(item)
179
+ questions[row["question_id"]].append(item)
180
+ if not units:
181
+ raise ValueError("No defined comparison targets")
182
+ agreement = float(np.mean([r["agreement"] for r in units]))
183
+ question_agreement = float(np.mean([all(x["agreement"] for x in r) for r in questions.values()]))
184
+ mlx_accuracy = float(np.mean([all(x["mlx_correct"] for x in r) for r in questions.values()]))
185
+ cuda_accuracy = float(np.mean([all(x["cuda_correct"] for x in r) for r in questions.values()]))
186
+ return {
187
+ "units": len(units),
188
+ "questions": len(questions),
189
+ "unit_decision_agreement": agreement,
190
+ "question_decision_agreement": float(
191
+ np.mean([all(x["agreement"] for x in r) for r in questions.values()])
192
+ ),
193
+ "mlx_whole_question_accuracy": mlx_accuracy,
194
+ "cuda_whole_question_accuracy": cuda_accuracy,
195
+ "accuracy_degradation_percentage_points": 100 * (cuda_accuracy - mlx_accuracy),
196
+ "max_probability_drift": max(r["probability_drift"] for r in units),
197
+ "probability_comparison": {
198
+ "mlx_temperatures": temperatures,
199
+ "cuda_temperatures": reference_temperatures,
200
+ },
201
+ "mean_probability_drift": float(np.mean([r["probability_drift"] for r in units])),
202
+ "quality_gate_passed": agreement >= 0.999
203
+ and question_agreement >= 0.999
204
+ and cuda_accuracy - mlx_accuracy <= 0.0025,
205
+ }
206
+
207
+
208
+ def read_cuda_scores(directory, panel, reference_identity):
209
+ """Reuse only scores bound to the exact pinned CUDA runtime and panel."""
210
+ jobs, manifest = load_panel(panel)
211
+ directory = Path(directory)
212
+ result = {}
213
+ for file in sorted(directory.glob("scores*.json.gz")):
214
+ payload = json.loads(gzip.decompress(file.read_bytes()))
215
+ identity = payload["identity"]
216
+ if not payload["complete"] or identity["runtime"] != reference_identity:
217
+ raise ValueError("Existing CUDA scores do not match the fresh reference runtime")
218
+ if identity["manifest"]["jobs_sha256"] != manifest["jobs_sha256"]:
219
+ raise ValueError("CUDA scores use another panel")
220
+ for key, row in payload["scores"].items():
221
+ if key in result:
222
+ raise ValueError("Duplicate CUDA score ID")
223
+ result[key] = row
224
+ if set(result) != {r["id"] for r in jobs}:
225
+ raise ValueError("CUDA score set is incomplete")
226
+ return [{**r, **result[r["id"]]} for r in jobs]
227
+
228
+
229
+ def select_calibration(fitted, dev_scores, output):
230
+ fitted, output = Path(fitted), Path(output)
231
+ if output.exists():
232
+ raise FileExistsError("Selected calibration is immutable")
233
+ fit = json.loads(fitted.read_text())
234
+ fit_payload = {k: v for k, v in fit.items() if k != "sha256"}
235
+ rows, identity = read_scores(dev_scores)
236
+ if (
237
+ fit["sha256"] != digest(fit_payload)
238
+ or identity["runtime"]["fingerprint"] != fit["runtime"]
239
+ or identity["panel_role"] != "dev"
240
+ ):
241
+ raise ValueError("Calibration or development identity mismatch")
242
+ temperatures, selection = {}, {}
243
+ for task in TASKS:
244
+ selected = [r for r in rows if r["task"] == task and unit(r)[0] is not None]
245
+ if not selected:
246
+ raise ValueError("Missing development task " + task)
247
+
248
+ def loss(t, selected=selected):
249
+ values = [unit(r, t) for r in selected]
250
+ return float(np.mean([-np.log(max(p[g], 1e-300)) for p, g in values]))
251
+
252
+ original, candidate = loss(1.0), loss(fit["temperatures"][task])
253
+ temperatures[task] = fit["temperatures"][task] if candidate < original else 1.0
254
+ selection[task] = {"untempered_nll": original, "fit_temperature_nll": candidate}
255
+ payload = {
256
+ **fit_payload,
257
+ "temperatures": temperatures,
258
+ "selection_role": "dev_selected",
259
+ "fit_artifact_sha256": sha256(fitted),
260
+ "dev_panel_sha256": identity["panel_sha256"],
261
+ "development_selection": selection,
262
+ }
263
+ output.write_text(json.dumps({**payload, "sha256": digest(payload)}, indent=2))
264
+ return payload
265
+
266
+
267
+ def heldout_report(
268
+ scores,
269
+ cuda_directory,
270
+ panel,
271
+ calibration,
272
+ reference,
273
+ output,
274
+ *,
275
+ reference_binding="evaluations/cuda-acceptance/input/serving-binding.json",
276
+ ):
277
+ """Evaluate a frozen configuration once; an existing output cannot be replaced."""
278
+ output = Path(output)
279
+ if output.exists():
280
+ raise FileExistsError("Held-out report already exists; do not reuse it for selection")
281
+ rows, identity = read_scores(scores)
282
+ cal = json.loads(Path(calibration).read_text())
283
+ payload = {k: v for k, v in cal.items() if k != "sha256"}
284
+ if (
285
+ cal["sha256"] != digest(payload)
286
+ or cal["runtime"] != identity["runtime"]["fingerprint"]
287
+ or cal["selection_role"] != "dev_selected"
288
+ or identity["panel_role"] != "cert"
289
+ ):
290
+ raise ValueError(
291
+ "Held-out evaluation requires frozen development-selected calibration and cert scores"
292
+ )
293
+ ref = json.loads(Path(reference).read_text())
294
+ cuda = read_cuda_scores(cuda_directory, panel, ref["identity"])
295
+ binding_path = Path(reference_binding)
296
+ source_manifest = json.loads((binding_path.parent / "manifest.json").read_text())
297
+ if sha256(binding_path) != source_manifest["files"][binding_path.name]:
298
+ raise ValueError("CUDA acceptance binding checksum mismatch")
299
+ binding = json.loads(binding_path.read_text())
300
+ for key in (
301
+ "adapter_sha256",
302
+ "trained_heads_sha256",
303
+ "model_sha256",
304
+ "numerics",
305
+ "placement",
306
+ "arithmetic",
307
+ ):
308
+ if binding["runtime"][key] != ref["identity"][key]:
309
+ raise ValueError("CUDA calibration belongs to another reference runtime")
310
+ reference_temperatures = {task: binding["temperatures"]["models"][task]["temperature"] for task in TASKS}
311
+ report = {
312
+ **compare_rows(
313
+ rows, cuda, temperatures=cal["temperatures"], reference_temperatures=reference_temperatures
314
+ ),
315
+ "cuda_calibration_binding_sha256": sha256(binding_path),
316
+ "runtime": identity["runtime"],
317
+ "panel_sha256": identity["panel_sha256"],
318
+ "calibration_sha256": sha256(calibration),
319
+ "reference_sha256": sha256(reference),
320
+ "scope": "text-only held-out panel",
321
+ "image_qualification": False,
322
+ }
323
+ output.write_text(json.dumps(report, indent=2))
324
+ return report
mlx/src/solomon_mlx/prepare.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify immutable input snapshots and create a separately checksummed BF16 model."""
2
+
3
+ import hashlib
4
+ import json
5
+ import shutil
6
+ from importlib.metadata import version
7
+ from pathlib import Path
8
+
9
+ from .artifacts import BASE_REVISION, SOLOMON_REVISION, sha256, verify_release
10
+
11
+
12
+ def verify_base(root, manifest):
13
+ root = Path(root)
14
+ if manifest["revision"] != BASE_REVISION:
15
+ raise ValueError("Wrong pinned base revision")
16
+ result = {}
17
+ for row in manifest["files"]:
18
+ path = root / row["name"]
19
+ if not path.resolve().is_relative_to(root.resolve()):
20
+ raise ValueError("Unsafe base manifest path")
21
+ if path.stat().st_size != row["size"]:
22
+ raise ValueError("Base size mismatch: " + row["name"])
23
+ actual = sha256(path)
24
+ if row["sha256"]:
25
+ if actual != row["sha256"]:
26
+ raise ValueError("Base checksum mismatch: " + row["name"])
27
+ else:
28
+ data = path.read_bytes()
29
+ git_hash = hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data).hexdigest()
30
+ if git_hash != row["blob_id"]:
31
+ raise ValueError("Base Git blob mismatch: " + row["name"])
32
+ result[row["name"]] = actual
33
+ return result
34
+
35
+
36
+ def prepare(base, solomon, output, manifest):
37
+
38
+ base, solomon, output = map(Path, (base, solomon, output))
39
+ source_hashes = verify_release(solomon)
40
+ base_hashes = verify_base(base, json.loads(Path(manifest).read_text()))
41
+ if output.exists():
42
+ raise FileExistsError("Use a new output directory; existing conversions are immutable")
43
+ output.mkdir(parents=True)
44
+ convert_bf16(base, output / "backbone")
45
+ shutil.copy2(solomon / "adapter/adapter.safetensors", output / "adapter.safetensors")
46
+ shutil.copy2(solomon / "adapter/heads.npz", output / "heads.npz")
47
+ for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"):
48
+ shutil.copy2(solomon / name, output / name)
49
+ files = {
50
+ str(p.relative_to(output)): sha256(p)
51
+ for p in sorted(output.rglob("*"))
52
+ if p.is_file() and ".cache" not in p.parts
53
+ }
54
+ binding = {
55
+ "schema": "solomon-mlx-binding-v1",
56
+ "profile": "quality",
57
+ "dtype": "bfloat16",
58
+ "adapter_dtype": "float32",
59
+ "head_dtype": "float32",
60
+ "recurrent_state_dtype": "float32",
61
+ "sensitive_parameters": "FP32 normalization weights, A_log and dt_bias",
62
+ "conversion": "upstream Qwen3.5 sanitization, norms promoted before unit offset",
63
+ "adapter_scale": 2.0,
64
+ "adapter_placement": "question",
65
+ "quantization": None,
66
+ "base_revision": BASE_REVISION,
67
+ "solomon_revision": SOLOMON_REVISION,
68
+ "source_hashes": source_hashes,
69
+ "base_hashes": base_hashes,
70
+ "dependencies": {p: version(p) for p in ("mlx", "mlx-vlm", "transformers", "numpy")},
71
+ "files": files,
72
+ }
73
+ (output / "binding.json").write_text(json.dumps(binding, indent=2, sort_keys=True))
74
+ return binding
75
+
76
+
77
+ def convert_bf16(base, output):
78
+ """Convert one original shard at a time; never copy download caches.
79
+
80
+ Keep normalization offsets in FP32 before adding one. Adding the unit
81
+ offset in BF16 would irreversibly round trained normalization parameters.
82
+ Large backbone matrices remain unquantized BF16.
83
+ """
84
+ import mlx.core as mx
85
+ from mlx.utils import tree_flatten
86
+ from mlx_vlm.models.qwen3_5.config import ModelConfig
87
+ from mlx_vlm.models.qwen3_5.qwen3_5 import Model
88
+
89
+ base, output = Path(base), Path(output)
90
+ output.mkdir(parents=True, exist_ok=False)
91
+ config = json.loads((base / "config.json").read_text())
92
+ if config.get("model_type") != "qwen3_5" or config.get("quantization"):
93
+ raise ValueError("Expected original unquantized Qwen3.5 architecture")
94
+ model = Model(ModelConfig.from_dict(config))
95
+ expected = {k: v.shape for k, v in tree_flatten(model.parameters())}
96
+ index = {"metadata": {"total_size": 0}, "weight_map": {}}
97
+ source_index = json.loads((base / "model.safetensors.index.json").read_text())
98
+ for filename in sorted(set(source_index["weight_map"].values())):
99
+ arrays = mx.load(str(base / filename))
100
+ for key, value in arrays.items():
101
+ sensitive = value.ndim == 1 and ("norm" in key or key.endswith(("A_log", "dt_bias")))
102
+ arrays[key] = value.astype(mx.float32 if sensitive else mx.bfloat16)
103
+ arrays = model.sanitize(arrays)
104
+ arrays = model.vision_tower.sanitize(arrays)
105
+ for key, value in arrays.items():
106
+ if key not in expected or value.shape != expected[key]:
107
+ raise ValueError("Converted tensor shape mismatch: " + key)
108
+ if key in index["weight_map"]:
109
+ raise ValueError("Duplicate converted tensor: " + key)
110
+ index["weight_map"][key] = filename
111
+ index["metadata"]["total_size"] += value.nbytes
112
+ mx.save_safetensors(str(output / filename), arrays, metadata={"format": "mlx"})
113
+ del arrays
114
+ mx.clear_cache()
115
+ print("Converted " + filename, flush=True)
116
+ if set(index["weight_map"]) != set(expected):
117
+ raise ValueError("Converted backbone is missing required parameters")
118
+ (output / "model.safetensors.index.json").write_text(json.dumps(index, indent=2, sort_keys=True))
119
+ for file in base.iterdir():
120
+ if file.is_file() and file.name != "model.safetensors.index.json" and file.suffix != ".safetensors":
121
+ shutil.copy2(file, output / file.name)
mlx/src/solomon_mlx_hub/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Prepare the adapter-only Hub release for the frozen Solomon MLX runtime."""
2
+
3
+ from .prepare import load, prepare_from_hub, prepare_from_snapshot, verify_prepared
4
+
5
+ __all__ = ["load", "prepare_from_hub", "prepare_from_snapshot", "verify_prepared"]