Text Classification
PEFT
lora
document-question-answering
structured-decisions
calibration
synthetic-evaluation
Instructions to use DoccyHealth/Solomon with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use DoccyHealth/Solomon with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Make MLX adapter-only and include the reproducible BF16 converter
Browse filesThe MLX setup reads the root adapter and heads, downloads the pinned Qwen base
separately, and assembles a verified local BF16 model. Includes the exact historical
converter, locked dependencies, source, integrity tests, setup instructions and dated
CUDA-parity status. No new temperature fitting; complete held-out parity remains
pending.
This view is limited to 50 files because it contains too many changes. See raw diff
- mlx/.gitignore +12 -0
- mlx/CONVERTER-SOURCE.json +21 -0
- mlx/LICENSE +202 -0
- mlx/MODIFICATIONS.md +25 -0
- mlx/NOTICE +78 -0
- mlx/README.md +146 -0
- mlx/bf16/LICENSE +202 -0
- mlx/bf16/MODIFICATIONS.md +75 -0
- mlx/bf16/NOTICE +78 -0
- mlx/bf16/README.md +51 -0
- mlx/bf16/base-manifest.json +197 -0
- mlx/bf16/binding.json +141 -0
- mlx/bf16/conversion.json +203 -0
- mlx/docs/UPSTREAM-MODIFICATIONS.md +75 -0
- mlx/docs/VALIDATION-20260921.md +46 -0
- mlx/examples/decide.py +21 -0
- mlx/pyproject.toml +35 -0
- mlx/requirements.cloud.lock +1072 -0
- mlx/requirements.lock +79 -0
- mlx/scripts/benchmark.py +122 -0
- mlx/scripts/benchmark_chunks.py +50 -0
- mlx/scripts/check_cuda_parity.py +172 -0
- mlx/scripts/score_parity.py +19 -0
- mlx/scripts/validate_api.py +128 -0
- mlx/src/solomon_mlx/__init__.py +5 -0
- mlx/src/solomon_mlx/_vendor/__init__.py +2 -0
- mlx/src/solomon_mlx/_vendor/contract.py +111 -0
- mlx/src/solomon_mlx/_vendor/evidence.py +156 -0
- mlx/src/solomon_mlx/_vendor/evidence_v3.py +425 -0
- mlx/src/solomon_mlx/_vendor/prompts.py +43 -0
- mlx/src/solomon_mlx/_vendor/retrieval.py +152 -0
- mlx/src/solomon_mlx/_vendor/semantics.py +71 -0
- mlx/src/solomon_mlx/api.py +321 -0
- mlx/src/solomon_mlx/artifacts.py +77 -0
- mlx/src/solomon_mlx/budget.py +71 -0
- mlx/src/solomon_mlx/cli.py +76 -0
- mlx/src/solomon_mlx/engine.py +333 -0
- mlx/src/solomon_mlx/evaluation.py +324 -0
- mlx/src/solomon_mlx/prepare.py +121 -0
- mlx/src/solomon_mlx_hub/__init__.py +5 -0
- mlx/src/solomon_mlx_hub/__main__.py +50 -0
- mlx/src/solomon_mlx_hub/data/base-manifest.json +197 -0
- mlx/src/solomon_mlx_hub/data/release.json +105 -0
- mlx/src/solomon_mlx_hub/prepare.py +212 -0
- mlx/tests/test_artifacts_and_evaluation.py +77 -0
- mlx/tests/test_budget.py +24 -0
- mlx/tests/test_contract.py +96 -0
- mlx/tests/test_hub.py +173 -0
- mlx/tests/test_metal_primitives.py +40 -0
- mlx/tests/test_parity.py +24 -0
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": "bbcae17fc1c35db80a79d5865133a42ef9a1b0cf342fff71949972e69cce43ec",
|
| 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": "d41d4d3f19aaf0dd54e4503d43807a29208d9528b8d4734b9f3d99cad18d9f93",
|
| 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 |
+
`2ec506902269e4636285c8811f6c0f52c9300c0c`. 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 = "2ec506902269e4636285c8811f6c0f52c9300c0c"
|
| 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"]
|
mlx/src/solomon_mlx_hub/__main__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Adapter-only Hub setup and verification command."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
|
| 6 |
+
from .prepare import prepare_from_hub, prepare_from_snapshot, verify_prepared
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def main():
|
| 10 |
+
parser = argparse.ArgumentParser(description="Prepare full BF16 Solomon from the adapter-only release")
|
| 11 |
+
commands = parser.add_subparsers(dest="command", required=True)
|
| 12 |
+
prepare = commands.add_parser("prepare")
|
| 13 |
+
prepare.add_argument("--revision", required=True, help="Full Solomon repository commit SHA")
|
| 14 |
+
prepare.add_argument("--output", default="models/quality")
|
| 15 |
+
prepare.add_argument("--base", help="Existing original Qwen snapshot, already downloaded")
|
| 16 |
+
prepare.add_argument("--snapshot", help="Existing adapter-only Solomon snapshot; requires --base")
|
| 17 |
+
prepare.add_argument("--cache-dir")
|
| 18 |
+
prepare.add_argument("--device", choices=("cpu", "gpu"), default="cpu")
|
| 19 |
+
verify = commands.add_parser("verify")
|
| 20 |
+
verify.add_argument("directory")
|
| 21 |
+
args = parser.parse_args()
|
| 22 |
+
if args.command == "verify":
|
| 23 |
+
binding = verify_prepared(args.directory)
|
| 24 |
+
print(json.dumps({"verified": True, "files": len(binding["files"]), "profile": binding["profile"]}))
|
| 25 |
+
elif args.snapshot:
|
| 26 |
+
if not args.base:
|
| 27 |
+
parser.error("--snapshot requires --base for offline preparation")
|
| 28 |
+
print(
|
| 29 |
+
prepare_from_snapshot(
|
| 30 |
+
args.snapshot,
|
| 31 |
+
args.output,
|
| 32 |
+
revision=args.revision,
|
| 33 |
+
base_dir=args.base,
|
| 34 |
+
device=args.device,
|
| 35 |
+
)
|
| 36 |
+
)
|
| 37 |
+
else:
|
| 38 |
+
print(
|
| 39 |
+
prepare_from_hub(
|
| 40 |
+
args.output,
|
| 41 |
+
revision=args.revision,
|
| 42 |
+
base_dir=args.base,
|
| 43 |
+
cache_dir=args.cache_dir,
|
| 44 |
+
device=args.device,
|
| 45 |
+
)
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
main()
|
mlx/src/solomon_mlx_hub/data/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/src/solomon_mlx_hub/data/release.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base_manifest_sha256": "e62a6942465b43f7f47616e36a16d6f32a1bd9daa5124dd26bf61b8eab0c3b6d",
|
| 3 |
+
"base_repo_id": "Qwen/Qwen3.8-27B",
|
| 4 |
+
"binding_contract": {
|
| 5 |
+
"adapter_dtype": "float32",
|
| 6 |
+
"adapter_placement": "question",
|
| 7 |
+
"adapter_scale": 2.0,
|
| 8 |
+
"base_hashes": {
|
| 9 |
+
".gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930",
|
| 10 |
+
"LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a",
|
| 11 |
+
"README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641",
|
| 12 |
+
"chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
|
| 13 |
+
"config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab",
|
| 14 |
+
"crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83",
|
| 15 |
+
"generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e",
|
| 16 |
+
"merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d",
|
| 17 |
+
"model-00001-of-00018.safetensors": "ba0ce20aae489ad196733da5064bcdf159a1fe84f53336648196e1ebb7751b1c",
|
| 18 |
+
"model-00002-of-00018.safetensors": "06a148c01bfbe3faa14a5f184a7ff29a706f7ae1c8b2705d2058e26d17a001fb",
|
| 19 |
+
"model-00003-of-00018.safetensors": "2e1bf62cbcd406eaa64b60d10353e1f0ef4039d0976e56f05cabe953454f9968",
|
| 20 |
+
"model-00004-of-00018.safetensors": "511e34063187882659753c4d93f3859f93c019fd438d8813071921c81d9a3f1a",
|
| 21 |
+
"model-00005-of-00018.safetensors": "635cb53446dc74f219740fc59e18b774f877b803b9722e289ca62575a6efa701",
|
| 22 |
+
"model-00006-of-00018.safetensors": "0bc5214fac607f0e6cc92eec3789d4b8559410ef9fce66621ba8158e8410dae0",
|
| 23 |
+
"model-00007-of-00018.safetensors": "80b0c49033e9a0d5762562aa12f4acdb7f54da586f3d0110f28c48d91cf07892",
|
| 24 |
+
"model-00008-of-00018.safetensors": "7192c5b66185d3592927daabee1cc19e6f6e0ce75988ee20e824b624765fda79",
|
| 25 |
+
"model-00009-of-00018.safetensors": "af3c48cc37af44f3db6ae0579baf019180d48d9c527caa0a1f03ff85813a56d8",
|
| 26 |
+
"model-00010-of-00018.safetensors": "163490a76f3bea3a40855b7efc04ce6d27afaf1a34f0bbde495b9491f76457c9",
|
| 27 |
+
"model-00011-of-00018.safetensors": "5f3ae1b948aeee39da77aec558e8236cd65fe4d7cb7686a76bb007acc563c6d8",
|
| 28 |
+
"model-00012-of-00018.safetensors": "a3de1c7114677a8f5ac5c4892c90e8238ea5c1e2038c80e757dfc87c3902ca55",
|
| 29 |
+
"model-00013-of-00018.safetensors": "06ab79a41f74c9c5cb734816feb0c7fc364104b227165ee7391231e1155aa02a",
|
| 30 |
+
"model-00014-of-00018.safetensors": "4138ed94603065ba884bbcadedb04d7718bb40117e85e6f5c6fc5b9c05b7a85b",
|
| 31 |
+
"model-00015-of-00018.safetensors": "69224e27b9de4e7dbf6fc936c6eaae08447bda3b80a6c31a871ab451173afd22",
|
| 32 |
+
"model-00016-of-00018.safetensors": "73cb9a1089fb6155cb648609478d6633be8a5c7d9ca5a05bc8925ce8a553cefe",
|
| 33 |
+
"model-00017-of-00018.safetensors": "beb51f01056142ac4984bd800507b0dd0fd18de57f8e9ef6ea41d1a3598983a8",
|
| 34 |
+
"model-00018-of-00018.safetensors": "1d3479509e21494658f9b64d317f5ea8e55c4025d28c702d6c4d0b356ce8ea06",
|
| 35 |
+
"model.safetensors.index.json": "77042094076611b69791a610065f28b7013b8c621795fa86ddccc8bac7d1b9df",
|
| 36 |
+
"preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516",
|
| 37 |
+
"tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3",
|
| 38 |
+
"tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27",
|
| 39 |
+
"video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13",
|
| 40 |
+
"vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003"
|
| 41 |
+
},
|
| 42 |
+
"base_revision": "1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0",
|
| 43 |
+
"conversion": "upstream Qwen3.5 sanitization, norms promoted before unit offset",
|
| 44 |
+
"dependencies": {
|
| 45 |
+
"mlx": "0.32.2",
|
| 46 |
+
"mlx-vlm": "0.7.1",
|
| 47 |
+
"numpy": "2.5.3",
|
| 48 |
+
"transformers": "5.17.0"
|
| 49 |
+
},
|
| 50 |
+
"dtype": "bfloat16",
|
| 51 |
+
"head_dtype": "float32",
|
| 52 |
+
"profile": "quality",
|
| 53 |
+
"quantization": null,
|
| 54 |
+
"recurrent_state_dtype": "float32",
|
| 55 |
+
"schema": "solomon-mlx-binding-v1",
|
| 56 |
+
"sensitive_parameters": "FP32 normalization weights, A_log and dt_bias",
|
| 57 |
+
"solomon_revision": "2ec506902269e4636285c8811f6c0f52c9300c0c"
|
| 58 |
+
},
|
| 59 |
+
"converter_code_sha256": "bbcae17fc1c35db80a79d5865133a42ef9a1b0cf342fff71949972e69cce43ec",
|
| 60 |
+
"reference_files": {
|
| 61 |
+
"LICENSE": "a61385b8bc4e786195136404b4975fede3c0bca526877f526482e9af93d47457",
|
| 62 |
+
"MODIFICATIONS.md": "218218b43b27132a6cfa032d2be908decd0966f5b655946a8668506731c77de7",
|
| 63 |
+
"NOTICE": "ccf2b8e98f0606945a5fcf9f6522c1f2c01b279d55dc21ee02c5a4acadf8e8d1",
|
| 64 |
+
"adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca",
|
| 65 |
+
"backbone/.gitattributes": "34448b82c17d60fec9b65b1f093c115ddbaadc04beb1b0140b6bfed2e012a930",
|
| 66 |
+
"backbone/LICENSE": "bbedc3fda3305820b977265f01b8619d87570a6739de3a5582c3464840f1e57a",
|
| 67 |
+
"backbone/README.md": "57e4bdb258ee1a7d2635c5174ebd4e56abe392505cdb5f8bbb356b0dc4293641",
|
| 68 |
+
"backbone/chat_template.jinja": "c3cf9e34abf4f9e36c2d72165aa9c132d3e2a725b6c2586aaa3a8af9d7a81041",
|
| 69 |
+
"backbone/config.json": "191e0af232104ed8b65258cf3fb2b842e288008baca7633c11b82a1ac7203aab",
|
| 70 |
+
"backbone/crc32.txt": "b42dd291f5f98b05807e458f5b47849969a9b3326acbf0c97d28b05826740b83",
|
| 71 |
+
"backbone/generation_config.json": "e70c136c1b78ddc1fb0905bac8e733a4dc448d4f852a5dd75143fffc70be550e",
|
| 72 |
+
"backbone/merges.txt": "a9d356d7bdf1ef4949e3e748e95b8e10ad9d4e2e838eddc38a0a7b6b94d1db8d",
|
| 73 |
+
"backbone/model-00001-of-00018.safetensors": "6c54d794f08d8a479e8a123b6ab3473b8a8f304f0da59b043aabe7bdb5f05a7a",
|
| 74 |
+
"backbone/model-00002-of-00018.safetensors": "3de19e7456388362e7dbe1455362f6b2661d8d7084a7a346e7d3f77053ff0f78",
|
| 75 |
+
"backbone/model-00003-of-00018.safetensors": "103e7f75798c312e696d2547889732b110a634725f6233b7b0bc654bf3365528",
|
| 76 |
+
"backbone/model-00004-of-00018.safetensors": "9b6ff88af2b387c943366b263d1ebef06090cf2a94e637895a74eb10300bf5da",
|
| 77 |
+
"backbone/model-00005-of-00018.safetensors": "f2e95aa5371257fadde49c6d735b716aab00868ae689befd54c091df7a6c57b4",
|
| 78 |
+
"backbone/model-00006-of-00018.safetensors": "7c196564fbb98f683573ae02d3a90bd74ccb7289d9103f09c76d9feffcc64b24",
|
| 79 |
+
"backbone/model-00007-of-00018.safetensors": "1a657de97643d5283547047925453e0bbec151b5f293f0a48ee8fbffb82c83cb",
|
| 80 |
+
"backbone/model-00008-of-00018.safetensors": "e26030a4ee68739f4092696d685b9ef5b4a7052f4843e949cf4419ee7d086b81",
|
| 81 |
+
"backbone/model-00009-of-00018.safetensors": "d73885d7e8995d58cd722b6fd4976035f30e3464189a71d88bb8e504a4612c53",
|
| 82 |
+
"backbone/model-00010-of-00018.safetensors": "ee367c91f6faf3a2d5245e28ee20dbe4e51a1ab08f8e27029c67e7c47a77bd89",
|
| 83 |
+
"backbone/model-00011-of-00018.safetensors": "01ea6406c8ee3f3bd0cbe96fb1ff120bdafc20bdeb4eb5dc8fc08894fef9221f",
|
| 84 |
+
"backbone/model-00012-of-00018.safetensors": "59da9b95e4d818c1189f681044df6bb9a5c7375ea0a01b8d6a6a682451fc4a8e",
|
| 85 |
+
"backbone/model-00013-of-00018.safetensors": "c2f63ec1a168d0f328da8ef74caee72d4e67d6dccfd9ccb595abcaedaeae86c4",
|
| 86 |
+
"backbone/model-00014-of-00018.safetensors": "d8a6453ec4fbe9763644dd9127c3a5ad5fec5c8a5cde6f1414f1101ee9a1250c",
|
| 87 |
+
"backbone/model-00015-of-00018.safetensors": "95db368ab83b8f60d1b9205de6ffa97adbe665d7e61e9adcfb237d25dabf9f8a",
|
| 88 |
+
"backbone/model-00016-of-00018.safetensors": "482d3d948c8cb31068323a85b7c2d445271a28a08699bbed48c13e465894715a",
|
| 89 |
+
"backbone/model-00017-of-00018.safetensors": "7615db84bee928608723f0e5e887f98e11091ccd86583453efcf9745050741f2",
|
| 90 |
+
"backbone/model-00018-of-00018.safetensors": "1219c9ea7951f2bb93c0ccafa2e6e65ac9111fda00961e3fbe614e30050c95e9",
|
| 91 |
+
"backbone/model.safetensors.index.json": "5aec933c5ef08d6c1d411b1b1f225b97d7c55f8eeed4b74535849ce9361e595f",
|
| 92 |
+
"backbone/preprocessor_config.json": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516",
|
| 93 |
+
"backbone/tokenizer.json": "0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3",
|
| 94 |
+
"backbone/tokenizer_config.json": "b11349aafa7cdc6a320767cf7ceb29ed82f7eda5d65e8e0819e76f0ce947bf27",
|
| 95 |
+
"backbone/video_preprocessor_config.json": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13",
|
| 96 |
+
"backbone/vocab.json": "ce99b4cb2983d118806ce0a8b777a35b093e2000a503ebde25853284c9dfa003",
|
| 97 |
+
"heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f"
|
| 98 |
+
},
|
| 99 |
+
"repo_id": "DoccyHealth/Solomon",
|
| 100 |
+
"schema": "solomon-mlx-hub-release-v1",
|
| 101 |
+
"source_hashes": {
|
| 102 |
+
"adapter/adapter.safetensors": "2addaf841ecc58829ad73081603b8d3e52743c53c6d558a17a1dd30e9bd2dbca",
|
| 103 |
+
"adapter/heads.npz": "126a9b5487dca937a768a4f228f2d2e7d513900d8ad0e99c4137fbaa42a1aa6f"
|
| 104 |
+
}
|
| 105 |
+
}
|
mlx/src/solomon_mlx_hub/prepare.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Verified, atomic local assembly without redistributing the Qwen backbone.
|
| 2 |
+
|
| 3 |
+
This separate package leaves the historical converter and running inference code
|
| 4 |
+
unchanged. Conversion uses the original FP32 normalization-offset treatment.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import copy
|
| 8 |
+
import fcntl
|
| 9 |
+
import json
|
| 10 |
+
import re
|
| 11 |
+
import shutil
|
| 12 |
+
import tempfile
|
| 13 |
+
from contextlib import contextmanager
|
| 14 |
+
from importlib.metadata import version
|
| 15 |
+
from pathlib import Path, PurePosixPath
|
| 16 |
+
|
| 17 |
+
from huggingface_hub import snapshot_download
|
| 18 |
+
|
| 19 |
+
from solomon_mlx.artifacts import code_identity, sha256
|
| 20 |
+
from solomon_mlx.prepare import convert_bf16, verify_base
|
| 21 |
+
|
| 22 |
+
DATA = Path(__file__).parent / "data"
|
| 23 |
+
RELEASE = json.loads((DATA / "release.json").read_text())
|
| 24 |
+
MANIFEST = DATA / "base-manifest.json"
|
| 25 |
+
SOURCE_PATTERNS = [
|
| 26 |
+
"adapter/**",
|
| 27 |
+
"mlx/bf16/LICENSE",
|
| 28 |
+
"mlx/bf16/NOTICE",
|
| 29 |
+
"mlx/bf16/MODIFICATIONS.md",
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _revision(revision):
|
| 34 |
+
if not isinstance(revision, str) or not re.fullmatch(r"[0-9a-f]{40}", revision):
|
| 35 |
+
raise ValueError("Pin the Solomon repository to a full 40-character commit SHA")
|
| 36 |
+
return revision
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _relative(name):
|
| 40 |
+
path = PurePosixPath(name)
|
| 41 |
+
if not name or path.is_absolute() or ".." in path.parts or "\\" in name:
|
| 42 |
+
raise ValueError("Unsafe artifact path: " + name)
|
| 43 |
+
return path
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _check_converter():
|
| 47 |
+
if code_identity() != RELEASE["converter_code_sha256"]:
|
| 48 |
+
raise ValueError("Converter source identity mismatch; install the pinned source distribution")
|
| 49 |
+
for package, expected in RELEASE["binding_contract"]["dependencies"].items():
|
| 50 |
+
if version(package) != expected:
|
| 51 |
+
raise ValueError(f"Converter requires {package}=={expected}")
|
| 52 |
+
if sha256(MANIFEST) != RELEASE["base_manifest_sha256"]:
|
| 53 |
+
raise ValueError("Pinned base manifest checksum mismatch")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def verify_prepared(directory):
|
| 57 |
+
"""Verify an existing local conversion without allocating model tensors.
|
| 58 |
+
|
| 59 |
+
Historical conversions remain usable. Their bindings and byte checksums are
|
| 60 |
+
preserved; a new conversion always receives its own output checksums.
|
| 61 |
+
"""
|
| 62 |
+
root = Path(directory).resolve()
|
| 63 |
+
binding = json.loads((root / "binding.json").read_text())
|
| 64 |
+
for key, expected in RELEASE["binding_contract"].items():
|
| 65 |
+
if binding.get(key) != expected:
|
| 66 |
+
raise ValueError("Local binding mismatch: " + key)
|
| 67 |
+
for key, expected in RELEASE["source_hashes"].items():
|
| 68 |
+
if binding.get("source_hashes", {}).get(key) != expected:
|
| 69 |
+
raise ValueError("Local source identity mismatch: " + key)
|
| 70 |
+
files = binding.get("files", {})
|
| 71 |
+
if set(files) != set(RELEASE["reference_files"]):
|
| 72 |
+
raise ValueError("Local conversion has missing or unexpected bound artifacts")
|
| 73 |
+
for name, expected in files.items():
|
| 74 |
+
path = root / _relative(name)
|
| 75 |
+
if not path.resolve().is_relative_to(root) or sha256(path) != expected:
|
| 76 |
+
raise ValueError("Local artifact checksum mismatch: " + name)
|
| 77 |
+
# Safetensors serialization can differ between CPU and Metal. Metadata,
|
| 78 |
+
# processor files, trained adapter and heads must remain byte-identical.
|
| 79 |
+
if (
|
| 80 |
+
not (name.startswith("backbone/") and name.endswith(".safetensors"))
|
| 81 |
+
and expected != RELEASE["reference_files"][name]
|
| 82 |
+
):
|
| 83 |
+
raise ValueError("Pinned artifact identity mismatch: " + name)
|
| 84 |
+
return binding
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@contextmanager
|
| 88 |
+
def _output_lock(output):
|
| 89 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 90 |
+
# Keep the lock inode after release so concurrent waiters cannot bypass it.
|
| 91 |
+
with (output.parent / f".{output.name}.prepare.lock").open("a") as handle:
|
| 92 |
+
try:
|
| 93 |
+
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
| 94 |
+
except BlockingIOError as exc:
|
| 95 |
+
raise RuntimeError("Another process is preparing this output") from exc
|
| 96 |
+
try:
|
| 97 |
+
yield
|
| 98 |
+
finally:
|
| 99 |
+
fcntl.flock(handle, fcntl.LOCK_UN)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _assemble(snapshot, output, *, revision, base_dir, device):
|
| 103 |
+
source = Path(snapshot)
|
| 104 |
+
for name, expected in RELEASE["source_hashes"].items():
|
| 105 |
+
if sha256(source / _relative(name)) != expected:
|
| 106 |
+
raise ValueError("Solomon source checksum mismatch: " + name)
|
| 107 |
+
for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"):
|
| 108 |
+
if sha256(source / "mlx/bf16" / name) != RELEASE["reference_files"][name]:
|
| 109 |
+
raise ValueError("Solomon license/provenance checksum mismatch: " + name)
|
| 110 |
+
|
| 111 |
+
manifest = json.loads(MANIFEST.read_text())
|
| 112 |
+
if base_dir is None:
|
| 113 |
+
base_dir = output.parent / ".solomon-sources" / ("base-" + manifest["revision"])
|
| 114 |
+
snapshot_download(
|
| 115 |
+
RELEASE["base_repo_id"],
|
| 116 |
+
revision=manifest["revision"],
|
| 117 |
+
local_dir=base_dir,
|
| 118 |
+
allow_patterns=[row["name"] for row in manifest["files"]],
|
| 119 |
+
max_workers=8,
|
| 120 |
+
)
|
| 121 |
+
base_dir = Path(base_dir)
|
| 122 |
+
base_hashes = verify_base(base_dir, manifest)
|
| 123 |
+
if base_hashes != RELEASE["binding_contract"]["base_hashes"]:
|
| 124 |
+
raise ValueError("Base input identity differs from the original conversion")
|
| 125 |
+
|
| 126 |
+
temporary = Path(tempfile.mkdtemp(prefix=f".{output.name}.", dir=output.parent))
|
| 127 |
+
try:
|
| 128 |
+
import mlx.core as mx
|
| 129 |
+
|
| 130 |
+
# Device selection is scoped to this conversion; CPU conversion also
|
| 131 |
+
# works on Linux. Inference still requires Apple Silicon and Metal.
|
| 132 |
+
with mx.stream(mx.cpu if device == "cpu" else mx.gpu):
|
| 133 |
+
convert_bf16(base_dir, temporary / "backbone")
|
| 134 |
+
for name in RELEASE["source_hashes"]:
|
| 135 |
+
shutil.copy2(source / name, temporary / Path(name).name)
|
| 136 |
+
for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"):
|
| 137 |
+
shutil.copy2(source / "mlx/bf16" / name, temporary / name)
|
| 138 |
+
binding = copy.deepcopy(RELEASE["binding_contract"])
|
| 139 |
+
binding["source_hashes"] = RELEASE["source_hashes"]
|
| 140 |
+
binding["files"] = {
|
| 141 |
+
str(p.relative_to(temporary)): sha256(p) for p in sorted(temporary.rglob("*")) if p.is_file()
|
| 142 |
+
}
|
| 143 |
+
binding["hub_preparation"] = {
|
| 144 |
+
"repo_id": RELEASE["repo_id"],
|
| 145 |
+
"revision": revision,
|
| 146 |
+
"converter_code_sha256": RELEASE["converter_code_sha256"],
|
| 147 |
+
"loader_code_sha256": sha256(__file__),
|
| 148 |
+
"device": device,
|
| 149 |
+
"base_manifest_sha256": RELEASE["base_manifest_sha256"],
|
| 150 |
+
"calibration": "none; CUDA parity only",
|
| 151 |
+
}
|
| 152 |
+
(temporary / "binding.json").write_text(json.dumps(binding, indent=2, sort_keys=True) + "\n")
|
| 153 |
+
verify_prepared(temporary)
|
| 154 |
+
temporary.rename(output)
|
| 155 |
+
finally:
|
| 156 |
+
if temporary.exists():
|
| 157 |
+
shutil.rmtree(temporary)
|
| 158 |
+
return output
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def prepare_from_snapshot(snapshot, output, *, revision, base_dir, device="cpu"):
|
| 162 |
+
"""Offline preparation from an adapter-only snapshot and original Qwen files."""
|
| 163 |
+
_revision(revision)
|
| 164 |
+
if base_dir is None:
|
| 165 |
+
raise ValueError("Offline preparation requires the original Qwen base directory")
|
| 166 |
+
if device not in ("cpu", "gpu"):
|
| 167 |
+
raise ValueError("Conversion device must be cpu or gpu")
|
| 168 |
+
_check_converter()
|
| 169 |
+
output = Path(output).absolute()
|
| 170 |
+
with _output_lock(output):
|
| 171 |
+
if output.exists():
|
| 172 |
+
verify_prepared(output)
|
| 173 |
+
return output
|
| 174 |
+
return _assemble(Path(snapshot), output, revision=revision, base_dir=base_dir, device=device)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def prepare_from_hub(output, *, revision, base_dir=None, cache_dir=None, device="cpu"):
|
| 178 |
+
"""Download root adapters and pinned base inputs; return a verified local model.
|
| 179 |
+
|
| 180 |
+
Existing valid outputs are reused without network traffic or rewriting their
|
| 181 |
+
runtime identity. Invalid outputs fail closed and are never overwritten.
|
| 182 |
+
"""
|
| 183 |
+
_revision(revision)
|
| 184 |
+
if device not in ("cpu", "gpu"):
|
| 185 |
+
raise ValueError("Conversion device must be cpu or gpu")
|
| 186 |
+
_check_converter()
|
| 187 |
+
output = Path(output).absolute()
|
| 188 |
+
with _output_lock(output):
|
| 189 |
+
if output.exists():
|
| 190 |
+
verify_prepared(output)
|
| 191 |
+
return output
|
| 192 |
+
snapshot = snapshot_download(
|
| 193 |
+
RELEASE["repo_id"],
|
| 194 |
+
revision=revision,
|
| 195 |
+
allow_patterns=SOURCE_PATTERNS,
|
| 196 |
+
cache_dir=cache_dir,
|
| 197 |
+
)
|
| 198 |
+
return _assemble(Path(snapshot), output, revision=revision, base_dir=base_dir, device=device)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def load(output, *, revision, base_dir=None, cache_dir=None, device="cpu", **model_options):
|
| 202 |
+
"""Prepare the adapter-only release, then load the standard Solomon API."""
|
| 203 |
+
from solomon_mlx import Solomon
|
| 204 |
+
|
| 205 |
+
directory = prepare_from_hub(
|
| 206 |
+
output,
|
| 207 |
+
revision=revision,
|
| 208 |
+
base_dir=base_dir,
|
| 209 |
+
cache_dir=cache_dir,
|
| 210 |
+
device=device,
|
| 211 |
+
)
|
| 212 |
+
return Solomon.load(directory, **model_options)
|
mlx/tests/test_artifacts_and_evaluation.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import pytest
|
| 6 |
+
|
| 7 |
+
from solomon_mlx.artifacts import BASE_REVISION, verify_release
|
| 8 |
+
from solomon_mlx.evaluation import compare_rows, unit
|
| 9 |
+
from solomon_mlx.prepare import verify_base
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def test_corrupt_source_and_base_fail_closed(tmp_path):
|
| 13 |
+
path = tmp_path / "input"
|
| 14 |
+
path.write_bytes(b"original")
|
| 15 |
+
sha = hashlib.sha256(b"original").hexdigest()
|
| 16 |
+
(tmp_path / "MANIFEST.json").write_text(
|
| 17 |
+
json.dumps({"files": [{"destination": "input", "staged_bytes": 8, "staged_sha256": sha}]})
|
| 18 |
+
)
|
| 19 |
+
assert verify_release(tmp_path)["input"] == sha
|
| 20 |
+
manifest = {"revision": BASE_REVISION, "files": [{"name": "input", "size": 8, "sha256": sha}]}
|
| 21 |
+
assert verify_base(tmp_path, manifest)["input"] == sha
|
| 22 |
+
path.write_bytes(b"corrupt!")
|
| 23 |
+
with pytest.raises(ValueError):
|
| 24 |
+
verify_release(tmp_path)
|
| 25 |
+
with pytest.raises(ValueError):
|
| 26 |
+
verify_base(tmp_path, manifest)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_reserved_choice_exclusion_and_whole_question_accuracy():
|
| 30 |
+
rows = [
|
| 31 |
+
{
|
| 32 |
+
"id": "a",
|
| 33 |
+
"task": "entity",
|
| 34 |
+
"n": 4,
|
| 35 |
+
"head_key": "entity/state4",
|
| 36 |
+
"question_id": "q",
|
| 37 |
+
"gold": 0,
|
| 38 |
+
"letter_logits": [5, 0, 0, 0],
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"id": "b",
|
| 42 |
+
"task": "entity",
|
| 43 |
+
"n": 4,
|
| 44 |
+
"head_key": "entity/state4",
|
| 45 |
+
"question_id": "q",
|
| 46 |
+
"gold": 3,
|
| 47 |
+
"letter_logits": [0, 0, 0, 5],
|
| 48 |
+
},
|
| 49 |
+
]
|
| 50 |
+
other = [{**rows[0]}, {**rows[1], "letter_logits": [5, 0, 0, 0]}]
|
| 51 |
+
report = compare_rows(rows, other)
|
| 52 |
+
assert report["unit_decision_agreement"] == 0.5
|
| 53 |
+
assert report["mlx_whole_question_accuracy"] == 1
|
| 54 |
+
assert report["cuda_whole_question_accuracy"] == 0
|
| 55 |
+
assert not report["quality_gate_passed"]
|
| 56 |
+
assert unit(
|
| 57 |
+
{"task": "single", "n": 4, "head_key": "single/choiceR", "gold": 3, "letter_logits": [1, 2, 3, 4]}
|
| 58 |
+
) == (None, None)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def test_norm_offset_preserves_bf16_source_values_in_fp32():
|
| 62 |
+
from types import SimpleNamespace
|
| 63 |
+
|
| 64 |
+
import mlx.core as mx
|
| 65 |
+
from mlx_vlm.models.qwen3_5.qwen3_5 import Model
|
| 66 |
+
|
| 67 |
+
# Sanitization requires config only for tied embeddings and image channels.
|
| 68 |
+
config = SimpleNamespace(
|
| 69 |
+
text_config=SimpleNamespace(tie_word_embeddings=False), vision_config=SimpleNamespace(in_channels=3)
|
| 70 |
+
)
|
| 71 |
+
original = mx.array([0.02, -0.04], dtype=mx.bfloat16)
|
| 72 |
+
converted = Model.sanitize(
|
| 73 |
+
SimpleNamespace(config=config), {"model.language_model.norm.weight": original.astype(mx.float32)}
|
| 74 |
+
)
|
| 75 |
+
out = converted["language_model.model.norm.weight"]
|
| 76 |
+
assert out.dtype == mx.float32
|
| 77 |
+
np.testing.assert_array_equal(np.asarray(out), np.asarray(original.astype(mx.float32)) + 1)
|
mlx/tests/test_budget.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
import pytest
|
| 4 |
+
|
| 5 |
+
from solomon_mlx.budget import Budget
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_budget_reserves_before_dispatch_and_exhausts(tmp_path):
|
| 9 |
+
path = tmp_path / "budget.json"
|
| 10 |
+
budget = Budget(path, 20)
|
| 11 |
+
with budget.reserve(15, "one"):
|
| 12 |
+
assert json.loads(path.read_text())["entries"][0]["status"] == "reserved"
|
| 13 |
+
with pytest.raises(RuntimeError), budget.reserve(1, "concurrent"):
|
| 14 |
+
pass
|
| 15 |
+
with pytest.raises(RuntimeError), budget.reserve(6, "too expensive"):
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_uncertain_dispatch_blocks_followups(tmp_path):
|
| 20 |
+
budget = Budget(tmp_path / "budget.json")
|
| 21 |
+
with pytest.raises(TimeoutError), budget.reserve(20, "uncertain"):
|
| 22 |
+
raise TimeoutError()
|
| 23 |
+
with pytest.raises(RuntimeError), budget.reserve(20, "retry"):
|
| 24 |
+
pass
|
mlx/tests/test_contract.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import threading
|
| 3 |
+
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from solomon_mlx._vendor.contract import parse_questions
|
| 7 |
+
from solomon_mlx._vendor.semantics import p_yes
|
| 8 |
+
from solomon_mlx.api import Solomon, branches, distributions, ordering_score
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_collapse_before_temperature():
|
| 12 |
+
p = p_yes([0, 0, 0, 0], 2)
|
| 13 |
+
assert p == pytest.approx(1 / (1 + math.sqrt(3)))
|
| 14 |
+
assert p != pytest.approx(0.25)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def test_routing_and_candidate_order():
|
| 18 |
+
specs = parse_questions(
|
| 19 |
+
{
|
| 20 |
+
"single": {"type": "choice", "instructions": "Who?", "options": ["A", "B"]},
|
| 21 |
+
"ordered": {"type": "score", "instructions": "Level?", "levels": ["low", "high"]},
|
| 22 |
+
"entity": {"instructions": "Is {candidate} certified?", "candidates": ["Z", "X"]},
|
| 23 |
+
}
|
| 24 |
+
)
|
| 25 |
+
assert branches(specs[0])[0][1:] == (4, "single/choiceR")
|
| 26 |
+
assert branches(specs[1])[0][1:] == (2, "ordered/choiceS")
|
| 27 |
+
assert "Is Z certified?" in branches(specs[2])[0][0]
|
| 28 |
+
assert distributions(specs[0], [{"letter_logits": [0, 0, 100, 100]}], 1) == [[0.5, 0.5]]
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def test_ordering_score_product():
|
| 32 |
+
assert ordering_score([[0.8, 0.2], [0.1, 0.9]]) == pytest.approx(0.72)
|
| 33 |
+
with pytest.raises(ValueError):
|
| 34 |
+
ordering_score([[0.8, 0.3]])
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class FakeEngine:
|
| 38 |
+
def __init__(self):
|
| 39 |
+
self.identity = {"fingerprint": "test-only"}
|
| 40 |
+
self.lock = threading.RLock()
|
| 41 |
+
self.prefills = []
|
| 42 |
+
|
| 43 |
+
def prefill(self, parts):
|
| 44 |
+
self.prefills.append(parts)
|
| 45 |
+
return {"parts": parts, "prefix_ids": [1, 2]}
|
| 46 |
+
|
| 47 |
+
def ask(self, state, block, width, head, **kwargs):
|
| 48 |
+
return {
|
| 49 |
+
"letter_logits": [2.0] + [0.0] * (width - 1),
|
| 50 |
+
"branch_tokens": 3,
|
| 51 |
+
"prompt_tokens": 5,
|
| 52 |
+
"head_key": head,
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def test_state_ownership_close_and_replay(tmp_path):
|
| 57 |
+
model = Solomon(FakeEngine())
|
| 58 |
+
other = Solomon(FakeEngine())
|
| 59 |
+
state = model.prefill("A fact.")
|
| 60 |
+
recipe = tmp_path / "state.json"
|
| 61 |
+
state.save(recipe)
|
| 62 |
+
with pytest.raises(ValueError):
|
| 63 |
+
other.decide(state=state, questions={"a": "Fact?"})
|
| 64 |
+
state.close()
|
| 65 |
+
with pytest.raises(ValueError):
|
| 66 |
+
model.decide(state=state, questions={"a": "Fact?"})
|
| 67 |
+
with model.replay(recipe) as restored:
|
| 68 |
+
assert restored.prefix_tokens == 2
|
| 69 |
+
recipe.write_text(recipe.read_text().replace("A fact.", "Bad fact."))
|
| 70 |
+
with pytest.raises(ValueError):
|
| 71 |
+
model.replay(recipe)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_evidence_budget_stops_fresh_calls():
|
| 75 |
+
engine = FakeEngine()
|
| 76 |
+
model = Solomon(engine)
|
| 77 |
+
with model.prefill("Alice is certified.\nBob is not certified.") as state:
|
| 78 |
+
result = model.decide(
|
| 79 |
+
state=state, questions={"a": "Is Alice certified?"}, evidence="removal", evidence_max_calls=0
|
| 80 |
+
)
|
| 81 |
+
assert result["answers"]["a"]["evidence_status"] == "budget_exhausted"
|
| 82 |
+
assert len(engine.prefills) == 1
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def test_evidence_spans_and_fresh_verification():
|
| 86 |
+
engine = FakeEngine()
|
| 87 |
+
model = Solomon(engine)
|
| 88 |
+
text = "Alice is certified.\nBob is not certified."
|
| 89 |
+
with model.prefill(text) as state:
|
| 90 |
+
out = model.decide(state=state, questions={"a": "Is Alice certified?"}, evidence="removal")[
|
| 91 |
+
"answers"
|
| 92 |
+
]["a"]
|
| 93 |
+
assert len(engine.prefills) == 3
|
| 94 |
+
for span in out["evidence"]:
|
| 95 |
+
assert text[span["start"] : span["end"]] == span["text"]
|
| 96 |
+
assert out["evidence_detail"]["verification"] == "fresh_source_reencoding"
|
mlx/tests/test_hub.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Adapter-only downloads, immutable reuse, integrity failures and atomic assembly."""
|
| 2 |
+
|
| 3 |
+
import copy
|
| 4 |
+
import fcntl
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import pytest
|
| 9 |
+
|
| 10 |
+
import solomon_mlx_hub.prepare as hub
|
| 11 |
+
from solomon_mlx.artifacts import sha256
|
| 12 |
+
|
| 13 |
+
REVISION = "a" * 40
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@pytest.fixture
|
| 17 |
+
def release(tmp_path, monkeypatch):
|
| 18 |
+
source, base = tmp_path / "source", tmp_path / "base"
|
| 19 |
+
(source / "adapter").mkdir(parents=True)
|
| 20 |
+
(source / "mlx/bf16").mkdir(parents=True)
|
| 21 |
+
base.mkdir()
|
| 22 |
+
payloads = {"adapter.safetensors": b"trained adapter", "heads.npz": b"trained heads"}
|
| 23 |
+
for name, value in payloads.items():
|
| 24 |
+
(source / "adapter" / name).write_bytes(value)
|
| 25 |
+
for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md"):
|
| 26 |
+
(source / "mlx/bf16" / name).write_text(name)
|
| 27 |
+
(base / "config.json").write_text('{"model_type":"qwen3_5"}')
|
| 28 |
+
manifest = tmp_path / "base-manifest.json"
|
| 29 |
+
manifest.write_text(
|
| 30 |
+
json.dumps(
|
| 31 |
+
{
|
| 32 |
+
"revision": hub.RELEASE["binding_contract"]["base_revision"],
|
| 33 |
+
"files": [
|
| 34 |
+
{
|
| 35 |
+
"name": "config.json",
|
| 36 |
+
"size": (base / "config.json").stat().st_size,
|
| 37 |
+
"sha256": sha256(base / "config.json"),
|
| 38 |
+
}
|
| 39 |
+
],
|
| 40 |
+
}
|
| 41 |
+
)
|
| 42 |
+
)
|
| 43 |
+
reference = {
|
| 44 |
+
**{name: sha256(source / "adapter" / name) for name in payloads},
|
| 45 |
+
**{name: sha256(source / "mlx/bf16" / name) for name in ("LICENSE", "NOTICE", "MODIFICATIONS.md")},
|
| 46 |
+
"backbone/config.json": sha256(base / "config.json"),
|
| 47 |
+
"backbone/model.safetensors": "historical-serialization-hash",
|
| 48 |
+
}
|
| 49 |
+
spec = copy.deepcopy(hub.RELEASE)
|
| 50 |
+
spec["reference_files"] = reference
|
| 51 |
+
spec["base_manifest_sha256"] = sha256(manifest)
|
| 52 |
+
spec["source_hashes"] = {"adapter/" + name: reference[name] for name in payloads}
|
| 53 |
+
spec["binding_contract"]["base_hashes"] = {"config.json": sha256(base / "config.json")}
|
| 54 |
+
monkeypatch.setattr(hub, "RELEASE", spec)
|
| 55 |
+
monkeypatch.setattr(hub, "MANIFEST", manifest)
|
| 56 |
+
calls = []
|
| 57 |
+
|
| 58 |
+
def convert(root, output):
|
| 59 |
+
assert Path(root) == base
|
| 60 |
+
calls.append("convert")
|
| 61 |
+
output.mkdir()
|
| 62 |
+
(output / "config.json").write_bytes((base / "config.json").read_bytes())
|
| 63 |
+
(output / "model.safetensors").write_bytes(b"new serialization")
|
| 64 |
+
|
| 65 |
+
monkeypatch.setattr(hub, "convert_bf16", convert)
|
| 66 |
+
return source, base, tmp_path / "model", calls
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def test_adapter_only_hub_then_verified_offline_reuse(release, monkeypatch):
|
| 70 |
+
source, base, output, calls = release
|
| 71 |
+
|
| 72 |
+
def download(repo_id, **kwargs):
|
| 73 |
+
assert repo_id == "DoccyHealth/Solomon"
|
| 74 |
+
assert kwargs["revision"] == REVISION
|
| 75 |
+
assert "adapter/**" in kwargs["allow_patterns"]
|
| 76 |
+
assert not any(
|
| 77 |
+
"backbone" in pattern or pattern == "mlx/bf16/**" for pattern in kwargs["allow_patterns"]
|
| 78 |
+
)
|
| 79 |
+
calls.append("download")
|
| 80 |
+
return str(source)
|
| 81 |
+
|
| 82 |
+
monkeypatch.setattr(hub, "snapshot_download", download)
|
| 83 |
+
assert hub.prepare_from_hub(output, revision=REVISION, base_dir=base) == output
|
| 84 |
+
binding = hub.verify_prepared(output)
|
| 85 |
+
assert binding["hub_preparation"]["revision"] == REVISION
|
| 86 |
+
assert binding["adapter_scale"] == 2.0
|
| 87 |
+
assert binding["files"]["backbone/model.safetensors"] != "historical-serialization-hash"
|
| 88 |
+
original_binding = (output / "binding.json").read_bytes()
|
| 89 |
+
assert hub.prepare_from_hub(output, revision=REVISION) == output
|
| 90 |
+
assert calls == ["download", "convert"]
|
| 91 |
+
assert (output / "binding.json").read_bytes() == original_binding
|
| 92 |
+
(output / "adapter.safetensors").write_bytes(b"corrupt")
|
| 93 |
+
with pytest.raises(ValueError, match="checksum mismatch"):
|
| 94 |
+
hub.prepare_from_hub(output, revision=REVISION)
|
| 95 |
+
assert calls == ["download", "convert"]
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def test_base_download_pinned_and_verified_before_conversion(release, monkeypatch):
|
| 99 |
+
source, _base, output, calls = release
|
| 100 |
+
|
| 101 |
+
def download(repo_id, **kwargs):
|
| 102 |
+
calls.append(repo_id)
|
| 103 |
+
if repo_id == "DoccyHealth/Solomon":
|
| 104 |
+
return source
|
| 105 |
+
assert repo_id == "Qwen/Qwen3.8-27B"
|
| 106 |
+
assert kwargs["revision"] == hub.RELEASE["binding_contract"]["base_revision"]
|
| 107 |
+
target = Path(kwargs["local_dir"])
|
| 108 |
+
target.mkdir(parents=True)
|
| 109 |
+
(target / "config.json").write_bytes(b"corrupt base")
|
| 110 |
+
|
| 111 |
+
monkeypatch.setattr(hub, "snapshot_download", download)
|
| 112 |
+
with pytest.raises(ValueError, match="Base size mismatch"):
|
| 113 |
+
hub.prepare_from_hub(output, revision=REVISION)
|
| 114 |
+
assert calls == ["DoccyHealth/Solomon", "Qwen/Qwen3.8-27B"]
|
| 115 |
+
assert not output.exists()
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def test_bad_source_stops_before_base_or_conversion(release):
|
| 119 |
+
source, base, output, calls = release
|
| 120 |
+
(source / "adapter/heads.npz").write_bytes(b"corrupt")
|
| 121 |
+
with pytest.raises(ValueError, match="source checksum mismatch"):
|
| 122 |
+
hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base)
|
| 123 |
+
assert not calls and not output.exists()
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def test_converter_failure_is_atomic_and_retryable(release, monkeypatch):
|
| 127 |
+
source, base, output, calls = release
|
| 128 |
+
convert = hub.convert_bf16
|
| 129 |
+
|
| 130 |
+
def fail(*args):
|
| 131 |
+
args[1].mkdir()
|
| 132 |
+
(args[1] / "partial").write_bytes(b"partial output")
|
| 133 |
+
raise RuntimeError("conversion failed")
|
| 134 |
+
|
| 135 |
+
monkeypatch.setattr(hub, "convert_bf16", fail)
|
| 136 |
+
with pytest.raises(RuntimeError, match="conversion failed"):
|
| 137 |
+
hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base)
|
| 138 |
+
assert not output.exists()
|
| 139 |
+
assert list(output.parent.glob(".model.*")) == [output.parent / ".model.prepare.lock"]
|
| 140 |
+
monkeypatch.setattr(hub, "convert_bf16", convert)
|
| 141 |
+
hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base)
|
| 142 |
+
assert calls == ["convert"]
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def test_mutable_revision_and_converter_drift_fail_closed(release, monkeypatch):
|
| 146 |
+
source, base, output, calls = release
|
| 147 |
+
for revision in (None, "main", "abc123", "A" * 40):
|
| 148 |
+
with pytest.raises(ValueError, match="commit SHA"):
|
| 149 |
+
hub.prepare_from_hub(output, revision=revision)
|
| 150 |
+
monkeypatch.setattr(hub, "code_identity", lambda: "changed source")
|
| 151 |
+
with pytest.raises(ValueError, match="Converter source identity"):
|
| 152 |
+
hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base)
|
| 153 |
+
assert not calls
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def test_bound_paths_cannot_escape_output(release):
|
| 157 |
+
source, base, output, _calls = release
|
| 158 |
+
hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base)
|
| 159 |
+
external = output.parent / "external"
|
| 160 |
+
target = output / "backbone/model.safetensors"
|
| 161 |
+
target.rename(external)
|
| 162 |
+
target.symlink_to(external)
|
| 163 |
+
with pytest.raises(ValueError, match="checksum mismatch"):
|
| 164 |
+
hub.verify_prepared(output)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def test_simultaneous_preparations_fail_without_mutation(release):
|
| 168 |
+
source, base, output, calls = release
|
| 169 |
+
with (output.parent / ".model.prepare.lock").open("a") as handle:
|
| 170 |
+
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
| 171 |
+
with pytest.raises(RuntimeError, match="Another process"):
|
| 172 |
+
hub.prepare_from_snapshot(source, output, revision=REVISION, base_dir=base)
|
| 173 |
+
assert not output.exists() and not calls
|
mlx/tests/test_metal_primitives.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import mlx.core as mx
|
| 2 |
+
import numpy as np
|
| 3 |
+
from mlx import nn
|
| 4 |
+
from mlx_vlm.models.cache import ArraysCache, KVCache
|
| 5 |
+
|
| 6 |
+
from solomon_mlx.engine import SwitchLoRA, fork_cache
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_question_cache_isolation():
|
| 10 |
+
kv = KVCache()
|
| 11 |
+
kv.update_and_fetch(mx.ones((1, 2, 7, 4)), mx.ones((1, 2, 7, 4)))
|
| 12 |
+
recurrent = ArraysCache(2)
|
| 13 |
+
recurrent.cache = [mx.ones((1, 3, 4)), mx.ones((1, 2, 4, 4), dtype=mx.float32)]
|
| 14 |
+
mx.eval(kv.state, recurrent.state)
|
| 15 |
+
branch = fork_cache([kv, recurrent])
|
| 16 |
+
branch[0].update_and_fetch(mx.full((1, 2, 2, 4), 9), mx.full((1, 2, 2, 4), 8))
|
| 17 |
+
branch[1].cache[0][:] = 12
|
| 18 |
+
branch[1].cache[1][:] = 15
|
| 19 |
+
mx.eval(branch[0].state, branch[1].state)
|
| 20 |
+
assert kv.offset == 7
|
| 21 |
+
np.testing.assert_array_equal(np.asarray(kv.keys[:, :, :7]), 1)
|
| 22 |
+
for array in recurrent.cache:
|
| 23 |
+
np.testing.assert_array_equal(np.asarray(array), 1)
|
| 24 |
+
# A second independent question starts at the original prefix.
|
| 25 |
+
assert fork_cache([kv, recurrent])[0].offset == 7
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_lora_boundary_multiplier_and_instance_isolation():
|
| 29 |
+
context = {"start": None}
|
| 30 |
+
base = nn.Linear(2, 3, bias=False)
|
| 31 |
+
base.weight = mx.zeros((3, 2), dtype=mx.bfloat16)
|
| 32 |
+
layer = SwitchLoRA(base, mx.ones((2, 1)), mx.ones((1, 3)), context)
|
| 33 |
+
x = mx.ones((1, 4, 2), dtype=mx.bfloat16)
|
| 34 |
+
np.testing.assert_array_equal(np.asarray(layer(x).astype(mx.float32)), 0)
|
| 35 |
+
context["start"] = 2
|
| 36 |
+
out = np.asarray(layer(x).astype(mx.float32))
|
| 37 |
+
np.testing.assert_array_equal(out[:, :2], 0)
|
| 38 |
+
np.testing.assert_array_equal(out[:, 2:], 4)
|
| 39 |
+
other = SwitchLoRA(base, mx.ones((2, 1)), mx.ones((1, 3)), {"start": None})
|
| 40 |
+
np.testing.assert_array_equal(np.asarray(other(x).astype(mx.float32)), 0)
|
mlx/tests/test_parity.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Keep the user-selected parity workflow free of calibration jobs."""
|
| 2 |
+
|
| 3 |
+
import importlib.util
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_parity_keeps_branches_whose_gold_label_is_reserved():
|
| 8 |
+
script = Path(__file__).resolve().parents[1] / "scripts/check_cuda_parity.py"
|
| 9 |
+
spec = importlib.util.spec_from_file_location("parity_checker", script)
|
| 10 |
+
checker = importlib.util.module_from_spec(spec)
|
| 11 |
+
spec.loader.exec_module(checker)
|
| 12 |
+
row = {
|
| 13 |
+
"task": "single",
|
| 14 |
+
"n": 4,
|
| 15 |
+
"head_key": "single/choiceR",
|
| 16 |
+
"gold": 3,
|
| 17 |
+
"letter_logits": [2, 0, 100, 100],
|
| 18 |
+
}
|
| 19 |
+
probabilities = checker.decision_probabilities(row, 1.0)
|
| 20 |
+
assert len(probabilities) == 2
|
| 21 |
+
assert probabilities.argmax() == 0
|
| 22 |
+
assert probabilities[0] > 0.88
|
| 23 |
+
|
| 24 |
+
|