imtk commited on
Commit
98f6291
·
verified ·
1 Parent(s): e339657

Publish validated dynamic FP32 ONNX Runtime graph

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ onnx/model.onnx.data filter=lfs diff=lfs merge=lfs -text
37
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
ATTRIBUTION.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Attribution
2
+
3
+ The source model and Python client are from
4
+ [iApp Technology / OpenThai](https://github.com/iapp-technology/openthai-systemone)
5
+ and are distributed under Apache-2.0.
6
+
7
+ - Model: `iapp/OpenThai-SystemOne`
8
+ - Model revision: `f3709948b5e3cc9606a57e74ba62b7a639d17dd3`
9
+ - Client repository: `iapp-technology/openthai-systemone`
10
+ - Client revision: `5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf`
11
+ - Backbone: Qwen3.5 text tower as identified by the source configuration
12
+
13
+ This project modifies the inference implementation by constructing an ONNX
14
+ Runtime-specific graph with dynamic dimensions, internal request state, and the source model's calibrated decision head.
15
+
INSTRUCTIONS.md ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Convert OpenThai-SystemOne to dynamic ONNX
2
+
3
+ These are the maintainer instructions for converting the pinned
4
+ `iapp/OpenThai-SystemOne` checkpoint, validating the result, and uploading it
5
+ to Hugging Face. For application-side `OnnxSystemOneClient` usage, see
6
+ [README.md](README.md).
7
+
8
+ Run every command from the repository root. The conversion and release code is
9
+ kept in `src/openthai_systemone_onnx_tools`; the independent runtime client is
10
+ in `src/openthai_systemone_onnx`.
11
+
12
+ ## 1. Create the conversion environment
13
+
14
+ ```bash
15
+ uv python pin 3.12
16
+ uv sync --extra tools --extra dev --frozen
17
+ ```
18
+
19
+ The first command selects Python 3.12. The second creates `.venv`, installs the
20
+ runtime package, adds the `tools` dependencies used to build and inspect ONNX,
21
+ and adds pytest from the `dev` extra. `--frozen` makes the operation fail if
22
+ `uv.lock` and `pyproject.toml` disagree instead of silently changing versions.
23
+
24
+ Important pinned versions include ONNX Runtime 1.30.0, ONNX Runtime GenAI
25
+ 0.16.0, source model revision
26
+ `f3709948b5e3cc9606a57e74ba62b7a639d17dd3`, and upstream client commit
27
+ `5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf`.
28
+
29
+ ## 2. Authenticate with Hugging Face
30
+
31
+ ```bash
32
+ uv run hf auth login
33
+ ```
34
+
35
+ This interactively stores a Hugging Face token. Downloading the public source
36
+ does not require write access; uploading the finished artifact does.
37
+
38
+ ## 3. Export the model
39
+
40
+ ```bash
41
+ uv run openthai-systemone-export \
42
+ --output artifacts/OpenThai-SystemOne-ONNX \
43
+ --cache-dir .cache/huggingface
44
+ ```
45
+
46
+ This command downloads the pinned checkpoint, loads its safetensor weights,
47
+ builds the dynamic Qwen3.5 graph, appends the calibrated SystemOne head, and
48
+ writes:
49
+
50
+ - `onnx/model.onnx` for graph structure;
51
+ - `onnx/model.onnx.data` for external FP32 weights;
52
+ - tokenizer files and the separated helper packages;
53
+ - license, attribution, model card, and usage documentation;
54
+ - `artifact-manifest.json` with revisions, limits, tensor schema, operator
55
+ requirements, and checksums.
56
+
57
+ The graph keeps symbolic batch, sequence, and question dimensions. It derives
58
+ MRoPE positions from `attention_mask` and initializes KV, convolution, and
59
+ recurrent state internally for every complete request.
60
+
61
+ ## 4. Run fast compatibility and exporter tests
62
+
63
+ ```bash
64
+ uv run openthai-systemone-validate \
65
+ --artifact artifacts/OpenThai-SystemOne-ONNX \
66
+ --client
67
+ ```
68
+
69
+ This checks upstream constructor/method signatures, response-class identity,
70
+ serialization, typed and raw questions, state formats, validation errors,
71
+ single/batch equivalence, automatic and explicit permutations, accounting,
72
+ exporter graph construction, and validator device diagnostics. It records the
73
+ result as `client_compatibility` in `validation-report.json`.
74
+
75
+ ## 5. Run normal PyTorch/ONNX parity
76
+
77
+ On CPU, including macOS:
78
+
79
+ ```bash
80
+ PYTORCH_PARITY_DEVICE=cpu \
81
+ ORT_PARITY_DEVICE=cpu \
82
+ uv run openthai-systemone-validate \
83
+ --artifact artifacts/OpenThai-SystemOne-ONNX \
84
+ --normal
85
+ ```
86
+
87
+ `PYTORCH_PARITY_DEVICE` selects the reference-model device.
88
+ `ORT_PARITY_DEVICE` selects the ONNX Runtime provider. The command reruns the
89
+ fast tests and then compares FP32 logits and probabilities at lengths 63, 64,
90
+ 65, 511, 512, 513, 2,048, and 8,192. It requires `rtol=1e-3`, `atol=1e-4`, and
91
+ identical winning slots.
92
+
93
+ On Apple Silicon, ONNX must remain on CPU because the required Microsoft
94
+ contrib operators do not have an ONNX Runtime MPS implementation. Check whether
95
+ the installed PyTorch build can use MPS:
96
+
97
+ ```bash
98
+ uv run python -c 'import torch; print(torch.backends.mps.is_available())'
99
+ ```
100
+
101
+ If this prints `True`, the PyTorch reference half may use the Apple GPU:
102
+
103
+ ```bash
104
+ PYTORCH_ENABLE_MPS_FALLBACK=1 \
105
+ PYTORCH_PARITY_DEVICE=mps \
106
+ ORT_PARITY_DEVICE=cpu \
107
+ uv run openthai-systemone-validate \
108
+ --artifact artifacts/OpenThai-SystemOne-ONNX \
109
+ --normal
110
+ ```
111
+
112
+ `PYTORCH_ENABLE_MPS_FALLBACK=1` permits unsupported PyTorch operations to fall
113
+ back to CPU. If the availability check prints `False`, use the all-CPU command.
114
+
115
+ ## 6. Validate the dynamic 262,144-token structure
116
+
117
+ ```bash
118
+ uv run openthai-systemone-validate \
119
+ --artifact artifacts/OpenThai-SystemOne-ONNX \
120
+ --structural
121
+ ```
122
+
123
+ This runs the ONNX checker, creates an ONNX Runtime CPU session, verifies the
124
+ symbolic public dimensions, required contrib operator counts, dynamic position
125
+ construction, internal state, manifest boundary, and artifact checksums. It
126
+ does not attempt a 262,144-token numerical inference. Lengths above 65,536 are
127
+ structurally validated only.
128
+
129
+ ## 7. Prepare a CUDA host for the slow certification
130
+
131
+ The 65,536-token parity test is high-memory and should run on an NVIDIA Linux
132
+ or Windows machine. CUDA cannot run on macOS.
133
+
134
+ First create the environment as in step 1. Then replace the CPU ONNX Runtime
135
+ wheel with its GPU counterpart:
136
+
137
+ ```bash
138
+ uv pip uninstall onnxruntime
139
+ uv pip install onnxruntime-gpu==1.30.0
140
+ ```
141
+
142
+ The first command removes the CPU package. The second installs the build that
143
+ provides `CUDAExecutionProvider`.
144
+
145
+ Verify both CUDA stacks before starting the expensive test:
146
+
147
+ ```bash
148
+ uv run --no-sync python -c \
149
+ 'import torch, onnxruntime as ort; print("torch CUDA:", torch.cuda.is_available()); print("ORT providers:", ort.get_available_providers())'
150
+ ```
151
+
152
+ The output must include `torch CUDA: True` and `CUDAExecutionProvider`.
153
+ `--no-sync` is required after the manual wheel replacement; plain `uv run` may
154
+ synchronize from the lockfile and restore CPU-only `onnxruntime`.
155
+
156
+ ## 8. Run the slow 65,536-token parity test
157
+
158
+ ```bash
159
+ PYTORCH_PARITY_DEVICE=cuda \
160
+ ORT_PARITY_DEVICE=cuda \
161
+ uv run --no-sync openthai-systemone-validate \
162
+ --artifact artifacts/OpenThai-SystemOne-ONNX \
163
+ --slow
164
+ ```
165
+
166
+ This runs one numerical comparison at exactly 65,536 tokens. To reduce peak
167
+ GPU memory, the test executes PyTorch first, copies only its decision outputs
168
+ to CPU, deletes the reference model and clears its CUDA cache, and then creates
169
+ the ONNX Runtime session. Thus the two full model copies are not resident on
170
+ the GPU simultaneously.
171
+
172
+ If the process exits with signal 9, the host or driver likely exhausted
173
+ memory. Move the test to a larger-memory CUDA machine. If CUDA is requested on
174
+ macOS or a CPU-only ONNX Runtime installation, the validator stops before
175
+ pytest and explains how to correct the environment.
176
+
177
+ ## 9. Optionally run all validation stages together
178
+
179
+ After preparing the CUDA environment:
180
+
181
+ ```bash
182
+ PYTORCH_PARITY_DEVICE=cuda \
183
+ ORT_PARITY_DEVICE=cuda \
184
+ uv run --no-sync openthai-systemone-validate \
185
+ --artifact artifacts/OpenThai-SystemOne-ONNX \
186
+ --all
187
+ ```
188
+
189
+ This runs client/exporter tests, normal parity, slow parity, and structural
190
+ validation in sequence. Each stage records its command, timestamp, return code,
191
+ and test counts. The command stops at the first failed stage.
192
+
193
+ ## 10. Check the upload gate without uploading
194
+
195
+ ```bash
196
+ uv run --no-sync openthai-systemone-upload \
197
+ --artifact artifacts/OpenThai-SystemOne-ONNX
198
+ ```
199
+
200
+ This is a local dry run. It requires every validation stage to have at least
201
+ one executed test, zero skips, and a passing result. It then recomputes all
202
+ manifest hashes. It does not contact or modify Hugging Face.
203
+
204
+ ## 11. Upload the validated artifact
205
+
206
+ ```bash
207
+ uv run --no-sync openthai-systemone-upload \
208
+ --artifact artifacts/OpenThai-SystemOne-ONNX \
209
+ --repo-id imtk/OpenThai-SystemOne-ONNX \
210
+ --execute
211
+ ```
212
+
213
+ This repeats the gate check, creates the Hugging Face model repository if it
214
+ does not exist, and uploads the artifact directory. `--execute` is the switch
215
+ that permits the external write; without it the command remains a dry run.
216
+
217
+ ## Exported tensor contract
218
+
219
+ All inputs are `int64`; outputs are `float32`.
220
+
221
+ | Tensor | Shape |
222
+ |---|---|
223
+ | `input_ids` | `[batch, sequence]` |
224
+ | `attention_mask` | `[batch, sequence]` |
225
+ | `answer_positions` | `[batch, questions]` |
226
+ | `option_counts` | `[batch, questions]` |
227
+ | `qtypes` | `[batch, questions]` |
228
+ | `logits` | `[batch, questions, 256]` |
229
+ | `probabilities` | `[batch, questions, 256]` |
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
MODEL_CARD.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: onnxruntime
4
+ base_model: iapp/OpenThai-SystemOne
5
+ tags:
6
+ - onnx
7
+ - onnxruntime
8
+ - thai
9
+ - qwen3.5
10
+ ---
11
+
12
+ # OpenThai-SystemOne ONNX
13
+
14
+ This is an FP32 ONNX Runtime conversion of `iapp/OpenThai-SystemOne` revision
15
+ `f3709948b5e3cc9606a57e74ba62b7a639d17dd3`.
16
+
17
+ The sequence dimension is symbolic. The accepted structural limit is 262,144
18
+ tokens, inherited from the Qwen3.5 text backbone. PyTorch/ONNX numerical parity
19
+ is validated through 65,536 tokens only. Larger requests may be infeasible on
20
+ available hardware and have structural—not numerical—validation.
21
+
22
+ The model requires ONNX Runtime 1.30.0. It uses Microsoft contrib operators and
23
+ will not run in arbitrary ONNX engines.
24
+
25
+ ## Usage
26
+
27
+ Clone this model repository and run `uv sync --frozen` once to install its
28
+ locked helper package and runtime dependencies, then:
29
+
30
+ ```python
31
+ from openthai_systemone_onnx import OnnxSystemOneClient, Choice
32
+
33
+ client = OnnxSystemOneClient("imtk/OpenThai-SystemOne-ONNX")
34
+ result = client.system_one(
35
+ "บริการดีมาก",
36
+ {"sentiment": Choice(instructions="Choose sentiment", criteria={"positive": None, "negative": None})},
37
+ )
38
+ ```
39
+
40
+ The client subclasses the upstream `SystemOneClient` at commit
41
+ `5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf`. Formatting, public methods,
42
+ response classes, decoding, confidence, permutations, and usage accounting are
43
+ inherited unchanged.
44
+
45
+ ## Validation
46
+
47
+ The uploaded `validation-report.json` is the machine-readable source of truth.
48
+ Upload is blocked unless the client suite, standard parity suite, 65,536-token
49
+ parity test, and structural 262,144-token checks all pass.
50
+
51
+ See `artifact-manifest.json` for the tensor schema, operator domains, runtime
52
+ versions, source revisions, limits, and SHA-256 checksums.
53
+
54
+ ## Limitations
55
+
56
+ - FP32 only.
57
+ - CPU and CUDA execution providers only through the packaged client.
58
+ - ONNX Runtime contrib operators make this artifact runtime-specific.
59
+ - The model performs complete-request inference; generation caches are
60
+ deliberately internal and reset for every call.
61
+ - 262,144 tokens is an accepted graph boundary, not a performance guarantee.
README.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: onnxruntime
4
+ base_model: iapp/OpenThai-SystemOne
5
+ tags:
6
+ - onnx
7
+ - onnxruntime
8
+ - thai
9
+ - qwen3.5
10
+ ---
11
+
12
+ # OpenThai-SystemOne ONNX
13
+
14
+ This is an FP32 ONNX Runtime conversion of `iapp/OpenThai-SystemOne` revision
15
+ `f3709948b5e3cc9606a57e74ba62b7a639d17dd3`.
16
+
17
+ The sequence dimension is symbolic. The accepted structural limit is 262,144
18
+ tokens, inherited from the Qwen3.5 text backbone. PyTorch/ONNX numerical parity
19
+ is validated through 65,536 tokens only. Larger requests may be infeasible on
20
+ available hardware and have structural—not numerical—validation.
21
+
22
+ The model requires ONNX Runtime 1.30.0. It uses Microsoft contrib operators and
23
+ will not run in arbitrary ONNX engines.
24
+
25
+ ## Usage
26
+
27
+ Clone this model repository and run `uv sync --frozen` once to install its
28
+ locked helper package and runtime dependencies, then:
29
+
30
+ ```python
31
+ from openthai_systemone_onnx import OnnxSystemOneClient, Choice
32
+
33
+ client = OnnxSystemOneClient("imtk/OpenThai-SystemOne-ONNX")
34
+ result = client.system_one(
35
+ "บริการดีมาก",
36
+ {"sentiment": Choice(instructions="Choose sentiment", criteria={"positive": None, "negative": None})},
37
+ )
38
+ ```
39
+
40
+ The client subclasses the upstream `SystemOneClient` at commit
41
+ `5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf`. Formatting, public methods,
42
+ response classes, decoding, confidence, permutations, and usage accounting are
43
+ inherited unchanged.
44
+
45
+ ## Validation
46
+
47
+ The uploaded `validation-report.json` is the machine-readable source of truth.
48
+ Upload is blocked unless the client suite, standard parity suite, 65,536-token
49
+ parity test, and structural 262,144-token checks all pass.
50
+
51
+ See `artifact-manifest.json` for the tensor schema, operator domains, runtime
52
+ versions, source revisions, limits, and SHA-256 checksums.
53
+
54
+ ## Limitations
55
+
56
+ - FP32 only.
57
+ - CPU and CUDA execution providers only through the packaged client.
58
+ - ONNX Runtime contrib operators make this artifact runtime-specific.
59
+ - The model performs complete-request inference; generation caches are
60
+ deliberately internal and reset for every call.
61
+ - 262,144 tokens is an accepted graph boundary, not a performance guarantee.
artifact-manifest.json ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "checksums": {
3
+ "ATTRIBUTION.md": "9550b4a6711bb74c3bd05d29093e819c53c1501fab6e45c955b7c8bca22a8feb",
4
+ "INSTRUCTIONS.md": "ec52b251d5e3caa4036b322bc8cf0d0478649ad3ad36e297804c5910eb33da25",
5
+ "LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
6
+ "MODEL_CARD.md": "0dae46157f2e819c25a2a1b31ae0916f4024a74441dce808f456005a4c0a0475",
7
+ "README.md": "0dae46157f2e819c25a2a1b31ae0916f4024a74441dce808f456005a4c0a0475",
8
+ "chat_template.jinja": "273d8e0e683b885071fb17e08d71e5f2a5ddfb5309756181681de4f5a1822d80",
9
+ "examples/inference.py": "d704adb6199bdcab25e9b1a9c32b2faf44ea5c6dbab239bd82ced2ea2dbd3bd2",
10
+ "onnx/model.onnx": "725e6b85effd2a861ccdf7ea15a4d8f05a089d4ec3bc34c6a058abace65f9532",
11
+ "onnx/model.onnx.data": "259b8a487cf1e4cecdc840cfc4eefa288f56538fa1e6b69376461d0ffbb0c9f3",
12
+ "pyproject.toml": "5bbd1a1906fa79b305567f08bc9f0442a8bbde5d6ab8e52ba0502b8c4757e574",
13
+ "src/openthai_systemone_onnx/__init__.py": "c507536cc93fd376c15f3060446a429231253c98cce18bd85663978ae7b29dd7",
14
+ "src/openthai_systemone_onnx/artifact.py": "6aff925ddf6b6d0453289fce1b78ee3b0b75a56dee06487655223553476bf773",
15
+ "src/openthai_systemone_onnx/client.py": "6e2e4e4d7a8eaec9ed75d325d0be67a48512c4f18b78a21c2a8378a295194669",
16
+ "src/openthai_systemone_onnx/constants.py": "289574a45fb0e50900733ac5f678dd8193df9e211399af65a97f43d928977058",
17
+ "src/openthai_systemone_onnx/export.py": "8b1bfe477b72af0fd540cf9b5c794c97fee4c0a5624d3ccc52bc279a94b77ba3",
18
+ "src/openthai_systemone_onnx/upload.py": "50bd6cd53b9799e178d6677d95dd06938c772223489e5fa96fc1eade0fed1442",
19
+ "src/openthai_systemone_onnx/validation.py": "e661cabc9f71a84b5ffd30eab1569c5863e2cfeed11ffac6eb33e5e46eb06c25",
20
+ "src/openthai_systemone_onnx_tools/__init__.py": "b1fa648f1ce1a3c264f0a423a32db18939f8f4287f39abef01b2195525adaef7",
21
+ "src/openthai_systemone_onnx_tools/artifact.py": "9cc30acca36447aaa263b82806c4935ef0c017fd6ca5e068a5c632279a73fc18",
22
+ "src/openthai_systemone_onnx_tools/constants.py": "a71bdf2666ee0eb4724ee3dd3b2099d4b5cdf44cfcfe57110cf1eeeca6e57640",
23
+ "src/openthai_systemone_onnx_tools/export.py": "8d3c850e8d2707b4b6d0ce1d342406a6f65d19b2a3d74d1a5e8a5e5e5c1dfd8c",
24
+ "src/openthai_systemone_onnx_tools/upload.py": "50bd6cd53b9799e178d6677d95dd06938c772223489e5fa96fc1eade0fed1442",
25
+ "src/openthai_systemone_onnx_tools/validation.py": "592dc5fc91026b9447262f5ebec68817eef39eb61c3993a04d2d3f4929af4c89",
26
+ "tokenizer.json": "d3400f1532d54f98d82b24c503e61908aa66f84e22e566e59fc80494add3a5c3",
27
+ "tokenizer_config.json": "171ecbe7ddae98d11840698f7df2b8d5b4722139db0f0620d3bbf429bd656250",
28
+ "uv.lock": "ef30a5bb85ede6aa473859b15d26b1ff8e6061fea951ecdba4bf09eaf110f683"
29
+ },
30
+ "created_at": "2026-09-25T07:16:52.369290+00:00",
31
+ "external_data": "onnx/model.onnx.data",
32
+ "format_version": 1,
33
+ "graph": "onnx/model.onnx",
34
+ "max_position_embeddings": 262144,
35
+ "numerically_validated_max_tokens": 65536,
36
+ "operator_domains": {
37
+ "": 23,
38
+ "com.microsoft": 1
39
+ },
40
+ "precision": "fp32",
41
+ "required_contrib_operators": [
42
+ "CausalConvWithState",
43
+ "GroupQueryAttention",
44
+ "LinearAttention"
45
+ ],
46
+ "runtime": {
47
+ "onnxruntime": "1.30.0",
48
+ "onnxruntime_genai_builder": "0.16.0",
49
+ "portable_to_arbitrary_onnx_engines": false
50
+ },
51
+ "source": {
52
+ "repo_id": "iapp/OpenThai-SystemOne",
53
+ "revision": "f3709948b5e3cc9606a57e74ba62b7a639d17dd3"
54
+ },
55
+ "state_contract": "zero convolution, recurrent, and KV state is initialized inside the graph per request",
56
+ "target_repository": "imtk/OpenThai-SystemOne-ONNX",
57
+ "tensor_schema": {
58
+ "inputs": {
59
+ "answer_positions": [
60
+ "batch",
61
+ "questions"
62
+ ],
63
+ "attention_mask": [
64
+ "batch",
65
+ "sequence"
66
+ ],
67
+ "input_ids": [
68
+ "batch",
69
+ "sequence"
70
+ ],
71
+ "option_counts": [
72
+ "batch",
73
+ "questions"
74
+ ],
75
+ "qtypes": [
76
+ "batch",
77
+ "questions"
78
+ ]
79
+ },
80
+ "outputs": {
81
+ "logits": [
82
+ "batch",
83
+ "questions",
84
+ 256
85
+ ],
86
+ "probabilities": [
87
+ "batch",
88
+ "questions",
89
+ 256
90
+ ]
91
+ }
92
+ },
93
+ "upstream_client": {
94
+ "repository": "iapp-technology/openthai-systemone",
95
+ "revision": "5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf"
96
+ }
97
+ }
chat_template.jinja ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set image_count = namespace(value=0) %}
2
+ {%- set video_count = namespace(value=0) %}
3
+ {%- macro render_content(content, do_vision_count, is_system_content=false) %}
4
+ {%- if content is string %}
5
+ {{- content }}
6
+ {%- elif content is iterable and content is not mapping %}
7
+ {%- for item in content %}
8
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
9
+ {%- if is_system_content %}
10
+ {{- raise_exception('System message cannot contain images.') }}
11
+ {%- endif %}
12
+ {%- if do_vision_count %}
13
+ {%- set image_count.value = image_count.value + 1 %}
14
+ {%- endif %}
15
+ {%- if add_vision_id %}
16
+ {{- 'Picture ' ~ image_count.value ~ ': ' }}
17
+ {%- endif %}
18
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
19
+ {%- elif 'video' in item or item.type == 'video' %}
20
+ {%- if is_system_content %}
21
+ {{- raise_exception('System message cannot contain videos.') }}
22
+ {%- endif %}
23
+ {%- if do_vision_count %}
24
+ {%- set video_count.value = video_count.value + 1 %}
25
+ {%- endif %}
26
+ {%- if add_vision_id %}
27
+ {{- 'Video ' ~ video_count.value ~ ': ' }}
28
+ {%- endif %}
29
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
30
+ {%- elif 'text' in item %}
31
+ {{- item.text }}
32
+ {%- else %}
33
+ {{- raise_exception('Unexpected item type in content.') }}
34
+ {%- endif %}
35
+ {%- endfor %}
36
+ {%- elif content is none or content is undefined %}
37
+ {{- '' }}
38
+ {%- else %}
39
+ {{- raise_exception('Unexpected content type.') }}
40
+ {%- endif %}
41
+ {%- endmacro %}
42
+ {%- if not messages %}
43
+ {{- raise_exception('No messages provided.') }}
44
+ {%- endif %}
45
+ {%- if tools and tools is iterable and tools is not mapping %}
46
+ {{- '<|im_start|>system\n' }}
47
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
48
+ {%- for tool in tools %}
49
+ {{- "\n" }}
50
+ {{- tool | tojson }}
51
+ {%- endfor %}
52
+ {{- "\n</tools>" }}
53
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
54
+ {%- if messages[0].role == 'system' %}
55
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
56
+ {%- if content %}
57
+ {{- '\n\n' + content }}
58
+ {%- endif %}
59
+ {%- endif %}
60
+ {{- '<|im_end|>\n' }}
61
+ {%- else %}
62
+ {%- if messages[0].role == 'system' %}
63
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
64
+ {{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
65
+ {%- endif %}
66
+ {%- endif %}
67
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
68
+ {%- for message in messages[::-1] %}
69
+ {%- set index = (messages|length - 1) - loop.index0 %}
70
+ {%- if ns.multi_step_tool and message.role == "user" %}
71
+ {%- set content = render_content(message.content, false)|trim %}
72
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
73
+ {%- set ns.multi_step_tool = false %}
74
+ {%- set ns.last_query_index = index %}
75
+ {%- endif %}
76
+ {%- endif %}
77
+ {%- endfor %}
78
+ {%- if ns.multi_step_tool %}
79
+ {{- raise_exception('No user query found in messages.') }}
80
+ {%- endif %}
81
+ {%- for message in messages %}
82
+ {%- set content = render_content(message.content, true)|trim %}
83
+ {%- if message.role == "system" %}
84
+ {%- if not loop.first %}
85
+ {{- raise_exception('System message must be at the beginning.') }}
86
+ {%- endif %}
87
+ {%- elif message.role == "user" %}
88
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
89
+ {%- elif message.role == "assistant" %}
90
+ {%- set reasoning_content = '' %}
91
+ {%- if message.reasoning_content is string %}
92
+ {%- set reasoning_content = message.reasoning_content %}
93
+ {%- else %}
94
+ {%- if '</think>' in content %}
95
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
96
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
97
+ {%- endif %}
98
+ {%- endif %}
99
+ {%- set reasoning_content = reasoning_content|trim %}
100
+ {%- if loop.index0 > ns.last_query_index %}
101
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
102
+ {%- else %}
103
+ {{- '<|im_start|>' + message.role + '\n' + content }}
104
+ {%- endif %}
105
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
106
+ {%- for tool_call in message.tool_calls %}
107
+ {%- if tool_call.function is defined %}
108
+ {%- set tool_call = tool_call.function %}
109
+ {%- endif %}
110
+ {%- if loop.first %}
111
+ {%- if content|trim %}
112
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
113
+ {%- else %}
114
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
115
+ {%- endif %}
116
+ {%- else %}
117
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
118
+ {%- endif %}
119
+ {%- if tool_call.arguments is defined %}
120
+ {%- for args_name, args_value in tool_call.arguments|items %}
121
+ {{- '<parameter=' + args_name + '>\n' }}
122
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
123
+ {{- args_value }}
124
+ {{- '\n</parameter>\n' }}
125
+ {%- endfor %}
126
+ {%- endif %}
127
+ {{- '</function>\n</tool_call>' }}
128
+ {%- endfor %}
129
+ {%- endif %}
130
+ {{- '<|im_end|>\n' }}
131
+ {%- elif message.role == "tool" %}
132
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
133
+ {{- '<|im_start|>user' }}
134
+ {%- endif %}
135
+ {{- '\n<tool_response>\n' }}
136
+ {{- content }}
137
+ {{- '\n</tool_response>' }}
138
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
139
+ {{- '<|im_end|>\n' }}
140
+ {%- elif loop.last %}
141
+ {{- '<|im_end|>\n' }}
142
+ {%- endif %}
143
+ {%- else %}
144
+ {{- raise_exception('Unexpected message role.') }}
145
+ {%- endif %}
146
+ {%- endfor %}
147
+ {%- if add_generation_prompt %}
148
+ {{- '<|im_start|>assistant\n' }}
149
+ {%- if enable_thinking is defined and enable_thinking is true %}
150
+ {{- '<think>\n' }}
151
+ {%- else %}
152
+ {{- '<think>\n\n</think>\n\n' }}
153
+ {%- endif %}
154
+ {%- endif %}
examples/inference.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openthai_systemone_onnx import Choice, Noul, OnnxSystemOneClient, Score
2
+
3
+
4
+ client = OnnxSystemOneClient("imtk/OpenThai-SystemOne-ONNX")
5
+ response = client.system_one(
6
+ "ร้านนี้บริการดีมาก แต่อาหารค่อนข้างช้า",
7
+ {
8
+ "sentiment": Choice(
9
+ instructions="What is the overall sentiment?",
10
+ criteria={"positive": None, "neutral": None, "negative": None},
11
+ ),
12
+ "recommend": Noul(instructions="Would the writer recommend the restaurant?"),
13
+ "quality": Score(
14
+ instructions="Rate the service quality.",
15
+ criteria=["poor", "acceptable", "good", "excellent"],
16
+ ),
17
+ },
18
+ )
19
+ print(response.model_dump_json(indent=2))
20
+
onnx/model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:725e6b85effd2a861ccdf7ea15a4d8f05a089d4ec3bc34c6a058abace65f9532
3
+ size 322508
onnx/model.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:259b8a487cf1e4cecdc840cfc4eefa288f56538fa1e6b69376461d0ffbb0c9f3
3
+ size 3078303744
pyproject.toml ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["hatchling==1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "openthai-systemone-onnx"
7
+ version = "0.1.0"
8
+ description = "Dynamic ONNX Runtime export and drop-in client for OpenThai-SystemOne"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12,<3.13"
11
+ license = {file = "LICENSE"}
12
+ authors = [{name = "OpenThai-SystemOne ONNX contributors"}]
13
+ dependencies = [
14
+ "huggingface-hub==1.5.0",
15
+ "numpy==2.3.3",
16
+ "onnxruntime==1.30.0",
17
+ "openthai-systemone @ git+https://github.com/iapp-technology/openthai-systemone.git@5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf",
18
+ "torch==2.8.0",
19
+ "transformers @ git+https://github.com/huggingface/transformers.git@856157a2f3e9594954310df18fdccc31ffddebe9",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ tools = [
24
+ "onnx==1.19.0",
25
+ "onnx-ir==0.1.3",
26
+ "onnxruntime-genai==0.16.0",
27
+ "safetensors==0.8.0",
28
+ ]
29
+ dev = [
30
+ "pytest==8.4.2",
31
+ "pytest-cov==7.0.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ openthai-systemone-export = "openthai_systemone_onnx_tools.export:main"
36
+ openthai-systemone-validate = "openthai_systemone_onnx_tools.validation:main"
37
+ openthai-systemone-upload = "openthai_systemone_onnx_tools.upload:main"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/openthai_systemone_onnx", "src/openthai_systemone_onnx_tools"]
41
+
42
+ [tool.hatch.metadata]
43
+ allow-direct-references = true
44
+
45
+ [tool.pytest.ini_options]
46
+ testpaths = ["tests"]
47
+ addopts = "-ra"
48
+ markers = [
49
+ "artifact: requires an exported ONNX artifact",
50
+ "parity: loads the source PyTorch model and ONNX graph",
51
+ "slow: high-memory 65,536-token parity validation",
52
+ ]
53
+
54
+ [tool.uv]
55
+ package = true
src/openthai_systemone_onnx/__init__.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Drop-in ONNX Runtime client for OpenThai-SystemOne."""
2
+
3
+ from openthai_systemone.types import (
4
+ Answer,
5
+ Choice,
6
+ ChoiceAnswer,
7
+ Noul,
8
+ NoulAnswer,
9
+ Question,
10
+ Score,
11
+ ScoreAnswer,
12
+ SystemOneRequest,
13
+ SystemOneResponse,
14
+ Usage,
15
+ )
16
+
17
+ from .client import OnnxSystemOneClient
18
+
19
+ __version__ = "0.1.0"
20
+
21
+ __all__ = [
22
+ "Answer",
23
+ "Choice",
24
+ "ChoiceAnswer",
25
+ "Noul",
26
+ "NoulAnswer",
27
+ "OnnxSystemOneClient",
28
+ "Question",
29
+ "Score",
30
+ "ScoreAnswer",
31
+ "SystemOneRequest",
32
+ "SystemOneResponse",
33
+ "Usage",
34
+ ]
35
+
src/openthai_systemone_onnx/artifact.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Artifact metadata and checksum helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .constants import (
12
+ MAX_POSITION_EMBEDDINGS,
13
+ NUMERICALLY_VALIDATED_MAX_TOKENS,
14
+ PUBLIC_TENSORS,
15
+ REQUIRED_CONTRIB_OPS,
16
+ REQUIRED_OPERATOR_DOMAINS,
17
+ REQUIRED_ORT_GENAI_VERSION,
18
+ REQUIRED_ORT_VERSION,
19
+ SOURCE_MODEL,
20
+ SOURCE_REVISION,
21
+ TARGET_REPOSITORY,
22
+ UPSTREAM_CLIENT_REVISION,
23
+ )
24
+
25
+
26
+ def sha256_file(path: Path, chunk_size: int = 8 * 1024 * 1024) -> str:
27
+ digest = hashlib.sha256()
28
+ with path.open("rb") as handle:
29
+ while block := handle.read(chunk_size):
30
+ digest.update(block)
31
+ return digest.hexdigest()
32
+
33
+
34
+ def artifact_checksums(root: Path) -> dict[str, str]:
35
+ ignored = {"artifact-manifest.json", "validation-report.json"}
36
+ return {
37
+ path.relative_to(root).as_posix(): sha256_file(path)
38
+ for path in sorted(root.rglob("*"))
39
+ if path.is_file() and path.relative_to(root).as_posix() not in ignored
40
+ }
41
+
42
+
43
+ def build_manifest(root: Path, *, extra: dict[str, Any] | None = None) -> dict[str, Any]:
44
+ manifest: dict[str, Any] = {
45
+ "format_version": 1,
46
+ "created_at": datetime.now(timezone.utc).isoformat(),
47
+ "source": {"repo_id": SOURCE_MODEL, "revision": SOURCE_REVISION},
48
+ "upstream_client": {
49
+ "repository": "iapp-technology/openthai-systemone",
50
+ "revision": UPSTREAM_CLIENT_REVISION,
51
+ },
52
+ "target_repository": TARGET_REPOSITORY,
53
+ "graph": "onnx/model.onnx",
54
+ "external_data": "onnx/model.onnx.data",
55
+ "precision": "fp32",
56
+ "max_position_embeddings": MAX_POSITION_EMBEDDINGS,
57
+ "numerically_validated_max_tokens": NUMERICALLY_VALIDATED_MAX_TOKENS,
58
+ "runtime": {
59
+ "onnxruntime": REQUIRED_ORT_VERSION,
60
+ "onnxruntime_genai_builder": REQUIRED_ORT_GENAI_VERSION,
61
+ "portable_to_arbitrary_onnx_engines": False,
62
+ },
63
+ "operator_domains": REQUIRED_OPERATOR_DOMAINS,
64
+ "required_contrib_operators": sorted(REQUIRED_CONTRIB_OPS),
65
+ "tensor_schema": PUBLIC_TENSORS,
66
+ "state_contract": "zero convolution, recurrent, and KV state is initialized inside the graph per request",
67
+ "checksums": artifact_checksums(root),
68
+ }
69
+ if extra:
70
+ manifest.update(extra)
71
+ return manifest
72
+
73
+
74
+ def write_manifest(root: Path, *, extra: dict[str, Any] | None = None) -> Path:
75
+ destination = root / "artifact-manifest.json"
76
+ destination.write_text(
77
+ json.dumps(build_manifest(root, extra=extra), indent=2, sort_keys=True) + "\n",
78
+ encoding="utf-8",
79
+ )
80
+ return destination
81
+
src/openthai_systemone_onnx/client.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ONNX implementation of the upstream local client.
2
+
3
+ Only initialization and ``_run`` differ from the pinned upstream client. All
4
+ formatting, permutation planning, decoding, confidence calculation, response
5
+ models, and token accounting remain upstream code.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from pathlib import Path
12
+ from types import SimpleNamespace
13
+ from typing import Optional, Sequence
14
+
15
+ import numpy as np
16
+ import onnxruntime as ort
17
+ import torch
18
+ from huggingface_hub import snapshot_download
19
+ from openthai_systemone.client import SystemOneClient
20
+ from openthai_systemone.formatting import Encoded, Formatter, collate
21
+ from openthai_systemone.modeling import QTYPE_INDEX
22
+ from transformers import AutoTokenizer
23
+
24
+ from .constants import ABSTAIN_SLOT, MAX_POSITION_EMBEDDINGS, REQUIRED_ORT_VERSION
25
+
26
+
27
+ def _resolve_artifact(model_path: str) -> tuple[Path, Path, dict]:
28
+ """Return ``(repository_root, onnx_path, manifest)`` for a local path or HF id."""
29
+ candidate = Path(model_path).expanduser()
30
+ if candidate.exists():
31
+ if candidate.is_file():
32
+ onnx_path = candidate.resolve()
33
+ root = onnx_path.parent.parent if onnx_path.parent.name == "onnx" else onnx_path.parent
34
+ else:
35
+ root = candidate.resolve()
36
+ onnx_path = root / "onnx" / "model.onnx"
37
+ else:
38
+ root = Path(
39
+ snapshot_download(
40
+ repo_id=model_path,
41
+ allow_patterns=[
42
+ "onnx/*",
43
+ "artifact-manifest.json",
44
+ "tokenizer*",
45
+ "special_tokens_map.json",
46
+ "chat_template.jinja",
47
+ ],
48
+ )
49
+ )
50
+ onnx_path = root / "onnx" / "model.onnx"
51
+
52
+ if not onnx_path.is_file():
53
+ raise FileNotFoundError(f"ONNX graph not found: {onnx_path}")
54
+ manifest_path = root / "artifact-manifest.json"
55
+ if not manifest_path.is_file():
56
+ raise FileNotFoundError(f"artifact manifest not found: {manifest_path}")
57
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
58
+ return root, onnx_path, manifest
59
+
60
+
61
+ class OnnxSystemOneClient(SystemOneClient):
62
+ """A drop-in ``SystemOneClient`` backed by one ONNX Runtime graph."""
63
+
64
+ def __init__(
65
+ self,
66
+ model_path: str,
67
+ *,
68
+ device: Optional[str] = None,
69
+ dtype: Optional[torch.dtype] = None,
70
+ max_total_tokens: int = 65536,
71
+ max_state_tokens: int = 32768,
72
+ model_name: str = "openthai-systemone",
73
+ ):
74
+ if dtype is not None and dtype is not torch.float32:
75
+ raise TypeError(
76
+ "OpenThai-SystemOne-ONNX is an FP32 artifact; dtype must be None or torch.float32"
77
+ )
78
+ if max_total_tokens < 1 or max_total_tokens > MAX_POSITION_EMBEDDINGS:
79
+ raise ValueError(
80
+ f"max_total_tokens must be in 1..{MAX_POSITION_EMBEDDINGS:,}, got {max_total_tokens:,}"
81
+ )
82
+ if max_state_tokens < 1 or max_state_tokens > MAX_POSITION_EMBEDDINGS:
83
+ raise ValueError(
84
+ f"max_state_tokens must be in 1..{MAX_POSITION_EMBEDDINGS:,}, got {max_state_tokens:,}"
85
+ )
86
+
87
+ normalized_device = "cpu" if device is None else str(device).lower()
88
+ provider_for_device = {
89
+ "cpu": "CPUExecutionProvider",
90
+ "cuda": "CUDAExecutionProvider",
91
+ }
92
+ if normalized_device not in provider_for_device:
93
+ raise ValueError("device must be None, 'cpu', or 'cuda'; MPS and other ONNX providers are unsupported")
94
+ provider = provider_for_device[normalized_device]
95
+ available = ort.get_available_providers()
96
+ if provider not in available:
97
+ raise RuntimeError(
98
+ f"{provider} is not installed; available ONNX Runtime providers: {available}"
99
+ )
100
+ if ort.__version__ != REQUIRED_ORT_VERSION:
101
+ raise RuntimeError(
102
+ f"This artifact requires onnxruntime=={REQUIRED_ORT_VERSION}; found {ort.__version__}"
103
+ )
104
+
105
+ root, onnx_path, manifest = _resolve_artifact(model_path)
106
+ artifact_limit = int(manifest.get("max_position_embeddings", 0))
107
+ if artifact_limit != MAX_POSITION_EMBEDDINGS:
108
+ raise ValueError(
109
+ f"artifact max_position_embeddings is {artifact_limit}, expected {MAX_POSITION_EMBEDDINGS}"
110
+ )
111
+
112
+ self.device = normalized_device
113
+ self.dtype = torch.float32
114
+ self.manifest = manifest
115
+ self.tok = AutoTokenizer.from_pretrained(root, local_files_only=True)
116
+ self.fmt = Formatter(
117
+ self.tok,
118
+ max_total_tokens=max_total_tokens,
119
+ max_state_tokens=max_state_tokens,
120
+ )
121
+ self.model_name = model_name
122
+ self.max_position_embeddings = artifact_limit
123
+ pad = self.tok.pad_token_id
124
+ self.pad_id = pad if pad is not None else (self.tok.eos_token_id or 0)
125
+
126
+ # The inherited decoder reads only model.config.abstain_slot.
127
+ self.model = SimpleNamespace(config=SimpleNamespace(abstain_slot=ABSTAIN_SLOT))
128
+ options = ort.SessionOptions()
129
+ options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
130
+ self.session = ort.InferenceSession(str(onnx_path), sess_options=options, providers=[provider])
131
+ expected_inputs = {"input_ids", "attention_mask", "answer_positions", "option_counts", "qtypes"}
132
+ actual_inputs = {item.name for item in self.session.get_inputs()}
133
+ if actual_inputs != expected_inputs:
134
+ raise ValueError(
135
+ f"unexpected ONNX input contract: expected {sorted(expected_inputs)}, got {sorted(actual_inputs)}"
136
+ )
137
+
138
+ @torch.no_grad()
139
+ def _run(self, encs: Sequence[Encoded]) -> torch.Tensor:
140
+ if not encs:
141
+ return torch.empty((0, 0, 256), dtype=torch.float32)
142
+ longest = max(enc.n_tokens for enc in encs)
143
+ if longest > self.max_position_embeddings:
144
+ raise ValueError(
145
+ f"encoded input has {longest:,} tokens; the backbone limit is "
146
+ f"{self.max_position_embeddings:,}"
147
+ )
148
+
149
+ batch = collate(encs, self.pad_id)
150
+ qtypes = torch.full_like(batch["option_counts"], -1)
151
+ for batch_index, encoded in enumerate(encs):
152
+ for question_index, spec in enumerate(encoded.specs):
153
+ qtypes[batch_index, question_index] = QTYPE_INDEX[spec.qtype]
154
+
155
+ feeds = {
156
+ "input_ids": batch["input_ids"].numpy().astype(np.int64, copy=False),
157
+ "attention_mask": batch["attention_mask"].numpy().astype(np.int64, copy=False),
158
+ "answer_positions": batch["answer_positions"].numpy().astype(np.int64, copy=False),
159
+ "option_counts": batch["option_counts"].numpy().astype(np.int64, copy=False),
160
+ "qtypes": qtypes.numpy().astype(np.int64, copy=False),
161
+ }
162
+ probabilities = self.session.run(["probabilities"], feeds)[0]
163
+ return torch.from_numpy(np.asarray(probabilities, dtype=np.float32))
164
+
src/openthai_systemone_onnx/constants.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """Constants required by the lightweight ONNX client runtime."""
2
+
3
+ MAX_POSITION_EMBEDDINGS = 262_144
4
+ ABSTAIN_SLOT = 255
5
+ N_SLOTS = 256
6
+
7
+ REQUIRED_ORT_VERSION = "1.30.0"
src/openthai_systemone_onnx/export.py ADDED
@@ -0,0 +1,576 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build the dynamic FP32 OpenThai-SystemOne ONNX Runtime graph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import onnx_ir as ir
11
+ import torch
12
+ from huggingface_hub import snapshot_download
13
+ from transformers import AutoConfig, AutoModel
14
+
15
+ from onnxruntime_genai.models.builders.qwen import Qwen35TextModel
16
+
17
+ from .artifact import write_manifest
18
+ from .constants import (
19
+ ABSTAIN_SLOT,
20
+ MAX_POSITION_EMBEDDINGS,
21
+ N_SLOTS,
22
+ REQUIRED_ORT_GENAI_VERSION,
23
+ SOURCE_MODEL,
24
+ SOURCE_REVISION,
25
+ )
26
+
27
+
28
+ class OpenThaiDynamicBuilder(Qwen35TextModel):
29
+ """Qwen3.5 builder with internal state and the OpenThai decision head."""
30
+
31
+ def make_node(self, op_type, inputs, outputs, *, name, domain="", **kwargs):
32
+ """Normalize ONNX integer attributes before ONNX IR serialization.
33
+
34
+ ONNX represents flags such as ``keepdims`` as INT attributes. ORT
35
+ GenAI 0.16 passes Python booleans for a few of them. ONNX IR 0.1.3
36
+ infers the correct INT attribute type but otherwise retains the bool,
37
+ which protobuf serialization rejects because ``bool`` is not accepted
38
+ for ``AttributeProto.i``.
39
+ """
40
+ # ORT GenAI 0.16 emits an ORT-private default-domain
41
+ # SimplifiedLayerNormalization op. ONNX Runtime can load that op, but
42
+ # the ONNX checker correctly rejects it because it has no ONNX schema.
43
+ # ONNX opset 23 standardizes the same RMSNorm operation, so lower the
44
+ # standalone form to RMSNormalization. The fused skip form remains the
45
+ # com.microsoft contrib op.
46
+ if op_type == "SimplifiedLayerNormalization" and not domain:
47
+ op_type = "RMSNormalization"
48
+ self.graph.opset_imports[""] = 23
49
+
50
+ normalized = {
51
+ key: int(value) if isinstance(value, (bool, np.bool_)) else value
52
+ for key, value in kwargs.items()
53
+ }
54
+ return super().make_node(
55
+ op_type,
56
+ inputs,
57
+ outputs,
58
+ name=name,
59
+ domain=domain,
60
+ **normalized,
61
+ )
62
+
63
+ def load_weights(self, input_path):
64
+ return AutoModel.from_pretrained(
65
+ input_path,
66
+ trust_remote_code=True,
67
+ local_files_only=True,
68
+ dtype=torch.float32,
69
+ ).eval()
70
+
71
+ def make_inputs_and_outputs(self):
72
+ self.input_shapes["position_ids"] = [3, "batch_size", "sequence_length"]
73
+
74
+ public_inputs = {
75
+ "input_ids": (ir.DataType.INT64, ["batch_size", "sequence_length"]),
76
+ "attention_mask": (ir.DataType.INT64, ["batch_size", "sequence_length"]),
77
+ "answer_positions": (ir.DataType.INT64, ["batch_size", "num_questions"]),
78
+ "option_counts": (ir.DataType.INT64, ["batch_size", "num_questions"]),
79
+ "qtypes": (ir.DataType.INT64, ["batch_size", "num_questions"]),
80
+ }
81
+ for name, (dtype, shape) in public_inputs.items():
82
+ self.model.graph.inputs.append(self.make_value(name, dtype=dtype, shape=shape))
83
+
84
+ output_shape = ["batch_size", "num_questions", N_SLOTS]
85
+ self.model.graph.outputs.append(self.make_value("logits", ir.DataType.FLOAT, output_shape))
86
+ self.model.graph.outputs.append(self.make_value("probabilities", ir.DataType.FLOAT, output_shape))
87
+
88
+ def _dynamic_batch_dimension(self) -> str:
89
+ shape_name = "/openthai/internal/input_shape"
90
+ self.make_shape(shape_name, "input_ids", shape=[2])
91
+ gather_name = "/openthai/internal/batch_dimension"
92
+ self.make_gather(
93
+ gather_name,
94
+ [f"{shape_name}/output_0", "/model/constants/INT64/0"],
95
+ dtype=ir.DataType.INT64,
96
+ shape=[],
97
+ axis=0,
98
+ )
99
+ unsqueeze_name = "/openthai/internal/batch_dimension_unsqueezed"
100
+ self.make_unsqueeze(
101
+ unsqueeze_name,
102
+ [f"{gather_name}/output_0", "/model/constants/INT64/[0]"],
103
+ dtype=ir.DataType.INT64,
104
+ shape=[1],
105
+ )
106
+ return f"{unsqueeze_name}/output_0"
107
+
108
+ def _zero_tensor(self, name: str, shape_input: str, dtype: ir.DataType, shape):
109
+ if dtype != ir.DataType.FLOAT:
110
+ raise TypeError(f"internal request state must be FP32, got {dtype}")
111
+ zero = ir.tensor(np.asarray([0], dtype=np.float32))
112
+ node_name = f"/openthai/internal/zero/{name.replace('.', '_')}"
113
+ self.make_node(
114
+ "ConstantOfShape",
115
+ inputs=[shape_input],
116
+ outputs=[name],
117
+ name=node_name,
118
+ value=zero,
119
+ )
120
+ self.make_value(name, dtype=dtype, shape=shape)
121
+
122
+ def _make_internal_request_state(self):
123
+ batch = self._dynamic_batch_dimension()
124
+ shape_specs = {
125
+ "kv": [self.num_kv_heads, 0, self.head_size],
126
+ "conv": [self.linear_conv_dim, self.linear_conv_kernel_dim - 1],
127
+ "recurrent": [
128
+ self.linear_num_value_heads,
129
+ self.linear_key_head_dim,
130
+ self.linear_value_head_dim,
131
+ ],
132
+ }
133
+ shape_paths: dict[str, str] = {}
134
+ for label, tail in shape_specs.items():
135
+ concat_name = f"/openthai/internal/{label}_state_shape"
136
+ self.make_concat(
137
+ concat_name,
138
+ [batch, f"/model/constants/INT64/{tail}"],
139
+ dtype=ir.DataType.INT64,
140
+ shape=[1 + len(tail)],
141
+ axis=0,
142
+ )
143
+ shape_paths[label] = f"{concat_name}/output_0"
144
+
145
+ for cache_kind in ("past_key_values.key", "past_key_values.value"):
146
+ for cache_name in self.input_names[cache_kind].values():
147
+ self._zero_tensor(
148
+ cache_name,
149
+ shape_paths["kv"],
150
+ self.io_dtype,
151
+ ["batch_size", self.num_kv_heads, 0, self.head_size],
152
+ )
153
+ for cache_name in self.input_names["past.conv"].values():
154
+ self._zero_tensor(
155
+ cache_name,
156
+ shape_paths["conv"],
157
+ self.io_dtype,
158
+ ["batch_size", self.linear_conv_dim, self.linear_conv_kernel_dim - 1],
159
+ )
160
+ for cache_name in self.input_names["past.recurrent"].values():
161
+ self._zero_tensor(
162
+ cache_name,
163
+ shape_paths["recurrent"],
164
+ self.input_types["past.recurrent"],
165
+ [
166
+ "batch_size",
167
+ self.linear_num_value_heads,
168
+ self.linear_key_head_dim,
169
+ self.linear_value_head_dim,
170
+ ],
171
+ )
172
+
173
+ def _make_dynamic_text_positions(self):
174
+ cumsum_name = "/openthai/positions/CumSum"
175
+ self.make_node(
176
+ "CumSum",
177
+ inputs=["attention_mask", "/model/constants/INT64/1"],
178
+ outputs=[f"{cumsum_name}/output_0"],
179
+ name=cumsum_name,
180
+ )
181
+ self.make_value(
182
+ f"{cumsum_name}/output_0",
183
+ ir.DataType.INT64,
184
+ ["batch_size", "sequence_length"],
185
+ )
186
+ sub_name = "/openthai/positions/SubOne"
187
+ self.make_sub(
188
+ sub_name,
189
+ [f"{cumsum_name}/output_0", "/model/constants/INT64/1"],
190
+ ir.DataType.INT64,
191
+ ["batch_size", "sequence_length"],
192
+ )
193
+ padded_name = "/openthai/positions/IsPadding"
194
+ self.make_equal(
195
+ padded_name,
196
+ ["attention_mask", "/model/constants/INT64/0"],
197
+ ["batch_size", "sequence_length"],
198
+ )
199
+ where_name = "/openthai/positions/ReplacePadding"
200
+ self.make_node(
201
+ "Where",
202
+ inputs=[
203
+ f"{padded_name}/output_0",
204
+ "/model/constants/INT64/0",
205
+ f"{sub_name}/output_0",
206
+ ],
207
+ outputs=[f"{where_name}/output_0"],
208
+ name=where_name,
209
+ )
210
+ self.make_value(
211
+ f"{where_name}/output_0",
212
+ ir.DataType.INT64,
213
+ ["batch_size", "sequence_length"],
214
+ )
215
+ unsqueeze_name = "/openthai/positions/Unsqueeze"
216
+ self.make_unsqueeze(
217
+ unsqueeze_name,
218
+ [f"{where_name}/output_0", "/model/constants/INT64/[0]"],
219
+ ir.DataType.INT64,
220
+ [1, "batch_size", "sequence_length"],
221
+ )
222
+ tile_name = "/openthai/positions/TileMRoPE"
223
+ self.make_node(
224
+ "Tile",
225
+ inputs=[f"{unsqueeze_name}/output_0", "/model/constants/INT64/[3, 1, 1]"],
226
+ outputs=[self.input_names["position_ids"]],
227
+ name=tile_name,
228
+ )
229
+ self.make_value(
230
+ self.input_names["position_ids"],
231
+ ir.DataType.INT64,
232
+ [3, "batch_size", "sequence_length"],
233
+ )
234
+
235
+ def make_preprocessing_nodes(self):
236
+ self._make_internal_request_state()
237
+ self._make_dynamic_text_positions()
238
+ super().make_preprocessing_nodes()
239
+
240
+ def make_postprocessing_nodes(self):
241
+ """Gather answers, apply the calibrated head, mask slots, and softmax."""
242
+ shape = ["batch_size", "num_questions", N_SLOTS]
243
+ index_shape_name = "/openthai/head/AnswerIndexShape"
244
+ self.make_shape(index_shape_name, "answer_positions", shape=[2])
245
+ target_shape_name = "/openthai/head/ExpandedIndexShape"
246
+ self.make_concat(
247
+ target_shape_name,
248
+ [f"{index_shape_name}/output_0", f"/model/constants/INT64/[{self.hidden_size}]"],
249
+ ir.DataType.INT64,
250
+ [3],
251
+ )
252
+ clamped_name = "/openthai/head/ClampAnswerPositions"
253
+ self.make_node(
254
+ "Max",
255
+ inputs=["answer_positions", "/model/constants/INT64/0"],
256
+ outputs=[f"{clamped_name}/output_0"],
257
+ name=clamped_name,
258
+ )
259
+ self.make_value(
260
+ f"{clamped_name}/output_0",
261
+ ir.DataType.INT64,
262
+ ["batch_size", "num_questions"],
263
+ )
264
+ unsqueeze_name = "/openthai/head/UnsqueezeAnswerPositions"
265
+ self.make_unsqueeze(
266
+ unsqueeze_name,
267
+ [f"{clamped_name}/output_0", "/model/constants/INT64/[-1]"],
268
+ ir.DataType.INT64,
269
+ ["batch_size", "num_questions", 1],
270
+ )
271
+ expand_name = "/openthai/head/ExpandAnswerPositions"
272
+ self.make_expand(
273
+ expand_name,
274
+ [f"{unsqueeze_name}/output_0", f"{target_shape_name}/output_0"],
275
+ ir.DataType.INT64,
276
+ ["batch_size", "num_questions", self.hidden_size],
277
+ )
278
+ gather_name = "/openthai/head/GatherAnswerStates"
279
+ self.make_node(
280
+ "GatherElements",
281
+ inputs=["hidden_states", f"{expand_name}/output_0"],
282
+ outputs=[f"{gather_name}/output_0"],
283
+ name=gather_name,
284
+ axis=1,
285
+ )
286
+ self.make_value(
287
+ f"{gather_name}/output_0",
288
+ ir.DataType.FLOAT,
289
+ ["batch_size", "num_questions", self.hidden_size],
290
+ )
291
+
292
+ weight_name = "openthai.slot_head.weight.transposed"
293
+ bias_name = "openthai.slot_head.bias"
294
+ temperature_name = "openthai.temperature"
295
+ self.make_initializer(
296
+ self.weights.slot_head.weight.detach().float().t().contiguous(),
297
+ weight_name,
298
+ to=ir.DataType.FLOAT,
299
+ )
300
+ self.make_initializer(
301
+ self.weights.slot_head.bias.detach().float(),
302
+ bias_name,
303
+ to=ir.DataType.FLOAT,
304
+ )
305
+ self.make_initializer(
306
+ self.weights.log_temperature.detach().float().exp(),
307
+ temperature_name,
308
+ to=ir.DataType.FLOAT,
309
+ )
310
+
311
+ matmul_name = "/openthai/head/SlotMatMul"
312
+ self.make_node(
313
+ "MatMul",
314
+ inputs=[f"{gather_name}/output_0", weight_name],
315
+ outputs=[f"{matmul_name}/output_0"],
316
+ name=matmul_name,
317
+ )
318
+ self.make_value(f"{matmul_name}/output_0", ir.DataType.FLOAT, shape)
319
+ add_name = "/openthai/head/SlotBias"
320
+ self.make_add(
321
+ add_name,
322
+ [f"{matmul_name}/output_0", bias_name],
323
+ ir.DataType.FLOAT,
324
+ shape,
325
+ )
326
+
327
+ qtype_name = "/openthai/head/ClampQuestionType"
328
+ self.make_node(
329
+ "Max",
330
+ inputs=["qtypes", "/model/constants/INT64/0"],
331
+ outputs=[f"{qtype_name}/output_0"],
332
+ name=qtype_name,
333
+ )
334
+ self.make_value(
335
+ f"{qtype_name}/output_0",
336
+ ir.DataType.INT64,
337
+ ["batch_size", "num_questions"],
338
+ )
339
+ temp_gather_name = "/openthai/head/GatherTemperature"
340
+ self.make_gather(
341
+ temp_gather_name,
342
+ [temperature_name, f"{qtype_name}/output_0"],
343
+ ir.DataType.FLOAT,
344
+ ["batch_size", "num_questions"],
345
+ axis=0,
346
+ )
347
+ temp_unsqueeze_name = "/openthai/head/UnsqueezeTemperature"
348
+ self.make_unsqueeze(
349
+ temp_unsqueeze_name,
350
+ [f"{temp_gather_name}/output_0", "/model/constants/INT64/[-1]"],
351
+ ir.DataType.FLOAT,
352
+ ["batch_size", "num_questions", 1],
353
+ )
354
+ scaled_name = "/openthai/head/ApplyTemperature"
355
+ self.make_div(
356
+ scaled_name,
357
+ [f"{add_name}/output_0", f"{temp_unsqueeze_name}/output_0"],
358
+ ir.DataType.FLOAT,
359
+ shape,
360
+ )
361
+
362
+ count_name = "/openthai/head/UnsqueezeOptionCounts"
363
+ self.make_unsqueeze(
364
+ count_name,
365
+ ["option_counts", "/model/constants/INT64/[-1]"],
366
+ ir.DataType.INT64,
367
+ ["batch_size", "num_questions", 1],
368
+ )
369
+ range_name = "/openthai/head/SlotRange"
370
+ self.make_range(
371
+ range_name,
372
+ [
373
+ "/model/constants/INT64/0",
374
+ f"/model/constants/INT64/{N_SLOTS}",
375
+ "/model/constants/INT64/1",
376
+ ],
377
+ ir.DataType.INT64,
378
+ [N_SLOTS],
379
+ )
380
+ less_name = "/openthai/head/ValidOptions"
381
+ self.make_node(
382
+ "Less",
383
+ inputs=[f"{range_name}/output_0", f"{count_name}/output_0"],
384
+ outputs=[f"{less_name}/output_0"],
385
+ name=less_name,
386
+ )
387
+ self.make_value(f"{less_name}/output_0", ir.DataType.BOOL, shape)
388
+ abstain_name = "/openthai/head/IsAbstain"
389
+ self.make_equal(
390
+ abstain_name,
391
+ [f"{range_name}/output_0", f"/model/constants/INT64/{ABSTAIN_SLOT}"],
392
+ [N_SLOTS],
393
+ )
394
+ valid_name = "/openthai/head/IncludeAbstain"
395
+ self.make_node(
396
+ "Or",
397
+ inputs=[f"{less_name}/output_0", f"{abstain_name}/output_0"],
398
+ outputs=[f"{valid_name}/output_0"],
399
+ name=valid_name,
400
+ )
401
+ self.make_value(f"{valid_name}/output_0", ir.DataType.BOOL, shape)
402
+
403
+ empty_name = "/openthai/head/EmptyQuestion"
404
+ self.make_equal(
405
+ empty_name,
406
+ ["option_counts", "/model/constants/INT64/0"],
407
+ ["batch_size", "num_questions"],
408
+ )
409
+ empty_unsqueeze_name = "/openthai/head/UnsqueezeEmptyQuestion"
410
+ self.make_unsqueeze(
411
+ empty_unsqueeze_name,
412
+ [f"{empty_name}/output_0", "/model/constants/INT64/[-1]"],
413
+ ir.DataType.BOOL,
414
+ ["batch_size", "num_questions", 1],
415
+ )
416
+ slot_zero_name = "/openthai/head/IsSlotZero"
417
+ self.make_equal(
418
+ slot_zero_name,
419
+ [f"{range_name}/output_0", "/model/constants/INT64/0"],
420
+ [N_SLOTS],
421
+ )
422
+ padding_slot_name = "/openthai/head/PaddingSlot"
423
+ self.make_node(
424
+ "And",
425
+ inputs=[f"{empty_unsqueeze_name}/output_0", f"{slot_zero_name}/output_0"],
426
+ outputs=[f"{padding_slot_name}/output_0"],
427
+ name=padding_slot_name,
428
+ )
429
+ self.make_value(f"{padding_slot_name}/output_0", ir.DataType.BOOL, shape)
430
+ final_valid_name = "/openthai/head/FinalValidMask"
431
+ self.make_node(
432
+ "Or",
433
+ inputs=[f"{valid_name}/output_0", f"{padding_slot_name}/output_0"],
434
+ outputs=[f"{final_valid_name}/output_0"],
435
+ name=final_valid_name,
436
+ )
437
+ self.make_value(f"{final_valid_name}/output_0", ir.DataType.BOOL, shape)
438
+
439
+ negative_infinity_name = "openthai.negative_infinity"
440
+ self.make_initializer(
441
+ np.asarray(float("-inf"), dtype=np.float32),
442
+ negative_infinity_name,
443
+ to=ir.DataType.FLOAT,
444
+ )
445
+ self.make_node(
446
+ "Where",
447
+ inputs=[
448
+ f"{final_valid_name}/output_0",
449
+ f"{scaled_name}/output_0",
450
+ negative_infinity_name,
451
+ ],
452
+ outputs=["logits"],
453
+ name="/openthai/head/MaskInvalidSlots",
454
+ )
455
+ self.make_value("logits", ir.DataType.FLOAT, shape)
456
+ self.make_node(
457
+ "Softmax",
458
+ inputs=["logits"],
459
+ outputs=["probabilities"],
460
+ name="/openthai/head/Softmax",
461
+ axis=-1,
462
+ )
463
+ self.make_value("probabilities", ir.DataType.FLOAT, shape)
464
+
465
+
466
+ def _copy_release_files(snapshot: Path, destination: Path):
467
+ tokenizer_names = {
468
+ "tokenizer.json",
469
+ "tokenizer_config.json",
470
+ "special_tokens_map.json",
471
+ "chat_template.jinja",
472
+ }
473
+ for name in tokenizer_names:
474
+ source = snapshot / name
475
+ if source.is_file():
476
+ shutil.copy2(source, destination / name)
477
+
478
+ project_root = Path(__file__).resolve().parents[2]
479
+ shutil.copy2(project_root / "LICENSE", destination / "LICENSE")
480
+ shutil.copy2(project_root / "MODEL_CARD.md", destination / "README.md")
481
+ shutil.copy2(project_root / "MODEL_CARD.md", destination / "MODEL_CARD.md")
482
+ shutil.copy2(project_root / "ATTRIBUTION.md", destination / "ATTRIBUTION.md")
483
+ shutil.copy2(project_root / "pyproject.toml", destination / "pyproject.toml")
484
+ shutil.copy2(project_root / "uv.lock", destination / "uv.lock")
485
+ shutil.copytree(
486
+ project_root / "src" / "openthai_systemone_onnx",
487
+ destination / "src" / "openthai_systemone_onnx",
488
+ dirs_exist_ok=True,
489
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
490
+ )
491
+ shutil.copytree(
492
+ project_root / "examples",
493
+ destination / "examples",
494
+ dirs_exist_ok=True,
495
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
496
+ )
497
+
498
+
499
+ def export_dynamic_model(output_dir: Path, cache_dir: Path, source: str, revision: str) -> Path:
500
+ if revision != SOURCE_REVISION:
501
+ raise ValueError(
502
+ f"This validated recipe is pinned to source revision {SOURCE_REVISION}; got {revision}"
503
+ )
504
+ snapshot = Path(
505
+ snapshot_download(
506
+ repo_id=source,
507
+ revision=revision,
508
+ cache_dir=cache_dir,
509
+ allow_patterns=[
510
+ "*.json",
511
+ "*.py",
512
+ "*.jinja",
513
+ "*.safetensors",
514
+ "LICENSE*",
515
+ "README.md",
516
+ ],
517
+ )
518
+ )
519
+ config = AutoConfig.from_pretrained(snapshot, trust_remote_code=True, local_files_only=True)
520
+ if config.text_config.max_position_embeddings != MAX_POSITION_EMBEDDINGS:
521
+ raise ValueError("the pinned backbone no longer reports max_position_embeddings=262144")
522
+
523
+ output_dir.mkdir(parents=True, exist_ok=True)
524
+ onnx_dir = output_dir / "onnx"
525
+ onnx_dir.mkdir(parents=True, exist_ok=True)
526
+ builder_cache = cache_dir / "builder"
527
+ builder_cache.mkdir(parents=True, exist_ok=True)
528
+ options = {
529
+ "exclude_lm_head": True,
530
+ "exclude_mtp": True,
531
+ "filename": "model.onnx",
532
+ "hf_remote": True,
533
+ "linear_attn_op": "linear_attention",
534
+ "state_window": 0,
535
+ "use_paged_attention": False,
536
+ }
537
+ builder = OpenThaiDynamicBuilder(
538
+ config,
539
+ ir.DataType.FLOAT,
540
+ ir.DataType.FLOAT,
541
+ "cpu",
542
+ str(builder_cache),
543
+ options,
544
+ )
545
+ builder.model.metadata_props.update(
546
+ {
547
+ "source_model": source,
548
+ "source_revision": revision,
549
+ "max_position_embeddings": str(MAX_POSITION_EMBEDDINGS),
550
+ "onnxruntime_genai_builder": REQUIRED_ORT_GENAI_VERSION,
551
+ "public_state": "internal-zero-state-per-request",
552
+ }
553
+ )
554
+ builder.make_model(str(snapshot))
555
+ builder.save_model(str(onnx_dir))
556
+ _copy_release_files(snapshot, output_dir)
557
+ return write_manifest(output_dir)
558
+
559
+
560
+ def build_parser() -> argparse.ArgumentParser:
561
+ parser = argparse.ArgumentParser(description=__doc__)
562
+ parser.add_argument("--output", type=Path, default=Path("artifacts/OpenThai-SystemOne-ONNX"))
563
+ parser.add_argument("--cache-dir", type=Path, default=Path(".cache/huggingface"))
564
+ parser.add_argument("--source", default=SOURCE_MODEL)
565
+ parser.add_argument("--revision", default=SOURCE_REVISION)
566
+ return parser
567
+
568
+
569
+ def main(argv: list[str] | None = None):
570
+ args = build_parser().parse_args(argv)
571
+ manifest = export_dynamic_model(args.output.resolve(), args.cache_dir.resolve(), args.source, args.revision)
572
+ print(f"Export complete: {manifest}")
573
+
574
+
575
+ if __name__ == "__main__":
576
+ main()
src/openthai_systemone_onnx/upload.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Upload a fully validated artifact to Hugging Face."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from pathlib import Path
8
+
9
+ from huggingface_hub import HfApi
10
+
11
+ from .artifact import sha256_file
12
+ from .constants import TARGET_REPOSITORY
13
+
14
+
15
+ REQUIRED_STAGES = {
16
+ "client_compatibility",
17
+ "normal_parity",
18
+ "slow_65536_parity",
19
+ "structural_262144",
20
+ }
21
+
22
+
23
+ def verify_upload_gate(artifact: Path):
24
+ report_path = artifact / "validation-report.json"
25
+ manifest_path = artifact / "artifact-manifest.json"
26
+ if not report_path.is_file() or not manifest_path.is_file():
27
+ raise RuntimeError("artifact manifest and validation report are both required")
28
+ report = json.loads(report_path.read_text(encoding="utf-8"))
29
+ stages = report.get("stages", {})
30
+ failed = sorted(
31
+ stage
32
+ for stage in REQUIRED_STAGES
33
+ if not stages.get(stage, {}).get("passed", False)
34
+ or stages.get(stage, {}).get("test_counts", {}).get("tests", 0) < 1
35
+ or stages.get(stage, {}).get("test_counts", {}).get("skipped", 1) != 0
36
+ )
37
+ if failed or not report.get("upload_gate", {}).get("passed", False):
38
+ raise RuntimeError(f"upload blocked; required validation stages not passed: {failed}")
39
+
40
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
41
+ bad = []
42
+ for relative, expected in manifest.get("checksums", {}).items():
43
+ path = artifact / relative
44
+ if not path.is_file() or sha256_file(path) != expected:
45
+ bad.append(relative)
46
+ if bad:
47
+ raise RuntimeError(f"upload blocked; artifact checksum mismatch: {bad}")
48
+
49
+
50
+ def main(argv: list[str] | None = None):
51
+ parser = argparse.ArgumentParser(description=__doc__)
52
+ parser.add_argument("--artifact", type=Path, required=True)
53
+ parser.add_argument("--repo-id", default=TARGET_REPOSITORY)
54
+ parser.add_argument("--execute", action="store_true", help="perform the upload (default is a gate-only dry run)")
55
+ args = parser.parse_args(argv)
56
+ artifact = args.artifact.resolve()
57
+ verify_upload_gate(artifact)
58
+ if not args.execute:
59
+ print(f"Upload gate passed for {artifact}; rerun with --execute to upload to {args.repo_id}")
60
+ return
61
+ api = HfApi()
62
+ api.create_repo(args.repo_id, repo_type="model", exist_ok=True)
63
+ api.upload_folder(
64
+ repo_id=args.repo_id,
65
+ repo_type="model",
66
+ folder_path=artifact,
67
+ commit_message="Publish validated dynamic FP32 ONNX Runtime graph",
68
+ )
69
+ print(f"Uploaded {artifact} to https://huggingface.co/{args.repo_id}")
70
+
71
+
72
+ if __name__ == "__main__":
73
+ main()
src/openthai_systemone_onnx/validation.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run reproducible validation stages and maintain the upload gate report."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ import xml.etree.ElementTree as ET
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+
14
+ from .constants import NUMERICALLY_VALIDATED_MAX_TOKENS, SOURCE_REVISION
15
+
16
+
17
+ STAGE_COMMANDS = {
18
+ "client_compatibility": ["tests/test_client_contract.py", "tests/test_export_builder.py"],
19
+ "normal_parity": ["tests/test_parity.py", "-m", "parity and not slow"],
20
+ "slow_65536_parity": ["tests/test_parity.py", "-m", "slow"],
21
+ "structural_262144": ["tests/test_structure.py"],
22
+ }
23
+
24
+
25
+ def _run_stage(name: str, artifact: Path) -> dict:
26
+ environment = os.environ.copy()
27
+ environment["OPENTHAI_ONNX_ARTIFACT"] = str(artifact)
28
+ if name == "normal_parity":
29
+ environment["RUN_PARITY"] = "1"
30
+ if name == "slow_65536_parity":
31
+ environment["RUN_PARITY"] = "1"
32
+ environment["RUN_SLOW_65536"] = "1"
33
+ junit_path = artifact / f".{name}.junit.xml"
34
+ command = [
35
+ sys.executable,
36
+ "-m",
37
+ "pytest",
38
+ "-q",
39
+ *STAGE_COMMANDS[name],
40
+ f"--junitxml={junit_path}",
41
+ ]
42
+ completed = subprocess.run(command, env=environment, text=True)
43
+ counts = {"tests": 0, "failures": 0, "errors": 0, "skipped": 0}
44
+ if junit_path.is_file():
45
+ try:
46
+ root = ET.parse(junit_path).getroot()
47
+ suites = [root] if root.tag == "testsuite" else list(root.iter("testsuite"))
48
+ for suite in suites:
49
+ for key in counts:
50
+ counts[key] += int(suite.attrib.get(key, 0))
51
+ finally:
52
+ junit_path.unlink(missing_ok=True)
53
+ passed = (
54
+ completed.returncode == 0
55
+ and counts["tests"] > 0
56
+ and counts["failures"] == 0
57
+ and counts["errors"] == 0
58
+ and counts["skipped"] == 0
59
+ )
60
+ return {
61
+ "passed": passed,
62
+ "returncode": completed.returncode,
63
+ "command": command,
64
+ "test_counts": counts,
65
+ "completed_at": datetime.now(timezone.utc).isoformat(),
66
+ }
67
+
68
+
69
+ def validate(artifact: Path, stages: list[str]) -> Path:
70
+ report_path = artifact / "validation-report.json"
71
+ if report_path.exists():
72
+ report = json.loads(report_path.read_text(encoding="utf-8"))
73
+ else:
74
+ report = {
75
+ "format_version": 1,
76
+ "source_revision": SOURCE_REVISION,
77
+ "numerical_parity_certified_through_tokens": NUMERICALLY_VALIDATED_MAX_TOKENS,
78
+ "lengths_above_65536": "structural validation only",
79
+ "stages": {},
80
+ }
81
+ for stage in stages:
82
+ print(f"\n=== {stage} ===", flush=True)
83
+ report["stages"][stage] = _run_stage(stage, artifact)
84
+ report["upload_gate"] = {
85
+ "required_stages": list(STAGE_COMMANDS),
86
+ "passed": all(report["stages"].get(item, {}).get("passed", False) for item in STAGE_COMMANDS),
87
+ }
88
+ report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
89
+ if not report["stages"][stage]["passed"]:
90
+ raise SystemExit(f"validation stage failed: {stage}")
91
+ return report_path
92
+
93
+
94
+ def build_parser() -> argparse.ArgumentParser:
95
+ parser = argparse.ArgumentParser(description=__doc__)
96
+ parser.add_argument("--artifact", type=Path, required=True)
97
+ group = parser.add_mutually_exclusive_group(required=True)
98
+ group.add_argument("--client", action="store_true")
99
+ group.add_argument("--normal", action="store_true")
100
+ group.add_argument("--slow", action="store_true")
101
+ group.add_argument("--structural", action="store_true")
102
+ group.add_argument("--all", action="store_true")
103
+ return parser
104
+
105
+
106
+ def main(argv: list[str] | None = None):
107
+ args = build_parser().parse_args(argv)
108
+ selection = {
109
+ "client": ["client_compatibility"],
110
+ "normal": ["client_compatibility", "normal_parity"],
111
+ "slow": ["slow_65536_parity"],
112
+ "structural": ["structural_262144"],
113
+ "all": list(STAGE_COMMANDS),
114
+ }
115
+ selected = next(key for key in selection if getattr(args, key))
116
+ report = validate(args.artifact.resolve(), selection[selected])
117
+ print(f"Validation report: {report}")
118
+
119
+
120
+ if __name__ == "__main__":
121
+ main()
src/openthai_systemone_onnx_tools/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Conversion, validation, and Hugging Face release tooling.
2
+
3
+ This package is intentionally separate from :mod:`openthai_systemone_onnx`,
4
+ which contains only the installed ONNX client runtime.
5
+ """
6
+
src/openthai_systemone_onnx_tools/artifact.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Artifact metadata and checksum helpers for conversion and release tooling."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .constants import (
12
+ MAX_POSITION_EMBEDDINGS,
13
+ NUMERICALLY_VALIDATED_MAX_TOKENS,
14
+ PUBLIC_TENSORS,
15
+ REQUIRED_CONTRIB_OPS,
16
+ REQUIRED_OPERATOR_DOMAINS,
17
+ REQUIRED_ORT_GENAI_VERSION,
18
+ REQUIRED_ORT_VERSION,
19
+ SOURCE_MODEL,
20
+ SOURCE_REVISION,
21
+ TARGET_REPOSITORY,
22
+ UPSTREAM_CLIENT_REVISION,
23
+ )
24
+
25
+
26
+ def sha256_file(path: Path, chunk_size: int = 8 * 1024 * 1024) -> str:
27
+ digest = hashlib.sha256()
28
+ with path.open("rb") as handle:
29
+ while block := handle.read(chunk_size):
30
+ digest.update(block)
31
+ return digest.hexdigest()
32
+
33
+
34
+ def artifact_checksums(root: Path) -> dict[str, str]:
35
+ ignored = {"artifact-manifest.json", "validation-report.json"}
36
+ return {
37
+ path.relative_to(root).as_posix(): sha256_file(path)
38
+ for path in sorted(root.rglob("*"))
39
+ if path.is_file() and path.relative_to(root).as_posix() not in ignored
40
+ }
41
+
42
+
43
+ def build_manifest(root: Path, *, extra: dict[str, Any] | None = None) -> dict[str, Any]:
44
+ manifest: dict[str, Any] = {
45
+ "format_version": 1,
46
+ "created_at": datetime.now(timezone.utc).isoformat(),
47
+ "source": {"repo_id": SOURCE_MODEL, "revision": SOURCE_REVISION},
48
+ "upstream_client": {
49
+ "repository": "iapp-technology/openthai-systemone",
50
+ "revision": UPSTREAM_CLIENT_REVISION,
51
+ },
52
+ "target_repository": TARGET_REPOSITORY,
53
+ "graph": "onnx/model.onnx",
54
+ "external_data": "onnx/model.onnx.data",
55
+ "precision": "fp32",
56
+ "max_position_embeddings": MAX_POSITION_EMBEDDINGS,
57
+ "numerically_validated_max_tokens": NUMERICALLY_VALIDATED_MAX_TOKENS,
58
+ "runtime": {
59
+ "onnxruntime": REQUIRED_ORT_VERSION,
60
+ "onnxruntime_genai_builder": REQUIRED_ORT_GENAI_VERSION,
61
+ "portable_to_arbitrary_onnx_engines": False,
62
+ },
63
+ "operator_domains": REQUIRED_OPERATOR_DOMAINS,
64
+ "required_contrib_operators": sorted(REQUIRED_CONTRIB_OPS),
65
+ "tensor_schema": PUBLIC_TENSORS,
66
+ "state_contract": "zero convolution, recurrent, and KV state is initialized inside the graph per request",
67
+ "checksums": artifact_checksums(root),
68
+ }
69
+ if extra:
70
+ manifest.update(extra)
71
+ return manifest
72
+
73
+
74
+ def write_manifest(root: Path, *, extra: dict[str, Any] | None = None) -> Path:
75
+ destination = root / "artifact-manifest.json"
76
+ destination.write_text(
77
+ json.dumps(build_manifest(root, extra=extra), indent=2, sort_keys=True) + "\n",
78
+ encoding="utf-8",
79
+ )
80
+ return destination
src/openthai_systemone_onnx_tools/constants.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pinned source, graph, validation, and release configuration."""
2
+
3
+ from openthai_systemone_onnx.constants import (
4
+ ABSTAIN_SLOT,
5
+ MAX_POSITION_EMBEDDINGS,
6
+ N_SLOTS,
7
+ REQUIRED_ORT_VERSION,
8
+ )
9
+
10
+ SOURCE_MODEL = "iapp/OpenThai-SystemOne"
11
+ SOURCE_REVISION = "f3709948b5e3cc9606a57e74ba62b7a639d17dd3"
12
+ UPSTREAM_CLIENT_REVISION = "5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf"
13
+ TARGET_REPOSITORY = "imtk/OpenThai-SystemOne-ONNX"
14
+
15
+ NUMERICALLY_VALIDATED_MAX_TOKENS = 65_536
16
+ REQUIRED_ORT_GENAI_VERSION = "0.16.0"
17
+ REQUIRED_OPERATOR_DOMAINS = {"": 23, "com.microsoft": 1}
18
+ REQUIRED_CONTRIB_OPS = {
19
+ "CausalConvWithState",
20
+ "LinearAttention",
21
+ "GroupQueryAttention",
22
+ }
23
+
24
+ PUBLIC_TENSORS = {
25
+ "inputs": {
26
+ "input_ids": ["batch", "sequence"],
27
+ "attention_mask": ["batch", "sequence"],
28
+ "answer_positions": ["batch", "questions"],
29
+ "option_counts": ["batch", "questions"],
30
+ "qtypes": ["batch", "questions"],
31
+ },
32
+ "outputs": {
33
+ "logits": ["batch", "questions", N_SLOTS],
34
+ "probabilities": ["batch", "questions", N_SLOTS],
35
+ },
36
+ }
37
+
src/openthai_systemone_onnx_tools/export.py ADDED
@@ -0,0 +1,583 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build the dynamic FP32 OpenThai-SystemOne ONNX Runtime graph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import shutil
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import onnx_ir as ir
11
+ import torch
12
+ from huggingface_hub import snapshot_download
13
+ from transformers import AutoConfig, AutoModel
14
+
15
+ from onnxruntime_genai.models.builders.qwen import Qwen35TextModel
16
+
17
+ from .artifact import write_manifest
18
+ from .constants import (
19
+ ABSTAIN_SLOT,
20
+ MAX_POSITION_EMBEDDINGS,
21
+ N_SLOTS,
22
+ REQUIRED_ORT_GENAI_VERSION,
23
+ SOURCE_MODEL,
24
+ SOURCE_REVISION,
25
+ )
26
+
27
+
28
+ class OpenThaiDynamicBuilder(Qwen35TextModel):
29
+ """Qwen3.5 builder with internal state and the OpenThai decision head."""
30
+
31
+ def make_node(self, op_type, inputs, outputs, *, name, domain="", **kwargs):
32
+ """Normalize ONNX integer attributes before ONNX IR serialization.
33
+
34
+ ONNX represents flags such as ``keepdims`` as INT attributes. ORT
35
+ GenAI 0.16 passes Python booleans for a few of them. ONNX IR 0.1.3
36
+ infers the correct INT attribute type but otherwise retains the bool,
37
+ which protobuf serialization rejects because ``bool`` is not accepted
38
+ for ``AttributeProto.i``.
39
+ """
40
+ # ORT GenAI 0.16 emits an ORT-private default-domain
41
+ # SimplifiedLayerNormalization op. ONNX Runtime can load that op, but
42
+ # the ONNX checker correctly rejects it because it has no ONNX schema.
43
+ # ONNX opset 23 standardizes the same RMSNorm operation, so lower the
44
+ # standalone form to RMSNormalization. The fused skip form remains the
45
+ # com.microsoft contrib op.
46
+ if op_type == "SimplifiedLayerNormalization" and not domain:
47
+ op_type = "RMSNormalization"
48
+ self.graph.opset_imports[""] = 23
49
+
50
+ normalized = {
51
+ key: int(value) if isinstance(value, (bool, np.bool_)) else value
52
+ for key, value in kwargs.items()
53
+ }
54
+ return super().make_node(
55
+ op_type,
56
+ inputs,
57
+ outputs,
58
+ name=name,
59
+ domain=domain,
60
+ **normalized,
61
+ )
62
+
63
+ def load_weights(self, input_path):
64
+ return AutoModel.from_pretrained(
65
+ input_path,
66
+ trust_remote_code=True,
67
+ local_files_only=True,
68
+ dtype=torch.float32,
69
+ ).eval()
70
+
71
+ def make_inputs_and_outputs(self):
72
+ self.input_shapes["position_ids"] = [3, "batch_size", "sequence_length"]
73
+
74
+ public_inputs = {
75
+ "input_ids": (ir.DataType.INT64, ["batch_size", "sequence_length"]),
76
+ "attention_mask": (ir.DataType.INT64, ["batch_size", "sequence_length"]),
77
+ "answer_positions": (ir.DataType.INT64, ["batch_size", "num_questions"]),
78
+ "option_counts": (ir.DataType.INT64, ["batch_size", "num_questions"]),
79
+ "qtypes": (ir.DataType.INT64, ["batch_size", "num_questions"]),
80
+ }
81
+ for name, (dtype, shape) in public_inputs.items():
82
+ self.model.graph.inputs.append(self.make_value(name, dtype=dtype, shape=shape))
83
+
84
+ output_shape = ["batch_size", "num_questions", N_SLOTS]
85
+ self.model.graph.outputs.append(self.make_value("logits", ir.DataType.FLOAT, output_shape))
86
+ self.model.graph.outputs.append(self.make_value("probabilities", ir.DataType.FLOAT, output_shape))
87
+
88
+ def _dynamic_batch_dimension(self) -> str:
89
+ shape_name = "/openthai/internal/input_shape"
90
+ self.make_shape(shape_name, "input_ids", shape=[2])
91
+ gather_name = "/openthai/internal/batch_dimension"
92
+ self.make_gather(
93
+ gather_name,
94
+ [f"{shape_name}/output_0", "/model/constants/INT64/0"],
95
+ dtype=ir.DataType.INT64,
96
+ shape=[],
97
+ axis=0,
98
+ )
99
+ unsqueeze_name = "/openthai/internal/batch_dimension_unsqueezed"
100
+ self.make_unsqueeze(
101
+ unsqueeze_name,
102
+ [f"{gather_name}/output_0", "/model/constants/INT64/[0]"],
103
+ dtype=ir.DataType.INT64,
104
+ shape=[1],
105
+ )
106
+ return f"{unsqueeze_name}/output_0"
107
+
108
+ def _zero_tensor(self, name: str, shape_input: str, dtype: ir.DataType, shape):
109
+ if dtype != ir.DataType.FLOAT:
110
+ raise TypeError(f"internal request state must be FP32, got {dtype}")
111
+ zero = ir.tensor(np.asarray([0], dtype=np.float32))
112
+ node_name = f"/openthai/internal/zero/{name.replace('.', '_')}"
113
+ self.make_node(
114
+ "ConstantOfShape",
115
+ inputs=[shape_input],
116
+ outputs=[name],
117
+ name=node_name,
118
+ value=zero,
119
+ )
120
+ self.make_value(name, dtype=dtype, shape=shape)
121
+
122
+ def _make_internal_request_state(self):
123
+ batch = self._dynamic_batch_dimension()
124
+ shape_specs = {
125
+ "kv": [self.num_kv_heads, 0, self.head_size],
126
+ "conv": [self.linear_conv_dim, self.linear_conv_kernel_dim - 1],
127
+ "recurrent": [
128
+ self.linear_num_value_heads,
129
+ self.linear_key_head_dim,
130
+ self.linear_value_head_dim,
131
+ ],
132
+ }
133
+ shape_paths: dict[str, str] = {}
134
+ for label, tail in shape_specs.items():
135
+ concat_name = f"/openthai/internal/{label}_state_shape"
136
+ self.make_concat(
137
+ concat_name,
138
+ [batch, f"/model/constants/INT64/{tail}"],
139
+ dtype=ir.DataType.INT64,
140
+ shape=[1 + len(tail)],
141
+ axis=0,
142
+ )
143
+ shape_paths[label] = f"{concat_name}/output_0"
144
+
145
+ for cache_kind in ("past_key_values.key", "past_key_values.value"):
146
+ for cache_name in self.input_names[cache_kind].values():
147
+ self._zero_tensor(
148
+ cache_name,
149
+ shape_paths["kv"],
150
+ self.io_dtype,
151
+ ["batch_size", self.num_kv_heads, 0, self.head_size],
152
+ )
153
+ for cache_name in self.input_names["past.conv"].values():
154
+ self._zero_tensor(
155
+ cache_name,
156
+ shape_paths["conv"],
157
+ self.io_dtype,
158
+ ["batch_size", self.linear_conv_dim, self.linear_conv_kernel_dim - 1],
159
+ )
160
+ for cache_name in self.input_names["past.recurrent"].values():
161
+ self._zero_tensor(
162
+ cache_name,
163
+ shape_paths["recurrent"],
164
+ self.input_types["past.recurrent"],
165
+ [
166
+ "batch_size",
167
+ self.linear_num_value_heads,
168
+ self.linear_key_head_dim,
169
+ self.linear_value_head_dim,
170
+ ],
171
+ )
172
+
173
+ def _make_dynamic_text_positions(self):
174
+ cumsum_name = "/openthai/positions/CumSum"
175
+ self.make_node(
176
+ "CumSum",
177
+ inputs=["attention_mask", "/model/constants/INT64/1"],
178
+ outputs=[f"{cumsum_name}/output_0"],
179
+ name=cumsum_name,
180
+ )
181
+ self.make_value(
182
+ f"{cumsum_name}/output_0",
183
+ ir.DataType.INT64,
184
+ ["batch_size", "sequence_length"],
185
+ )
186
+ sub_name = "/openthai/positions/SubOne"
187
+ self.make_sub(
188
+ sub_name,
189
+ [f"{cumsum_name}/output_0", "/model/constants/INT64/1"],
190
+ ir.DataType.INT64,
191
+ ["batch_size", "sequence_length"],
192
+ )
193
+ padded_name = "/openthai/positions/IsPadding"
194
+ self.make_equal(
195
+ padded_name,
196
+ ["attention_mask", "/model/constants/INT64/0"],
197
+ ["batch_size", "sequence_length"],
198
+ )
199
+ where_name = "/openthai/positions/ReplacePadding"
200
+ self.make_node(
201
+ "Where",
202
+ inputs=[
203
+ f"{padded_name}/output_0",
204
+ "/model/constants/INT64/0",
205
+ f"{sub_name}/output_0",
206
+ ],
207
+ outputs=[f"{where_name}/output_0"],
208
+ name=where_name,
209
+ )
210
+ self.make_value(
211
+ f"{where_name}/output_0",
212
+ ir.DataType.INT64,
213
+ ["batch_size", "sequence_length"],
214
+ )
215
+ unsqueeze_name = "/openthai/positions/Unsqueeze"
216
+ self.make_unsqueeze(
217
+ unsqueeze_name,
218
+ [f"{where_name}/output_0", "/model/constants/INT64/[0]"],
219
+ ir.DataType.INT64,
220
+ [1, "batch_size", "sequence_length"],
221
+ )
222
+ tile_name = "/openthai/positions/TileMRoPE"
223
+ self.make_node(
224
+ "Tile",
225
+ inputs=[f"{unsqueeze_name}/output_0", "/model/constants/INT64/[3, 1, 1]"],
226
+ outputs=[self.input_names["position_ids"]],
227
+ name=tile_name,
228
+ )
229
+ self.make_value(
230
+ self.input_names["position_ids"],
231
+ ir.DataType.INT64,
232
+ [3, "batch_size", "sequence_length"],
233
+ )
234
+
235
+ def make_preprocessing_nodes(self):
236
+ self._make_internal_request_state()
237
+ self._make_dynamic_text_positions()
238
+ super().make_preprocessing_nodes()
239
+
240
+ def make_postprocessing_nodes(self):
241
+ """Gather answers, apply the calibrated head, mask slots, and softmax."""
242
+ shape = ["batch_size", "num_questions", N_SLOTS]
243
+ index_shape_name = "/openthai/head/AnswerIndexShape"
244
+ self.make_shape(index_shape_name, "answer_positions", shape=[2])
245
+ target_shape_name = "/openthai/head/ExpandedIndexShape"
246
+ self.make_concat(
247
+ target_shape_name,
248
+ [f"{index_shape_name}/output_0", f"/model/constants/INT64/[{self.hidden_size}]"],
249
+ ir.DataType.INT64,
250
+ [3],
251
+ )
252
+ clamped_name = "/openthai/head/ClampAnswerPositions"
253
+ self.make_node(
254
+ "Max",
255
+ inputs=["answer_positions", "/model/constants/INT64/0"],
256
+ outputs=[f"{clamped_name}/output_0"],
257
+ name=clamped_name,
258
+ )
259
+ self.make_value(
260
+ f"{clamped_name}/output_0",
261
+ ir.DataType.INT64,
262
+ ["batch_size", "num_questions"],
263
+ )
264
+ unsqueeze_name = "/openthai/head/UnsqueezeAnswerPositions"
265
+ self.make_unsqueeze(
266
+ unsqueeze_name,
267
+ [f"{clamped_name}/output_0", "/model/constants/INT64/[-1]"],
268
+ ir.DataType.INT64,
269
+ ["batch_size", "num_questions", 1],
270
+ )
271
+ expand_name = "/openthai/head/ExpandAnswerPositions"
272
+ self.make_expand(
273
+ expand_name,
274
+ [f"{unsqueeze_name}/output_0", f"{target_shape_name}/output_0"],
275
+ ir.DataType.INT64,
276
+ ["batch_size", "num_questions", self.hidden_size],
277
+ )
278
+ gather_name = "/openthai/head/GatherAnswerStates"
279
+ self.make_node(
280
+ "GatherElements",
281
+ inputs=["hidden_states", f"{expand_name}/output_0"],
282
+ outputs=[f"{gather_name}/output_0"],
283
+ name=gather_name,
284
+ axis=1,
285
+ )
286
+ self.make_value(
287
+ f"{gather_name}/output_0",
288
+ ir.DataType.FLOAT,
289
+ ["batch_size", "num_questions", self.hidden_size],
290
+ )
291
+
292
+ weight_name = "openthai.slot_head.weight.transposed"
293
+ bias_name = "openthai.slot_head.bias"
294
+ temperature_name = "openthai.temperature"
295
+ self.make_initializer(
296
+ self.weights.slot_head.weight.detach().float().t().contiguous(),
297
+ weight_name,
298
+ to=ir.DataType.FLOAT,
299
+ )
300
+ self.make_initializer(
301
+ self.weights.slot_head.bias.detach().float(),
302
+ bias_name,
303
+ to=ir.DataType.FLOAT,
304
+ )
305
+ self.make_initializer(
306
+ self.weights.log_temperature.detach().float().exp(),
307
+ temperature_name,
308
+ to=ir.DataType.FLOAT,
309
+ )
310
+
311
+ matmul_name = "/openthai/head/SlotMatMul"
312
+ self.make_node(
313
+ "MatMul",
314
+ inputs=[f"{gather_name}/output_0", weight_name],
315
+ outputs=[f"{matmul_name}/output_0"],
316
+ name=matmul_name,
317
+ )
318
+ self.make_value(f"{matmul_name}/output_0", ir.DataType.FLOAT, shape)
319
+ add_name = "/openthai/head/SlotBias"
320
+ self.make_add(
321
+ add_name,
322
+ [f"{matmul_name}/output_0", bias_name],
323
+ ir.DataType.FLOAT,
324
+ shape,
325
+ )
326
+
327
+ qtype_name = "/openthai/head/ClampQuestionType"
328
+ self.make_node(
329
+ "Max",
330
+ inputs=["qtypes", "/model/constants/INT64/0"],
331
+ outputs=[f"{qtype_name}/output_0"],
332
+ name=qtype_name,
333
+ )
334
+ self.make_value(
335
+ f"{qtype_name}/output_0",
336
+ ir.DataType.INT64,
337
+ ["batch_size", "num_questions"],
338
+ )
339
+ temp_gather_name = "/openthai/head/GatherTemperature"
340
+ self.make_gather(
341
+ temp_gather_name,
342
+ [temperature_name, f"{qtype_name}/output_0"],
343
+ ir.DataType.FLOAT,
344
+ ["batch_size", "num_questions"],
345
+ axis=0,
346
+ )
347
+ temp_unsqueeze_name = "/openthai/head/UnsqueezeTemperature"
348
+ self.make_unsqueeze(
349
+ temp_unsqueeze_name,
350
+ [f"{temp_gather_name}/output_0", "/model/constants/INT64/[-1]"],
351
+ ir.DataType.FLOAT,
352
+ ["batch_size", "num_questions", 1],
353
+ )
354
+ scaled_name = "/openthai/head/ApplyTemperature"
355
+ self.make_div(
356
+ scaled_name,
357
+ [f"{add_name}/output_0", f"{temp_unsqueeze_name}/output_0"],
358
+ ir.DataType.FLOAT,
359
+ shape,
360
+ )
361
+
362
+ count_name = "/openthai/head/UnsqueezeOptionCounts"
363
+ self.make_unsqueeze(
364
+ count_name,
365
+ ["option_counts", "/model/constants/INT64/[-1]"],
366
+ ir.DataType.INT64,
367
+ ["batch_size", "num_questions", 1],
368
+ )
369
+ range_name = "/openthai/head/SlotRange"
370
+ self.make_range(
371
+ range_name,
372
+ [
373
+ "/model/constants/INT64/0",
374
+ f"/model/constants/INT64/{N_SLOTS}",
375
+ "/model/constants/INT64/1",
376
+ ],
377
+ ir.DataType.INT64,
378
+ [N_SLOTS],
379
+ )
380
+ less_name = "/openthai/head/ValidOptions"
381
+ self.make_node(
382
+ "Less",
383
+ inputs=[f"{range_name}/output_0", f"{count_name}/output_0"],
384
+ outputs=[f"{less_name}/output_0"],
385
+ name=less_name,
386
+ )
387
+ self.make_value(f"{less_name}/output_0", ir.DataType.BOOL, shape)
388
+ abstain_name = "/openthai/head/IsAbstain"
389
+ self.make_equal(
390
+ abstain_name,
391
+ [f"{range_name}/output_0", f"/model/constants/INT64/{ABSTAIN_SLOT}"],
392
+ [N_SLOTS],
393
+ )
394
+ valid_name = "/openthai/head/IncludeAbstain"
395
+ self.make_node(
396
+ "Or",
397
+ inputs=[f"{less_name}/output_0", f"{abstain_name}/output_0"],
398
+ outputs=[f"{valid_name}/output_0"],
399
+ name=valid_name,
400
+ )
401
+ self.make_value(f"{valid_name}/output_0", ir.DataType.BOOL, shape)
402
+
403
+ empty_name = "/openthai/head/EmptyQuestion"
404
+ self.make_equal(
405
+ empty_name,
406
+ ["option_counts", "/model/constants/INT64/0"],
407
+ ["batch_size", "num_questions"],
408
+ )
409
+ empty_unsqueeze_name = "/openthai/head/UnsqueezeEmptyQuestion"
410
+ self.make_unsqueeze(
411
+ empty_unsqueeze_name,
412
+ [f"{empty_name}/output_0", "/model/constants/INT64/[-1]"],
413
+ ir.DataType.BOOL,
414
+ ["batch_size", "num_questions", 1],
415
+ )
416
+ slot_zero_name = "/openthai/head/IsSlotZero"
417
+ self.make_equal(
418
+ slot_zero_name,
419
+ [f"{range_name}/output_0", "/model/constants/INT64/0"],
420
+ [N_SLOTS],
421
+ )
422
+ padding_slot_name = "/openthai/head/PaddingSlot"
423
+ self.make_node(
424
+ "And",
425
+ inputs=[f"{empty_unsqueeze_name}/output_0", f"{slot_zero_name}/output_0"],
426
+ outputs=[f"{padding_slot_name}/output_0"],
427
+ name=padding_slot_name,
428
+ )
429
+ self.make_value(f"{padding_slot_name}/output_0", ir.DataType.BOOL, shape)
430
+ final_valid_name = "/openthai/head/FinalValidMask"
431
+ self.make_node(
432
+ "Or",
433
+ inputs=[f"{valid_name}/output_0", f"{padding_slot_name}/output_0"],
434
+ outputs=[f"{final_valid_name}/output_0"],
435
+ name=final_valid_name,
436
+ )
437
+ self.make_value(f"{final_valid_name}/output_0", ir.DataType.BOOL, shape)
438
+
439
+ negative_infinity_name = "openthai.negative_infinity"
440
+ self.make_initializer(
441
+ np.asarray(float("-inf"), dtype=np.float32),
442
+ negative_infinity_name,
443
+ to=ir.DataType.FLOAT,
444
+ )
445
+ self.make_node(
446
+ "Where",
447
+ inputs=[
448
+ f"{final_valid_name}/output_0",
449
+ f"{scaled_name}/output_0",
450
+ negative_infinity_name,
451
+ ],
452
+ outputs=["logits"],
453
+ name="/openthai/head/MaskInvalidSlots",
454
+ )
455
+ self.make_value("logits", ir.DataType.FLOAT, shape)
456
+ self.make_node(
457
+ "Softmax",
458
+ inputs=["logits"],
459
+ outputs=["probabilities"],
460
+ name="/openthai/head/Softmax",
461
+ axis=-1,
462
+ )
463
+ self.make_value("probabilities", ir.DataType.FLOAT, shape)
464
+
465
+
466
+ def _copy_release_files(snapshot: Path, destination: Path):
467
+ tokenizer_names = {
468
+ "tokenizer.json",
469
+ "tokenizer_config.json",
470
+ "special_tokens_map.json",
471
+ "chat_template.jinja",
472
+ }
473
+ for name in tokenizer_names:
474
+ source = snapshot / name
475
+ if source.is_file():
476
+ shutil.copy2(source, destination / name)
477
+
478
+ project_root = Path(__file__).resolve().parents[2]
479
+ shutil.copy2(project_root / "LICENSE", destination / "LICENSE")
480
+ shutil.copy2(project_root / "MODEL_CARD.md", destination / "README.md")
481
+ shutil.copy2(project_root / "MODEL_CARD.md", destination / "MODEL_CARD.md")
482
+ shutil.copy2(project_root / "ATTRIBUTION.md", destination / "ATTRIBUTION.md")
483
+ shutil.copy2(project_root / "INSTRUCTIONS.md", destination / "INSTRUCTIONS.md")
484
+ shutil.copy2(project_root / "pyproject.toml", destination / "pyproject.toml")
485
+ shutil.copy2(project_root / "uv.lock", destination / "uv.lock")
486
+ shutil.copytree(
487
+ project_root / "src" / "openthai_systemone_onnx",
488
+ destination / "src" / "openthai_systemone_onnx",
489
+ dirs_exist_ok=True,
490
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
491
+ )
492
+ shutil.copytree(
493
+ project_root / "src" / "openthai_systemone_onnx_tools",
494
+ destination / "src" / "openthai_systemone_onnx_tools",
495
+ dirs_exist_ok=True,
496
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
497
+ )
498
+ shutil.copytree(
499
+ project_root / "examples",
500
+ destination / "examples",
501
+ dirs_exist_ok=True,
502
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
503
+ )
504
+
505
+
506
+ def export_dynamic_model(output_dir: Path, cache_dir: Path, source: str, revision: str) -> Path:
507
+ if revision != SOURCE_REVISION:
508
+ raise ValueError(
509
+ f"This validated recipe is pinned to source revision {SOURCE_REVISION}; got {revision}"
510
+ )
511
+ snapshot = Path(
512
+ snapshot_download(
513
+ repo_id=source,
514
+ revision=revision,
515
+ cache_dir=cache_dir,
516
+ allow_patterns=[
517
+ "*.json",
518
+ "*.py",
519
+ "*.jinja",
520
+ "*.safetensors",
521
+ "LICENSE*",
522
+ "README.md",
523
+ ],
524
+ )
525
+ )
526
+ config = AutoConfig.from_pretrained(snapshot, trust_remote_code=True, local_files_only=True)
527
+ if config.text_config.max_position_embeddings != MAX_POSITION_EMBEDDINGS:
528
+ raise ValueError("the pinned backbone no longer reports max_position_embeddings=262144")
529
+
530
+ output_dir.mkdir(parents=True, exist_ok=True)
531
+ onnx_dir = output_dir / "onnx"
532
+ onnx_dir.mkdir(parents=True, exist_ok=True)
533
+ builder_cache = cache_dir / "builder"
534
+ builder_cache.mkdir(parents=True, exist_ok=True)
535
+ options = {
536
+ "exclude_lm_head": True,
537
+ "exclude_mtp": True,
538
+ "filename": "model.onnx",
539
+ "hf_remote": True,
540
+ "linear_attn_op": "linear_attention",
541
+ "state_window": 0,
542
+ "use_paged_attention": False,
543
+ }
544
+ builder = OpenThaiDynamicBuilder(
545
+ config,
546
+ ir.DataType.FLOAT,
547
+ ir.DataType.FLOAT,
548
+ "cpu",
549
+ str(builder_cache),
550
+ options,
551
+ )
552
+ builder.model.metadata_props.update(
553
+ {
554
+ "source_model": source,
555
+ "source_revision": revision,
556
+ "max_position_embeddings": str(MAX_POSITION_EMBEDDINGS),
557
+ "onnxruntime_genai_builder": REQUIRED_ORT_GENAI_VERSION,
558
+ "public_state": "internal-zero-state-per-request",
559
+ }
560
+ )
561
+ builder.make_model(str(snapshot))
562
+ builder.save_model(str(onnx_dir))
563
+ _copy_release_files(snapshot, output_dir)
564
+ return write_manifest(output_dir)
565
+
566
+
567
+ def build_parser() -> argparse.ArgumentParser:
568
+ parser = argparse.ArgumentParser(description=__doc__)
569
+ parser.add_argument("--output", type=Path, default=Path("artifacts/OpenThai-SystemOne-ONNX"))
570
+ parser.add_argument("--cache-dir", type=Path, default=Path(".cache/huggingface"))
571
+ parser.add_argument("--source", default=SOURCE_MODEL)
572
+ parser.add_argument("--revision", default=SOURCE_REVISION)
573
+ return parser
574
+
575
+
576
+ def main(argv: list[str] | None = None):
577
+ args = build_parser().parse_args(argv)
578
+ manifest = export_dynamic_model(args.output.resolve(), args.cache_dir.resolve(), args.source, args.revision)
579
+ print(f"Export complete: {manifest}")
580
+
581
+
582
+ if __name__ == "__main__":
583
+ main()
src/openthai_systemone_onnx_tools/upload.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Upload a fully validated artifact to Hugging Face."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ from pathlib import Path
8
+
9
+ from huggingface_hub import HfApi
10
+
11
+ from .artifact import sha256_file
12
+ from .constants import TARGET_REPOSITORY
13
+
14
+
15
+ REQUIRED_STAGES = {
16
+ "client_compatibility",
17
+ "normal_parity",
18
+ "slow_65536_parity",
19
+ "structural_262144",
20
+ }
21
+
22
+
23
+ def verify_upload_gate(artifact: Path):
24
+ report_path = artifact / "validation-report.json"
25
+ manifest_path = artifact / "artifact-manifest.json"
26
+ if not report_path.is_file() or not manifest_path.is_file():
27
+ raise RuntimeError("artifact manifest and validation report are both required")
28
+ report = json.loads(report_path.read_text(encoding="utf-8"))
29
+ stages = report.get("stages", {})
30
+ failed = sorted(
31
+ stage
32
+ for stage in REQUIRED_STAGES
33
+ if not stages.get(stage, {}).get("passed", False)
34
+ or stages.get(stage, {}).get("test_counts", {}).get("tests", 0) < 1
35
+ or stages.get(stage, {}).get("test_counts", {}).get("skipped", 1) != 0
36
+ )
37
+ if failed or not report.get("upload_gate", {}).get("passed", False):
38
+ raise RuntimeError(f"upload blocked; required validation stages not passed: {failed}")
39
+
40
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
41
+ bad = []
42
+ for relative, expected in manifest.get("checksums", {}).items():
43
+ path = artifact / relative
44
+ if not path.is_file() or sha256_file(path) != expected:
45
+ bad.append(relative)
46
+ if bad:
47
+ raise RuntimeError(f"upload blocked; artifact checksum mismatch: {bad}")
48
+
49
+
50
+ def main(argv: list[str] | None = None):
51
+ parser = argparse.ArgumentParser(description=__doc__)
52
+ parser.add_argument("--artifact", type=Path, required=True)
53
+ parser.add_argument("--repo-id", default=TARGET_REPOSITORY)
54
+ parser.add_argument("--execute", action="store_true", help="perform the upload (default is a gate-only dry run)")
55
+ args = parser.parse_args(argv)
56
+ artifact = args.artifact.resolve()
57
+ verify_upload_gate(artifact)
58
+ if not args.execute:
59
+ print(f"Upload gate passed for {artifact}; rerun with --execute to upload to {args.repo_id}")
60
+ return
61
+ api = HfApi()
62
+ api.create_repo(args.repo_id, repo_type="model", exist_ok=True)
63
+ api.upload_folder(
64
+ repo_id=args.repo_id,
65
+ repo_type="model",
66
+ folder_path=artifact,
67
+ commit_message="Publish validated dynamic FP32 ONNX Runtime graph",
68
+ )
69
+ print(f"Uploaded {artifact} to https://huggingface.co/{args.repo_id}")
70
+
71
+
72
+ if __name__ == "__main__":
73
+ main()
src/openthai_systemone_onnx_tools/validation.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run reproducible validation stages and maintain the upload gate report."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ import xml.etree.ElementTree as ET
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+
14
+ from .constants import NUMERICALLY_VALIDATED_MAX_TOKENS, SOURCE_REVISION
15
+
16
+
17
+ STAGE_COMMANDS = {
18
+ "client_compatibility": [
19
+ "tests/test_client_contract.py",
20
+ "tests/test_export_builder.py",
21
+ "tests/test_validation.py",
22
+ ],
23
+ "normal_parity": ["tests/test_parity.py", "-m", "parity and not slow"],
24
+ "slow_65536_parity": ["tests/test_parity.py", "-m", "slow"],
25
+ "structural_262144": ["tests/test_structure.py"],
26
+ }
27
+
28
+
29
+ def _accelerator_preflight(name: str) -> None:
30
+ """Fail early with an actionable message for unavailable requested devices."""
31
+ if name not in {"normal_parity", "slow_65536_parity"}:
32
+ return
33
+
34
+ import onnxruntime as ort
35
+ import torch
36
+
37
+ requested_ort = os.environ.get("ORT_PARITY_DEVICE")
38
+ if requested_ort == "cuda" and "CUDAExecutionProvider" not in ort.get_available_providers():
39
+ raise SystemExit(
40
+ "ORT_PARITY_DEVICE=cuda was requested, but CUDAExecutionProvider is unavailable. "
41
+ "CUDA cannot run on macOS. On an NVIDIA Linux/Windows host, replace onnxruntime "
42
+ "with onnxruntime-gpu==1.30.0 and invoke the validator with `uv run --no-sync` "
43
+ "so uv does not restore the CPU wheel."
44
+ )
45
+
46
+ requested_torch = os.environ.get("PYTORCH_PARITY_DEVICE", requested_ort)
47
+ if requested_torch == "cuda" and not torch.cuda.is_available():
48
+ raise SystemExit(
49
+ "PYTORCH_PARITY_DEVICE=cuda was requested (or inherited from ORT_PARITY_DEVICE), "
50
+ "but PyTorch CUDA is unavailable. Run this certification on an NVIDIA CUDA host."
51
+ )
52
+ if requested_torch == "mps" and not torch.backends.mps.is_available():
53
+ raise SystemExit(
54
+ "PYTORCH_PARITY_DEVICE=mps was requested, but PyTorch reports MPS unavailable."
55
+ )
56
+
57
+
58
+ def _run_stage(name: str, artifact: Path) -> dict:
59
+ _accelerator_preflight(name)
60
+ environment = os.environ.copy()
61
+ environment["OPENTHAI_ONNX_ARTIFACT"] = str(artifact)
62
+ if name == "normal_parity":
63
+ environment["RUN_PARITY"] = "1"
64
+ if name == "slow_65536_parity":
65
+ environment["RUN_PARITY"] = "1"
66
+ environment["RUN_SLOW_65536"] = "1"
67
+ junit_path = artifact / f".{name}.junit.xml"
68
+ command = [
69
+ sys.executable,
70
+ "-m",
71
+ "pytest",
72
+ "-q",
73
+ *STAGE_COMMANDS[name],
74
+ f"--junitxml={junit_path}",
75
+ ]
76
+ completed = subprocess.run(command, env=environment, text=True)
77
+ counts = {"tests": 0, "failures": 0, "errors": 0, "skipped": 0}
78
+ if junit_path.is_file():
79
+ try:
80
+ root = ET.parse(junit_path).getroot()
81
+ suites = [root] if root.tag == "testsuite" else list(root.iter("testsuite"))
82
+ for suite in suites:
83
+ for key in counts:
84
+ counts[key] += int(suite.attrib.get(key, 0))
85
+ finally:
86
+ junit_path.unlink(missing_ok=True)
87
+ passed = (
88
+ completed.returncode == 0
89
+ and counts["tests"] > 0
90
+ and counts["failures"] == 0
91
+ and counts["errors"] == 0
92
+ and counts["skipped"] == 0
93
+ )
94
+ return {
95
+ "passed": passed,
96
+ "returncode": completed.returncode,
97
+ "command": command,
98
+ "test_counts": counts,
99
+ "completed_at": datetime.now(timezone.utc).isoformat(),
100
+ "failure_hint": (
101
+ "validation process was killed (signal 9), usually because host or GPU memory was exhausted"
102
+ if completed.returncode == -9
103
+ else None
104
+ ),
105
+ }
106
+
107
+
108
+ def validate(artifact: Path, stages: list[str]) -> Path:
109
+ report_path = artifact / "validation-report.json"
110
+ if report_path.exists():
111
+ report = json.loads(report_path.read_text(encoding="utf-8"))
112
+ else:
113
+ report = {
114
+ "format_version": 1,
115
+ "source_revision": SOURCE_REVISION,
116
+ "numerical_parity_certified_through_tokens": NUMERICALLY_VALIDATED_MAX_TOKENS,
117
+ "lengths_above_65536": "structural validation only",
118
+ "stages": {},
119
+ }
120
+ for stage in stages:
121
+ print(f"\n=== {stage} ===", flush=True)
122
+ report["stages"][stage] = _run_stage(stage, artifact)
123
+ report["upload_gate"] = {
124
+ "required_stages": list(STAGE_COMMANDS),
125
+ "passed": all(report["stages"].get(item, {}).get("passed", False) for item in STAGE_COMMANDS),
126
+ }
127
+ report_path.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
128
+ if not report["stages"][stage]["passed"]:
129
+ hint = report["stages"][stage].get("failure_hint")
130
+ suffix = f" ({hint})" if hint else ""
131
+ raise SystemExit(f"validation stage failed: {stage}{suffix}")
132
+ return report_path
133
+
134
+
135
+ def build_parser() -> argparse.ArgumentParser:
136
+ parser = argparse.ArgumentParser(description=__doc__)
137
+ parser.add_argument("--artifact", type=Path, required=True)
138
+ group = parser.add_mutually_exclusive_group(required=True)
139
+ group.add_argument("--client", action="store_true")
140
+ group.add_argument("--normal", action="store_true")
141
+ group.add_argument("--slow", action="store_true")
142
+ group.add_argument("--structural", action="store_true")
143
+ group.add_argument("--all", action="store_true")
144
+ return parser
145
+
146
+
147
+ def main(argv: list[str] | None = None):
148
+ args = build_parser().parse_args(argv)
149
+ selection = {
150
+ "client": ["client_compatibility"],
151
+ "normal": ["client_compatibility", "normal_parity"],
152
+ "slow": ["slow_65536_parity"],
153
+ "structural": ["structural_262144"],
154
+ "all": list(STAGE_COMMANDS),
155
+ }
156
+ selected = next(key for key in selection if getattr(args, key))
157
+ report = validate(args.artifact.resolve(), selection[selected])
158
+ print(f"Validation report: {report}")
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3400f1532d54f98d82b24c503e61908aa66f84e22e566e59fc80494add3a5c3
3
+ size 20039242
tokenizer_config.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": null,
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|endoftext|>",
10
+ "errors": "replace",
11
+ "image_token": "<|image_pad|>",
12
+ "is_local": true,
13
+ "local_files_only": false,
14
+ "model_max_length": 262144,
15
+ "model_specific_special_tokens": {
16
+ "audio_bos_token": "<|audio_start|>",
17
+ "audio_eos_token": "<|audio_end|>",
18
+ "audio_token": "<|audio_pad|>",
19
+ "image_token": "<|image_pad|>",
20
+ "video_token": "<|video_pad|>",
21
+ "vision_bos_token": "<|vision_start|>",
22
+ "vision_eos_token": "<|vision_end|>"
23
+ },
24
+ "pad_token": "<|endoftext|>",
25
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
26
+ "split_special_tokens": false,
27
+ "tokenizer_class": "Qwen2Tokenizer",
28
+ "unk_token": null,
29
+ "video_token": "<|video_pad|>",
30
+ "vision_bos_token": "<|vision_start|>",
31
+ "vision_eos_token": "<|vision_end|>"
32
+ }
uv.lock ADDED
@@ -0,0 +1,909 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version = 1
2
+ revision = 3
3
+ requires-python = "==3.12.*"
4
+
5
+ [[package]]
6
+ name = "annotated-doc"
7
+ version = "0.0.5"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "annotated-types"
16
+ version = "0.8.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
19
+ wheels = [
20
+ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
21
+ ]
22
+
23
+ [[package]]
24
+ name = "anyio"
25
+ version = "4.15.1"
26
+ source = { registry = "https://pypi.org/simple" }
27
+ dependencies = [
28
+ { name = "idna" },
29
+ { name = "typing-extensions" },
30
+ ]
31
+ sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" }
32
+ wheels = [
33
+ { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
34
+ ]
35
+
36
+ [[package]]
37
+ name = "certifi"
38
+ version = "2026.7.22"
39
+ source = { registry = "https://pypi.org/simple" }
40
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
41
+ wheels = [
42
+ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
43
+ ]
44
+
45
+ [[package]]
46
+ name = "colorama"
47
+ version = "0.4.6"
48
+ source = { registry = "https://pypi.org/simple" }
49
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
50
+ wheels = [
51
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
52
+ ]
53
+
54
+ [[package]]
55
+ name = "coverage"
56
+ version = "7.16.1"
57
+ source = { registry = "https://pypi.org/simple" }
58
+ sdist = { url = "https://files.pythonhosted.org/packages/65/2d/c738872f477f5687152acae68635790387425d407ae37dd3d3a8a6692307/coverage-7.16.1.tar.gz", hash = "sha256:f83981779bcf9dfa06fa0a8d4cb43e0faec1706328ce07aa3e7b665b4ac0f210", size = 969651, upload-time = "2026-09-13T19:12:21.422Z" }
59
+ wheels = [
60
+ { url = "https://files.pythonhosted.org/packages/1a/f7/7cd4c9f2a3b7574414222ec425d5eee21cc690d867a013315e3be8ba185c/coverage-7.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b7f2c26ce6ce0b1e0ca0d5fae96ea510e3a2e78b7207f06e76b7f2c87fa3d0af", size = 223475, upload-time = "2026-09-13T19:09:10.145Z" },
61
+ { url = "https://files.pythonhosted.org/packages/3e/f2/9ac65f9cedd43f91e2d3657c11f13aef82aebae89b2243dc9e44fe58a740/coverage-7.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070acb9da788dff743a4d36fc015feee12d68f0349959017542017c79f59c21c", size = 223845, upload-time = "2026-09-13T19:09:11.988Z" },
62
+ { url = "https://files.pythonhosted.org/packages/62/4d/f13db452d4367fe1122abfd98ae330596f65a3b40498f8e1a5811f68f123/coverage-7.16.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e366587b370bc9b8b51b7b7272c610c56db5d5b4795b9e4a29d28ff2f440f809", size = 255341, upload-time = "2026-09-13T19:09:13.708Z" },
63
+ { url = "https://files.pythonhosted.org/packages/5d/6b/85ed86e82835a96ddfbe7705c387c28ce1cbbecd1b6d606f5ddfeeb712df/coverage-7.16.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e82e10b9d290f60b63459cfb245a841aec347603997206296b93881463a93dcf", size = 258078, upload-time = "2026-09-13T19:09:15.368Z" },
64
+ { url = "https://files.pythonhosted.org/packages/cd/d2/f66945853d850b9c05f4e012e37396f1f31d0cd40d062394b229c22e7b56/coverage-7.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d73bb1f85c4150ac208fb0755beb04b2e44897bad81414de9380f98dd74729f", size = 259191, upload-time = "2026-09-13T19:09:17.046Z" },
65
+ { url = "https://files.pythonhosted.org/packages/77/a1/7b907abe62461f289035c7ac60ab3e6334efb8c425151aac4abfa0c97820/coverage-7.16.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3397b9032553d281ad6a9253b12675b65e0cc8cd7a3b0633cf48872c9eb13360", size = 261452, upload-time = "2026-09-13T19:09:18.756Z" },
66
+ { url = "https://files.pythonhosted.org/packages/51/e6/e26f4d6b1069a2a2fee3238a132f85c5b4c1dc25aebd88e015451ff0bd68/coverage-7.16.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77890395cf37026a5907d3ad32376aa51f41c0f163b7477fdbd4f94966cc1d08", size = 255698, upload-time = "2026-09-13T19:09:20.786Z" },
67
+ { url = "https://files.pythonhosted.org/packages/7f/ec/99db7450e813050ebce300e31bbd4f997c76a8c6e3a6d168c5dffc104109/coverage-7.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b0944dc3bee3091039bf970d73caaf930c906013128a421bdc132e797494d941", size = 257111, upload-time = "2026-09-13T19:09:22.693Z" },
68
+ { url = "https://files.pythonhosted.org/packages/2e/9d/500df9d3cd8c541ac84b5e0bcab00646be75654e05aa34e8410ec851282d/coverage-7.16.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b89d22a89d5bc05dd95b64e08295b8394aa96dc88e08f8ba210c9ebfebbe0489", size = 255258, upload-time = "2026-09-13T19:09:24.429Z" },
69
+ { url = "https://files.pythonhosted.org/packages/c0/24/53318d96da332bb4946f8ac646fa19fd37fecbd0c49144735c716ac69bf0/coverage-7.16.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:550a2a1faf7559f13d5344f12d1eb886ad87955155d7dfab2a3fe5c8ec8fe776", size = 259326, upload-time = "2026-09-13T19:09:26.32Z" },
70
+ { url = "https://files.pythonhosted.org/packages/c5/ce/abc0462b2e6ae96ae22197d2bc30fe33b6b4e52937e782745436ceb2a761/coverage-7.16.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:55eb268e5b81aefac759766c9162625b06c1bedb7b77d936225bafc4f038a6f6", size = 254827, upload-time = "2026-09-13T19:09:28.191Z" },
71
+ { url = "https://files.pythonhosted.org/packages/8b/e1/0a04eedaaf19196b51f0180968134228c7646e469ed29914e48690a7cf3e/coverage-7.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65a8fc80898c9ce59f04349fe8b4849b1f9787f14e52ead990e5f849ff4727a0", size = 256698, upload-time = "2026-09-13T19:09:29.922Z" },
72
+ { url = "https://files.pythonhosted.org/packages/de/1d/d441a55cf22ce9d8e9e34814806c47441ab844bd534e0f4b64e1c6ae8bd1/coverage-7.16.1-cp312-cp312-win32.whl", hash = "sha256:528a61be40977c340cf201d23b69bd6a6bab507da60e9dbda85f8b30e935d70d", size = 225541, upload-time = "2026-09-13T19:09:31.752Z" },
73
+ { url = "https://files.pythonhosted.org/packages/73/27/ec3d032375735dd331477caa051419678079ff90fcb53d0284a6c2bfb757/coverage-7.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:d0f02c633630e2b74522108ee95a84ad6e1204a8016a6cca5297f335ea27147e", size = 226075, upload-time = "2026-09-13T19:09:33.556Z" },
74
+ { url = "https://files.pythonhosted.org/packages/94/00/90e9f5c4434878494306b9c0ee8068ebbd829483737d8cefccc58884d728/coverage-7.16.1-cp312-cp312-win_arm64.whl", hash = "sha256:2959978f9d1d20a2c0c15d0a68baaeccf615ac1aa214cf4a05a10d6f568926c8", size = 225461, upload-time = "2026-09-13T19:09:35.48Z" },
75
+ { url = "https://files.pythonhosted.org/packages/96/1a/d6d16babd0a5fe4c3fae40702158c570351694e74516d8d81b86c5637448/coverage-7.16.1-py3-none-any.whl", hash = "sha256:3d8bd4e58b6a5c2018d808f297905393c6c61da466a48c3f0596a76a4900ebe4", size = 215264, upload-time = "2026-09-13T19:12:18.895Z" },
76
+ ]
77
+
78
+ [[package]]
79
+ name = "filelock"
80
+ version = "4.0.3"
81
+ source = { registry = "https://pypi.org/simple" }
82
+ sdist = { url = "https://files.pythonhosted.org/packages/4f/b8/9ba8f569df649beb7058db5eb392a5f779bdbc3b82cf3942f0be439fb99e/filelock-4.0.3.tar.gz", hash = "sha256:87296d60478e14204fd9406e79831400fef76693bae2895deec236c98e87a8aa", size = 244309, upload-time = "2026-09-23T23:40:22.569Z" }
83
+ wheels = [
84
+ { url = "https://files.pythonhosted.org/packages/7a/6d/db8b86e168fef0cf904737e275bccb683558088ef971e92d3b41ce980699/filelock-4.0.3-py3-none-any.whl", hash = "sha256:30cd166e2aee2c7534ce2c33c6367c4cd051b8368e20e2c4eb0c34f699bacfab", size = 108444, upload-time = "2026-09-23T23:40:20.819Z" },
85
+ ]
86
+
87
+ [[package]]
88
+ name = "flatbuffers"
89
+ version = "25.12.19"
90
+ source = { registry = "https://pypi.org/simple" }
91
+ wheels = [
92
+ { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" },
93
+ ]
94
+
95
+ [[package]]
96
+ name = "fsspec"
97
+ version = "2026.9.0"
98
+ source = { registry = "https://pypi.org/simple" }
99
+ sdist = { url = "https://files.pythonhosted.org/packages/77/cd/9be253869fc42e764de7f3dedd6969af7d44ff9c3375214a3442a6f3fc08/fsspec-2026.9.0.tar.gz", hash = "sha256:0f08147951c8cb31d844c3547d631053b127863b60be04cf06e121333ee0e2fe", size = 333545, upload-time = "2026-09-18T17:50:42.825Z" }
100
+ wheels = [
101
+ { url = "https://files.pythonhosted.org/packages/6c/c0/a98505f18594f1bce828bb159cec0fcf9860562f1a2c85913409fc8f3d9e/fsspec-2026.9.0-py3-none-any.whl", hash = "sha256:8dd6e646e99ea382bd85f97a45e6b526a442d79423a7dc673f1e2756d05fcb5f", size = 221738, upload-time = "2026-09-18T17:50:41.341Z" },
102
+ ]
103
+
104
+ [[package]]
105
+ name = "h11"
106
+ version = "0.16.0"
107
+ source = { registry = "https://pypi.org/simple" }
108
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
109
+ wheels = [
110
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
111
+ ]
112
+
113
+ [[package]]
114
+ name = "hf-xet"
115
+ version = "1.6.0"
116
+ source = { registry = "https://pypi.org/simple" }
117
+ sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527, upload-time = "2026-08-03T22:33:13.243Z" }
118
+ wheels = [
119
+ { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729, upload-time = "2026-08-03T22:33:00.721Z" },
120
+ { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287, upload-time = "2026-08-03T22:33:02.251Z" },
121
+ { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663, upload-time = "2026-08-03T22:33:03.802Z" },
122
+ { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538, upload-time = "2026-08-03T22:33:05.287Z" },
123
+ { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520, upload-time = "2026-08-03T22:33:06.81Z" },
124
+ { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937, upload-time = "2026-08-03T22:33:08.616Z" },
125
+ { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128, upload-time = "2026-08-03T22:33:10.171Z" },
126
+ { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359, upload-time = "2026-08-03T22:33:11.725Z" },
127
+ ]
128
+
129
+ [[package]]
130
+ name = "httpcore"
131
+ version = "1.0.9"
132
+ source = { registry = "https://pypi.org/simple" }
133
+ dependencies = [
134
+ { name = "certifi" },
135
+ { name = "h11" },
136
+ ]
137
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
138
+ wheels = [
139
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
140
+ ]
141
+
142
+ [[package]]
143
+ name = "httpx"
144
+ version = "0.28.1"
145
+ source = { registry = "https://pypi.org/simple" }
146
+ dependencies = [
147
+ { name = "anyio" },
148
+ { name = "certifi" },
149
+ { name = "httpcore" },
150
+ { name = "idna" },
151
+ ]
152
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
153
+ wheels = [
154
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
155
+ ]
156
+
157
+ [[package]]
158
+ name = "huggingface-hub"
159
+ version = "1.5.0"
160
+ source = { registry = "https://pypi.org/simple" }
161
+ dependencies = [
162
+ { name = "filelock" },
163
+ { name = "fsspec" },
164
+ { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
165
+ { name = "httpx" },
166
+ { name = "packaging" },
167
+ { name = "pyyaml" },
168
+ { name = "tqdm" },
169
+ { name = "typer" },
170
+ { name = "typing-extensions" },
171
+ ]
172
+ sdist = { url = "https://files.pythonhosted.org/packages/ae/76/b5efb3033d8499b17f9386beaf60f64c461798e1ee16d10bc9c0077beba5/huggingface_hub-1.5.0.tar.gz", hash = "sha256:f281838db29265880fb543de7a23b0f81d3504675de82044307ea3c6c62f799d", size = 695872, upload-time = "2026-02-26T15:35:32.745Z" }
173
+ wheels = [
174
+ { url = "https://files.pythonhosted.org/packages/ec/74/2bc951622e2dbba1af9a460d93c51d15e458becd486e62c29cc0ccb08178/huggingface_hub-1.5.0-py3-none-any.whl", hash = "sha256:c9c0b3ab95a777fc91666111f3b3ede71c0cdced3614c553a64e98920585c4ee", size = 596261, upload-time = "2026-02-26T15:35:31.1Z" },
175
+ ]
176
+
177
+ [[package]]
178
+ name = "idna"
179
+ version = "3.20"
180
+ source = { registry = "https://pypi.org/simple" }
181
+ sdist = { url = "https://files.pythonhosted.org/packages/f5/08/8eea9d4b8302028f3abb2c0813953f7aec26d33b7a8960ed760e65ff29fa/idna-3.20.tar.gz", hash = "sha256:a7db850025b95ded1eae8a46181a1a6c56c92c96f0e2b005d9ff8dc0210cab44", size = 216463, upload-time = "2026-09-17T14:11:04.752Z" }
182
+ wheels = [
183
+ { url = "https://files.pythonhosted.org/packages/58/a2/bb081bab032533a855d44de1d56f8e8426114ff1ba5d1f07a438a0a654f8/idna-3.20-py3-none-any.whl", hash = "sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c", size = 69583, upload-time = "2026-09-17T14:11:03.168Z" },
184
+ ]
185
+
186
+ [[package]]
187
+ name = "iniconfig"
188
+ version = "2.3.0"
189
+ source = { registry = "https://pypi.org/simple" }
190
+ sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
191
+ wheels = [
192
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
193
+ ]
194
+
195
+ [[package]]
196
+ name = "jinja2"
197
+ version = "3.1.6"
198
+ source = { registry = "https://pypi.org/simple" }
199
+ dependencies = [
200
+ { name = "markupsafe" },
201
+ ]
202
+ sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
203
+ wheels = [
204
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
205
+ ]
206
+
207
+ [[package]]
208
+ name = "markdown-it-py"
209
+ version = "4.2.0"
210
+ source = { registry = "https://pypi.org/simple" }
211
+ dependencies = [
212
+ { name = "mdurl" },
213
+ ]
214
+ sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
215
+ wheels = [
216
+ { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
217
+ ]
218
+
219
+ [[package]]
220
+ name = "markupsafe"
221
+ version = "3.0.3"
222
+ source = { registry = "https://pypi.org/simple" }
223
+ sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
224
+ wheels = [
225
+ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
226
+ { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
227
+ { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
228
+ { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
229
+ { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
230
+ { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
231
+ { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
232
+ { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
233
+ { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
234
+ { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
235
+ { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
236
+ ]
237
+
238
+ [[package]]
239
+ name = "mdurl"
240
+ version = "0.1.2"
241
+ source = { registry = "https://pypi.org/simple" }
242
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
243
+ wheels = [
244
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
245
+ ]
246
+
247
+ [[package]]
248
+ name = "ml-dtypes"
249
+ version = "0.6.0"
250
+ source = { registry = "https://pypi.org/simple" }
251
+ dependencies = [
252
+ { name = "numpy" },
253
+ ]
254
+ sdist = { url = "https://files.pythonhosted.org/packages/12/72/307d7c4bd0600601c7133fba5cb78af7db968152951c1cd473abb1cda782/ml_dtypes-0.6.0.tar.gz", hash = "sha256:5e60251d32ced5598972e4d5e06a2f044341f9291402551a3f6f0ec44f9299b0", size = 3032327, upload-time = "2026-08-13T14:14:40.215Z" }
255
+ wheels = [
256
+ { url = "https://files.pythonhosted.org/packages/84/6a/441eb053b078954f7fea284dfb288701884d0a1404d39babb858e1649023/ml_dtypes-0.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5359c588cc62de6f78d7430f06b65853d884955494d86d6ad90b6dd64a3f3a08", size = 565447, upload-time = "2026-08-13T14:14:01.737Z" },
257
+ { url = "https://files.pythonhosted.org/packages/ed/cf/87e8a6c57eed63a91782a0d229856ddf73e138ce004dd71e2799a9dcdb33/ml_dtypes-0.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37da32aa97749251025666d62372775019594577b9c9e9cfda83bed48d778fdb", size = 360227, upload-time = "2026-08-13T14:14:02.938Z" },
258
+ { url = "https://files.pythonhosted.org/packages/c7/f9/7d76c1eae866f5d4636401b31b6d6dd90e4b4ced1fa7cfdfcca9c60e4bd3/ml_dtypes-0.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b4a480aa8fd54a1805b8ac10f3f91763926a74f73c0c364c10f9231854f4170", size = 409890, upload-time = "2026-08-13T14:14:04.248Z" },
259
+ { url = "https://files.pythonhosted.org/packages/ba/db/9c61ec2760b5cbfb1c6558d5c991a6d8fd3271053c32db20506a9a90272b/ml_dtypes-0.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a3e9d53925597fbffafd2a37048dadeddd0bdaba58058f6ae0869ed709a184d", size = 439333, upload-time = "2026-08-13T14:14:05.501Z" },
260
+ { url = "https://files.pythonhosted.org/packages/6a/57/780ca3e5ab135b9fbdd8e5441abf5f801b30398371b691291e05ab9834c0/ml_dtypes-0.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:6eaed129a4afe90694b8685e2f9b6294849f5eda4af9a15be83a4326eeebd775", size = 552268, upload-time = "2026-08-13T14:14:06.866Z" },
261
+ ]
262
+
263
+ [[package]]
264
+ name = "mpmath"
265
+ version = "1.3.0"
266
+ source = { registry = "https://pypi.org/simple" }
267
+ sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
268
+ wheels = [
269
+ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
270
+ ]
271
+
272
+ [[package]]
273
+ name = "networkx"
274
+ version = "3.7"
275
+ source = { registry = "https://pypi.org/simple" }
276
+ sdist = { url = "https://files.pythonhosted.org/packages/dc/76/3af777226b63a5e64a6b36b1ec5855c14e2b94a37096d4760e595fc43511/networkx-3.7.tar.gz", hash = "sha256:fd77a511bd90f39f3d016351345b52cf5319b813bdca01de3f755d3cca62e96a", size = 1866482, upload-time = "2026-09-21T16:45:16.974Z" }
277
+ wheels = [
278
+ { url = "https://files.pythonhosted.org/packages/7e/cd/fe58041e9011f307c490e3e17dd48cc516448f7c698a3f2d9d9d65d7e6a8/networkx-3.7-py3-none-any.whl", hash = "sha256:e3fd2c13a7814cee3746340d8d7f8598a67f16a58bf47fb7f8793fab6efca1b0", size = 2142205, upload-time = "2026-09-21T16:45:14.609Z" },
279
+ ]
280
+
281
+ [[package]]
282
+ name = "numpy"
283
+ version = "2.3.3"
284
+ source = { registry = "https://pypi.org/simple" }
285
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" }
286
+ wheels = [
287
+ { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" },
288
+ { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" },
289
+ { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" },
290
+ { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" },
291
+ { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" },
292
+ { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" },
293
+ { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" },
294
+ { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" },
295
+ { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" },
296
+ { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" },
297
+ { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" },
298
+ ]
299
+
300
+ [[package]]
301
+ name = "nvidia-cublas-cu12"
302
+ version = "12.8.4.1"
303
+ source = { registry = "https://pypi.org/simple" }
304
+ wheels = [
305
+ { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" },
306
+ ]
307
+
308
+ [[package]]
309
+ name = "nvidia-cuda-cupti-cu12"
310
+ version = "12.8.90"
311
+ source = { registry = "https://pypi.org/simple" }
312
+ wheels = [
313
+ { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" },
314
+ ]
315
+
316
+ [[package]]
317
+ name = "nvidia-cuda-nvrtc-cu12"
318
+ version = "12.8.93"
319
+ source = { registry = "https://pypi.org/simple" }
320
+ wheels = [
321
+ { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" },
322
+ ]
323
+
324
+ [[package]]
325
+ name = "nvidia-cuda-runtime-cu12"
326
+ version = "12.8.90"
327
+ source = { registry = "https://pypi.org/simple" }
328
+ wheels = [
329
+ { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" },
330
+ ]
331
+
332
+ [[package]]
333
+ name = "nvidia-cudnn-cu12"
334
+ version = "9.10.2.21"
335
+ source = { registry = "https://pypi.org/simple" }
336
+ dependencies = [
337
+ { name = "nvidia-cublas-cu12" },
338
+ ]
339
+ wheels = [
340
+ { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
341
+ ]
342
+
343
+ [[package]]
344
+ name = "nvidia-cufft-cu12"
345
+ version = "11.3.3.83"
346
+ source = { registry = "https://pypi.org/simple" }
347
+ dependencies = [
348
+ { name = "nvidia-nvjitlink-cu12" },
349
+ ]
350
+ wheels = [
351
+ { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
352
+ ]
353
+
354
+ [[package]]
355
+ name = "nvidia-cufile-cu12"
356
+ version = "1.13.1.3"
357
+ source = { registry = "https://pypi.org/simple" }
358
+ wheels = [
359
+ { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" },
360
+ ]
361
+
362
+ [[package]]
363
+ name = "nvidia-curand-cu12"
364
+ version = "10.3.9.90"
365
+ source = { registry = "https://pypi.org/simple" }
366
+ wheels = [
367
+ { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" },
368
+ ]
369
+
370
+ [[package]]
371
+ name = "nvidia-cusolver-cu12"
372
+ version = "11.7.3.90"
373
+ source = { registry = "https://pypi.org/simple" }
374
+ dependencies = [
375
+ { name = "nvidia-cublas-cu12" },
376
+ { name = "nvidia-cusparse-cu12" },
377
+ { name = "nvidia-nvjitlink-cu12" },
378
+ ]
379
+ wheels = [
380
+ { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
381
+ ]
382
+
383
+ [[package]]
384
+ name = "nvidia-cusparse-cu12"
385
+ version = "12.5.8.93"
386
+ source = { registry = "https://pypi.org/simple" }
387
+ dependencies = [
388
+ { name = "nvidia-nvjitlink-cu12" },
389
+ ]
390
+ wheels = [
391
+ { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
392
+ ]
393
+
394
+ [[package]]
395
+ name = "nvidia-cusparselt-cu12"
396
+ version = "0.7.1"
397
+ source = { registry = "https://pypi.org/simple" }
398
+ wheels = [
399
+ { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" },
400
+ ]
401
+
402
+ [[package]]
403
+ name = "nvidia-nccl-cu12"
404
+ version = "2.27.3"
405
+ source = { registry = "https://pypi.org/simple" }
406
+ wheels = [
407
+ { url = "https://files.pythonhosted.org/packages/5c/5b/4e4fff7bad39adf89f735f2bc87248c81db71205b62bcc0d5ca5b606b3c3/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039", size = 322364134, upload-time = "2025-06-03T21:58:04.013Z" },
408
+ ]
409
+
410
+ [[package]]
411
+ name = "nvidia-nvjitlink-cu12"
412
+ version = "12.8.93"
413
+ source = { registry = "https://pypi.org/simple" }
414
+ wheels = [
415
+ { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" },
416
+ ]
417
+
418
+ [[package]]
419
+ name = "nvidia-nvtx-cu12"
420
+ version = "12.8.90"
421
+ source = { registry = "https://pypi.org/simple" }
422
+ wheels = [
423
+ { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" },
424
+ ]
425
+
426
+ [[package]]
427
+ name = "onnx"
428
+ version = "1.19.0"
429
+ source = { registry = "https://pypi.org/simple" }
430
+ dependencies = [
431
+ { name = "ml-dtypes" },
432
+ { name = "numpy" },
433
+ { name = "protobuf" },
434
+ { name = "typing-extensions" },
435
+ ]
436
+ sdist = { url = "https://files.pythonhosted.org/packages/5b/bf/b0a63ee9f3759dcd177b28c6f2cb22f2aecc6d9b3efecaabc298883caa5f/onnx-1.19.0.tar.gz", hash = "sha256:aa3f70b60f54a29015e41639298ace06adf1dd6b023b9b30f1bca91bb0db9473", size = 11949859, upload-time = "2025-08-27T02:34:27.107Z" }
437
+ wheels = [
438
+ { url = "https://files.pythonhosted.org/packages/0d/94/f56f6ca5e2f921b28c0f0476705eab56486b279f04e1d568ed64c14e7764/onnx-1.19.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:61d94e6498ca636756f8f4ee2135708434601b2892b7c09536befb19bc8ca007", size = 18322331, upload-time = "2025-08-27T02:33:20.373Z" },
439
+ { url = "https://files.pythonhosted.org/packages/c8/00/8cc3f3c40b54b28f96923380f57c9176872e475face726f7d7a78bd74098/onnx-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:224473354462f005bae985c72028aaa5c85ab11de1b71d55b06fdadd64a667dd", size = 18027513, upload-time = "2025-08-27T02:33:23.44Z" },
440
+ { url = "https://files.pythonhosted.org/packages/61/90/17c4d2566fd0117a5e412688c9525f8950d467f477fbd574e6b32bc9cb8d/onnx-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae475c85c89bc4d1f16571006fd21a3e7c0e258dd2c091f6e8aafb083d1ed9b", size = 18202278, upload-time = "2025-08-27T02:33:26.103Z" },
441
+ { url = "https://files.pythonhosted.org/packages/bc/6e/a9383d9cf6db4ac761a129b081e9fa5d0cd89aad43cf1e3fc6285b915c7d/onnx-1.19.0-cp312-cp312-win32.whl", hash = "sha256:323f6a96383a9cdb3960396cffea0a922593d221f3929b17312781e9f9b7fb9f", size = 16333080, upload-time = "2025-08-27T02:33:28.559Z" },
442
+ { url = "https://files.pythonhosted.org/packages/a7/2e/3ff480a8c1fa7939662bdc973e41914add2d4a1f2b8572a3c39c2e4982e5/onnx-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:50220f3499a499b1a15e19451a678a58e22ad21b34edf2c844c6ef1d9febddc2", size = 16453927, upload-time = "2025-08-27T02:33:31.177Z" },
443
+ { url = "https://files.pythonhosted.org/packages/57/37/ad500945b1b5c154fe9d7b826b30816ebd629d10211ea82071b5bcc30aa4/onnx-1.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:efb768299580b786e21abe504e1652ae6189f0beed02ab087cd841cb4bb37e43", size = 16426022, upload-time = "2025-08-27T02:33:33.515Z" },
444
+ ]
445
+
446
+ [[package]]
447
+ name = "onnx-ir"
448
+ version = "0.1.3"
449
+ source = { registry = "https://pypi.org/simple" }
450
+ dependencies = [
451
+ { name = "ml-dtypes" },
452
+ { name = "numpy" },
453
+ { name = "onnx" },
454
+ { name = "typing-extensions" },
455
+ ]
456
+ sdist = { url = "https://files.pythonhosted.org/packages/1c/ad/00807b7b6887a9107f651d7d549dec9cb9d34f09884d326e39a05a5572e1/onnx_ir-0.1.3.tar.gz", hash = "sha256:239dab04addc5cadbf496b8d41caeac771f6791239718fd43cef3987b5d20305", size = 105791, upload-time = "2025-06-26T16:23:37.216Z" }
457
+ wheels = [
458
+ { url = "https://files.pythonhosted.org/packages/04/5b/e44d474d70e10b633f547692b1f6bbda29ccaae2eb4eb8ad73fc699aaed3/onnx_ir-0.1.3-py3-none-any.whl", hash = "sha256:d52f4316fa39a51bf6547cb0fed4a17811ea452023ef870ea6ba9f3eec1ff52c", size = 119195, upload-time = "2025-06-26T16:23:35.545Z" },
459
+ ]
460
+
461
+ [[package]]
462
+ name = "onnxruntime"
463
+ version = "1.30.0"
464
+ source = { registry = "https://pypi.org/simple" }
465
+ dependencies = [
466
+ { name = "flatbuffers" },
467
+ { name = "numpy" },
468
+ { name = "packaging" },
469
+ { name = "protobuf" },
470
+ ]
471
+ wheels = [
472
+ { url = "https://files.pythonhosted.org/packages/31/6f/48169f2e62b405bff5053cbd1d73fb5ce41ef7ecd13bb3bfcc191e689b8a/onnxruntime-1.30.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:001ed726c9bd5e2bc92faade7d37d889e9606a350b7d5529f0227df2e3bb57fd", size = 21544867, upload-time = "2026-09-10T16:31:19.876Z" },
473
+ { url = "https://files.pythonhosted.org/packages/16/bd/cbc5b8f91963689fdd622f463508c01d0aa95d3f944747b1e0b1eb2160b8/onnxruntime-1.30.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6c32a000d5139a38ba9349030b0032e3331acb559d596b22738d9d2b343a2b83", size = 21345202, upload-time = "2026-09-10T16:31:23.361Z" },
474
+ { url = "https://files.pythonhosted.org/packages/34/35/e7f862dbacbc99fadd9b14a614e49c99bf0f35fd9927a82f096e3de33531/onnxruntime-1.30.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:fa688e7891a6aa206636fe7372e27ee75fd17713289f6b4fc7b190e0a7de9328", size = 23585654, upload-time = "2026-09-10T16:31:26.65Z" },
475
+ { url = "https://files.pythonhosted.org/packages/a6/13/0f1699f6de549c9324bc9112a2a85b14c517904cd11b562a654643b755a1/onnxruntime-1.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3501472571f1b1eee50e017851e7929f5ea37312d2d8c2494a19e8fc58b4a38", size = 14311470, upload-time = "2026-09-10T16:31:31.273Z" },
476
+ { url = "https://files.pythonhosted.org/packages/a2/17/02b13e5f51461f0453b18ab854e2d0bc1b6ec353241b05a2ee6b79e35d87/onnxruntime-1.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:dc4c706f1935ebb62356e6a095b047859badd854482c40560888e95c328ed262", size = 14175072, upload-time = "2026-09-10T16:31:34.541Z" },
477
+ ]
478
+
479
+ [[package]]
480
+ name = "onnxruntime-genai"
481
+ version = "0.16.0"
482
+ source = { registry = "https://pypi.org/simple" }
483
+ dependencies = [
484
+ { name = "numpy" },
485
+ { name = "onnxruntime" },
486
+ ]
487
+ wheels = [
488
+ { url = "https://files.pythonhosted.org/packages/a0/ba/8ad8cdf62ee816e019a9b3f56bf2cb2a63629a9aeeb0cf9d1a256dc28d5d/onnxruntime_genai-0.16.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:1a861b2176424fa172795250bb0c2326be1340538706f454bffef7e70918702b", size = 5236371, upload-time = "2026-09-13T07:42:10.745Z" },
489
+ { url = "https://files.pythonhosted.org/packages/84/c5/36a3d6be3f4742bc1d658329196b7a791c55c5771230d3ac20e9415ad583/onnxruntime_genai-0.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:504b9ec876fe35d83f01b53816ee0091ca4be7c1673806947f20754643fb5f77", size = 11997330, upload-time = "2026-09-13T07:42:12.709Z" },
490
+ { url = "https://files.pythonhosted.org/packages/f5/6a/3732820f12d502f6fe094bb4ff0edb490fe72cf970bb62c07b803672bda4/onnxruntime_genai-0.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0fd61645bdffedc644cba9e02001082d28d5cbf92e705925ea1a769bfeba3d8c", size = 12359325, upload-time = "2026-09-13T07:42:14.875Z" },
491
+ { url = "https://files.pythonhosted.org/packages/8d/e3/2e79dea57d8e2dcdbfd985825d8044f994a174cf9cfe98b29ee3af407589/onnxruntime_genai-0.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:4427d8f3a6a9f1a77c1cdfb9efc1a9af518ab78bd70991db3eb9eae3ac7bdc56", size = 4128674, upload-time = "2026-09-13T07:42:16.987Z" },
492
+ { url = "https://files.pythonhosted.org/packages/4d/f4/1ed79731608b8e6dc52b6b25e92e0596750e30a04aebaa22c55913bbe3f5/onnxruntime_genai-0.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:f3bdf8a701e761c427449a68f88aa26de0b6fcfaf6084b605a5bb1ff7a623742", size = 3967050, upload-time = "2026-09-13T07:42:18.486Z" },
493
+ ]
494
+
495
+ [[package]]
496
+ name = "openthai-systemone"
497
+ version = "0.1.0"
498
+ source = { git = "https://github.com/iapp-technology/openthai-systemone.git?rev=5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf#5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf" }
499
+ dependencies = [
500
+ { name = "numpy" },
501
+ { name = "pydantic" },
502
+ { name = "torch" },
503
+ { name = "transformers" },
504
+ ]
505
+
506
+ [[package]]
507
+ name = "openthai-systemone-onnx"
508
+ version = "0.1.0"
509
+ source = { editable = "." }
510
+ dependencies = [
511
+ { name = "huggingface-hub" },
512
+ { name = "numpy" },
513
+ { name = "onnxruntime" },
514
+ { name = "openthai-systemone" },
515
+ { name = "torch" },
516
+ { name = "transformers" },
517
+ ]
518
+
519
+ [package.optional-dependencies]
520
+ dev = [
521
+ { name = "pytest" },
522
+ { name = "pytest-cov" },
523
+ ]
524
+ tools = [
525
+ { name = "onnx" },
526
+ { name = "onnx-ir" },
527
+ { name = "onnxruntime-genai" },
528
+ { name = "safetensors" },
529
+ ]
530
+
531
+ [package.metadata]
532
+ requires-dist = [
533
+ { name = "huggingface-hub", specifier = "==1.5.0" },
534
+ { name = "numpy", specifier = "==2.3.3" },
535
+ { name = "onnx", marker = "extra == 'tools'", specifier = "==1.19.0" },
536
+ { name = "onnx-ir", marker = "extra == 'tools'", specifier = "==0.1.3" },
537
+ { name = "onnxruntime", specifier = "==1.30.0" },
538
+ { name = "onnxruntime-genai", marker = "extra == 'tools'", specifier = "==0.16.0" },
539
+ { name = "openthai-systemone", git = "https://github.com/iapp-technology/openthai-systemone.git?rev=5d04bcca0c58bd10e7dac2d3d369d8f760bea6cf" },
540
+ { name = "pytest", marker = "extra == 'dev'", specifier = "==8.4.2" },
541
+ { name = "pytest-cov", marker = "extra == 'dev'", specifier = "==7.0.0" },
542
+ { name = "safetensors", marker = "extra == 'tools'", specifier = "==0.8.0" },
543
+ { name = "torch", specifier = "==2.8.0" },
544
+ { name = "transformers", git = "https://github.com/huggingface/transformers.git?rev=856157a2f3e9594954310df18fdccc31ffddebe9" },
545
+ ]
546
+ provides-extras = ["tools", "dev"]
547
+
548
+ [[package]]
549
+ name = "packaging"
550
+ version = "26.3"
551
+ source = { registry = "https://pypi.org/simple" }
552
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
553
+ wheels = [
554
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
555
+ ]
556
+
557
+ [[package]]
558
+ name = "pluggy"
559
+ version = "1.6.0"
560
+ source = { registry = "https://pypi.org/simple" }
561
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
562
+ wheels = [
563
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
564
+ ]
565
+
566
+ [[package]]
567
+ name = "protobuf"
568
+ version = "7.36.2"
569
+ source = { registry = "https://pypi.org/simple" }
570
+ sdist = { url = "https://files.pythonhosted.org/packages/d9/89/5b8517baa72f84a67b8a307ba953c91057af618bf40bf676f3c03551f8f0/protobuf-7.36.2.tar.gz", hash = "sha256:497d0463ff3316681da6c0b9e8d06cb465d61abce00b613ab42226175644d1bb", size = 512737, upload-time = "2026-09-17T20:07:59.326Z" }
571
+ wheels = [
572
+ { url = "https://files.pythonhosted.org/packages/32/72/98342feb672507c8f3a69e34b4fa8961f608edba5c1a48a6f47156d92cb5/protobuf-7.36.2-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:cbc70b17ee27e28894c7fee8bb04be1abead49e936bc70eb60052531eee2079e", size = 456039, upload-time = "2026-09-17T20:07:51.542Z" },
573
+ { url = "https://files.pythonhosted.org/packages/b6/ea/91fdf7c2b8bbd49cde056f00a9df6773532987e1c00fe2830b895af95c7e/protobuf-7.36.2-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:e11e1f0180583a2af89db6a2ecd9e8dc40aa6d2988ca175bfd0e6d12ea72d74e", size = 344219, upload-time = "2026-09-17T20:07:52.914Z" },
574
+ { url = "https://files.pythonhosted.org/packages/17/ab/5fd5f8ece73fad885c5a09aa849b32d70472f954ba3a92d3bb5974ea953b/protobuf-7.36.2-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:f4fee11ec330d238b34a05c9b675f693c20415d1c5bd7d5320cc2f8a798eb9cf", size = 357223, upload-time = "2026-09-17T20:07:53.985Z" },
575
+ { url = "https://files.pythonhosted.org/packages/db/f3/3996583dd2906297a637af12114deddf7658af6e683fedb83be061983fb5/protobuf-7.36.2-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:89f23aa53c24553a2416fd4fd1ec06f74fa42b14b546d8883128813f775bbfd2", size = 343223, upload-time = "2026-09-17T20:07:54.931Z" },
576
+ { url = "https://files.pythonhosted.org/packages/fc/1b/dcc64f358fcb51811b58ae40b3d28f820725f116d86487cc20bd4b130701/protobuf-7.36.2-cp310-abi3-win32.whl", hash = "sha256:912c1221170e16c08d1f086762f563dd61ff83c18b5fa6652952dfaded66f728", size = 442998, upload-time = "2026-09-17T20:07:55.826Z" },
577
+ { url = "https://files.pythonhosted.org/packages/8a/55/b77bda4e5e5f5971fb51b07663694690e9afdb9402136c16a522bd621cad/protobuf-7.36.2-cp310-abi3-win_amd64.whl", hash = "sha256:a300819d441e078a5608c0d3c709796bb548136058fda017ae51d425b44fd353", size = 456514, upload-time = "2026-09-17T20:07:57.188Z" },
578
+ { url = "https://files.pythonhosted.org/packages/e4/04/d52c7016b04b6c5108f26691f9d33ec82a9b65d041f1a9c771137693d618/protobuf-7.36.2-py3-none-any.whl", hash = "sha256:bdb3a345d48db958e6ce1f18e508beb0cc981d64f24088427549c866cd039f1e", size = 179806, upload-time = "2026-09-17T20:07:58.211Z" },
579
+ ]
580
+
581
+ [[package]]
582
+ name = "pydantic"
583
+ version = "2.13.5"
584
+ source = { registry = "https://pypi.org/simple" }
585
+ dependencies = [
586
+ { name = "annotated-types" },
587
+ { name = "pydantic-core" },
588
+ { name = "typing-extensions" },
589
+ { name = "typing-inspection" },
590
+ ]
591
+ sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" }
592
+ wheels = [
593
+ { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" },
594
+ ]
595
+
596
+ [[package]]
597
+ name = "pydantic-core"
598
+ version = "2.46.5"
599
+ source = { registry = "https://pypi.org/simple" }
600
+ dependencies = [
601
+ { name = "typing-extensions" },
602
+ ]
603
+ sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" }
604
+ wheels = [
605
+ { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" },
606
+ { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" },
607
+ { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" },
608
+ { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" },
609
+ { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" },
610
+ { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" },
611
+ { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" },
612
+ { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" },
613
+ { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" },
614
+ { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" },
615
+ { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" },
616
+ { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" },
617
+ { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" },
618
+ { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" },
619
+ { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" },
620
+ { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" },
621
+ { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" },
622
+ { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" },
623
+ { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" },
624
+ ]
625
+
626
+ [[package]]
627
+ name = "pygments"
628
+ version = "2.21.0"
629
+ source = { registry = "https://pypi.org/simple" }
630
+ sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
631
+ wheels = [
632
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
633
+ ]
634
+
635
+ [[package]]
636
+ name = "pytest"
637
+ version = "8.4.2"
638
+ source = { registry = "https://pypi.org/simple" }
639
+ dependencies = [
640
+ { name = "colorama", marker = "sys_platform == 'win32'" },
641
+ { name = "iniconfig" },
642
+ { name = "packaging" },
643
+ { name = "pluggy" },
644
+ { name = "pygments" },
645
+ ]
646
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
647
+ wheels = [
648
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
649
+ ]
650
+
651
+ [[package]]
652
+ name = "pytest-cov"
653
+ version = "7.0.0"
654
+ source = { registry = "https://pypi.org/simple" }
655
+ dependencies = [
656
+ { name = "coverage" },
657
+ { name = "pluggy" },
658
+ { name = "pytest" },
659
+ ]
660
+ sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
661
+ wheels = [
662
+ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
663
+ ]
664
+
665
+ [[package]]
666
+ name = "pyyaml"
667
+ version = "6.0.3"
668
+ source = { registry = "https://pypi.org/simple" }
669
+ sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
670
+ wheels = [
671
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
672
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
673
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
674
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
675
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
676
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
677
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
678
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
679
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
680
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
681
+ ]
682
+
683
+ [[package]]
684
+ name = "regex"
685
+ version = "2026.9.10"
686
+ source = { registry = "https://pypi.org/simple" }
687
+ sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072, upload-time = "2026-09-09T21:00:21.521Z" }
688
+ wheels = [
689
+ { url = "https://files.pythonhosted.org/packages/32/c8/bfbe893e90ee0148bd2860dd086f09b5d2080ca2b125f740c2e118c16982/regex-2026.9.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db", size = 496609, upload-time = "2026-09-09T20:57:09.987Z" },
690
+ { url = "https://files.pythonhosted.org/packages/8c/ac/56d5ae6efb759255c3b3db650a4be25f96a844ea3613b91e1e189a3b7294/regex-2026.9.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a", size = 297024, upload-time = "2026-09-09T20:57:11.35Z" },
691
+ { url = "https://files.pythonhosted.org/packages/60/4b/0f2d5f6bbb791cc10f22f0ed16c487e630dde8fa8fa0bd92a2bfe21a4b20/regex-2026.9.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb", size = 291905, upload-time = "2026-09-09T20:57:13.127Z" },
692
+ { url = "https://files.pythonhosted.org/packages/89/51/3fb5fe0d32f4cf0bc982286722c729a8d6f522d2fa2d5d14a702d9fc87f8/regex-2026.9.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1", size = 800055, upload-time = "2026-09-09T20:57:14.68Z" },
693
+ { url = "https://files.pythonhosted.org/packages/89/46/ee507bd2f9d4420f26a594b35c551d7194b66f5d7897f63730fae6ec05c1/regex-2026.9.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4", size = 871133, upload-time = "2026-09-09T20:57:16.674Z" },
694
+ { url = "https://files.pythonhosted.org/packages/d1/75/cbaa90689684f91b1bc017e7f8c6d9425c6bd299108db02482dc51376d8a/regex-2026.9.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd", size = 919627, upload-time = "2026-09-09T20:57:18.402Z" },
695
+ { url = "https://files.pythonhosted.org/packages/cb/f5/dcdf5e0d898024005cfcce631e3e934d111dfbe177ca0b7f253ae8a735a2/regex-2026.9.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0", size = 804587, upload-time = "2026-09-09T20:57:19.859Z" },
696
+ { url = "https://files.pythonhosted.org/packages/73/70/eedfe81c29bae266a06ab4250978361a9bccd474704d88d4f4ef4506dff8/regex-2026.9.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3", size = 777320, upload-time = "2026-09-09T20:57:21.62Z" },
697
+ { url = "https://files.pythonhosted.org/packages/ca/e5/86b207077efbcd91305700488f170b7eb1e1c54721cea74175273aa3b9a4/regex-2026.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8", size = 790572, upload-time = "2026-09-09T20:57:23.048Z" },
698
+ { url = "https://files.pythonhosted.org/packages/b2/92/f622c3b2323f4c035b98e80221740a442127ad7993135b814f52057430db/regex-2026.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23", size = 865485, upload-time = "2026-09-09T20:57:24.658Z" },
699
+ { url = "https://files.pythonhosted.org/packages/4a/be/34bd621d3d6ac906ad67e57ed56c40cd45f7d51b9c0328335e97a7cb8ecb/regex-2026.9.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944", size = 767925, upload-time = "2026-09-09T20:57:26.268Z" },
700
+ { url = "https://files.pythonhosted.org/packages/0d/28/ddbf7cba86f2adf5038c6c16aa829636ffc6e437f81bb0cbf302899cea5e/regex-2026.9.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0", size = 858800, upload-time = "2026-09-09T20:57:27.901Z" },
701
+ { url = "https://files.pythonhosted.org/packages/d0/f1/e8d7656ff3d3bd32e881d32f540b4981c79dee61908d6b790a45966e6895/regex-2026.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1", size = 791648, upload-time = "2026-09-09T20:57:29.786Z" },
702
+ { url = "https://files.pythonhosted.org/packages/fc/65/eac1a79115c8475d8ff539602eb54031564d719fdea5a599c4b0a1a26d1b/regex-2026.9.10-cp312-cp312-win32.whl", hash = "sha256:239620b0e0681669367c0e218c8eb2551d9f8fe3b9fccfc8d0003377804e8348", size = 267326, upload-time = "2026-09-09T20:57:31.782Z" },
703
+ { url = "https://files.pythonhosted.org/packages/ad/d4/4dcd0a05d3e97ca829165df1c39717f899d1d484dac8a6032439f2cb8d6d/regex-2026.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4db7d00c4afbfbb55b8e17b1e371da11418ea9389b030acec63c1fa4c7ad4b86", size = 277933, upload-time = "2026-09-09T20:57:33.648Z" },
704
+ { url = "https://files.pythonhosted.org/packages/55/f8/22617a80dee28f2451011eae36bc26b3d78c4994ba87b5281d60acf9b6c0/regex-2026.9.10-cp312-cp312-win_arm64.whl", hash = "sha256:c25a754bb81a2edcfc3b65eda50f017d736f818112ed43e8aafd595cb00678ae", size = 277447, upload-time = "2026-09-09T20:57:35.156Z" },
705
+ ]
706
+
707
+ [[package]]
708
+ name = "rich"
709
+ version = "15.0.0"
710
+ source = { registry = "https://pypi.org/simple" }
711
+ dependencies = [
712
+ { name = "markdown-it-py" },
713
+ { name = "pygments" },
714
+ ]
715
+ sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
716
+ wheels = [
717
+ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
718
+ ]
719
+
720
+ [[package]]
721
+ name = "safetensors"
722
+ version = "0.8.0"
723
+ source = { registry = "https://pypi.org/simple" }
724
+ sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" }
725
+ wheels = [
726
+ { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" },
727
+ { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" },
728
+ { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" },
729
+ { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" },
730
+ { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" },
731
+ { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" },
732
+ { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" },
733
+ { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" },
734
+ { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" },
735
+ { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" },
736
+ { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" },
737
+ { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" },
738
+ { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" },
739
+ { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" },
740
+ { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" },
741
+ { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" },
742
+ ]
743
+
744
+ [[package]]
745
+ name = "setuptools"
746
+ version = "84.0.0"
747
+ source = { registry = "https://pypi.org/simple" }
748
+ sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" }
749
+ wheels = [
750
+ { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" },
751
+ ]
752
+
753
+ [[package]]
754
+ name = "shellingham"
755
+ version = "1.5.4"
756
+ source = { registry = "https://pypi.org/simple" }
757
+ sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
758
+ wheels = [
759
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
760
+ ]
761
+
762
+ [[package]]
763
+ name = "sympy"
764
+ version = "1.14.0"
765
+ source = { registry = "https://pypi.org/simple" }
766
+ dependencies = [
767
+ { name = "mpmath" },
768
+ ]
769
+ sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
770
+ wheels = [
771
+ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
772
+ ]
773
+
774
+ [[package]]
775
+ name = "tokenizers"
776
+ version = "0.23.2"
777
+ source = { registry = "https://pypi.org/simple" }
778
+ dependencies = [
779
+ { name = "huggingface-hub" },
780
+ ]
781
+ sdist = { url = "https://files.pythonhosted.org/packages/18/1e/bc6587c5ab643b2e17776cace9070a2ae73549c86bffac9934a600bf3c31/tokenizers-0.23.2.tar.gz", hash = "sha256:7f0f085686b9de0d0079e6f874ae053600db64c5d13049e0bbc0119926d25aac", size = 385745, upload-time = "2026-09-03T08:55:42.89Z" }
782
+ wheels = [
783
+ { url = "https://files.pythonhosted.org/packages/4d/ed/8a443528baa6fac8dfe8c3b75b038c63ac92bb539bcabe311e227c718173/tokenizers-0.23.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:85a9a357a3764aecc904ee76bdaf8cf1ad8e5a67a1b929a487c4a39b49ed0e90", size = 3148852, upload-time = "2026-09-03T08:55:30.874Z" },
784
+ { url = "https://files.pythonhosted.org/packages/67/49/22da045a91732384d3a3771816bf188dc5a1f702c32e635afa7c679c0bef/tokenizers-0.23.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:986670e43691469dcee610ea0f846f91a8f84e91fc6f7a48d4c064414c0ec2bf", size = 3101593, upload-time = "2026-09-03T08:55:28.587Z" },
785
+ { url = "https://files.pythonhosted.org/packages/2e/4d/8f569ed49372a3ed8e57099bd515055fd48d7c95912c4307cda6973c2168/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a37039b5dfc4af84eb3ef0a92f4307e28936c8f9adccba2629d36f652e9bf7a2", size = 3516830, upload-time = "2026-09-03T08:55:14.741Z" },
786
+ { url = "https://files.pythonhosted.org/packages/2a/de/e2f14c8919d5bf51874051d00d6c7b7e0e8bde6c6a2dbeddda7f642896ff/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b7e37ba198f24150f523e1242e83c4970de4a525480586be5dcc24d9add32c5", size = 3407975, upload-time = "2026-09-03T08:55:16.842Z" },
787
+ { url = "https://files.pythonhosted.org/packages/c5/bd/93c69152d02ef06ce47aed8b2bf4952dcf733c935a62791873932b2934d9/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43e4f2071e3cc8d5d86421c874aebc82659bb51a68bcdef5a0da75ee89511ccb", size = 3748165, upload-time = "2026-09-03T08:55:24.769Z" },
788
+ { url = "https://files.pythonhosted.org/packages/2d/b7/56b84b80bc96942bba8eb23751a9e8a1fce4faaf4390425e7083f721c98c/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:325fee2e0418a9dc6c9ecf736a5f5f0db7875183ace9549ae339da76f7a1fbb7", size = 4024165, upload-time = "2026-09-03T08:55:18.806Z" },
789
+ { url = "https://files.pythonhosted.org/packages/9b/8a/0175e216f005c2fe08238292663aa41e4c802b216e71047a69a0e9fc6fa3/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:950d7c9426fa72406a0ffeacdbc0bb9985f5db20eb8b263f29c79aaf83105703", size = 3591899, upload-time = "2026-09-03T08:55:22.752Z" },
790
+ { url = "https://files.pythonhosted.org/packages/2c/ca/ca6b93c7820df123b2662a9469e8facc826ccc94e98fdd0d615f6431e73a/tokenizers-0.23.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41c2f84d172449b4dadb9cdc508e3e364076613c35b16e76ecfe47a60d1e3305", size = 3386843, upload-time = "2026-09-03T08:55:26.584Z" },
791
+ { url = "https://files.pythonhosted.org/packages/e9/a4/4f9106d317b14a80aefea9f0e3a8d07ef25f856a7607eb7f5ab894281fcb/tokenizers-0.23.2-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:12f0835dc2ee694746a76adf7b1567d4346a4a502ebe93fb1f5f80ea49799b78", size = 3577314, upload-time = "2026-09-03T08:55:20.825Z" },
792
+ { url = "https://files.pythonhosted.org/packages/8d/6a/1552b70fb0d9ab074fd3fc961435d01364e79c9058481822c3af6e8d402c/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb2f9c8a24da020ea8c11a01a19c1c2547912d92121ae4a01cfbca46125dee40", size = 9967367, upload-time = "2026-09-03T08:55:33.188Z" },
793
+ { url = "https://files.pythonhosted.org/packages/06/01/3ccb3a956c7528b2507b8a9714155c4baf86af593039db6ea375dd0c96c3/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f486f402f6f9abee5bb032553736813af0c710a86b2e0ca592634c55cea1f835", size = 9811886, upload-time = "2026-09-03T08:55:35.642Z" },
794
+ { url = "https://files.pythonhosted.org/packages/fa/73/7038e612d48bda1599457f712f6bd3854eae1a9dc9c13aa47f835349db48/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:bef235815a067b2648caf6dcc7a71091b0b0fff9ee8057f6451eb9335fae52ef", size = 10146224, upload-time = "2026-09-03T08:55:38.391Z" },
795
+ { url = "https://files.pythonhosted.org/packages/b5/d8/8e9e4e0b287a338d8f88976729628c9d22e8a54cfaf9777018a7f7cb58a0/tokenizers-0.23.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5c56bda1511921587789163e524d196ed8284174ac23abd7685d5ea8da6c4718", size = 10256304, upload-time = "2026-09-03T08:55:40.977Z" },
796
+ { url = "https://files.pythonhosted.org/packages/f3/1f/c79a01f671a49728ebb0b61f7ff9ea45663b66cab40bc0858e9859b25c16/tokenizers-0.23.2-cp310-abi3-win32.whl", hash = "sha256:debf978920d93ba9c219bd67cc4bbfaf912c9039e41e7a28b91ec15e3728c95a", size = 2592809, upload-time = "2026-09-03T08:55:48.02Z" },
797
+ { url = "https://files.pythonhosted.org/packages/db/f7/0a69ac6b82dbccf3f71add938a161c497952749294b8dd6dfe03a819dc40/tokenizers-0.23.2-cp310-abi3-win_amd64.whl", hash = "sha256:2e96f5699d5249c9c64aa8412e044f727aae3a4098cf830f9901ec1afc361cde", size = 2863236, upload-time = "2026-09-03T08:55:46.193Z" },
798
+ { url = "https://files.pythonhosted.org/packages/d7/b0/dee84cb44175be1b4c35bd2f770727494e78f0bb38e571a623ade94dbebb/tokenizers-0.23.2-cp310-abi3-win_arm64.whl", hash = "sha256:e49c394456dd9985787fec76132438ba3fb8911f857b1bf3d40119f9292d41aa", size = 2729352, upload-time = "2026-09-03T08:55:44.345Z" },
799
+ ]
800
+
801
+ [[package]]
802
+ name = "torch"
803
+ version = "2.8.0"
804
+ source = { registry = "https://pypi.org/simple" }
805
+ dependencies = [
806
+ { name = "filelock" },
807
+ { name = "fsspec" },
808
+ { name = "jinja2" },
809
+ { name = "networkx" },
810
+ { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
811
+ { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
812
+ { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
813
+ { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
814
+ { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
815
+ { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
816
+ { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
817
+ { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
818
+ { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
819
+ { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
820
+ { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
821
+ { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
822
+ { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
823
+ { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
824
+ { name = "setuptools" },
825
+ { name = "sympy" },
826
+ { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
827
+ { name = "typing-extensions" },
828
+ ]
829
+ wheels = [
830
+ { url = "https://files.pythonhosted.org/packages/49/0c/2fd4df0d83a495bb5e54dca4474c4ec5f9c62db185421563deeb5dabf609/torch-2.8.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2fab4153768d433f8ed9279c8133a114a034a61e77a3a104dcdf54388838705", size = 101906089, upload-time = "2025-08-06T14:53:52.631Z" },
831
+ { url = "https://files.pythonhosted.org/packages/99/a8/6acf48d48838fb8fe480597d98a0668c2beb02ee4755cc136de92a0a956f/torch-2.8.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2aca0939fb7e4d842561febbd4ffda67a8e958ff725c1c27e244e85e982173c", size = 887913624, upload-time = "2025-08-06T14:56:44.33Z" },
832
+ { url = "https://files.pythonhosted.org/packages/af/8a/5c87f08e3abd825c7dfecef5a0f1d9aa5df5dd0e3fd1fa2f490a8e512402/torch-2.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:2f4ac52f0130275d7517b03a33d2493bab3693c83dcfadf4f81688ea82147d2e", size = 241326087, upload-time = "2025-08-06T14:53:46.503Z" },
833
+ { url = "https://files.pythonhosted.org/packages/be/66/5c9a321b325aaecb92d4d1855421e3a055abd77903b7dab6575ca07796db/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:619c2869db3ada2c0105487ba21b5008defcc472d23f8b80ed91ac4a380283b0", size = 73630478, upload-time = "2025-08-06T14:53:57.144Z" },
834
+ ]
835
+
836
+ [[package]]
837
+ name = "tqdm"
838
+ version = "4.70.1"
839
+ source = { registry = "https://pypi.org/simple" }
840
+ dependencies = [
841
+ { name = "colorama", marker = "sys_platform == 'win32'" },
842
+ ]
843
+ sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846, upload-time = "2026-09-11T07:25:16.601Z" }
844
+ wheels = [
845
+ { url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" },
846
+ ]
847
+
848
+ [[package]]
849
+ name = "transformers"
850
+ version = "5.17.0"
851
+ source = { git = "https://github.com/huggingface/transformers.git?rev=856157a2f3e9594954310df18fdccc31ffddebe9#856157a2f3e9594954310df18fdccc31ffddebe9" }
852
+ dependencies = [
853
+ { name = "huggingface-hub" },
854
+ { name = "numpy" },
855
+ { name = "packaging" },
856
+ { name = "pyyaml" },
857
+ { name = "regex" },
858
+ { name = "safetensors" },
859
+ { name = "tokenizers" },
860
+ { name = "tqdm" },
861
+ { name = "typer" },
862
+ ]
863
+
864
+ [[package]]
865
+ name = "triton"
866
+ version = "3.4.0"
867
+ source = { registry = "https://pypi.org/simple" }
868
+ dependencies = [
869
+ { name = "setuptools" },
870
+ ]
871
+ wheels = [
872
+ { url = "https://files.pythonhosted.org/packages/d0/66/b1eb52839f563623d185f0927eb3530ee4d5ffe9d377cdaf5346b306689e/triton-3.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31c1d84a5c0ec2c0f8e8a072d7fd150cab84a9c239eaddc6706c081bfae4eb04", size = 155560068, upload-time = "2025-07-30T19:58:37.081Z" },
873
+ ]
874
+
875
+ [[package]]
876
+ name = "typer"
877
+ version = "0.27.2"
878
+ source = { registry = "https://pypi.org/simple" }
879
+ dependencies = [
880
+ { name = "annotated-doc" },
881
+ { name = "colorama", marker = "sys_platform == 'win32'" },
882
+ { name = "rich" },
883
+ { name = "shellingham" },
884
+ ]
885
+ sdist = { url = "https://files.pythonhosted.org/packages/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" }
886
+ wheels = [
887
+ { url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" },
888
+ ]
889
+
890
+ [[package]]
891
+ name = "typing-extensions"
892
+ version = "4.16.0"
893
+ source = { registry = "https://pypi.org/simple" }
894
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
895
+ wheels = [
896
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
897
+ ]
898
+
899
+ [[package]]
900
+ name = "typing-inspection"
901
+ version = "0.4.4"
902
+ source = { registry = "https://pypi.org/simple" }
903
+ dependencies = [
904
+ { name = "typing-extensions" },
905
+ ]
906
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
907
+ wheels = [
908
+ { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
909
+ ]
validation-report.json ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format_version": 1,
3
+ "lengths_above_65536": "structural validation only",
4
+ "numerical_parity_certified_through_tokens": 65536,
5
+ "source_revision": "f3709948b5e3cc9606a57e74ba62b7a639d17dd3",
6
+ "stages": {
7
+ "client_compatibility": {
8
+ "command": [
9
+ "/Users/imtk/Desktop/OpenThaiONNX-Codex/.venv/bin/python3",
10
+ "-m",
11
+ "pytest",
12
+ "-q",
13
+ "tests/test_client_contract.py",
14
+ "tests/test_export_builder.py",
15
+ "tests/test_validation.py",
16
+ "--junitxml=/Users/imtk/Desktop/OpenThaiONNX-Codex/artifacts/OpenThai-SystemOne-ONNX/.client_compatibility.junit.xml"
17
+ ],
18
+ "completed_at": "2026-09-25T07:17:29.243609+00:00",
19
+ "failure_hint": null,
20
+ "passed": true,
21
+ "returncode": 0,
22
+ "test_counts": {
23
+ "errors": 0,
24
+ "failures": 0,
25
+ "skipped": 0,
26
+ "tests": 16
27
+ }
28
+ },
29
+ "normal_parity": {
30
+ "command": [
31
+ "/Users/imtk/Desktop/OpenThaiONNX-Codex/.venv/bin/python3",
32
+ "-m",
33
+ "pytest",
34
+ "-q",
35
+ "tests/test_parity.py",
36
+ "-m",
37
+ "parity and not slow",
38
+ "--junitxml=/Users/imtk/Desktop/OpenThaiONNX-Codex/artifacts/OpenThai-SystemOne-ONNX/.normal_parity.junit.xml"
39
+ ],
40
+ "completed_at": "2026-09-25T07:19:30.694251+00:00",
41
+ "failure_hint": null,
42
+ "passed": true,
43
+ "returncode": 0,
44
+ "test_counts": {
45
+ "errors": 0,
46
+ "failures": 0,
47
+ "skipped": 0,
48
+ "tests": 9
49
+ }
50
+ },
51
+ "slow_65536_parity": {
52
+ "command": [
53
+ "/Users/imtk/Desktop/OpenThaiONNX-Codex/.venv/bin/python3",
54
+ "-m",
55
+ "pytest",
56
+ "-q",
57
+ "tests/test_parity.py",
58
+ "-m",
59
+ "slow",
60
+ "--junitxml=/Users/imtk/Desktop/OpenThaiONNX-Codex/artifacts/OpenThai-SystemOne-ONNX/.slow_65536_parity.junit.xml"
61
+ ],
62
+ "completed_at": "2026-09-25T07:59:48.172858+00:00",
63
+ "failure_hint": null,
64
+ "passed": true,
65
+ "returncode": 0,
66
+ "test_counts": {
67
+ "errors": 0,
68
+ "failures": 0,
69
+ "skipped": 0,
70
+ "tests": 1
71
+ }
72
+ },
73
+ "structural_262144": {
74
+ "command": [
75
+ "/Users/imtk/Desktop/OpenThaiONNX-Codex/.venv/bin/python3",
76
+ "-m",
77
+ "pytest",
78
+ "-q",
79
+ "tests/test_structure.py",
80
+ "--junitxml=/Users/imtk/Desktop/OpenThaiONNX-Codex/artifacts/OpenThai-SystemOne-ONNX/.structural_262144.junit.xml"
81
+ ],
82
+ "completed_at": "2026-09-25T07:19:46.848105+00:00",
83
+ "failure_hint": null,
84
+ "passed": true,
85
+ "returncode": 0,
86
+ "test_counts": {
87
+ "errors": 0,
88
+ "failures": 0,
89
+ "skipped": 0,
90
+ "tests": 3
91
+ }
92
+ }
93
+ },
94
+ "upload_gate": {
95
+ "passed": true,
96
+ "required_stages": [
97
+ "client_compatibility",
98
+ "normal_parity",
99
+ "slow_65536_parity",
100
+ "structural_262144"
101
+ ]
102
+ }
103
+ }